diff --git a/ovos_workshop/decorators/__init__.py b/ovos_workshop/decorators/__init__.py
index e42d8029..3dbc8432 100644
--- a/ovos_workshop/decorators/__init__.py
+++ b/ovos_workshop/decorators/__init__.py
@@ -1,5 +1,5 @@
 from functools import wraps
-
+from typing import Optional
 from ovos_utils.log import log_deprecation
 
 from ovos_workshop.decorators.killable import killable_intent, killable_event
@@ -157,3 +157,23 @@ def real_decorator(func):
         return func
 
     return real_decorator
+
+
+def homescreen_app(icon: str, name: Optional[str] = None):
+    """
+    Decorator for adding a method as a homescreen app
+
+    the icon file MUST be located under 'gui' subfolder
+
+    @param icon: icon file to use in app drawer (relative to "gui" folder)
+    @param name: short name to show under the icon in app drawer
+    """
+
+    def real_decorator(func):
+        # Store the icon inside the function
+        # This will be used later to call register_homescreen_app
+        func.homescreen_app_icon = icon
+        func.homescreen_app_name = name
+        return func
+
+    return real_decorator
diff --git a/ovos_workshop/res/text/en/skill.error.dialog b/ovos_workshop/res/text/en/skill.error.dialog
new file mode 100644
index 00000000..03f10b6e
--- /dev/null
+++ b/ovos_workshop/res/text/en/skill.error.dialog
@@ -0,0 +1 @@
+An error occurred while processing a request in {skill}
\ No newline at end of file
diff --git a/ovos_workshop/resource_files.py b/ovos_workshop/resource_files.py
index 4bdd42a7..20e777f0 100644
--- a/ovos_workshop/resource_files.py
+++ b/ovos_workshop/resource_files.py
@@ -13,12 +13,14 @@
 # limitations under the License.
 #
 """Handling of skill data such as intents and regular expressions."""
+import abc
+import json
 import re
 from collections import namedtuple
 from os import walk
 from os.path import dirname
 from pathlib import Path
-from typing import List, Optional, Tuple
+from typing import List, Optional, Tuple, Dict, Any
 
 from langcodes import tag_distance
 from ovos_config.config import Configuration
@@ -40,7 +42,8 @@
         "template",
         "vocabulary",
         "word",
-        "qml"
+        "qml",
+        "json"
     ]
 )
 
@@ -56,8 +59,7 @@ def locate_base_directories(skill_directory: str,
     """
     base_dirs = [Path(skill_directory, resource_subdirectory)] if \
         resource_subdirectory else []
-    base_dirs += [Path(skill_directory, "locale"),
-                  Path(skill_directory, "text")]
+    base_dirs += [Path(skill_directory, "locale")]
     candidates = []
     for directory in base_dirs:
         if directory.exists():
@@ -76,8 +78,7 @@ def locate_lang_directories(lang: str, skill_directory: str,
     @param resource_subdirectory: optional extra resource directory to prepend
     @return: list of existing skill resource directories for the given lang
     """
-    base_dirs = [Path(skill_directory, "locale"),
-                 Path(skill_directory, "text")]
+    base_dirs = [Path(skill_directory, "locale")]
     if resource_subdirectory:
         base_dirs.append(Path(skill_directory, resource_subdirectory))
     candidates = []
@@ -100,41 +101,6 @@ def locate_lang_directories(lang: str, skill_directory: str,
     return [c[0] for c in candidates]
 
 
-def resolve_resource_file(res_name: str) -> Optional[str]:
-    """Convert a resource into an absolute filename.
-
-    Resource names are in the form: 'filename.ext'
-    or 'path/filename.ext'
-
-    The system wil look for $XDG_DATA_DIRS/mycroft/res_name first
-    (defaults to ~/.local/share/mycroft/res_name), and if not found will
-    look at /opt/mycroft/res_name, then finally it will look for res_name
-    in the 'mycroft/res' folder of the source code package.
-
-    Example:
-        With mycroft running as the user 'bob', if you called
-        ``resolve_resource_file('snd/beep.wav')``
-        it would return either:
-        '$XDG_DATA_DIRS/mycroft/beep.wav',
-        '/home/bob/.mycroft/snd/beep.wav' or
-        '/opt/mycroft/snd/beep.wav' or
-        '.../mycroft/res/snd/beep.wav'
-        where the '...' is replaced by the path
-        where the package has been installed.
-
-    Args:
-        res_name (str): a resource path/name
-
-    Returns:
-        (str) path to resource or None if no resource found
-    """
-    log_deprecation(f"This method has moved to `ovos_utils.file_utils`",
-                    "0.1.0")
-    from ovos_utils.file_utils import resolve_resource_file
-    config = Configuration()
-    return resolve_resource_file(res_name, config=config)
-
-
 def find_resource(res_name: str, root_dir: str, res_dirname: str,
                   lang: Optional[str] = None) -> Optional[Path]:
     """
@@ -254,12 +220,13 @@ def locate_base_directory(self, skill_directory: str) -> Optional[str]:
             return
 
         # check for lang resources shipped by the skill
-        possible_directories = (
-            Path(skill_directory, "locale", self.language),
-            Path(skill_directory, resource_subdirectory, self.language),
-            Path(skill_directory, resource_subdirectory),
-            Path(skill_directory, "text", self.language),
-        )
+        possible_directories = [Path(skill_directory, "locale", self.language)]
+        if resource_subdirectory:
+            possible_directories += [
+                Path(skill_directory, resource_subdirectory, self.language),
+                Path(skill_directory, resource_subdirectory)
+            ]
+
         for directory in possible_directories:
             if directory.exists():
                 self.base_directory = directory
@@ -279,7 +246,7 @@ def locate_base_directory(self, skill_directory: str) -> Optional[str]:
         if self.user_directory:
             self.base_directory = self.user_directory
 
-    def _get_resource_subdirectory(self) -> str:
+    def _get_resource_subdirectory(self) -> Optional[str]:
         """Returns the subdirectory for this resource type.
 
         In the older directory schemes, several resource types were stored
@@ -295,10 +262,10 @@ def _get_resource_subdirectory(self) -> str:
             template="dialog",
             vocab="vocab",
             word="dialog",
-            qml="ui"
+            qml="gui"
         )
 
-        return subdirectories[self.resource_type]
+        return subdirectories.get(self.resource_type)
 
 
 class ResourceFile:
@@ -315,14 +282,13 @@ def __init__(self, resource_type: ResourceType, resource_name: str):
         self.resource_name = resource_name
         self.file_path = self._locate()
 
-    def _locate(self) -> str:
+    def _locate(self) -> Optional[str]:
         """Locates a resource file in the skill's locale directory.
 
         A skill's locale directory can contain a subdirectory structure defined
         by the skill author.  Walk the directory and any subdirectories to
         find the resource file.
         """
-        from ovos_utils.file_utils import resolve_resource_file
         file_path = None
         if self.resource_name.endswith(self.resource_type.file_extension):
             file_name = self.resource_name
@@ -345,22 +311,12 @@ def _locate(self) -> str:
                 if file_name in file_names:
                     file_path = Path(directory, file_name)
 
-        # check the core resources
-        if file_path is None and self.resource_type.language:
-            sub_path = Path("text", self.resource_type.language, file_name)
-            file_path = resolve_resource_file(str(sub_path),
-                                              config=Configuration())
-
-        # check non-lang specific core resources
         if file_path is None:
-            file_path = resolve_resource_file(file_name,
-                                              config=Configuration())
-
-        if file_path is None:
-            LOG.error(f"Could not find resource file {file_name}")
+            LOG.debug(f"Could not find resource file {file_name} for lang: {self.resource_type.language}")
 
         return file_path
 
+    @abc.abstractmethod
     def load(self):
         """Override in subclass to define resource type loading behavior."""
         pass
@@ -377,7 +333,6 @@ def _read(self) -> str:
 class QmlFile(ResourceFile):
     def _locate(self):
         """ QML files are special because we do not want to walk the directory """
-        from ovos_utils.file_utils import resolve_resource_file
         file_path = None
         if self.resource_name.endswith(self.resource_type.file_extension):
             file_name = self.resource_name
@@ -398,13 +353,6 @@ def _locate(self):
                 if x.is_file() and file_name == x.name:
                     file_path = Path(self.resource_type.base_directory, file_name)
 
-        # check the core resources
-        if file_path is None:
-            file_path = resolve_resource_file(file_name,
-                                              config=Configuration()) or \
-                        resolve_resource_file(f"ui/{file_name}",
-                                              config=Configuration())
-
         if file_path is None:
             LOG.error(f"Could not find resource file {file_name}")
 
@@ -414,6 +362,17 @@ def load(self):
         return str(self.file_path)
 
 
+class JsonFile(ResourceFile):
+    def load(self) -> Dict[str, Any]:
+        if self.file_path is not None:
+            try:
+                with open(self.file_path) as f:
+                    return json.load(f)
+            except Exception as e:
+                LOG.error(f"Failed to load {self.file_path}: {e}")
+        return {}
+
+
 class DialogFile(ResourceFile):
     """Defines a dialog file, which is used instruct TTS what to speak."""
 
@@ -474,6 +433,7 @@ def load(self) -> List[List[str]]:
 
 class IntentFile(ResourceFile):
     """Defines an intent file, which skill use to form intents."""
+
     def __init__(self, resource_type, resource_name):
         super().__init__(resource_type, resource_name)
         self.data = None
@@ -646,7 +606,8 @@ def _define_resource_types(self) -> SkillResourceTypes:
             template=ResourceType("template", ".template", self.language),
             vocabulary=ResourceType("vocab", ".voc", self.language),
             word=ResourceType("word", ".word", self.language),
-            qml=ResourceType("qml", ".qml")
+            qml=ResourceType("qml", ".qml"),
+            json=ResourceType("json", ".json", self.language)
         )
         for resource_type in resource_types.values():
             if self.skill_id:
