summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/icon.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-23 15:15:35 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-23 15:15:35 +0200
commit9f02ee2c09652abf1308bdfa4a3eec4e9ca9ac83 (patch)
treeb13198a79a0965f254c828adba3eb41dd5e9a5b4 /packages/meshbay-hub/src/meshbay_hub/static/icon.js
parent8dc11dc05a35a5d64ba4d2c892ccc01c7bfae3da (diff)
downloadmeshbay-9f02ee2c09652abf1308bdfa4a3eec4e9ca9ac83.tar.gz
feat(hub): split the group UI into a pluggable "applications" architecture
GroupPage's 6620-line app.js carried Chat and Files wedged in directly, with no way to add another group-level app without touching the shell itself. It is now app.js (routing, non-group pages) plus nine focused files — apps.js (the registry), chat-app.js, files-app.js, video-player.js, group-page.js (the shell), group-settings.js, hub-client.js, icon.js and file-utils.js — with docs/apps.md as the checklist for adding one (Videos/Music/Photos are sketched there, not built). Node side gained the matching enablement mechanism, mirroring member_upload exactly: a roster setting, a signed apps_enabled op enforced by _has_admin_authority, exposed in the handshake ack. Operators toggle applications per group from Settings, which also gained a small reorder: Invite, Pairing, Applications, Shared directories, Uploads, danger zone, Your devices, Members. Two bugs surfaced during the split, both missing an import across the new file boundary and invisible to node --check or a module-load probe since they only throw when the code path actually runs: - group-page.js called onRefreshAuth on a stale-token handshake rejection, but app.js never imported refreshAccessToken from hub-client.js — so a brand new member (including a group's own creator) hit "Not a member of this group" and the retry silently failed, throwing before it could refresh the token. - chat-app.js called getLocale() for message timestamps without importing it from i18n.js. Opening Chat on a group with real messages threw mid- render; uncaught, that appears to wedge Preact's render scheduler, so every button on the page stopped responding until reload. Caught the second class of bug with a proper no-undef audit across all split files (a temporarily installed ESLint 9, since the system one is too old to parse this codebase's syntax) rather than trusting grep. 827 tests pass; 6 new ones cover the apps_enabled policy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016SF6RKNBKg9qejmoMJ9ybA
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/icon.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/icon.js91
1 files changed, 91 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/icon.js b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
new file mode 100644
index 0000000..4dc24c0
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/icon.js
@@ -0,0 +1,91 @@
+import { html } from './vendor/htm-preact.js';
+
+// ── Icons ────────────────────────────────────────────────────────────────────
+//
+// One stroked set, drawn in currentColor and sized in em, so an icon takes the
+// weight and colour of the text beside it. The Administration entry was already
+// an outline shield while the rest of the site was colour emoji — a different
+// drawing on every operating system, and never the same line weight twice.
+//
+// The explorer keeps its emoji on purpose. There the icon says what kind of file
+// this is, and the colour is doing real work; a wall of identical grey outlines
+// would be a worse file list.
+
+const ICON_PATHS = {
+ menu: ['M4 7h16M4 12h16M4 17h16'],
+ bell: ['M18 9a6 6 0 1 0-12 0c0 6-2.5 7.5-2.5 7.5h17S18 15 18 9',
+ 'M10.3 20a2 2 0 0 0 3.4 0'],
+ shield: ['M12 3l7.5 3v5.2c0 4.6-3.1 8.6-7.5 10.3-4.4-1.7-7.5-5.7-7.5-10.3V6z'],
+ globe: ['M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18',
+ 'M3.4 9.2h17.2M3.4 14.8h17.2',
+ 'M12 3c-2.6 2.4-4 5.6-4 9s1.4 6.6 4 9c2.6-2.4 4-5.6 4-9s-1.4-6.6-4-9'],
+ archive: ['M3 7.5h18v3H3z', 'M4.5 10.5V19a1.5 1.5 0 0 0 1.5 1.5h12a1.5 1.5 0 0 0 1.5-1.5v-8.5',
+ 'M10 14h4'],
+ play: ['M8 5.5v13l11-6.5z'],
+ eye: ['M2 12s3.6-6.5 10-6.5S22 12 22 12s-3.6 6.5-10 6.5S2 12 2 12',
+ 'M12 14.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5'],
+ trash: ['M4 7h16', 'M10 11v6M14 11v6',
+ 'M6 7l1 12.5A1.5 1.5 0 0 0 8.5 21h7a1.5 1.5 0 0 0 1.5-1.5L18 7',
+ 'M9.5 7V5a1.5 1.5 0 0 1 1.5-1.5h2A1.5 1.5 0 0 1 14.5 5v2'],
+ user: ['M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8',
+ 'M4.5 20a7.5 7.5 0 0 1 15 0'],
+ gear: ['M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6',
+ 'M19.2 14.4a1.7 1.7 0 0 0 .3 1.9 2 2 0 1 1-2.8 2.8 1.7 1.7 0 0 0-2.9 1.2 2 2 0 0 1-4 0 1.7 1.7 0 0 0-2.9-1.2 2 2 0 1 1-2.8-2.8 1.7 1.7 0 0 0-1.2-2.9 2 2 0 0 1 0-4 1.7 1.7 0 0 0 1.2-2.9 2 2 0 1 1 2.8-2.8 1.7 1.7 0 0 0 2.9-1.2 2 2 0 0 1 4 0 1.7 1.7 0 0 0 2.9 1.2 2 2 0 1 1 2.8 2.8 1.7 1.7 0 0 0 1.2 2.9 2 2 0 0 1 0 4 1.7 1.7 0 0 0-1.5 1.1z'],
+ sun: ['M12 8.2a3.8 3.8 0 1 0 0 7.6 3.8 3.8 0 0 0 0-7.6',
+ 'M12 2.5v2M12 19.5v2M2.5 12h2M19.5 12h2M5.2 5.2l1.4 1.4M17.4 17.4l1.4 1.4M18.8 5.2l-1.4 1.4M6.6 17.4l-1.4 1.4'],
+ moon: ['M20.8 13.4A8.6 8.6 0 1 1 10.6 3.2a6.9 6.9 0 0 0 10.2 10.2z'],
+ power: ['M12 3.2v8.4', 'M6.9 6.6a7.6 7.6 0 1 0 10.2 0'],
+ lock: ['M5.5 11h13a1 1 0 0 1 1 1v7.5a1 1 0 0 1-1 1h-13a1 1 0 0 1-1-1V12a1 1 0 0 1 1-1z',
+ 'M8 11V7.4a4 4 0 0 1 8 0V11'],
+ envelope: ['M4 5.5h16a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-11a1 1 0 0 1 1-1z',
+ 'M3.4 6.6L12 13.4l8.6-6.8'],
+ door: ['M13.5 3.5H6a1 1 0 0 0-1 1v15a1 1 0 0 0 1 1h7.5',
+ 'M10.5 12H21', 'M17.8 8.8L21 12l-3.2 3.2'],
+ download: ['M12 3.5v12', 'M7.5 11l4.5 4.5 4.5-4.5', 'M4.5 20h15'],
+ upload: ['M12 20.5v-12', 'M7.5 13l4.5-4.5 4.5 4.5', 'M4.5 4h15'],
+ transfer: ['M6.5 3.5v11', 'M3.5 11l3 3.5 3-3.5',
+ 'M17.5 20.5v-11', 'M14.5 13l3-3.5 3 3.5'],
+ search: ['M11 4.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13', 'M15.8 15.8L20.5 20.5'],
+ dots: ['M12 5.6h.01', 'M12 12h.01', 'M12 18.4h.01'],
+ checkbox: ['M5.5 4h13a1.5 1.5 0 0 1 1.5 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 18.5v-13A1.5 1.5 0 0 1 5.5 4z'],
+ home: ['M4 11.2L12 4.5l8 6.7', 'M6.2 9.8V19a1 1 0 0 0 1 1h9.6a1 1 0 0 0 1-1V9.8'],
+ 'folder-plus': ['M3.5 6.6a1 1 0 0 1 1-1h4.2l2 2.4h7.8a1 1 0 0 1 1 1v9.4a1 1 0 0 1-1 1h-14a1 1 0 0 1-1-1z',
+ 'M12 11.4v5', 'M9.5 13.9h5'],
+ plus: ['M12 5v14', 'M5 12h14'],
+ clip: ['M20.5 11.8l-8.4 8.4a5.4 5.4 0 0 1-7.6-7.6l8.8-8.8a3.6 3.6 0 0 1 5.1 5.1l-8.8 8.8a1.8 1.8 0 0 1-2.5-2.5l8.1-8.1'],
+ pencil: ['M4 20h4l10.5-10.5a2.1 2.1 0 0 0-3-3L5 17v3',
+ 'M14.5 6.5l3 3'],
+ check: ['M4.5 12.5l5 5 10-11'],
+ chevron: ['M6 9.5l6 6 6-6'],
+ close: ['M6 6l12 12M18 6L6 18'],
+ chat: ['M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'],
+ folder: ['M3.5 6.6a1 1 0 0 1 1-1h4.2l2 2.4h7.8a1 1 0 0 1 1 1v9.4a1 1 0 0 1-1 1h-14a1 1 0 0 1-1-1z'],
+ 'bell-off': ['M18 9a6 6 0 0 0-12 0c0 6-2.5 7.5-2.5 7.5h17S18 15 18 9',
+ 'M10.3 20a2 2 0 0 0 3.4 0',
+ 'M4 4l16 16'],
+ server: ['M4 6.5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2z',
+ 'M4 15.5a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2z',
+ 'M8 7.5h.01', 'M8 16.5h.01'],
+ cast: ['M2 16.1A5 5 0 0 1 6.9 21', 'M2 12.05A9 9 0 0 1 12.95 21',
+ 'M2 8V6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2h-6',
+ 'M2 21h.01'],
+};
+
+// The M of the wordmark is a picture; the rest is text. Resolved from this
+// module's own URL so the hub's fingerprinted path and the application's
+// app:// scheme both come out right without either being named here.
+const BRAND_M = new URL('./meshbay-m.png', import.meta.url).href;
+
+function Icon({ name, cls = '' }) {
+ const paths = ICON_PATHS[name];
+ if (!paths) return null;
+ return html`
+ <svg class="icon ${cls}" viewBox="0 0 24 24" aria-hidden="true" focusable="false"
+ fill="none" stroke="currentColor" stroke-width="1.6"
+ stroke-linecap="round" stroke-linejoin="round">
+ ${paths.map((d, i) => html`<path key=${i} d=${d} />`)}
+ </svg>
+ `;
+}
+
+export { Icon };