# Proof audit pass 1 — sidebar shelf motion

PR [#25413](https://github.com/pingdotgg/t3code/pull/25413), GitCafe [#25412](https://git.cafe/pingdotgg/t3code/pull/25412), issue [#25383](https://git.cafe/pingdotgg/t3code/issues/25383).

Reviewed head [f4bbe57480](https://git.cafe/pingdotgg/t3code/commit/f4bbe57480e4498d279fce1dcf99b1aece1100b5), branch `fix/issue-25383`; comparison is the complete merge-base tree [1316d48b0c](https://git.cafe/pingdotgg/t3code/commit/1316d48b0c68151afd8b47eb0c11a114cf0b4daa), not HEAD with one file replaced. Source-only planning pass; **no capture has passed yet**. Status PLAN means source-supported seed and capture procedure, not executed evidence.

## Findings first

1. A sufficient capture plan exists. The claim is transient: settled endpoints alone cannot prove it. Every motion/interaction row below requires a recording AND comparable before/after checkpoint frames with Diff0 and Diff16. R01 must visibly reproduce actual glyph/header overlap on the complete base tree; an animation count alone is insufficient.
2. Working rows must come from authoritative runtime state. A title prefix, source predicate edit, synthetic shell, or database projection write is not acceptable for required rows. The source accepts running/waiting/preparing/queued/starting or idle runtime, subject to attention exclusions, but this plan uses real running Codex turns. Two independent attached provider commands plus shell/runtime gates are specified below.
3. All three shelves feed the changed key. The entire list controller resets on a toggle, including existing moves and exit clones. Positive controls must show ordinary movements still animate after a toggle. Collapsed shelves retain the routed thread; that is distinct from multiselection and needs its own proof. The first nonempty Working/Snoozed shelf, otherwise Settled, owns the bottom auto margin and undo notice.
4. No concrete implementation defect established in this planning pass. This is not a merge-readiness verdict. The code path, not previous approvals or green CI, defines the plan. Current forge metadata matched the requested head; 39 successful checks were reported. The earlier evidence index could not be fetched (DNS failure, then web fetch failure); no old capture was credited.

## Code inventory and equivalence reductions

Source links are pinned to reviewed head. The only diff is [Sidebar.tsx layout effect](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/apps/web/src/components/Sidebar.tsx#L1756).

- **Callers / routes:** [AppSidebarLayout](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/apps/web/src/components/AppSidebarLayout.tsx#L443) is the sole production caller of this Sidebar, using `routeReveal`. [RootRouteBranch](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/apps/web/src/routes/__root.tsx#L190) mounts that layout for welcome and the authenticated/hosted app shell. Thread, draft, Settings, usage/project utility routes share it. Guest, pairing/connect, auth-gated and hosted prompt-link landing branches bypass it. Legacy-sidebar setting selects a different component. Web and Electron share this component; native iOS/Android do not.
- **Changed effect branches:** initial ref equals the initial three-boolean key; unchanged key preserves existing `animate` input; changing any one or several booleans calls `update(false)` on that pass. Animation input is false during drag or when there are no list items and no visible drafts. The effect may see a null controller while search replaces the normal list. Remount starts with `motion.update(false)`. List layout key includes ordered marker/section/thread identities, visible draft count and routed draft id. Stream-only updates that leave those unchanged should not manufacture motion. Persistence keys are `t3code:sidebar:working-expanded`, `t3code:sidebar:snoozed-expanded`, `t3code:sidebar:settled-expanded` (Boolean, default false).
- **Membership / counts:** [partitionSidebarThreads and useSidebarThreads](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/apps/web/src/components/Sidebar.tsx#L370): scoped and visible top-level threads partition into Snoozed, Settled, Pinned, then Working/Active. Snooze/settlement have capability gates. Pinned rows stay out of Working. Working beta off puts those rows in Active. Working and Snoozed headers exist only for nonzero counts. Settled header exists whenever the main list is nonempty, including Settled count zero. No threads yields no markers; drafts can still exist. Counts 0, 1 and >=2 are structurally distinct: absent shelf, one row / retained-only no-op, and actual disappearing rows. Exact larger counts repeat the same branch until overflow or pagination limits.
- **Expansion / route:** expanded shows all W/Z rows and the visible settled page; collapsed shows only `routeThreadOnly`. A routed settled row beyond the first page is additionally retained. Route reveal can widen a project filter and scroll an active row; subagent routes can resolve to a parent row. Selection highlights and routed retention are separate. Use each target shelf in both directions. R01–R03 explicitly sweep the four neighboring expansion-bit combinations; no independent 2^3 screenshots are needed because the recorded sweep covers every edge/state.
- **List geometry:** direct `ul` children include draft rows/divider, zero-height pinned/active markers, active/pinned/Working **card** rows, 32px shelf markers, Snoozed/Settled **slim** rows, zero-height settled placeholder, and optional pagination button. Working header gets `mt-auto`; absent Working transfers it to Snoozed; absent both transfers it to Settled. Undo notice sits above that first header. Tall lists overflow/scroll instead of absorbing height into the auto margin. Card metadata (branch, remote/environment label, PR/diff, terminal) distinguishes full-height cards from new-design compact cards (`metaLineEmpty`). Long names truncate. Slim rows have fixed height. Rename, selection/read/woke/attention status, hover actions, keyboard hints, permission/capability gating and drop indicators change contents/tone; geometry is covered by mixed cards, selected retention and drag controls, rather than multiplying every icon/status/provider into its own fixture. Genuine attention requests remove a thread from Working; never use an approval-pending turn as the required Working seed.
- **Presentation:** New design versus classic is independent of the legacy sidebar. Counts persist on expanded headers in New design but are hidden when expanded in classic; chevrons differ. Light/dark use different surface/foreground tokens. Desktop sidebar resizes (default 256 CSS px, min >=208 subject to brand probe); below md the same Sidebar is in a Sheet, width `100vw - spacing(3)`. Electron adds titlebar/drag chrome/insets, reducing available space. These change available height/width, not the key predicate. Native mobile is out of scope. Local, hosted and remote/relay use the same DOM; the RPC seeding must target the sandbox environment. No connection-mode Cartesian product is warranted for this local layout effect.
- **Other interactions / equivalent paths:** Header project picker supports filter query, clear scope, project Settings, and Add project/New thread. Thread rows support activate/double-click, jump shortcuts, rename, context menu and multi-context actions, selection/range selection/select-all/clear, archive/unarchive/delete, read/unread, pin/reorder, settle/unsettle, snooze/custom snooze/unsnooze, title regeneration, branch/project actions, PR/navigation, context dragging and file drops; action sweeps batch settle/unsettle/unsnooze. Only those that change list membership/order, drafts, route retention or measured row shape can feed this effect: R14 covers membership mutations including archive/delete of disposable demo rows, R15 covers rename/draft and metadata geometry, R20 covers reorder/sweep/undo/drop/cancel and placeholders, R24 covers scope/reveal, and R05–R08 cover route/selection. Context menus, confirmation dialogs, failed action toasts, tooltips, unavailable permissions and title-regeneration busy state do not create a different shelf expansion branch; overlay styling is outside this claim. Undo notice must be present for one R20 settle/undo phase, verifying the first shelf host does not overlap it. W-context drag does not reorder (it becomes chat context); include one real context-drag/cancel phase in R20. Actions through palette/keybindings/history use the same projected list and are represented by one route-reveal path plus the explicit keyboard shelf cycles.
- **Empty/loading/error:** no projects, no threads globally, scoped empty, draft-only; search has results, pending and no-results branches and unmounts the motion list; older Settled has Show-more, Loading, Retry and no-more branches. Settings hydration and project config/snapshot readiness may change scope/rows. There is no separate main-list error/skeleton branch in Sidebar.tsx. Route and connection failures are shell concerns, not a distinct changed shelf layout.
- **Controller constraints:** [Sidebar.motion.ts](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/apps/web/src/components/Sidebar.motion.ts#L1) is unchanged. FLIP/entry/exit duration is 150ms, ease-out. `update(false)` clears fade clones and cancels moves before storing new positions. Other existing suppressors: no previous positions, real `prefers-reduced-motion: reduce`, more than 40 entering+exiting nonzero-height direct children, suspension while dragging. Drag release has its own move reconciliation. The 40 limit is on fade candidates, not total thread count. These get controls, not a full animation-system re-audit.

## Real app fixture recipe (no source edits)

All runtime work belongs to the **capturer**, not this read-only audit. Never point a dev server at live `~/.t3/userdata`. Use an explicitly isolated T3 home and disposable fixture project. Keep scripts, logs and captures under a fresh timestamped capture-step directory in `/tmp/lead-25413-r1/`. Do not put scratch material in the tracked source. Obtain pairing/auth through the approved dev workflow; never upload tokens, cookies, full provider config, or private thread data.

1. Start authenticated app against sandbox state; add a disposable git project through Add project or the public project RPC. Obtain its project id and environment id from the server. Read `server.getConfig` / `subscribeServerConfig` for an actually available Codex instance id and supported model. Use the already configured hub; do not change hub settings. In Settings > Beta (`/settings/beta`), enable **Working section**, select **New design** as prescribed, and disable **Sidebar (legacy)** in appearance. Use real Settings controls for theme. Use shelf selectors `[data-testid="sidebar-working-shelf-toggle"]`, `[data-testid="sidebar-snoozed-shelf-toggle"]`, `[data-testid="sidebar-settled-shelf-toggle"]`. Set all project scope, no sidebar search, and no selected thread via `/` (or a nonthread utility route).
2. Public commands are typed in [orchestrationV2.ts](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/packages/contracts/src/orchestrationV2.ts#L2735). Drive the normal authenticated WebSocket RPC (`orchestration.dispatchCommand`) using unique command ids and the repository's Effect RPC JSON protocol; reuse [RpcSession's setup](https://git.cafe/pingdotgg/t3code/blob/f4bbe57480e4498d279fce1dcf99b1aece1100b5/packages/client-runtime/src/rpc/session.ts#L214), not an invented JSON-RPC envelope. The protocol is `RpcClient.make(WsRpcGroup)` with socket protocol and `RpcSerialization.layerJson`. A browser driver may instead click real UI controls; do not call private React setters or inject shells.
3. **Working W1/W2:** call `orchestration.launchThread` twice, once each with `{commandId, projectId, title:'Working alpha'/'Working beta', generateTitle:false, modelSelection:{instanceId:<real-codex-id>,model:<supported-model>}, runtimeMode:'full-access', interactionMode:'default', workspaceStrategy:{type:'root'}, initialMessage:{text:<hold prompt>,attachments:[]}}`. That method's schema supplies creation details; do not mix MCP tool field names into this RPC. Hold prompt: ask the provider to execute `python3 -c 'import pathlib,time; pathlib.Path("<timestamped-run-dir>/W1.ready").write_text("ready"); time.sleep(1200)'` (W2 gets a distinct ready file), keep waiting on the command until completion if the harness yields a process session, and make no repo edits. This is a real tool-running turn, not a model merely promising to wait. Full-access avoids an approval gate. Subscribe to `orchestration.subscribeThread` / shell updates and require latest run running/waiting, runtime active, no pending approvals/input/subagent request, no pin, no snooze, no settled override, and rendered Working count exactly two. Check ready sentinels and record run ids plus sanitized gate values. If a provider ends/fails or attention appears, reject/reseed; do not relabel it Working. Idle runtimes can also be Working by source, but do not silently substitute one for this running-turn proof.
4. **Active A1/A2, Snoozed Z1/Z2, Settled T1/T2, Pinned P1:** create idle thread records through `orchestration.dispatchCommand` with `{type:'thread.create',commandId,threadId,projectId,title,modelSelection,runtimeMode:'full-access',interactionMode:'default',branch:null,worktreePath:null,createdBy:'user',creationSource:'web'}` and no provider message. Newly created no-runtime records are Active. For Z1/Z2 dispatch `{type:'thread.snooze',commandId,threadId,snoozedUntil:<same fixed future ISO instant>}`. Require future timestamp, no queued message or pending request; the server rejects those blockers. For T1/T2 dispatch `{type:'thread.settle',commandId,threadId}`; require `settledOverride:'settled'`. Settle rejects live/queued runs or blocking requests, so use these idle records, not the two holding Working turns. Pin P1 with `thread.pin`. UI equivalents are row context menu Snooze > custom future date, Settle, and Pin. Await command receipt sequence **and projected shell/DOM**, not only RPC return. Use unsnooze/unsettle/unpin actions to reverse states.
5. **Counts / height:** use project scoping or create dedicated fixture projects rather than deleting live data. Empty project gives scoped-empty. A no-project sandbox gives welcome/no-project. For W=0 stop the two seeded runs with normal thread Stop / `{type:'thread.stop',commandId,threadId}` and wait for authoritative nonworking placement (including the 2s exit grace if applicable); alternatively switch to a fixture project with no W. Working beta disabled is a separate row with the two turns still running in Active. For a metadata-bearing card use a real fixture git branch via launch workspace strategy / branch selection; for compact cards use branch:null with no PR/diff/terminal/remote metadata. Change titles through Rename / `thread.metadata.update`, never source edits. Create an unsent draft with New thread + typed text, navigate away without sending, and return via its real row to exercise route draft identity. Long names are harmless demo text.
6. **Pagination:** make 36 idle top-level threads and settle each through real commands, with distinct settlement times (public optional `settledAt` can be supplied for deterministic order). The server keeps 25 newest settled rows and counts older ones; UI initially shows 10, then adds 25 per Show more. Use two pages and a route to a settled row outside the first ten. The older cutoff is relative rank, not age: no days-long wait or database backdating is required. For Loading/Retry hold then fail the public `getOlderSettledThreads` response in a labelled transport harness; see R19. Do not pretend an injected failure is ordinary state.
7. Reset expansion bits using shelf buttons. Fresh browser storage may initialize the three documented Boolean preferences before page load, but the transition being proved must use actual click/Enter/Space. A driver must also record all state preconditions, shelf counts, route id, scrollTop, design/theme, viewport/sidebar width, reduced-motion media result and animation manifest. A failure to achieve a required real seed is a failed row, not permission for a source fixture.
8. Cleanup by stopping only the provider runs and dev servers the capturer started, using captured run ids/PIDs. Long-running provider tool commands must be accounted for; do not leave them running after evidence upload. No process-pattern kills.

## Viewports and minimum capture cells

D = 1440x900 CSS px, sidebar 256px; N = same viewport with sidebar resized by the actual rail to the measured minimum (>=208px); M = 390x900 CSS px, actual mobile-web drawer open; H = 1024x640 CSS px with overflowing list; E = real macOS Electron 1440x900 content area (record actual titlebar/sidebar dimensions). DPR=1 or 2 is acceptable but must be identical before/after, recorded, and unrescaled. Theme L=light, K=dark. New design is default except R02 classic. Freeze browser display wall clock to the same instant only **after** real server seeding, keeping timers/monotonic time functional; do not freeze a real server or substitute shell state. Use same locale, fonts and sanitized fixture identities/ordering. Running-duration labels and spinner phases must be aligned, or separately disclosed as live noise with raw whole-surface frames retained.

The table is the minimum set for this plan: every named cell gets before and after, with the phases described. A row is one structural fixture; phase sequences cover its state changes. Required means required for this plan's sufficiency; conditional harness rows are inventory coverage, not blockers to the narrow overlap claim. Do not claim Electron verification unless E is actually run.

| ID / fixture | Data and distinct layout | Interaction and observations | Diff effect | Minimum cells / medium | Seed and pass-1 status |
|---|---|---|---|---|---|
| R01 — reported Working overlap / New design | W=2, Z=2, T=2, A=1, P=1; branch-bearing full-height W cards; route outside shelves | W collapse/expand. Repeat both directions with Z/T expansion neighbors 00,01,10,11. Whole surface + overlap crop + row/header rectangles | Direct | D-K, D-L, M-K, M-L; **recording** + frames | Recipes 1–4; **PLAN REAL, REQUIRED** |
| R02 — classic shared Sidebar | R01, New design off, legacy Sidebar still off | All three toggles in both directions, expanded/collapsed count and chevron forms | Direct | D-L classic; **recording** + frames | Settings > Beta > New design off; **PLAN REAL, REQUIRED** |
| R03 — Snoozed between populated shelves | R01; W stays present; Z slim rows; no routed Z | Z collapse/expand with W/T neighbors 00,01,10,11 | Direct | D-K; **recording** + frames | Snooze idle Z records; **PLAN REAL, REQUIRED** |
| R04 — Settled under populated shelves | R01; T slim rows; no routed T | T collapse/expand with W/Z neighbors 00,01,10,11 | Direct | D-K; **recording** + frames | Settle idle T records; **PLAN REAL, REQUIRED** |
| R05 — routed Working retained | W=2,Z=2,T=2; navigate to W2 | Collapse/expand W: W2 stays mounted, W1 disappears/returns; verify retained title never crosses lower labels | Direct | D-K; **recording** + frames | Click W2 or `/<environmentId>/<threadId>`; **PLAN REAL, REQUIRED** |
| R06 — routed Snoozed retained | Same counts; route Z2 | Z both directions; retained slim row vs removed sibling | Direct | D-K; **recording** + frames | Open snoozed Z2 without waking it; **PLAN REAL, REQUIRED** |
| R07 — routed Settled retained | Same counts; route T2 | T both directions; retained slim row vs removed sibling | Direct | D-K; **recording** + frames | Open settled T2 without unsetting it; **PLAN REAL, REQUIRED** |
| R08 — singleton shelves / retained-only | W=1,Z=1,T=1; no route, then each corresponding routed singleton | Toggle each shelf both ways without route; then route singleton and toggle (same row remains, key still changes). Include true multiselection on a separate repeat | Direct / no-geometry control | D-K; **recording** + frames | Separate project, one of each real seed; modifier-click multiselect distinct from route; **PLAN REAL, REQUIRED** |
| R09 — Snoozed becomes bottom anchor | W=0,Z=2,T=2 | Z and T both directions; Z owns auto margin and undo host | Direct | D-K; **recording** + frames | Scoped project with no W, not fake predicate; **PLAN REAL, REQUIRED** |
| R10 — no Snoozed header | W=2,Z=0,T=2 | W and T both directions; Working rows can cross Settled alone on base | Direct | D-K; **recording** + frames | Fixture scope with only W/T; **PLAN REAL, REQUIRED** |
| R11 — Settled only / empty Settled | W=0,Z=0,T=2, then T=0 with A=1 | T toggle with rows, then toggle Settled (0); auto margin on T; zero-count header has no row change | Direct / no-op control | D-K; **recording** + frames | Dedicated scope; unsettled idle A supplies nonempty list for T=0; **PLAN REAL, REQUIRED** |
| R12 — Working beta disabled | W actual running=2 but all appear Active; Z=2,T=2 | Disable Working section via Settings, return to list, toggle Z/T. No Working header | Direct for Z/T; setting move is unchanged-key control | D-K; **recording** + frames | Same real Codex runs, beta control off; **PLAN REAL, REQUIRED** |
| R13 — interrupted motion / rapid reversals | R01, several ordinary list moves already in flight | Start pin/unpin or settle/undo move; while held at 60ms toggle each shelf; then repeat rapid W open/close/open without finishing prior base animation. Inspect transforms and inert clones | Direct cancellation of prior moves/fades | D-K; **recording** + frames | Real actions/RPC then actual shelf activation; **PLAN REAL, REQUIRED** |
| R14 — ordinary movement remains | R01, expansion bits fixed after R13 | Pin/unpin, settle/unsettle, snooze/unsnooze idle A; archive/unarchive and delete disposable demo rows. Also start/stop a real run to move Active/Working; await projected placement and grace. Show a new 150ms move after toggle | Unchanged-key positive control | D-K; **recording** + frames | Public actions/commands; **PLAN REAL, REQUIRED** |
| R15 — crowded, mixed heights, long names, draft | W=2,Z=2,T>=12,A>=8,P=1, one unsent draft; both compact branchless and full-height branch cards; long demo names | Scroll to shelves; all toggles; resize sidebar narrow; route draft and return; observe clipping/scroll and truncated glyphs | Direct in overflowing geometry; draft key control | H-K, N-L; **recording** + frames | Create rows through recipe, unsent draft via composer, real git branch; **PLAN REAL, REQUIRED** |
| R16 — pagination and beyond-page route | 36 distinctly ordered settled top-level rows, W/Z optionally populated | Expand T (first 10), Show more (+25), last page, route outside first 10, collapse/expand retaining routed T. Capture count, Show-more presence/absence | Direct for toggle, unchanged-key page growth | D-K; **recording** + frames | Recipe 6; all state through server commands; **PLAN REAL, REQUIRED** |
| R17 — search replaces list and remounts | R01 and unique query terms | Search matching thread then impossible query, clear; collapse/expand afterward; visit Settings and back; mobile close/reopen drawer. No stale clone/move after remount | Null controller / cold-controller control | D-K, M-K; **recording** + frames | Real sidebar search and routes/drawer, no mock; **PLAN REAL, REQUIRED** |
| R18 — real reduced-motion preference | R01, browser media emulation `reducedMotion:'reduce'` set before mount | All toggles and one ordinary move; query media result; controller does not animate rows in either revision | Already-suppressed control | D-K; **recording** + frames | Real browser/CDP media emulation affecting CSS and JS, not a matchMedia stub; **PLAN REAL, REQUIRED** |
| R19 — older loading / failure / recovery | R16, older page genuinely requested | Hold older-page response to show Loading (disabled); inject typed RPC failure to show Retry; restore transport and Retry; collapse/expand in these states | Same layout pass; no new error branch in diff | D-K; **recording** + frames | **PLAN LABELLED TRANSPORT HARNESS, CONDITIONAL**; fault injection, no source edits; optional to narrow overlap proof, required if claiming these states verified |
| R20 — drag suspension and release | R01, draggable pinned/active/settled rows; empty destination in one phase | Drag into/out of Pinned/Active/Settled, hover placeholders, drop and Escape cancel; one Working context-drag/cancel and settle sweep/undo; keep shelf bits fixed, then toggle after release | Existing animate=false/release controls | D-K; **recording** + frames | Real pointer sensor and capabilities, no private drag state injection; **PLAN REAL, REQUIRED** |
| R21 — cold mount / empty / draft-only | No projects; empty project global and scoped; draft-only with no real threads; populated persisted-expansion reload | Load welcome or `/`, add project, scope empty project, type unsent draft and navigate away, reload with persisted bits | No-list/draft/initial position controls | D-L; **recording** + endpoint frames | New isolated empty home + app actions; **PLAN REAL, REQUIRED** |
| R22 — many-entry fade cap | >40 fade-candidate direct children changed in one pass | In all-project scope create >=41 active idle cards, switch to an empty project and back. Also expand a shelf with >40 changed children if available without many provider runs (Settled page growth can exceed 40 after enough Show-more actions) | Existing cap suppresses base too; not proof of main fix | D-K; **recording** + frames | Real create/scoping/pagination, measure fade-candidate count, not total count; **PLAN REAL, CONTROL ONLY**; optional to narrow overlap claim |
| R23 — Electron shell | R01 in real Electron, macOS titlebar/safe-area insets measured | W then Z/T both ways, including retained W | Same component, shell geometry | E-K; **recording** + frames | Real isolated desktop target; **PLAN REAL, PLATFORM EXTENSION**; required for an explicit macOS-verified claim, otherwise label unrun/shared-code inference |
| R24 — filtered and remote-decorated rows | Two fixture projects with long names; a second already-authorized sandbox environment if available; mixed capability metadata only from actual servers | Change project filter; open a hidden thread via history/palette to widen scope; collapse/expand shelves after reveal. Remote labels/full metadata covered at same geometry as R15 | Filter/order control, shared local motion | N-K; **recording** + frames | App project picker/history/palette; **PLAN REAL, CONDITIONAL EXTENSION**; local scoped branch required and covered R15/R21, multi-environment extension only if claiming it verified |

**Excluded by source, not missing captures:** native-mobile sidebar; legacy per-project-tree Sidebar; pairing/guest/auth landing without this component; each individual provider logo/status icon; a Cartesian product of every project, provider, network transport and theme. No provider adapter, shared RPC contract, MCP action, scheduled task or native-mobile implementation changes. Keyboard activation is **not excluded**: R01/R03/R04 must each contain a clearly labelled Enter and Space cycle as well as pointer activation, with the intended shelf focused. Narrow-web row activation closes the drawer; reopen it through the real control before the retained-row phases. On small viewports repeat via pointer and include keyboard on desktop.

## Comparable recordings, frames, and acceptance contract

1. **Provenance:** build two complete clean trees at the exact SHAs above. Save `git rev-parse HEAD`, clean status, dependency/lock identity, build command, asset hash and each capture's route/config in a sanitized manifest. Do not swap just Sidebar.tsx. Use production builds or establish the same compiler/HMR-free environment in both. Backend bytes are identical in this diff; both fully built clients may target one freshly seeded sandbox server so ids/timestamps/provider state truly match. Record the shared-server arrangement explicitly. Otherwise use deterministic, equivalent independently seeded environments and disclose changed ids outside visual content. Never replay synthetic projections as required proof.
2. **Two complementary runs:** one normal-speed uninterrupted recording per row/phase sequence demonstrates actual behavior and chevron motion; a second instrumented replay produces deterministic mid-transition checkpoints. Save raw uncaptioned clips; captions/annotations are supplemental. Begin recording before the input, show the target, the action, the whole sidebar throughout, and the settled state. Label before/after and phase id visibly outside the compared pixels. Stills never substitute for recording rows.
3. **Deterministic WAAPI checkpoint:** in an external browser-driver evaluate hook, wrap `Element.prototype.animate` only to call the original with unchanged arguments, record returned animation plus target/keyframes/timing, then immediately `pause()` and set `currentTime=0`. Limit the hook to the normal thread list and descendants. Clear the tracked set before each action. Invoke the real shelf button, wait for its `aria-expanded` and expected row-membership DOM to commit, then seek every created list animation to **60ms**. Use matching header-CSS-transition currentTime or finish only the chevron transitions identically on both sides. Screenshot once the paused state is presented. On after, zero list animations is an expected result; still capture at the same logical phase and seek any CSS chevron to the same phase. Record that zero-animation state explicitly. Do not wait for an animation to exist on after. Capture additional 0ms/30ms/90ms checkpoints if 60ms does not visibly intersect text at that geometry. The issue's 60ms coordinate sample is guidance, not a guaranteed pixel location for this seed. A baseline without visible overlap has not proved the fix: choose another deterministic offset/geometry and pair that same offset on after.
4. **Finish / replays:** for each target and direction also finish/resume captured animations and take a settled endpoint (>=150ms logical time, plus actual pending DOM complete). A timeline driver should use the instrumented animation ledger/DOM milestones, not arbitrary wall sleeps, for reproducibility. Before the next ordinary run restore the original animate method and media environment. For rapid-reversal R13, seek the first pass to 60ms, apply second/third real toggle with each commit, then seek the new animations to stated offsets. Keep previous animation state alive until the real app cancels it. For an in-flight ordinary move, seek its ledger to 60ms then toggle the shelf and log cancellation/clone disposal. Do not artificially cancel row animations before the action.
5. **Frame pairs for EVERY recording row:** save Before.png and After.png at matching initial/mid/final checkpoints, a full viewport and a deterministic sidebar crop with identical dimensions. Produce **Diff0** (strict nonzero per-channel pixel differences, optionally amplified 16x for viewing) and **Diff16** (per-pixel max absolute channel difference >16/255). Publish threshold/formula, changed-pixel counts and raw originals. These names are difference thresholds, not timestamps. If dynamically updating provider labels/spinners produce noise, preserve raw diffs and disclose it; align time/animation phases, and optionally provide a second labelled region diff. Never silently mask the row titles, headers, opacity or transforms being proved. Use identical pointer parking/focus on both sides to avoid hover contamination. Include focus outlines in keyboard pairs.
6. **Geometry alongside images:** collect row and header client rects, transforms, opacity, animation target/keyframe/currentTime/playState, exiting inert `aria-hidden` clones, route and shelf counts at each checkpoint. R01 before must show visible thread-title/metadata glyphs over Snoozed/Settled labels, not merely overlapping bounding boxes. After must show readable separation at matched checkpoints; on non-drag shelf-toggle passes the expected list row animation ledger is empty immediately, no exit clones remain, and retained rows sit at layout positions. R20 drag release may legitimately create its separate reconciliation move; judge that phase by correct drop/release positions and absence of stale transforms, not a blanket zero-animation assertion. Chevron CSS may still transition. Endpoints should have the same intended layout. For R14 an unchanged-key update must create normal move/fade animation after the shelf reset, so a blanket loss of animation fails the control.
7. **Delivery:** upload raw recordings, paired frames, Diff0/Diff16 images, sanitized fixture driver and animation ledger, build manifest and a row-indexed results table. Each row/cell/phase lists artifact URLs and PASS/FAIL/UNRUN/HARNESS, with exclusions stated. No private config or credential material. Upload before scratch cleanup. A few clean endpoint PNGs plus videos with no comparable midframes do not satisfy this matrix.

## Friction and limits

FRICTION:
- tool | Previous evidence index DNS failed in curl and was inaccessible through web fetch; prior evidence could not be independently inspected | Retain a resolvable evidence mirror or hand off the index contents alongside its URL.
- forge | One `cafe pr reviews 25412 --json` call returned retryable SERVER_ERROR (ConnectionRefused); a later single retry succeeded | Stable forge connectivity / typed client reconnect.

No browser, provider, dev server, emulator or device lease was started in this planning pass. No edits, pushes, review comments or PR mutations. Lead-owned worktree and parent scratch directory are preserved. The auditor's own scratch subdirectory is removed after successful upload. Budget 60 minutes; source inventory, real-state recipes, minimum cells, WAAPI comparison contract and upload are the covered work. Runtime seed and captures remain the capturer's work.

VERDICT: SUFFICIENT