@@ -654,6 +615,10 @@ def _define_resource_types(self) -> SkillResourceTypes:
             resource_type.locate_base_directory(self.skill_directory)
         return SkillResourceTypes(**resource_types)
 
+    def load_json_file(self, name: str = "skill.json") -> Dict[str, str]:
+        jsonf = JsonFile(self.types.json, name)
+        return jsonf.load()
+
     def load_dialog_file(self, name: str,
                          data: Optional[dict] = None) -> List[str]:
         """
@@ -671,10 +636,10 @@ def load_dialog_file(self, name: str,
         dialog_file = DialogFile(self.types.dialog, name)
         dialog_file.data = data
         return dialog_file.load()
-    
+
     def load_intent_file(self, name: str,
-                               data: Optional[dict] = None,
-                               entities: bool = True) -> List[str]:
+                         data: Optional[dict] = None,
+                         entities: bool = True) -> List[str]:
         """
         Loads the contents of an intent file.
 
@@ -858,7 +823,7 @@ def load_skill_regex(self, alphanumeric_skill_id: str) -> List[str]:
         )
 
         return skill_regexes
-    
+
     @classmethod
     def get_available_languages(cls, skill_directory: str) -> List[str]:
         """
@@ -885,22 +850,22 @@ def get_inventory(self, specific_type: str = "", language: str = "en-us"):
         if language not in languages:
             raise ValueError(f"Language {language} not available for skill")
 
-        inventory = dict()        
+        inventory = dict()
         for type_ in self.types:
             if specific_type and type_.resource_type != specific_type:
                 continue
 
             inventory[type_.resource_type] = list()
-            
+
             # search all files in the directory and subdirectories and dump its name in a list
             base_dirs = locate_lang_directories(language, self.skill_directory)
             for directory in base_dirs:
                 for file in directory.iterdir():
                     if file.suffix == type_.file_extension:
                         inventory[type_.resource_type].append(file.stem)
-        
+
         inventory["languages"] = languages
-        
+
         return inventory
 
     @staticmethod
diff --git a/ovos_workshop/settings.py b/ovos_workshop/settings.py
index ca826806..2a6c15c7 100644
--- a/ovos_workshop/settings.py
+++ b/ovos_workshop/settings.py
@@ -116,7 +116,7 @@ def save_meta(self, generate: bool = False):
     @requires_backend
     def upload(self, generate: bool = False):
         if not is_paired():
-            LOG.error("Device needs to be paired to upload settings")
+            LOG.debug("Device needs to be paired to upload settings")
             return
         self.remote_settings.settings = dict(self.skill.settings)
         if generate:
@@ -126,7 +126,7 @@ def upload(self, generate: bool = False):
     @requires_backend
     def upload_meta(self, generate: bool = False):
         if not is_paired():
-            LOG.error("Device needs to be paired to upload settingsmeta")
+            LOG.debug("Device needs to be paired to upload settingsmeta")
             return
         if generate:
             self.remote_settings.settings = dict(self.skill.settings)
@@ -136,7 +136,7 @@ def upload_meta(self, generate: bool = False):
     @requires_backend
     def download(self):
         if not is_paired():
-            LOG.error("Device needs to be paired to download remote settings")
+            LOG.debug("Device needs to be paired to download remote settings")
             return
         self.remote_settings.download()
         # we do not update skill object settings directly
diff --git a/ovos_workshop/skills/ovos.py b/ovos_workshop/skills/ovos.py
index c96c9ddb..3d452082 100644
--- a/ovos_workshop/skills/ovos.py
+++ b/ovos_workshop/skills/ovos.py
@@ -2,6 +2,7 @@
 import json
 import os
 import re
+import shutil
 import sys
 import time
 import traceback
@@ -16,8 +17,6 @@
 import binascii
 from json_database import JsonStorage
 from langcodes import closest_match
-from ovos_number_parser import pronounce_number, extract_number
-from ovos_yes_no_solver import YesNoSolver
 from ovos_bus_client import MessageBusClient
 from ovos_bus_client.apis.enclosure import EnclosureAPI
 from ovos_bus_client.apis.gui import GUIInterface
@@ -26,10 +25,12 @@
 from ovos_bus_client.session import SessionManager, Session
 from ovos_bus_client.util import get_message_lang
 from ovos_config.config import Configuration
+from ovos_config.locations import get_xdg_cache_save_path
 from ovos_config.locations import get_xdg_config_save_path
+from ovos_number_parser import pronounce_number, extract_number
 from ovos_plugin_manager.language import OVOSLangTranslationFactory, OVOSLangDetectionFactory
 from ovos_utils import camel_case_split, classproperty
-from ovos_utils.dialog import get_dialog, MustacheDialogRenderer
+from ovos_utils.dialog import MustacheDialogRenderer
 from ovos_utils.events import EventContainer, EventSchedulerInterface
 from ovos_utils.events import get_handler_name, create_wrapper
 from ovos_utils.file_utils import FileWatcher
@@ -38,9 +39,12 @@
 from ovos_utils.lang import standardize_lang_tag
 from ovos_utils.log import LOG
 from ovos_utils.parse import match_one
-from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap, ProcessState
+from ovos_utils.process_utils import ProcessStatus, StatusCallbackMap
 from ovos_utils.process_utils import RuntimeRequirements
 from ovos_utils.skills import get_non_properties
+from ovos_yes_no_solver import YesNoSolver
+from padacioso import IntentContainer
+
 from ovos_workshop.decorators.killable import AbortEvent, killable_event, \
     AbortQuestion
 from ovos_workshop.decorators.layers import IntentLayers
@@ -51,7 +55,6 @@
     CoreResources, find_resource, SkillResources
 from ovos_workshop.settings import PrivateSettings
 from ovos_workshop.settings import SkillSettingsManager
-from padacioso import IntentContainer
 
 
 def simple_trace(stack_trace: List[str]) -> str:
@@ -811,7 +814,9 @@ def _startup(self, bus: MessageBusClient, skill_id: str = ""):
             if self._enable_settings_manager:
                 self._init_settings_manager()
             self.load_data_files()
+            self._register_skill_json()
             self._register_decorated()
+            self._register_app_launcher()
             self.register_resting_screen()
 
             self.status.set_started()
@@ -829,6 +834,42 @@ def _startup(self, bus: MessageBusClient, skill_id: str = ""):
                 LOG.debug(e2)
             raise e
 
+    def _register_skill_json(self, root_directory: Optional[str] = None):
+        """Load skill.json metadata found under locale folder and register with homescreen"""
+        root_directory = root_directory or self.res_dir
+        for lang in self.native_langs:
+            resources = self.load_lang(root_directory, lang)
+            if resources.types.json.base_directory is None:
+                self.log.debug(f'No skill.json loaded for {lang}')
+            else:
+                skill_meta = resources.load_json_file("skill.json")
+                utts = skill_meta.get("examples", [])
+                if utts:
+                    self.log.info(f"Registering example utterances with homescreen for lang: {lang} - {utts}")
+                    self.bus.emit(Message("homescreen.register.examples",
+                                          {"skill_id": self.skill_id, "utterances": utts, "lang": lang}))
+
+    def _register_app_launcher(self):
+        # register app launcher if registered via decorator
+        for attr_name in get_non_properties(self):
+            method = getattr(self, attr_name)
+            if hasattr(method, 'homescreen_app_icon'):
+                name = getattr(method, 'homescreen_app_name')
+                event = f"{self.skill_id}.{name or method.__name__}.homescreen.app"
+                icon = getattr(method, 'homescreen_app_icon')
+                name = name or self.__skill_id2name
+                LOG.debug(f"homescreen app registered: {name} - '{event}'")
+                self.register_homescreen_app(icon=icon,
+                                             name=name or self.skill_id,
+                                             event=event)
+                self.add_event(event, method, speak_errors=False)
+
+    @property
+    def __skill_id2name(self) -> str:
+        """helper to make a nice string out of a skill_id"""
+        return (self.skill_id.split(".")[0].replace("_", " ").
+                replace("-", " ").replace("skill", "").title().strip())
+
     def _init_settings(self):
         """
         Set up skill settings. Defines settings in the specified file path,
@@ -853,7 +894,7 @@ def _init_settings(self):
 
         # starting on ovos-core 0.0.8 a bus event is emitted
         # all settings.json files are monitored for changes in ovos-core
-        self.add_event("ovos.skills.settings_changed", self._handle_settings_changed)
+        self.add_event("ovos.skills.settings_changed", self._handle_settings_changed, speak_errors=False)
 
         if self._monitor_own_settings:
             self._start_filewatcher()
@@ -882,6 +923,23 @@ def _init_settings_manager(self):
         """
         self.settings_manager = SkillSettingsManager(self)
 
+    def register_homescreen_app(self, icon: str, name: str, event: str):
+        """the icon file MUST be located under 'gui' subfolder"""
+        # this path is hardcoded in ovos_gui.constants and follows XDG spec
+        # we use it to ensure resource availability between containers
+        # it is the only path assured to be accessible both by skills and GUI
+        GUI_CACHE_PATH = get_xdg_cache_save_path('ovos_gui')
+
+        full_icon_path = f"{self.root_dir}/gui/{icon}"
+        shared_path = f"{GUI_CACHE_PATH}/{self.skill_id}/{icon}"
+        shutil.copy(full_icon_path, shared_path)
+
+        self.bus.emit(Message("homescreen.register.app",
+                              {"skill_id": self.skill_id,
+                               "icon": shared_path,
+                               "name": name,
+                               "event": event}))
+
     def register_resting_screen(self):
         """
         Registers resting screen from the resting_screen_handler decorator.
