From 33e8907d4fb7e4e492482ac476e22726ed1f8a6f Mon Sep 17 00:00:00 2001
From: dragonwocky <thedragonring.bod@gmail.com>
Date: Sat, 2 Oct 2021 12:31:07 +1000
Subject: [PATCH] replace hook with pseudo-mod, env folder = simpler
 documentation

---
 extension/api/_.mjs                           | 17 +++--
 extension/api/components/_.mjs                | 21 ++++++
 .../components}/sidebar.css                   |  0
 extension/api/components/sidebar.mjs          | 24 +++++++
 .../components}/tooltip.css                   |  0
 extension/api/components/tooltip.mjs          | 41 ++++++++++++
 extension/api/env.mjs                         |  2 +-
 extension/api/fs.mjs                          | 46 +++++++++++++
 extension/api/registry-validation.mjs         | 16 -----
 extension/api/storage.mjs                     | 67 +++++++++++++++++++
 extension/env.mjs                             | 24 -------
 extension/env/env.mjs                         | 44 ++++++++++++
 .../{api/extension-fs.mjs => env/fs.mjs}      |  0
 .../extension-storage.mjs => env/storage.mjs} |  0
 extension/launcher.js                         | 30 +++------
 extension/manifest.json                       |  2 +-
 .../hook.mjs                                  | 38 -----------
 .../mod.json                                  | 17 ++---
 .../blocks.mjs                                |  4 +-
 .../loader.mjs                                | 32 ---------
 .../menu.mjs                                  | 15 ++++-
 .../notifications.mjs                         |  3 +-
 .../router.mjs                                |  3 +-
 23 files changed, 284 insertions(+), 162 deletions(-)
 create mode 100644 extension/api/components/_.mjs
 rename extension/{repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d => api/components}/sidebar.css (100%)
 create mode 100644 extension/api/components/sidebar.mjs
 rename extension/{repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d => api/components}/tooltip.css (100%)
 create mode 100644 extension/api/components/tooltip.mjs
 create mode 100644 extension/api/fs.mjs
 create mode 100644 extension/api/storage.mjs
 delete mode 100644 extension/env.mjs
 create mode 100644 extension/env/env.mjs
 rename extension/{api/extension-fs.mjs => env/fs.mjs} (100%)
 rename extension/{api/extension-storage.mjs => env/storage.mjs} (100%)
 delete mode 100644 extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/hook.mjs
 delete mode 100644 extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/loader.mjs

diff --git a/extension/api/_.mjs b/extension/api/_.mjs
index dc8ce08..c1a108d 100644
--- a/extension/api/_.mjs
+++ b/extension/api/_.mjs
@@ -9,18 +9,17 @@
 /** @module notion-enhancer/api */
 
 /** environment-specific methods and constants */
-import * as env from './env.mjs';
-
+export * as env from './env.mjs';
 /** environment-specific filesystem reading */
-const fs = env.name === 'extension' ? await import('./extension-fs.mjs') : {};
+export * as fs from './fs.mjs';
 /** environment-specific data persistence */
-const storage = env.name === 'extension' ? await import('./extension-storage.mjs') : {};
+export * as storage from './storage.mjs';
 
 /** helpers for formatting, validating and parsing values */
-import * as fmt from './fmt.mjs';
+export * as fmt from './fmt.mjs';
 /** interactions with the enhancer's repository of mods */
-import * as registry from './registry.mjs';
+export * as registry from './registry.mjs';
 /** helpers for manipulation of a webpage */
