Skip to content

feat(ui): Flyout tray panel - #103

Merged
YueMiyuki merged 5 commits into
masterfrom
next-dev
Jun 8, 2026
Merged

feat(ui): Flyout tray panel#103
YueMiyuki merged 5 commits into
masterfrom
next-dev

Conversation

@YueMiyuki

@YueMiyuki YueMiyuki commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Summary by cubic

Adds a new tray “Quick Panel” flyout for fast task control without opening the main app. Improves anchoring, focus/blur auto‑hide, and macOS activation for a smoother, more consistent experience.

  • New Features

    • Tray flyout window tray-panel with task list, total transfer speeds, per‑task actions (pause/resume/stop seeding/open/delete), search/filter, add‑URI input, and keyboard tab navigation.
    • Footer actions: pause all, resume all, new task, show main, preferences, quit.
    • Left‑click tray toggles the flyout; added “Quick Panel” tray menu item. Caches tray icon rect, anchors/clamps to the active monitor, auto‑hides on blur and close request, refreshes on open, and polls every 1s while visible.
    • i18n and UI polish: new labels in en-US, zh-CN, zh-TW; consistent border‑radius; removed menu bar gap; on macOS, uses Accessory activation policy when hiding in tray modes.
  • Dependencies

    • Enabled tauri feature macos-private-api; set app.macOSPrivateApi to true.
    • Added tray-panel capability and a flyout manager; stored tray anchor in app state.
    • Introduced utils/run_mode to centralize run‑mode and macOS activation policy; used by commands and flyout.
    • Updated Vite build to include tray.html via rollupOptions.input.
    • Tweaked tray canvas sizes in shared constants.

Written for commit b1fe40a. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Quick Panel (tray flyout) with task management UI, search/filter, add-task form, and per-task action buttons (pause/resume/open/stop seeding/delete)
    • Tray left-click toggles the Quick Panel and panel positioning anchored to the tray icon
  • Bug Fixes / Behavior
    • Panel auto-hides when losing focus
  • Style / UI
    • Updated tray icon sizing
  • Localization
    • Added English, Simplified Chinese, and Traditional Chinese strings for the Quick Panel and related UI

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds an anchored tray “Quick Panel” flyout: backend/capability/config updates, flyout window manager and positioning, tray event/menu integration, renderer entry + Vue components, styles, locales, and run-mode helpers.

Changes

Tray Quick Panel Feature

Layer / File(s) Summary
Backend configuration and app state
src-tauri/Cargo.toml, src-tauri/tauri.conf.json, src-tauri/capabilities/tray-panel.json, src-tauri/src/state.rs
Enables macOS private API, adds tray-panel capability manifest, and introduces AppState.tray_anchor to store cached tray icon coordinates.
Flyout window manager
src-tauri/src/managers/mod.rs, src-tauri/src/managers/flyout.rs
Implements flyout setup (creates hidden WebviewWindow), tray-rect caching with physical-pixel conversion, toggle behavior, and monitor-aware positioning with cursor fallback.
Tray menu and icon event integration
src-tauri/src/managers/tray.rs, src/renderer/pages/index/main.ts
Adds Quick Panel tray menu item, captures tray icon geometry from tray events, changes left-click behavior to toggle the flyout, and updates tray label mapping.
Application window event handling
src-tauri/src/lib.rs
Initializes flyout during startup before background tasks, hides flyout instead of closing, and hides flyout on focus loss with optional macOS activation-policy changes when in tray mode.
Tray renderer entry and runtime loop
vite.renderer.config.ts, src/renderer/pages/index/tray.html, src/renderer/pages/index/tray.ts
Adds a separate tray.html entry, applies effective theme, mounts the tray Vue app, and runs visibility-aware polling with Tauri listeners for flyout:show and command events.
Flyout components and task interactions
src/renderer/components/Flyout/Flyout.vue, src/renderer/components/Flyout/FlyoutTaskItem.vue
Adds flyout UI with accessible tab bar, search/add-task flow, task list rendering, per-task actions (pause/resume/stop-seeding/open/delete) wired to stores and native APIs.
Flyout visual styling
src/renderer/styles/flyout.css
Complete CSS for flyout layout, header, tabs, controls, scrollable task list, row interactions, and footer action buttons.
Locales and tray visual constants
src/shared/constants.ts, src/shared/locales/*/app.ts, src/shared/locales/*/task.ts
Adjusts tray canvas sizing constants and adds flyout-related locale keys and task action labels across en-US/zh-CN/zh-TW.
Run-mode helpers and command reexports
src-tauri/src/utils/run_mode.rs, src-tauri/src/utils/mod.rs, src-tauri/src/commands/app_cmds.rs
Adds current_run_mode, is_tray_mode, and update_macos_activation_policy_for_mode, and re-exports them for use in app commands.
Non-functional edits
src-tauri/risuko-bt/src/torrent.rs, src-tauri/src/commands/file_cmds.rs
Doc comment and minor formatting-only changes with no behavioral effect.