@@ -889,30 +947,27 @@ def register_resting_screen(self):
         This only allows one screen and if two is registered only one
         will be used.
         """
-        resting_name = None
         for attr_name in get_non_properties(self):
             handler = getattr(self, attr_name)
             if hasattr(handler, 'resting_handler'):
                 resting_name = handler.resting_handler
+                LOG.debug(f"{get_handler_name(handler)} is a resting screen, name: {resting_name}")
 
-                def register(message=None):
-                    self.log.info(f'Registering resting screen {resting_name} for {self.skill_id}.')
+                def register(message=None, name=resting_name):
+                    self.log.info(f'Registering resting screen {name} for {self.skill_id}.')
                     self.bus.emit(Message("homescreen.manager.add",
-                                          {"class": "IdleDisplaySkill",  # TODO - rm in ovos-gui, only for compat
-                                           "name": resting_name,
-                                           "id": self.skill_id}))
+                                          {"name": name, "id": self.skill_id}))
 
                 register()  # initial registering
 
-                self.add_event("homescreen.manager.reload.list", register,
-                               speak_errors=False)
+                self.add_event("homescreen.manager.reload.list", register, speak_errors=False)
 
-                def wrapper(message):
+                def wrapper(message, cb=handler):
                     if message.data["homescreen_id"] == self.skill_id:
-                        handler(message)
+                        LOG.debug(f"triggering resting_handler: {get_handler_name(cb)}")
+                        cb(message)
 
-                self.add_event("homescreen.manager.activate.display", wrapper,
-                               speak_errors=False)
+                self.add_event("homescreen.manager.activate.display", wrapper, speak_errors=False)
 
                 def shutdown_handler(message):
                     if message.data["id"] == self.skill_id:
@@ -920,14 +975,8 @@ def shutdown_handler(message):
                                               {"id": self.skill_id})
                         self.bus.emit(msg)
 
-                self.add_event("mycroft.skills.shutdown", shutdown_handler,
-                               speak_errors=False)
-
-                # backwards compat listener
-                self.add_event(f'{self.skill_id}.idle', handler,
-                               speak_errors=False)
-
-                return
+                self.add_event("mycroft.skills.shutdown", shutdown_handler, speak_errors=False)
+                break  # TODO - if multiple decorators are used what do? this is not deterministic
 
     def _start_filewatcher(self):
         """
