feat(downloads): Chrome-style downloads indicator in the titlebar - #3441
Conversation
A pill button appears in the titlebar when a download starts, showing live aggregate progress across active downloads. Clicking it opens a recent-downloads popup (5 latest, session-scoped) with pause/resume/ cancel for active items, click-to-reveal for completed ones, muted Canceled/Failed rows for terminal states, a dismiss button that hides the pill until a new download starts, and a link to the full Downloads manager. Mounted next to UpdateLabel in all four platform/layout branches of Shell. Also fixes formatBytes(0) returning '???' (Math.log(0) is -Infinity), which surfaced as '??? of 901.8 kB' for just-started downloads. i18n: new tabBar.downloads namespace in en and all 16 locales.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (6)
WalkthroughAdds a tab-bar downloads indicator with localized controls, progress display, dismissal, download actions, shell integration across platforms, and tests. Updates zero-byte formatting to return a base-unit value instead of a sentinel. ChangesDownloads indicator
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant DownloadsStore
participant DownloadsIndicator
participant DownloadsIndicatorItem
participant ElectronIPC
DownloadsStore->>DownloadsIndicator: provide download state
DownloadsIndicator->>DownloadsIndicatorItem: render download data
DownloadsIndicatorItem->>ElectronIPC: pause, resume, cancel, or show folder
DownloadsIndicator->>DownloadsStore: dispatch show-all downloads
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/ui/components/TopBar/DownloadsIndicator.spec.tsx (2)
112-138: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename this test to match what it verifies.
The mock at lines 14-26 replaces
t, so the real i18next formatter andformatBytesnever run. The assertion at line 131 therefore cannot detect the'???'sentinel. What this test proves is thatreceivedBytesandtotalBytesreach the interpolation options. The sentinel behavior is covered insrc/i18n/__tests__/common.spec.ts.💚 Proposed rename
- it('renders progress size text for a 0-byte-received download without the ??? sentinel', async () => { + it('passes 0 received bytes through to the progress size interpolation', async () => {🤖 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/ui/components/TopBar/DownloadsIndicator.spec.tsx` around lines 112 - 138, Rename the test around the DownloadsIndicator progress-size case to reflect that it verifies receivedBytes and totalBytes are passed as interpolation options, not that formatting avoids the '???' sentinel. Keep the existing assertions for those serialized values, and leave sentinel-formatting coverage to the common i18n tests.
153-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the remaining item actions and the list cap.
This suite covers
downloads/pauseanddownloads/show-in-folder.downloads/resumefrom thepausedstate,downloads/cancel, and theMAX_RECENT_DOWNLOADScap of five items have no coverage. Those paths carry user-visible IPC side effects.🤖 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/ui/components/TopBar/DownloadsIndicator.spec.tsx` around lines 153 - 167, Extend the DownloadsIndicator test suite with cases for the paused-state resume action and the cancel action, asserting invokeMock receives downloads/resume or downloads/cancel with the item ID. Add coverage for the MAX_RECENT_DOWNLOADS limit by rendering more than five downloads and verifying only five recent items are displayed.
🤖 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/i18n/ja.i18n.json`:
- Around line 292-303: Update the downloads.canceled translation in the Japanese
i18n resource to use wording distinct from the downloads.cancel action, while
leaving the cancel button label unchanged so terminal canceled downloads are
clearly distinguishable.
In `@src/i18n/pl.i18n.json`:
- Around line 335-346: Update the “canceled” and “failed” values in the
“downloads” localization object to use impersonal Polish wording, avoiding
feminine adjective forms and agreeing naturally with the download status labels.
Confirm the revised wording with a Polish speaker.
In `@src/i18n/sv.i18n.json`:
- Around line 522-533: Update the downloads translations to reuse the
established terminology: in src/i18n/sv.i18n.json lines 522-533, replace “Hämtar
filer”, “Hämtningar”, and “Visa alla hämtningar” with the existing “Nedladdning”
wording from line 170; in src/i18n/no.i18n.json lines 544-555, change
downloads.cancel from “Avbryt” to “Kanseller” and downloads.canceled from
“Avbrutt” to “Kansellert”, matching the existing translations at lines 202 and
206.
In `@src/ui/components/TopBar/DownloadsIndicator.tsx`:
- Around line 222-223: Align the DownloadsIndicator popup semantics with its
trigger: either add role="dialog" and an accessible label to the popup container
rendered near the isOpen content, or change aria-haspopup from "dialog" and
update the container to a correctly structured menu. Preserve aria-expanded
behavior and ensure the chosen role matches the popup’s actual interaction
model.
In `@src/ui/components/TopBar/DownloadsIndicatorItem.tsx`:
- Around line 47-60: Update the sizeLabel useMemo so the state === 'completed'
branch runs before the !totalBytes guard, allowing completed zero-byte downloads
to use i18n.format(totalBytes, 'byteSize'). Preserve the existing terminal-error
handling and progress-size behavior for non-completed downloads.
---
Nitpick comments:
In `@src/ui/components/TopBar/DownloadsIndicator.spec.tsx`:
- Around line 112-138: Rename the test around the DownloadsIndicator
progress-size case to reflect that it verifies receivedBytes and totalBytes are
passed as interpolation options, not that formatting avoids the '???' sentinel.
Keep the existing assertions for those serialized values, and leave
sentinel-formatting coverage to the common i18n tests.
- Around line 153-167: Extend the DownloadsIndicator test suite with cases for
the paused-state resume action and the cancel action, asserting invokeMock
receives downloads/resume or downloads/cancel with the item ID. Add coverage for
the MAX_RECENT_DOWNLOADS limit by rendering more than five downloads and
verifying only five recent items are displayed.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 43084fc0-2efe-490b-855f-3e068954eec3
📒 Files selected for processing (23)
src/i18n/__tests__/common.spec.tssrc/i18n/common.tssrc/i18n/de-DE.i18n.jsonsrc/i18n/en.i18n.jsonsrc/i18n/es.i18n.jsonsrc/i18n/fi.i18n.jsonsrc/i18n/fr.i18n.jsonsrc/i18n/hu.i18n.jsonsrc/i18n/it-IT.i18n.jsonsrc/i18n/ja.i18n.jsonsrc/i18n/no.i18n.jsonsrc/i18n/pl.i18n.jsonsrc/i18n/pt-BR.i18n.jsonsrc/i18n/ru.i18n.jsonsrc/i18n/sv.i18n.jsonsrc/i18n/tr-TR.i18n.jsonsrc/i18n/uk-UA.i18n.jsonsrc/i18n/zh-CN.i18n.jsonsrc/i18n/zh-TW.i18n.jsonsrc/ui/components/Shell/index.tsxsrc/ui/components/TopBar/DownloadsIndicator.spec.tsxsrc/ui/components/TopBar/DownloadsIndicator.tsxsrc/ui/components/TopBar/DownloadsIndicatorItem.tsx
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript for new code unless explicitly told otherwise.
Use Fuselage components from@rocket.chat/fuselagefor UI work unless the design requires something Fuselage does not provide.
CheckTheme.d.tsfor valid color tokens before using Fuselage colors.
Verify library props, APIs, and tokens against official docs or local.d.tsfiles instead of assuming.
Use React functional components with hooks.
Redux actions follow FSA shape.
Use camelCase for file names and PascalCase for components.
Prefer clear names over unnecessary comments.
Prefer editing existing files over creating new abstractions unless the new abstraction removes real complexity or matches an existing pattern.
**/*.{ts,tsx}: Use TypeScript for all new code unless explicitly told otherwise.
Use Fuselage components for all UI work; create custom components only when Fuselage lacks the required functionality.
Import Fuselage components from@rocket.chat/fuselage.
Use only valid color tokens documented byTheme.d.ts.
Use optional chaining with fallbacks for platform-specific APIs, especially Linux-only process APIs such asprocess.getuid(),getgid(),geteuid(), andgetegid().
Use TypeScript strict mode.
Redux actions must follow the Flux Standard Action pattern.
Use camelCase for file names and PascalCase for component names.
Avoid unnecessary comments; prefer self-documenting code through clear naming.
Do not commit or push without explicit user permission.
Verify library APIs, props, tokens, and types against official documentation and.d.tsfiles instead of assuming they are valid.
Files:
src/i18n/__tests__/common.spec.tssrc/ui/components/TopBar/DownloadsIndicator.tsxsrc/i18n/common.tssrc/ui/components/Shell/index.tsxsrc/ui/components/TopBar/DownloadsIndicator.spec.tsxsrc/ui/components/TopBar/DownloadsIndicatorItem.tsx
**/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Renderer specs use
*.spec.ts/*.spec.tsx.
Files:
src/i18n/__tests__/common.spec.tssrc/ui/components/TopBar/DownloadsIndicator.spec.tsx
src/*/*/*.spec.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Renderer specs must live in a Jest-matched nested path, such as
src/<module>/<subdir>/*.spec.ts(x); flatsrc/<module>/*.spec.tsfiles are not discovered by the currenttestMatch.
Files:
src/i18n/__tests__/common.spec.ts
**/*.spec.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Use
*.spec.tsfor renderer process tests.
Files:
src/i18n/__tests__/common.spec.ts
src/**/*.{spec.ts,spec.tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Renderer test files should be placed in nested module paths such as
src/<module>/<subdir>/*.spec.ts(x)so Jest discovers them.
Files:
src/i18n/__tests__/common.spec.tssrc/ui/components/TopBar/DownloadsIndicator.spec.tsx
**/*.{tsx,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use React functional components with hooks.
Files:
src/ui/components/TopBar/DownloadsIndicator.tsxsrc/ui/components/Shell/index.tsxsrc/ui/components/TopBar/DownloadsIndicator.spec.tsxsrc/ui/components/TopBar/DownloadsIndicatorItem.tsx
🧠 Learnings (1)
📚 Learning: 2026-06-26T18:14:15.295Z
Learnt from: jeanfbrito
Repo: RocketChat/Rocket.Chat.Electron PR: 3358
File: src/i18n/it-IT.i18n.json:39-42
Timestamp: 2026-06-26T18:14:15.295Z
Learning: In the i18n JSON files, the translation key `minimizeOnClose.disabledHint` is intentionally displayed when `isTrayIconEnabled` is true and the minimize-on-close toggle is disabled. The hint text should therefore instruct the user to disable the tray icon to make the setting available. During reviews, don’t “correct” this translation for seeming mismatches with the toggle state—first confirm it matches the component’s intended behavior; only update the wording if the underlying product logic/UX requirement changes.
Applied to files:
src/i18n/zh-CN.i18n.jsonsrc/i18n/it-IT.i18n.jsonsrc/i18n/zh-TW.i18n.jsonsrc/i18n/en.i18n.jsonsrc/i18n/es.i18n.jsonsrc/i18n/fr.i18n.jsonsrc/i18n/no.i18n.jsonsrc/i18n/pt-BR.i18n.jsonsrc/i18n/ja.i18n.jsonsrc/i18n/sv.i18n.jsonsrc/i18n/fi.i18n.jsonsrc/i18n/de-DE.i18n.jsonsrc/i18n/ru.i18n.jsonsrc/i18n/tr-TR.i18n.jsonsrc/i18n/uk-UA.i18n.jsonsrc/i18n/hu.i18n.jsonsrc/i18n/pl.i18n.json
🔇 Additional comments (18)
src/i18n/__tests__/common.spec.ts (1)
59-64: LGTM!src/i18n/it-IT.i18n.json (1)
74-85: LGTM!src/i18n/pt-BR.i18n.json (1)
502-513: LGTM!src/i18n/ru.i18n.json (1)
471-482: LGTM!src/ui/components/TopBar/DownloadsIndicator.tsx (2)
21-105: LGTM!
148-192: 🎯 Functional CorrectnessNo change needed. The store converts Electron
DownloadItem.getStartTime()seconds into milliseconds before comparison.src/ui/components/Shell/index.tsx (1)
24-24: LGTM!Also applies to: 81-81, 91-91, 103-108, 117-117
src/i18n/common.ts (1)
15-21: 🎯 Functional CorrectnessKeep the zero-byte branch as is.
src/i18n/en.i18n.json (1)
545-557: LGTM!src/i18n/de-DE.i18n.json (1)
466-478: LGTM!src/i18n/es.i18n.json (1)
496-508: LGTM!src/i18n/fi.i18n.json (1)
464-476: LGTM!src/i18n/fr.i18n.json (1)
465-477: LGTM!src/i18n/hu.i18n.json (1)
653-665: LGTM!src/i18n/tr-TR.i18n.json (1)
310-322: LGTM!src/i18n/uk-UA.i18n.json (1)
303-315: LGTM!src/i18n/zh-CN.i18n.json (1)
346-358: LGTM!src/i18n/zh-TW.i18n.json (1)
268-280: LGTM!
- show size label for completed zero-byte downloads - add role=dialog and accessible name to the downloads popup - ja: distinguish canceled status from cancel action - pl: use impersonal forms for canceled/failed statuses - sv/no: reuse existing downloads terminology
* docs: desktop UI guidelines and dev-app-verify skill Distills the lessons from the downloads indicator work (PRs #3441 and #3443) into permanent project knowledge: - docs/desktop-ui-guidelines.md: Fuselage token semantics and traps (runtime-injected tokens, status-background vs bullet vs badge, the icon-button color chain), icon/button geometry facts, the .18s micro-interaction standard, tab bar button conventions (the 0.6 dimming rule and its override, square icon-only buttons, compact titlebar sizing, flex-flow traps), and the SVG transform-origin clipping pitfall - skills/dev-app-verify/SKILL.md: ready-made script to drive and screenshot the running dev app through the port-9339 inspector, with the three pitfalls that produce false alarms (occlusion, watcher restarts, singleton wedges) — plain Markdown, usable by any agent like skills/desktop-qa-flows - CLAUDE.md: pointers to both from the UI and Testing sections * docs: point AGENTS.md at the UI guidelines and dev-app-verify skill CLAUDE.md got the pointers but AGENTS.md — what non-Claude agents read — did not; both files now direct agents to docs/desktop-ui-guidelines.md from UI Work and to skills/dev-app-verify from Testing. * chore: share Claude Code skills with the repo The blanket .claude/ gitignore was hiding project knowledge: CLAUDE.md references .claude/skills/gitnexus/* six times, and skills like electron-build, ship-release, and boot-wedge-debug encode repo processes any Claude Code user needs. The ignore is now selective — only genuinely personal/machine state stays out (settings.local.json, mytasks/, worktrees/, rules/, commands/, scheduled_tasks.lock) — and the skills plus the hooks-only settings.json are committed. The dev-app-verify Claude stub points at the canonical skills/dev-app-verify/SKILL.md so the two copies cannot drift. * docs: AGENTS.md becomes the canonical agent guide, CLAUDE.md imports it The two files carried parallel, drifting versions of the same guidance that had to be edited twice. AGENTS.md is now the single source (union of both, restoring details each copy had dropped: KMS two-phase rationale, worktree commands, working principles, the screen-capture VM constraint, the GitNexus block with its tool-managed markers), and CLAUDE.md is a thin @AGENTS.md import with a marked spot for genuinely Claude-specific additions. * docs: CLAUDE.md stub directs writes to AGENTS.md explicitly The stub's guidance was an HTML comment a rushed session could ignore; it is now a visible imperative rule — loaded into every session's context — telling agents to record shared guidance in AGENTS.md and reserve this file for genuinely Claude-only content. * docs: reindexing guidance and gitnexus-interference pitfall - AGENTS.md Working Principles: reindex with --index-only at quiet points — a plain background analyze rewrites the tracked gitnexus doc blocks (stats churn) and mutates worktree git state (drops staged index entries, touches watched sources restarting the dev app) - dev-app-verify skill: the same interference documented as pitfall 4 * chore: read-only permission allowlist for agent verification loops Derived from transcript analysis (29 recent sessions): the DoD verification commands agents run constantly (yarn test/lint, npx tsc --noEmit, the gitnexus runner) and the read-only GitNexus/context-mode MCP tools that AGENTS.md mandates. No interpreters, task-runner wildcards, or mutating commands — those still prompt. * docs: Fuselage Select needs a react-aria-recognized label Salvaged from the retired OpenWolf buglog: Fuselage Select is backed by react-aria useSelect, which only accepts a visible label, aria-label, or aria-labelledby — a labeled wrapper does not satisfy it. * docs: address CodeRabbit review feedback - ship-release: semver-sorted baseline tags, worktree-pinned commands, approval gate before first push, fail-closed manual tag fallback, prerelease flag applied while still a draft - dev-app-verify: per-request CDP timeouts + pending rejection on socket loss, fail-closed menu trigger (developerMode gate + item checks), stable root-window predicate, macOS-only scope note - electron-bump: downgrade guard, unused-API list as hint not exclusion, drop unresolved PR placeholder from commit message - desktop-ui-guidelines: min-width 3ch wording, progress normalization note - AGENTS.md: drop point-in-time GitNexus counts - CLAUDE.md: route shared vs Claude-only guidance by content - markdown fence language identifiers across skill docs
What
Screen.Recording.2026-08-06.at.4.06.12.PM.mov
Adds a Chrome-style downloads indicator to the titlebar, mounted next to the update label in all four platform/layout branches (Windows/macOS × tabs/no-tabs).
Pill button
UpdateLabel; shows a count when more than one download is active.Popup (Fuselage Dropdown, same backdrop/Escape pattern as the update panel)
downloads/*IPC channels — no new IPC).downloads/show-in-folder), with a "Show in folder" tooltip/aria-label.banicon and are not clickable; interrupted/expired rows show "Failed" (Chrome parity).Bug fix:
formatBytes(0)insrc/i18n/common.tsreturned'???'becauseMath.log(0)is-Infinity, so just-started downloads rendered "??? of 901.8 kB". Now renders "0 B". The Downloads manager masked this by hiding the size text at 0 bytes.Why
Downloads currently give no ambient feedback — the only way to see progress is opening the Downloads manager. This mirrors the browser pattern users already know, using state and IPC channels that already exist (
state.downloadsvia redux-over-IPC).i18n
New
tabBar.downloadsnamespace (tooltip, title, showAll, pause, resume, cancel, showInFolder, percent, canceled, failed, dismiss) in English and translated in all 16 other locales, reusing each locale's established downloads terminology.Verification
npx tsc --noEmit— 0 errorsyarn lint— cleanyarn test --runTestsByPath src/ui/components/TopBar/DownloadsIndicator.spec.tsx src/ui/components/TopBar/UpdateLabel.spec.tsx— 40/40 pass (13 new specs covering visibility rules, progress text, popup actions, canceled/failed rendering, dismiss behavior)yarn test --runTestsByPath src/i18n/__tests__/common.spec.ts— 32/32 pass (0-byte fix)Summary by CodeRabbit
New Features
Localization
Bug Fixes
0 byteinstead of an unknown value.