Verify a native app end to end
Press the real UI, explain its render, and prove Query cache delivery on iOS.
Demo: examples/expo-demo
Use the UI driver to check visible state and Genie to inspect React causes and Query observer delivery. This case records an Expo Go run on an iPhone simulator; it does not establish behavior on every device or React Native version.
Start the demo
Terminal 1:
pnpm --filter @genie-react/expo-demo hubTerminal 2:
EXPO_PUBLIC_GENIE_URL=ws://127.0.0.1:4390/__genie/ws \
pnpm --filter @genie-react/expo-demo start -- --clear --port 8081Terminal 3:
agent-device open 'Expo Go' exp://127.0.0.1:8081 \
--relaunch \
--session genie-expo-check \
--platform ios
agent-device wait text 'Native integration demo' 30000 \
--session genie-expo-check
export GENIE_BRIDGE_URL=ws://127.0.0.1:4390/__genie/ws
genie() {
pnpm --filter @genie-react/expo-demo exec genie-react "$@"
}
status_json="$(genie status --json)"
export GENIE_SESSION="$(
jq -er '
[
.sessions[]
| select(
.ready == true and
.staleMs == null and
.app.name == "Expo demo"
)
] as $matches
| if ($matches | length) == 1
then $matches[0].sessionId
else error("expected exactly one ready Expo demo session")
end
' <<<"$status_json"
)"
genie status --jsonCheck these selected status fields before continuing:
{
"schemaVersion": "1.0",
"connected": true,
"ready": true,
"app": { "name": "Expo demo" }
}Inspect domains and discover tools react and tools query in your running version. Tool counts
and renderer versions change. Native tools can be advertised while individual browser-only
operations remain unavailable; check each result's support or coverage fields. For multi-domain
timing, the timeline workflow explains native lane limits.
Press once and explain the render
Start a clean observation with enough budget for the native tree:
genie call react_clear_renders \
'{
"components":["App"],
"budget":{
"fiberLimit":5000,
"operationLimit":200000,
"timeLimitMs":50,
"targetOperationReserve":50000,
"targetTimeReserveMs":25,
"adaptive":false
}
}' --json
agent-device get text 'id="counter-value"' \
--session genie-expo-check --json
agent-device press 'id="increment-button"' \
--session genie-expo-check --json
agent-device wait text '1' 5000 --session genie-expo-check
agent-device get text 'id="counter-value"' \
--session genie-expo-check --jsonSelected visible result:
{
"success": true,
"data": {
"text": "1"
}
}Read the cause:
genie call react_render_causes \
'{"component":"App","limit":20,"appOnly":false}' --jsonSelected live output:
{
"observation": { "id": "observation:1" },
"events": [
{
"renderEventId": "render:5:42",
"componentName": "App",
"causes": [
{
"kind": "state",
"evidence": "exact",
"name": "state[0]",
"before": 0,
"after": 1,
"hook": { "index": 0, "stateIndex": 0, "kind": "state" }
}
],
"necessity": "necessary",
"assessment": {
"behaviorEvidence": {
"subtreeHostMutations": { "status": "observed", "count": 20 }
},
"optimizationSafety": "not-proven-safe"
}
}
],
"coverage": {
"complete": false,
"inputAttributionComplete": false,
"droppedRenderEvents": 0
}
}The agent proved the visible 0 → 1 change and tied it to one exact hook update. The render changed
native host output, so removing it would be wrong. The incomplete global coverage also prevents a
broader performance claim from this sample.
Prove Query cache to native UI
Write one exact cache value, then check both the live observer and visible text:
agent-device scroll down --pixels 700 --session genie-expo-check
genie call query_set_data \
'{"queryKey":["expo-tool-audit"],"data":{"label":"agent-proof","revision":2}}' \
--json
genie call devtools_wait \
'{"condition":"react-quiet","quietMs":300,"timeoutMs":5000}' \
--json --fail-on-result-error
agent-device wait text 'query:success:agent-proof:2' 5000 \
--session genie-expo-check
genie call query_get \
'{"queryKey":["expo-tool-audit"]}' --json
agent-device get text 'id="fixture-query-value"' \
--session genie-expo-check --jsonSelected live Query output:
{
"queryHash": "[\"expo-tool-audit\"]",
"status": "success",
"fetchStatus": "idle",
"data": { "label": "agent-proof", "revision": 2 },
"observerCount": 1,
"observers": [
{
"notificationPolicy": {
"mode": "auto-tracked",
"fields": ["status", "data"]
},
"deliveryEvidence": "public-track-prop-observed",
"subscriber": {
"componentName": "QueryFixture",
"renderEventId": "render:7:170"
}
}
]
}Selected visible result:
{
"success": true,
"data": {
"text": "query:success:agent-proof:2"
}
}The cache value, observer fields, subscriber component, render event, and native text agree. This
proves cache-to-UI delivery instead of stopping at query_set_data: ok.
Restore and close
agent-device press 'id="fixture-query-restore"' \
--session genie-expo-check --json
agent-device wait text 'query:success:server-value:1' 5000 \
--session genie-expo-check
agent-device get text 'id="fixture-query-value"' \
--session genie-expo-check --json
agent-device close --session genie-expo-check
unset GENIE_BRIDGE_URL GENIE_SESSION status_json
unset -f genieThe final text must be query:success:server-value:1 before the agent closes the session.