@@ -976,11 +1025,9 @@ def _upload_settings(self):
         """
         Upload settings to a remote backend if configured.
         """
-        if self.settings_manager and self.config_core.get("skills",
-                                                          {}).get("sync2way"):
+        if self.settings_manager and self.config_core.get("skills", {}).get("sync2way"):
             # upload new settings to backend
-            generate = self.config_core.get("skills", {}).get("autogen_meta",
-                                                              True)
+            generate = self.config_core.get("skills", {}).get("autogen_meta", True)
             # this will check global sync flag
             self.settings_manager.upload(generate)
             if generate:
@@ -1058,31 +1105,22 @@ def _register_system_event_handlers(self):
         self.add_event(f"{self.skill_id}.stop", self._handle_session_stop, speak_errors=False)
         self.add_event(f"{self.skill_id}.stop.ping", self._handle_stop_ack, speak_errors=False)
 
-        self.add_event(f"{self.skill_id}.converse.ping", self._handle_converse_ack,
-                       speak_errors=False)
-        self.add_event(f"{self.skill_id}.converse.request", self._handle_converse_request,
-                       speak_errors=False)
-        self.add_event(f"{self.skill_id}.activate", self.handle_activate,
-                       speak_errors=False)
-        self.add_event(f"{self.skill_id}.deactivate", self.handle_deactivate,
-                       speak_errors=False)
-        self.add_event("intent.service.skills.deactivated",
-                       self._handle_skill_deactivated, speak_errors=False)
-        self.add_event("intent.service.skills.activated",
-                       self._handle_skill_activated, speak_errors=False)
-        self.add_event('mycroft.skill.enable_intent', self.handle_enable_intent,
-                       speak_errors=False)
-        self.add_event('mycroft.skill.disable_intent',
-                       self.handle_disable_intent, speak_errors=False)
-        self.add_event('mycroft.skill.set_cross_context',
-                       self.handle_set_cross_context, speak_errors=False)
-        self.add_event('mycroft.skill.remove_cross_context',
-                       self.handle_remove_cross_context, speak_errors=False)
-        self.add_event('mycroft.skills.settings.changed',
-                       self.handle_settings_change, speak_errors=False)
-
-        self.add_event(f"{self.skill_id}.converse.get_response", self.__handle_get_response,
-                       speak_errors=False)
+        self.add_event(f"{self.skill_id}.converse.ping", self._handle_converse_ack, speak_errors=False)
+        self.add_event(f"{self.skill_id}.converse.request", self._handle_converse_request, speak_errors=False)
+        self.add_event(f"{self.skill_id}.activate", self.handle_activate, speak_errors=False)
+        self.add_event(f"{self.skill_id}.deactivate", self.handle_deactivate, speak_errors=False)
+        self.add_event("intent.service.skills.deactivated", self._handle_skill_deactivated, speak_errors=False)
+        self.add_event("intent.service.skills.activated", self._handle_skill_activated, speak_errors=False)
+        self.add_event('mycroft.skill.enable_intent', self.handle_enable_intent, speak_errors=False)
+        self.add_event('mycroft.skill.disable_intent', self.handle_disable_intent, speak_errors=False)
+        self.add_event('mycroft.skill.set_cross_context', self.handle_set_cross_context, speak_errors=False)
+        self.add_event('mycroft.skill.remove_cross_context', self.handle_remove_cross_context, speak_errors=False)
+        self.add_event('mycroft.skills.settings.changed', self.handle_settings_change, speak_errors=False)
+
+        self.add_event(f"{self.skill_id}.converse.get_response", self.__handle_get_response, speak_errors=False)
+
+        # homescreen might load after this skill and miss the original events
+        self.add_event("homescreen.metadata.get", self.handle_homescreen_loaded, speak_errors=False)
 
     def _send_public_api(self, message: Message):
         """
@@ -1464,6 +1502,11 @@ def register_regex(self, regex_str: str, lang: Optional[str] = None):
         self.intent_service.register_adapt_regex(regex, lang=standardize_lang_tag(lang or self.lang))
 
     # event/intent registering internal handlers
+    def handle_homescreen_loaded(self, message: Message):
+        """homescreen loaded, we should re-register any metadata we want to provide"""
+        self._register_skill_json()
+        self._register_app_launcher()
+
     def handle_enable_intent(self, message: Message):
         """
         Listener to enable a registered intent if it belongs to this skill.
