Skip to content

feat(desktop): complete system tray lifecycle - #187

Merged
HsiangNianian merged 5 commits into
mainfrom
feat/alpha9-system-tray
Aug 3, 2026
Merged

HsiangNianian merged 5 commits into
mainfrom
feat/alpha9-system-tray

Conversation

@HsiangNianian

@HsiangNianian HsiangNianian commented Jul 30, 2026 •

Copy link
Copy Markdown
Member

Summary

  • replace the restart-bound tray with localized window, recent launch, download, and explicit quit controls
  • enforce the same account, Java, file, memory, active-download, and single-game readiness gates used by the Home launcher
  • return every recovery path to Home before revealing the window, so guidance is visible even when the launcher was hidden from another page
  • add close-to-tray, start-minimized, minimize-after-launch, desktop notifications, and a cross-platform download-progress icon badge
  • keep raw configuration saves, recent instances, EN/ZH guidance, and release metadata synchronized with tray state

Platform behavior

  • Windows: double-click the tray icon to show or hide the launcher
  • macOS: click the tray icon to show or hide the launcher
  • Linux: use the tray context menu because desktop tray click events are inconsistent

Validation

  • cargo test --manifest-path src-tauri/Cargo.toml: 199 passed, 1 ignored
  • cargo clippy --manifest-path src-tauri/Cargo.toml
  • pnpm -C packages/ui lint
  • pnpm -C packages/ui build
  • full Playwright launcher suite: 124 passed
  • final focused tray suite: 16 passed across default and minimum window projects
  • pnpm -C packages/docs build
  • pnpm deploy:docs:dry-run
  • pnpm exec prek run --all-files

Closes #48

@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements a complete desktop system tray lifecycle across Rust/Tauri, React UI, Playwright tests, and docs: introduces a dedicated desktop module for tray/menu/notification handling, wires it into launcher config and window lifecycle, exposes tray-related commands to the frontend, adds UI controls and tests for tray behavior, and documents the feature in English and Chinese manuals.

Sequence diagram for tray quick launch from system tray

sequenceDiagram
  actor User
  participant TrayIcon
  participant AppHandle
  participant TrayController
  participant InstanceStore
  participant GameStore

  User->>TrayIcon: select quick-launch menu item
  TrayIcon->>AppHandle: on_menu_event(id)
  AppHandle->>AppHandle: emit("tray-quick-launch", TrayLaunchRequest)
  AppHandle-->>TrayController: event "tray-quick-launch"
  TrayController->>TrayController: quickLaunch(request)
  TrayController->>InstanceStore: useInstanceStore.getState()
  alt instance missing
    TrayController->>InstanceStore: instanceState.refresh()
    TrayController->>InstanceStore: useInstanceStore.getState()
  end
  TrayController->>InstanceStore: instanceState.setActiveInstance(instance)
  TrayController->>GameStore: startGame(request.instanceId, request.versionId)
  GameStore-->>TrayController: result
  alt result === null
    TrayController->>AppHandle: showMainWindow()
  end
Loading

Sequence diagram for download progress updates and tray notifications

sequenceDiagram
  participant DownloadStore
  participant TrayController
  participant AppHandle
  participant TrayDownloadState
  participant Notification

  DownloadStore-->>TrayController: state change(active, percentage, status)
  TrayController->>TrayController: useEffect
  TrayController->>AppHandle: update_tray_download_status(status)
  AppHandle->>TrayDownloadState: lock state.0
  TrayDownloadState-->>TrayDownloadState: TrayDownloadStatus.normalized()
  TrayDownloadState-->>AppHandle: updated status
  AppHandle->>AppHandle: refresh_tray(app)
  AppHandle-->>TrayController: command result
  alt completed && enable_system_tray
    AppHandle->>Notification: notification().builder().title(download_complete_title).body(download_complete_body).show()
  end
Loading

File-Level Changes

Change Details Files
Add a dedicated Rust desktop/tray lifecycle module and integrate it into the Tauri app startup, commands, and window-close behavior.
  • Introduce src-tauri/src/desktop.rs with tray menu setup, quick-launch targets, download progress state, lifecycle helpers, and notification utilities.
  • Replace the old minimal tray setup in main.rs with desktop::setup_system_tray, wiring TrayDownloadState as managed state and enabling the notification plugin.
  • Use new desktop helpers from main.rs to control start-minimized, close-to-tray, and minimize-after-launch behavior, and to send crash notifications and refresh tray state on game start and settings save.
  • Expose Tauri commands from the desktop module (refresh_system_tray, update_tray_download_status, show_system_notification, show_main_window) through the main builder command list.
src-tauri/src/desktop.rs
src-tauri/src/main.rs
Extend launcher configuration to support tray lifecycle preferences with safe defaults and add tests for legacy config behavior.
  • Add close_to_tray, start_minimized_to_tray, and minimize_to_tray_after_launch fields with serde defaults, including helper default functions, to LauncherConfig.
  • Update LauncherConfig::Default implementation and TypeScript bindings/config editor schema to include the new tray lifecycle properties.
  • Add a Rust unit test ensuring legacy JSON configs deserialize with safe tray lifecycle defaults when enableSystemTray is set.
  • Update fixtures to include the new tray-related config fields for UI tests and storyboard behavior.
src-tauri/src/core/config.rs
packages/ui/src/types/bindings/config.ts
packages/ui/src/components/config-editor.tsx
packages/ui/src/fixtures/launcher.ts
Add frontend tray controller logic that syncs download progress and recent instances to the tray, and handles events from the tray for quick-launch and downloads.
  • Introduce TrayController React component that pushes download state to update_tray_download_status, refreshes tray launch targets when instances change, and listens for tray-quick-launch and tray-open-downloads events.
  • Wire TrayController into the main IndexPage layout so it is always active when the launcher shell is mounted.
  • Implement client-side wrappers for new Tauri commands: refreshSystemTray, showMainWindow, showSystemNotification, updateTrayDownloadStatus, and add TrayDownloadStatus type in bindings.
  • Ensure fixture invoke handler recognizes the new tray-related commands and treats them as no-op in fixture mode.
packages/ui/src/components/tray-controller.tsx
packages/ui/src/pages/index.tsx
packages/ui/src/client.ts
packages/ui/src/types/bindings/core.ts
packages/ui/src/fixtures/launcher.ts
Expand the Settings UI and layout to expose tray lifecycle options and improve minimum window behavior, with accompanying Playwright coverage.
  • Adjust SettingsPage container and tab layouts (min-h-0, flex-1, shrink-0, pb-1, min-h-full) to better support minimum-window and scroll-area behavior.
  • Add orientation="horizontal" to Field components to standardize form row layout.
  • Extend the Advanced → System Tray fieldset with a description hint and three new switches: Close window to tray, Start minimized to tray, and Minimize after launching Minecraft, wired to config and disabled when tray is off.
  • Add a Playwright test that validates progressive tray lifecycle settings behavior (dependent disabling/enabling) and ensures scroll behavior for minimum-window projects.
packages/ui/src/pages/settings.tsx
packages/ui/tests/launcher-fixtures.spec.ts
Integrate system notifications with the updater and document system tray behavior and desktop lifecycle in English and Chinese manuals.
  • Update the updater hook to call showSystemNotification when a new version is available, with basic error logging on failures.
  • Add System Tray feature cards and matrix entries to the English and Chinese feature index pages, and describe desktop lifecycle capability in main manual index pages.
  • Create new English and Chinese manual pages dedicated to the System Tray, detailing lifecycle options, tray menu contents, platform-specific behavior, and background notifications.
  • Update locale JSON files and feature meta.json as needed to support new documentation routes and labels (files referenced but not shown in diff).
packages/ui/src/components/updater.tsx
packages/docs/content/en/manual/features/index.mdx
packages/docs/content/zh/manual/features/index.mdx
packages/docs/content/en/manual/index.mdx
packages/docs/content/zh/manual/index.mdx
packages/docs/content/en/manual/features/system-tray.mdx
packages/docs/content/zh/manual/features/system-tray.mdx
packages/docs/content/en/manual/features/meta.json
packages/docs/content/zh/manual/features/meta.json
packages/ui/src/locales/en.json
packages/ui/src/locales/zh-CN.json

Assessment against linked issues

Issue Objective Addressed Explanation
#48 Implement a full system tray feature for the launcher, including tray icon, show/hide behavior, right-click menu with show/hide, recent quick-launch items, download status display, explicit exit, and appropriate cross-platform interactions. ✅
#48 Add advanced system tray lifecycle behaviors and settings: options for closing the window to tray, starting minimized to tray, minimizing after game launch, and providing tray status indication plus desktop notifications for download completion, game crashes, and launcher updates. ✅
#48 Document the system tray feature and its lifecycle/notification behavior in both English and Chinese manuals. ✅

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Workspace change through: 34d7f86

1 changesets found

Planned changes to release
Package Bump Level Current Version Next Version
@dropout/docs patch 0.1.0-alpha.4 0.1.0-alpha.5
@dropout/ui patch 0.1.0-alpha.6 0.1.0-alpha.7
dropout patch 0.2.0-alpha.8 0.2.0-alpha.9

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The current_config helper in desktop.rs clones the entire LauncherConfig on every call, including within hot paths like refresh_tray and notifications; consider passing a borrowed config or caching it per operation to reduce redundant cloning and lock contention.
  • In TrayController, the quickLaunch helper re-fetches and searches instances from the store even though a subscription is already driving tray refresh; consider reusing the subscribed state or precomputing a lookup map to avoid repeated full-list scans on each tray launch.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `current_config` helper in `desktop.rs` clones the entire `LauncherConfig` on every call, including within hot paths like `refresh_tray` and notifications; consider passing a borrowed config or caching it per operation to reduce redundant cloning and lock contention.
- In `TrayController`, the `quickLaunch` helper re-fetches and searches instances from the store even though a subscription is already driving tray refresh; consider reusing the subscribed state or precomputing a lookup map to avoid repeated full-list scans on each tray launch.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@HsiangNianian

Copy link
Copy Markdown
Member Author

Thanks for the review. Both choices are intentional: the config snapshot releases the ConfigState mutex before native menu and notification calls, and the launcher config is small with progress updates already throttled. The tray launch handler reads the live Zustand state so a click cannot use a stale instance map; the subscription only synchronizes native menu labels.

Copilot AI review requested due to automatic review settings August 3, 2026 09:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a complete “desktop lifecycle” system tray experience for the Tauri launcher, including tray-managed window visibility, quick-launch of recent instances, bounded download progress + notifications, and corresponding UI/config/docs updates.

Changes:

  • Added a new Rust desktop module that owns tray menu construction, refresh logic, download progress badging, and desktop notifications.
  • Extended launcher settings/config (Rust + UI bindings + Settings page) with progressive tray lifecycle toggles (close-to-tray, start-minimized, minimize-after-launch).
  • Added UI tray controller wiring, Playwright coverage, and bilingual documentation for the system tray workflow.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src-tauri/src/main.rs Wires tray module into app lifecycle, adds notification plugin, updates launch/close behaviors, and exposes new tray-related commands.
src-tauri/src/desktop.rs New module implementing tray menu, quick launch targets, download badge rendering, and system notifications.
src-tauri/src/core/config.rs Adds tray lifecycle config fields, defaults, sanitization, and legacy-defaults test.
packages/ui/tests/launcher-fixtures.spec.ts Adds Playwright tests validating tray settings progression + tray quick-launch recovery behavior.
packages/ui/src/types/bindings/core.ts Adds TrayDownloadStatus binding type for tray download status updates.
packages/ui/src/types/bindings/config.ts Adds tray lifecycle fields to LauncherConfig bindings.
packages/ui/src/pages/settings.tsx Updates settings layout constraints and adds system tray lifecycle switches + hint text.
packages/ui/src/pages/index.tsx Mounts the new TrayController at the app shell level.
packages/ui/src/locales/zh-CN.json Adds/updates Chinese strings for tray lifecycle settings and hints.
packages/ui/src/locales/en.json Adds/updates English strings for tray lifecycle settings and hints.
packages/ui/src/lib/launcher-runtime.ts Extends supported fixture names for tray quick-launch scenarios.
packages/ui/src/fixtures/launcher.ts Updates fixture config defaults, adds new scenarios, and tracks invoked commands for assertions.
packages/ui/src/components/updater.tsx Emits a desktop notification when an update is available (tray-enabled).
packages/ui/src/components/tray-controller.tsx New component bridging tray events to UI actions and pushing download progress updates to the tray.
packages/ui/src/components/config-editor.tsx Extends config editor schema to include new tray lifecycle fields.
packages/ui/src/client.ts Adds client wrappers for new tray/notification commands.
packages/docs/content/zh/manual/index.mdx Mentions the new desktop lifecycle capability in the Chinese manual index.
packages/docs/content/zh/manual/features/system-tray.mdx New Chinese feature page documenting the tray workflow.
packages/docs/content/zh/manual/features/meta.json Adds the system tray feature page to the Chinese manual nav metadata.
packages/docs/content/zh/manual/features/index.mdx Adds system tray to the Chinese features index and feature matrix.
packages/docs/content/en/manual/index.mdx Mentions the new desktop lifecycle capability in the English manual index.
packages/docs/content/en/manual/features/system-tray.mdx New English feature page documenting the tray workflow.
packages/docs/content/en/manual/features/meta.json Adds the system tray feature page to the English manual nav metadata.
packages/docs/content/en/manual/features/index.mdx Adds system tray to the English features index and feature matrix.
.changes/alpha9-system-tray.md Adds a changeset entry describing the new tray lifecycle feature set.

Comment thread src-tauri/src/main.rs
Comment thread src-tauri/src/main.rs
Comment thread packages/ui/src/components/tray-controller.tsx
Copilot AI review requested due to automatic review settings August 3, 2026 09:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src-tauri/src/main.rs:1869

  • save_raw_config writes the normalized config to disk and updates in-memory state, but will still return an error if refresh_tray fails. That can report a failed save even though the config is already persisted. Consider treating the tray refresh as best-effort here too.
    // Update in-memory state
    *state.config.lock().unwrap() = new_config;
    desktop::refresh_tray(&app)?;

    Ok(())

src-tauri/src/main.rs:1828

  • save_settings now fails if refreshing the system tray menu fails, even though the config has already been written to disk. That changes the command’s error semantics and can surface spurious “save failed” UI states unrelated to persistence. Consider making the tray refresh best-effort (consistent with start_game).

This issue also appears on line 1865 of the same file.

    config.sanitize();
    *state.config.lock().unwrap() = config;
    state.save()?;
    desktop::refresh_tray(&app)?;
    Ok(())

src-tauri/src/desktop.rs:491

  • update_tray_download_status calls current_config(&app) twice, which means two mutex locks + clones on a hot path (download progress updates) and could even observe different config values between the two reads. Read config once and reuse it for the enable check + language selection.
    if completed && current_config(&app).enable_system_tray {
        let labels = tray_labels(&current_config(&app).language);
        let _ = app

@HsiangNianian
HsiangNianian force-pushed the feat/alpha9-system-tray branch from 9958a17 to b982949 Compare August 3, 2026 18:51
Copilot AI review requested due to automatic review settings August 3, 2026 18:51
@HsiangNianian

Copy link
Copy Markdown
Member Author

Rebased onto the released Alpha 8 mainline and completed the final review hardening in b982949: persisted config saves no longer report failure when a best-effort tray refresh fails, and download completion reuses one config snapshot. Quick launch already reuses the subscribed instance store and refreshes only when the requested target is missing. Validation: 233 Rust tests passed (1 external fixture ignored), 138/138 Playwright tests passed across both window profiles, UI unit/build and docs build passed, and full Prek/Clippy passed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

packages/ui/src/components/tray-controller.tsx:21

  • TrayController defines its own TrayLaunchRequest interface, but other cross-boundary payloads in this repo are exported from Rust via ts_rs into the generated bindings (e.g., GameExitedEvent), then consumed from @/types. Keeping this tray payload as an ad-hoc TS interface risks drift between the Rust emitter and the UI listener over time.

Consider exporting the Rust TrayLaunchRequest from src-tauri into the bindings (with #[derive(TS)] + #[ts(export, export_to = "core.ts")]) and importing that generated type here instead of redefining it.

interface TrayLaunchRequest {
  instanceId: string;
  instanceName: string;
  versionId: string;
}

Comment thread src-tauri/src/main.rs
Copilot AI review requested due to automatic review settings August 3, 2026 19:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated no new comments.

@HsiangNianian
HsiangNianian merged commit 28bcde5 into main Aug 3, 2026
26 of 27 checks passed
@HsiangNianian
HsiangNianian deleted the feat/alpha9-system-tray branch August 3, 2026 19:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: 添加系统托盘支持

2 participants