Skip to content

feat(tray): Dynamic tray icon with primary progress bars + about dialog#51

Merged
robinebers merged 2 commits into
mainfrom
feat/dynamic-tray-icon
Feb 3, 2026
Merged

feat(tray): Dynamic tray icon with primary progress bars + about dialog#51
robinebers merged 2 commits into
mainfrom
feat/dynamic-tray-icon

Conversation

@robinebers
Copy link
Copy Markdown
Owner

@robinebers robinebers commented Feb 3, 2026

Summary

  • Display up to 4 usage bars in the system tray icon from enabled plugins' primary progress metrics
  • Plugins can mark one progress line as primary: true to appear in the tray
  • Bars update on probe results (debounced 500ms) and settings changes (debounced 2s)

Description

  • Add primary: boolean field to plugin manifest lines (Rust + TS)
  • Normalize primary flags during load (only type: "progress" lines, first wins)
  • New tray-bars-icon.ts for SVG generation and canvas rasterization
  • New tray-primary-progress.ts for extracting bar data from plugin states
  • Revert to packaged gauge icon when no primary bars available
  • Update all bundled plugins with primary: true on Session/Plan usage
  • Comprehensive test coverage for manifest normalization and tray rendering logic

Test plan

  • Verify tray shows bars when plugins with primary: true are enabled
  • Verify bars update after probe completes
  • Verify disabling all primary plugins reverts to gauge icon
  • Run bun test to verify all tests pass

Made with Cursor


Note

Medium Risk
Adds new tray/icon rendering and plugin-manifest schema behavior across Rust + frontend, plus updates bundled plugins’ auth/refresh flows; regressions would mainly show up as broken tray updates or failed usage probes.

Overview
Adds a dynamic system tray icon that can render up to 4 horizontal usage bars derived from enabled plugins’ primary progress metric, with debounced updates on probe results and settings changes and a fallback back to the packaged gauge icon when no primaries are available.

Extends the plugin manifest schema with an optional lines[].primary flag, normalizes it at plugin-load time (only progress, first wins), and exposes primaryProgressLabel to the frontend via list_plugins; bundled plugins (claude, codex, cursor) mark a primary line accordingly.

Introduces new tray utilities (tray-bars-icon, tray-primary-progress) and broad test coverage, plus a small UX addition: an About dialog opened from the footer version label and additional navigation/detail-view + error-logging regression tests.

Written by Cursor Bugbot for commit e6d9617. This will update automatically on new commits. Configure here.


Summary by cubic

Adds a dynamic tray icon that shows up to 4 usage bars from enabled plugins’ primary progress metrics for quick, glanceable usage. The icon updates on probe results and settings changes, falls back to the default gauge when no bars are available, and avoids race conditions during updates.

  • New Features

    • Plugin schema: add primary on progress lines; normalize so only the first progress primary is used (exposed as primary_progress_label).
    • Tray rendering: generate SVG bars and rasterize to a tray image sized for device pixel ratio; set as a template icon with packaged gauge fallback.
    • App logic: extract primary bars from plugin state; debounce updates (probe 500ms, settings 2s); bundled plugins updated to mark primary usage.
  • Bug Fixes

    • Prevent race conditions by stabilizing the tray update scheduler and reading state from refs; coalesce timers.
    • Initialize tray handle before first paint and restore the gauge icon reliably; log failures when tray handle or resources are unavailable.

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

Display up to 4 usage bars in the system tray icon from enabled plugins' primary progress metrics. Allows users to see usage at a glance without opening the app.

- Add `primary: boolean` field to plugin manifest lines (Rust + TS)
- Normalize primary flags (only progress lines, first wins)
- New tray-bars-icon.ts for SVG generation and rasterization
- New tray-primary-progress.ts for extracting bar data from plugin states
- Debounced tray updates on probe results (500ms) and settings changes (2s)
- Revert to packaged gauge icon when no primary bars available
- Update all bundled plugins with `primary: true` on Session/Plan usage
- Comprehensive test coverage for manifest normalization and tray logic

Co-authored-by: Cursor <cursoragent@cursor.com>
@macroscopeapp
Copy link
Copy Markdown

macroscopeapp Bot commented Feb 3, 2026

Render up to 4 primary progress bars in the system tray and add tray behavior text to About dialog via App.tsx tray icon updates and manifest primary flags

Add primary progress flags in plugin manifests and surface primaryProgressLabel through Tauri, compute primary bars from probe data, and render a rasterized tray bars icon from SVG; update About dialog copy and declare tray/image capabilities.

📍Where to Start

Start with tray update flow in scheduleTrayIconUpdate within src/App.tsx, then review primary progress selection in src/lib/tray-primary-progress.ts and icon rendering in src/lib/tray-bars-icon.ts.


Macroscope summarized e6d9617.

@robinebers robinebers changed the title feat(tray): Dynamic tray icon with primary progress bars feat(tray): Dynamic tray icon with primary progress bars + about dialog Feb 3, 2026
Comment thread src/App.tsx
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 23987cfc0d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/App.tsx Outdated
Comment on lines +83 to +90
trayUpdateTimerRef.current = window.setTimeout(() => {
trayUpdateTimerRef.current = null
if (trayUpdatePendingRef.current) return
trayUpdatePendingRef.current = true

// Execute directly (no requestAnimationFrame - it's throttled when panel is hidden)
const tray = trayRef.current
trayUpdatePendingRef.current = false
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 Badge Prevent stale tray icons from out-of-order renders

trayUpdatePendingRef is cleared before the async renderTrayBarsIcon finishes, so rapid successive updates (e.g., settings change followed by probe results) can run concurrently; if the earlier render resolves last, it overwrites the newer icon and the tray shows stale bars until the next update. Keeping the pending flag (or a sequence token) until setIcon completes would prevent out-of-order icon updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

3 issues found across 24 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="src/lib/tray-bars-icon.ts">

<violation number="1" location="src/lib/tray-bars-icon.ts:7">
P2: Dead code: `Uint8ClampedArray` is not an instance of `Uint8Array` (they're sibling TypedArray classes), so this condition is always false. Simplify by removing the unnecessary check.</violation>
</file>

<file name="src-tauri/src/plugin_engine/manifest.rs">

<violation number="1" location="src-tauri/src/plugin_engine/manifest.rs:183">
P2: Tests duplicate the normalization logic instead of testing the actual implementation. If `load_single_plugin`'s normalization changes, these tests won't catch regressions. Consider extracting the normalization into a separate function (e.g., `normalize_primary_flags(manifest: &mut PluginManifest)`) that both `load_single_plugin` and tests can call.</violation>
</file>

<file name="src/App.tsx">

<violation number="1" location="src/App.tsx:90">
P2: The `trayUpdatePendingRef` flag is ineffective - it's reset synchronously before the async operations (`renderTrayBarsIcon`, `tray.setIcon`) complete. Move `trayUpdatePendingRef.current = false` into the `.then()` or `.finally()` block to properly guard against concurrent updates.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread src/lib/tray-bars-icon.ts Outdated
"#,
);

// Run the same normalization logic used on load.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Feb 3, 2026

Choose a reason for hiding this comment

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

P2: Tests duplicate the normalization logic instead of testing the actual implementation. If load_single_plugin's normalization changes, these tests won't catch regressions. Consider extracting the normalization into a separate function (e.g., normalize_primary_flags(manifest: &mut PluginManifest)) that both load_single_plugin and tests can call.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src-tauri/src/plugin_engine/manifest.rs, line 183:

<comment>Tests duplicate the normalization logic instead of testing the actual implementation. If `load_single_plugin`'s normalization changes, these tests won't catch regressions. Consider extracting the normalization into a separate function (e.g., `normalize_primary_flags(manifest: &mut PluginManifest)`) that both `load_single_plugin` and tests can call.</comment>

<file context>
@@ -94,3 +129,117 @@ fn load_single_plugin(
+            "#,
+        );
+
+        // Run the same normalization logic used on load.
+        let mut seen_primary_progress = false;
+        for line in manifest.lines.iter_mut() {
</file context>
Fix with Cubic

Comment thread src/App.tsx Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Comment thread src/App.tsx
Comment thread src/App.tsx Outdated
Comment thread src/App.tsx
- Move trayUpdatePendingRef reset to .finally() after async work completes
- Store state in refs so scheduleTrayIconUpdate callback is stable (fixes debounce)
- Handle gauge icon restore path properly with Promise.all
- Remove dead code in rgbaToImageDataBytes (Uint8ClampedArray instanceof check)

Co-authored-by: Cursor <cursoragent@cursor.com>
@robinebers robinebers merged commit b2495ad into main Feb 3, 2026
3 checks passed
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.

1 participant