# Android medium-sidebar proof audit — pass 1

Findings: visual proof is missing. No confirmed implementation defect emerged from this code-derived fixture audit. The highest-risk proof is stateful: mount the floating style in a thread, then return to a docked list without reloading. A cold list screenshot cannot establish that Reanimated cleared its previous `left` value. The thread's closed and open-sidebar states must also show that the page reserves the rail's 72dp.

Scope: [#33021](https://git.cafe/pingdotgg/t3code/pull/33021), GitHub mirror [#33021](https://github.com/pingdotgg/t3code/pull/33021), issue [#33020](https://github.com/pingdotgg/t3code/issues/33020). Read-only inspection in the supplied review worktree, whose local merge combines pushed head [e3bea01241](https://git.cafe/pingdotgg/t3code/commit/e3bea01241bc3937fe1f6f5843a5c0e26797ba4a) with baseline [bcee0f5d28](https://git.cafe/pingdotgg/t3code/commit/bcee0f5d2872ba193c66ed9ba979bbe424f89ff9). `git diff origin/main` contains only AdaptiveWorkspaceLayout.tsx, 10 insertions and 13 deletions. This document plans captures; it contains no claimed runtime verification. Contributor screenshots and green CI are not maintainer capture evidence.

## Render paths and inputs

- Sole production caller: [RootStackLayout in Stack.tsx](https://git.cafe/pingdotgg/t3code/blob/bcee0f5d2872ba193c66ed9ba979bbe424f89ff9/apps/mobile/src/Stack.tsx). It passes `children`, the effective workspace `pathname`, `workspaceRouteKey`, and `workspaceRouteName`. Modal/overlay routes are removed when deriving that workspace location. A modal therefore does not itself change floating/docked geometry.
- [AdaptiveWorkspaceLayout](https://git.cafe/pingdotgg/t3code/blob/e3bea01241bc3937fe1f6f5843a5c0e26797ba4a/apps/mobile/src/features/layout/AdaptiveWorkspaceLayout.tsx) returns null while preferences load; failed preferences render Content with default grouping; successful preferences pass resolved grouping and saved list filters. Grouping/filter/loading/error/empty-list data changes contents, not the rail/sidebar geometry. Two synthetic stopped threads with visibly different titles cover the container; production thread contents, real PRs, and provider requests are unnecessary.
- Content receives appearance navigation style, viewport width/height, native metrics/safe areas, native-workspace and presentation-host contexts, workspace selected tab, Android settings state, pane preferences, registered inspector and focused route, and hinge geometry. Android uses `useWindowDimensions` because its NativeLayoutObserver is the no-op implementation; its separate AndroidHingeObserver can still align panes to a hinge. Do not inject iOS native metrics into an Android fixture.
- Native-workspace Content returns providers, children, and the inspector sheet before `renderColumns`. Android uses the presentation-host branch: [WorkspaceStackView](https://git.cafe/pingdotgg/t3code/blob/bcee0f5d2872ba193c66ed9ba979bbe424f89ff9/apps/mobile/src/features/layout/createWorkspaceStackNavigator.android.tsx) invokes the supplied renderer with the workspace NativeStackView and Home descriptor rendered as sidebar. No renderer or no visible workspace route falls back to NativeStackView. The other Content branch calls `renderColumns(children)` directly; the default sidebar is WorkspaceThreadSidebar when no sidebar node is supplied, including a null Home descriptor. Older/non-native iOS uses the direct branch. There is no web or desktop caller.
- In [deriveLayout](https://git.cafe/pingdotgg/t3code/blob/bcee0f5d2872ba193c66ed9ba979bbe424f89ff9/apps/mobile/src/lib/layout.ts), ordinary Android is compact at width <600 OR height <600, medium at 600<=width<720 with height>=600, and expanded at width>=720 with height>=600. The upper medium bound is 719, not 720. List width is `clamp(round(width*0.32),280,380)` before hinge/pill adjustment; every ordinary medium fixture has a 280dp list.
- Android `nativeWorkspace=false`. Root paths `/` and `/settings` pin the primary sidebar; `/settings` is nevertheless covered by workspacePage. `workspacePage` is true for Android Settings or selected Usage. Only non-root, non-workspace-page medium screens float the sidebar. Thus Home/list: docked/pinned; Thread, draft Thread, PR detail or nested thread page: floating; Usage/Settings: hidden primary sidebar retained under full workspace content. Expanded never floats. Compact mounts no primary sidebar or rail.
- Material navigation shows a 72dp rail for split layouts. One UI uses floating bottom navigation and rail width zero. The rail spacer now stays in row flow in float mode. The actual rail remains an absolute overlay; when its native expansion reports `railOverlay`, its clipping wrapper covers the workspace, and in float mode its z-index is above sidebar and scrim. Capture expansion/collapse in the core flow.
- Sidebar visibility: medium starts visible on list paths, closes on detail selection/path change, and may be revealed/toggled on a detail. `openWorkspaceTab` can preserve visibility over the remembered detail using `keepOpenFor`; the native Android rail instead resets to Home and calls reveal. These are different navigation paths. Expanded uses persistent preferred visibility; inspector suppression can hide it. Workspace pages force hidden state regardless of the preference. The hidden sidebar is inaccessible/noninteractive. The floating scrim is pointer-active only while open; content accessibility is hidden while floating sidebar is open.
- Changed style always attaches and writes `left=renderedRailWidth` while floating, `left=0` otherwise. Nonfloating list width/translation belongs to the Android strip; floating sidebar is absolute with elevation, and workspacePage sidebar is absolute behind its content. Rendered width, opacity, translation, content anchoring, and scrim animate with 260ms timing (system reduced-motion setting can remove timing). Record with ordinary animation settings; no screenshot can prove the stale-style reset or the transition.
- Inspector: none/inactive/closed all reserve zero width. Active preferred-open becomes a column only when split and post-rail width>=820; otherwise it is an explicitly opened sheet tied to the focused route. Therefore a medium Android inspector column is unreachable. Material column threshold is raw width 892dp; One UI threshold is 820dp. A wide column may suppress a docked sidebar if main content would be <560dp. At raw width 1000 with Material the sidebar suppresses; at 1400 it can remain visible. Inspector width changes and suppression/reveal are regression controls, not newly changed medium behavior.
- Hinge: alignment changes list width/gap only when `hinge.x-railWidth>=280` AND `viewportWidth-hinge.x-hinge.width>=320`; otherwise ignored. A centered crease at x=346 on a 692dp window fails the first check with Material (274<280), but can align without a rail. For 600dp ordinary height>=600 both hinge minimums cannot fit with a rail. Use actual measured hinge bounds; do not infer alignment from a foldable model name.
- Floating-pill sizing may increase list width if no hinge gap, pill row>list width, and viewport minus pill row>=320. At default font 16, FAB 56 and all three tabs plus Settings, row=340dp. At width692 it increases the list to340; at width600 it does not (600-340<320). At larger text the condition can fall back again. Theme only changes colors; no theme conditional changes this geometry. Enabled tab count changes pill width, while selected Usage changes workspacePage. Current navigation customization is unavailable with three registered tabs; no need to invent unsupported hidden-tab UI fixtures.

## Synthetic seed and capture recipe

Use a newly empty task-owned T3 home (`T3CODE_DEV_SEED=0 vp run dev`, confirm the actual ports and isolated home in output), not the live database. If the selected home already contains state, the flag does not clear it: select a fresh isolated home. Keep reusable pairing credentials and startup URLs out of captures and uploads. Prepare the matching dev client with `vp run mobile:install android --prepare` before leasing. Connect a named agent-device proxy session, lease one named Android device, install with `vp run mobile:install android --session <session>`, start this checkout's Metro, and open the printed dev-client URL with developer chrome disabled. Pair via the test-t3-mobile helper using a backend and Metro origin reachable from the pooled device over the tailnet. Do not assume the emulator's 10.0.2.2 reaches this machine.

Create a synthetic git folder inside this task's scratch, containing README.md and a narrow and a long line of harmless text for the file inspector. Add that folder through Add project -> Local folder. Name the project `Sidebar Fixture`. Through the app create two durable threads using the free Replay (dev) provider and synthetic prompts/titles (`Alpha - sidebar alignment fixture` and `Beta - trailing edge fixture with a longer title`). Use one short paragraph and one code block in the conversation so both left and right bounds are visible. A draft alone may not survive leaving it, so persist the threads before running back/restore flows. Do not run a paid provider. If Replay is not visible on mobile, create those same threads through the dev web app paired to the same isolated backend; the mobile app then reads the shared project/threads. No SQL writes are needed.

Seed keys used below: S = that project and two threads; F = S plus the synthetic files; U = Usage tab with its empty/no-provider-data state (no account credentials needed); G = Settings list and Appearance detail (no extra server data). Pulls uses its empty/no-forge-data state to exercise the alternate sidebar renderer. Actual PR records are unnecessary for changed outer geometry; a real PR detail is equivalent to Thread for these predicates.

Explicitly select Settings -> Appearance -> Navigation -> Material or One UI; manufacturer/preview defaults differ. Select light or dark explicitly in Appearance. Use default base font16, system font scale1, ordinary animations enabled, and keyboard closed except the dedicated keyboard step.

Proposed Android overrides on the leased emulator's authorized ADB transport (not on arbitrary devices): `adb -s <leased-serial> shell wm density 160`, then `adb -s <leased-serial> shell wm size 692x1000`. This gives 692dp width and enough height after system insets, so ordinary Android selects medium. Also use 599x1000, 600x1000, 719x1000, and 720x1000 at160dpi for exact width boundaries; 1000x599 guarantees compact by height; 1000x1000 and1400x1000 at160dpi cover inspector suppression and three panes. Equivalent 2x recipe is1384x2000 at320dpi for692dp. Validate actual RN window width/height and selected variant before recording; OS bars/cutouts can alter app bounds. Keep original size/density readings and restore them at teardown before returning the lease. Installed agent-device0.21.32 has no generic adb/shell command; the capture operator must establish the pool's lease-scoped ADB/display-override mechanism, or use Android's Display size UI and verify the resulting width. This audit did not lease a device or establish that mechanism.

Primary device can be the offered Pixel9/PixelTablet with these overrides; use one hinge-free device for controlled width comparisons. Use the offered Pixel9ProFold, or the current warm catalog's corresponding Pixel10ProFold/GalaxyZFold8, only for the hinge/fold row. Re-check availability when leasing. A fixed resized phone is sufficient for the main692dp failure, but cannot stand in for the hinge row.

## Fixture matrix

Status `MISSING` means no maintainer capture yet. `CODE-EXEMPT` means the row is traced and needs no capture for this diff. A recording may cover several rows; keep labeled settled stills as well for direct before/after comparisons. Every listed interaction/time-dependent row is marked recording.

| Row | Named fixture / branch | Data and route or setup | Diff effect and expected observation | Minimum device/viewport x theme; evidence | Status |
|---|---|---|---|---|---|
| A1 | Cold docked Material list | S; Home `/`, medium; sidebar visible, no inspector | Always writes left0; rail then list with no extra72dp offset. Cold control for A4. | Hinge-free692x>=600dp, light; still, captured before interaction | MISSING |
| A2 | Material detail, sidebar closed | S; choose Alpha from A1; medium, sidebarFloats=true | Direct: page starts after72dp rail; target width616dp at692 (=692-72-4), no header/composer/text under rail. | Same device692dp, light and dark; recording from list to detail, settled still | MISSING |
| A3 | Material floating sidebar open/dismissed | S; from A2 open sidebar, select Beta; reopen and dismiss using scrim, then hardware Back; toggle using header and keyboard command if available; also restore a remembered detail through the command palette/section-tab path to cover keepOpenFor | Direct: floating list starts x72 and width280, page stays x72 underneath; rail stays usable above scrim; scrim fades and input/accessibility returns on close. Back first closes overlay. | Same692dp, light and dark; recording covering each open/close path and a row selection | MISSING |
| A4 | Warm returned docked list | S; A2/A3 -> thread Back -> Home with no reload; repeat once | Core stale-key proof: list resumes at x72 with its right-hand row actions visible; no extra72dp shift/clipping, no late jump. | Same692dp, light and dark; uninterrupted recording that includes floating style before return; settled before/after stills | MISSING |
| A5 | Material Usage workspace page | U; select Usage from detail and return Threads | workspacePage=true, sidebarFloats=false, primary sidebar retained hidden; page starts after rail and consumes width616dp; no click-through. Existing geometry regression at float/page switch. | Same692dp, light; recording through tab switch and back | MISSING |
| A6 | Material Settings root | G; rail Settings from thread/list; no setting selected | workspacePage=true; hidden primary sidebar; Settings owns an inner list/detail pair. Select-a-setting placeholder and list must be unoccluded. | Same692dp, light; recording into Settings and out | MISSING |
| A7 | Material Settings detail / runtime navigation style | G; open Appearance; switch Material -> One UI -> Material, then return Threads | Same workspacePage predicate but detail replaces placeholder; rail spacer animates72<->0 and later medium float/dock must not retain offset. | Same692dp, light; recording with both style changes and final thread/back sequence | MISSING |
| A8 | Medium inspector sheet / overlay precedence | F; open thread Files or Git inspector; close; open with floating sidebar already shown and press Back | Column unreachable at medium; sheet opens on focused route without narrowing content. An overlay takes Back before floating sidebar. Verify no rail/page overlap after dismissal. | Same692dp, light; recording including open/dismiss/Back | MISSING |
| A9 | Material expanded rail overlay and alternate list | S; expand native rail menu if exposed, collapse; select Pulls (empty list), then Threads | railOverlay changes overlay clip only; page/list cannot jump, rail remains above float scrim. Pulls shares docked outer geometry but supplies a different sidebar node. | Same692dp, light; recording, can be in A3/A5 run | MISSING |
| B1 | One UI docked list / pill width | S; One UI, Home; medium, no rail, sidebarFloats=false | No numeric changed rail term (zero); always-left0 regression control. Default pill widens list to340 at692, versus280 at600. Row actions and pill fit. | Hinge-free692dp plus600dp, light; stills (record transition with B2) | MISSING |
| B2 | One UI floating and returned list | S; B1 -> Alpha -> open/close float -> Back; new-chat FAB then return | rail0: detail x0, overlay x0; no phantom72dp gap; draft/thread select closes sidebar; returned list aligns. Repeat once with larger app text to exercise pill-width fallback. | Same692dp, light; recording; default and larger text segments | MISSING |
| B3 | One UI workspace pages | U+G; from B2 visit Usage, Settings root, Appearance and return Threads | Primary sidebar hidden; pill moves under workspace page and content gets bottom safe-area clearance; x0 and full post-gap width; no stale offset on return. | Same692dp, light; recording through both destinations | MISSING |
| C1 | Width and short-height compact boundaries | S; Material, thread with sidebar open; resize599 ->600 ->719 ->720dp and reverse; separately1000x599dp; visit compact Usage and Settings/detail and return | Compact mounts no rail/sidebar and resets content width/translation;600/719 float,720 docks. Reverse and mid-transition changes must clear left/margin/translation keys. At600 default list280 and detail244 are expected authored widths, not a new320dp minimum. | Same leased device,160dpi boundary recipes, light; recording with actual window bounds recorded; include600/719 settled stills | MISSING |
| C2 | Expanded docked strip visible/hidden | S; Material720dp then wide1400dp; Thread; toggle sidebar and navigate list->thread->back; visit expanded Usage and Settings/detail | No changed steady-state sizing; unconditional left0 must not shift strip. Sidebar0/visible and inspector0, content touches remain within column bounds; list pinned at Home. | Hinge-free720dp and1400dp, light; recording, may share C1/C3 | MISSING |
| C3 | Expanded inspector column / suppressed sidebar | F; Material1400dp with active Files column, close/reopen/resize it; resize1000dp while column remains visible; reveal sidebar then close inspector | Wide three columns; narrower column suppresses sidebar; revealing sidebar hides inspector. Old/new width formulas are equivalent here. Check no persistent offset or hole after resize/toggle. | PixelTablet or controlled hinge-free1400x1000 and1000x1000 at160dpi, light; recording | MISSING |
| C4 | Keyboard-induced short window | S; Material medium detail, float opened, focus search/composer; show/dismiss real Gboard | Depending on resize mode keyboard may reduce reported height below600 and trigger compact, or only inset content. Record measured state and ensure rail/sidebar/page recover on dismiss. Same branch as C1 if compact; no fabricated expectation that keyboard always changes variant. | Same692dp, light; recording, may append to C1 | MISSING |
| C5 | Real fold / hinge alignment | F; named pooled foldable; fold open -> half-open -> closed -> open while thread/float is mounted; Material and One UI when possible | Exercise accepted hinge gap and rejected hinge cases using actual bounds; dynamic list floor/ceiling/rail and content width must settle without shift or crease overlap. A centered692dp Material hinge is rejected by minimums; One UI may accept it. Not evidence unless actual hinge report confirms which branch ran. | Actual foldable, measured split/cover viewport, light; recording; hinge data with capture | MISSING |
| X1 | Preference pending/failure, no Home descriptor, empty/error sidebar contents | No extra seed needed; traced wrapper/null and default-sidebar fallback | No changed geometry predicate; empty/error contents do not alter fixed outer dimensions. Core S provides nonempty proof. | No extra capture; structural equivalence to A1-A4 | CODE-EXEMPT |
| X2 | iOS native columns incl. portrait Duo | NativeWorkspaceModeContext=true, native metrics/size class/hinge | Rail width always0, workspacePage alwaysfalse; Content returns before changed renderColumns. Scalar width/content-left/content-width are unchanged because every removed rail term was0. | No iOS simulator required for this diff | CODE-EXEMPT |
| X3 | iOS compact and older/non-native split | NativeWorkspaceModeContext=false; direct renderColumns; iOS metrics mean no generic medium phone float | Rail never mounts, left remains0 in the newly attached style; other changed formulas algebraically equal. No Android navigation-style toggle on iOS. | No iOS simulator required; web/desktop also outside this caller tree | CODE-EXEMPT |

## Minimum capture bundles

Do not multiply every row by every device and theme. Run the same synthetic seed and capture sequences on baseline and candidate. Keep app/theme/navigation settings, density, text scale, viewport and data identical; do not reload between a flow's float and return-to-list steps.

1. **P1, hinge-free692dp light, baseline and candidate:** A1-A4 plus A8-A9. One uninterrupted recording can cover cold list, both thread selections, open/close/dismiss/back, rail expansion, inspector overlay, and final returned list. Export labeled A1/A2/A3/A4 settled stills. A4's still alone is insufficient.
2. **P2, hinge-free692dp dark, baseline and candidate:** A2-A4. One short recording verifies rail/page separation, scrim, and warmed list in the other theme. Light/dark are the only required theme variants; custom palettes are not geometry branches.
3. **P3, hinge-free692dp light, baseline and candidate:** A5-A7 and B1-B3. One recording visits Usage and both Settings states, changes Material/One UI in Appearance, exercises One UI list/detail/overlay/back/new draft and larger text, then restores Material. Reset larger text to16 before other bundles. At600dp B1 is covered in P4.
4. **P4, hinge-free controlled widths light, baseline and candidate:** C1-C2, C4, and B1 at600dp. Record boundary traversal and a real keyboard segment; capture settled600/719/720 states. A rapid toggle/resize during one in-flight animation is needed in addition to settled endpoints.
5. **P5, PixelTablet or the same controlled hinge-free device,1400/1000dp light, baseline and candidate:** C2-C3. One recording captures three panes, inspector width drag, suppression, sidebar reveal, and reverse recovery.
6. **P6, real pooled foldable light, baseline and candidate:** C5. One recording per necessary navigation style/pose sequence, with reported dimensions and hinge geometry. Ordinary resizing in P4 cannot prove this row. If the leased device never reports a usable vertical hinge, report C5 incomplete rather than claim the accepted branch passed.

Use the command palette tab switch in A3 to cover `openWorkspaceTab/keepOpenFor`; the native rail tab reset in A9 cannot substitute for it. Compact Usage/Settings in C1 cover navigation under pages; expanded Usage/Settings in C2 reuse the wider workspacePage branch.

The irreducible main-bug comparison is P1 plus P2; complete regression coverage for the changed component adds P3-P6. There is no need for an iOS device in this pass. All rows involving navigation, animation, timing, toggles, keyboard, resizing, or folding require recordings; stills supplement them, never replace them. A single artifact can satisfy multiple rows only if its continuous flow shows their preconditions.

## Audit status and friction

Covered: complete one-file diff, its caller/renderer paths, layout predicates, navigation/inspector state changes, appearance sizing, hinge guards, platform exclusions, fixture seed plan, and capture scope. Not run: app/device sessions, visual captures, tests, or repository checks; pass1 asks for the matrix before captures. No source edits, pushes, comments, servers, emulators or leases were started. Worktree was clean on inspection. Disk available108GB, above the5GB stop threshold.

FRICTION:
- tool | installed agent-device has no generic adb/shell command; exact wm recipes are specified but a lease-scoped execution path remains unverified | expose/document a pool display-override operation, or use Android Display size UI and measure RN bounds.

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

Tools used: git diff/rev-parse/status, rg and source reads, cafe pr view/comments/reviews/status and issue view, agent-device CLI help, and disk availability checks. No tests were run in this planning pass.

VERDICT: INSUFFICIENT A1-A9, B1-B3, C1-C5 require baseline/candidate captures; interaction rows require recordings
