Invert the hierarchy: durable intent (tasks) becomes the organizing spine of the project and threads become attempts beneath it. The project opens to a three-column mission-control surface — backlog → selected intent → live execution — so the relationship between what you meant and what the agents are doing is the product's default view, not a side panel. Threads that serve no task are demoted to an orphan group.
TESTED AGAINST · busy backlog (10 tasks) · empty backlog · capture from a live thread (# log mode) · multi-attempt history (failed → retry, live) · starting an attempt (provider/model) · blocked task · done task + evidence + reopen · mobile task home · mobile task screen · mobile quick capture
Close the command palette with Esc and keyboard focus drops to <body>; the focus ring disappears until you Tab twice. Repro on Safari and Chrome. Likely the palette unmount steals focus before the restore handler runs.
Logged from Pinned-thread drag ordering · 6w ago
Failed — regression in palette focus trap; change reverted, findings kept.
Restoring focus via a sentinel element captured on palette open; a11y suite running.
Reproduced at 240 threads. On reconnect the list gets a fresh query key, so the virtualizer treats every row as new and remounts. Bisecting the reconnect diff to find where the key started including the socket generation.
Soak test running — reconnecting every 20s against the 240-thread fixture while I narrow the range.
This project's outstanding work will live here — log with # from any thread, or press # right here.
Log a taskSpawned focused thread · reading ThreadListVirtualizer.tsx…
No attempts — blocked tasks can't spawn one. Unblocks automatically when #231 closes.
Migrated 14 call sites; visual parity confirmed against the old renderer.
Esc from the palette drops focus to body; ring gone until you Tab twice. Safari + Chrome.