Sequence Diagram(s)

sequenceDiagram
  participant TauriApp as Tauri App
  participant FlyoutMgr as managers::flyout
  participant AppState as AppState.tray_anchor
  participant Webview as WebviewWindow
  participant Monitor as Monitor
  TauriApp->>FlyoutMgr: setup_flyout(app)
  TauriApp->>FlyoutMgr: cache_tray_rect(rect, scale)
  FlyoutMgr->>AppState: store (x,y,w,h)
  TauriApp->>FlyoutMgr: toggle_flyout()
  alt hidden -> show
    FlyoutMgr->>Monitor: resolve monitor from anchor or cursor
    FlyoutMgr->>Webview: set_position & show & set_focus
    FlyoutMgr->>TauriApp: emit flyout:show
  else visible -> hide
    FlyoutMgr->>Webview: (maybe update macOS policy) hide
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • YueMiyuki/Risuko#102: Touches the BEP-55 holepunch code near handle_holepunch / try_initiate_holepunch, which this PR also edits.

Poem

🐰 I stitched a tiny tray-side door,
Tasks peek out, I hop and soar,
Click, a whisper—panels bloom,
Quiet helper in the room. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(ui): Flyout tray panel' clearly and specifically summarizes the main change—a new flyout UI panel for the tray. It is concise, uses conventional commit semantics, and matches the primary focus of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the next The "next" steps label Jun 8, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

5 issues found across 24 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src-tauri/src/lib.rs
Comment thread src/renderer/styles/flyout.css
Comment thread src/renderer/components/Flyout/Flyout.vue Outdated
Comment thread src/renderer/pages/index/tray.ts Outdated
Comment thread src/renderer/pages/index/tray.html Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/capabilities/default.json`:
- Line 4: The default "tray-panel" entry currently inherits the full "windows"
capability; create a new minimal capability object (e.g., "tray-panel-minimal")
in the JSON that only grants the specific window operations and read-only
backend commands used by the flyout—include the window show/hide APIs and the
exact commands show_window, get_global_stat, tell_active, tell_waiting, and
tell_stopped—and remove all broad permissions (filesystem, shell, process, OS)
from that profile, then replace the "tray-panel" reference in the top-level
capabilities list with the new minimal profile name so the tray webview is
scoped to those limited commands only.

In `@src-tauri/src/lib.rs`:
- Around line 357-360: The CloseRequested handler currently returns early for
non-"main" windows, which lets the "tray-panel" be destroyed; change the logic
so that when event matches tauri::WindowEvent::CloseRequested you check for
window.label() == "tray-panel" and, instead of returning, call
api.prevent_close() and window.hide() (mirroring the main-window behavior) so
the tray-panel is hidden rather than closed, ensuring toggle_flyout/setup_flyout
can still operate on the existing window instance.

In `@src/renderer/components/Flyout/Flyout.vue`:
- Line 8: Replace the hardcoded aria-label on the element with a localized
binding (e.g. change aria-label="Total transfer speed" to a dynamic binding like
:aria-label="$t('flyout.totalTransferSpeed')" or
this.$t('flyout.totalTransferSpeed')) and add the corresponding translation key
("flyout.totalTransferSpeed") to your locale files; ensure the Flyout component
uses the app i18n instance (or this.$t) so the aria-label is translated at
runtime.
- Around line 225-242: The emit calls in methods newTask, openPreferences, and
quit can reject and prevent this.hideFlyout() from running; update each of these
methods (newTask, openPreferences, quit in Flyout.vue) to wrap the await
emit(...) call in a try { await emit(...); } finally { await this.hideFlyout();
} so hideFlyout() always runs (you may still catch/log errors inside the try if
desired) — leave openMain as-is since it only calls showMain() then
hideFlyout().

In `@src/renderer/components/Flyout/FlyoutTaskItem.vue`:
- Around line 34-47: The task action buttons in FlyoutTaskItem.vue rely only on
title for accessibility; update the button element (the one iterating
v-for="action in actions" with class "flyout-task-action") to add an explicit
aria-label attribute set to the same accessible text returned by
actionTitle(action) (i.e., aria-label="actionTitle(action)") so screen readers
get a consistent name; leave the existing :title and
`@click.stop`="onActionClick(action)" intact and ensure no visual change to the
icons Play/Pause/Square/FolderOpen/Trash2.

In `@src/renderer/pages/index/tray.html`:
- Around line 10-20: The inline <style> block currently placed outside the
<head> (the HTML/CSS block starting with "html, body { margin: 0; ... }") must
be moved into the document <head>; locate the <style> element in
src/renderer/pages/index/tray.html, cut it from its current position and paste
it inside the <head> section (or create a <head> if missing) before the <body>,
ensuring the same CSS rules are preserved and the duplicate/empty style is
removed from the original location so document structure is valid.

In `@src/renderer/pages/index/tray.ts`:
- Around line 52-54: The current setInterval call schedules the async function
tick without awaiting it, allowing overlapping runs of tick (and its async calls
fetchGlobalStat and fetchList) when they take longer than POLL_INTERVAL; fix by
replacing the setInterval usage with a self-scheduling pattern or a running-flag
guard: modify the code around tick, timer and POLL_INTERVAL so that tick awaits
its async work before scheduling the next invocation (e.g., use await tick();
then setTimeout to call tick again or add a boolean isRunning checked at the top
of tick to early-return if already running), ensuring fetchGlobalStat and
fetchList never run concurrently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 26945d6a-d86b-4c74-9631-979c8615fe17

📥 Commits

Reviewing files that changed from the base of the PR and between c4f062f and 080f8e7.

📒 Files selected for processing (24)
  • src-tauri/Cargo.toml
  • src-tauri/capabilities/default.json
  • src-tauri/risuko-bt/src/torrent.rs
  • src-tauri/src/commands/file_cmds.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/managers/flyout.rs
  • src-tauri/src/managers/mod.rs
  • src-tauri/src/managers/tray.rs
  • src-tauri/src/state.rs
  • src-tauri/tauri.conf.json
  • src/renderer/components/Flyout/Flyout.vue
  • src/renderer/components/Flyout/FlyoutTaskItem.vue
  • src/renderer/pages/index/main.ts
  • src/renderer/pages/index/tray.html
  • src/renderer/pages/index/tray.ts
  • src/renderer/styles/flyout.css
  • src/shared/constants.ts
  • src/shared/locales/en-US/app.ts
  • src/shared/locales/en-US/task.ts
  • src/shared/locales/zh-CN/app.ts
  • src/shared/locales/zh-CN/task.ts
  • src/shared/locales/zh-TW/app.ts
  • src/shared/locales/zh-TW/task.ts
  • vite.renderer.config.ts

Comment thread src-tauri/capabilities/default.json Outdated
"identifier": "default",
"description": "Default capabilities for Risuko",
"windows": ["main"],
"windows": ["main", "tray-panel"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect tray/flyout renderer invoke surface (what permissions are actually needed).
fd -i 'tray\.ts|Flyout\.vue|FlyoutTaskItem\.vue' src/renderer \
  | xargs -r rg -n -C2 '\binvoke\s*\('

# 2) Inspect backend command exposure list for mapping.
rg -n -C2 'generate_handler!\[' src-tauri/src/lib.rs

# 3) Review current capability grants applied to tray-panel.
cat src-tauri/capabilities/default.json

Repository: YueMiyuki/Risuko

Length of output: 2430


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Get full list of handlers exposed by generate_handler!
rg -n 'generate_handler!\[' src-tauri/src/lib.rs -A 100

# 2) Search all invoke calls in Flyout and related tray components (broader search)
find src/renderer -type f \( -name "*.vue" -o -name "*.ts" \) -path "*[Ff]lyout*" -o -path "*[Tt]ray*" | head -20

# 3) Check if other capability profiles exist
find src-tauri/capabilities -type f -name "*.json"

# 4) Search for all invoke calls across tray-related files more thoroughly
rg -n 'invoke\(' src/renderer --glob '*[Tt]ray*' -B 2 -A 2

Repository: YueMiyuki/Risuko

Length of output: 5773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Check desktop.json to understand capability profiles
cat src-tauri/capabilities/desktop.json

# 2) Check tray.ts for all invoke calls (may have more than Flyout.vue)
cat src/renderer/pages/index/tray.ts

# 3) Check tray.worker.ts for invoke calls
cat src/renderer/workers/tray.worker.ts

# 4) Check if main/tauri.conf.json shows which capability applies to which window
find src-tauri -name "tauri.conf.json" -o -name "tauri.conf.*.json" | xargs -r cat 2>/dev/null | head -200

Repository: YueMiyuki/Risuko

Length of output: 6698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Search for tray-panel window creation/registration
rg -n 'tray-panel' src-tauri --type rust -B 3 -A 3

# 2) Check where tray window/panel is spawned
rg -n 'tray' src-tauri/src --type rust -i | grep -i 'window\|spawn\|create' | head -20

# 3) Check store methods to understand what commands are invoked
rg -n 'fetchGlobalStat\|fetchList' src/renderer --type ts -B 2 -A 5 | head -50

Repository: YueMiyuki/Risuko

Length of output: 686


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find and read the store files to see what commands are actually invoked
find src/renderer/store -type f -name "*.ts" | head -10

# 2) Check app store specifically for what commands are invoked
rg -n 'fetchGlobalStat|fetchList' src/renderer/store --type ts -B 2 -A 8 | head -80

# 3) Check complete WebviewWindowBuilder setup for tray-panel to confirm capability assignment
cat src-tauri/src/managers/flyout.rs

Repository: YueMiyuki/Risuko

Length of output: 10200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the api object definition to understand what commands are invoked
find src/renderer -name "api.ts" -o -name "api.js" | head -5

# Check what fetchGlobalStat and fetchList actually call
rg -n 'fetchGlobalStat|fetchList' src/renderer/store/app.ts -B 2 -A 8

# Verify the api calls for these methods
rg -n 'fetchTaskList\|fetchGlobalStat' src/renderer -A 3 -B 1 | grep -E '(invoke|api\.)' | head -30

Repository: YueMiyuki/Risuko

Length of output: 734


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find api definitions
find src/renderer -type f \( -name "*.ts" -o -name "*.js" \) | xargs rg -l "getGlobalStat\|fetchTaskList" | head -5

# Look for the api module/service
find src/renderer -type d -name "api*" -o -name "service*" | head -5

# Search for where getGlobalStat is defined
rg -n "getGlobalStat|fetchTaskList" src/renderer --type ts | grep "=\|:" | head -20

Repository: YueMiyuki/Risuko

Length of output: 44


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Simple search for where api is imported/defined
rg -n "const api\|export.*api\|import.*api" src/renderer/store/app.ts

# Also check the imports at top of app.ts
head -30 src/renderer/store/app.ts

# Find all files with "api" in the name
fd -i "api" src/renderer --type f

Repository: YueMiyuki/Risuko

Length of output: 42


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the beginning of app.ts to see imports
head -50 src/renderer/store/app.ts

# Simple list of files in store directory
ls -la src/renderer/store/

# Try to find where "api" variable comes from
rg "import.*api\|const api" src/renderer/store/ --type ts -n

Repository: YueMiyuki/Risuko

Length of output: 1902


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the api directory/file
find src/renderer -path "*api*" -type f | grep -v node_modules

# Check if it's a directory
ls -la src/renderer/api/ 2>/dev/null || ls -la src/renderer/api.ts 2>/dev/null || echo "Not found, searching..."

# Search for `@/api` alias definition and api files
rg -n "getGlobalStat|get_global_stat" src/renderer --type ts -B 1 -A 3 | head -50

Repository: YueMiyuki/Risuko

Length of output: 967


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the complete Api.ts file to see all commands tray-panel can invoke
cat src/renderer/api/Api.ts

Repository: YueMiyuki/Risuko

Length of output: 20797


Scope tray-panel to a minimal capability set.

Line 4 adds tray-panel to the default capability, which grants it all 80+ exposed commands and broad permissions including shell execution, file access, process control, and OS operations. However, the flyout only calls 4 read-only backend commands: show_window, get_global_stat, tell_active, tell_waiting, and tell_stopped. If the tray panel webview is compromised, an attacker gains access to file operations, process management, and shell execution across the entire application. Create a dedicated minimal capability profile for tray-panel with only the window show/hide operations and the specific read-only query commands it uses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/capabilities/default.json` at line 4, The default "tray-panel"
entry currently inherits the full "windows" capability; create a new minimal
capability object (e.g., "tray-panel-minimal") in the JSON that only grants the
specific window operations and read-only backend commands used by the
flyout—include the window show/hide APIs and the exact commands show_window,
get_global_stat, tell_active, tell_waiting, and tell_stopped—and remove all
broad permissions (filesystem, shell, process, OS) from that profile, then
replace the "tray-panel" reference in the top-level capabilities list with the
new minimal profile name so the tray webview is scoped to those limited commands
only.

