Files
orca/mobile/rpc-foundation
Jinwoo Hong 2fccacadbe test(mobile): mount screens, declared device stores and declared OS state in the RPC recorder (#20884)
* test(mobile): mount screens, declared device stores and declared OS state in the RPC recorder

Three recorder capabilities, each with a test, plus four holdout sites recorded against main's
product code to prove them. No product source changes.

- JSX compiles through the automatic runtime, which is what product sources use; the classic
  `React.createElement` emit threw `React is not defined` on the first render of every screen.
- An unlisted package answers `__esModule` as undefined, so a default import loads and the refusal
  defers to the first real member read instead of killing the module at load.
- The substitutes table gains the inert view packages a screen needs, split into
  `screen-native-substitutes.ts` behind the same rule: only what a screen reads is listed.
- A scenario may declare `deviceStore` and `deviceState.notificationTray`. Reads resolve the
  declaration or null and never a write; writes are recorded as effects. Undeclared is unchanged.
- `screenMount` mounts a component with a crash boundary, so a reply partition that takes a screen
  down is a recording rather than a suite failure.

Every pre-existing golden re-records byte-identical except `recorderSha256`, recorded from a
detached worktree at the pinned baseline.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): declare the crash boundary's state type instead of asserting it

The changed-code casting gate counts `null as string | null` as a type assertion. Re-records every
golden from the pinned baseline, because the edit moves `recorderSha256`.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): refuse an unlisted native package by the member a mount reads

Both emitted interop helpers short-circuit on `__esModule`, so answering `true`
hands the refusing trap back unwrapped to `__importDefault` and `__importStar`.
All three import forms now load the importer and throw the named refusal at the
first member read, instead of a namespace import silently yielding `undefined`
and failing later at the call.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): run a task deferred past interactions instead of dropping it

An inert `runAfterInteractions` swallows whatever send the screen deferred, and
the recording then claims the screen sends nothing. It runs the task on a
microtask and returns the RN-shaped handle, so a cancel before the task runs
still prevents it. `Alert.alert` stays inert.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record every golden for the two recorder engine changes

Only `recorderSha256` moves, from c58067de to a59b30e7. Recorded from a detached
worktree at the pinned baseline c6a7216984 with this branch's recorder and
scenario file overlaid, so no golden body is a claim about migrated source.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): stop exporting three recorder symbols with no importer

`projectScreenTree`, `DeviceEffect` and `DeclaredNotification` are only used by
the file that declares them.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record every golden for the dropped exports

Only `recorderSha256` moves, from a59b30e7 to 4df43aef. Recorded from a detached
worktree at the pinned baseline c6a7216984 with this branch's recorder and
scenario file overlaid.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): list only the native members a recording is known to read

Instrumenting `partialNativeModule` across all 522 recordings found 68 declared
members and 23 read. The unread ones are gone: the whole react-native-svg,
react-native-reanimated and react-native-gesture-handler entries, two expo-router
hooks, and twelve react-native members including `InteractionManager` and
`Alert`. A member listed before a recording reads it turns a refusal that would
have forced a decision into a silent stand-in, which is how an inert scheduler
swallows a deferred send. Wave 3 re-adds each one with the recording that reads
it.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): order each effect against the sends with a send count

Sender and effects are two independent lists, so a send reordered ahead of a
device write moved neither. Scheduling the codex journal write on a timer instead
of awaiting it left all 520 goldens byte-identical; with `sent` it moves two.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): report a boundary crash through the effect sink

A hook mount projects the hook's own value and never a crash, so an adapter that
never reads `crash()` recorded a screen that quietly stopped rendering. The
boundary now reports to the effect sink as well, which forces a cleanup
checkpoint and puts the crash in the golden with no adapter cooperation. Two
reply-matrix goldens gain the effect. Also folds the two near-identical tree
walkers into one `walk(tree, visit)`.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): state the __esModule interop rule once

It was restated at four sites with four different answers. The canonical
paragraph is in `native-module-traps.ts`; the loader and the declared tray point
at it in a line. The loader comment also said the refusal names the member the
product read, which for a default import is `.default` instead.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record for the send ordinal, the crash effect and the pruned table

`recorderSha256` moves on all 520. Of the 509 pre-existing goldens, 260 are
header-only and 249 gain `sent` on their effects and nothing else; two of this
branch's own reply-matrix goldens also gain a `screen.crash` effect. Recorded
from a detached worktree at the pinned baseline c6a7216984.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* fix(lint): drop Reflect.get from the native traps

main adopted anti-slop's `no-reflect-get` in #20874 and fixed the copy that lived
in `native-mounting-substitutes.ts`. This branch moved both traps into
`native-module-traps.ts`, so the rule lands here instead and the static-analysis
gate is red without the same narrowing.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record for the Reflect.get narrowing

`recorderSha256` only, 8ca81bc6 to 0ddc0dc4. No golden body moves, which is the
claim the narrowing makes: the trap reads the same member it always did.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb

* test(mobile): re-record every golden at main's new baseline pin

One record from a detached worktree at `e7206f62a8` — main's pin since #20895 —
with this branch's `rpc-recording/`, `pilot-scenarios.json` and
`scripts/rpc-recording.mts` laid over it, so the goldens are recorded against
main's product tree rather than a branch that edits the engine.

All 520 goldens now share one `recorderSha256`
(`0ddc0dc472e2efbe58004c6dfaf5360e368fed321e40dfe15c11a9ad1817dea4`) and one
`baseline`. Against main, 262 move on the header alone and 247 also gain the
`sent` ordinal on an effect, which is the whole of the body change; the nine
goldens #20895 re-digested carry a byte-identical body, and the six the new
worktree catalog adapter (#20873) recorded keep main's observation.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-15 18:52:47 -04:00
..