Skip to content

feat(desktop): system tray + minimize-to-tray (T2)#190

Merged
qnbs merged 12 commits into
mainfrom
feat/tauri-desktop-tray
Jun 20, 2026
Merged

feat(desktop): system tray + minimize-to-tray (T2)#190
qnbs merged 12 commits into
mainfrom
feat/tauri-desktop-tray

Conversation

@qnbs

@qnbs qnbs commented Jun 19, 2026

Copy link
Copy Markdown
Owner

User description

Phase T2 — System tray

Stacked on T1 (#189). Native-desktop plan, Core+power. CodeAnt review deferred to the combined final pass.

What

  • Real system tray (the prior "tray service" was only a window show/hide helper). Built in JS via @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.
  • Minimize-to-tray: installCloseToTray(getter) hides instead of quitting when enabled (reads the setting live). New desktop settings group (minimizeToTray, default off) — type + slice default + setDesktopSettings + normalizePersistedSettings backfill; DesktopSection toggle in Settings → About (desktop-only).
  • i18n desktop.tray.* + desktop.settings.* across all 17 locales (5 core translated); bundles rebuilt, parity green.
  • Rust: 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.
  • Rust verified via tauri-build.yml dispatch (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

  • The desktop app now shows a system tray icon with actions to reopen the app, open Settings, open the Command Palette, or quit
  • Clicking the tray icon brings the window back to the front
  • A new desktop setting lets users keep the app running in the tray when they close the window instead of quitting
  • Existing saved settings now fill in the new desktop option and treat invalid values as off, so close-to-tray works consistently

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:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

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:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

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.

qnbs and others added 6 commits June 19, 2026 05:20
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>
@codeant-ai

codeant-ai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
worldscript-studio Ready Ready Preview, Comment Jun 20, 2026 11:04am

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Jun 19, 2026
Comment thread services/storage/idbProjectStore.ts
Comment thread App.tsx Outdated
Comment thread services/desktop/desktopTray.ts Outdated
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Jun 20, 2026
Comment thread services/storage/idbProjectStore.ts Outdated
@qnbs

qnbs commented Jun 20, 2026

Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

…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>
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@qnbs

qnbs commented Jun 20, 2026

Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files size:XXL This PR changes 1000+ lines, ignoring generated files labels Jun 20, 2026
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Sequence Diagram

This 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
Loading

Generated by CodeAnt AI

Comment thread App.tsx
Comment thread services/desktop/desktopTray.ts
Comment thread App.tsx Outdated
…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>
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@qnbs

qnbs commented Jun 20, 2026

Copy link
Copy Markdown
Owner Author

@CodeAnt-AI review

@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added size:XXL This PR changes 1000+ lines, ignoring generated files and removed size:XXL This PR changes 1000+ lines, ignoring generated files labels Jun 20, 2026
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Sequence Diagram

This 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
Loading

Generated by CodeAnt AI

Base automatically changed from feat/tauri-desktop-menu to main June 20, 2026 10:57
# 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
@qnbs
qnbs merged commit a6ea32f into main Jun 20, 2026
6 of 7 checks passed
@qnbs
qnbs deleted the feat/tauri-desktop-tray branch June 20, 2026 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant