Skip to content

qa(device-proof): wire apps/mobile harness for PR #8 checklist reproducibility - #29

Open
obvious-autobuild[bot] wants to merge 4 commits into
masterfrom
qa/ios-device-proof-harness
Open

obvious-autobuild[bot] wants to merge 4 commits into
masterfrom
qa/ios-device-proof-harness

Conversation

@obvious-autobuild

Copy link
Copy Markdown
Contributor

Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)

Why

The PR #8 spike (voice dictation + photo capture for iOS 26) ends with a 10-item device-proof checklist that can only be closed on Apple hardware — but this workspace cannot run that verification itself: macOS sandboxes are not enabled (obvious computers provisionSandbox --platform=macos → FORBIDDEN: macos sandboxes are not enabled for this workspace), and no Mac is paired or registered. Until now, apps/mobile was a bare Expo 57 shell with no capture dependencies, so the eventual simulator/device run would have required rebuilding the wiring from the spike's notes by hand. This PR makes that run mechanical: correct pins, correct native config, and a debug screen that exercises every checklist item and logs timestamped, class-labeled evidence rows on-screen.

What

1. Spike dependency pins in apps/mobile (exact versions from the spike README's pinned set): expo-audio 57.0.5, expo-image-picker 57.0.18, expo-file-system 57.0.7, @react-native-ai/apple 0.12.0. newArchEnabled: true retained (TurboModule requirement).

2. Expo config wiring per the README's "Expo config wiring": expo-audio plugin with its microphonePermission string, expo-image-picker plugin with its photosPermission string, ios.infoPlist.NSSpeechRecognitionUsageDescription with the README string.

3. Spike ports importable from the app — workspace import (preferred path). spikes/voice-photo is added to pnpm-workspace.yaml and apps/mobile depends on @journal/spike-voice-photo: workspace:* (metro.config.js already watches the workspace root). Chosen over copying src/transcription/* into the app because it keeps a single source of truth for the transcriber port and its error taxonomy — a copy would fork the tested port and silently drift. The spike's own npm lockfile/vitest setup is untouched: its standalone gates still run inside spikes/voice-photo unchanged (38/38 below).

4. DeviceProof debug screen (reachable via a visible "Device proof checklist" button on the home screen; the app now also boots when EXPO_PUBLIC_CONVEX_URL is unset, running the harness on the fake transport). It runs and logs timestamped on-screen evidence rows for:

  • (a) TurboModule probe — __apple__llm__transcribe__ global present at runtime
  • (b) isAvailable(locale) matrix over en-US, en-GB, es-MX, es-US, fr-FR, de-DE + one deliberately bogus locale to capture the failure path
  • (c) prepare('en-US') with errors mapped to the spike's taxonomy codes
  • (d) ~5s HIGH_QUALITY recording (file URI, byte size, mimeType) then transcribeToText via the spike adapter — unavailable/null adapter and isAvailable=false states degrade to evidence rows, no crashes
  • (e) expo-image-picker photo pick with size and type
  • (f) the spike's 3-request Convex photo upload against the real dev deployment when EXPO_PUBLIC_CONVEX_URL is set, else against a deterministic in-memory fake — every row labeled with which transport it used

Every evidence row carries a class label: simulator / fake / hardware-only-blocked. Synthetic family data only ("Milo ate oatmeal at 8am", invented author). No credentials anywhere: no URLs or keys committed, .env stays gitignored (.env.example comment extensions only).

5. Type-environment fix (app-local, no spike changes): React Native's global AbortSignal class (src/types/globals.d.ts) satisfies @types/node's typeof globalThis extends { onmessage: any } conditional, which empties node's AbortSignal augmentation and drops reason from the merged global type — the spike's port reads signal.reason for cancellation causes. apps/mobile/src/types/abort-signal.d.ts restores it via interface merging.

How to Review

  • apps/mobile/src/device-proof/ — DeviceProofScreen.tsx (UI + run controls), checks.ts (the six checks), evidence.ts (row model + classes), convexTransport.ts / fakeTransport.ts (the port's two transports). convexTransport.ts speaks the documented Convex HTTP API over plain fetch (the photoStorage module is not deployed to the dev deployment yet, so step 1/3 failures are an expected, honestly-reported outcome until that module is ported into backend/convex).
  • The upload body is sent as ArrayBuffer (photo.bytes.slice().buffer) — accepted by RN networking, Expo's winter fetch, and Convex's upload endpoint alike; new Blob([bytes]) does not typecheck under RN's global types.
  • expo prebuild -p ios output (ios/) is gitignored per Expo's continuous-native-generation convention (apps/mobile/ios/, apps/mobile/android/ in .gitignore); prebuild also synced app.json (explicit bundleIdentifier) and switched the run scripts to expo run:* — both accepted as-is in the second commit.
  • Intentionally out of scope: the actual simulator/device run (needs macOS — see Why), a streaming-transcription implementation (checklist item 10 is out of scope for the spike's batch-only MVP), and porting photoStorage into backend/convex.

Gate outputs (Node v20.20.2, pnpm 10.34.5 via Corepack, Bun 1.3.14)

Tested head SHA: 837f132b372063994386e67f9336bec44e80e81e (all outputs below ran against this exact commit after merging current master).

  1. pnpm install clean; pnpm install --frozen-lockfile (CI parity): Done in 1.8s, no errors.

  2. pnpm typecheck (turbo, all packages incl. spike + mobile): Tasks: 10 successful, 10 total.

  3. Spike vitest: Test Files 3 passed (3), Tests 38 passed (38).

  4. pnpm test: 6 successful, 6 total; pnpm build: 5 successful, 5 total; bun test ./security: 17 pass, 0 fail; evaluation corpus: Summary: 6/6 fixtures passed; negative control (--adapter=./src/example/broken-adapter.ts --expect-failure): Summary: 1/6 fixtures passed, exit 0 (expected failure confirmed).

  5. npx expo prebuild -p ios --no-install succeeds on Linux. Generated ios/SharedChildJournal/Info.plist contains (quoted):

    • <key>NSMicrophoneUsageDescription</key> / <string>Allow Shared Child Journal to access your microphone so you can dictate journal entries.</string>
    • <key>NSPhotoLibraryUsageDescription</key> / <string>Allow Shared Child Journal to attach pictures to journal entries.</string>
    • <key>NSSpeechRecognitionUsageDescription</key> / <string>Shared Child Journal transcribes your dictated entries on-device.</string>

    Expo's Podfile does not name pods literally — it delegates to autolinking at pod install time (use_native_modules!(config_command)). Running that exact command (node --eval "require('expo/bin/autolinking')" expo-modules-autolinking react-native-config --json --platform ios) resolves @react-native-ai/apple v0.12.0 with podspecPath: node_modules/@react-native-ai/apple/AppleLLM.podspec — the pod links when pod install runs on a Mac. Git status clean at the end; ios/ output untracked.

Checklist mapping (PR #8 items 1–10 → what this harness exercises)

# Checklist item This harness Still hardware-only
1 iOS 26 runtime / SpeechAnalyzer behavior Runs prepare/isAvailable and reports Actual on-device model behavior
2 Custom dev-client build, __apple__llm__transcribe__ present Probe (a) checks the global at runtime; pod verified via autolinking + prebuild The dev-client build itself
3 Microphone permission prompt Plugin wiring + generated NSMicrophoneUsageDescription verified The dialog on first record
4 Photo permission prompt Plugin wiring + generated NSPhotoLibraryUsageDescription verified The dialog on first pick
5 Speech recognition authorization NSSpeechRecognitionUsageDescription generated Whether SpeechAnalyzer triggers the prompt
6 prepare(locale) asset download Check (c) calls prepare, maps failures to taxonomy codes Real asset download/network conditions
7 Locale availability Check (b) runs the 6-locale matrix + bogus locale Per-device true/false answers
8 Recording hardware path Check (d) records ~5s HIGH_QUALITY, logs URI/size/type, transcribes Output format, metering, byte stream
9 Live Convex deployment Check (f) runs the 3-request flow (real transport when URL set, else labeled fake) Upload URL expiry and 2-min timeout observation
10 Live streaming transcription Not claimed (batch-only MVP) Everything

Per this task's brief: do not merge — this PR is the wiring that makes the hardware run mechanical; the run happens on a Mac outside this workspace and its evidence should land against this branch.

🔗 Obvious Project · 🧵 Obvious Thread

ObviousApp and others added 4 commits September 17, 2026 18:51
apps/mobile becomes the reproducible vehicle for the 10-item device-proof
checklist (spikes/voice-photo/README.md): spike deps at the README pins,
Expo config wiring for microphone/photo/speech permissions, the spike's
transcription + photo-storage ports imported via the pnpm workspace, and a
DeviceProof debug screen that logs timestamped, class-labeled evidence rows
for every checklist item (TurboModule probe, locale availability matrix,
prepare taxonomy, ~5s recording + transcribeToText, photo pick, and the
3-request Convex upload with a deterministic fake transport fallback).

Type-environment notes: React Native's global AbortSignal class satisfies
@types/node's onmessage check, dropping `reason` from the merged global —
restored via a local interface augmentation; the upload body is sent as
ArrayBuffer, accepted by RN networking, Expo winter fetch, and Convex alike.

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
…n scripts)

`npx expo prebuild -p ios --no-install` syncs app config in bare workflow:
adds the explicit ios.bundleIdentifier default and switches the android/ios
scripts to `expo run:*`. Generated ios/ output stays untracked per the
gitignore added in the parent commit.

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants