# Read-only proof audit, pass 1

PR [#21165](https://git.cafe/pingdotgg/t3code/pulls/21165) / GitHub [#21165](https://github.com/pingdotgg/t3code/pull/21165), Aforno/t3code `fix/thread-details-pr-spacing`, original head [991c2ea](https://github.com/Aforno/t3code/commit/991c2ea1f7c06a7870c9dbe944821a52d6e3e228), against main.

This is a source-derived capture plan, not executed visual verification. No server/browser, repository writes, commits, comments, push, or watcher were used. No live T3 userdata was accessed. Only the specified checkout was inspected for project source. Skill instructions were read outside it; this artifact is outside it. The parent owns capture execution through native preview.

## Scope and source findings

The complete cafe diff was returned untruncated, one file and one hunk: remove `-mt-1` from the metadata flex row below `openRow(detail.title, "row")`. Cafe reported head `991c2ea1f7c06a7870c9dbe944821a52d6e3e228` and comparison/base `952921f39acd7793187e2b14650f1cbc447f483d`. The original head object is not locally available; no fetch was attempted. Current checkout is takeover/pr-21165 at `eea9895368`, with the implementation agent's matching one-line working change observed during this audit. This pass makes no claim to have reviewed the historical full component at the original head.

Source inspected: `apps/web/src/components/chat/ThreadDetailsPrRow.tsx`, `ThreadDetailsPrRows.tsx`, `ThreadDetailsPanel.tsx`, `ThreadDetailsCard.tsx`, `ThreadDetailsControl.tsx`, `threadDetailsPanelStyles.ts`, `threadDetailsCardLayout.ts`; `apps/web/src/components/BranchToolbarBranchSelector.tsx`; PR action/presentation logic; client PR query families and contracts.

Affected predicate: `detail !== null && (showChecks || trailingAction !== null || watching)`. `showChecks` requires a non-null checks rollup, no conflict and non-draft. A closed/merged PR can still enter this branch if its retained checks produce a rollup: do not classify all closed rows as unaffected. `watching` additionally requires the stop callback and an open state. Actions prioritize Resolve, Ready, Fix, then Merge. Pending checks alone have no action. Empty checks or only neutral/skipped checks have no count rollup.

Title stays one line with truncation; long titles do not wrap. Metadata stays `h-6`, with number, optional check count, optional eye, flexible spacer, optional action. Removing the negative margin adds one spacing unit (normally 4px) between title and metadata and to total affected row flow height. Verify the computed value in the client. Main title control is `h-8`; metadata `h-6`; outer `pb-2` remains. Unaffected rows below affected rows move down with normal flow.

Only production path into this component: panel-mode branch selector -> ThreadDetailsPrRows -> main row, and expanded rest -> ThreadDetailsPrLinkRow -> the same component. Toolbar mode renders a different badge. Main PR can come from linked PR selection or branch-status fallback. Expanded links resolve their own matching project by host/repository. Missing project/capability/reference prevents detail loading. With no current linked PR, rest is empty even if links exist. With no number or URL the row is absent. Dismissed stack links are filtered; the current PR is deduplicated from rest. Flat and stack links use the same expanded row renderer.

## Collapsed fixture matrix

Legend: A = affected class path; U = unchanged own layout; R = recording required for interaction/time behavior; S = still endpoint proof. All captures are whole real app, not isolated components, cropped panels, or DOM reconstructions. “390” means responsive web, not native mobile.

| ID | Fixture / conditional layout | Affected? | Minimum proof | Collapse / assertions |
|---|---|---|---|---|
| A1 | Main long title; open, non-draft, passing checks, Merge, watching | A | S + R1 | Most crowded plain-action row: title ellipsis; number/count/eye/action separation; metadata starts below title hover target. |
| A2 | Expanded linked short title; failing checks, Fix with arrow, watching | A | S + R1 | Same component, different caller and destructive arrow action. Place below A1; spacing between rows and Show less remains clear. |
| B1 | Main short title; draft with Ready permission; no count, no watch | A | S | Action-only branch. Ready vs Merge differs in label/tone, not row geometry. |
| B2 | Expanded linked long title; conflicting, Resolve with arrow, watching | A | S + R2 | Count suppressed even when checks exist. Draft conflict also prioritizes Resolve; no need for another still. |
| C1 | Main long title; non-draft, pending checks, no action, no watch | A | S + R3 | Count-only row. Closed/merged retained passing checks are geometrically equivalent; include one as C1 alternate endpoint in R3. |
| C2 | Expanded linked short title; open draft, no Ready permission, empty checks, watching | A | S + R2 | Eye-only metadata. Stop watching causes simple-row fallback; confirms branch exit and surrounding flow. |
| U1 | Detail null: short/long snapshot label or number-only label; not watched | U | Source + endpoint in R3 | Loading/failure/no project/no reference/no capability collapse to simple row; snapshots alone never prove the changed layout. |
| U2 | Detail null, open/default-open snapshot, watching | U | Endpoint in R3 | Split primary/secondary layout with separator and eye. Detail arrival enters A1/C2. Closed snapshot cannot show watch control. |
| U3 | Detail exists but no count/action/watch: closed/merged with empty checks; draft lacking Ready; open lacking Merge permission with empty checks | U | Source + C2 post-stop endpoint | All render openRow(label, row), not the detail-title/metadata branch. Do not enumerate state icons as separate spacing fixtures. |
| U4 | Toolbar badge, absent PR, hidden card, no git cwd/repository, no number or URL; no-rest/collapsed/dismissed links | U | Source; collapsed endpoint in R1 | No changed element rendered. Single/no-rest vs collapsed rest share visible main row spacing. |
| L1 | Full/compact/essential density; inline vs popover; expanded list scrolling | A for flow, shared row geometry | R1 + R4 | 1440 normally inline; 390 normally popover, verify actual presentation. Version-control branch survives density changes; workspace controls disappear. More affected rows add flow height; expanded list height is excluded from density fitting measurement. |
| I1 | Title/checks/watch/action hover and keyboard focus; checks popover; Merge dialog then Cancel | A anchoring; overlay internals U | R1 | Record actual input, open/close and return to rest. No real merge, Ready, Fix, or Resolve mutation needed for this CSS change. |
| I2 | Pending/disabled labels: Merging..., Marking..., Preparing...; checks pending -> passing/failing; detail initial -> success | A anchoring; behavior U | R3 | Long pending labels are width-risk endpoints. Use controlled synthetic query/action state only if practical; never trigger a real agent/forge mutation just for evidence. If unavailable, explicitly report this as source-only coverage. |

## Minimum capture schedule

Use three bundles, each with one main and one expanded linked row: A=(A1,A2), B=(B1,B2), C=(C1,C2). Swap which PR is current in synthetic data rather than editing layout. Each bundle deliberately includes one short and one long title, across main and expanded callers. This covers six meaningful metadata shapes without an exhaustive Boolean/state/provider Cartesian product.

For EACH A/B/C bundle capture before AND after at 1440x1000 light, 1440x1000 dark, 390x844 light, 390x844 dark: 3 bundles x 4 viewport/theme variants x 2 revisions = **24 whole-app stills**. Six fixture rows are represented in twelve matched pairs. This is a lower bound only if both rows and adjacent controls are visibly legible in the viewport. If the 390 panel cannot show both, take additional whole-app scroll-position stills; never hide rows or shrink UI to meet the count. Keep dimensions, zoom, app state, card visibility, scroll position and cursor-rest location identical within each pair. Annotate actual density/presentation and PR IDs in filenames/manifest.

Record R1 (expand/collapse, title/check-count hover or focus, checks popover, eye hover, Merge dialog/Cancel), R2 (stop watching B2 and C2; resulting action-only and simple rows), R3 (synthetic detail arrival, checks changes, closed retained-count endpoint and feasible pending-label demonstration), R4 (height reduction to density changes plus expanded-list scroll). Interaction/time assertions require video, not a collection of endpoint screenshots. No need to wait ten minutes for refresh: controlled state delivery tests layout, not refresh timing.

Minimum honest recording plan: one short continuous session per width and revision, combining R1-R4 and switching themes as needed = **4 recordings**, with timestamps for each assertion and both themes' focus/hover endpoints. This does not require every interaction in both themes: the 24 stills cover static theme geometry. If combining makes clips unwieldy, split by bundle without reducing covered actions. A video frame can supply a named static endpoint only if exported as a whole-app still at the exact required viewport/theme and included in the manifest. Recordings do not silently replace missing still pairs.

Density boundary testing needs additional heights beyond the fixed still viewports. Choose them from actual measured content/available height, not arbitrary screenshots. Verify no oscillation, cut-off metadata or inaccessible Show less at the boundary. This is an ordinary consequence of the 4px flow increase, not a demand to retest every panel control.

## Real-app synthetic seeding route (parent execution; not run here)

1. Use a NEW isolated T3 home exactly `/tmp/ev-21165` (refuse an existing path or symlink; do not overwrite someone else's fixture). Run the existing dev runner only later in the parent's authorized execution: `T3CODE_DEV_SEED=0 vp run dev --no-seed --home-dir /tmp/ev-21165`. Explicit home wins over worktree default. Keep VITE_HTTP_URL and VITE_WS_URL unset. Never run migrate-dev-db, copy any existing userdata, or access `~/.t3/userdata`. No startup pairing token or credential belongs in public uploads.
2. Let the new empty home migrate, then create a synthetic project and stopped thread using normal app/RPC paths. Point the project to a synthetic git repository under `/tmp/ev-21165`, with a synthetic GitHub-shaped remote/repository identity so expanded links find that same project. Do not change this checkout's remotes. Do not start a provider turn or schedule work. A raw DB seed is possible only after stopping the isolated server and using its current schema, but RPC creation is less brittle than hand-copying projection payloads. The existing seed-issue-21397 script defaults to copying another database and is NOT an appropriate command for this requirement.
3. Seed thread links with `thread.pull-request.link` (host, repository, positive number, URL, source manual) and `thread.pull-request-link.sync` (valid snapshot, stack null); use explicit unique command IDs. Use two links per bundle, not six simultaneously. Suggested title strings: `Fix spacing` and `Keep linked pull request metadata comfortably separated from a very long title in the thread details card on narrow screens`. Synthetic URLs need not correspond to real PRs; never open them on the forge. Set watch via `thread.pull-request.watch` only within this synthetic stopped thread, or supply a schema-valid watch in a stopped-server synthetic payload if the normal command requires provider data. Watch starts server monitoring, so avoid running any synthetic agent and stub host reads if using it.
4. IMPORTANT: links and snapshots only populate selection/fallback labels. The changed branch needs a **successful PullRequestDetail** query. For this CSS proof the least invasive available frontend seam is the existing writable detail family: import `/src/state/pullRequests.ts` and `/src/rpc/atomRegistry.ts` through the dev page in native preview, resolve `pullRequestEnvironment.detail({environmentId,input:{projectId,host,repository,number,allowStale:false}})`, and write a schema-valid `AsyncResult.success(detail)` through `appAtomRegistry.set`. `packages/client-runtime/src/state/pullRequests.ts` intentionally wraps detail queries in `writableQueryFamily`; its write sets self. This is fixture data in the real mounted app, not a component harness or painted DOM. Import Effect's AsyncResult through the same Vite-resolved module identity; do not invent a serialized imitation. Include provider, branches, state/draft/mergeability, counts, checks, action capabilities, viewer permissions and supported merge methods from the actual contracts. Permission/capability fields must permit Ready/Merge where requested; title/state/checks alone do not produce those actions.
5. This injection route is source-supported but NOT verified here. A later successful underlying query can replace injected data, and the separate checks query can override detail checks by timestamp. Let outstanding reads settle, inject deterministic details, inspect the actual resolved controls, and reinject deliberately for R3. Do not claim a stable fixture if live query traffic changes it. If necessary use an external synthetic RPC/host fixture transport for detail, checks, summary and watch reads; scripts and logs stay outside the checkout and must be uploaded. Simply shimming `gh pr view` is insufficient: the current GitHub implementation uses direct REST/GraphQL. This is the main setup feasibility risk. An unavailable query-injection/transport seam is a reason to report missing proof, not to label snapshot fallback screenshots as affected.
6. For U1/U2, use a distinct synthetic link with no matching project or settle the detail request without data; for U3, inject detail with empty checks and no available action/watch. For pending action labels, the local hooks own pending state: writing detail alone cannot create it. Either safely fixture the action/handoff transport, or retain source-only coverage and say so. Never call real forge actions or start work just to show Preparing....
7. Capture the unchanged baseline and implementation with identical seed/config and fixture recipe, recording actual commit/worktree state. The parent must coordinate with the implementation agent before any baseline source toggle; this read-only auditor did not perform one. Fresh home means no real thread history: add only synthetic stopped messages if necessary to establish realistic chat/card placement. Reload/reapply fixtures between before/after as necessary. Keep all helper scripts outside the worktree and upload any that are created; none were created by this audit.

## Feasibility and proportionality

Do not build a permanent test harness, add backend tests, or exercise provider-specific mutations for one spacing class. Web and desktop share this component; native mobile has a separate implementation and is unaffected. Remote/tunnel routing, provider CLIs, command palette and Settings introduce no additional renderer here. Inline/popover and responsive panel density are relevant; use source equivalence for the rest. Stack and flat PR lists share row geometry, so one flat linked set suffices; prove expansion ordering/dedup from source rather than minting native forge stacks.

A practical parent sequence is: prove A1/A2 injection at 390 first, before committing to all captures; settle how checks/watch are controlled; then batch the three bundles across widths/themes. If only static seam works, ship static proof with interaction omissions explicitly marked. Do not describe this audit as visually passed. No concrete code defect beyond the intended spacing issue was found by this source pass; that is not a visual acceptance verdict.

FRICTION: Original head absent locally; complete original cafe diff supplied the exact one-class scope without fetching or changing refs.

FRICTION: The implementation agent changed the target file during this read-only audit; observed working diff matches original intent. No auditor repo edits occurred.

FRICTION: Snapshot seeding alone cannot enter the affected layout; full detail query fixture and separate-checks precedence must be resolved before capture.

FRICTION: Pending action labels require controlled action/handoff state, not only injected detail; keep source-only if a safe synthetic seam is unavailable.

FRICTION: No server/browser/captures executed by instruction. The proposed 24 stills and 4 recordings are obligations for the parent, not delivered evidence.
