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

[PR #28065](https://git.cafe/pingdotgg/t3code/pull/28065), branch [fix/issue-28052](https://github.com/pingdotgg/t3code/tree/fix%2Fissue-28052), reviewed head [0277fd6](https://github.com/pingdotgg/t3code/commit/0277fd6975bcf7b79b12833bee93ed714844b319), comparison base [85bd3f2](https://github.com/pingdotgg/t3code/commit/85bd3f2a2a0ad4a8ddf2950e01db80765e9a1bf4). Read-only code-derived audit; no browser, mobile device, app server or captures were started. Pass 1 approves matrix completeness, not the implementation or missing visual evidence. Every capture remains PENDING.

## Findings to carry into capture

1. The two changes have different consumers. Pooled account names change on web/desktop Limits bars, compact legends, segment popover badges and reset-credit progress/outcome messages; on native clients they change phone legend initials, sufficiently wide inline names, accessibility names, account details and sorting. The slash popup uses a separate report, not the pooled account name, and must be captured separately.
2. Mobile phone legends render **text initials** after clearing the default name (e.g. `AE`), not an email chip. The chip is a separate branch in wide inline bars. Web single-account wide bars suppress names entirely; a screenshot of only that bar cannot prove this fix.
3. P2 name-preservation edge, confirmed by directly running the head helper: two native instances for `a@example.com`, first named `Claude`, second `Work`, then the matching hub account, yield `displayName: null`; reversing the native order yields `Work`. The first native merge already discarded `Work`; the new hub branch then clears `Claude`. The broad “chosen names kept” claim needs this qualification or a fix. Fixture C02 is a known-failure row, not omitted from the matrix.
4. Stripping suffixes can create identical visible popup labels: IDs `same` and `same.json` both become `CLIProxyAPI · same`. This is code-confirmed for accepted snapshot data; authentic hub filename constraints may make the collision uncommon. `.json` alone leaves only the source label and separator. Fixture B07 records these boundary outcomes without asserting an unproven production frequency.
5. A base URL and a hub stub alone do not create native usage or a native email. Claude's status reader gets these from SDK initialize/get_usage, then attaches a custom endpoint auth label. However an existing repository fake-Claude binary implements exactly that local control protocol. Use that binary boundary plus a stub hub to feed the real app. No first-party OAuth is needed for the main merged-account fixture. This path is code-supported, not live-verified by this pass.

Diagnostic artifacts: [direct helper results](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e6a73ea2e9098586/derived-results.json), [reproduction script](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/e3875df125b038fc/derive-fixtures.mjs). These are synthetic helper runs, not visual evidence.

## Complete consumer graph and changed render slots

| Helper / direct caller | Downstream render path | Effect of this diff |
| --- | --- | --- |
| `collectLimitAccounts` → web `UsageLimitsPooled` (via `UsageLimits.UsageLimitsSection` → `UsagePage`) | `/usage`, Limits metric. Sidebar/settings navigation, command palette `usage.open`, configured `usage.open` and `usage.limits` keybindings converge here. Desktop uses the same web renderer. | `AccountName` in wide multi-account segments, compact `LegendRow`, redeem progress/outcome; `AccountAvatar` in segment popover changes `showBadge` when name becomes null; aria labels and equal-reset sort order change. |
| `collectLimitAccounts` → native `UsageLimitsPooled.UsageLimitsSection` (via `UsageRouteScreen`) | `SettingsUsage`, deep link `settings/usage?tab=limits`; workspace route also uses `UsageRouteScreen(workspace)`; widget tap links to SettingsUsage. | Phone/narrow legend `accountName` becomes email initials. Wide bar always shows chip if email; custom name is additionally shown only at segment width >=220. Equal-reset sort order changes. |
| `collectLimitAccounts` → native `UsageLimitAccountScreen` | `SettingsUsageAccount`, only reached by tapping a bar/legend; receives account key, window ID/kind, selected environment IDs, frozen `now`. | Default header falls back to driver name (visually the same for exact `Claude`, case-normalized for `cLaUdE`). Email reveal, source vs Signed in, missing account/window and credit sections remain. |
| `collectLimitAccounts` → `buildSubscriptionUsageSnapshot` → `SubscriptionUsageCoordinator` → platform publisher → iOS `SubscriptionUsage.tsx` / Android `SubscriptionUsage.android.tsx` | Native home screen widgets; iOS timeline and Android snapshot. | No account name/email is rendered or serialized. Aggregates and windows are unchanged; names only affect account ordering, which these widgets do not render. Excluded from visual minimum; code inspection is sufficient for this diff. |
| `collectProviderUsageLimits` → web `useUsageLimitsPanel` → `usageLimitsBannerItem` → `UsageLimitsBannerBody` | ChatView composer. Selecting `/usage-limits` command opens locally; typing exact `/usage-limits` and sending also opens locally, with no attachments/nonprompt content. | Hub label strips terminal `.json` case-insensitively. One account: label in banner description; >1: count in description, labels in body. Labels containing `@` use `RedactedSensitiveText`; other labels are plain. |
| `collectProviderUsageLimits` → web `ThreadDetailsUsage` (via `ThreadDetailsPanel`) | Chat details Usage limits section. | Reads only `entry.limits` for selected native instance (or raw instance limits), never report label. No visible delta; excluded from minimum. |
| `collectProviderUsageLimits` → native `ThreadComposer.openUsageLimits` → `ThreadDetailScreen` state; same helper recomputes report in `ThreadDetailScreen` → native `ComposerUsageLimits` → `AccountLimits` | Native composer command selection or typed exact `/usage-limits` send, with no attachments. | Hub label becomes `instanceLabel` beside driver heading. `@` labels use a separate tap-to-reveal component with fixed bullet placeholder; no-`@` labels are one-line truncated text. Close button sits in first account row; zero rows gets separate close header. |

No changed helper is called directly by server RPC, an MCP tool, or scheduled tasks. The server delivers provider/source snapshots; clients compute these presentations. `packages/client-runtime` carries the environment presentations and settings/RPC plumbing, but has no additional direct call to either changed helper. Other providers, recovery banners and ChatGPT notices call different helpers and are unaffected.

Source anchors: [shared merge and hub label](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/packages/shared/src/usageLimits.ts#L257), [shared report](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/packages/shared/src/usageLimits.ts#L753), [web pooled renderer](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/web/src/components/usage/UsageLimitsPooled.tsx), [web popup](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/web/src/components/chat/ComposerUsageLimits.tsx), [native pooled/details renderer](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/mobile/src/features/usage/UsageLimitsPooled.tsx), [native popup](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/mobile/src/features/threads/ComposerUsageLimits.tsx), [native account row](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/mobile/src/features/usage/UsageLimitsSection.tsx), [widget projection](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts).

## Fixture data and seed recipes

All names, email addresses, keys and traffic must be synthetic. Each new driver run gets a timestamped directory under the lead scratch root. Do not read real credentials or use the live T3 database for this task. Keep the app on its isolated dev home, and let Vite proxy the single origin. Capturer owns device/browser authorization and cleanup.

Data convention: `T` is captured once at seed time, `checkedAt=T-5m`. A normal session is `{id:'five_hour',kind:'session',label:'Session',usedPercent:40,resetsAt:T+2h,windowDurationMins:300}`; weekly is `{id:'seven_day',kind:'weekly',label:'Weekly',usedPercent:25,resetsAt:T+4d,windowDurationMins:10080}`. Every matching hub/native pair has exactly the same driver and normalized email. Use different first letters of local part/domain to make chips and initials distinguishable (`a@example.com` → AE; `beta@work.test` → BW). Freeze `now` in a labelled harness; in the real app use future timestamps relative to T and explicit refresh/opening. Never silently substitute stale 2026 fixture dates.

**MIX**: one ready/enabled/installed native Claude instance default name `Claude`, email `a@example.com`; another instance named `Work`, email `beta@work.test`, custom accent; hub has those two matching emails and hub-only `c@example.net`, plus a hub-only no-email `team-seat.json` if needed. Each has normal session and weekly limits. Pooled output has one row per login, not duplicate native/hub bars. Default instance drops name, Work stays. Popup output deduplicates usable native logins and therefore shows native labels for the first two and hub labels for the remaining logins. A mixed popup alone cannot prove the merged-default chip; capture Limits separately.

**R-HUB — real app, stubbed CLIProxyAPI HTTP boundary**:

1. Configure in the isolated server settings (UI add-source dialog or authenticated `server.updateSettings` RPC): `usageLimitSources.audit={kind:'cliproxy',label:'CLIProxyAPI',url:'http://127.0.0.1:<stub-port>',managementKey:'fixture-only',enabled:true}`. Change label/ID count per row. Settings live in the dev server's own home, never `~/.t3/userdata`. RPC expects `{patch:{usageLimitSources:{audit:config}}}`; secrets may be stored separately by settings service, so prefer UI/RPC over guessing stored secret encoding.
2. Stub GET `/v0/management/auth-files`: `{files:[{id:'claude-a@example.com.json',auth_index:'a',provider:'claude',email:'a@example.com'},...]}`. Disabled=true is filtered. Only provider `claude` and `codex` are accepted by this adapter. Omit `email` for team-seat; use `id_token.chatgpt_account_id` for distinct Codex workspaces.
3. Stub POST `/v0/management/api-call`, dispatching on `auth_index` and request body's `url`. Return envelope `{status_code:200,body:JSON.stringify({...})}`. Claude body: `{five_hour:{utilization:40,resets_at:iso(T+2h)},seven_day:{utilization:25,resets_at:iso(T+4d)}}`; set utilization=100 for exhausted; use null/missing windows for empty. The adapter always supplies Claude plan `Claude Subscription`.
4. Codex `/backend-api/wham/usage` body: `{plan_type:'plus',rate_limit:{primary_window:{used_percent:40,reset_at:epochSeconds(T+2h),limit_window_seconds:18000},secondary_window:{used_percent:25,reset_at:epochSeconds(T+4d),limit_window_seconds:604800}}}`. For `/rate-limit-reset-credits`, return `{credits:[{id:'fixture-credit',status:'available',reset_type:'codex_rate_limits',expires_at:iso(T+1d)}]}` or `credits:[]`. For `/consume`, return `{code:'reset'}` / other supported outcome; POST `/v0/management/reset-quota` succeeds. After reset, change returned utilization and credits for next refresh. Errors are injected by HTTP failure or envelope status_code>=400; top-level auth-files failure produces source.error, per-account failures produce unavailable/probeFailed with the account label retained.
5. Refresh via the app's Limits refresh/pull-to-refresh, or authenticated `server.refreshProviders` with `{fresh:true}` and **no instanceId** (instance-only refresh does not refresh the hub). Wait for delivered `usageLimitSourcesUpdated`/provider snapshots or the completed RPC, not a fixed sleep. Change stub responses to drive later states. This is real-app evidence with stub data; label it that way.

**R-NATIVE — real app, fake Claude binary boundary**:

Copy [existing fake-Claude protocol fixture](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/server/src/provider/testing/ProviderInstanceRegistryLive.fixture.mjs) to the driver's timestamped scratch run, make it executable, and adapt only the scratch copy so initialize responds with the desired synthetic email and get_usage with the windows above. It handles `--version`, `control_request.initialize`, `control_request.get_usage`, and exits on stdin close. Do not use it for a real turn. The unmodified fixture uses `test@example.com` and a 100%-used session with no reset; pair the hub to that email if no adaptation is needed.

Create/upsert the provider through authenticated `server.updateSettings` with `providerInstanceMutation:{operation:'upsert',instanceId:'claude-a',instance:{driver:'claudeAgent',enabled:true,displayName:'Claude',environment:[{name:'ANTHROPIC_BASE_URL',value:'http://127.0.0.1:<stub-port>',sensitive:false}],config:{binaryPath:'<absolute fake binary>',homePath:'<isolated scratch Claude home>'}}},patch:{}`. Use an isolated home so organization metadata/Claude settings do not accidentally change workspace identity or endpoint selection. Inherit no real auth files. Set a second fake copy to beta@work.test and displayName Work. Use separate IDs for duplicate-instance cases. `server.refreshProviders` gets their status/email/usage through the real driver. The code derives all these from SDK control responses; a base URL by itself cannot synthesize them. [Claude status path](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/server/src/provider/ClaudeProvider.ts#L600), [live registry fixture use](https://github.com/pingdotgg/t3code/blob/0277fd6975bcf7b79b12833bee93ed714844b319/apps/server/src/provider/ProviderInstanceRegistry.test.ts#L417).

For multi-environment rows connect a second isolated server, or use H below if the capturer cannot safely run a second environment. Do not pretend one source with two labels is two environments.

**H — labelled component harness**: render the existing shipped component(s), importing the real shared helper and supplying synthetic `LimitPresentations`, `UsageLimitsReport` or test atom registry/navigation. Use the normal CSS/theme and native renderer. No app source changes belong in the PR. Label every capture “component harness — synthetic snapshots”; include fixture data and viewport. This is required for a *single hub-only report*: a healthy selected Claude/Codex instance normally contributes a native limits or notice row. To get exactly one hub row deterministically, set the selected provider's usageLimits undefined in harness data and retain one hub source account. Similarly use H for a null report, arbitrary hub externalUsage, unknown drivers, missing/no clock combinations the HTTP adapter cannot produce, and Codex merged reset-status data if the existing fake Codex fixture is not extended. No public RPC writes arbitrary ServerProvider snapshots.

## Capture conventions / minimum set

All REQUIRED rows are before/after at the pinned base/head with identical data and geometry, clearly labelled. A recording includes the relevant resting state and end state; do not replace it with stills. For email-containing popup labels it must include **reveal**, because masked text alone cannot prove suffix removal. Preserve synthetic addresses in evidence so no privacy issue prevents inspection.

Viewport keys (CSS px for web, logical points/dp for native): W=1440×1000; K=900×1000 (web pool width below 672 despite md two-column card); P=390×844 (web phone); I=390×844 iOS; A=412×915 Android; T=800×1100 native tablet; L=1024×1366 native large tablet. D=dark, Lgt=light. Web pool @2xl cutoff is **42rem = 672px of pool container**, not viewport; log the measured pool width and ensure W/K/P land on the intended side. Outer card changes at md=768px. Native wide starts at measured route width >=600; further thresholds are measured `barWidth/accounts.length` >=96 inline labels, >=150 reset, >=220 chosen name. Record those widths in capture metadata rather than assuming a device name selects the branch.

Web and Electron share components/styles. A full duplicate desktop matrix is unnecessary; the REQUIRED Electron A01 and B02 captures verify shell geometry. All native REQUIRED rows run on both iOS and Android unless a cell explicitly chooses a representative; no web rendering may be called a native capture. This pass has not provisioned a device; a missing device is capture work, not an excuse to replace native evidence with unlabelled HTML.

Every row's status is **READY** (fixture defined; capture pending), **KNOWN FAILURE** (defined and code-confirmed contradiction), or **EXCLUDED** (no changed rendered value; no screenshot needed). “Regression” entries are optional additions to the minimum, retained so coverage is explicit rather than silently forgotten. Navigation to a static routed page does not itself make a still fixture an interaction test; rows whose claim depends on hover, tap, reveal, filtering, refresh, scroll, lifecycle or elapsed time are marked recording.

| ID / named fixture | Data / visually distinct branch | Affected by diff? | Minimum viewport × theme; medium | Seed and route | Status |
| --- | --- | --- | --- | --- | --- |
| A01 pooled default + chosen + hub-only, inline | MIX first 3 logins; session+weekly, one custom accent. Wide multi-account `AccountName`: default becomes chip; Work stays text; hub-only remains chip. Also checks sort order when resets tie. | Yes; core | **REQUIRED** W×{Lgt,D}, still pair; Electron W×D still pair | R-HUB + R-NATIVE; `/usage` → Limits | READY |
| A02 pooled compact legend, desktop card | Same MIX; pool <672, viewport >=768: numbered bars, chip/name legend; two-column card. | Yes | **REQUIRED** K×D, still pair | Same seed; measured pool width <672 | READY |
| A03 pooled phone web, stacked card | Same MIX; viewport<768, compact legends. | Yes | **REQUIRED** P×Lgt, still pair | Same seed; `/usage` Limits | READY |
| A04 one merged login | Default native+matching hub only. Wide bar omits account name; compact legend includes chip; percentages identical. | Yes in compact/accessibility/popover; no wide bar text delta | **REQUIRED** P×D still pair; W×D regression still | R-HUB + R-NATIVE with one login | READY |
| A05 stress pooled, 5 accounts / long chosen name / exhausted / sparse | MIX plus very long Work name (80 chars), long hub ID without email, weekly exhausted=100, another account missing weekly; equal reset tie to show changed sorting. One account no reset clock. | Yes label width/sort; bars pre-existing | Regression W×D and P×D still pairs; may fold long-name/exhausted into A01/A03 | R for long labels/exhaustion/sparse; H for arbitrary no-clock native/hub combinations | READY |
| A06 segment details and privacy | A01: hover/focus/click default, Work, hub-only email with no redeem, and Codex hub-only with redeem. Open shared popover, reveal/hide email, move to another trigger, dismiss. Shows removed default instance badge, retained custom badge, Signed in vs Via, optional plan/clock/pace/refill/credits. | Yes default badge and accessibility names; title exact Claude unchanged | **REQUIRED** W×Lgt and P×D, **recording** | R-HUB+R-NATIVE; bar or legend opens popover; Codex hub credit via R-HUB | READY |
| A07 reset credit progress/outcome name | Codex default-named native merged to email hub, one banked credit; chosen-name control. Confirm/cancel, busy, success or error status outside closed popover; `AccountName` there also switches to chip. Disabled permission branch is a control. | Yes name slot in progress/outcome | **REQUIRED** W×D **recording**; read-only permission/cancel/error regression in same clip | H with real `UsageLimitsPooled` and controlled RPC command service; optionally real app after extending fake Codex account/rate-limit protocol. R-HUB supplies consume/reset-quota response | READY — labelled H |
| A08 filter / refresh / live reorder | A01 with two environments and equal reset times: deselect matching hub environment (native default name returns), restore; change stub and refresh; retain values on failure; remove active account while popover open. | Yes name predicate/sort can change across updates; general load/error behavior pre-existing | Regression W×D **recording**; distinct same-login order is REQUIRED C02 | Two isolated real servers + filters, or H for live atom updates | READY |
| A09 native phone pooled | MIX: default becomes `AE` text in legend, Work stays text, hub-only `CE`; bars numbered, names not inline. | Yes; distinct from web chip | **REQUIRED** I×{Lgt,D}, A×{Lgt,D}, still pairs | R-HUB+R-NATIVE; Settings → Usage → Limits, or settings/usage?tab=limits | READY |
| A10 native wide, 96<=segment<150 | 4 accounts at T, confirm actual segment widths. Inline chips+percent; reset hidden; custom text hidden when email. | Default name change largely aria/sort here; wide UI branch must be identified | Regression T×D on one native platform, still pair | Same R seed, four logins | READY |
| A11 native wide, 150<=segment<220 | 3 accounts at T, widths checked. Inline chip+percent+reset; custom name text still hidden when email. | Mostly aria/sort; reset/name width boundary | Regression T×Lgt on one native platform, still pair | Same R seed, three logins | READY |
| A12 native wide, segment>=220 | 2 accounts at L: merged default and very long chosen Work label. Chip remains, removed default text disappears, chosen name stays+truncates; one vs many data. | Yes; actual native inline name slot | **REQUIRED** L×D on iOS and Android, still pairs | R-HUB+R-NATIVE; adjust count/width until segment>=220 | READY |
| A13 native wide but crowded, segment<96 | 6 accounts at T: falls back to numbered bars+legend even though route wide. | Yes initials/name in legend | Regression T×D on one native platform, still pair | R-HUB+R-NATIVE, six logins | READY |
| A14 native account details | Tap A09/A12 default, mixed-case default `cLaUdE`, Work, hub-only. Reveal/hide email, back; inspect signed-in environments/source, plan present/absent, credits present/absent. Remove account while detail open → missing account/window message. | Default header normalization; name/sort determines correct target; other sections pre-existing | **REQUIRED** I×D and A×D **recording**; missing-account branch may be regression tail | Real app R, press bar/legend → SettingsUsageAccount (no deep-link route); H only for exact removal if needed | READY |
| A15 native filter / refresh / account disappears | Toggle environments, pull-to-refresh, stub failure/recovery, account removed. Limits now advances only on refresh; account screen uses tap snapshot. | Name can reappear/disappear on merge; time/error behavior pre-existing | Regression I×D or A×D **recording** | R-HUB+R-NATIVE; real second environment or labelled H | READY |
| B01 multiple popup rows, plain labels | One native login + unmatched hub no-email `team-seat.json`; short source label; plan present. Header is count on web; row labels in body; native driver+instance label, first/non-first borders and close. | Yes .json removed from hub label; native row unchanged | **REQUIRED** W×Lgt, I×Lgt, A×Lgt, **recording**; includes command-selection open, close, typed command open | Real app R; selected ready Claude; unmatched hub account, no attachments | READY |
| B02 email + long popup + scrolling | Native login + 7 unmatched hub rows including `claude-b@example.com.json`, 100-char source/account label, no-email plain row. Reveal/hide `@` label, scroll first→last, close. Show desktop “Details” affordance if summary actually overflows. Native constrained maxHeight, keyboard visible/hidden. | Yes suffix in revealed/redacted labels and clipping length | **REQUIRED** W×D, P×D, I×D, A×D **recording**; Electron W×D same clip | R-HUB+R-NATIVE; open composer /usage-limits; keep synthetic addresses | READY |
| B03 single hub popup, plain name | Exactly one hub no-email row `team-seat.json`, selected provider with no usageLimits. Web single summary bears label, body omits repeated label; native single first row/close. | Yes; distinct one-account path | **REQUIRED** P×Lgt, I×Lgt, A×Lgt **recording** | **H** report from real collectProviderUsageLimits with selected provider usageLimits undefined; healthy real ready native normally contributes own row | READY — labelled H |
| B04 single hub popup, email | Exactly one unmatched email hub `claude-b@example.com.json`; plan absent then present. Web description RedactedSensitiveText; native fixed bullet mask then reveal. | Yes suffix in header/label, unlike multi body | **REQUIRED** W×D, I×D, A×D **recording** reveal/hide/close | Same H as B03; do not call a two-account popup single | READY — labelled H |
| B05 popup usable + failed/empty/native notices | Native unsupported/probeFailed row + unmatched hub: one normal, one per-account failure, one zero-window. Separate source error with zero accounts then recover. Header count/zero accounts; notice replaces windows, labels retained. | Yes labels still change on unavailable/empty hub rows; no delta on zero-account notices | **REQUIRED** W×D, I×D, A×D **recording** for one labelled failed hub row; other empty/source-only permutations regression | R-HUB for per-account failure/null windows/auth-files failure; H for zero accounts report and deterministic unsupported/externalUsage combinations | READY |
| B06 same email, distinct workspaces | Two Codex hub auth-file IDs `codex-team-a.json`, `codex-team-b.JSON`, same email, workspace IDs wa/wb, selected unrelated native account. Reveal each; ensure distinct suffix-free IDs stay in labels; no email-based collapse. | Yes suffix removal while maintaining workspace identity | **REQUIRED** W×D, I×D, A×D **recording**; can fold into B02 | R-HUB auth-files id_token.chatgpt_account_id; selected fake Codex instance or H if no selectable Codex on device | READY |
| B07 suffix edges + collision | IDs `UPPER.JSON`, `no-suffix`, `inner.json.backup`, `same`, `same.json`, `.json`; source+ID still used even when email differs. Expected only terminal case-insensitive suffix stripped; same pair becomes indistinguishable, `.json` loses account portion. | Yes edges; collision is a known limitation | **REQUIRED data transcript**; one W×D **recording** of plain rows can fold into B01/B02; native repetition regression | R-HUB stub can serve these schema-accepted IDs; label synthetic boundary fixture, do not assert genuine hub emits all of them | READY; collision known |
| B08 popup lifecycle/live updates | Open via selection, dismiss, reopen via typed exact command; source label/id changes and refresh while open; reconnect; switch selected provider/thread (panel invalidates); send real turn only with a separate fake turn-capable driver. | Live label content changes; lifecycle unaffected | REQUIRED command/reveal/dismiss already B01/B02; extra live update/reconnect is regression W×D and I/A×D **recording** | R-HUB+R-NATIVE for safe source mutation; H for spend/resume to avoid sending a real provider prompt | READY |
| C01 no-hub / chosen-name / no-email controls | Default native only stays Claude; named merged Work stays; case-insensitive cLaUdE drops on emailed hub merge; native without email does not merge with emailed hub unless same credentialFingerprint; hub-only email remains chip, hub-only no-email name strips suffix as before. | Guard controls; case branch affected | **REQUIRED transcript** all variants; include default/native-only and Work in A01/A06/A09, mixed-case in A14; no standalone duplicate screenshots | R-HUB+R-NATIVE except arbitrary shared fingerprint → H; native-only by removing/disabling source | READY |
| C02 same login, default before chosen | Native Claude then Work share a@example.com plus hub a@example.com; reverse order as control. Both pooled to one login; first order loses Work on head. Multiple environments can also trigger this merge. | Yes; broad preservation claim contradicted | **REQUIRED** W×D and I×D **recording** order/source toggle; A native platform repetition regression; transcript both orders | R-NATIVE copies with same email; R-HUB matching account; H if preserving provider iteration order is unreliable | KNOWN FAILURE, capture pending |
| C03 empty/loading/error page and filtered zero | No connection → Connect environment; empty selected set → Select environment; connected with no usable limits → no-provider message; disconnected/null serverConfig; refresh busy/failure retained values; native warning with failedLabels. | No changed account object reaches UI in fully empty/loading paths | Regression W×D, I/A×D **recording** when refresh/filter driven; no minimum still | R disconnect/filter/stub; H for exact transient config=null | READY, outside minimum |
| C04 clock/pace/refill windows | Null reset vs future reset, session vs weekly, 0/partial/100 utilization; ahead/on/under pace; <=1m resets and expired reset; weekly exhaustion suppresses session; missing window leaves gap. | Name branch independent; order may change when clocks tie/reset | Partial/full/sparse included A05; clock transition regression **recording**, W×D or native phone×D | R responses; H with controlled clock for exact boundaries. Do not assert ticking: page now is explicit refresh/open/tap snapshot | READY, outside minimum |
| C05 external usage / Cursor prompt / permissions | Native externalUsage cards; Cursor enable prompt placement; read-only reset action disabled; optional plan/accent. | No changed name in external links or prompt. Permission changes ability, not name selection. | Plan/accent included A01/A06; reset disabled in A07 regression; no external-card/prompt capture required | Real settings where available; H for arbitrary externalUsage in report | EXCLUDED from minimum |
| C06 web details and native widgets | ThreadDetailsUsage selects limits only. Widget snapshot serializes driver-level aggregates/windows, never displayName. iOS timeline expiry/Android widget size/theme families exist but consume unchanged values. | No | No capture required | Source trace; optional helper snapshot comparison only | EXCLUDED |
| C07 remaining drivers / unknown driver | PROVIDER_DISPLAY_NAMES has Claude, Codex, Cursor, Grok, Antigravity, fx, Muse Code, ACP Registry, Pi, OpenCode; undefined driver default leaves chosen name. Real hub adapter emits only Claude/Codex. | Predicate generic; real affected hub surface only Claude/Codex | Claude core + Codex B06/A07 sufficient; generic mapping table/data control, no per-driver capture | H only for fabricated non-Claude/Codex hub account; do not claim supported real ingestion | EXCLUDED from visual minimum |

## Branch collapse and interaction inventory

- **Names and identities:** default case variants, chosen/long chosen, no name, email/no email, hub present/absent, native/hub-only/merged, one vs multiple instances/environments, same email/different workspace, repeated merge order are covered by A01/A04/A05, B06 and C01/C02. Sort depends on reset then displayName/email/key; equal reset inputs are deliberate.
- **Layout/data shape:** web count=1 vs >1 controls inline names; @2xl switches inline/legend; md switches summary/bar columns. Native wide >=600 then 96/150/220 per-segment thresholds; zero/missing window yields a gap. Popup count=0/1/>1, first vs later row, @ mask vs plain, source/ID length, optional plan/accent, notice vs windows, one/multiple windows, credit footer, external link, maxHeight/overflow/keyboard are covered or explicitly excluded above. No pagination exists in these renderers.
- **Settings page props:** selectedEnvironmentIds null/all/empty/subset; web hiddenProviders includes both native/source filtering; mobile failedLabels/cursorPrompt/wide; web cursorPrompt and notices/external links. Page-level spinner/errors do not call a new name branch. Keep cached values where the current UI does; do not synthesize a loading skeleton that does not exist.
- **Web interactions:** entry routes/palette/configured shortcuts converge on same page; metric Limits switch, environment/provider filters, refresh; segment hover, focus/keyboard, click, moving shared popover and removal of its trigger, email reveal/hide; credit confirm/cancel/busy/outcome/disabled; popup command selection vs exact send with no attachments, scrolling and overflow details, dismiss/reopen and model/thread/run change invalidation. Covered in recording rows; only changed name-sensitive interactions are in minimum.
- **Native interactions:** Settings/workspace/deep link and widget tap converge on UsageRouteScreen; environment menu and pull refresh; segment/legend tap pushes account detail, reveal/hide and back; credit flow; exact command/command selection, no attachments, close, scrolling with keyboard changes and maxHeight, provider/thread/run/user-input invalidation. iOS and Android use the same React Native feature code, but each requires core native evidence for text/layout differences.
- **Time:** no continuously ticking page claim. Pooled page `now` advances on explicit refresh, composer report uses opening time even as snapshots update, and native detail receives the tap-time `now`. Native widgets have an independent expiry timeline but serialize none of the changed names. Any clock-boundary proof must be a recording; frozen-time static fixtures are stills.
- **Transport:** no wire/schema change; local, relay and tunnel deliver the same serverConfig shapes. A separate remote/tunnel capture set is unnecessary for these pure shared transformations. Capturing against an isolated remote server is valid real-app evidence.

## Pass-1 handoff / status

Fixture definitions and all paths are covered. The minimum set is the REQUIRED cells above, with shared long-name/workspace/boundary clips explicitly allowed to satisfy several IDs; do not take a Cartesian product of every unrelated error/clock/permission branch. Each required recording must show its branch and include the resting frame needed to compare base/head. C02 is a code-confirmed behavior failure to show or fix, not a missing fixture. B07 carries a synthetic collision limitation for the lead to adjudicate. No captures are attached yet; a later proof pass must inspect the actual uploaded artifacts against these IDs.

Read-only footprint: only `/tmp/lead-28065-r1/audit1` created by this audit; no worktree edits, comments, pushes, fetch/ref changes, servers, browsers, devices or leases. Matrix plus diagnostic artifacts uploaded before audit scratch cleanup; the lead's worktree and scratch root are preserved.

FRICTION: none

VERDICT: SUFFICIENT