@@ -1554,7 +1597,7 @@ def _on_event_error(self, error: str, message: Message, handler_info: str,
         # Convert "MyFancySkill" to "My Fancy Skill" for speaking
         handler_name = camel_case_split(self.name)
         msg_data = {'skill': handler_name}
-        speech = get_dialog('skill.error', self.lang, msg_data)
+        speech = _get_dialog('skill.error', self.lang, msg_data)
         if speak_errors:
             self.speak(speech)
         self.log.exception(error)
@@ -1671,7 +1714,7 @@ def speak_dialog(self, key: str, data: Optional[dict] = None,
                 expect_response, wait, meta={'dialog': key, 'data': data}
             )
         else:
-            self.log.warning(
+            self.log.error(
                 'dialog_render is None, does the locale/dialog folder exist?'
             )
             self.speak(key, expect_response, wait, {})
@@ -2044,7 +2087,8 @@ def ask_selection(self, options: List[str], dialog: str = '',
         Returns:
               string: list element selected by user, or None
         """
-        assert isinstance(options, list)
+        if not isinstance(options, list):
+            raise ValueError("invalid value for 'options', must be a list of strings")
 
         if not len(options):
             return None
@@ -2164,7 +2208,7 @@ def add_event(self, name: str, handler: callable,
         Create event handler for executing intent or other event.
 
         Args:
-            name (string): IntentParser name
+            name (string): event name
             handler (func): Method to call
             handler_info (string): Base message when reporting skill event
                                    handler status on messagebus.
@@ -2183,7 +2227,8 @@ def on_error(error, message):
                 self._on_event_end(message, handler_info, skill_data,
                                    is_intent=is_intent)
                 return
-            self._on_event_error(error, message, handler_info, skill_data,
+            LOG.error(f"Error handling event '{name}' : {error}")
+            self._on_event_error(str(error), message, handler_info, skill_data,
                                  speak_errors)
 
         def on_start(message):
@@ -2496,6 +2541,33 @@ def __init__(self, skill: OVOSSkill):
                               ui_directories=ui_directories)
 
 
+def _get_dialog(phrase: str, lang: str, context: Optional[dict] = None) -> str:
+    """
+    Looks up a resource file for the given phrase in the specified language.
+
+    Meant only for resources bundled with ovos-workshop and shared across skills
+
+    Args:
+        phrase (str): resource phrase to retrieve/translate
+        lang (str): the language to use
+        context (dict): values to be inserted into the string
+
+    Returns:
+        str: a randomized and/or translated version of the phrase
+    """
+    filename = f"{dirname(dirname(__file__))}/res/text/{lang.split('-')[0]}/{phrase}.dialog"
+
+    if not isfile(filename):
+        LOG.debug('Resource file not found: {}'.format(filename))
+        return phrase
+
+    stache = MustacheDialogRenderer()
+    stache.load_template_file('template', filename)
+    if not context:
+        context = {}
+    return stache.render('template', context)
+
+
 def _get_word(lang, connector):
     """ Helper to get word translations
 
diff --git a/test/unittests/test_resource_files.py b/test/unittests/test_resource_files.py
index e2ae7148..da25ef71 100644
--- a/test/unittests/test_resource_files.py
+++ b/test/unittests/test_resource_files.py
@@ -15,10 +15,6 @@ def test_locate_lang_directories(self):
         from ovos_workshop.resource_files import locate_lang_directories
         # TODO
 
-    def test_resolve_resource_file(self):
-        from ovos_workshop.resource_files import resolve_resource_file
-        # TODO
-
     def test_find_resource(self):
         from ovos_workshop.resource_files import find_resource
         test_dir = join(dirname(__file__), "test_res")