Order active workspaces newest-first and pin the needs-you strip#207
Order active workspaces newest-first and pin the needs-you strip#207Zeus-Deus wants to merge 2 commits into
Conversation
…-you strip New workspaces were appended to the bottom of the sidebar, so anyone holding a long list of still-open workspaces had to scroll to the bottom to reach the one they just created. Ordering - Add `orderActiveWorkspaces`, which reverses `AppStateSnapshot.workspaces`. Every backend creation path ends in a `Vec::push` and close uses `Vec::remove`, so that array is creation order; `WorkspaceSnapshot` carries no `created_at`, making the reversal both the cheapest and the only lossless way to get newest-first. No backend change, no migration. - The expanded inbox and the collapsed rail share the helper, so the two surfaces can no longer disagree about order. - Order stays deliberately status-blind: a card holds its position from creation until it settles, so the list only moves at lifecycle transitions and never shifts under the pointer because an agent started or finished. Sorting by agent activity would churn the list constantly and break both spatial memory and the digit jump shortcuts. Needs-you strip - Mount `SidebarNeedsYouStrip`, which was fully built and unit-tested but had never been rendered. It now lives in the sidebar's sticky header block, so blocked work stays reachable however far the list has scrolled. - Entries are jump-links: they activate the blocked workspace and scroll its card into view without moving it. Surfacing attention by duplication rather than by reordering is what lets the list below stay status-blind. - Scope it by the active repo filter so a filtered sidebar never points at a workspace the list is hiding; match the flat inbox's `data-inbox-card` in addition to the legacy `data-ws-id`; cap at four rows with a `+N more below` line so a pinned strip can never swallow the sidebar. Also keep the active workspace rendered when it settled long ago and sits past the settled paging window, and exclude that escaped row from the hidden count.
Review — Opus 5 (high effort)The implementation is correct. Two things I specifically went looking for and found clean:
The rationale and the change pull in opposite directionsThe PR's central argument is positional stability — "a card holds its position from open until settled, so the screen only moves at lifecycle transitions", and that this is what makes But newest-first is the ordering that maximises churn for existing rows. Appending put every new workspace at the bottom and left all N existing cards exactly where they were, with their digit bindings intact. Reversing means every workspace you create shifts all N existing cards down by one and re-maps every single That may still be the right trade (reaching the thing you just made matters more than a stable The strip can point at a settled workspace
Overlap#206 implements the same newest-first ordering independently, via |
|
Review by GPT 5.6 Sol xhigh found an ordering bug in the Needs You strip.
Please sort the derived entries by their permission-status timestamp before slicing (with a deterministic fallback for a timestamp not seeded yet), and add a regression where creation order and blocked-since order disagree. The focused suites passed 38/38 tests, confirming the current coverage does not exercise that case. Coordination note: this head has pairwise textual conflicts with #206 and #213. |
The strip collected entries in project-tree order and never sorted them, while claiming oldest-blocked-first. Tree order is not blocked order (the active list runs newest-first and a block can land on any card), so with the four-row cap the longest-waiting blocker could hide behind the '+N more below' line. Sort entries by the density-store permission timestamp before slicing; an unseeded timestamp ranks as newest and ties keep stable tree order. Also pin two adjacent behaviors with tests: a blocked settled workspace keeps both a strip jump-link and a rendered card (via the permission resurface net), and the active workspace's settled row always renders past the settled fold via the below-fold escape. Docs: describe the actual strip sort, and acknowledge the newest-first trade-off honestly — each creation shifts existing cards and their jump digits down one; the win is the newest workspace always sits on slot 1.
Problem
New workspaces were appended to the bottom of the sidebar. With a long list of still-open workspaces, reaching the one you just created meant scrolling to the bottom every time — even when the workspaces above it were idle with no agent running.
The obvious fix is to float working / needs-input workspaces to the top. I researched how a comparable agentic dev environment handles this first, and it turns out that's the wrong answer.
What the research found
That tool's sidebar sorts by creation time, newest first, and its ordering never reacts to agent state. The load-bearing comment in their sort helper:
They have status-priority ordering in exactly one place: a small glanceable mobile widget, ordered attention-first. Not the list you navigate by muscle memory. A second status-priority table exists in their sidebar but is used only to pick which badge a collapsed group shows — never for position.
The reasoning holds for us: a list that reorders itself as agents flip between working → done → needs-input moves under your cursor, destroys spatial memory, and breaks our digit jump shortcuts.
What this PR does
Ordering —
orderActiveWorkspacesreversesAppStateSnapshot.workspaces. Every backend creation path ends in aVec::pushand close usesVec::remove, so that array is creation order;WorkspaceSnapshothas nocreated_at, making the reversal both the cheapest and the only lossless way to get newest-first. No backend change, no migration. The expanded inbox and the collapsed rail share the helper so they can't drift.Order is otherwise frozen and status-blind, matching the rationale above.
Needs-you strip — this is where we go further than the tool I looked at. They have no equivalent in the sidebar at all; blocked work is found by scanning colored pills. We already had
SidebarNeedsYouStripfully built and unit-tested but never rendered. It's now mounted in the sidebar's sticky header block, so blocked work stays visible however far you scroll.Entries are jump-links: they activate the blocked workspace and scroll its card into view without moving it. Surfacing attention by duplication rather than reordering is precisely what lets the list below stay status-blind — you get "attention is always at the top" without paying the reordering tax.
Adaptations needed to mount it: scoped by the active repo filter so a filtered sidebar never points at a hidden workspace; jump selector now matches the flat inbox's
data-inbox-card(it was written for the removed project tree'sdata-ws-id); capped at 4 rows with a+N more belowline, since a pinned strip could otherwise swallow the sidebar.Active-workspace escape — the selected workspace now always renders even if it settled long ago and sits past the settled paging window, and the
+N hiddencount excludes the escaped row so it stays truthful. (Same guarantee the other tool makes, via a different mechanism.)Verification
npm run checkclean; 3056 tests / 210 files passwsSiteMain, wsSiteRedesign, wsScratchpad, and the sidebar now rendersscratchpad → landing-refresh → personal-siteNEEDS YOU · 2; clicking a jump-link activatedworkflow-approval(position 10 in the list) and scrolled its card into view; scrolling deep keeps the strip pinned with ages tickingdocs/features/sidebar.mdupdated — ordering rule, the status-blindness rationale, the strip, and the dead-code inventory that had listed the strip as unmountednpm run verifycould not complete locally:cargo checkfails on a missingbinaries/agent-browser-x86_64-unknown-linux-gnusidecar in a fresh worktree. No Rust files are touched by this PR.Review notes
Four test assertions changed. Three were straightforward old-order expectations. The fourth is worth a look:
getByText("Waiting for your input")now finds two nodes because the strip mirrors the card's blocker text. I asserted exactly 2 rather than scoping the query, so the duplication is pinned as intended behaviour.Follow-ups (not in this PR)
sidebar-project-group.tsx,sidebar-live-section.tsx,sidebar-live-grouping.ts) are still unmounted and could be removed