feat(ui): implement Download Details Panel - #18
Conversation
Right sidebar panel showing detailed info for the selected download: 8 sections (File Info, Metrics, Segments, Speed History, Source, Integrity, Module, Logs) with real-time updates from Zustand store, SVG speed sparkline, segment visualization, and scrollable logs.
📝 WalkthroughWalkthroughAdded a right-side Download Details Panel with eight sections (file info, metrics, segments, speed history, source, integrity, module, logs). Introduced hooks Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as DownloadsView
participant Store as uiStore / downloadStore
participant Panel as DownloadDetailsPanel
participant Hook as useDownloadDetail
participant SpeedHook as useSpeedHistory
participant Tauri as Tauri API
User->>UI: Click download row
UI->>Store: selectDownload(id)
Store->>Store: selectedDownloadId = id\ndetailsPanelOpen = true
Store-->>Panel: state update (selectedDownloadId, detailsPanelOpen)
Panel->>Hook: useDownloadDetail(downloadId)
Hook->>Tauri: invoke('query_download_detail', { id })
Tauri-->>Hook: DownloadDetailView
Hook-->>Panel: detail data
Panel->>Panel: render sections (FileInfo, Metrics, Segments, Speed, Source, Integrity, Module, Logs)
Panel->>SpeedHook: useSpeedHistory(downloadId)
SpeedHook->>Store: read progressMap every 2s
Store-->>SpeedHook: progress entry
SpeedHook-->>Panel: speed samples
Panel->>Panel: update SpeedSparkline
User->>Panel: Click close (X)
Panel->>Store: selectDownload(null)
Store->>Store: selectedDownloadId = null\ndetailsPanelOpen = false
Panel->>Panel: unmount
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Greptile SummaryThis PR introduces a Download Details Panel — a fixed right-side sidebar with 8 collapsible sections (File Info, Metrics, Segments, Speed History, Source, Integrity, Module, Logs) — plus two supporting hooks ( A couple of issues are worth addressing before merge:
Confidence Score: 4/5Mostly safe to merge; one P1 concern (hardcoded SHA-256) should be fixed before shipping to avoid showing users incorrect integrity metadata. The implementation is solid overall — hooks are correct, the store change is clean, and all 178 tests pass. The hardcoded SHA-256 algorithm label in IntegritySection is a P1 because it will actively display wrong information to users if the backend ever uses a different algorithm, and the DownloadDetailView type provides no field to resolve the actual algorithm. The Connections mislabelling is a P2 cosmetic/UX issue. src/views/DownloadDetailsPanel/IntegritySection.tsx — hardcoded SHA-256 algorithm label needs to be removed or made conditional on actual data. Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant DownloadsTable
participant uiStore
participant DownloadDetailsPanel
participant useDownloadDetail
participant useSpeedHistory
participant TauriBackend
User->>DownloadsTable: click row
DownloadsTable->>uiStore: selectDownload(id)
uiStore-->>uiStore: set selectedDownloadId=id, detailsPanelOpen=true
uiStore-->>DownloadDetailsPanel: detailsPanelOpen=true, selectedDownloadId=id
DownloadDetailsPanel->>useDownloadDetail: useDownloadDetail(id)
useDownloadDetail->>TauriBackend: invoke(query_download_detail, {id})
TauriBackend-->>useDownloadDetail: DownloadDetailView
useDownloadDetail-->>DownloadDetailsPanel: data
DownloadDetailsPanel->>useSpeedHistory: useSpeedHistory(id)
loop every 2s
useSpeedHistory->>uiStore: downloadStore.getState().progressMap[id]
uiStore-->>useSpeedHistory: SpeedSample
useSpeedHistory-->>DownloadDetailsPanel: SpeedSample[]
end
User->>DownloadDetailsPanel: click X
DownloadDetailsPanel->>uiStore: setDetailsPanelOpen(false)
uiStore-->>DownloadDetailsPanel: detailsPanelOpen=false, renders null
Reviews (1): Last reviewed commit: "feat(ui): implement Download Details Pan..." | Re-trigger Greptile |
| </div> | ||
| <div> | ||
| <p className="text-muted-foreground">Expected Hash</p> |
There was a problem hiding this comment.
Hardcoded algorithm label is incorrect
"SHA-256" is always shown regardless of what algorithm the backend actually uses. DownloadDetailView has no checksumAlgorithm field, so this will display wrong information whenever the backend uses MD5, SHA-1, or any other algorithm. At minimum, show "—" when the algorithm is unknown, or derive it from the data.
| </div> | |
| <div> | |
| <p className="text-muted-foreground">Expected Hash</p> | |
| <div> | |
| <p className="text-muted-foreground">Algorithm</p> | |
| <p className="font-mono">—</p> | |
| </div> |
| <div className="text-muted-foreground">Connections</div> | ||
| <div className="font-mono font-semibold">{download.segments.length}</div> | ||
| </div> | ||
|
|
There was a problem hiding this comment.
"Connections" shows total segments, not active connections
download.segments.length is the total segment count including Completed, Pending, and Error segments. In download manager UIs "Connections" conventionally means currently active connections — i.e. segments in the 'Downloading' state. Showing the total count overstates the number of live connections for any non-trivially progressed download.
| <div className="text-muted-foreground">Connections</div> | |
| <div className="font-mono font-semibold">{download.segments.length}</div> | |
| </div> | |
| <div className="col-span-2 rounded bg-background p-2"> | |
| <div className="text-muted-foreground">Connections</div> | |
| <div className="font-mono font-semibold"> | |
| {download.segments.filter((s) => s.state === 'Downloading').length} | |
| </div> | |
| </div> |
| if (!selectedDownloadId) { | ||
| return ( | ||
| <aside className="w-80 shrink-0 border-l bg-muted/30 p-4 text-center text-sm text-muted-foreground"> | ||
| Select a download to view details | ||
| </aside> | ||
| ); |
There was a problem hiding this comment.
Empty-state aside has no close/dismiss button
When detailsPanelOpen is true but selectedDownloadId is null, the panel renders with no way to dismiss it. This state is unreachable today (because selectDownload sets both atomically), but the defensive branch exists — so it should be consistent with the rest of the panel UX and include a close button to avoid trapping users if the state is ever reachable in the future.
| <p className="text-muted-foreground">Resume Supported</p> | ||
| <input | ||
| type="checkbox" | ||
| disabled | ||
| checked={download.resumeSupported} | ||
| readOnly | ||
| className="mt-1" | ||
| /> |
There was a problem hiding this comment.
Unlabelled checkbox is inaccessible to screen readers
A bare <input type="checkbox" disabled /> without an associated <label> or aria-label won't be announced meaningfully by assistive technology. Consider replacing it with a visible text indicator or adding an aria-label.
| <p className="text-muted-foreground">Resume Supported</p> | |
| <input | |
| type="checkbox" | |
| disabled | |
| checked={download.resumeSupported} | |
| readOnly | |
| className="mt-1" | |
| /> | |
| <input | |
| type="checkbox" | |
| disabled | |
| checked={download.resumeSupported} | |
| readOnly | |
| aria-label="Resume supported" | |
| className="mt-1" | |
| /> |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/hooks/useSpeedHistory.ts (1)
21-33: Sample once immediately to avoid initial 2s blank history.Current logic waits one interval before first datapoint. Triggering one immediate sample improves perceived responsiveness when opening/switching details.
♻️ Suggested refactor
- const interval = setInterval(() => { + const sample = () => { const progress = useDownloadStore.getState().progressMap[downloadId]; const speed = progress?.speedBytesPerSec ?? 0; const now = Date.now(); const cutoff = now - MAX_AGE_MS; @@ setSamples([...samplesRef.current]); - }, SAMPLE_INTERVAL_MS); + }; + + sample(); + const interval = setInterval(sample, SAMPLE_INTERVAL_MS);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useSpeedHistory.ts` around lines 21 - 33, The hook currently waits SAMPLE_INTERVAL_MS before the first datapoint; immediately sample once before starting the setInterval to avoid a 2s blank history: extract the sampling logic that reads useDownloadStore.getState().progressMap[downloadId], computes now, cutoff (MAX_AGE_MS), updates samplesRef.current (filter by s.time > cutoff, append {time: now, speed}, slice(-MAX_SAMPLES)) and calls setSamples([...samplesRef.current]) into a small helper (or call the same block) and invoke it once immediately, then start the existing setInterval that repeats the same logic every SAMPLE_INTERVAL_MS; keep references to samplesRef, downloadId, MAX_AGE_MS, MAX_SAMPLES, and setSamples unchanged.src/hooks/useDownloadDetail.ts (1)
5-10: Consider guarding against emptydownloadId.If
downloadIdis an empty string, the query will still execute and likely fail or return unexpected results. Consider adding anenabledoption to prevent unnecessary requests.♻️ Proposed fix
export function useDownloadDetail(downloadId: string) { return useTauriQuery<DownloadDetailView>( 'query_download_detail', { id: downloadId }, - { queryKey: downloadQueries.detail(downloadId), staleTime: 500 }, + { queryKey: downloadQueries.detail(downloadId), staleTime: 500, enabled: !!downloadId }, ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/useDownloadDetail.ts` around lines 5 - 10, The hook useDownloadDetail currently runs the useTauriQuery even when downloadId is empty; update useDownloadDetail to prevent the query from executing for falsy/empty ids by passing an enabled flag (e.g., enabled: Boolean(downloadId)) into the options object passed to useTauriQuery (preserving existing staleTime and queryKey usage like downloadQueries.detail(downloadId)); reference useDownloadDetail, useTauriQuery, downloadQueries.detail and DownloadDetailView when making the change.src/views/DownloadDetailsPanel/SourceInfoSection.tsx (1)
54-60: Redundant attributes on checkbox; consider a non-interactive indicator.The
readOnlyattribute is redundant whendisabledis already set. Additionally, using a checkbox for a read-only boolean display is unconventional—a text label or icon might be clearer.♻️ Proposed fix using text indicator
<div> <p className="text-muted-foreground">Resume Supported</p> - <input - type="checkbox" - disabled - checked={download.resumeSupported} - readOnly - className="mt-1" - /> + <p className="font-mono">{download.resumeSupported ? 'Yes' : 'No'}</p> </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/DownloadDetailsPanel/SourceInfoSection.tsx` around lines 54 - 60, In SourceInfoSection replace the disabled checkbox input used to show download.resumeSupported with a non-interactive indicator: remove the redundant readOnly attribute (if you keep the input) and preferably replace the <input type="checkbox" ... /> with a plain text/icon element (e.g., a <span> or an SVG icon) that displays "Supported"/"Not supported" or a check/x symbol based on download.resumeSupported; ensure accessibility by adding an aria-label or role="status" so screen readers can read the state, and update any className styling applied to the original input to the new element.src/views/DownloadDetailsPanel/IntegritySection.tsx (1)
15-18: Hardcoded algorithm may not reflect actual checksum type.The algorithm is fixed to "SHA-256", but if the backend supports multiple hash algorithms (MD5, SHA-1, etc.), this could be misleading. Consider deriving the algorithm from the data if available, or clarifying this is an assumed default.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/DownloadDetailsPanel/IntegritySection.tsx` around lines 15 - 18, The UI in IntegritySection currently hardcodes "SHA-256"; update the component to read the actual algorithm from the incoming data (e.g., a prop or object like integrity.algorithm or checksumAlgorithm) and render that value, falling back to "SHA-256" only if no algorithm is provided; update any related prop types or interface (IntegritySection props) so the algorithm field is optional and used in the display, and ensure the displayed label shows the resolved algorithm string rather than a hardcoded literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx`:
- Around line 71-73: The close button in DownloadDetailsPanel is icon-only and
lacks an accessible name; update the Button (the element rendering <X
className="size-3.5" /> and using onClose) to provide an accessible label—e.g.,
add an aria-label like "Close" or include a visually-hidden text node inside the
Button—so assistive technologies can announce the button’s purpose.
In `@src/views/DownloadDetailsPanel/LogsSection.tsx`:
- Around line 9-21: The component currently treats unresolved query data as an
empty state by checking logs directly; update the useTauriQuery call to also
destructure the loading state (e.g., isLoading or isFetching) and change the
render logic in LogsSection (where logs is used) to show a loading indicator or
placeholder when isLoading is true, and only render the "No logs" message when
isLoading is false and logs is empty; reference the useTauriQuery invocation and
the logs variable in your change and ensure the conditional branch inside the
ScrollArea checks isLoading before deciding between loading UI, "No logs", or
the logs list.
In `@src/views/DownloadDetailsPanel/MetricsSection.tsx`:
- Around line 16-17: The computed progressPercent can exceed 0-100 and should be
clamped before render: wrap the existing calculation for progressPercent (the
expression using total, downloaded, and download.progressPercent) with a clamp
to the [0,100] range (e.g., Math.min(Math.max(..., 0), 100)), and apply the same
clamping to the other progress computation referenced at the second occurrence
(around the usage at line ~56) so both places always output a value between 0
and 100.
---
Nitpick comments:
In `@src/hooks/useDownloadDetail.ts`:
- Around line 5-10: The hook useDownloadDetail currently runs the useTauriQuery
even when downloadId is empty; update useDownloadDetail to prevent the query
from executing for falsy/empty ids by passing an enabled flag (e.g., enabled:
Boolean(downloadId)) into the options object passed to useTauriQuery (preserving
existing staleTime and queryKey usage like downloadQueries.detail(downloadId));
reference useDownloadDetail, useTauriQuery, downloadQueries.detail and
DownloadDetailView when making the change.
In `@src/hooks/useSpeedHistory.ts`:
- Around line 21-33: The hook currently waits SAMPLE_INTERVAL_MS before the
first datapoint; immediately sample once before starting the setInterval to
avoid a 2s blank history: extract the sampling logic that reads
useDownloadStore.getState().progressMap[downloadId], computes now, cutoff
(MAX_AGE_MS), updates samplesRef.current (filter by s.time > cutoff, append
{time: now, speed}, slice(-MAX_SAMPLES)) and calls
setSamples([...samplesRef.current]) into a small helper (or call the same block)
and invoke it once immediately, then start the existing setInterval that repeats
the same logic every SAMPLE_INTERVAL_MS; keep references to samplesRef,
downloadId, MAX_AGE_MS, MAX_SAMPLES, and setSamples unchanged.
In `@src/views/DownloadDetailsPanel/IntegritySection.tsx`:
- Around line 15-18: The UI in IntegritySection currently hardcodes "SHA-256";
update the component to read the actual algorithm from the incoming data (e.g.,
a prop or object like integrity.algorithm or checksumAlgorithm) and render that
value, falling back to "SHA-256" only if no algorithm is provided; update any
related prop types or interface (IntegritySection props) so the algorithm field
is optional and used in the display, and ensure the displayed label shows the
resolved algorithm string rather than a hardcoded literal.
In `@src/views/DownloadDetailsPanel/SourceInfoSection.tsx`:
- Around line 54-60: In SourceInfoSection replace the disabled checkbox input
used to show download.resumeSupported with a non-interactive indicator: remove
the redundant readOnly attribute (if you keep the input) and preferably replace
the <input type="checkbox" ... /> with a plain text/icon element (e.g., a <span>
or an SVG icon) that displays "Supported"/"Not supported" or a check/x symbol
based on download.resumeSupported; ensure accessibility by adding an aria-label
or role="status" so screen readers can read the state, and update any className
styling applied to the original input to the new element.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a75b1290-7a0d-4959-8910-8c46136f7b16
📒 Files selected for processing (23)
CHANGELOG.mdsrc/hooks/__tests__/useDownloadDetail.test.tssrc/hooks/__tests__/useSpeedHistory.test.tssrc/hooks/useDownloadDetail.tssrc/hooks/useSpeedHistory.tssrc/stores/uiStore.tssrc/views/DownloadDetailsPanel/DownloadDetailsPanel.tsxsrc/views/DownloadDetailsPanel/FileInfoSection.tsxsrc/views/DownloadDetailsPanel/IntegritySection.tsxsrc/views/DownloadDetailsPanel/LogsSection.tsxsrc/views/DownloadDetailsPanel/MetricsSection.tsxsrc/views/DownloadDetailsPanel/ModuleSection.tsxsrc/views/DownloadDetailsPanel/SegmentVisualization.tsxsrc/views/DownloadDetailsPanel/SourceInfoSection.tsxsrc/views/DownloadDetailsPanel/SpeedSparkline.tsxsrc/views/DownloadDetailsPanel/__tests__/DownloadDetailsPanel.test.tsxsrc/views/DownloadDetailsPanel/__tests__/FileInfoSection.test.tsxsrc/views/DownloadDetailsPanel/__tests__/LogsSection.test.tsxsrc/views/DownloadDetailsPanel/__tests__/MetricsSection.test.tsxsrc/views/DownloadDetailsPanel/__tests__/SegmentVisualization.test.tsxsrc/views/DownloadDetailsPanel/__tests__/SpeedSparkline.test.tsxsrc/views/DownloadDetailsPanel/index.tssrc/views/DownloadsView/DownloadsView.tsx
| const progressPercent = | ||
| total && total > 0 ? (downloaded / total) * 100 : download.progressPercent; |
There was a problem hiding this comment.
Clamp computed progress to [0, 100] before rendering.
At Line 16-17, transient data can produce values above 100. Clamping avoids visual overflow and keeps the progress bar stable.
Suggested fix
- const progressPercent =
- total && total > 0 ? (downloaded / total) * 100 : download.progressPercent;
+ const rawProgressPercent =
+ total && total > 0 ? (downloaded / total) * 100 : download.progressPercent;
+ const progressPercent = Math.max(0, Math.min(100, rawProgressPercent));Also applies to: 56-56
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/views/DownloadDetailsPanel/MetricsSection.tsx` around lines 16 - 17, The
computed progressPercent can exceed 0-100 and should be clamped before render:
wrap the existing calculation for progressPercent (the expression using total,
downloaded, and download.progressPercent) with a clamp to the [0,100] range
(e.g., Math.min(Math.max(..., 0), 100)), and apply the same clamping to the
other progress computation referenced at the second occurrence (around the usage
at line ~56) so both places always output a value between 0 and 100.
There was a problem hiding this comment.
2 issues found across 23 files
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/views/DownloadDetailsPanel/MetricsSection.tsx">
<violation number="1" location="src/views/DownloadDetailsPanel/MetricsSection.tsx:51">
P2: The "Connections" value should represent active connections, but this counts every segment. Filter to `Downloading` segments so the metric reflects live connections.</violation>
</file>
<file name="src/views/DownloadDetailsPanel/FileInfoSection.tsx">
<violation number="1" location="src/views/DownloadDetailsPanel/FileInfoSection.tsx:10">
P2: Extension parsing treats filenames without `.` as if the whole name were an extension, producing incorrect MIME values (e.g. `README` -> `application/readme`).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
- IntegritySection: show algorithm only when checksum is present - MetricsSection: clamp progressPercent to [0,100], filter active connections - DownloadDetailsPanel: add close button to empty state, aria-label on close - SourceInfoSection: replace inaccessible checkbox with Yes/No text - LogsSection: handle loading state separately from empty state - FileInfoSection: fix MIME detection for extensionless filenames - useSpeedHistory: sample immediately on mount (avoid 2s blank) - useDownloadDetail: add enabled guard for empty downloadId
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx`:
- Around line 55-73: The panel header and close control are being removed in the
isLoading and !detail branches; always render the DownloadDetailsPanel header
(and its close button) outside those early returns and only conditionally render
the body content. Move the header JSX (the element that contains the close
control) above the isLoading/detail checks inside the DownloadDetailsPanel
component, keep the loading skeleton when isLoading is true, show a distinct
error message when a fetch error exists (use the existing error/fetchError prop
or state if available, or add one) instead of conflating it with "Download not
found", and only show "Download not found" when the fetch succeeded but returned
no detail. Ensure references to isLoading, detail, and the header/close button
JSX (the header element) are updated accordingly.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fcdc300e-22bc-4194-af41-bbb3c6b95dc1
📒 Files selected for processing (10)
src/hooks/__tests__/useSpeedHistory.test.tssrc/hooks/useDownloadDetail.tssrc/hooks/useSpeedHistory.tssrc/views/DownloadDetailsPanel/DownloadDetailsPanel.tsxsrc/views/DownloadDetailsPanel/FileInfoSection.tsxsrc/views/DownloadDetailsPanel/IntegritySection.tsxsrc/views/DownloadDetailsPanel/LogsSection.tsxsrc/views/DownloadDetailsPanel/MetricsSection.tsxsrc/views/DownloadDetailsPanel/SourceInfoSection.tsxsrc/views/DownloadDetailsPanel/__tests__/DownloadDetailsPanel.test.tsx
🚧 Files skipped from review as they are similar to previous changes (9)
- src/views/DownloadDetailsPanel/SourceInfoSection.tsx
- src/views/DownloadDetailsPanel/IntegritySection.tsx
- src/hooks/tests/useSpeedHistory.test.ts
- src/views/DownloadDetailsPanel/tests/DownloadDetailsPanel.test.tsx
- src/views/DownloadDetailsPanel/LogsSection.tsx
- src/hooks/useDownloadDetail.ts
- src/hooks/useSpeedHistory.ts
- src/views/DownloadDetailsPanel/MetricsSection.tsx
- src/views/DownloadDetailsPanel/FileInfoSection.tsx
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx (1)
25-30: Optional: extract duplicated panel header into a shared subcomponent.The header markup is duplicated in two places; extracting it would reduce drift risk for future tweaks (title, button props, a11y updates).
♻️ Suggested refactor
+function DetailsPanelHeader({ onClose }: { onClose: () => void }) { + return ( + <div className="flex items-center justify-between border-b px-4 py-2"> + <h2 className="text-sm font-semibold">Details</h2> + <Button variant="ghost" size="icon" className="h-7 w-7" aria-label="Close details panel" onClick={onClose}> + <X className="size-3.5" /> + </Button> + </div> + ); +} + export function DownloadDetailsPanel() { @@ - <div className="flex items-center justify-between border-b px-4 py-2"> - <h2 className="text-sm font-semibold">Details</h2> - <Button variant="ghost" size="icon" className="h-7 w-7" aria-label="Close details panel" onClick={() => setDetailsPanelOpen(false)}> - <X className="size-3.5" /> - </Button> - </div> + <DetailsPanelHeader onClose={() => setDetailsPanelOpen(false)} /> @@ - <div className="flex items-center justify-between border-b px-4 py-2"> - <h2 className="text-sm font-semibold">Details</h2> - <Button variant="ghost" size="icon" className="h-7 w-7" aria-label="Close details panel" onClick={onClose}> - <X className="size-3.5" /> - </Button> - </div> + <DetailsPanelHeader onClose={onClose} />Also applies to: 57-62
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx` around lines 25 - 30, The header markup in DownloadDetailsPanel is duplicated; extract it into a small shared subcomponent (e.g., PanelHeader) and replace both occurrences to avoid drift: create a PanelHeader that accepts props for title (string), onClose (callback) and optional ariaLabel/button props, move the div with h2 and the Button/X icon into that component, and update DownloadDetailsPanel to render <PanelHeader title="Details" onClose={() => setDetailsPanelOpen(false)} /> (and the second location similarly) so title, Button props, and accessibility updates are centralized.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx`:
- Around line 25-30: The header markup in DownloadDetailsPanel is duplicated;
extract it into a small shared subcomponent (e.g., PanelHeader) and replace both
occurrences to avoid drift: create a PanelHeader that accepts props for title
(string), onClose (callback) and optional ariaLabel/button props, move the div
with h2 and the Button/X icon into that component, and update
DownloadDetailsPanel to render <PanelHeader title="Details" onClose={() =>
setDetailsPanelOpen(false)} /> (and the second location similarly) so title,
Button props, and accessibility updates are centralized.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bff6e77e-1336-4b19-b4ad-f39422ada3ed
📒 Files selected for processing (1)
src/views/DownloadDetailsPanel/DownloadDetailsPanel.tsx
Summary
downloadStore.progressMap(speed, ETA, downloaded/total, connections) with dynamic ETA calculationselectDownloadnow setsdetailsPanelOpen: true)New files
src/views/DownloadDetailsPanel/— 9 components (panel + 8 sections) + barrel exportsrc/hooks/useDownloadDetail.ts— TanStack Query wrapper (500ms staleTime)src/hooks/useSpeedHistory.ts— Speed sampling hook (2s interval, 2min window)Modified files
src/stores/uiStore.ts—selectDownloadauto-opens details panelsrc/views/DownloadsView/DownloadsView.tsx— Flex-row layout with panelCHANGELOG.md— Added entry for Download Details PanelTest plan
Summary by cubic
Adds a right sidebar Download Details Panel for the selected download with real-time metrics, speed history, segments, and logs. Improves accuracy, accessibility, and keeps the header/close control visible during loading and errors.
New Features
zustandprogress map with dynamic ETA.useDownloadDetail(500ms stale, enabled guard) anduseSpeedHistory(2s sampling, 2-min window).Bug Fixes
Written for commit 05c4588. Summary will update on new commits.
Summary by CodeRabbit