Proof audit, pass 1 — [#28527](https://git.cafe/pingdotgg/t3code/pull/28527)

Reviewed branch `fix/issue-28489`, exact head [676ecd159a](https://git.cafe/pingdotgg/t3code/commit/676ecd159aaa43ac22a9d066ad37d62c07f0620e) against merge-base [955e2bfbb5](https://git.cafe/pingdotgg/t3code/commit/955e2bfbb5c092c54ad795c8d379fec99702a6b5). Local HEAD, git merge-base, complete two-file diff, and current GitCafe PR metadata agree. This is a source-derived capture specification, not a visual pass or a merge-readiness verdict. No captures or tests were run; no source files, PR comments, branches, or forge state were changed.

Findings, in priority order:

1. **Evidence gate is open.** No supplied capture compares these exact revisions. Every affected row below is MISSING. Earlier images and timing results are background only; none satisfies a cell in this matrix.
2. **The changed path includes `#`, threads, and machines.** The sole production caller is ChatComposer. Its one shared menu renders seven item types; capturing only skills misses plain rows, leading icons, mixed row heights, and recycled cross-type contents.
3. **Sizing and lifecycle need moving evidence.** A 28 px estimate and `min(N*1.75rem+1rem,18rem)` reserve coexist with skill badges that make rows 30 px above `sm` and 34 px below it at the default root font size. The diff also replaces the scroll container, fade mechanism, and active-row scrolling. First-open, fade edges, recycling, filtering through zero, and constrained placement therefore require recordings, with named checkpoint stills extracted for comparison. These are capture risks, not confirmed regressions.
4. **Two historical suspicions remain unverified.** A missing initial fade on plain rows and stale fades after empty-to-short transitions have not been reproduced in this audit. R12 and R11 expressly cover them in the real app. Do not report them as bugs based on older harness evidence.

The minimum is **14 affected fixtures, 25 explicit viewport/theme cells per revision**, all in the real web app. R14 is one continuous resize/theme recording with three checkpoints; the other cells are independently initialized runs. This is 23 recording runs per revision, 46 total, plus checkpoint stills and small DOM/geometry transcripts. Recordings may contain more than one named checkpoint in a single flow; do not substitute a static image for the flow. No Firefox, Electron, native mobile, OS matrix, 1,000-skill duplicate, or mandatory component harness is required for this diff.

Profiles: **DL** = 1440×900 light; **DD** = 1440×900 dark; **NL** = 390×844 light; **ND** = 390×844 dark; **SL** = 1440×450 light. Chromium, DPR 1, default 16 px root font, same browser build/fonts/reduced-motion setting on both revisions. Width and theme are explicit per row, not a cross-product. These are mobile-width web captures, not native mobile verification.

| ID | Named fixture and required checkpoints | Data needed / real-app seeding and driver | Diff affected | Minimum cells | Evidence | Pass-1 status |
|---|---|---|---|---|---|---|
| R01 | Mixed `/`, long list, initial top window | Draft route; selected available Codex provider; Show skills in slash menu on; 200 enabled project skills plus real built-in and provider slash commands. Warm inventory with one character then clear. Start recording before typing `/`; show first painted window, settled full drawer, command-to-skill boundary, `/skill:` prefix and source badge without `Skill` suffix. | Yes: new list/container, mixed heights, first window | DL, DD, NL | recording + first/settled/boundary stills | MISSING |
| R02 | `$`, long skill list, initial top window | Same project/inventory, type `$`; 200 seeded skills plus any identical ambient inventory. Include short and long names/descriptions. First row active; no leading icon; source badge has `Skill` suffix. Record initial and settled top before any scroll; bottom fade must be assessed here. | Yes: badge-height list, reserve and fade | DL, ND | recording + first/settled stills | MISSING |
| R03 | `$`, singleton, no overflow | Give one short-named skill a unique singleton marker in frontmatter description; filter to exactly one enabled match. Record opening and settling. Show complete row, drawer padding/seam, no overflow scrollbar or edge fades. | Yes: smallest nonempty LegendList | DL, NL | recording + settled still | MISSING |
| R04 | `$`, four long rows, no overflow | Unique four-result marker; four skills with 70–100 character names and 160+ character descriptions, including non-ASCII text. Filter to exactly four. Show one-line truncation, badge width, all four complete rows and bottom padding. Existing personal/system skills may supply additional badge kinds only if they match the same deterministic inventory on both sides. | Yes: measured height differs from estimate; narrow truncation | DD, ND | recording + settled still | MISSING |
| R05 | `$`, nine badge rows, just below height cap on desktop | Unique nine-result marker, exactly nine project skills, normal one-line labels. At default styles nine badge rows plus p-2 total about 286 px; verify actual geometry. Record settled no-overflow drawer. Do not assume 28 px estimates are actual row heights. | Yes: uncapped reserve with nearly full drawer | DL | recording + settled still + geometry | MISSING |
| R06 | `$`, ten badge rows, capped/overflowing | Unique ten-result marker, exactly ten project skills. At desktop styles content exceeds 18rem; narrow badge rows are taller still. Show initial capped viewport, bottom fade/native scrollbar, then make the final row fully visible. | Yes: reserve capped at 18rem, overflow onset | DL, NL | recording + top/end stills + geometry | MISSING |
| R07 | Long `$`, middle window, both fades | Return to long R02 list. Keep first row active and wheel into the middle without moving the pointer onto rows. Stop with both content edges off-screen. Show recycled rows, both fades and scrollbar thumb; log active option still mounted. | Yes: scroll listener, both-edge mask, pin/recycling | DL, ND | recording from top + middle still + DOM transcript | MISSING |
| R08 | Long `$`, bottom window, only top fade | Same list. Record scrolling to the true end and dragging the native scrollbar thumb to the end/back/end using pointer input. Show final item, bottom padding, only top fade at the true end, scrollbar track/inset. Wheel must stay contained in menu rather than scrolling conversation. Do not use identical numeric scrollTop as a cross-revision checkpoint. | Yes: native scrollbar, end padding, fade and overscroll | DL, ND | recording + final-row/end still | MISSING |
| R09 | Keyboard highlight leaves window, wraps, selects | Start `$` with first row active. ArrowUp to last, ArrowDown to first, then repeated ArrowDown beyond first mounted window (e.g. index 150 of seeded skill inventory). Stop on a far row. Tab-select it, reopen, navigate to another far skill and Enter-select it. Record selected skill token and dismissed menu, without submitting a provider turn. Log editor aria-activedescendant resolving to exactly one head option, matching aria-selected, 1-based aria-posinset and full aria-setsize. | Yes: alwaysRender, dataVersion, per-row scrolling/indexes | DL | recording + far/wrap/selection stills + DOM transcript | MISSING |
| R10 | Pinned off-screen row → newly visible pointer target | Open `/`, leave first row active, wheel well beyond initial window while pointer is outside rows; log pinned option still mounted. Move pointer onto a newly mounted middle skill, show highlight update, click it. Reopen and choose another newly visible row after a second recycle. Correct label, description, badge and inserted skill must agree; editor keeps focus through mousedown. | Yes: cross-type recycling, highlight callbacks and click identity | DD | recording + before-hover/hover/selected stills + DOM transcript | MISSING |
| R11 | Scrolled long → filtered long → few → zero → few → restored | In `$`, wheel to middle, filter to a 30-result marker, then four-result marker, then a guaranteed no-match string, then four again, then clear query back to long list. Keep the menu mounted while editing the token. Show first match active/visible after each change, empty text, no stale fades on four rows, and correct fades after restoring long list. Also clear trigger entirely, reopen and select a filtered skill to exercise remount/selection. | Yes: data refresh, height shrink/grow, zero-row unmount/remount, fade state | DL | recording + each count/empty checkpoint + geometry | MISSING |
| R12 | `@` plain/mixed icon rows, delayed results and selection | Seed at least 100 matching tracked files/folders with `.ts`, `.md`, extensionless and nested long paths; names contain `qzv28527`. Seed five stopped ordinary sibling threads titled `qzv28527 …` via thread.create/thread.metadata.update. Add the other isolated dev server as a saved paired environment named `qzv28527 remote…`; enable Linked environments beta on the fixture server. Open bare `@` then type `qzv28527`: files are queried only for nonempty query (80-file result cap); threads cap at five and also require query; machine needs query and peer-links capability/beta. Show initial plain-row fade, mixed leading icons, long basename/path truncation, recycling into file rows. Select a file, reopen/select thread, reopen/select machine; show correct resulting context. | Yes: no-badge height, themed file/folder icons, thread/machine leading icons, async data and mixed recycled types | DL, ND | recording + populated/middle/selection stills + DOM counts | MISSING |
| R13 | `#` pull-request results, 12-result capped list | Existing stopped-thread route in a project with a recognized repository. Seed 12 local linked PR snapshots from read-only forge metadata, using thread.pull-request.link then thread.pull-request-link.sync through the fixture server's dispatch RPC; include open, draft, closed, merged and a long title, with distinct numbers. Use actual PR identities, no forge writes. Type `#`, record loading if it occurs, first window, scroll last match into view, select it and show inserted PR context. Use numeric query for deterministic subset if host list changes; local linked entries sort first. | Yes: same virtualizer, colored PR glyph, no badge, 12-row cap | DD | recording + top/end/selected stills | MISSING |
| R14 | Existing thread, expanded composer, live theme/width/height changes | Existing stopped-thread route. Start a normal multiline draft ending with ` $` and long skill inventory. With menu open record DL → ND → SL, changing viewport and theme through real app appearance controls/settings RPC, preserving draft/menu state (do not navigate away to settings). Expand text enough that available layer height is below 18rem in SL; log actual anchor/menu/list rectangles and compare reachability to base. Open a short four-result menu at the final geometry as well. Include live theme refresh of visible rows and native scrollbar. A tiny layer already clipping on base must be labelled shared behavior, not asserted as fixed by this PR. | Yes: new minimum reservation vs parent constraint, resize remeasurement, extraData theme refresh | DL → ND → SL | one continuous recording with all three checkpoints + geometry | MISSING |

Seed plan for the capturer:

- Create a new timestamped capture-run directory under the capturer's assigned scratch root; keep both revisions' raw runs and logs. Do not change this audit's worktree. Use two isolated development homes/databases, one at each exact revision. No server may use the developer's live T3 home. Use the same generated project folder contents and same selected provider inventory on both sides. Start dev through the repository's supported runner and record emitted ports; do not bake VITE_HTTP_URL or VITE_WS_URL into the bundle.
- Create a small git project with 200 `.agents/skills/<name>/SKILL.md` files. Each file needs valid YAML frontmatter with `name` and `description`, followed by nonempty content. Names share a unique audit prefix; assign description markers to groups of exactly 1, 4, 9, 10 and 30 of those 200 skills. Use long names/descriptions in the four-result group, short ones for singleton, and disjoint marker words unlikely to fuzzy-match ambient skills. Keep exact ordering, file tree and description contents in the manifest. The same skills serve R01–R11/R14: do not create a new app/database per row.
- Add the folder through the real app, select an available configured provider, create a draft, enable Show skills in slash menu at `/settings/chat` (Advanced), then return to the draft at `/draft/<draftId>`. Refresh workspace discovery via `server.refreshProviders` with provider instance and cwd, or type once and wait for the real workspace snapshot. A folder alone is insufficient if its selected provider has not loaded that cwd. Record actual provider/command/skill inventories. Never assume the full `/` or `$` list has exactly 200 entries when personal/system skills are also installed. The filtered count fixtures must be exactly 1/4/9/10/30; if fuzzy matches add entries, adjust unique markers before either side is captured.
- For R12, query `@qzv28527`, not bare `@`: the current hook does not request filesystem search for an empty query. Use `thread.create` plus `thread.metadata.update` on isolated state for five nonarchived ordinary threads, without running a provider. The current composer thread is excluded. The two revision servers can be each other's saved environment; environment context selection does not run a remote agent. `server.updateSettings` accepts `{patch:{linkedEnvironmentsEnabled:true}}`; actual server capability is peerLinks:true. A saved second environment and enabled beta suffice for machine suggestions; no fleet machine needs to be modified or contacted. If pairing the second isolated environment cannot be completed, mark machine coverage MISSING rather than silently dropping that item type or labelling the whole `@` run complete.
- For R13, use actual forge metadata and local cached link/snapshot commands, never create PRs to manufacture fixtures. Snapshot fields include state/title/headBranch/baseBranch/isDraft/updatedAt/syncedAt, and stack:null. The project repository identity and link repository must match. Since linked PRs sort before host results and the menu caps results at 12, twelve links stabilize the visible set; record their final ordered metadata after discovery settles. If live sync changes it, make both revisions use the same settled snapshots before capture. This row is real app with seeded data, not a component harness.
- Reuse new-draft route for most fixtures and `/<environmentId>/<threadId>` for R12–R14. UI settings/palette/keybindings do not separately instantiate this component. One stable stopped thread is sufficient; no provider turn, pending question, or native simulator is necessary.
- The source-derived default row estimates (30/34 px skill rows, 28/32 px plain rows) are predictions, not measurements. Log actual item count, row height, clientHeight/scrollHeight/scrollTop and menu/anchor rectangles for R05/R06/R11/R14. Distinguish a physically off-screen pinned node from a visibly highlighted row; screenshots cannot prove ARIA pinning. Before has no new full-set ARIA fields; their absence on base is expected, not a failing comparison.
- Use a recording that exposes the first painted frame for R01/R02/R06/R12. A video encoder dropping initial frames cannot prove first-frame fill; retain a frame/timestamp trace and mark first-paint coverage MISSING if it was missed. Do not wait for a settled screenshot and claim that it proves first paint. For all other interactions keep the trigger, movement and final checkpoint visible in the same recording. Freeze incidental timestamps where feasible or identify unrelated sidebar differences; do not edit menu pixels.
- Budget guide within 90 minutes: 20 minutes for both server setups/shared seed, 40 for the reusable recording driver and specified cells, 20 for side-by-side checkpoint export/upload and manifest, 10 contingency. If a required cell is not produced, retain its MISSING status; do not silently substitute an older commit, harness, browser, theme or viewport.

Render-path inventory and why the matrix is this size:

- **Changed production component and callers.** Only `ComposerCommandMenu.tsx` changes product code. ChatComposer is its sole JSX production caller, rendered through ComposerCommandMenuLayer and portalled to body when a trigger exists. ChatView mounts ChatComposer for both draft and server-thread routes. The private ComposerCommandMenuItem is called only by the new LegendList renderer. The other changed file adds six browser tests and a local Menu helper; that helper is test-only and needs no product screenshot. ComposerStashMenu shares the layer but never calls the changed menu; command palette results, model picker, and branch picker use shared primitives that this diff does not modify.
- **Props.** `items` controls zero/nonzero, count-dependent minimum, cap, key/order, row type and measured height. `activeItemId` controls `alwaysRender`, `dataVersion`, aria-selected, active background and the new per-row scrollIntoView effect. Nonempty production menus resolve a valid first item by default; null active with a nonempty list is a test-only shape, not a mandatory app fixture. `resolvedTheme` reaches path icons and extraData; CSS theme colors reach all rows/fades/scrollbars. `triggerKind` controls listbox label, slash skill prefix and badge suffix. `isLoading` and `emptyStateText` affect only the unchanged empty text. `listId`/optionId affect identity/ARIA without changing layout. `onHighlightedItemChange`/`onSelect` reach recycled rows and must retain the newly visible item's identity.
- **Every item shape.** Built-in slash command and provider slash command: label/description only, covered together in R01. Skill: no leading icon; `/skill:` prefix only for slash trigger; source badge with suffix only for dollar trigger, R01/R02/R04. Path: file/folder kind plus theme-specific Pierre icon, basename label, parent-directory description, R12. Thread: MessagesSquare icon, title and local/remote thread description, R12. Environment: machine icon, label and Machine description, R12. Pull request: state-dependent icon/color, number and title, R13. File/folder and PR open/draft/closed/merged glyphs fit in the same fixed icon box, so put them in mixed lists, not separate viewport/theme matrices.
- **Badge branches.** Source-kind resolution covers App (plugin path), Repo, Project, Personal, System and Provider fallback, using six icon/label pairs. The diff changes neither resolver nor badge renderer; all use the same fixed-height, nonwrapping, shrink-0 badge. Project vs Project Skill already supplies suffix/no-suffix width and measured-height cases. Existing alternate-source entries can be included without separate fixtures; do not invent six provider installations or six harness rows to cover unchanged glyph/word substitutions. R04 covers label/description competition with a badge at narrow width. Enabled/user-invocable filtering, deduplication, scope/description fallbacks, built-in availability, compact availability, side-chat reason text and prompt-start eligibility alter items/count/text, not another changed layout branch; their shapes reduce to R01/R03–R06/R11.
- **Empty/loading/error branches.** Empty skill/slash loading says Searching workspace skills; PR loading says Finding pull request; path/null loading says Searching workspace files. Settled defaults distinguish skill, path and command, with custom PR unavailable/error/no-match/no-PR messages supplied by useComposerMenuItems. There is no separate error component, spinner, or error-row type. This JSX is unchanged and LegendList is absent when items is empty, so no separate still matrix for these messages is required. The relevant changed lifecycle is populated → zero → populated in R11 and asynchronous path arrival in R12. `isLoading=true` with nonempty items intentionally leaves the same list visible; no separate loading-overlay fixture exists. Trigger null with nonempty data is not produced by this caller.
- **Counts and lengths.** Zero; one; few; nine badge rows below desktop cap; ten at capped branch; hundreds overflowing; filtered-long-to-short and back. Empty descriptions, short labels and no leading icon fit the same one-line minimum as normal plain rows; long/Unicode labels truncate instead of wrapping. R01/R04/R12 cover width competition, R05/R06 the sizing boundary. An additional 1,000-item run would duplicate the same visual branch; count-dependent DOM windowing is already covered with 200 plus a mounted-node count transcript.
- **Interaction branches.** First open/measurement; keyboard ArrowUp/Down including modulo wrap; Enter/Tab selection; mousemove highlight, preventDefault mousedown and click; wheel and native thumb drag; top/middle/bottom fades (1 px edge threshold); off-screen active pin; new pin/dataVersion; recycled-row item-id change; query filtering and empty remount; Escape/dismiss and reopen; live theme/viewport/anchor changes. R01/R02/R06–R14 cover these. Include Escape specifically before reopening in R11. No PageUp/PageDown/Home/End menu handler exists; do not add dedicated rows for nonexistent commands. An exact >1 px fade toggle is a DOM/scroll transcript checkpoint within R07/R08, not a separate video matrix.
- **Geometry and surfaces.** The layer tracks anchor/form/main-surface geometry, window scroll/resize and ancestor resize. It computes width from composer inset and available maxHeight, with a 96 px floor, while this diff adds a list minimum up to 18rem. R14 deliberately tests that constraint. Existing-thread docking and multiline expansion matter through geometry; a pending-question answer uses the same call and geometry, suppresses machine suggestions, and literal pending answers suppress triggers entirely. No pending-question-specific changed rendering is present, so no provider question fixture is required. Web and Electron share this React code; there is no platform conditional in the changed components. Native mobile has a separate composer and is not changed. Local/remote/tunnel modes do not introduce another render branch here; asynchronous data arrival is represented by R12 and no wire contract changes.

Excluded standalone capture rows (status is scope exclusion, not evidence of visual correctness):

| ID | Family | Diff affected? | Status / coverage |
|---|---|---|---|
| U01 | Every empty/loading/error message spelling, trigger-null test-only shapes | Text renderer unchanged; zero/nonzero transition affected | EXCLUDED as standalone captures; transition required in R11/R12 |
| U02 | Every provider, badge scope, machine kind, file extension, PR state as independent fixtures | Glyph/content substitutions unchanged; shared row virtualized | COLLAPSED into R01/R02/R04/R12/R13, all seven item shapes still required |
| U03 | Settings toggle, plan/side/compact availability, inventory refresh, disabled/noninvocable skills | Alter items, not changed layout branches | COLLAPSED into mixed/count/filter fixtures; record inventory and setting values |
| U04 | Native mobile, Electron shell, Firefox, macOS/Windows native scrollbars, high-DPR/zoom, extra 1,000-skill count, screen-reader speech | No changed platform/browser-specific source path in this scope | OUTSIDE this minimum; no claim of verification |

Required manifest per run: row ID, exact git SHA, base SHA, Chromium version/OS, viewport/theme/DPR/root font, route kind, seed/inventory digest and actual ordered item IDs/count, checkpoints/actions, recording URL, checkpoint still URLs, DOM/geometry transcript URL, capture classification real-app, and final status MISSING/PASS/DIFFERENCE/FAIL with observed differences. Mark any substitute harness as HARNESS and keep the missing real-app cell open unless the lead explicitly accepts the substitution. At pass 1 every R01–R14 status remains MISSING.

Source anchors: [menu and private row renderer](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/ComposerCommandMenu.tsx#L95), [sole production call](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/ChatComposer.tsx#L3301), [item construction and loading/empty handling](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/useComposerMenuItems.ts#L185), [layer constraints](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/ComposerCommandMenuLayer.tsx#L21), [keyboard behavior](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/composerCommandKey.ts#L198), [active default](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/chat/composerMenuHighlight.ts#L1), [plain-row styles](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/ui/autocomplete.tsx#L122), [badge dimensions](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/ui/badge.tsx#L6), [fade state branches](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/components/ui/scroll-area.tsx#L5), [path query gating and limit](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/apps/web/src/state/queries.ts#L291), [thread/machine query rules](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/packages/client-runtime/src/composerThreadItems.ts#L48), [PR local link/snapshot commands](https://git.cafe/pingdotgg/t3code/blob/676ecd159aaa43ac22a9d066ad37d62c07f0620e/packages/contracts/src/orchestrationV2.ts#L3036).

FRICTION: none. No server or browser started; worktree retained unchanged. Scratch artifacts remain under `/tmp/lead-28527-r1/audit1/`. Available root disk was 19 GB at both checks, above the 5 GB stop threshold.

VERDICT: INSUFFICIENT R01–R14 (all affected capture cells missing)