-import * as web from './web.mjs';
-
-export { env, fs, storage, fmt, registry, web };
+export * as web from './web.mjs';
+/** notion-style elements inc. the sidebar */
+export * as components from './components/_.mjs';
diff --git a/extension/api/components/_.mjs b/extension/api/components/_.mjs
new file mode 100644
index 0000000..63e8daa
--- /dev/null
+++ b/extension/api/components/_.mjs
@@ -0,0 +1,21 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * notion-style elements inc. the sidebar
+ * @module notion-enhancer/api/components
+ */
+
+/**
+ * add a tooltip to show extra information on hover
+ * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered
+ * @param {string} text - the markdown content of the tooltip
+ */
+export { tooltip } from './tooltip.mjs';
+
+export { side } from './tooltip.mjs';
diff --git a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/sidebar.css b/extension/api/components/sidebar.css
similarity index 100%
rename from extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/sidebar.css
rename to extension/api/components/sidebar.css
diff --git a/extension/api/components/sidebar.mjs b/extension/api/components/sidebar.mjs
new file mode 100644
index 0000000..7593a46
--- /dev/null
+++ b/extension/api/components/sidebar.mjs
@@ -0,0 +1,24 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * notion-style elements inc. the sidebar
+ * @module notion-enhancer/api/components/side-panel
+ */
+
+import { web } from '../_.mjs';
+
+let _$sidebar;
+
+export const sidebar = (icon, name, loader = ($panel) => {}) => {
+  if (!_$sidebar) {
+    web.loadStylesheet('api/components/sidebar.css');
+    _$sidebar = web.html`<div id="enhancer--sidebar"></div>`;
+    web.render(document.body, _$sidebar);
+  }
+};
diff --git a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/tooltip.css b/extension/api/components/tooltip.css
similarity index 100%
rename from extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/tooltip.css
rename to extension/api/components/tooltip.css
diff --git a/extension/api/components/tooltip.mjs b/extension/api/components/tooltip.mjs
new file mode 100644
index 0000000..5d03bb8
--- /dev/null
+++ b/extension/api/components/tooltip.mjs
@@ -0,0 +1,41 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * notion-style elements inc. the sidebar
+ * @module notion-enhancer/api/components/tooltip
+ */
+
+import { fmt, web } from '../_.mjs';
+
+let _$tooltip;
+
+/**
+ * add a tooltip to show extra information on hover
+ * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered
+ * @param {string} text - the markdown content of the tooltip
+ */
+export const tooltip = ($ref, text) => {
+  if (!_$tooltip) {
+    web.loadStylesheet('api/components/tooltip.css');
+    _$tooltip = web.html`<div id="enhancer--tooltip"></div>`;
+    web.render(document.body, _$tooltip);
+  }
+  text = fmt.md.render(text);
+  $ref.addEventListener('mouseover', (event) => {
+    _$tooltip.innerHTML = text;
+    _$tooltip.style.display = 'block';
+  });
+  $ref.addEventListener('mousemove', (event) => {
+    _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px';
+    _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px';
+  });
+  $ref.addEventListener('mouseout', (event) => {
+    _$tooltip.style.display = '';
+  });
+};
diff --git a/extension/api/env.mjs b/extension/api/env.mjs
index 1a33040..b2bccdc 100644
--- a/extension/api/env.mjs
+++ b/extension/api/env.mjs
@@ -11,7 +11,7 @@
  * @module notion-enhancer/api/env
  */
 
-import env from '../env.mjs';
+import * as env from '../env/env.mjs';
 
 /**
  * the environment/platform name code is currently being executed in
diff --git a/extension/api/fs.mjs b/extension/api/fs.mjs
new file mode 100644
index 0000000..7aa05b3
--- /dev/null
+++ b/extension/api/fs.mjs
@@ -0,0 +1,46 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * environment-specific filesystem reading
+ * @module notion-enhancer/api/fs
+ */
+
+import * as fs from '../env/fs.mjs';
+
+/**
+ * transform a path relative to the enhancer root directory into an absolute path
+ * @type {function}
+ * @param {string} path - a url or within-the-enhancer filepath
+ * @returns {string} an absolute filepath
+ */
+export const localPath = fs.localPath;
+
+/**
+ * fetch and parse a json file's contents
+ * @type {function}
+ * @param {string} path - a url or within-the-enhancer filepath
+ * @returns {object} the json value of the requested file as a js object
+ */
+export const getJSON = fs.getJSON;
+
+/**
+ * fetch a text file's contents
+ * @type {function}
+ * @param {string} path - a url or within-the-enhancer filepath
+ * @returns {string} the text content of the requested file
+ */
+export const getText = fs.getText;
+
+/**
+ * check if a file exists
+ * @type {function}
+ * @param {string} path - a url or within-the-enhancer filepath
+ * @returns {boolean} whether or not the file exists
+ */
+export const isFile = fs.isFile;
diff --git a/extension/api/registry-validation.mjs b/extension/api/registry-validation.mjs
index 48add10..afe7819 100644
--- a/extension/api/registry-validation.mjs
+++ b/extension/api/registry-validation.mjs
@@ -148,22 +148,6 @@ const check = async (
         });
         tests.push(test);
       }
