diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-23 15:15:35 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-23 15:15:35 +0200 |
| commit | 9f02ee2c09652abf1308bdfa4a3eec4e9ca9ac83 (patch) | |
| tree | b13198a79a0965f254c828adba3eb41dd5e9a5b4 /packages/meshbay-hub/src/meshbay_hub/static/icon.js | |
| parent | 8dc11dc05a35a5d64ba4d2c892ccc01c7bfae3da (diff) | |
| download | meshbay-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.js | 91 |
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 }; |