feat(desktop): system tray + minimize-to-tray (T2)#190
Conversation
Root-cause fix + detection unification. Tauri v2 injects `__TAURI_INTERNALS__` unconditionally but `__TAURI__` only when `app.withGlobalTauri` is enabled — and this app does not set it. The canonical `isTauriRuntime()` checked `__TAURI__` alone, so it returned FALSE inside the real desktop shell, silently dead-ending the JS desktop layer (storage→filesystem backend, Tauri JSONL log sink, native HTTP/CORS-bypass fetch for AI providers, desktop device profile, Ollama-localhost path, desktop-only provider UI). The bug was masked because the Rust-side menu works regardless of JS detection. - `isTauriRuntime()` now accepts `__TAURI_INTERNALS__` OR `__TAURI__` (matches the robust check already in register-sw.ts). Web stays false → zero PWA regression. - Unify all seven raw detection sites onto `isTauriRuntime()`: storageService, loraTrainingService (drops its local `__TAURI_INTERNALS__` helper), logger, ai/fetchAdapter, ai/localAiDeviceProfiler, aiProviderService (Ollama), settings/AiProviderCard. - Test: isTauriRuntime returns true when only `__TAURI_INTERNALS__` is present. (T0 foundation for the native-desktop plan. The `services/desktop/` layer + the `desktop` i18n module are introduced in T1 with their first consumers, to avoid shipping unused scaffold here.) typecheck/lint green; tauriRuntime/storageService/fetchAdapter/localAiDeviceProfiler/ loraTrainingService/aiProviderService/logger tests pass (120 across the touched set). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (T1) Migrate the native menu from hardcoded-English Rust to a localized JS menu — the only way to localize labels, since there is no t() outside React (ADR-D1). - New `services/desktop/` layer: `desktopEvents.ts` (command-id contract) + `desktopMenu.ts` (`installDesktopMenu(t, runCommand)`), built with `@tauri-apps/api/menu` and gated by `isTauriRuntime()` (no-op on web). - Full localized menu: File (Export/Settings/Quit), Edit + Window (predefined OS-localized items, no wiring), View (Command Palette, Cmd/Ctrl+K), Help. Custom items route through `executeCommand` (ADR-D3). `setAsAppMenu()` overrides the minimal Rust menu, which stays as a pre-paint fallback (no Rust change, no flash). - Installed from `App.tsx` (rebuilds on language change). Falls back silently to the Rust menu if the API throws — never breaks startup. - New `desktop` i18n module (`desktop.menu.*`) across all 17 locales (5 core translated, Beta/RTL English-fallback) + registered in both i18n scripts; bundles rebuilt, i18n:check green (parity 17×). Deferred: dynamic **Recent Projects**. The app is single-active-project (switched only by importing a file via importProjectThunk); there is no runtime "open stored project by id → activate" path to wire menu items to. That needs a project-switch thunk (save-current/load-target + entity rehydration) — its own follow-up, not a menu sub-task. Tracked. Tests: desktopMenu builder (5 — structure, app-menu install, command routing, web no-op, error-safe). typecheck (validates the menu API) / lint / i18n:check / placeholder-guard green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a real system tray (the prior "tray service" was only a window show/hide
helper). Built in JS via @tauri-apps/api/tray with localized labels (ADR-D1);
Rust only enables the tray-icon Cargo feature + tray/window capabilities.
- `services/desktop/desktopTray.ts`: `installDesktopTray(t, runCommand)` — tray
icon + tooltip, localized context menu (Show / Settings / Command Palette /
Quit-predefined), left-click focuses the window. Created once per session,
isTauriRuntime-gated (no-op on web). `installCloseToTray(getter)` hides to tray
instead of quitting when enabled (reads the setting live).
- New `desktop` settings group (`DesktopSettings.minimizeToTray`, default off):
type, slice default + `setDesktopSettings` reducer, normalizePersistedSettings
backfill. `components/settings/DesktopSection.tsx` toggle (desktop-only) in
Settings → About.
- Wired in App.tsx (tray install + close-to-tray with cleanup).
- i18n `desktop.tray.*` + `desktop.settings.*` across all 17 locales (5 core
translated) + bundles rebuilt; i18n:check parity green.
- Rust: `tauri = { features = ["tray-icon"] }` + capabilities (core:tray:default,
core:app:allow-default-window-icon, core:window:allow-show/hide/set-focus/
is-visible/close/destroy).
Tests: desktopTray (7 — creation, once-guard, left-click focus, command routing,
close-to-tray on/off, web no-op) + settingsSlice desktop default/setter (+2) +
fixed the languageToolClient full-Settings mock. typecheck/lint/token-audit/
i18n:check green. Rust change verified via tauri-build.yml dispatch (no PR gate).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…into feat/tauri-desktop-menu
…at/tauri-desktop-tray
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
@CodeAnt-AI review |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
…ults (CodeAnt #190) - idbProjectStore: when desktop settings exist but are malformed, coerce minimizeToTray to a strict boolean (=== true) so an imported truthy non-boolean (e.g. "false") can't misdrive close-to-tray. - App.tsx: read settings.desktop?.minimizeToTray defensively (?? false) — imported/malformed settings can leave `desktop` null and crash the close handler. - desktopTray: added an in-flight guard (trayInstalling) set BEFORE the async imports — `trayInstalled` only flips after creation, so two concurrent installDesktopTray() calls could both pass the early check and race to create the same tray. New concurrency test asserts only one tray is created. - Extracted defaultDesktopSettings/defaultVoiceSettings into a side-effect-free settingsDefaults.ts; the storage layer now imports from there instead of settingsSlice (which runs applyInitialTheme() at load), so importing the storage layer no longer touches the DOM/theme. settingsSlice re-exports them. Typecheck clean; settingsSlice 33/33 + desktopTray 8/8 green; suppressions unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
@CodeAnt-AI review |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis PR adds a desktop system tray for the Tauri app and a setting that lets closing the window hide it to the tray instead of quitting, wiring tray menu actions into existing commands. sequenceDiagram
participant User
participant App
participant Tray
participant SettingsStore
participant Window
App->>Tray: Install desktop tray with localized menu
App->>Window: Install close-to-tray handler using live minimizeToTray setting
User->>Tray: Click tray menu item (Show, Settings, Command Palette)
Tray->>App: Route selected command
App->>Window: Show or focus window and open requested view
User->>App: Toggle minimize to tray in Desktop settings
App->>SettingsStore: Update desktop.minimizeToTray
User->>Window: Click close button
Window->>App: Close requested
App->>SettingsStore: Read desktop.minimizeToTray
alt Minimize to tray enabled
App->>Window: Prevent close and hide to tray
else Disabled
App->>Window: Allow close and quit
end
Generated by CodeAnt AI |
…odeAnt #190 wave-3) - App.tsx close-to-tray effect: guard the async race — if the component unmounts before installCloseToTray resolves, the resolved listener leaked (unlisten was still null during cleanup). Track a cancelled flag and tear the resolved listener down immediately in that case. - installDesktopTray now RELABELS the existing tray on a re-call (setMenu + setTooltip with fresh localized labels) instead of no-op'ing — the tray effect depends on [t], so a language change now refreshes tray labels without a restart. executeCommand held in a ref so it rebuilds on locale only. New test asserts re-call relabels (no second TrayIcon.new). Typecheck clean; desktopTray 8/8 + settingsSlice green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
|
@CodeAnt-AI review |
Thanks for using CodeAnt! 🎉We're free for open-source projects. if you're enjoying it, help us grow by sharing. Share on X · |
Sequence DiagramThis PR adds a real system tray for the desktop app and a desktop setting that, when enabled, hides the window to the tray instead of quitting on close, with tray actions able to restore the window or open app features. sequenceDiagram
participant User
participant DesktopApp
participant SettingsStore
participant SystemTray
participant MainWindow
DesktopApp->>SystemTray: Install tray with localized menu and tooltip
User->>DesktopApp: Toggle minimize to tray setting
DesktopApp->>SettingsStore: setDesktopSettings(minimizeToTray)
User->>MainWindow: Click window close button
MainWindow->>DesktopApp: Close requested
DesktopApp->>SettingsStore: Read desktop.minimizeToTray
alt Minimize to tray enabled
DesktopApp->>MainWindow: Prevent close and hide to tray
User->>SystemTray: Activate tray icon or Show menu item
SystemTray->>DesktopApp: Route show or focus command
DesktopApp->>MainWindow: Show and focus window
else Minimize to tray disabled
DesktopApp->>MainWindow: Allow window to close and app to quit
end
Generated by CodeAnt AI |
# Conflicts: # App.tsx # graphify-out/GRAPH_REPORT.md # locales/ar/desktop.json # locales/de/desktop.json # locales/el/desktop.json # locales/en/desktop.json # locales/es/desktop.json # locales/eu/desktop.json # locales/fa/desktop.json # locales/fi/desktop.json # locales/fr/desktop.json # locales/he/desktop.json # locales/hu/desktop.json # locales/is/desktop.json # locales/it/desktop.json # locales/ja/desktop.json # locales/pt/desktop.json # locales/sv/desktop.json # locales/zh/desktop.json # public/locales/ar/bundle.json # public/locales/de/bundle.json # public/locales/el/bundle.json # public/locales/en/bundle.json # public/locales/es/bundle.json # public/locales/eu/bundle.json # public/locales/fa/bundle.json # public/locales/fi/bundle.json # public/locales/fr/bundle.json # public/locales/he/bundle.json # public/locales/hu/bundle.json # public/locales/is/bundle.json # public/locales/it/bundle.json # public/locales/ja/bundle.json # public/locales/pt/bundle.json # public/locales/sv/bundle.json # public/locales/zh/bundle.json # services/desktop/desktopMenu.ts # tests/unit/desktopMenu.test.ts
User description
Phase T2 — System tray
Stacked on T1 (#189). Native-desktop plan, Core+power. CodeAnt review deferred to the combined final pass.
What
@tauri-apps/api/tray, localized (ADR-D1): icon + tooltip, context menu (Show / Settings / Command Palette / Quit), left-click focuses the window.isTauriRuntime()-gated → no-op on web; created once per session.installCloseToTray(getter)hides instead of quitting when enabled (reads the setting live). Newdesktopsettings group (minimizeToTray, default off) — type + slice default +setDesktopSettings+normalizePersistedSettingsbackfill;DesktopSectiontoggle in Settings → About (desktop-only).desktop.tray.*+desktop.settings.*across all 17 locales (5 core translated); bundles rebuilt, parity green.tauri = { features = ["tray-icon"] }+ tray/window capabilities.Tests / gates
desktopTray(7) + settingsSlice desktop default/setter (2) + fixed the languageToolClient full-Settings mock. typecheck/lint/token-audit (160≤165)/i18n:check green.tauri-build.ymldispatch (no PR gate). Tray capability/runtime behavior is confirmed on a real desktop (can't run a Tauri shell on the CI/dev host).🤖 Generated with Claude Code
CodeAnt-AI Description
Add a system tray and an optional close-to-tray setting for the desktop app
What Changed
Impact
✅ Fewer accidental quits on desktop✅ Faster reopen from the tray✅ Clearer desktop controls for Tauri users💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.