-      if (mod.js.hook) {
-        if (mod.tags.includes('core')) {
-          const test = check(mod, 'js.hook', mod.js.hook, 'file', {
-            extension: '.mjs',
-          });
-          tests.push(test);
-        } else {
-          registry._errors.push({
-            source: mod._dir,
-            message: `js.hook (only core mods can register hooks): ${JSON.stringify(
-              mod.tags
-            )}`,
-          });
-          tests.push(false);
-        }
-      }
       return tests;
     });
   },
diff --git a/extension/api/storage.mjs b/extension/api/storage.mjs
new file mode 100644
index 0000000..4a3e63b
--- /dev/null
+++ b/extension/api/storage.mjs
@@ -0,0 +1,67 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * environment-specific data persistence
+ * @module notion-enhancer/api/storage
+ */
+
+import * as storage from '../env/storage.mjs';
+
+/**
+ * get persisted data
+ * @type {function}
+ * @param {array<string>} path - the path of keys to the value being fetched
+ * @param {*} [fallback] - a default value if the path is not matched
+ * @returns {Promise} value ?? fallback
+ */
+export const get = storage.get;
+
+/**
+ * persist data
+ * @type {function}
+ * @param {array<string>} path - the path of keys to the value being set
+ * @param {*} value - the data to save
+ * @returns {Promise} resolves when data has been saved
+ */
+export const set = storage.set;
+
+/**
+ * create a wrapper for accessing a partition of the storage
+ * @type {function}
+ * @param {array<string>} namespace - the path of keys to prefix all storage requests with
+ * @param {function} [get] - the storage get function to be wrapped
+ * @param {function} [set] - the storage set function to be wrapped
+ * @returns {object} an object with the wrapped get/set functions
+ */
+export const db = storage.db;
+
+/**
+ * add an event listener for changes in storage
+ * @type {function}
+ * @param {onStorageChangeCallback} callback - called whenever a change in
+ * storage is initiated from the current process
+ */
+export const addChangeListener = storage.addChangeListener;
+
+/**
+ * remove a listener added with storage.addChangeListener
+ * @type {function}
+ * @param {onStorageChangeCallback} callback
+ */
+export const removeChangeListener = storage.removeChangeListener;
+
+/**
+ * @callback onStorageChangeCallback
+ * @param {object} event
+ * @param {string} event.type - 'set' or 'reset'
+ * @param {string} event.namespace- the name of the store, e.g. a mod id
+ * @param {string} [event.key] - the key associated with the changed value
+ * @param {string} [event.new] - the new value being persisted to the store
+ * @param {string} [event.old] - the previous value associated with the key
+ */
diff --git a/extension/env.mjs b/extension/env.mjs
deleted file mode 100644
index 6d75f6e..0000000
--- a/extension/env.mjs
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * notion-enhancer: api
- * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
- * (https://notion-enhancer.github.io/) under the MIT license
- */
-
-'use strict';
-
-/**
- * environment-specific methods and constants
- * @module notion-enhancer/api/env
- */
-
-const focusMenu = () => chrome.runtime.sendMessage({ action: 'focusMenu' }),
-  focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' }),
-  reload = () => chrome.runtime.sendMessage({ action: 'reload' });
-
-export default {
-  name: 'extension',
-  version: chrome.runtime.getManifest().version,
-  focusMenu,
-  focusNotion,
-  reload,
-};
diff --git a/extension/env/env.mjs b/extension/env/env.mjs
new file mode 100644
index 0000000..af15ca2
--- /dev/null
+++ b/extension/env/env.mjs
@@ -0,0 +1,44 @@
+/*
+ * notion-enhancer: api
+ * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
+ * (https://notion-enhancer.github.io/) under the MIT license
+ */
+
+'use strict';
+
+/**
+ * environment-specific methods and constants
+ * @module notion-enhancer/api/env
+ */
+
+/**
+ * the environment/platform name code is currently being executed in
+ * @constant
+ * @type {string}
+ */
+export const name = 'extension';
+
+/**
+ * the current version of the enhancer
+ * @constant
+ * @type {string}
+ */
+export const version = chrome.runtime.getManifest().version;
+
+/**
+ * open the enhancer's menu
+ * @type {function}
+ */
+export const focusMenu = () => chrome.runtime.sendMessage({ action: 'focusMenu' });
+
+/**
+ * focus an active notion tab
+ * @type {function}
+ */
+export const focusNotion = () => chrome.runtime.sendMessage({ action: 'focusNotion' });
+
+/**
+ * reload all notion and enhancer menu tabs to apply changes
+ * @type {function}
+ */
+export const reload = () => chrome.runtime.sendMessage({ action: 'reload' });
diff --git a/extension/api/extension-fs.mjs b/extension/env/fs.mjs
similarity index 100%
rename from extension/api/extension-fs.mjs
rename to extension/env/fs.mjs
diff --git a/extension/api/extension-storage.mjs b/extension/env/storage.mjs
similarity index 100%
rename from extension/api/extension-storage.mjs
rename to extension/env/storage.mjs
diff --git a/extension/launcher.js b/extension/launcher.js
index cf7173c..730ba8f 100644
--- a/extension/launcher.js
+++ b/extension/launcher.js
@@ -13,28 +13,16 @@
     page = location.pathname.split(/[/-]/g).reverse()[0].length === 32;
 
   if (site || page) {
-    import(chrome.runtime.getURL('api/_.mjs')).then(async ({ ...api }) => {
-      const { fs, registry, web } = api,
-        insert = async (mod) => {
-          for (const sheet of mod.css?.client || []) {
-            web.loadStylesheet(`repo/${mod._dir}/${sheet}`);
-          }
-          for (let script of mod.js?.client || []) {
-            script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
-            script.default(api, await registry.db(mod.id));
-          }
-          return true;
-        };
-      for (const mod of await registry.list((mod) => registry.core.includes(mod.id))) {
-        if (mod.js?.hook) {
-          let script = mod.js.hook;
-          script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
-          api[mod.name] = await script.default(api, await registry.db(mod.id));
-        }
-        await insert(mod);
-      }
+    import(chrome.runtime.getURL('api/_.mjs')).then(async (api) => {
+      const { fs, registry, web } = api;
       for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
-        if (!registry.core.includes(mod.id)) await insert(mod);
+        for (const sheet of mod.css?.client || []) {
+          web.loadStylesheet(`repo/${mod._dir}/${sheet}`);
+        }
+        for (let script of mod.js?.client || []) {
+          script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
+          script.default(api, await registry.db(mod.id));
+        }
       }
       const errors = await registry.errors();
       if (errors.length) {
diff --git a/extension/manifest.json b/extension/manifest.json
index cb74f57..c0e7351 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -19,7 +19,7 @@
     "page": "repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.html",
     "open_in_tab": true
   },
-  "web_accessible_resources": ["env.mjs", "api/*", "dep/*", "icon/*", "repo/*"],
+  "web_accessible_resources": ["env/*", "api/*", "dep/*", "icon/*", "repo/*"],
   "content_scripts": [
     {
       "matches": ["https://*.notion.so/*", "https://*.notion.site/*"],
diff --git a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/hook.mjs b/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/hook.mjs
deleted file mode 100644
index ee8c4b8..0000000
--- a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/hook.mjs
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * notion-enhancer core: components
- * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
- * (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
- * (https://notion-enhancer.github.io/) under the MIT license
- */
-
-let _$tooltip;
-
-export default function (api, db) {
-  const { web, fmt } = api;
-
-  return {
-    /**
-     * add a tooltip to show extra information on hover
-     * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered
-     * @param {string} text - the markdown content of the tooltip
-     */
-    tooltip: ($ref, text) => {
-      if (!_$tooltip) {
-        _$tooltip = web.html`<div id="enhancer--tooltip"></div>`;
-        web.render(document.body, _$tooltip);
-      }
-      text = fmt.md.render(text);
-      $ref.addEventListener('mouseover', (event) => {
-        _$tooltip.innerHTML = text;
-        _$tooltip.style.display = 'block';
-      });
-      $ref.addEventListener('mousemove', (event) => {
-        _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px';
-        _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px';
-      });
-      $ref.addEventListener('mouseout', (event) => {
-        _$tooltip.style.display = '';
-      });
-    },
-  };
-}
diff --git a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/mod.json b/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/mod.json
index 343efc2..5b43a62 100644
--- a/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/mod.json
+++ b/extension/repo/components@36a2ffc9-27ff-480e-84a7-c7700a7d232d/mod.json
@@ -1,8 +1,9 @@
 {
+  "__comment": "pseudo-mod to allow configuration of API-provided components",
   "name": "components",
   "id": "36a2ffc9-27ff-480e-84a7-c7700a7d232d",
   "version": "0.2.0",
-  "description": "notion-style elements reuseable by other mods, inc. tooltips and the side panel.",
+  "description": "notion-style elements reused by other mods, inc. the sidebar.",
   "tags": ["core"],
   "authors": [
     {
@@ -18,21 +19,15 @@
       "avatar": "https://avatars.githubusercontent.com/u/54142180"
     }
   ],
-  "js": {
-    "hook": "hook.mjs"
-  },
-  "css": {
-    "client": ["tooltip.css", "sidebar.css"],
-    "menu": ["tooltip.css"],
-    "frame": ["tooltip.css"]
-  },
+  "js": {},
+  "css": {},
   "options": [
     {
       "type": "hotkey",
       "key": "side-panel-hotkey",
-      "label": "toggle side panel hotkey",
+      "label": "toggle enhancer sidebar hotkey",
       "value": "Ctrl+Alt+\\",
-      "tooltip": "opens the side panel in notion - will only work if a mod is making use of it."
+      "tooltip": "opens/closes the extra sidebar in notion - will only work if a mod is making use of it."
     }
   ]
 }
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/blocks.mjs b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/blocks.mjs
index 472f850..41cf1cb 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/blocks.mjs
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/blocks.mjs
@@ -6,9 +6,9 @@
 
 'use strict';
 
-import { api, profileDB } from './loader.mjs';
+import { fmt, web, registry, components } from '../../api/_.mjs';
 import { notifications } from './notifications.mjs';
-const { fmt, web, components } = api;
+const profileDB = await registry.profileDB();
 
 export const blocks = {
   preview: (url) => web.html`<img
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/loader.mjs b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/loader.mjs
deleted file mode 100644
index 3db3711..0000000
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/loader.mjs
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * notion-enhancer core: menu
- * (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
- * (https://notion-enhancer.github.io/) under the MIT license
- */
-
-'use strict';
-
-import * as _api from '../../api/_.mjs';
-export const api = { ..._api },
-  { fs, registry, web } = api;
-
-export const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'),
-  profileName = await registry.profileName(),
-  profileDB = await registry.profileDB();
-
-const insert = (mod) => {
-  for (const sheet of mod.css?.menu || []) {
-    web.loadStylesheet(`repo/${mod._dir}/${sheet}`);
-  }
-};
-for (const mod of await registry.list((mod) => registry.core.includes(mod.id))) {
-  if (mod.js?.hook) {
-    let script = mod.js.hook;
-    script = await import(fs.localPath(`repo/${mod._dir}/${script}`));
-    api[mod.name] = await script.default(api, await registry.db(mod.id));
-  }
-  await insert(mod);
-}
-for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
-  if (!registry.core.includes(mod.id)) await insert(mod);
-}
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.mjs b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.mjs
index 819720b..49c2611 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.mjs
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/menu.mjs
@@ -6,11 +6,20 @@
 
 'use strict';
 
-import { api, db, profileName, profileDB } from './loader.mjs';
-import './styles.mjs';
+import { env, fs, storage, registry, web } from '../../api/_.mjs';
 import { notifications } from './notifications.mjs';
 import { blocks, options } from './blocks.mjs';
-const { env, fs, storage, registry, web } = api;
+import './styles.mjs';
+
+const db = await registry.db('a6621988-551d-495a-97d8-3c568bca2e9e'),
+  profileName = await registry.profileName(),
+  profileDB = await registry.profileDB();
+
+for (const mod of await registry.list((mod) => registry.enabled(mod.id))) {
+  for (const sheet of mod.css?.menu || []) {
+    web.loadStylesheet(`repo/${mod._dir}/${sheet}`);
+  }
+}
 
 web.addHotkeyListener(await db.get(['hotkey']), env.focusNotion);
 
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/notifications.mjs b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/notifications.mjs
index 305dae6..64fcf64 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/notifications.mjs
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/notifications.mjs
@@ -6,9 +6,8 @@
 
 'use strict';
 
-import { api } from './loader.mjs';
+import { env, fs, storage, fmt, registry, web } from '../../api/_.mjs';
 import { tw } from './styles.mjs';
-const { env, fs, storage, fmt, registry, web } = api;
 
 export const notifications = {
   $container: web.html`<div class="notifications-container"></div>`,
diff --git a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/router.mjs b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/router.mjs
index 6ea1353..8a5e70e 100644
--- a/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/router.mjs
+++ b/extension/repo/menu@a6621988-551d-495a-97d8-3c568bca2e9e/router.mjs
@@ -6,8 +6,7 @@
 
 'use strict';
 
-import { api } from './loader.mjs';
-const { web } = api;
+import { web } from '../../api/_.mjs';
 
 let _defaultView = '';
 const _views = new Map();