# Proof audit, pass 1: MCP App initial color scheme

Issue [#33303](https://git.cafe/pingdotgg/t3code/issues/33303), also mirrored as [#33303](https://github.com/pingdotgg/t3code/issues/33303).

Audited local branch `fix/issue-33303`, head [6ac3e2bfe3](https://git.cafe/pingdotgg/t3code/commit/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5), against the supplied base [1a43f4870d](https://git.cafe/pingdotgg/t3code/commit/1a43f4870d). The explicit base-to-head diff contains exactly three files: the frame, shared bootstrap, and bootstrap test. Local `main` has since moved; `git merge-base HEAD main` currently returns [184d915d87](https://git.cafe/pingdotgg/t3code/commit/184d915d8774615a01ebae0350235a1039852e85). Use the explicit supplied base for before evidence, not the current name `main`.

This is a source-derived capture specification, not a finding that captures have passed. No existing image was evaluated, no browser was driven, no tests were executed, no worktree file was changed, and no server was started or stopped by this auditor.

## Source anchors and caller inventory

| Changed unit | Every production caller and render path | Consequence for the matrix |
| --- | --- | --- |
| [McpAppFrame](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/components/chat/McpAppFrame.tsx) | Its only direct caller is [McpAppTimelineRow](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/components/chat/TimelineAssistantRows.tsx), dispatched for `row.kind === "mcp-app"` by [MessagesTimeline](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/components/chat/MessagesTimeline.tsx). [Session projection](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/session-logic.ts) derives that row from a `dynamic_tool` item with an accepted app reference. A valid reference is recognized independently of item completion; an HTML-render result has earlier precedence. | Existing persisted tool cards and newly appearing cards share the component. The key is `attachmentId`; a new attachment/remount initializes fresh state. Ordinary item revisions do not inherently replace the document. |
| [injectMcpAppThemeBootstrap](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/packages/shared/src/mcpApp.ts) | The only production invocation is [assetFileResponse in http.ts](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/server/src/http.ts). It runs for `renderIntent === "mcp-app"`, inline rather than download, HTML content type, and a readable document within the size bound. The normal asset route calls it for GET and HEAD; HEAD computes the body but does not render one. | Query parsing happens in the child at execution time, not in a theme-specific stored snapshot. Valid light/dark and missing/invalid query states form the relevant partition. Other downloads/HTML previews are outside this bootstrap path. |
| Shared helper consumers | Web/desktop mint URLs with the new parameter. [Mobile ThreadMcpApp](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/mobile/src/features/threads/McpAppWebView.tsx) requests the same server render intent but assigns the asset URL unchanged to a nested iframe. Its feed and fullscreen screen both use that component. | Mobile exercises the missing-query fallback. This diff does not implement the early-theme handoff there; do not claim a mobile initial-paint fix. |

## Complete branch, prop, and interaction inventory

The following inventory includes unchanged branches so their exclusion from the minimum proof set is explicit. “Inherited” means a fresh document uses the changed bootstrap, while its steady layout remains unchanged.

| Input or branch | Visually distinct states and interactions | Diff effect / fixture coverage |
| --- | --- | --- |
| `src`, asset state | `src === null` + Loading: empty transparent 320px reserved row; Failure: centered “Unable to load the … app”; Success: adopt URL then render iframe. Once adopted, later URL mints do not replace it. HTTP failure after URL adoption is a browser error document, distinct from the host Failure row. | Success adoption directly changed: C1–C6. Loading/failure/HTTP-error guards: G1–G3. |
| Theme resolution | [useHtmlRenderTheme](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/hooks/useHtmlRenderTheme.ts) receives resolved `light`/`dark`, never raw `system`. Explicit mode can agree with or differ from browser `prefers-color-scheme`; system follows it. Custom palette/font values are delivered by later host context, not by the new query. | Direct: C1–C4. Explicit agreement collapses to the same resolved scheme as those rows. Custom-palette boundary: G12. |
| Theme entry points | Settings → Appearance; command palette → Change appearance; registered `appearance.cycle` command. All call [setAppearanceMode](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/apps/web/src/hooks/useTheme.ts). Theme selection/preview can also change resolved appearance or palette through the same theme hook. | One mode mutation path is enough for initial scheme proof; C5/C6 use palette/command controls while keeping the frame mounted. No duplicate capture per entry point. |
| Initialization and time | New frame before app HTML paints; HTML painted but no `ui/initialize`; initialize response; `ui/notifications/initialized`; settled tool input/result delivery. The bootstrap listens for parent initialize response and parent host-context theme messages after setting the URL scheme. An app may initialize immediately, after 4s, or never. | Delayed phase and handoff: C1–C4. Live settled update: C5. Pre-init theme change: C6. Never/instant cases: G4/G5. All transitions require recording. |
| URL lifetime | First URL stays frozen while theme, width, tool information, or item detail changes. Close/Show app explicitly clears `src` and increments document generation. Thread return, page reload, virtualization/fold remount, and new attachment create another document. | C1–C4 cover thread return and reload. C5 covers same-component reopen with latest scheme. A document must not reload on theme update. |
| `closed` | Closed compact border row with server label and Show app button; Show app creates a new frame document. `onRequestTeardown` while fullscreen first returns inline; inline teardown waits for host teardown then closes. | Reopen directly reaches new URL code: C5. Closed layout is unchanged. |
| `navigatedAway`, load count | First document load is allowed; another load in the same generation disposes host and replaces frame with “The … app left its page and was stopped”. No restart button in this state. | G6; unchanged guard, not proof of the initial theme fix. |
| `app.prefersBorder` | `true`: rounded border in inline mode. `false` or absent: no border. Fullscreen suppresses border. | G7; inherited early canvas, unchanged border branch. No three-way cross product needed for false/absent. |
| Display mode, support, width | Inline: observed parent width, no width breakpoint in this component, height defaults to 320px. Fullscreen: top-layer viewport box, 40px header with truncated server label and exit button, frame flexes to remaining height, inline spacer retained. Unsupported popover capability advertises inline only. `width === 0` is initial measurement, not a separate JSX layout. | G8/G9; unchanged. Wide core proof suffices for the query/bootstrap branch. Narrow smoke row G10 is specified separately. |
| Height and size notifications | Missing height retains prior/default height; finite values clamp/round to 80–2000px; non-finite values reset to 320px. Fullscreen ignores app height updates; restoring inline retains its previous height. | G11; unchanged. Each boundary is a labelled harness stimulus, not a new theme combination. |
| `awaitingUser`, fullscreen exits | Transition to `awaitingUser === true` returns inline; false/undefined retain display mode. Exit button, Escape/light dismiss, focus leaving the box, and approval requests return inline. Fullscreen request is refused if unsupported, frame not focused, no activation, recent parent-page input, awaiting user, active confirm, or another card fullscreen. | G8/G9. These are every fullscreen branch; all refusals collapse to unchanged inline layout. Each exercised interaction is recording. |
| Server/tool/reference strings | `attachmentId` selects asset and component key; `tool` determines filename and lookup; `resourceUri` and server/tool identity must validate. `server` appears in iframe title, closed/error/stopped text, fullscreen header, and approval copy. Long names change wrapping/truncation in those fallback/chrome states, not initial canvas. | G1/G2/G6/G8 use a 256-character server label in a harness if testing overflow. Invalid or absent references produce no card: G0. |
| Item/detail/context props | Environment selects destination asset/RPC origin; source thread and item/revision select detail; conversation thread may differ for inherited history. Undefined detail or non-dynamic detail supplies no tool call; dynamic input can exist before result; later result is delivered once initialized. Tool definition absent/present affects host context. `onFullscreenChange` pins/unpins timeline; `onSendMessage` absent/present allows/refuses send. | No frame layout branches from these data shapes. New persisted cards, live result updates, inherited rows, and remote origin all use C1’s scheme path. G13 provides integration representatives without multiplying the capture set. |
| App interactions exposed by host | Tool call: missing permission/noncallable/read-only/writable approval/decline/RPC failure; read resource success/failure; open link requires frame focus + activation; send message requires callback + approval; update model context includes conversation ID; download approval, embedded versus linked resource, missing bytes, >25MiB refusal, multiple filenames. Approvals force inline and display the shared confirm dialog. | No changed theme computation or frame URL creation here. G14 inventories the unchanged modal/refusal layout; behavior variants sharing the same chrome do not require separate screenshots for this fix. |
| Bootstrap query branches | `light` and `dark` set the document root scheme immediately; missing, empty, or any other string leave it unchanged. `URLSearchParams`/location access throwing is caught and listener registration still proceeds. Existing unrelated URL query/path/fragment components are retained by URL parsing and `searchParams.set`; parameter replaces a prior value. | Direct: C1–C4; fallback harness H1–H3. Normal asset URL is resolved absolute by [assetUrlStateFromResult](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/packages/client-runtime/src/state/assets.ts); malformed URLs already become Failure before this component. |
| Bootstrap messages | Only parent-window messages, JSON-RPC 2.0, initialize responses with defined ID/no method and `result.hostContext.theme`, or `ui/notifications/host-context-changed` with `params.theme`; only light/dark mutate the scheme. Other source, shape, method, version, or theme is ignored. | Existing behavior retained: C1–C6 show accepted messages; H3 inventories rejects. |
| HTML insertion and vendor ownership | Existing doctype with optional stored CSP: insert after prefix and before vendor HTML/scripts; no matching prefix: prepend doctype + script. Stored CSP remains before executable bootstrap. The HTTP pipeline also inserts cookie compatibility bootstrap. Vendor CSS/scripts can explicitly override root scheme or paint their own background. | H4 covers insertion shapes; H5 covers explicit vendor surface. The change sets a browser scheme, not exact T3 palette variables/background and not a vendor-style override. |
| Platform / connection | Web vs Electron changes `hostContext.platform` only after initialization; no initial-theme platform branch. Coarse pointer/hover, locale, timezone and zero safe-area insets affect host context only. Local, remote and tunnel use the prepared connection’s HTTP base URL. | One headless Chromium web pass proves this source path for web/desktop. Real Electron is not a minimum requirement here; do not label it an Electron run. One real minted-URL success exercises route acceptance with the added query; no independent connection-mode capture is required absent a transport failure. |

## Fixture seed and observation contract

Use the lead’s existing isolated dev server and Claude thread; never restart or stop it for this audit. Navigate through the app’s known thread link, or `/threads/<environmentId>/<threadId>` (redirects to `/<environmentId>/<threadId>`). Use a second ordinary thread as the away target. No real thread IDs, origin, pairing link, signed URL, auth token, or private thread contents belong in published evidence.

The reviewed existing scratch `slow-app-server.mjs` offers `slowapp.show_chart`, resource `ui://slow-chart/app.html`, MIME `text/html;profile=mcp-app`, with an empty object input. Its document leaves the canvas/background unstyled, shows “Loading bundle…”, sends `ui/initialize` after 4000ms, then sends `ui/notifications/initialized` and displays “App loaded (theme: …)”. Its resource metadata does not set `prefersBorder`, it sends no size notification, and it has no close/fullscreen controls. Invoke it twice through the lead’s already configured Claude thread to get two distinct stored attachments. This is a real T3 integration fixture using a synthetic MCP app; label it accordingly. It is not a Datadog capture.

For every core take, begin recording before the mounting action; keep recording across the whole 4s pre-init window and until both cards show the loaded label. In each theme case, record (1) another thread → fixture thread, then (2) full page reload while the fixture thread is active. An initial direct route load and a return are the same new-frame branch; return is mandatory because it reproduces the issue. Keep both cards visible at wide viewport, or explicitly record scrolling if they do not fit. Stills cannot demonstrate absence of a flash. Use a continuous recording at sufficient cadence to see a subsecond white interval; optional contact-sheet stills supplement the recording, never replace it. Record the actual capture frame rate/cadence, viewport, T3 appearance, emulated preference, base/head and the initialize milestone. Do not infer initialization from a fixed post-click sleep alone.

The loading label is the pre-init phase; the loaded label is the initialize-response milestone. For diagnostic corroboration, inspect the child root’s inline `colorScheme`, frame load count, and only the presence/value of the public theme parameter; do not publish a full signed iframe URL. The existing loaded label records the theme at initialization and does not update on later theme notifications; use the child root or pixels for C5 rather than interpreting that stale label as current theme.

## Minimum capture matrix

D = 1400 × 1022 CSS pixels, normal browser scale, both 320px cards visible. T3 uses the standard palette. Browser preference below means Playwright-emulated `prefers-color-scheme`; there is no independent headless OS-theme axis. These six fixture rows are the minimum: four paired base/head theme takes, plus two temporal regression/boundary takes at head. Each row is **recording** because it depends on an interaction or time. A clip may contain both required mounting actions with a clear label between them.

| ID / named fixture | Data, T3 mode × browser preference | Viewport / minimum capture | Action, expected observation, and how to seed | Diff affects row? |
| --- | --- | --- | --- | --- |
| C1 — forced dark despite light preference, two delayed cards | Two distinct `slowapp.show_chart({})` results; mode Dark; preference light; no app-owned background; default 320px; border absent | D × dark × C1; base + head; **recording**, return + reload in each | Set Dark in Settings → Appearance before mount, emulate light, then use existing thread driver to return and reload. Base should reproduce white pre-init canvas; head should paint a dark canvas from child first paint through initialization. Both cards must load. This is the strongest check that the new query follows T3 rather than the browser preference. | Direct |
| C2 — forced light despite dark preference, two delayed cards | Same data; mode Light; preference dark | D × light × C2; base + head; **recording**, return + reload in each | Set Light before mount, emulate dark; same driver. Head stays light before and after initialize; no stale dark query or new opposite flash. Base is a control, not an assumption of a particular failure color. | Direct |
| C3 — system resolves dark, two delayed cards | Same data; mode System; preference dark | D × dark × C3; base + head; **recording**, return + reload in each | Set System, emulate dark before mount. Head URL carries resolved dark, never literal system; no white pre-init block; loaded dark theme. | Direct |
| C4 — system resolves light, two delayed cards | Same data; mode System; preference light | D × light × C4; base + head; **recording**, return + reload in each | Set System, emulate light before mount. Head URL carries light; loading and loaded canvas remain light. Completes both resolver outcomes and invalid-`system` guard. | Direct |
| C5 — mounted theme updates, close, latest-theme reopen | One delayed card sufficient; start Dark/preference light, allow initialized; then Light → close → Dark → Show app; reverse light reopen may be included in same take | D × dark/light × C5; head; **recording** | Use command palette Change appearance while card remains mounted, prove scheme updates without iframe reload. Current fixture has no close control: a driver may send `ui/notifications/request-teardown` through that actual child’s `parent.postMessage` (record driver-assisted real app state), or use a scratch MCP app with a close button. After closed row appears, switch T3 to Dark, click Show app. Reopened child must receive fresh dark query and remain dark in its new 4s pre-init phase. Same server/item data; do not replace the component to simulate Show app. | Reopen directly changed; settled message updates retained |
| C6 — theme changes after URL adoption but before initialize | Delayed card; preference fixed light; start Light then change to Dark after loading label; repeat Dark → Light in another mount | D × light→dark and dark→light × C6; head; **recording** | Use command palette appearance controls promptly while frame is mounted; if 4s is too short, use a separately labelled scratch fixture with longer delay, without modifying/restarting the lead’s existing server. Frozen query retains the mount-time scheme until initialize response: [host updateHostContext](https://git.cafe/pingdotgg/t3code/blob/6ac3e2bfe3e755bd1ffaeacef744aa56ec8ad1c5/packages/client-runtime/src/mcpApps/host.ts) deliberately sends no context before initialization. At initialize the child must converge to current T3 theme, without document reload. Report any intermediate mismatch as this boundary; do not claim continuous current-theme matching across this interaction. | Direct new URL-scheme persistence boundary |

This is six named recording groups: four base/head pairs (eight takes), plus two head takes. The first four takes each include thread return and page reload; if tooling splits actions into clips, preserve the same matrix coverage. C5/C6 can be combined in one labelled head recording if each milestone remains clear. No still-only substitute is sufficient.

## Additional render-state fixtures and collapses

These rows complete the component inventory; they do not expand the minimum above because the diff does not change their layouts or they are not reachable from the production URL handoff. A capturer may collect them for broader regression coverage. Any row marked **recording** still needs motion evidence if chosen. “Harness” must be visible in the evidence caption when a mocked state or manually constructed iframe substitutes for an integrated T3 path.

| ID / named fixture | Distinct data/state and theme/viewport | Seed route or explicit harness method | Evidence type / minimum status | Diff relationship |
| --- | --- | --- | --- | --- |
| G0 — absent card | No `dynamic_tool` app output, invalid app reference, tool/server mismatch, or no app results; empty/ordinary chat | Ordinary empty thread or a normal tool result; malformed reference needs a labelled projected-row harness. There is no zero-card branch inside McpAppFrame. | Still if chosen; excluded | Component not mounted |
| G1 — asset URL loading | Valid card, no adopted URL, pending URL mint or connection preparation; blank reserved 320px row | Labelled component harness holding asset state Loading. Alternatively intercept/hold only URL RPC in an isolated capture driver; browser-wide offline is not equivalent. | **recording** if observing loading → ready; excluded | URL injection has not run |
| G2 — URL creation failure | Valid card, asset Failure, centered host error; short and 256-character server labels | Labelled component harness for Failure or fail the URL RPC in an isolated driver. Unpairing a real environment is unnecessary. Missing-file permission gate does not apply to this attachment resource. | Still for stable error, **recording** for failure transition; excluded | No adopted URL |
| G3 — adopted URL HTTP error | Mint succeeds but asset GET returns 404/413/500/browser error; iframe present, not host Failure message | Isolated network interception of that child request, clearly labelled driver fault injection. Do not delete attachments or alter the lead server. | **recording**; excluded | Bootstrap cannot run when response is not app HTML |
| G4 — never initializes | Unstyled slow app with no initialize call, one card, dark/light | Separate scratch MCP fixture or labelled bootstrap harness; existing 4s fixture cannot seed this indefinitely. | **recording** through a declared observation window; excluded | Direct bootstrap persists; equivalent pre-init scheme branch to C1–C4 |
| G5 — immediate initialization | Same app calls initialize synchronously without delay; loaded result | Separate scratch MCP fixture, labelled; existing delayed fixture cannot seed it unchanged. | **recording**; excluded | Same URL/bootstrap order, C1–C4 already expose the harder delayed interval |
| G6 — left-page guard | Second iframe load; centered stopped-page text; long server label variant | Separate scratch app navigates itself after init, or driver-trigger child navigation; do not navigate host. | **recording**; excluded | Existing guard; no new valid-document bootstrap |
| G7 — bordered vs borderless inline | One card with resource `_meta.ui.prefersBorder: true`; one false/absent. Dark and light share branch; 320px | Separate scratch resource returns `_meta: { ui: { prefersBorder: true } }`; invoke tool again to snapshot metadata. Existing stored references do not change when server metadata changes. Component harness is an alternative, label it. | **recording** to include loading; excluded | Border classes unchanged; early scheme inherited |
| G8 — fullscreen and all return paths | Initialized card, fullscreen capability, viewport header with short/256-character server name; Escape/button/blur/approval/awaitingUser exits; inline spacer retained | Separate scratch app advertises/request fullscreen from a genuine in-frame user action. Parent-page activation must be older than 5s; existing fixture has no fullscreen button. AwaitingUser transition needs a labelled harness or real pending approval. | **recording**; excluded | No new document on web fullscreen transition; unchanged layout |
| G9 — fullscreen denied / unsupported | Inline after invalid activation, parent recent input, pending confirmation, awaiting user, other fullscreen card, unsupported popover, or app not advertising mode | Denials via driver/scratch app; unsupported popover specifically needs a labelled capability harness or older browser, not Chromium 148. All denial reasons yield same inline layout. | **recording**; excluded | Existing guard, C1 inline visual representative |
| G10 — narrow web viewport | 390 × 844, Dark/preference light, two cards; only one needs to be visible initially, same default height; responsive vendor text | Same real fixture thread at narrow viewport, return then reload. Scroll to second card if testing its remount; recording must include scroll. | **recording**; optional one head smoke; no width cross-product | No width conditional changed; 390px web is not native mobile proof |
| G11 — size boundaries | App height omitted/default 320; request 1 → 80; 2000/oversized → 2000; nonfinite → 320 in host-level harness; inline vs fullscreen ignore | Scratch app posts `ui/notifications/size-changed` for finite values. Nonfinite and fractional boundaries can be host/component harness stimuli. Cannot seed from existing fixture unchanged. | **recording**; excluded | Height code unchanged; one default-height C1 representative suffices |
| G12 — custom palette / vendor-owned surface | Custom dark palette whose T3 background differs from UA dark canvas; custom fonts. Separately explicit vendor white/light background or later scheme override | Select existing custom palette in Settings and use normal fixture. Separate resource for app-owned background/style. | **recording** for loading or theme change; excluded | Query carries only dark/light, not palette variables; explicit vendor paint is not overridden |
| G13 — dynamic detail / inherited / remote | Detail missing/loading; dynamic input only then result; tool definition absent/present; inherited source thread differs from conversation thread; prepared remote HTTP origin | Existing real thread/RPC can supply these, or label projected-row/detail harness. Remote representative uses a previously connected isolated environment and normal app route; do not create new pairing obligations. | **recording** for data arrival/remount; excluded | Same initial scheme handoff, no additional host-frame layout branch |
| G14 — approval modal / refusal | App writable tool/send/download prompts, approve/decline, sender unavailable; linked/embedded/multiple files and long names | Separate interactive scratch app drives those APIs through normal parent host. No such controls in the current fixture. Read-only tool/resource successes and rejected requests without modal collapse to existing app canvas. | **recording**; excluded | Existing shared approval chrome, not touched by diff |
| H1 — missing/invalid initial hint | Sandbox iframe with injected bootstrap; query absent, empty or `normal`, initial root scheme unset; light/dark browser preferences | Labelled isolated bootstrap harness, or driver removes/changes only the theme parameter on a normal asset iframe. Mobile normally supplies absent query; this is not a mobile device capture. | Still for stable state; **recording** if observing initialization; excluded | Explicit fallback branch, validated structurally by changed test; valid production web URLs do not hit it |
| H2 — query parsing and URL preservation | Valid dark/light with other query parameters and fragment, prior theme parameter replaced; location/search parsing throws | Labelled harness for URL mutation and throwing globals. The real app uses an absolute resolved URL; do not manufacture relative asset success in a component harness and call it production. | Nonvisual source/test evidence; excluded | Direct parsing boundary without distinct additional supported UI state |
| H3 — message acceptance/refusal after URL hint | Begin with valid hint, parent initialize can change theme; parent context notification changes it; invalid source/version/method/theme leaves it alone, parser throw still leaves listener registered | Labelled bootstrap harness sends specified MessageEvents/parent messages; accepted paths also exercised by C5/C6. | **recording** for visual theme transitions; excluded | Existing listener behavior retained; existing tests cover reject/accept shapes |
| H4 — HTML prefix forms | Stored CSP + doctype, doctype only with whitespace/BOM/case variation, fragment without doctype; vendor inline/external script behind bootstrap | Labelled helper/HTML harness or separate scratch resources through normal snapshot path. Snapshot path normally adds doctype+CSP, so unmatched prefix is helper-only compatibility. | Nonvisual source/test evidence or **recording** for paint order; excluded | Insertion logic unchanged, added code executes in all forms |
| H5 — native mobile missing hint | Mobile inline and fullscreen nested WebView has no theme query; later initialization theme still accepted | Actual native mobile capture only if separately authorized and devices available. Bootstrap harness H1 covers fallback without claiming native evidence. | **recording** if chosen; excluded | Shared bootstrap safely falls through; no mobile fix claim |

One/two/many cards do not select a different component branch; two is the required representative because the issue reports two. More than two, very long tool names, argument/result lengths, touch/hover, and providers other than Claude do not create an additional changed render path. Their own app content/layout can vary arbitrarily and is not enumerated as a T3 layout matrix. No account-count or card-grid branch exists here.

## Pass-1 conclusion and handoff

The matrix covers every production caller, changed initial-scheme branch, theme resolution outcome, mounting/reopening path, existing visible fallback/layout state, and interaction class of the changed component. It specifies which states need the real app versus a labelled harness. It deliberately does not multiply unchanged props into a Cartesian product.

Pass 2 must inspect the actual recordings and compare the paired base/head takes, including the pre-initialize interval and successful handoff. It should check C6’s documented boundary rather than assume live pre-init synchronization. No claim about eliminating a real vendor app’s explicit white background, perfect custom-palette matching, native mobile behavior, or actual Electron execution follows from Chromium evidence.

Verification tools used: `git` explicit diff/history/status, `rg`, source reads, and read-only `cafe issue view`. Tests and browser captures: not run in this pass.

AI-assisted; verified with the tests and tools listed above.

FRICTION:
brief | The supplied merge-base differs from current local main; explicit supplied base was used. | Pin capture commands to the supplied base/head pair rather than a moving branch name.

VERDICT: SUFFICIENT
