feat(sidebar): hover details card for workspaces - #208
Conversation
The sidebar is lossy on every surface: an inbox card truncates its title and never renders git_behind or git_changed_files at all, a settled row shows only a title, and a collapsed rail avatar shows nothing but a status dot. Resting the pointer on any of the three now opens a shared read-only HoverCard to the right (350ms open, 290px, side="right" align="start"). Contents, all conditional except the header and Location: repo eyebrow + provider marks + agent state with elapsed, the full untruncated title, the linked issue, then label/value rows — Branch, Uncommitted, Changed files, Ahead, Behind (or a single "Working tree clean" row when there is nothing to report), Pull request, Issue, Port(s) capped at 3 with a +N overflow, Location, Notifications muted — and finally the real path on disk with $HOME collapsed to ~. No new Tauri command: every field already rides on WorkspaceSnapshot or appState.detected_ports. Reuses the DetailRow label/value shape from the context bar's popover. Implementation notes: - The body is a separate component so Radix's unmount-when-closed keeps a sidebar of N workspaces from subscribing N times to the ports/hosts stores at rest. - Ports are selected as a raw slice and narrowed in a memo; filtering inside the zustand selector returns a fresh array per call, which breaks useSyncExternalStore snapshot equality and spins React into an infinite re-render loop. - Remote-ness keys off host_id alone. The hosts list loads asynchronously, so an unresolved name falls back to "Another device" rather than letting a failed lookup claim the workspace is local. - Mounted inside WorkspaceInboxMenu on a different node than ContextMenuTrigger so the two asChild triggers never compose onto one element. - The label holds its width and the value truncates; the reverse left long worktree branches rendering as "Bran…" next to the thing you came to see. Replaces the rail's title-only tooltip and the card's redundant native title attribute, which would have raced the card on the same element.
Review — Opus 5 (high effort)Solid, and the two hardest details are handled correctly:
|
|
Review by GPT 5.6 Sol xhigh found a path-boundary bug.
Please compare path components (or require the next character to be a separator, while handling exact-home as a special case) and add the sibling-prefix regression. The focused hover-card suite passed 19/19 tests; it currently covers an actual child and a completely unrelated path, but not this boundary. Coordination note: this head has pairwise textual conflicts with #206 and #213. |
- shortenPath now requires a real path-separator boundary after $HOME, so a sibling like /home/u2/project no longer renders as ~2/project; exact-home collapses to a bare ~ and a trailing separator on the reported home dir is tolerated (plus \ for Windows-style hosts) - drop pointer-events-none from the card content so the path and branch can be selected and copied; Radix already holds the card open while the cursor crosses into it, and side="right" floats it over the main pane rather than other sidebar rows - settled rows now pass the workspace's computed agent status to the hover card instead of hard-coding null, so a settled 'review' workspace no longer reads Idle - the elapsed label ticks on the shared coarse (~30s) clock while the card is open instead of freezing at its mount-time value - document the bare settled-row wrapper div as an intentional layout-neutral trigger node Adds regression tests for the sibling-prefix path, exact-home and trailing-separator cases, the settled-row status pass-through, and the ticking elapsed label.
|
Verified: all five review issues are genuinely fixed at the head (pointer-events restored on the card, settled rows pass real status, elapsed ticks via the shared coarse clock, Blocked on a rebase now that #206 merged — conflicts in |
Why
Every sidebar surface is lossy. An inbox card truncates its title and never renders
git_behindorgit_changed_filesat all — both already ride onWorkspaceSnapshotand were displayed nowhere. A settled row shows only a title. A collapsed rail avatar shows nothing but a status dot, behind a title-only tooltip.Resting the pointer on any of the three now opens a shared read-only
HoverCardto the right (350ms open, 290px,side="right" align="start").What it shows
Header — repo eyebrow, provider marks, agent state with elapsed, the full untruncated title, and the linked issue's number + title.
Rows, all conditional except Location — Branch, Uncommitted
+A −D, Changed files, Ahead, Behind (or a single "Working tree · clean" row when there is nothing to report), Pull request, Issue, Port(s) capped at 3 with a+Noverflow, Location (This device/ host name /host · in place), Notifications muted.Footer — the real path on disk (
worktree_path ?? remote_cwd ?? cwd),$HOMEcollapsed to~, wrapped rather than truncated.No new Tauri command. Every field is already on
WorkspaceSnapshotor inappState.detected_ports. Styling reuses theDetailRowlabel/value shape from the context bar's popover rather than inventing a second vocabulary.Implementation notes
useMemo. Filtering inside the zustand selector returns a fresh array per call, which breaksuseSyncExternalStoresnapshot equality and spins React into an infinite re-render loop — this crashed the card tree during development and was caught only by running the real app.host_idalone. The hosts list loads asynchronously, so an unresolved name falls back to "Another device" rather than letting a failed lookup claim the workspace is local.WorkspaceInboxMenuon a different node thanContextMenuTrigger, so the twoasChildtriggers never compose onto one element.Bran…next to the thing you came to see.pointer-events-none— a detail surface, not an interactive one. Reviewers may want to overrule this: it means you cannot mouse in to select the branch name or path.Removes the rail's title-only tooltip and the card's redundant native
titleattribute, which would have raced the hover card on the same element.Verification
npm run checkclean.npm run test— 210 files, 3069 tests pass, including 19 new ones covering the git rows, clean-tree fallback, non-git workspaces, PR/issue tones, port capping, the async-host fallback, mute, and path shortening.pointerenterproving real mouse input opens it.npm run verifydoes not complete in this worktree:cargo checkfails on a missingsrc-tauri/binaries/agent-browsersidecar. Pre-existing environment gap, unrelated — this branch touches no Rust.Docs updated in
docs/features/sidebar.md(inbox section, rail section, What Works, Constraints, Touch Points).