diff --git a/.agents/skills/verify-deepseekbot/features/phone-pairing.md b/.agents/skills/verify-deepseekbot/features/phone-pairing.md
index 2c0d7addb7..aba2669b88 100644
--- a/.agents/skills/verify-deepseekbot/features/phone-pairing.md
+++ b/.agents/skills/verify-deepseekbot/features/phone-pairing.md
@@ -15,7 +15,7 @@ A Human pairs the phone app with their Host by a one-time QR code, and the phone
## How to get to it (user POV)
- Computer: Bot mode → settings → Devices → Pair a phone. The dialog shows the QR code and a copy link; the Devices list has Revoke.
-- Phone: the app opens on "Pair with your computer"; scan the code or paste the link, then tap Pair.
+- Phone: first use opens "Pair with your computer". From an existing session: Workspaces → Computers → Pair a new computer. Scan the code or paste the link, then tap Pair. Cloud → Join by link or QR is the separate cloud invitation path; its preview identifies organization membership versus one-channel guest access. The category tabs are the first drawer row; swipe, backdrop, Android Back and accessibility escape dismiss it.
## Driving it with verify-app
diff --git a/.github/pr-screenshots/2161-workspace-drawer/README.md b/.github/pr-screenshots/2161-workspace-drawer/README.md
new file mode 100644
index 0000000000..277c832284
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/README.md
@@ -0,0 +1,21 @@
+# Workspace drawer UAT evidence — Batch 22
+
+Draft PR #2170; issue #2161. Runtime `bfb5cc4ec9b5b3029bebd7d544905efa6d2dcd4b`, baseline `d2529feb520ace2d773db6ac0966dc265887bdc5`. Installed native binary `494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b`; its native inputs match. TOB UAT `bf6d896e813457382cd2bb2e7c74b22c03ba01ca`. This commit adds evidence and docs only. Source CI [39104178457](https://github.com/BotHarness/DeepSeekBot/actions/runs/39104178457) passed at the runtime head.
+
+## Observed behavior
+
+- Before: an unpaired user enters the separate Cloud services screen, verifies the saved session, then opens Cloud workspaces. `before-verified-cloud-services.png` captures this prior entry point, not a predecessor drawer with cloud rows.
+- After: the cloud header opens the same root Workspaces drawer. The synthetic B account sees its own organization, two member organizations, and a channel-only guest organization. Selecting the member organization lists four channels; selecting the guest organization lists only its invited channel. Organization names may match, but role and instance remain distinct.
+- `scope-accepted.json` and `scope-revoked.json` use the actual native session for independent public HTTP readback: no guest-organization membership, sibling denied, filtered directory limited to the granted organization, and revoked grant removed. These checks supplement the UI; they are not screenshots masquerading as API proof.
+- An unsent draft survives a real Refresh session action and token rotation. Cold launch restores the directory. The directory itself does not claim verified live identity; Check session separately re-establishes it.
+- Revoking only the test grant and reopening removes the guest organization. B sign-out leaves only a sign-in entry; a separately prepared A session after restart shows Owner instead of B's Member/Guest roles. This verifies logout and account change across restart, not an in-place browser account replacement.
+- Both native sessions were signed out through the UI. The latest refresh tokens were rejected, baseline sessions retained, and local credentials removed. The temporary fixture files were deleted only after this verification.
+- Three Dynamic Type sizes (large, extra-extra-extra-large, accessibility-extra-extra-extra-large) × light/dark: labels wrap, the list scrolls, Create workspace and Cloud services remain reachable. The maximum-size Cloud services action was actually pressed. `workspace-switch.mp4` is a 26-second member→guest navigation recording; the contact sheet was inspected before publication.
+
+## Preserved failures and limits
+
+Initial Metro bound only IPv6 while the launch URL used IPv4; restarting this task's Metro with IPv4-first DNS resolved it. One generic scroll-top command misread the nested scroll surface despite already being at the top; `first-matrix-scroll-failure.png` preserves the scene, and targeted swipes completed the matrix. A cold checkpoint was requested before live identity verification and correctly refused; Check session then passed. An old temporary logout wrapper referenced a removed worktree; the current source wrapper verified logout successfully. No product source fix was needed in this run.
+
+No computer was paired in this Simulator, so computer selection/return was not exercised and no new pairing was introduced. Pagination, error/retry and exact cancelled-request ordering retain source-test evidence only. Android, physical camera, Human/VoiceOver acceptance, new-instance Hosted AuthKit continuation, sender sharing and QR rendering/save remain open. Synthetic API-prepared sessions do not count as a Hosted AuthKit login.
+
+The original large/light settings were restored, only the task's grant/sessions were cleaned up, and its app/driver/Metro were stopped. Existing shared documents, installed app and trusted config, other sessions, physical phone, Hosts and signed Products were preserved. No native build/install/erase, production change or merge.
diff --git a/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-dark-bottom.png b/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-dark-bottom.png
new file mode 100644
index 0000000000..945fc996c5
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-dark-bottom.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-light-top.png b/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-light-top.png
new file mode 100644
index 0000000000..6109395cc9
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/accessibility-extra-extra-extra-large-light-top.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-account-a.png b/.github/pr-screenshots/2161-workspace-drawer/after-account-a.png
new file mode 100644
index 0000000000..ca796660ae
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-account-a.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-cold-drawer.png b/.github/pr-screenshots/2161-workspace-drawer/after-cold-drawer.png
new file mode 100644
index 0000000000..22c281cc3f
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-cold-drawer.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-drawer-light.png b/.github/pr-screenshots/2161-workspace-drawer/after-drawer-light.png
new file mode 100644
index 0000000000..34f9280699
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-drawer-light.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-guest-channels.png b/.github/pr-screenshots/2161-workspace-drawer/after-guest-channels.png
new file mode 100644
index 0000000000..6b10c718c5
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-guest-channels.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-member-channels.png b/.github/pr-screenshots/2161-workspace-drawer/after-member-channels.png
new file mode 100644
index 0000000000..f90ef6b5d5
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-member-channels.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-refresh-draft.png b/.github/pr-screenshots/2161-workspace-drawer/after-refresh-draft.png
new file mode 100644
index 0000000000..19e558c08a
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-refresh-draft.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-revoke-drawer.png b/.github/pr-screenshots/2161-workspace-drawer/after-revoke-drawer.png
new file mode 100644
index 0000000000..90cdc47d7e
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-revoke-drawer.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/after-signed-out-drawer.png b/.github/pr-screenshots/2161-workspace-drawer/after-signed-out-drawer.png
new file mode 100644
index 0000000000..c525cc6a8c
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/after-signed-out-drawer.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/before-verified-cloud-services.png b/.github/pr-screenshots/2161-workspace-drawer/before-verified-cloud-services.png
new file mode 100644
index 0000000000..2714dfb714
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/before-verified-cloud-services.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-dark-bottom.png b/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-dark-bottom.png
new file mode 100644
index 0000000000..70f4b4095a
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-dark-bottom.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-light-top.png b/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-light-top.png
new file mode 100644
index 0000000000..5460f7add1
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/extra-extra-extra-large-light-top.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/first-matrix-scroll-failure.png b/.github/pr-screenshots/2161-workspace-drawer/first-matrix-scroll-failure.png
new file mode 100644
index 0000000000..5bf0916198
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/first-matrix-scroll-failure.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/large-dark-top.png b/.github/pr-screenshots/2161-workspace-drawer/large-dark-top.png
new file mode 100644
index 0000000000..5b5dfc07ac
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/large-dark-top.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/logout-a.json b/.github/pr-screenshots/2161-workspace-drawer/logout-a.json
new file mode 100644
index 0000000000..779c147c03
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/logout-a.json
@@ -0,0 +1,6 @@
+{
+ "event": "uat_logout_verified",
+ "ownSessionRevoked": true,
+ "latestRefreshRejected": true,
+ "otherSessionsPreserved": true
+}
diff --git a/.github/pr-screenshots/2161-workspace-drawer/logout-b.json b/.github/pr-screenshots/2161-workspace-drawer/logout-b.json
new file mode 100644
index 0000000000..779c147c03
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/logout-b.json
@@ -0,0 +1,6 @@
+{
+ "event": "uat_logout_verified",
+ "ownSessionRevoked": true,
+ "latestRefreshRejected": true,
+ "otherSessionsPreserved": true
+}
diff --git a/.github/pr-screenshots/2161-workspace-drawer/proof.json b/.github/pr-screenshots/2161-workspace-drawer/proof.json
new file mode 100644
index 0000000000..4b0d0bef47
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/proof.json
@@ -0,0 +1,80 @@
+{
+ "date": "2026-10-18",
+ "batch": 22,
+ "before": "d2529feb520ace2d773db6ac0966dc265887bdc5",
+ "runtime": "bfb5cc4ec9b5b3029bebd7d544905efa6d2dcd4b",
+ "native": "494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b",
+ "uat": "bf6d896e813457382cd2bb2e7c74b22c03ba01ca",
+ "ci": 39104178457,
+ "viewport": [402, 874],
+ "syntheticTestSession": true,
+ "hostedAuthKitClaim": false,
+ "nativePassed": [
+ "header opens shared root drawer",
+ "member selection lists four organization channels",
+ "guest organization lists one authorized channel",
+ "actual native token has no guest-organization membership and sibling access denied",
+ "refresh rotates token and preserves unsent draft",
+ "cold directory restores; Check session separately verifies live identity",
+ "reopening after grant revocation removes guest organization",
+ "logout clears directory; A fixture after restart shows owner identities instead of B member or guest identities",
+ "both mobile sessions remote-revoked; latest refresh denied; other baseline sessions retained"
+ ],
+ "matrix": [
+ {
+ "size": "large",
+ "theme": "light",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ },
+ {
+ "size": "large",
+ "theme": "dark",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ },
+ {
+ "size": "extra-extra-extra-large",
+ "theme": "light",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ },
+ {
+ "size": "extra-extra-extra-large",
+ "theme": "dark",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ },
+ {
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "light",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ },
+ {
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "dark",
+ "cloudServicesReachable": true,
+ "createWorkspaceVisibleAfterScroll": true
+ }
+ ],
+ "nativeUntested": [
+ "computer selection: no computer saved in this simulator; no new pairing added",
+ "pagination, injected errors/retry and exact cancelled-request ordering: source tests only",
+ "in-place account replacement without fixture restart",
+ "Android",
+ "physical camera",
+ "Human and VoiceOver acceptance",
+ "new-instance Hosted AuthKit continuation",
+ "native sender share and QR generation/save"
+ ],
+ "cleanup": {
+ "ownGuestGrantRevoked": true,
+ "temporarySessionsDeletedAfterRevocationProof": true,
+ "largeLightRestored": true,
+ "ownDriverAndMetroStopped": true,
+ "nativeBuildInstallErase": false,
+ "prodTouched": false,
+ "merged": false
+ }
+}
diff --git a/.github/pr-screenshots/2161-workspace-drawer/scope-accepted.json b/.github/pr-screenshots/2161-workspace-drawer/scope-accepted.json
new file mode 100644
index 0000000000..12764383f4
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/scope-accepted.json
@@ -0,0 +1,9 @@
+{
+ "at": "2026-10-18T07:26:22.637Z",
+ "usingActualNativeSession": true,
+ "expectedGuestGrantVisible": true,
+ "grantRevoked": false,
+ "noOrganizationMembership": true,
+ "siblingDenied": true,
+ "orgFilter": true
+}
diff --git a/.github/pr-screenshots/2161-workspace-drawer/scope-revoked.json b/.github/pr-screenshots/2161-workspace-drawer/scope-revoked.json
new file mode 100644
index 0000000000..c8046304c8
--- /dev/null
+++ b/.github/pr-screenshots/2161-workspace-drawer/scope-revoked.json
@@ -0,0 +1,9 @@
+{
+ "at": "2026-10-18T07:29:22.014Z",
+ "usingActualNativeSession": true,
+ "expectedGuestGrantVisible": false,
+ "grantRevoked": true,
+ "noOrganizationMembership": true,
+ "siblingDenied": true,
+ "orgFilter": true
+}
diff --git a/.github/pr-screenshots/2161-workspace-drawer/workspace-switch-contact.png b/.github/pr-screenshots/2161-workspace-drawer/workspace-switch-contact.png
new file mode 100644
index 0000000000..a9f9121302
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/workspace-switch-contact.png differ
diff --git a/.github/pr-screenshots/2161-workspace-drawer/workspace-switch.mp4 b/.github/pr-screenshots/2161-workspace-drawer/workspace-switch.mp4
new file mode 100644
index 0000000000..d0d6ced31c
Binary files /dev/null and b/.github/pr-screenshots/2161-workspace-drawer/workspace-switch.mp4 differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/README.md b/.github/pr-screenshots/2171-drawer-headers/README.md
new file mode 100644
index 0000000000..b5861688ff
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/README.md
@@ -0,0 +1,26 @@
+# Drawer headers — Batch 23
+
+Runtime `4185d28a380bd37f68eafb765d28d24ee947af4a`; base `3be6b52c384812f801d2b9d58013580c0d6e4c0f`; installed native `494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b`. [Runtime CI](https://github.com/BotHarness/DeepSeekBot/actions/runs/39109396184) passed all applicable jobs.
+
+The assigned existing iOS 26 Simulator rendered both views at 402 × 874 in English. Before/after pairs use matching default text, light theme and signed-out data. No new dependency, native build, account login, service mutation or Prod change.
+
+## Results
+
+- Workspace drawer and generic statistics Sheet: default/intermediate/maximum Dynamic Type × light/dark captured and inspected. Long titles wrap; maximum-size close actually clicked in both surfaces. Sheet body was scrolled; the secondary Got it button was partly clipped in the captured position, so this is not a full body-action reachability qualification.
+- Normal-size iOS title uses native glass from existing Expo UI. Larger text uses an opaque rounded rectangle. Close is a separate 44pt target. Android source/bundle passed but was not driven natively.
+- Workspace close/reopen and backdrop dismissal were actually performed. The 3.7-second recording and 14-cell contact sheet were inspected.
+- Real Settings Reduce Transparency toggle was observed checked, App returned to opaque title/close, then the original unchecked state and glass rendering were restored. No app restart was needed for the setting change.
+- Original statistics preferences were backed up privately, noticeSeen was temporarily cleared to show the production Sheet, then the original bytes were restored. Development telemetry transport was unconfigured. No telemetry identity or private file is published.
+
+## First failures retained
+
+- Initial sandbox source run could not bind loopback; permitted rerun passed 649 tests / 8 skipped.
+- Initial CI rejected comments/useEffect; external-store subscription replaced it. The final source-policy, typecheck, build, affected tests and both mobile bundles passed.
+- First native maximum-light glass title became a dark ellipse around multiline text (`first-pass/`). Final runtime uses the rounded opaque large-text treatment; all six cases rerun.
+- Initial capture was during bundling and was discarded from final comparisons. First system-switch label click did not toggle the switch; unchecked state and unchanged glass were retained. Actual switch tap then produced checked → opaque → unchecked → glass evidence.
+
+## Boundaries and cleanup
+
+Settings/SheetStack remain unchanged; this does not requalify #2112 or claim every drawer migrated. Android, physical phone, VoiceOver/Human and older-iOS rendering remain unverified. Source and iOS results do not close those gates.
+
+Large/light and Reduce Transparency off restored; original statistics preference file restored byte-for-byte; own App, driver and Metro stopped. Installed app/data/Keychain, signed Products, rollback, three original QA documents, physical-phone lane and all Hosts preserved. No merge.
diff --git a/.github/pr-screenshots/2171-drawer-headers/after-drawer.png b/.github/pr-screenshots/2171-drawer-headers/after-drawer.png
new file mode 100644
index 0000000000..b7bd60c61d
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/after-drawer.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/after-sheet.png b/.github/pr-screenshots/2171-drawer-headers/after-sheet.png
new file mode 100644
index 0000000000..93da6fc6a5
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/after-sheet.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/before-drawer.png b/.github/pr-screenshots/2171-drawer-headers/before-drawer.png
new file mode 100644
index 0000000000..ed68d15f6d
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/before-drawer.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/before-sheet.png b/.github/pr-screenshots/2171-drawer-headers/before-sheet.png
new file mode 100644
index 0000000000..d4f45f88ba
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/before-sheet.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-dark.png
new file mode 100644
index 0000000000..ef9d04456b
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-light.png b/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-light.png
new file mode 100644
index 0000000000..bd6ede9c1c
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-accessibility-extra-extra-extra-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-close-reopen.mp4 b/.github/pr-screenshots/2171-drawer-headers/drawer-close-reopen.mp4
new file mode 100644
index 0000000000..aa1f34e6fe
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-close-reopen.mp4 differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-dark.png
new file mode 100644
index 0000000000..6f67af1005
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-light.png b/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-light.png
new file mode 100644
index 0000000000..75afe8a46c
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-extra-extra-extra-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/drawer-large-dark.png
new file mode 100644
index 0000000000..92da6093d0
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-large-light.png b/.github/pr-screenshots/2171-drawer-headers/drawer-large-light.png
new file mode 100644
index 0000000000..a638b0df75
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/drawer-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/drawer-matrix.json b/.github/pr-screenshots/2171-drawer-headers/drawer-matrix.json
new file mode 100644
index 0000000000..15a4beea44
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/drawer-matrix.json
@@ -0,0 +1,38 @@
+[
+ {
+ "kind": "drawer",
+ "size": "large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "drawer",
+ "size": "large",
+ "theme": "dark",
+ "closeAccessible": true
+ },
+ {
+ "kind": "drawer",
+ "size": "extra-extra-extra-large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "drawer",
+ "size": "extra-extra-extra-large",
+ "theme": "dark",
+ "closeAccessible": true
+ },
+ {
+ "kind": "drawer",
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "drawer",
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "dark",
+ "closeAccessible": true
+ }
+]
diff --git a/.github/pr-screenshots/2171-drawer-headers/first-pass/sheet-accessibility-extra-extra-extra-large-light.png b/.github/pr-screenshots/2171-drawer-headers/first-pass/sheet-accessibility-extra-extra-extra-large-light.png
new file mode 100644
index 0000000000..1c271e33bc
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/first-pass/sheet-accessibility-extra-extra-extra-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/first-pass/transparency-not-enabled.png b/.github/pr-screenshots/2171-drawer-headers/first-pass/transparency-not-enabled.png
new file mode 100644
index 0000000000..72b9a7a6a4
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/first-pass/transparency-not-enabled.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/proof.json b/.github/pr-screenshots/2171-drawer-headers/proof.json
new file mode 100644
index 0000000000..ba0f4153e2
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/proof.json
@@ -0,0 +1,36 @@
+{
+ "issue": 2171,
+ "pr": 2172,
+ "base": "3be6b52c384812f801d2b9d58013580c0d6e4c0f",
+ "runtime": "4185d28a380bd37f68eafb765d28d24ee947af4a",
+ "native": "494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b",
+ "ci": 39109396184,
+ "environment": "local iOS Simulator, existing UAT signed-out configuration",
+ "viewport": "402x874",
+ "matrices": {
+ "drawer": "3 text sizes x light/dark",
+ "sheet": "3 text sizes x light/dark"
+ },
+ "pass": [
+ "native title material",
+ "maximum-text close in drawer and sheet",
+ "drawer close/reopen/backdrop",
+ "sheet body scroll",
+ "Reduce Transparency on/opaque and off/glass restored"
+ ],
+ "unverified": [
+ "Android native",
+ "physical phone",
+ "Human and VoiceOver",
+ "older iOS native fallback",
+ "Settings / SheetStack adoption"
+ ],
+ "cleanup": [
+ "large/light restored",
+ "Reduce Transparency restored off",
+ "original statistics preferences restored byte-for-byte",
+ "own app/driver/Metro stopped",
+ "no login or service writes",
+ "other Hosts and physical phone unchanged"
+ ]
+}
diff --git a/.github/pr-screenshots/2171-drawer-headers/recording-contact.png b/.github/pr-screenshots/2171-drawer-headers/recording-contact.png
new file mode 100644
index 0000000000..7a4cbc9f56
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/recording-contact.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/reduce-transparency.png b/.github/pr-screenshots/2171-drawer-headers/reduce-transparency.png
new file mode 100644
index 0000000000..61f4b8facb
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/reduce-transparency.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/restored-glass.png b/.github/pr-screenshots/2171-drawer-headers/restored-glass.png
new file mode 100644
index 0000000000..9c0e852fa2
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/restored-glass.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-dark.png
new file mode 100644
index 0000000000..f71e413f01
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-light.png b/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-light.png
new file mode 100644
index 0000000000..90add8f205
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-accessibility-extra-extra-extra-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-dark.png
new file mode 100644
index 0000000000..ef729088d0
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-light.png b/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-light.png
new file mode 100644
index 0000000000..9dcb892770
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-extra-extra-extra-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-large-dark.png b/.github/pr-screenshots/2171-drawer-headers/sheet-large-dark.png
new file mode 100644
index 0000000000..1cd03b44e0
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-large-dark.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-large-light.png b/.github/pr-screenshots/2171-drawer-headers/sheet-large-light.png
new file mode 100644
index 0000000000..93da6fc6a5
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-large-light.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-matrix.json b/.github/pr-screenshots/2171-drawer-headers/sheet-matrix.json
new file mode 100644
index 0000000000..fa8640f830
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/sheet-matrix.json
@@ -0,0 +1,38 @@
+[
+ {
+ "kind": "sheet",
+ "size": "large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "sheet",
+ "size": "large",
+ "theme": "dark",
+ "closeAccessible": true
+ },
+ {
+ "kind": "sheet",
+ "size": "extra-extra-extra-large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "sheet",
+ "size": "extra-extra-extra-large",
+ "theme": "dark",
+ "closeAccessible": true
+ },
+ {
+ "kind": "sheet",
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "light",
+ "closeAccessible": true
+ },
+ {
+ "kind": "sheet",
+ "size": "accessibility-extra-extra-extra-large",
+ "theme": "dark",
+ "closeAccessible": true
+ }
+]
diff --git a/.github/pr-screenshots/2171-drawer-headers/sheet-max-bottom.png b/.github/pr-screenshots/2171-drawer-headers/sheet-max-bottom.png
new file mode 100644
index 0000000000..cd71901e56
Binary files /dev/null and b/.github/pr-screenshots/2171-drawer-headers/sheet-max-bottom.png differ
diff --git a/.github/pr-screenshots/2171-drawer-headers/transparency-app.txt b/.github/pr-screenshots/2171-drawer-headers/transparency-app.txt
new file mode 100644
index 0000000000..aee3b78b40
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/transparency-app.txt
@@ -0,0 +1,33 @@
+Page: ai.botharness.deepseekbot.dev
+App: ai.botharness.deepseekbot.dev
+Snapshot: 27 visible nodes
+Simulator AX snapshot inapplicable (system-surface-host-lingering); used XCTest, which read app content: the system surface host process was still running but no longer presenting.
+@e1 [application] "DeepSeekBot Dev"
+@e2 [other] "gearshape.fill"
+@e3 [image] "gearshape.fill"
+@e4 [navigation-bar] "Back"
+@e5 [button] "Back"
+@e6 [text] "Cloud services"
+@e7 [button] "Workspaces"
+@e8 [other] "tob-instance-uat"
+@e9 [scroll-area] "Import the UAT configuration supplied by your deployer. Cloud sign-in is separate from pairing a computer." [scrollable]
+ [content below scroll-area hidden]
+@e10 [other] [same label as parent]
+@e11 [button] "Scan configuration"
+@e12 [button] "Paste configuration"
+@e13 [text] "https://tob-service-uat.yangmufeng233.workers.dev"
+@e14 [text] "UAT"
+@e15 [text] "Signed out"
+@e16 [button] "Sign in"
+@e17 [other] "Workspaces"
+@e18 [button] "Close workspaces"
+@e19 [scroll-area] "Computers" [scrollable]
+ [content below scroll-area hidden]
+@e20 [other] "Computers"
+@e21 [text] "Cloud workspaces"
+@e22 [button] "tob-service-uat.yangmufeng233.workers.dev · Sign in to continue"
+@e23 [button] "Join with invitation"
+@e24 [button] "Create workspace"
+@e25 [button] "Pair a new computer"
+@e26 [button] "Cloud services"
+@e27 [button] "Notifications"
diff --git a/.github/pr-screenshots/2171-drawer-headers/transparency-enabled.txt b/.github/pr-screenshots/2171-drawer-headers/transparency-enabled.txt
new file mode 100644
index 0000000000..9c0988d5fc
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/transparency-enabled.txt
@@ -0,0 +1,23 @@
+Page: com.apple.Preferences
+App: com.apple.Preferences
+Snapshot: 18 visible nodes
+Simulator AX snapshot inapplicable (system-surface-host-lingering); used XCTest, which read app content: the system surface host process was still running but no longer presenting.
+@e1 [application] "Settings"
+@e2 [navigation-bar] "Back"
+@e3 [button] "Back"
+@e4 [text] "Display & Text Size"
+@e5 [table] "Bold Text"
+ [content below table hidden]
+@e6 [switch] "Bold Text" [unchecked]
+@e7 [cell] "Larger Text, On"
+@e8 [switch] "Button Shapes" [unchecked]
+@e9 [switch] "On/Off Labels" [unchecked]
+@e10 [link] "What's new in Display & Text Size…"
+@e11 [switch] "Reduce Transparency" [checked]
+@e12 [other] "Improve contrast by reducing transparency and blurs on some backgrounds to increase legibility."
+@e13 [switch] "Increase Contrast" [unchecked]
+@e14 [other] "Increase color contrast between app foreground and background colors."
+@e15 [switch] "Differentiate Without Color" [unchecked]
+@e16 [other] "Replaces user interface items that rely solely on color to convey information with alternatives."
+@e17 [switch] "Prefer Horizontal Text" [unchecked]
+@e18 [other] "Prefer horizontal text in languages that support vertical text."
diff --git a/.github/pr-screenshots/2171-drawer-headers/transparency-restored.txt b/.github/pr-screenshots/2171-drawer-headers/transparency-restored.txt
new file mode 100644
index 0000000000..dd68a451cb
--- /dev/null
+++ b/.github/pr-screenshots/2171-drawer-headers/transparency-restored.txt
@@ -0,0 +1,23 @@
+Page: com.apple.Preferences
+App: com.apple.Preferences
+Snapshot: 18 visible nodes
+Simulator AX snapshot inapplicable (system-surface-host-lingering); used XCTest, which read app content: the system surface host process was still running but no longer presenting.
+@e1 [application] "Settings"
+@e2 [navigation-bar] "Back"
+@e3 [button] "Back"
+@e4 [text] "Display & Text Size"
+@e5 [table] "Bold Text"
+ [content below table hidden]
+@e6 [switch] "Bold Text" [unchecked]
+@e7 [cell] "Larger Text, On"
+@e8 [switch] "Button Shapes" [unchecked]
+@e9 [switch] "On/Off Labels" [unchecked]
+@e10 [link] "What's new in Display & Text Size…"
+@e11 [switch] "Reduce Transparency" [unchecked]
+@e12 [other] "Improve contrast by reducing transparency and blurs on some backgrounds to increase legibility."
+@e13 [switch] "Increase Contrast" [unchecked]
+@e14 [other] "Increase color contrast between app foreground and background colors."
+@e15 [switch] "Differentiate Without Color" [unchecked]
+@e16 [other] "Replaces user interface items that rely solely on color to convey information with alternatives."
+@e17 [switch] "Prefer Horizontal Text" [unchecked]
+@e18 [other] "Prefer horizontal text in languages that support vertical text."
diff --git a/.github/pr-screenshots/2174-drawer-compact/README.md b/.github/pr-screenshots/2174-drawer-compact/README.md
new file mode 100644
index 0000000000..1e70bb1d0d
--- /dev/null
+++ b/.github/pr-screenshots/2174-drawer-compact/README.md
@@ -0,0 +1,13 @@
+# Compact workspace drawer — native evidence
+
+Final drawer source `dc15ffd3a7003ef0c0f4110f834f02f15a990e6c`, integrated `1376b50b1f8358d9d9b22c89e55901fb5a141d24`; retained native `494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b`. No native dependency/config changes. Real shared iOS Simulator screenshots, 402 × 874, English.
+
+`before-*` is the prior title/X + mixed Add iteration at source `59fc97fd7bada6d54cbae1feacc4d93d032e4c82`. The same empty-computer/signed-out-UAT fixture, viewport and themes are used. The exposed screen behind the drawer differs; compare the drawer only. These are not screenshots of the PR base branch. Earlier base evidence remains in `../2174-drawer-tabs/` and is superseded for final appearance.
+
+`*-default-*`, `*-intermediate-*`, `*-max-*`, `join-direct` and `pair-direct` use the final source. `cloud-max-scrolled-dark` shows the address after a swipe in the constrained middle list; the footer stayed at x0/y757.6667/w342/h78.3333. A duplicate footer under the backdrop was found during review, removed, and these final screenshots recaptured.
+
+Naming screenshots use source `f2d0f4af3ebeecfe5df9f25d2bc1a3636340531d`, integrated `9e77d9810ad642388ed8ac8bcd9a4b9725f181c0`; naming code is identical in the final source. Native edit changed the display name to `TOB UAT`, saved, survived cold restart, then restored `tob-instance-uat` and cold-read it back. No login, credentials, Host identity or organization name changed. `pair-name*` only verifies the optional input; no pairing was redeemed. Driver keystroke injection opened Expo's development menu, so native clipboard Paste was used for this nonsecret test name.
+
+Local checks: typecheck, focused lint/format/source-policy/release-ledger checks; 74 affected focused tests plus 14 host-look/pairing-telemetry tests at f2d0 (counts overlap). After the backdrop-only fix: typecheck and 19 drawer/style tests passed. CI status is recorded in the PR, separately from these runtime checks.
+
+Limits: no Android/physical/VoiceOver/Human acceptance; no signed-in org/guest fixture or actual named-computer pairing in this run. The previously observed Pair-modal → cloud-services root-drawer overlay issue remains unqualified; the normal cold-open path works. This is not a full navigation or authentication pass. No production change or merge.
diff --git a/.github/pr-screenshots/2174-drawer-compact/before-cloud-dark.png b/.github/pr-screenshots/2174-drawer-compact/before-cloud-dark.png
new file mode 100644
index 0000000000..33dde0ceca
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/before-cloud-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/before-cloud-light.png b/.github/pr-screenshots/2174-drawer-compact/before-cloud-light.png
new file mode 100644
index 0000000000..161e941e90
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/before-cloud-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/before-computers-dark.png b/.github/pr-screenshots/2174-drawer-compact/before-computers-dark.png
new file mode 100644
index 0000000000..0de82d4d75
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/before-computers-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/before-computers-light.png b/.github/pr-screenshots/2174-drawer-compact/before-computers-light.png
new file mode 100644
index 0000000000..17cc4c59c0
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/before-computers-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-default-dark.png b/.github/pr-screenshots/2174-drawer-compact/cloud-default-dark.png
new file mode 100644
index 0000000000..2edf3010f5
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-default-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-default-light.png b/.github/pr-screenshots/2174-drawer-compact/cloud-default-light.png
new file mode 100644
index 0000000000..88043f6b85
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-default-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-dark.png b/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-dark.png
new file mode 100644
index 0000000000..5e864512f5
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-light.png b/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-light.png
new file mode 100644
index 0000000000..99be202dab
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-intermediate-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-max-dark.png b/.github/pr-screenshots/2174-drawer-compact/cloud-max-dark.png
new file mode 100644
index 0000000000..e0a2380834
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-max-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-max-light.png b/.github/pr-screenshots/2174-drawer-compact/cloud-max-light.png
new file mode 100644
index 0000000000..99bf1d0054
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-max-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/cloud-max-scrolled-dark.png b/.github/pr-screenshots/2174-drawer-compact/cloud-max-scrolled-dark.png
new file mode 100644
index 0000000000..9386b032e0
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/cloud-max-scrolled-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-default-dark.png b/.github/pr-screenshots/2174-drawer-compact/computers-default-dark.png
new file mode 100644
index 0000000000..70e1cb4c03
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-default-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-default-light.png b/.github/pr-screenshots/2174-drawer-compact/computers-default-light.png
new file mode 100644
index 0000000000..db6685942c
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-default-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-dark.png b/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-dark.png
new file mode 100644
index 0000000000..b710be7d4a
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-light.png b/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-light.png
new file mode 100644
index 0000000000..d9e2c4daba
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-intermediate-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-max-dark.png b/.github/pr-screenshots/2174-drawer-compact/computers-max-dark.png
new file mode 100644
index 0000000000..1466c4365f
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-max-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/computers-max-light.png b/.github/pr-screenshots/2174-drawer-compact/computers-max-light.png
new file mode 100644
index 0000000000..1ff42a6b8f
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/computers-max-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/join-direct.png b/.github/pr-screenshots/2174-drawer-compact/join-direct.png
new file mode 100644
index 0000000000..60ce9b328e
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/join-direct.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/name-restored.png b/.github/pr-screenshots/2174-drawer-compact/name-restored.png
new file mode 100644
index 0000000000..f4ee1f28f1
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/name-restored.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/pair-direct.png b/.github/pr-screenshots/2174-drawer-compact/pair-direct.png
new file mode 100644
index 0000000000..b9f0f1375a
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/pair-direct.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/pair-name-keyboard.png b/.github/pr-screenshots/2174-drawer-compact/pair-name-keyboard.png
new file mode 100644
index 0000000000..0826a1b0b7
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/pair-name-keyboard.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/pair-name.png b/.github/pr-screenshots/2174-drawer-compact/pair-name.png
new file mode 100644
index 0000000000..b1d0ee5e3a
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/pair-name.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/proof.json b/.github/pr-screenshots/2174-drawer-compact/proof.json
new file mode 100644
index 0000000000..524dd85ffe
--- /dev/null
+++ b/.github/pr-screenshots/2174-drawer-compact/proof.json
@@ -0,0 +1,47 @@
+{
+ "issue": 2174,
+ "pr": 2175,
+ "source": "dc15ffd3a7003ef0c0f4110f834f02f15a990e6c",
+ "integrated": "1376b50b1f8358d9d9b22c89e55901fb5a141d24",
+ "native": "494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b",
+ "beforeSource": "59fc97fd7bada6d54cbae1feacc4d93d032e4c82",
+ "beforeIntegrated": "9a73f9c21440235d8caa6fdc9f7d65d5dc6dfde0",
+ "namingSource": "f2d0f4af3ebeecfe5df9f25d2bc1a3636340531d",
+ "namingIntegrated": "9e77d9810ad642388ed8ac8bcd9a4b9725f181c0",
+ "viewport": "402x874",
+ "locale": "en",
+ "fixture": "empty Computer directory and signed-out public UAT cloud service",
+ "passed": [
+ "Computers/Cloud tab selection and independent contextual Add destinations",
+ "fixed Notifications footer in both tabs; cloud management only in Cloud",
+ "default/intermediate/maximum Dynamic Type in light/dark",
+ "maximum text middle-list swipe exposes the address while Notifications rect remains unchanged",
+ "left-swipe dismissal",
+ "local cloud name save and cold-start persistence; exact original name restored and cold-read back",
+ "computer optional name input; no pairing redeemed"
+ ],
+ "footerRectAtMax": {
+ "x": 0,
+ "y": 757.6666717529297,
+ "width": 342,
+ "height": 78.33331298828125
+ },
+ "unverified": [
+ "Android native and physical phone",
+ "VoiceOver and Human acceptance",
+ "signed-in organization/member/guest rendering on this revision",
+ "real computer pairing with name persistence",
+ "full keyboard/text-size matrix and every footer destination",
+ "Reduce Transparency toggle"
+ ],
+ "knownCaveat": "Opening cloud-services from the native Pair modal can hide the root drawer; normal cold-open path works. Regression origin is not established; full navigation acceptance is not claimed.",
+ "correction": "Native review at f2d0 found duplicate footer in backdrop. dc15 removed only that duplicate; all final drawer layout screenshots and contextual Add actions were recaptured. Naming code is unchanged.",
+ "comparisonLimit": "Before/after use the same viewport, theme, locale and drawer fixture. The exposed underlying route differs (pairing vs invitation), so comparison is limited to the drawer.",
+ "cleanup": [
+ "original UAT display name restored and cold-read back",
+ "driver closed; own Metro stopped",
+ "qa/batch-24 and original three untracked documents preserved",
+ "light/large and original locale restored; task clipboard cleared",
+ "no account/Host/Prod writes, pairing redemption, native build or merge"
+ ]
+}
diff --git a/.github/pr-screenshots/2174-drawer-compact/rename-keyboard.png b/.github/pr-screenshots/2174-drawer-compact/rename-keyboard.png
new file mode 100644
index 0000000000..7d3f02b6ab
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/rename-keyboard.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/renamed-after-restart.png b/.github/pr-screenshots/2174-drawer-compact/renamed-after-restart.png
new file mode 100644
index 0000000000..1a379ed759
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/renamed-after-restart.png differ
diff --git a/.github/pr-screenshots/2174-drawer-compact/renamed-cloud.png b/.github/pr-screenshots/2174-drawer-compact/renamed-cloud.png
new file mode 100644
index 0000000000..38b7707bee
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-compact/renamed-cloud.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/README.md b/.github/pr-screenshots/2174-drawer-tabs/README.md
new file mode 100644
index 0000000000..f6de575750
--- /dev/null
+++ b/.github/pr-screenshots/2174-drawer-tabs/README.md
@@ -0,0 +1,7 @@
+# Drawer tabs and Add screenshots
+
+Real shared iOS Simulator captures for #2175, 402 × 874, English. Before and after use the same retained QA computer and signed-out public UAT configuration; there are no credentials in these images. The service address shown is the public UAT endpoint.
+
+The before view combines both lists and three direct actions. The after views show Computers and Cloud separately and the new Add chooser. The chooser has no old modal counterpart: the old direct actions are its before state. `proof.json` binds source, integrated batch and native identities.
+
+Navigation checks reached the invitation and pairing pages without signing in, accepting an invitation or changing pairing. Maximum-text images show wrapping and a scrollable chooser; they do not establish a complete accessibility matrix. A nested native-modal route observation remains open in the proof; Android, physical phone and Human QA are unverified. The shared environment was restored after capture.
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-add-dark.png b/.github/pr-screenshots/2174-drawer-tabs/after-add-dark.png
new file mode 100644
index 0000000000..8b5b87487c
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-add-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-add-light.png b/.github/pr-screenshots/2174-drawer-tabs/after-add-light.png
new file mode 100644
index 0000000000..12f421ccec
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-add-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-add-max-light.png b/.github/pr-screenshots/2174-drawer-tabs/after-add-max-light.png
new file mode 100644
index 0000000000..c5a14d767c
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-add-max-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-add-max-scrolled.png b/.github/pr-screenshots/2174-drawer-tabs/after-add-max-scrolled.png
new file mode 100644
index 0000000000..204c4f2275
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-add-max-scrolled.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-cloud-dark.png b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-dark.png
new file mode 100644
index 0000000000..031e59ea52
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-cloud-light.png b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-light.png
new file mode 100644
index 0000000000..b1d25609a8
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-cloud-max-light.png b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-max-light.png
new file mode 100644
index 0000000000..f2483bfdf3
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-cloud-max-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-computers-dark.png b/.github/pr-screenshots/2174-drawer-tabs/after-computers-dark.png
new file mode 100644
index 0000000000..af12aee7ca
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-computers-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/after-computers-light.png b/.github/pr-screenshots/2174-drawer-tabs/after-computers-light.png
new file mode 100644
index 0000000000..7b4f7eb98c
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/after-computers-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/before-dark.png b/.github/pr-screenshots/2174-drawer-tabs/before-dark.png
new file mode 100644
index 0000000000..ea6ec03fbd
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/before-dark.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/before-light.png b/.github/pr-screenshots/2174-drawer-tabs/before-light.png
new file mode 100644
index 0000000000..3fd9da8713
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/before-light.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/join-destination.png b/.github/pr-screenshots/2174-drawer-tabs/join-destination.png
new file mode 100644
index 0000000000..8e15401aec
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/join-destination.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/pair-destination.png b/.github/pr-screenshots/2174-drawer-tabs/pair-destination.png
new file mode 100644
index 0000000000..f22f38e69e
Binary files /dev/null and b/.github/pr-screenshots/2174-drawer-tabs/pair-destination.png differ
diff --git a/.github/pr-screenshots/2174-drawer-tabs/proof.json b/.github/pr-screenshots/2174-drawer-tabs/proof.json
new file mode 100644
index 0000000000..3348450684
--- /dev/null
+++ b/.github/pr-screenshots/2174-drawer-tabs/proof.json
@@ -0,0 +1,39 @@
+{
+ "issue": 2174,
+ "pr": 2175,
+ "source": "6292fbf41123bc583f813116e349ac2788e3871a",
+ "baseSource": "e8472aab4a0e22c0404f99a44d6831dcfe5d0988",
+ "baseBatch": "bfbcc648fa789ed551c2249b1a392f9341128f04",
+ "afterBatch": "06c92fc78b71643f6cdd74d42d50913ea92d640a",
+ "main": "648c1202c44f5a5e82ce22596682934745705cef",
+ "native": "494a20f1eb4f0666a2c90bc3c758af4e7ff5f46b",
+ "viewport": "402x874",
+ "locale": "English",
+ "environment": "shared iOS 26 Simulator, retained QA computer and signed-out public UAT configuration",
+ "passed": [
+ "Computers/Cloud selected tabs and separate directories",
+ "one Add target with decorative icons",
+ "Add close returns to selected tab",
+ "Add opens existing invitation page",
+ "Add opens existing pairing page",
+ "normal text light/dark actual screenshots",
+ "maximum text wraps; Add opens, chooser scrolls, close works"
+ ],
+ "unverified": [
+ "Android native",
+ "physical phone",
+ "Human/VoiceOver",
+ "new login or invitation acceptance",
+ "full large-text matrix and all footer actions",
+ "Reduce Transparency toggle in this run"
+ ],
+ "observation": "After opening Pair (native modal) and following a cloud-services deep link, Workspaces did not visibly overlay the native modal. A cold reopen to the normal cloud-services route restored the working drawer. This route is not marked passed; regression origin not established.",
+ "cleanup": [
+ "driver closed",
+ "own Metro stopped; 8091 free",
+ "qa/batch-24 restored at main648c1202c",
+ "light/large restored",
+ "three original untracked QA documents retained",
+ "no account, pairing or Host writes; no Prod or merge"
+ ]
+}
diff --git a/apps/mobile/DESIGN.md b/apps/mobile/DESIGN.md
index e830cabebb..b6808892c6 100644
--- a/apps/mobile/DESIGN.md
+++ b/apps/mobile/DESIGN.md
@@ -32,22 +32,27 @@ References: [Apple toolbars](https://developer.apple.com/design/human-interface-
The shell is set by [ADR-0211](../../docs/adr/0211-the-mobile-app-has-two-tabs-with-settings-and-hosts-in-the-header.md): what the Human does most sits under the thumb, and what they do rarely sits in the header.
-- **Hosts are workspaces.** Each paired computer is a Host.
+- **One workspace drawer, two connection types.** Computers are paired Hosts; cloud workspaces belong to an explicitly trusted TOB instance. The existing left drawer is shared across both, with Computers and Cloud tabs. Cloud headers expose the same workspace switcher. A guest sees the organization initial/name and “invited channels only”; selecting it shows only current channel grants. Instance domains disambiguate identically named workspaces. The contextual join/pair action stays below the tabs; the directory scrolls between that header and a fixed footer.
+- **Computer workspaces.** Each paired computer is a Host.
- The app stays connected to every paired Host at once.
- The Chats list and every chat show one Host at a time. The Chats header has a Slack-style band in the accent colour (a muted accent in dark mode); the list sits on a panel with rounded top corners below it.
- - The **Host chip** at the top left shows the Host's initial tile, its name and ▾. Tapping it opens the **Host drawer**.
+ - The **Host chip** at the top left shows the Host's initial tile, its name and ▾. Tapping it opens the shared **workspace drawer**.
- **Customize computer** sets how each Host looks, kept on the phone per Host. It is a form sheet with a live preview of the band, opened from that Host's ⋯ menu in the Host drawer (or a long press on its row), so each paired Host is customized where it is listed.
- Name: a local name shown in the Host chip and the drawer instead of the Host's own name; clearing it goes back to the Host's name.
- Avatar: the initial tile (default), one of eight pixel portraits from the PersonaBot avatar generator, or a photo cropped square.
- Banner: the accent band (default), a solid colour (ten swatches or a typed hex), a two-stop gradient (six presets or typed stops), or a picture under a dark scrim.
- A custom colour goes through the accent band's steps, so dark mode mutes it the same way. The ink is white or near-black, whichever reads on every stop; a picture always takes white ink with a soft shadow.
-- **Host drawer** switches Hosts. It slides in from the left, covers about 85% of the width and dims the list behind it.
- - Like Slack's workspace drawer it has three parts on the `sheet` layer: a fixed "Computers" header, a scrolling list of Hosts, and a fixed footer with **Pair a new computer** (opens the pairing flow) and **Notifications** (opens notification settings), separated from the list by a divider.
+- **Workspace drawer** switches between paired Computers and Cloud organization workspaces. It slides in from the left, covers about 85% of the width and dims the list behind it.
+ - Rounded **Computers / Cloud** tabs are the first row above one scrolling list, with no duplicate title or close button. Opening the drawer selects the current route's tab; switching tabs only changes the list, never the active computer or cloud session. Android uses opaque raised chips; iOS uses the existing glass material with Reduce Transparency, older OS and large-text fallbacks. Tabs expose their selected state to accessibility.
+ - One contextual plus action sits directly below the tabs, above the scrolling list: Computers opens **Pair a new computer** directly; Cloud opens **Join by link or QR** directly. There is no mixed action chooser or stacked icon group. Organization creation and service configuration live under **Manage cloud services** on the Cloud tab; Notifications stays at the bottom of a fixed footer on both tabs; Cloud service management sits above it. Only the directory scrolls.
+ - A cloud service is an instance/address; its organization is a cloud workspace; a channel belongs to that organization. An invitation already identifies its service and scope: workspace membership or access to only one channel. The existing preview and acceptance flow keeps this distinction explicit. No separate server-selection step or new authorization store is introduced.
- Each Host row has its Computer avatar tile (the same tile as the Host chip; the current Host's tile has an `fg` ring at radius `lg`, concentric with the tile, as Slack marks the current workspace), its name, an online/offline/checking dot and label, then one **count chip** on the right (see Count chip below). It shows **Needs you** when anything waits for the Human, because that matters most; otherwise it shows **Unread**, the count of unread conversations. A ⋯ button at the end, or a long press on the row, opens the Host's menu: Customize (name, avatar and banner), Show this computer, and Unpair, which asks for confirmation.
- The Host chip opens it, and so does a swipe right from the left edge of a tab's root screen. The edge swipe is never attached inside a pushed chat, where it stays the system Back.
- - Tapping a Host switches to it and closes the drawer. Tapping the backdrop or swiping left closes it.
+ - Tapping a Host switches to it and closes the drawer. Tapping the backdrop, swiping left or using the accessibility escape gesture closes it. Opening focuses the selected tab.
- It takes screen-reader focus when it opens and returns focus to the Host chip when it closes. With Reduce Motion on it appears without sliding.
- Android Back closes it. Android draws the same drawer.
+ - Normal-size rows use a name and a smaller muted address, with one line each. Accessibility sizes may wrap. Status dots and accessible labels retain online/permission context; channel-only guests keep the invited-channel scope in their secondary label.
+ - Pairing accepts an optional local name using the existing computer appearance store; direct-link pairing can keep the original name, then rename in Customize computer. Cloud import accepts a display name; Manage cloud services can rename it later. These names affect only this phone and never change Host identity, organization names, trusted endpoints or sessions.
- **Tabs** are **Chats** and **Activity**. iOS uses `NativeTabs`; Android uses a custom floating bar through the JS tab navigator. There is no search tab.
- Activity counts the device's selected Human Attention types, defaulting to pending tool approvals only. Global Settings → Activity count supports multiple types: tool approvals, questions, private input, other pending actions, unread mentions/replies and other unread conversations. Selecting none keeps only the icon. Each unique pending item or unread conversation counts once; handled history is excluded. This device-local choice does not change push preferences or the app icon's push badge. Reads use the existing authenticated `humanAttention` API, including pagination, deduplication and early saturation at 100 for the 99+ display. The observer shares the current Inbox lifecycle, coalesces refreshes, retains same-selection cached counts on failure and discards stale responses after selection or Host changes. Android replaces the inactive icon; iOS uses its native badge.
- A chat is pushed above the tabs, so the tab bar hides while it is open.
@@ -331,3 +336,9 @@ Every editable field on iOS and Android belongs to a layout that responds to the
- Root `KeyboardRoot` supplies the keyboard controller on both native platforms. Modal windows and native form sheets need their own bounded layout; test their actual presented geometry. System resize alone and a keyboard-dismiss button do not establish visibility. Use native keyboard tracking instead of a timer or post-show measurement to reposition forms.
- Embedded WebView input keeps native viewport/scroll handling as its keyboard owner; verify the focused web field and web action too. Modern Android WebView resizes its visual viewport for IME overlap ([platform contract](https://developer.android.com/develop/ui/views/layout/webapps/understand-window-insets)). Verify older WebView providers explicitly and check for duplicate insets before adding a native resize wrapper. Remote Browser/Computer text entry must keep the remote caret visible in the resized stage.
- Verify each changed input path with the real soft keyboard: open it from a low field, type and move the caret through several lines, switch focus while it stays open, reveal and activate the form action, dismiss/reopen, and verify draft preservation after a failed save. Repeat default/intermediate/maximum system text in light/dark on each available platform. Record unavailable native cases explicitly; source tests and hidden or floating keyboards cannot prove soft-keyboard clearance.
+
+### Shared drawer title treatment
+
+The workspace drawer starts with its category tabs. `DrawerHeader` supplies generic bottom `Sheet` headers: a leading semantic title, optional decorative icon, and an independently accessible trailing close action. The iOS title uses a content-sized capsule with native glass on iOS 26; older iOS and Reduce Transparency use an opaque raised capsule. At accessibility text sizes, the title uses an opaque rounded rectangle so multiline text stays within a readable surface. Android keeps a plain left-aligned title on the opaque overlay. The close target is at least the platform touch minimum. Long titles wrap without changing the font scale; subtitles remain outside the capsule.
+
+Material is a decorative background only: text, focus, hit testing and navigation remain in the existing React Native tree. It must not capture gestures or become another accessibility element. Drawer body and grabber stay opaque and continuous. Header styling does not add dismissal controls to Settings or other native `SheetStack` routes; those retain their established handle/back policy until separately verified.
diff --git a/apps/mobile/app/(tabs)/_layout.android.tsx b/apps/mobile/app/(tabs)/_layout.android.tsx
index b08088284f..0901873ae8 100644
--- a/apps/mobile/app/(tabs)/_layout.android.tsx
+++ b/apps/mobile/app/(tabs)/_layout.android.tsx
@@ -12,7 +12,6 @@ import { useDesignTheme } from '../../src/ui/design/theme';
import { Text } from '../../src/ui/primitives/text';
import { edge, edgeFade, navigationBar, radius, space } from '../../src/ui/design/tokens';
import { FloatingTabInset } from '../../src/ui/shell/floating-tab-inset';
-import { HostDrawer } from '../../src/ui/shell/host-drawer';
import { ActivityIcon, ChatsIcon } from '../../src/ui/shell/icons';
import { NavigationButton } from '../../src/ui/shell/navigation-button.android';
import { NavigationIcon } from '../../src/ui/shell/navigation-icon.android';
@@ -146,7 +145,6 @@ function AndroidTabLayout() {
}}
/>
-
);
diff --git a/apps/mobile/app/(tabs)/_layout.tsx b/apps/mobile/app/(tabs)/_layout.tsx
index 6189b75a7a..589a8b2706 100644
--- a/apps/mobile/app/(tabs)/_layout.tsx
+++ b/apps/mobile/app/(tabs)/_layout.tsx
@@ -6,7 +6,6 @@ import { Platform, View } from 'react-native';
import { useActivityCount } from '../../src/host/native-activity-count';
import { badgeCount } from '../../src/ui/primitives/badge-count';
import { useDesignTheme, type DesignTheme } from '../../src/ui/design/theme';
-import { HostDrawer } from '../../src/ui/shell/host-drawer';
import { ScrollChromeProvider, useChromeHidden } from '../../src/ui/shell/scroll-chrome';
import { t } from '../../src/ui/strings';
@@ -51,7 +50,6 @@ function NativeTabLayout() {
) : null}
-
);
}
diff --git a/apps/mobile/app/(tabs)/_layout.web.tsx b/apps/mobile/app/(tabs)/_layout.web.tsx
index 6f7a4972b3..a810436bec 100644
--- a/apps/mobile/app/(tabs)/_layout.web.tsx
+++ b/apps/mobile/app/(tabs)/_layout.web.tsx
@@ -6,7 +6,6 @@ import { actionCount } from '../../src/host/inbox';
import { useHostSession, useInbox } from '../../src/host/host-session';
import { glassShadow, glassTint, useDesignTheme } from '../../src/ui/design/theme';
import { edge, glass, radius, space } from '../../src/ui/design/tokens';
-import { HostDrawer } from '../../src/ui/shell/host-drawer';
import { ActivityIcon, ChatsIcon } from '../../src/ui/shell/icons';
import { t } from '../../src/ui/strings';
@@ -55,7 +54,6 @@ export default function TabsLayout() {
}}
/>
-
);
}
diff --git a/apps/mobile/app/_layout.tsx b/apps/mobile/app/_layout.tsx
index 6ca3d26090..34c6b95562 100644
--- a/apps/mobile/app/_layout.tsx
+++ b/apps/mobile/app/_layout.tsx
@@ -5,6 +5,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { usePushRouting } from '../src/host/push-routing';
+import { HostDrawer, WorkspaceButton } from '../src/ui/shell/host-drawer';
import { StatisticsNotice } from '../src/ui/shell/statistics-notice';
import { KeyboardRoot } from '../src/ui/primitives/keyboard-frame';
import { AppDialogHost } from '../src/ui/primitives/app-dialog-host';
@@ -61,18 +62,36 @@ export default function RootLayout() {
/>
-
-
+ }}
+ />
+ }}
+ />
,
+ }}
/>
,
+ }}
+ />
+ }}
+ />
+ }}
/>
-
-
+
diff --git a/apps/mobile/app/pair.tsx b/apps/mobile/app/pair.tsx
index 9bc1de2ba9..0a5f6a8276 100644
--- a/apps/mobile/app/pair.tsx
+++ b/apps/mobile/app/pair.tsx
@@ -6,6 +6,9 @@ import { useCallback, useRef, useState } from 'react';
import { ActivityIndicator, Platform, Pressable, View } from 'react-native';
import { hostSession } from '../src/host/host-session';
+import { hostLooks } from '../src/host/native-host-look';
+import { HOST_NAME_MAX, hostLookName, withHostName } from '../src/ui/design/host-look';
+import { ConnectionNameField } from '../src/ui/primitives/connection-name-field';
import { nativeCarrier } from '../src/host/native-carrier';
import { pairHost, type PairingFailure } from '../src/host/pairing';
import { mobileTelemetry } from '../src/telemetry/native';
@@ -25,6 +28,7 @@ export default function PairScreen() {
const params = useLocalSearchParams<{ p?: string }>();
const [permission, requestPermission] = useCameraPermissions();
const [link, setLink] = useState('');
+ const [connectionName, setConnectionName] = useState('');
const [busy, setBusy] = useState(false);
const [focused, setFocused] = useState(false);
const [error, setError] = useState();
@@ -46,7 +50,11 @@ export default function PairScreen() {
method,
deviceName: deviceName(),
carrier: nativeCarrier(),
- save: hostSession.pair,
+ save: async (host) => {
+ await hostSession.pair(host);
+ const name = hostLookName(connectionName);
+ if (name) hostLooks.set(host.hostId, withHostName(hostLooks.look(host.hostId), name));
+ },
telemetry: mobileTelemetry,
signal: operation.signal,
});
@@ -85,6 +93,12 @@ export default function PairScreen() {
>