Skip to content

feat(desktop): localized JS application menu via @tauri-apps/api/menu (T1)#189

Merged
qnbs merged 9 commits into
mainfrom
feat/tauri-desktop-menu
Jun 20, 2026
Merged

feat(desktop): localized JS application menu via @tauri-apps/api/menu (T1)#189
qnbs merged 9 commits into
mainfrom
feat/tauri-desktop-menu

Conversation

@qnbs

@qnbs qnbs commented Jun 19, 2026

Copy link
Copy Markdown
Owner

User description

Phase T1 — JS menu migration

Stacked on T0 (#188, base feat/tauri-desktop-foundations). Native-desktop plan, Core+power. CodeAnt review deferred to the combined final pass per docs/CODEANT-LOOP-RUNBOOK.md.

What

  • New services/desktop/ layer: desktopEvents.ts (command-id contract) + desktopMenu.ts (installDesktopMenu(t, runCommand)), built with @tauri-apps/api/menu, gated by isTauriRuntime()no-op on web.
  • Localized menu (the point — there's no t() outside React, ADR-D1): 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 menuless flash.
  • Installed from App.tsx (rebuilds on language change); silently falls back to the Rust menu if the API throws.
  • 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 parity green.

Deferred (honest scope): dynamic Recent Projects

The app is single-active-project — switched only by importing a file (importProjectThunk); there is no runtime "open stored project by id → make active" path (loadProject(id) exists but only feeds backup/dataset extraction). Wiring Recent-Projects menu items needs a project-switch thunk (save-current / load-target + entity-adapter rehydration + image handling) — a real feature with state-handoff risk, not a menu sub-task. Tracked for a dedicated follow-up.

Tests / gates

  • tests/unit/desktopMenu.test.ts (5): menu structure, setAsAppMenu install, command routing, web no-op, error-safe.
  • Green: typecheck (validates the menu API), lint, i18n:check (17× parity), placeholder-guard.
  • No Rust change → no tauri-build.yml dispatch needed this phase.

Zero PWA regression

installDesktopMenu is isTauriRuntime()-gated; web returns early. Web build markup unchanged.

🤖 Generated with Claude Code


CodeAnt-AI Description

Add a localized native menu for the desktop app and stop duplicate menu actions

What Changed

  • The desktop app now shows a localized menu with File, Edit, View, Window, and Help sections instead of the old fixed English menu.
  • Menu actions for Export, Settings, Command Palette, and Help now open the matching in-app features from the native menu.
  • The menu updates when the app language changes and falls back safely if the native menu cannot be installed.
  • Menu clicks now trigger each action once, fixing the duplicate execution caused by the old menu handler.
  • Added translated desktop menu labels and checks to keep the new menu text in sync across languages.

Impact

✅ Localized desktop menus
✅ Fewer double-triggered menu actions
✅ Clearer native app navigation

💡 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 4 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>
@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:00am

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Jun 19, 2026
Comment thread App.tsx 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/desktop/desktopMenu.ts
@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: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
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR moves the desktop application menu into a localized JS layer using the Tauri menu API, installing it from the React App and routing menu actions into the existing command system.

sequenceDiagram
    participant User
    participant DesktopApp
    participant DesktopMenuService
    participant TauriMenuAPI
    participant CommandSystem

    User->>DesktopApp: Open desktop app or change language
    DesktopApp->>DesktopMenuService: installDesktopMenu(t, executeCommand)
    DesktopMenuService->>TauriMenuAPI: Build localized menu and set as app menu

    User->>TauriMenuAPI: Select menu item (Settings, Export, Help, Command Palette)
    TauriMenuAPI->>DesktopMenuService: Invoke item action callback
    DesktopMenuService->>CommandSystem: executeCommand(mapped desktop command id)
Loading

Generated by CodeAnt AI

Comment thread services/desktop/desktopMenu.ts
…f-guard rebuild (CodeAnt #189)

- HIGH: desktop.json used a nested { "menu": { ... } } object, but build-i18n.mjs copies top-level
  keys verbatim and I18nContext.t() only does FLAT lookups, so t('desktop.menu.file') never resolved
  (menu labels would fall back to raw keys). Flattened all 17 locales to flat "desktop.menu.*" keys
  (matching the dashboard.* convention) and rebuilt bundles.
- Removed the registerTauriMenuHandler effect: the localized JS menu (installDesktopMenu) already
  dispatches each action via its item callback, and the Rust `menu-action` bridge reused the SAME item
  ids — so every desktop menu click dispatched twice. The JS menu is now the single source of truth.
- installDesktopMenu effect now holds executeCommand in a ref and depends only on [t], so the native
  menu rebuilds on language change instead of on every executeCommand identity change (it recreates
  whenever characters/worlds/settings/… change).

i18n:check green (parity, 2725 keys/locale); tsgo clean.

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

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR moves the desktop application menu into a localized JS layer, installs it on startup for Tauri runtimes, and routes native menu actions directly into the app command system.

sequenceDiagram
    participant App
    participant JSMenu as Desktop menu service
    participant MenuAPI as Tauri menu API
    participant Commands as Command system
    participant User

    App->>JSMenu: installDesktopMenu(t, runCommand)
    JSMenu->>JSMenu: Check desktop runtime
    JSMenu->>MenuAPI: Build localized menus from translations
    MenuAPI-->>JSMenu: Return menu structure
    JSMenu->>MenuAPI: Set as application menu

    User->>MenuAPI: Selects menu item
    MenuAPI->>JSMenu: Trigger item action callback
    JSMenu->>Commands: runCommand(commandId)
    Commands-->>App: Execute mapped app action
Loading

Generated by CodeAnt AI

Comment thread App.tsx
Comment thread services/desktop/desktopMenu.ts
… win (CodeAnt #189 wave-2)

installDesktopMenu is async and re-runs on every language change; two overlapping calls raced and
whichever finished setAsAppMenu() LAST won — even if it started earlier with a stale locale. Each
call now captures a monotonic token (++menuInstallToken) at start and bails before setAsAppMenu if a
newer call has since started, so only the latest-started install applies. New concurrency test asserts
the superseded call never applies its menu. Covers both the App.tsx effect and desktopMenu threads
(the guard lives at the service level, the single source of truth).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@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: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
@codeant-ai

codeant-ai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Sequence Diagram

This PR adds a JS-based localized desktop application menu for the Tauri runtime and wires menu item actions into the existing command execution system, rebuilding the menu on language changes while falling back to the Rust menu on failure.

sequenceDiagram
    participant User
    participant DesktopShell as Desktop shell
    participant App as React App
    participant MenuService as Desktop menu service
    participant Commands as Command system

    App->>MenuService: installDesktopMenu(t, runCommand)
    MenuService->>MenuService: Check desktop runtime and build localized menu
    MenuService->>DesktopShell: Set menu as app menu
    User->>DesktopShell: Click native menu item
    DesktopShell-->>MenuService: Trigger item action callback
    MenuService->>Commands: runCommand(mapped command id)
    Commands-->>App: Execute command (navigate or export)
Loading

Generated by CodeAnt AI

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

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant