The hybrid WebView required a recent native-observed touch before a bridge
capability could run. A scroll armed the window, so it gated nothing an
attacker on the first-party page could not already reach, and no peer hybrid
framework (Capacitor, Cordova, RN WebView) gates bridge calls this way.
Removes the gesture module, its React authority hook, the shared requirement
and its census, and all 20 gate sites: native.alert, clipboard write, external
link open, terminal text-scale and custom-key updates, dictation start and
model management, account select and reset-credit consume, agent-history
resume, terminal clipboard paste and image attach, navigation reconnect,
removeHost and terminal-settings route, and both workspace-creation writes.
Each operation now just runs.
The AppState foreground reporting the gesture hook also carried moves to
use-mobile-web-app-foreground-authority. permission_required stays in the
bridge error enum: it parses inbound responses, so narrowing it would break a
new page paired with an older shell.
Drops the G3 gesture-window e2e probe and renames its runner to
run-hosted-ios-webview-app-bound-probe.mjs, which keeps the app-bound probe.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
No WKAppBoundDomains is declared, and an A/B native rebuild showed identical
behavior with and without the flag: the shell's navigation delegate refuses
external navigation either way. The probe script stays as the regression check.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
#10179 ("bound OOM-prone accumulators") was reverted on main by #10255, but
the hybrid WebView rewrite carried parts of it forward wherever the revert
could not apply cleanly. Remove those, keeping the hybrid work that had
merely adopted them.
Deleted the bounded mobile RPC queues, ledgers, budgets and JSON admission;
the bounded emulator-script readers; the transcript record buffer and
retention window; and the bounded relay/SSH directory and markdown-document
listing modules. Restored main's connection log store, E2EE v2 channel,
daemon spawner and relay error codes, and reverted the transcript readers'
page and drain budgets.
Kept, and rewired onto main's modules: the terminal binary frame path, the
hosted privacy redactions, the transcript file-source plumbing, and the
chunked file and terminal-artifact reads the hosted client depends on.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Two security-review questions about the hybrid iOS shell needed measurement
rather than reading: which native touches arm the 5s user-gesture window that
privileged page requests spend, and whether limitsNavigationsToAppBoundDomains
does anything while app.json declares no WKAppBoundDomains key.
The gesture probe drives clipboardWrite, the cheapest gesture-gated mutation,
through the page bridge after each candidate arming action and records whether
the shell granted or denied it. The app-bound probe asks the page to navigate
to an external https origin and records who refuses it: the shell's navigation
delegate raises a native warning banner, while an app-bound refusal would fail
the provisional navigation inside WebKit with no delegate decision.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Hosted iOS security e2e (three branch-only harness bugs):
- `simctl privacy grant` terminates the app; the relaunch cold-resumes
straight onto the session route, so skip workspace-row activation when
the hybrid handoff already landed on /session/. Activation failures now
name the document href.
- Uploaded-path regex fused two abutting orca-paste-*.png paths; use a
lazy segment quantifier.
- Photos orders by capture date, which addmedia takes from file birth
time; stage a freshly written fixture copy (copyFile clones birth time
on APFS) so "last Photo" is the fixture.
Root tests:
- Allowlist walker skips -test-fakes / -test-fixture(s) / .test-support
sources, which are not mobile call sites.
- Drop the height-only OSC-8 test: main's #17759 clears restored ranges
on any dimension change and pins it.
- Drop serve-update-handoff-startup-order.test.ts: main pins the same
order against main-process-preflight.ts.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
native.alert is now gesture-gated, so the simulator journey taps the page
before posting the probe. The task project request client sat one line over
its max-lines cap; its project echo check moves to its own module.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as
a re-export barrel so the import sites did not have to change. This removes
the barrel: every consumer now imports from the module that actually declares
the type, and `src/shared/types.ts` is deleted.
Barrels hide where a type lives, make every consumer look like it depends on
the whole domain, and let an unrelated edit invalidate a module that ~2,000
files transitively import.
2,323 import declarations across 2,321 files. Rewritten mechanically: each
specifier was resolved to an absolute path via the TypeScript AST and
recomputed, rather than string-substituted, so alias forms (`@/../../shared/
types`) and per-specifier `type` modifiers survive.
Four cases the mechanical pass had to handle, each found by a gate rather than
by reading the diff:
- Modules inside `src/shared` import the barrel as `./types`, not
`shared/types`. A pre-filter on the latter string skipped 176 of them and
left imports dangling at a deleted file, which surfaced as confusing
`Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>`
errors rather than "module not found".
- The barrel RENAMED one type on the way through
(`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name
in the owning module has to be re-aliased at each consumer.
- Three test files put `;(globalThis as ...)` on the line after the import.
TypeScript parses that `;` as the import statement's terminator, so
replacing through `statement.getEnd()` deletes it and breaks ASI. The
rewrite now stops at the module specifier.
- A file that already imported directly from a module got a SECOND import
from it, because the barrel re-exported those same names — which trips
`import/no-duplicates` under `--deny-warnings`. A post-pass merges
declarations sharing a specifier and type-only-ness; the `import type` plus
`import` pair from one module is left alone, since that form is allowed.
Splitting one barrel import into several genuinely adds lines, which pushed
`terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character
import must wrap, and neither local type collapses onto one line (101 and 116
characters). Rather than contort a type declaration to fit a line budget,
`collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` —
they are pure structural operations on the layout tree and independent of PTY
ownership. `visible-worktrees.ts` similarly loses its own mini-barrel
re-export of `isDefaultBranchWorkspace`, with the four real consumers
repointed at the declaring module. No `max-lines` bypass added.
Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches); the full
`pnpm lint` green, not just bare oxlint — the narrower local check is what let
the duplicate imports reach CI; max-lines ratchet OK at 344.