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

@qnbsqnbs 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.

qnbsand 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

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

vercelBot commented Jun 19, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
worldscript-studioReadyReadyPreview, CommentJun 20, 2026 11:04am

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

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-aicodeant-aiBot 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 threadservices/storage/idbProjectStore.ts Outdated
@qnbs

qnbs commented Jun 20, 2026

Copy link
Copy Markdown
OwnerAuthor

@CodeAnt-AI review

@codeant-ai

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

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
OwnerAuthor

@CodeAnt-AI review

@codeant-ai

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-aicodeant-aiBot 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

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 threadApp.tsx
Comment threadservices/desktop/desktopTray.ts
Comment threadApp.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

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
OwnerAuthor

@CodeAnt-AI review

@codeant-ai

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-aicodeant-aiBot 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

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 mainJune 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 mainJun 20, 2026
6 of 7 checks passed
@qnbs
qnbs deleted the feat/tauri-desktop-tray branch June 20, 2026 11:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXLThis PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@qnbs