Comment thread src-tauri/src/lib.rs
Comment thread src/renderer/components/Flyout/Flyout.vue Outdated
Comment thread src/renderer/components/Flyout/Flyout.vue Outdated
Comment thread src/renderer/components/Flyout/FlyoutTaskItem.vue
Comment thread src/renderer/pages/index/tray.html Outdated
Comment thread src/renderer/pages/index/tray.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/pages/index/tray.ts (1)

149-156: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Startup .catch does not handle init() failures.

Line 152 calls init(config) but does not return/await it, so a rejection from init can bypass the .catch at Line 154.

Proposed fix
 usePreferenceStore()
 	.fetchPreference()
 	.then((config) => {
-		init(config);
+		return init(config);
 	})
 	.catch((err: unknown) => {
 		logger.warn("[Risuko] flyout init failed:", err);
 	});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/pages/index/tray.ts` around lines 149 - 156, The current startup
chain calls init(config) inside the .then callback without returning or awaiting
it, so any rejection from init(config) escapes the outer .catch; modify the
.then handler to return the Promise from init(config) (or make the callback
async and await init(config)) so that rejections propagate to the existing
.catch; look for the fetchPreference().then(...) block in tray.ts and ensure
init(config) is returned/awaited there.
♻️ Duplicate comments (1)
src/renderer/pages/index/tray.ts (1)

57-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Polling still allows concurrent fetch cycles.

Line 74 triggers tick() and Line 75 schedules loop immediately; if tick() is still running when the timeout fires, fetchGlobalStat/fetchList can overlap. Line 130 can also start another unsynchronized tick().

Proposed fix
 function startPolling() {
 	const appStore = useAppStore();
 	const taskStore = useTaskStore();
 	let timer: number | null = null;
 	let running = false;
+	let inFlight = false;

+	const pollOnce = async () => {
+		if (document.hidden || inFlight) {
+			return;
+		}
+		inFlight = true;
+		try {
+			await appStore.fetchGlobalStat();
+			await taskStore.fetchList();
+		} catch (err) {
+			logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
+		} finally {
+			inFlight = false;
+		}
+	};
+
 	const scheduleNext = () => {
 		if (running) {
 			timer = window.setTimeout(loop, POLL_INTERVAL);
 		}
 	};

 	const loop = async () => {
-		if (!running || document.hidden) {
+		if (!running) {
 			scheduleNext();
 			return;
 		}
-		try {
-			await appStore.fetchGlobalStat();
-			await taskStore.fetchList();
-		} catch (err) {
-			logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
-		}
+		await pollOnce();
 		scheduleNext();
 	};

-	const tick = async () => {
-		if (document.hidden) {
-			return;
-		}
-		try {
-			await appStore.fetchGlobalStat();
-			await taskStore.fetchList();
-		} catch (err) {
-			logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
-		}
-	};
+	const tick = async () => pollOnce();

Also applies to: 69-76, 127-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/pages/index/tray.ts` around lines 57 - 67, The poll allows
overlapping runs because tick() is called without synchronization; change the
polling logic to serialize invocations by adding a reentrancy guard (e.g., a
boolean "isPolling" or a simple mutex) used by tick() and any external triggers
so that if isPolling is true you skip or queue the request; ensure the
loop/scheduler waits for tick() to finish before scheduling the next timeout
(i.e., schedule setTimeout after awaiting tick()), and update any other callers
(the unsynchronized start at the other trigger) to check the same guard or
enqueue a single run so appStore.fetchGlobalStat and taskStore.fetchList cannot
run concurrently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/capabilities/tray-panel.json`:
- Around line 4-11: The tray-panel window currently still inherits full default
permissions because the "tray-panel" entry remains in the default capabilities'
"windows" list; remove "tray-panel" from the default capability's windows array
(so only the specialized tray-panel manifest's "windows": ["tray-panel"]
manifest applies) and verify no other capability manifests include "tray-panel"
in their "windows" scopes to enforce the intended least-privilege isolation.

---

Outside diff comments:
In `@src/renderer/pages/index/tray.ts`:
- Around line 149-156: The current startup chain calls init(config) inside the
.then callback without returning or awaiting it, so any rejection from
init(config) escapes the outer .catch; modify the .then handler to return the
Promise from init(config) (or make the callback async and await init(config)) so
that rejections propagate to the existing .catch; look for the
fetchPreference().then(...) block in tray.ts and ensure init(config) is
returned/awaited there.

---

Duplicate comments:
In `@src/renderer/pages/index/tray.ts`:
- Around line 57-67: The poll allows overlapping runs because tick() is called
without synchronization; change the polling logic to serialize invocations by
adding a reentrancy guard (e.g., a boolean "isPolling" or a simple mutex) used
by tick() and any external triggers so that if isPolling is true you skip or
queue the request; ensure the loop/scheduler waits for tick() to finish before
scheduling the next timeout (i.e., schedule setTimeout after awaiting tick()),
and update any other callers (the unsynchronized start at the other trigger) to
check the same guard or enqueue a single run so appStore.fetchGlobalStat and
taskStore.fetchList cannot run concurrently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 25a7f4c7-864b-420c-adf7-fee32ae8b4b8

📥 Commits

Reviewing files that changed from the base of the PR and between 080f8e7 and 3cb11a9.

📒 Files selected for processing (11)
  • src-tauri/capabilities/tray-panel.json
  • src-tauri/src/lib.rs
  • src-tauri/src/managers/flyout.rs
  • src/renderer/components/Flyout/Flyout.vue
  • src/renderer/components/Flyout/FlyoutTaskItem.vue
  • src/renderer/pages/index/tray.html
  • src/renderer/pages/index/tray.ts
  • src/renderer/styles/flyout.css
  • src/shared/locales/en-US/app.ts
  • src/shared/locales/zh-CN/app.ts
  • src/shared/locales/zh-TW/app.ts

Comment thread src-tauri/capabilities/tray-panel.json

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

4 issues found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/capabilities/tray-panel.json">

<violation number="1" location="src-tauri/capabilities/tray-panel.json:1">
P2: Misleading security description: `tray-panel.json` claims "No filesystem, shell, process, OS, or dialog access", but `default.json` already includes `"tray-panel"` in its `windows` list and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove `"tray-panel"` from `default.json`'s windows array if the intent is genuine restriction, or update the description to match reality.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@@ -0,0 +1,13 @@
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Misleading security description: tray-panel.json claims "No filesystem, shell, process, OS, or dialog access", but default.json already includes "tray-panel" in its windows list and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove "tray-panel" from default.json's windows array if the intent is genuine restriction, or update the description to match reality.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/capabilities/tray-panel.json:

<comment>Misleading security description: `tray-panel.json` claims "No filesystem, shell, process, OS, or dialog access", but `default.json` already includes `"tray-panel"` in its `windows` list and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove `"tray-panel"` from `default.json`'s windows array if the intent is genuine restriction, or update the description to match reality.</comment>

<file context>
@@ -0,0 +1,13 @@
+{
+	"identifier": "tray-panel",
+	"description": "Minimal capabilities for the tray flyout panel. Grants only window management, store access, and the invoke channel needed for flyout commands (show_window, get_global_stat, tell_active, tell_waiting, tell_stopped, pause/unpause/remove task, add_uri, open_path, reveal_in_folder). No filesystem, shell, process, OS, or dialog access.",
+	"windows": ["tray-panel"],
+	"permissions": [
+		"core:default",
+		"core:window:allow-show",
+		"core:window:allow-hide",
+		"core:window:allow-set-focus",
</file context>

Comment thread src/renderer/components/Flyout/Flyout.vue
Comment thread src-tauri/src/managers/flyout.rs
Comment thread src-tauri/src/lib.rs Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/capabilities/tray-panel.json">

<violation number="1" location="src-tauri/capabilities/tray-panel.json:1">
P2: Misleading security description: `tray-panel.json` claims "No filesystem, shell, process, OS, or dialog access", but `default.json` already includes `"tray-panel"` in its `windows` list and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove `"tray-panel"` from `default.json`'s windows array if the intent is genuine restriction, or update the description to match reality.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src-tauri/src/managers/flyout.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/renderer/pages/index/tray.ts (1)

36-70: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Unify poll concurrency guard across both poll paths.

isPolling protects tick(), but loop() bypasses it and can still overlap the same fetches with tick(). The race source is split polling logic.

Proposed fix
 let timer: number | null = null;
 let running = false;
 let isPolling = false;

+const pollOnce = async () => {
+  if (document.hidden || isPolling) {
+    return;
+  }
+  isPolling = true;
+  try {
+    await appStore.fetchGlobalStat();
+    await taskStore.fetchList();
+  } catch (err) {
+    logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
+  } finally {
+    isPolling = false;
+  }
+};
+
 const loop = async () => {
   if (!running || document.hidden) {
     scheduleNext();
     return;
   }
-  try {
-    await appStore.fetchGlobalStat();
-    await taskStore.fetchList();
-  } catch (err) {
-    logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
-  }
+  await pollOnce();
   scheduleNext();
 };

 const tick = async () => {
-  if (document.hidden || isPolling) {
-    return;
-  }
-  isPolling = true;
-  try {
-    await appStore.fetchGlobalStat();
-    await taskStore.fetchList();
-  } catch (err) {
-    logger.warn("[Risuko] flyout poll failed:", (err as Error).message);
-  } finally {
-    isPolling = false;
-  }
+  await pollOnce();
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/pages/index/tray.ts` around lines 36 - 70, The loop() and tick()
functions both perform the same fetches but only tick() uses the isPolling
guard, causing overlapping polls; refactor so both paths share a single guarded
polling routine (e.g. extract a doPoll/performPoll function) that checks and
sets isPolling (or uses an equivalent atomic guard) and does the await
appStore.fetchGlobalStat() and await taskStore.fetchList(), then have loop() and
tick() call that shared function and scheduleNext() as before; ensure the guard
is set before the awaits and cleared in finally so no concurrent fetches occur.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/renderer/components/Flyout/Flyout.vue`:
- Around line 316-321: The logger.warn call in the Flyout.vue catch block is
exposing the raw user-provided uri; modify the catch to avoid logging full URIs
by removing the raw uri from the log and instead logging a redacted/hashed
identifier or a static message. Update the block around taskStore.addUri and
logger.warn so errors still push the err.message into errors, but logger.warn
only includes a safe placeholder or derived non-sensitive token (e.g., a
truncated/hashed uri or "REDACTED_URI") along with the error object; ensure
references remain to taskStore.addUri, errors, and logger.warn so the change is
localized.

---

Duplicate comments:
In `@src/renderer/pages/index/tray.ts`:
- Around line 36-70: The loop() and tick() functions both perform the same
fetches but only tick() uses the isPolling guard, causing overlapping polls;
refactor so both paths share a single guarded polling routine (e.g. extract a
doPoll/performPoll function) that checks and sets isPolling (or uses an
equivalent atomic guard) and does the await appStore.fetchGlobalStat() and await
taskStore.fetchList(), then have loop() and tick() call that shared function and
scheduleNext() as before; ensure the guard is set before the awaits and cleared
in finally so no concurrent fetches occur.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 27d3f121-4465-4488-85d8-6d4a76c3dcc7

📥 Commits

Reviewing files that changed from the base of the PR and between 3cb11a9 and 84091b1.

📒 Files selected for processing (5)
  • src-tauri/capabilities/tray-panel.json
  • src-tauri/src/lib.rs
  • src-tauri/src/managers/flyout.rs
  • src/renderer/components/Flyout/Flyout.vue
  • src/renderer/pages/index/tray.ts

Comment thread src/renderer/components/Flyout/Flyout.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/src/utils/run_mode.rs`:
- Around line 4-7: is_tray_mode references RUN_MODE_TRAY and
RUN_MODE_HIDE_TRAY_LEGACY but those constants are currently only defined under
#[cfg(target_os = "macos")], causing cross-target compile failures; remove the
macOS-only cfg on the constants so RUN_MODE_TRAY and RUN_MODE_HIDE_TRAY_LEGACY
are defined for all targets (keep their numeric values), ensuring is_tray_mode
can compile anywhere without changing its logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 388dcfe5-8dcf-43ed-9d05-6e5c988f3863

📥 Commits

Reviewing files that changed from the base of the PR and between 84091b1 and 2c3842a.

📒 Files selected for processing (7)
  • src-tauri/src/commands/app_cmds.rs
  • src-tauri/src/lib.rs
  • src-tauri/src/managers/flyout.rs
  • src-tauri/src/utils/mod.rs
  • src-tauri/src/utils/run_mode.rs
  • src/renderer/components/Flyout/Flyout.vue
  • src/renderer/pages/index/tray.ts

Comment thread src-tauri/src/utils/run_mode.rs Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src-tauri/capabilities/tray-panel.json">

<violation number="1" location="src-tauri/capabilities/tray-panel.json:1">
P2: Misleading security description: `tray-panel.json` claims "No filesystem, shell, process, OS, or dialog access", but `default.json` already includes `"tray-panel"` in its `windows` list and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove `"tray-panel"` from `default.json`'s windows array if the intent is genuine restriction, or update the description to match reality.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/renderer/components/Flyout/Flyout.vue Outdated
@YueMiyuki
YueMiyuki merged commit cce584f into master Jun 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

next The "next" steps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant