Skip to content

feat(ios): pocket ios — transitional ios-dev target, NativeScript shell, play on the simulator - #256

Open
NathanWalker wants to merge 2 commits into
pocket-stack:mainfrom
NathanWalker:feat/ios-cli
Open

feat(ios): pocket ios — transitional ios-dev target, NativeScript shell, play on the simulator#256
NathanWalker wants to merge 2 commits into
pocket-stack:mainfrom
NathanWalker:feat/ios-cli

Conversation

@NathanWalker

@NathanWalker NathanWalker commented Aug 9, 2026

Copy link
Copy Markdown

Stacked on #255 (feat/apple-host) — the first six commits here are that PR; review the last six. Rebases onto main once #255 merges.

What

The follow-up #255's description names: a dev-profile target id and CLI integration for the Apple host. pocket play ios nsengine now builds a guest against a resolved plan, stages it into a committed NativeScript shell, and launches it on an arm64 iOS simulator.

  • tools/ios-profile.ts — transitional ios-dev profile (hostAbi 7, platform ios, form embedded, 480×272, raster density 1–4 as a registry input, capabilities input.touch + text.glyphs.baked), following the tools/iphone2g-profile.ts pattern: out of POCKET_TARGETS until device-level acceptance.
  • tools/ios.tsdoctor / setup / devices / native / build / stage / play, mirroring the symbian shape. pocket play ios <app> delegates here. Doctor is green-path only on Apple Silicon (both native artifacts ship arm64 slices only); Rust is needed only for --rebuild-native — the published plugin carries a prebuilt xcframework. CocoaPods is verifiably not required.
  • hosts/apple/ns-shell/ — a minimal committed NativeScript app, plan-driven (viewport, density, and guest mode come from the staged plan + current.json), answering the ns.ping service channel. Runs in place for incremental rebuilds.
  • apps/nsengine/pocket.json — the demo's first committed manifest (the root-template fallback demands input.buttons, which this surface cannot advertise).
  • Identity fix in PocketSurfaceView.m — plan-built bundles bake __POCKET_TARGET__/__POCKET_HOST_ABI__ and assertNativeHostContract refuses hosts that don't publish the pair. The surface now applies pocket_apple_set_identity("ios-dev", 7) at init (the C ABI existed; nothing called it). External-guest hosts publish the same pair on the ui namespace they mount (@nativescript/pocketjs). Without this, the very first plan-built run fails at mount.
  • Tests: tests/ios-profile.test.ts (profile privacy, exact plan, density selection, refusals, an identity source-text guard, checkAppTypes), the nsengine admission-matrix row, CLI dispatch cases.
  • Docs: docs/APPLE.md runbook, docs/STRUCTURE.md, README platform-evidence row.

Validation (iOS 26.5 simulator, Apple Silicon)

  • pocket ios doctor fully green; pocket play ios nsengine --density=4 from a wiped shell (cold ~47 s) and warm (~16 s): nsengine renders, spinner animates, and the guest→host stat reads pong 1 with no interaction — the whole runEffect → svcSend → onEffect → post → svcPoll → deliver loop through the shell's handler.
  • --external-guest: platform stat reads iOS 26.5 via NativeScript (guest code reading UIDevice through the metadata bindings). Same bundle, both modes.
  • bun tools/test.ts unit stage green including the new file.

Notes for review

  • The shell's package.json pins @nativescript/pocketjs and @nativescript/ios-quickjs by their npm names; both publish imminently. Validation above used --plugin-path/--runtime-tgz (documented in docs/APPLE.md), which override for the install and restore the committed template.
  • No POCKET_TARGETS change and no schema change — promotion is a follow-up gated on a device acceptance suite, per the transitional-profile convention.
  • ProMotion follow-up: PocketSurfaceView pins its CADisplayLink to 60 because the core advances in exact 1/60 s steps (the determinism/baked-motion contract). Driving iPhone ProMotion displays at 120 needs the step rate to become a target-profile property (durations resolved against the profile tick, CADisableMinimumFrameDurationOnPhone, and an adaptive preferredFrameRateRange). Note the iOS simulator reports maximumFramesPerSecond = 60 regardless, so 120 is device-only evidence.

🤖 Generated with Claude Code

NathanWalker and others added 2 commits August 9, 2026 11:51
…xternal-guest mode

A new engine/apple workspace member packaging modern iOS as a PocketJS
host. The composition mirrors hosts/pocketbook: one pocket_mod::Guest
realm, one pocket_ui_surface::UiSurface, and pocketjs_core::raster driven
incrementally through a DamageTracker, exposed behind a small C ABI.

- engine/apple/src/lib.rs — guest-owning mode: create/load_pak/eval_bundle/
  frame/render/hit_test_bounds/destroy, plus an effect channel over the
  ui.svc* ops (set_effect_callback drains guest svcSend lines during frame;
  post_event queues lines for the guest's next svcPoll).
- engine/apple/src/core_host.rs — external-guest mode: pocket_apple_core_*
  owns only the core, pak feed, raster pipeline, and svc queues, for hosts
  whose JS engine lives elsewhere (demonstrated with the NativeScript
  runtime evaluating the guest bundle in its own context).
- engine/apple/apple/PocketSurfaceView.{h,m} — CADisplayLink capped at
  60 Hz, latched touch contacts (a down+up between two ticks still reaches
  the guest as one present frame then a release), aspect-fit inverse touch
  mapping, damage-gated compositing of the ARGB32 framebuffer.
- engine/apple/build-xcframework.sh — clang-linked dynamic framework per
  slice (device arm64 + simulator arm64), no Xcode project.
- pocket-ui-surface additionally mounts hitTestBounds (spec op 42), the
  touch-path hit authority the gesture layer prefers over the ink-claiming
  hitTest.
- apps/nsengine — reference guest: an effect driver over svcSend, a
  per-frame poll pump, a focusable pressable button, and a platform-reach
  probe that distinguishes a sidecar realm from an embedding-runtime host.

rquickjs uses its bindgen feature: no pregenerated bindings exist for
aarch64-apple-ios targets. Validation: the render_hero example drives the
ABI end to end — 180 frames of apps/hero/main.tsx at 480x272 density 2
render non-blank and byte-identical across two independent instances.
Build guests with --density matching the surface density (glyphs bake at
build time; density 4 supersamples cleanly on 3x screens).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ll, play on the simulator

The CLI follow-up feat/apple-host's description names: a dev-profile target
id and a first-class run flow for the Apple host. `pocket play ios nsengine`
builds a guest from a resolved plan, stages it into a committed NativeScript
shell, and launches it on an arm64 iOS simulator.

tools/ios-profile.ts registers the transitional ios-dev profile (hostAbi 7,
platform ios, form embedded, fixed 480x272, raster density 1..4 as a registry
input, capabilities input.touch + text.glyphs.baked) following the
iphone2g-profile pattern: out of POCKET_TARGETS until device acceptance.
apps/nsengine gains a committed manifest — the root-template fallback demands
input.buttons, which this surface cannot advertise.

PocketSurfaceView now applies pocket_apple_set_identity("ios-dev", 7) at
init: plan-built bundles bake __POCKET_TARGET__/__POCKET_HOST_ABI__ and
assertNativeHostContract refuses hosts that publish nothing; the C ABI
existed but nothing called it. External-guest hosts (@nativescript/pocketjs)
mount the same pair on their ui namespace, and a source-text test guards the
agreement.

tools/ios.ts owns the flow — doctor/setup (symbian shape; Rust targets are
the only mutation, and only --rebuild-native needs them since the published
plugin ships a prebuilt PocketApple.xcframework), devices, native, build,
stage, and play (admissible-simulator pick, simctl boot, ns run ios
--device <udid> --no-hmr --justlaunch). --plugin-path/--runtime-tgz point the
shell at local builds for pre-publish validation and restore the committed
template afterwards. pocket play ios delegates here; bin.mjs gains the ios
passthrough.

hosts/apple/ns-shell is the committed shell: plan-driven (viewport, density,
and guest mode read from the staged plan + current.json), answers the ns.ping
service channel, and runs in place so repeat runs rebuild incrementally
(~47 s cold, ~16 s warm on Apple Silicon). Its tsconfig pins
@nativescript/core paths so the plugin's typings resolve from a file:
symlink.

Validated on the iOS 26.5 simulator in both guest modes: unprompted ns.ping
round trip renders pong 1, and --external-guest reads
UIDevice.currentDevice.systemVersion from guest code. Unit stage green
including tests/ios-profile.test.ts, the nsengine admission-matrix row, and
the CLI dispatch cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.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.

1 participant