Skip to content

iOS preview: shell-owns-agent flash-free respawn (draft)#241

Draft
obj-p wants to merge 4 commits into
mainfrom
ios-option2
Draft

iOS preview: shell-owns-agent flash-free respawn (draft)#241
obj-p wants to merge 4 commits into
mainfrom
ios-option2

Conversation

@obj-p

@obj-p obj-p commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Draft for review of the diff. Not ready to merge (xcrun removal, default-flip, and the pre-merge /simplify + /code-review gate are still pending).

What this does

Makes the iOS preview respawn the agent with no flash, on the surface we actually ship (the device display), which all three use cases consume: agent CLI/MCP capture, a human on the real Simulator.app, and the MCP-app stream.

The long-lived foreground SHELL hosts the respawnable AGENT's SwiftUI scene as a real cross-process FrontBoard scene (display + input). On respawn the shell holds a cached frame + dim + spinner over the gap, then re-hosts the new agent. No shell restart, so the device never blanks or shows springboard.

Mechanism (all derisked on the sim first)

  • Shell periodically snapshots its live hosted view (drawViewHierarchyInRect rasterizes the cross-process scene — verified by probe).
  • Shell detects agent death via its agent-UDS connection returning EOF.
  • On death: freeze cached frame + dim scrim + spinner, reconnect to the same stable sock the new agent rebinds, re-host into the existing window, drop the overlay.
  • Daemon _relaunch relaunches ONLY the agent on a stable agentSockPath; the live shell re-hosts itself.
  • Crash fixed: a BaseBoard foreground assertion must be invalidated before release (it traps in -dealloc), which had dropped the shell to springboard mid-gap.

Why leaner than the original plan

Stage 2 evidence showed the agent survives scene-less and direct daemon↔agent control re-dials fine, so the planned agent self-assertion (Stage 3) and control brokering (Stage 5) were unnecessary and are skipped.

Reverse-engineering that informed this

Captured Apple's real Xcode Previews in a VM: the agent runs foreground, and Apple renders to the Xcode CANVAS while the preview DEVICE screen stays black. We deliberately diverge (composite to the device display), so the device-display gap UX has no Apple precedent and is ours — hence hold-frame + spinner.

Verification (dev sim)

  • flashFreeRespawnGap: kills only the agent, asserts the held band (not black ~61KB, not springboard ~364KB; held ~86KB), saves frames.
  • relaunchReRendersCurrentPreview: full respawn re-host; saves the post-relaunch device frame (shows re-hosted content).
  • A/B confirmed visually: pre-change the gap was black; post-change it holds the frame + spinner.

Not done yet

  • Remove xcrun (CoreSimulator-only launch/terminate/boot).
  • Make shell-owned the default, delete the legacy daemon-launches-agent path, update docs.
  • Polish: spinner only after ~1s (currently immediate); a robust shell-liveness assertion (the size-band guard is a heuristic).
  • Pre-merge: /simplify + /code-review.

🤖 Generated with Claude Code

obj-p and others added 4 commits June 20, 2026 20:24
Checkpoint of the shell-owns-agent work toward the daemon<->shell<->agent
topology (plan: shell-owns-agent iOS preview architecture).

Stage 0 (in-session spawn + xcrun-free CoreSimulator primitives + agent
applicationState breadcrumb) and the productionized FrontBoard scene-hosting
shell (HostAppSource/Shell).

Stage 2 (this session): _relaunch() now passes --agent-sock to the relaunched
agent and relaunches the shell so it re-hosts the new agent's cross-process
scene. Previously _relaunch relaunched only the agent (no --agent-sock, no
re-host), so the shell kept hosting the dead scene and the device-display
composite went stale. This flashes for now (shell restart); Stage 6 makes
respawn flash-free via shell-owned respawn + hold-last-frame.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
relaunchReRendersCurrentPreview now saves the post-relaunch device-display
screenshot (the shell composite). A JPEG-size assertion can't distinguish a
re-hosted frame from a dead-black one (cap-E showed both ~70KB), so re-host is
verified visually. Confirmed: after relaunch the device display shows the
rendered preview re-hosted by the shell (not a stale/black scene).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… respawn

The shell now owns the agent respawn gap so the device display never blanks.
Mechanism (all derisked on the sim first):
- The shell periodically snapshots its live hosted view (drawViewHierarchyInRect
  rasterizes the cross-process scene — proven by probe).
- It detects the agent's death via its agent-UDS connection returning EOF.
- On death it freezes the cached frame + a dim scrim + spinner over the dead
  scene, reconnects to the same stable sock path the new agent rebinds, re-hosts
  into its existing window, and drops the overlay. No shell restart, no flash.
- Fixed a crash: a BaseBoard foreground assertion must be invalidated before
  release (it traps in -dealloc otherwise), which had dropped the shell to
  springboard during the gap.

Daemon: _relaunch relaunches ONLY the agent on a stable agent-sock path
(agentSockPath kept on the session); the live shell re-hosts itself. This skips
the planned agent self-assertion (Stage 3) and control brokering (Stage 5),
both shown unnecessary by the Stage 2 evidence.

Verified on the dev sim: the gap shows the held "Hello" + spinner (not black,
not springboard); re-host shows the new content. flashFreeRespawnGap asserts the
held band; relaunchReRendersCurrentPreview covers re-host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ground

The cross-process hosted scene loses its content when the shell backgrounds and
does not recover on its own (returning from background showed a blank screen).
Re-asserting the foreground activation was not enough; the scene must be re-hosted.

On sceneWillEnterForeground (after a background), the shell now tears down the
host and re-hosts the live agent via the existing connectAndHost path (briefly
holding the cached frame). Snapshot caching stops on background so the overlay
keeps the last good frame. Factored the teardown into teardownHost (shared with
the respawn path) and the activation into activateHosted.

Verified live on the dev sim: background to Safari then return now restores the
preview (was blank before).

Co-Authored-By: Claude Opus 4.8 (1M context) <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