diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-08 03:14:34 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-08 03:14:34 +0200 |
| commit | 8b0f4ba3bc5fc146bad4730b1ccdcd5be55f192d (patch) | |
| tree | 49d7d169115a9b7c87d46d251abaf8590133191e /packages/meshbay-hub/src/meshbay_hub/static | |
| parent | 6a5997655d9f2fa583bf707a5611bbb0c0f109fc (diff) | |
| download | meshbay-8b0f4ba3bc5fc146bad4730b1ccdcd5be55f192d.tar.gz | |
feat(client): create the system tray at launch, not on first minimise
ensureTray() was reachable only from the window:minimize-to-tray handler, so
the indicator did not exist until you had already hidden the window into it.
That is backwards on both desktops — most of what a tray is for is finding an
application that is not in front of you — and on Windows it read as the app
having no tray presence at all.
Created during app.whenReady(), after registerBridge() and before
createWindow(). The order matters: buildTrayMenu reads the nodeService that
registerBridge assigns, so the other way round puts the Start/Stop entry on
the menu one five-second poll late.
The menu's labels were the one thing that came *from* the minimise call, since
the main process has no i18n. A new tray:labels IPC (platform.setTrayLabels)
carries them instead, sent from the renderer's boot once initLocale() has a
catalogue; a language change reloads the page, so the same call covers it. The
window between launch and that first message shows TRAY_FALLBACK, in English.
§5.10's two platform gates become one — trayOS() in main.js, which every tray
path calls. test_desktop_shell.py's existing test is rewritten against it and
two are added: the launch ordering, and that no tray path tests
process.platform inline instead of calling the gate.
Windows still files a new tray icon under hidden icons until the person drags
it onto the taskbar. No API promotes it; documented in WINDOWS-PORT.md §5.11
rather than worked around.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V8EDjk6pkYZrCbo63m2x87
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/app.js | 21 | ||||
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/platform.js | 16 |
2 files changed, 31 insertions, 6 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 9f925c3..c2858f4 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -245,9 +245,7 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, <div class="nav-right"> ${user && html`<${TransferWidget} />`} ${platform.capabilities.tray && html` - <button class="nav-tray" onClick=${() => platform.minimizeToTray({ - show: t('tray.show'), quit: t('tray.quit'), - start_node: t('tray.start_node'), stop_node: t('tray.stop_node') })} + <button class="nav-tray" onClick=${() => platform.minimizeToTray(trayLabels())} title=${t('nav.minimize_tray')} aria-label=${t('nav.minimize_tray')}> <${Icon} name="tray" /> </button> @@ -929,10 +927,25 @@ function App() { // ── Boot ───────────────────────────────────────────────────────────────────── +// The tray menu's four strings. The main process has no i18n (see +// packages/meshbay-client/src/main.js), so they are translated here and sent +// over the bridge — once at boot, because the app now creates its indicator at +// launch rather than on the first minimise, and again from the nav button. +const trayLabels = () => ({ + show: t('tray.show'), quit: t('tray.quit'), + start_node: t('tray.start_node'), stop_node: t('tray.stop_node'), +}); + // Catalogues are fetched, so the first render waits for one: mounting earlier // would paint the interface in English and then swap every string. initLocale() // falls back to English rather than rejecting, so this cannot strand the page. -const mount = () => render(html`<${App} />`, document.getElementById('app')); +const mount = () => { + render(html`<${App} />`, document.getElementById('app')); + // After the catalogue, so the labels are in the right language. A no-op in a + // browser and on macOS. A language change reloads the page, which comes back + // through here, so nothing else has to watch for it. + platform.setTrayLabels(trayLabels()).catch(() => {}); +}; initLocale().then(mount, (err) => { // Nothing in initLocale() is supposed to reject. If something does, an // English interface is still an interface; an unhandled rejection here is a diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js index b009ebe..e93ffb3 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js @@ -470,9 +470,21 @@ export async function minimizeToTray(labels) { return bridge.minimizeToTray(labels); } +/** + * Translate the tray menu the app created at launch. + * + * The main process has no i18n -- a second string table is how two of them + * start disagreeing -- so the labels come from here, once the catalogue has + * loaded. A no-op in a browser and on macOS, where there is no indicator. + */ +export async function setTrayLabels(labels) { + if (!bridge || !bridge.setTrayLabels) return false; + return bridge.setTrayLabels(labels); +} + export default { isNative, hubBase, capabilities, secrets, nativeSave, apiFetch, device, bridgeMessage, folder, rootPicker, node, - cast, minimizeToTray }; + cast, minimizeToTray, setTrayLabels }; // Also a global, because `transport.js` is loaded as a classic script — it // predates the module graph and exposes `MeshBayTransport` the same way. The @@ -482,5 +494,5 @@ if (typeof window !== 'undefined') { window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets, nativeSave, apiFetch, device, bridgeMessage, folder, rootPicker, node, - cast, minimizeToTray }; + cast, minimizeToTray, setTrayLabels }; } |