Skip to content

Apple device env: live simulator across machines, agent recording and proof integrity - #1299

Merged
arul28 merged 104 commits into
mainfrom
ade/apple-sim-preview-and-live-stream
Sep 24, 2026
Merged

arul28 merged 104 commits into
mainfrom
ade/apple-sim-preview-and-live-stream

Conversation

@arul28

@arul28 arul28 commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Problem

An agent on an iOS task had no ADE-owned simulator to drive, watch or record, so it used the Simulator app and simctl directly. The user saw nothing, the recordings were not ADE's, and an agent could attach an old or copied video as proof.

What this lane adds

Live simulator, everywhere

  • One vendored Swift helper per Mac drives each lane's simulator. H.264 video goes to the desktop pane, a floating player (with picture in picture), paired desktops, the iPhone app and the web client.
  • A second viewer joins the running capture and never restarts it. A new bitrate cap goes to the live encoder.
  • Watching never boots a device. An off device shows an Off card with Start and "Choose another device".
  • The pane and the floating player share one lease, reconnect by themselves, and hand the device over without a stop. A desktop viewer's stop does not end a capture a phone reads.

Agents

  • The agent's ade shim names the brain that launched it, so two ADEs on one Mac never cross.
  • An older ade on PATH hands the call to the CLI that ADE launched.
  • A lane with a device gives the agent a short hint: use "$ADE_CLI_PATH" apple, record with record-start/record-stop, check each step, and never attach an old recording.
  • ade ui show / ade apple show open the pane, the floating player or the proof drawer, and answer "shown" only when it is on screen.
  • apple type --submit, apple key return|tab, apple button app-switcher.

Recording and proof

  • The recorder cuts idle time, and agent recordings have a 10-minute cap.
  • Proof integrity: a copied file is refused (SHA-256), an older video is flagged (mvhd creation time), and every proof shows its source.
  • Proof videos play from a 127.0.0.1 token server. Electron's protocol.handle cannot serve media ranges; see the commit message of b8b7476.
  • .mov proof plays on desktop, and large videos stream on paired desktops and the phone.

iOS app

  • Lane tool chips in the chat's badge row (the simulator chip opens the live viewer).
  • Proof source lines, sliced video downloads, and an "is off on your Mac" message.

Other fixes found on the way

Tested live

On the owner's MacBook (ADE Alpha) with the Studio, Windows and the iPhone:

  • cross-machine watch and drive;
  • recording, and proof playback on all three;
  • the off state and the floating player;
  • agent recording runs.

Verification

  • /quality: 7 review passes (about 110, 45, 14, 8, 3, 3 and 1 findings), then a post-merge revalidation with 4 more passes. Every accepted finding is fixed. The gate is empty.
  • /test: 75 correctness findings, each with a named regression test or an alternate check. 8 findings had no test; each now has a regression test that fails without its fix.
  • Latest focused runs: desktop 2,461 tests in 48 affected files, AgentChatPane 324, CLI 561, account directory 216, scripts 14. Desktop, CLI and account-directory typechecks are clean.
  • iOS: the changed Swift files pass swiftc -parse. XCTest did not run locally, because the disk had less than 30 GB free; the iOS CI job runs it.
  • Windows: the new path, media-server, shim and delegation tests now run in the windows-latest job.
  • main merged in at 5dc796a, with 9 conflicts resolved (see that commit message).

Not merged by the lane: the owner stacks the desktop lane on top and merges.

Authored with Claude Opus 5.5 via Claude Code.

🤖 Generated with Claude Code

ADE   Open in ADE  ·  ade/apple-sim-preview-and-live-stream branch  ·  PR #1299


Note

High Risk
Changes authentication-adjacent RPC scoping, proof ownership, and account-directory relay transport; mistakes could hide proof, cross lanes, or break machine-removal relay in production.

Overview
This PR tightens proof and computer-use filing so agent screenshots and recordings land in the right lane drawer and cannot be mislabeled. The RPC server infers lane ownership from the caller's worktree for chat-less CLI agents, rejects synthetic ade-cli:<pid> chat owners, blocks ownerless ingests before storage, and routes ingests through a capture registry that classifies bytes as ADE capture vs attach (SHA-256 match, duplicate rules, older-video warnings) instead of trusting CLI arguments. Scoped callers can prune broken ownerless artifacts; local screenshot_environment / record_environment proof paths get the same lane inference and provenance metadata.

Apple / agent control expands the CLI (ade apple start/stop/detach, type --submit, key, show, ui show, record flags) with dedicated help, wires work_tools.show through the runtime to desktops, scopes agent show to their chat, and notifies the UI when agents drive the simulator. User-only simulator deletes are limited to named desktop clients; the headless brain gets resolveLaneIdForPath so Apple actions file against the correct lane. Agent ade shims embed the launching brain's socket/ADE_HOME; older CLIs can delegate via an early entry hook.

Account directory drops public relay fetch (service binding required, redirect: "manual" on relay calls) and simplifies wrangler env section lookup. Docs/CI document detached dev:desktop and ade-apple; Windows CI runs extra CLI shim, delegation, and desktop path/media tests.

Reviewed by Cursor Bugbot for commit 8336448. Configure here.

arul28 and others added 30 commits September 21, 2026 10:31
…r, live column, recording, cross-machine stream

Replace idb with a vendored serve-sim Swift helper (ADESimHelper) signed and
built by every macOS dist script. One simulator per lane, created on first ask
and deleted on lane archive. Add the Apple sibling column in the Work tab with
3D and flat device views, live inspect, Home and Rotate, and recording with
tap rings and typed-text badges. Add a ticketed H.264 stream through the brain
for the web client (interact) and the iOS app (view only). Rename the CLI to
`ade apple` with an `ade ios-sim` alias for one minor release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
package:alpha and package:beta never built the notch, the capture helper, or
the new sim helper, all gitignored build outputs. An alpha build then lacked
ade-sim-helper and every Apple device action failed. The channel script now
runs build:mac-native before the desktop build, as dist:mac already does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-and-live-stream

# Conflicts:
#	apps/desktop/src/renderer/components/app/CommandPalette.test.tsx
#	apps/desktop/src/renderer/components/app/CommandPalette.tsx
#	apps/desktop/src/renderer/components/terminals/NativeToolFeedsContext.test.tsx
#	apps/desktop/src/renderer/components/terminals/NativeToolFeedsContext.tsx
#	apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
#	apps/desktop/src/renderer/components/terminals/WorkSidebar.test.tsx
#	apps/desktop/src/renderer/components/terminals/WorkToolPicker.test.tsx
#	apps/desktop/src/renderer/components/terminals/workTools.ts
#	apps/ios/ADE.xcodeproj/project.pbxproj
#	docs/features/terminals-and-sessions/ui-surfaces.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ot-on-start, tools drawer

Replace the sibling column, create dialog, header toggle, and unlabeled icon
stack with a single pane inside the tools pane, modeled on t3code's device
pane. The picker is the empty state; Start attaches, boots, and streams in
one click through the new deviceStart contract. Every rail button has a
tooltip. Errors map to one sentence via describeAppleError. Preview Lab moves
into a drawer section. Add `ade apple start`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Show Borders is iOS's Button Shapes flag; add it as the button-shapes
accessibility option through the same preference write and change
notification the other switches use. Add getForegroundApp, which asks the
helper's accessibility bridge for the frontmost app, and poll it from the
tools drawer so apps opened outside ADE show up and the Event log follows
them. Wire the method through IPC, preload, the web adapter, the remote
allowlist, and the action policy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A dev brain must never compete for the machine-wide sync host lease. On
2026-09-21 a lane dev brain took the lease from the installed ADE and the
agents under that brain died with it. Set ADE_DEV_RUNTIME_SYNC=1 to opt in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…me, with an isolation report

The launcher's auto path spawned the dev brain without --no-sync, so it
could take the machine-wide sync host lease from the installed ADE. It now
always passes --no-sync (ADE_DEV_RUNTIME_SYNC=1 opts in) and prints a dev
isolation report before the window opens. The local-development doc and
the context skill state the single supported command.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The renderer fetches the stream from another origin with an authorization
header, so the browser preflights with OPTIONS. The helper answered 405 and
every in-app reader failed with "Failed to fetch" while curl worked. The
frame server now answers the preflight and labels every response with
Access-Control-Allow-Origin, so a 403 is readable too. Map the browser's
fetch failure to "Could not connect to the video stream." with Reconnect.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d solid

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n-tool decision

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Function: an 8 s per-control expiry in the serial simulator queue ends the
tap storm (the helper's 30 s timeout outlived the desktop's 25 s one, so one
wedged touch stalled every later tap); auto-record starts only for agent
input; every recording that stops becomes a proof artifact with a Saved
receipt; status reads route through the bound runtime; decoder errors
recover in place; the floating mini player renders the shared stream with
hover-only chrome and follows the shared close/minimize/preview rules
(mirrors of lane mac-desktop's workLiveCardState, chatCompanionUiState,
workLiveCard, workToolPreviewControls, closeWorkToolForReal).

Design: the tool is Apple Development with the Apple logo; the picker is
gradient cards grouped by iPhone, iPad, Apple Watch, Apple TV, and Apple
Vision with the model under custom names; every surface is opaque; drawer
rows never collide; the loading card says what is booting and which step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nfig

`@import "tailwindcss"` in v4 does not read tailwind.config.cjs, and the CSS
never declared @config. So every token utility in that config compiled to
nothing: bg-surface, bg-surface-overlay, text-fg, muted-fg and border-border
produced no CSS at all. Panels rendered with no background, text inherited its
colour, and the shutdown dialog shipped see-through. Class-name tests could not
catch it because jsdom computes no styles.

One @config line restores all of them. A guard test now fails if the directive
goes missing, if a token disappears, or if a token points at a CSS variable
index.css never defines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ols, four-group drawer

Owner decisions from the live test. Record and Inspect move into the rail. The
procedural body and its realistic flag are deleted, so the vendored Apple
models always load; the black cover glass shared a plane with the screen, so
the screen now lifts fractionally out of the tie. 3D and Flat become one
toggle, 3D by default. 3D taps were measured in decoded pixels rather than
device points and landed three times off. Reset view rebuilt the renderer and
exhausted the WebGL contexts. The flat view capped its scale at 1, so a large
pane stayed mostly empty.

Inspect was never mounted at all; it now draws frames that follow the tilted
body and its card inserts the element into the chat. The drawer becomes four
collapsible groups with a closed group unmounted. Closing the tab powers the
device off behind an ADE dialog. Reopening the tool used to hang on
"Connecting video": the outgoing floating player released the lease the
arriving pane had just adopted, which stopped the capture the service still
reported as running. Leases are epoch-stamped and the retake hands over.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… take the compiling guard

The mac-desktop lane challenged 938eee2. Two corrections, one accepted.

Accepted: my guard was the wrong shape. It asserted the config CONTAINS token
keys, while the real failure is a class the product spells that the config does
NOT define. Their guard compiles the real stylesheet and fails on any colour
class in our own vocabulary that emits no CSS. Taken from d1de304, with the
blind spot closed: a name also counts as ours when index.css defines
--color-<name> for it, which is how text-success was invisible to a
token-family check, and React key props no longer read as class names.

It then caught five dead classes here. bg-secondary is a rail button's PRESSED
state, so every toggled-on rail control rendered with no background, and the
CTO timeline's info/success/warning/error text had no colour. All five had
variables in index.css in both themes and no token. Registered.

Not accepted: that @config is a no-op. Compiling this stylesheet through
@tailwindcss/postcss 4.1 here gives 742,847 bytes with it and 681,927 without,
and bg-surface, bg-surface-overlay, text-fg and border-border are emitted only
with it. My earlier commit message still overstated its effect: that commit
also swept in 34 lines of card CSS left uncommitted by another unit, which I
did not notice and which did part of the work I credited to the directive.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Both round-5 units hit the Opus session limit mid-write. The tree they left
compiles and passes (desktop tsc clean, 1757 tests across apple, terminals,
services/ios and the token guard), so this records it rather than risking it
across a session boundary.

Landed: the flat view now knows about orientation, so a landscape device is
drawn upright instead of lying on its side; the rail gains a named
orientation control; the inspect context path is reworked; closing the tab
powers the device off for real; and the agent-facing surfaces (action policy,
apple remote commands, CLI verbs, screenshot-as-proof) are part way through
the parity pass.

Not done: the skills and docs rewrite, and the CLI help regeneration. Neither
unit reached it. Nothing here is verified on screen yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Finishes round 5 §S5, which neither unit reached before the Opus limit.

The skill is rewritten around what an agent actually does: read `status` for
`capabilities` rather than guessing verbs, use `start` to bring the device up
and `stop` to power it off, drive by element query, and rely on proof filing
itself. Its old pin-to-proof instructions were wrong — every recording and
every screenshot now files itself and returns a proofArtifactId. The three
historical negations (idb, window capture, live-start, --backend) collapse to
one line that tells a reader those docs are describing a dead version.

Also: `stop`, `scroll` and `foreground` were implemented but appeared in no
help text, so an agent could never find them — added and the help regenerated.
ARCHITECTURE listed simulatorAppShell.ts, deleted two rounds ago. The feature
README gains the shipped desktop surface and the discovery contract, and loses
its wrong proof-bundle claim.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the chat

A capture with no chat session arrived at the broker with an EMPTY owner list,
because both paths claimed only a chat owner. The broker links an artifact to
its owners and derives the lane from them, so that artifact belonged to
nobody: `ade proof list` returned it under no scope, project-wide included,
while `screenshot` still handed back a real artifact id. It looked filed and
was unreachable — which is every CLI and agent capture, the exact workflow the
feature exists for. `ade proof attach` never had the bug because it claims
both owners.

Verified live: a screenshot taken with no chat session now appears in the
drawer owned by its lane. Two tests, one of them the empty-owner case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s a screen

An agent asked the owner for permission to use a simulator another lane owned,
and offered to substitute a passing unit test for the screenshot he asked for.
Both were reasonable from the inside and both were wrong, because the skill
never said otherwise.

It now states that a device owned elsewhere is not a blocker — one runtime
serves unlimited devices, so cloning one for your lane costs seconds and no
iOS copy — and that a request for simulator proof is owed a screen, with the
real obstacle named when the screen cannot be reached. It also separates
runtime from device, so 'Shutdown' reads as installed and ready to boot rather
than as missing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
My own regression test omitted chatSessionId, which vitest accepted because it
does not typecheck. It is now passed explicitly as null, which is the point of
the case rather than an omission: that is what a CLI or agent capture looks
like, and what used to leave the artifact with no owner at all.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…vice

Rotation. My diagnosis was half wrong: applied:true only meant a mach message
was sent, but rotation works with no Simulator.app — this Xcode does not even
have that app on disk. What refused was the app on screen. The Home Screen and
Settings are portrait-only on a non-Max iPhone, so they ignored every rotation
while the OS took all of them; launching Safari on the same device came up
landscape on its first frame, which proves it. Rotate now measures the real
framebuffer before and after and claims success only on a confirmed turn, or
when the screen already sits on the requested axis. A refusal returns a reason
and reads "The app on screen does not support that orientation", replacing an
invented excuse about a window needing to be open.

Picker. The owner counted four of five simulators, because the hero card sat
outside its family section — and that hero was not even his lane's device,
since the picker fell back to the newest iPhone when the lane had none. It also
offered Open on a simulator another lane owned, with no hint, which is the same
blind spot that made an agent stop and ask him for permission. Now: one
inventory line naming the runtimes, counts and device-data size; a hero only
for the lane's real device; unowned devices under Available; and another lane's
device shown with that lane's name, no Open, and a takeover behind a
confirmation. Per-device sizes from one cached directory pass.

One lane owns a device at a time. deviceAttach duplicated a binding instead of
moving it, which nothing could reach until the takeover button existed. The
registry now re-keys the row in a single statement, so there is no instant when
two lanes own it, releases the losing lane's stream, claim and session first,
and keeps the simulator running for the lane taking it. Provenance stays with
the simulator, so a clone ADE made stays ADE's to delete.

Whole-tree gates: desktop and CLI typecheck clean, 7236 desktop tests, 1271
CLI tests, lint clean on every changed file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…claim banner

Two faults the owner hit in one live test.

An OpenCode question card is durable — it IS the `approval_request`
transcript event, and the renderer rebuilds it from that event — while the
waiter that receives its answer is a closure in one process. When the pooled
server went away with the card open, the card was redrawn fully interactive
with nothing behind it. Answering it reached `settleUnclaimedPendingInput`,
which never read `answers` or `responseText`: it recorded the accept as a
cancellation, painted "That request is no longer active", and returned
success, so the composer never restored the draft it had cleared. The answer
was lost twice, and retyping it by hand was the only way out.

The card can still outlive its waiter; that is now handled rather than
prevented. An accept carrying real content on a question-shaped request is
re-routed as an ordinary user message, which is the mechanism async Codex
questions already use. A re-route failure throws, so the card stays and the
composer puts the answer back. A card already receipted `accepted` is a
silent no-op, so a click racing a keypress can no longer overwrite an
answered receipt with a cancellation. Approvals, plans, elicitations and
secret questions keep the old settle: "Approve and implement" is not prose,
and a re-routed secret would be written into the synced transcript.

Separately, Apple Development no longer claims itself for another lane. The
banner was written when one simulator session WAS the pane. A lane now owns a
device, one runtime install serves any number of them, and the picker states
per device whether it is free, this lane's, or in use elsewhere. The banner
sat over a page offering four free simulators and said the view was
second-hand — and it taught an agent to stop and ask for a device instead of
creating its own. App Control keeps its banner, which is still one per lane.
…g it verified

An agent filed two before/after screenshots, reported success, and the owner
could not open either one. The files were on disk. The record's only owner was
`chat_session: ade-cli:56056` — a client name and a pid — so no lane resolved
from it and no proof drawer could scope to it.

Four faults, stacked, each of which alone would have hidden the capture.

`resolveComputerUseOwners` treated the caller id as an implicit chat session
whenever the caller named none. For the direct CLI that id is minted as
`<client>:<pid>`, so a process became the owner of a durable record. A
synthetic id is now never an owner; the lane owner serves that caller instead.

The ingest authorized a named lane only against the caller's chat-session
lane. An agent whose shell carries no chat session — every OpenCode agent,
because one `opencode serve` is shared across chats and so cannot carry a
per-chat environment — had two ways to fail and none to succeed: name its lane
and be told it does not match a session it has not got, or name nothing and
depend entirely on inference. The lane inferred from `callerRoot` now also
authorizes a lane the caller names, which is the stronger check: an
environment variable is a claim, standing inside the worktree is a fact. A
lane named from outside its worktree is still refused, and the refusal now
names the requested lane, the session lane and what the callerRoot resolves
to.

A failed inference logged nothing. The ingest refused and the CLI printed a
generic "requires an authorized lane worktree", which is why this took a night
to find rather than a minute. It now warns with the callerRoot and how many
lanes were considered, and an ingest that lands with no lane and no chat is
warned about too — not refused, because a CTO scene still legitimately files
with no owner.

Worst of all, the verification passed. `summarizeProofFiling` computed the
lane and the chat session in order to PRINT them and never required either,
and the re-read found the row because an unscoped caller lists project-wide.
The agent read "verified: re-read through ade proof list" and told its owner
the proof was attached. A record with no owner is now a failure, named as one,
and a synthetic owner id is discounted on this side too — the CLI is used
against whatever brain is installed, including versions that still write one.

Both new server tests were seen to fail against the old guard, and the CLI
test against the old summary, before they passed.
… and that simctl captures lose their owner

Three gaps a live agent run exposed, in the order it hit them.

It read this skill and then built with xcodebuild by hand, because nothing here
said `ade apple launch` resolves, builds, installs and starts a target in one
call. It does, and that is the answer to "run the app on a simulator".

It captured with `xcrun simctl io screenshot` and handed the file to
`ade proof attach`. The picture was right and the owner was not: a shell with
no chat session produced a record owned by nothing, which no drawer could show.
The `ade apple` capture commands file themselves against the lane and the
asking chat, so the skill now says to use them and why, and says to read the
owner line when attaching a file from elsewhere.

It also led with cloning, when binding a free installed device is the cheaper
first move and what `start` already does.
… honest copy costs

The owner's own layout, in his order, after reading round 5 on screen.

Gone: the summary box at the top (the runtime, a count installed, a count
running, and a paragraph about one runtime serving many devices), the caption
under Available, the line at the foot of the page, and the separate section
for a device another lane holds. He could see how many devices he had by
looking at them; the prose sat between him and the thing he came to click.

What replaced it says the same facts in the list itself. "Available" leads,
with the free count beside the word and one glyph per device he owns set at
the heading's own size — three iPhones and two iPads read as three iPhones and
two iPads at a glance, and a device on hold elsewhere is dimmed rather than
absent. Family is now the only grouping, so the number of cards on the page
and the number beside the heading finally agree; round 4 lifted a hero out of
the groups and five simulators read as one plus four, which is why he counted
four.

Each card is a glyph, a name, `iOS 26.3 · iPhone 15 Pro`, and a menu. The model
shows on every card, even when the name already contains it, so a grid of
cards does not disagree about its own line count. The whole card starts the
device.

A device another lane holds says TAKEN, carries no menu, and does not respond
to a click. Round 5's "Take over…" is gone from the panel entirely at his
instruction — a device on hold by a working lane is not a choice this panel
should offer. The CLI keeps `--force` for recovering one from an archived lane.

The menu is new down to the service. `deviceDeleteInstalled` deletes one
installed simulator by udid and refuses any a lane holds with
`APPLE_DEVICE_OWNED_BY_LANE`, because the picker's rendering is not a
safeguard: a CLI caller and a stale renderer reach the same method, and the
cost of getting it wrong is another lane's live view vanishing mid-test. It
also refuses to run at all without `confirmedByUser: true` — deleting a
simulator is not recoverable, the owner's standing rule is that nothing
deletes one without his approval, and this verb is reachable by any agent
because ADE keeps one action list per domain. The menu asks twice and names
the measured size in the second ask, since "delete iPhone 17e" and "delete
iPhone 17e, 3.2 GB" are different decisions.

"Create a new one" sits at the foot with a row per installed device and what
each copy costs. `simctl clone` duplicates the source's data directory, so the
source's measured size IS the estimate, and every row says no download —
which is the fact that was buried in the deleted paragraph and the reason an
agent thought it had to ask for a device.

One real bug fell out of writing the tests: `appleDefaultTemplateUdid`
returned the project's last used template on being installed alone, without
checking it was still cloneable. A template since booted or taken by another
lane became the default Create source, and `simctl clone` on a booted device
fails — so the one source the page offered by default was the one that could
not work.

Refresh moved to the top right of the pane, where a refresh belongs.
…age reachable in the web preview

I looked at the rebuilt picker in a browser instead of trusting its tests, and
found four things jsdom cannot see. That is the third round in a row where
green unit tests shipped a page the owner had to correct, so the last change
here is the one that stops it repeating.

The create list offered every installed device as a clone source, including a
booted one and one another lane holds. `simctl clone` fails on a booted device
and a held device is not this panel's to copy, so on the owner's own machine
the first row was a source that could only ever fail. It now offers what a
clone can actually be made from.

Each of those rows read `iPad Air 13-inch (M4) · iPad Air 13-inch M4`, because
`appleDeviceModelLine` appends the model to the name and for most devices the
name already IS the model. The row names the device.

The page was centred vertically in the pane, so a short list floated in the
middle of an empty column. It starts at the top.

The inventory glyphs beside "Available" were set at 13px and 70% opacity and
read as blank boxes rather than as phones and tablets, which is the one job
they have. 15px at full strength, and a held device dimmed rather than erased.

And the reason all four survived: the browser mock reported
`supported: false` with no `deviceList`, so the Apple pane stopped at "this
host is not a Mac" and the picker could only be seen by building Electron. The
mock now carries the owner's machine — five devices on one runtime, two
booted, one held elsewhere — so `npm run dev:vite` shows the real page. jsdom
computes no styles and a class name is not a layout; this is the cheap way to
look, and it was only useless because the page it was meant to show could not
be reached.
…cket, read the report

An agent told to start a dev app ran the launcher as an ordinary command. The
command runs in the foreground for as long as the window is open, so it held
its turn open, and when the harness ended the turn it killed the process group
and the window closed. Nothing in AGENTS.md, the context skill, the local
development doc or the desktop README said that, so the agent was not wrong to
read it as a normal command.

All four now carry the same block: background it, give the lane its own socket
because the default is shared and two dev brains on one socket restart each
other, then wait for the isolation report and read it — sync ON means a dev
brain is holding the machine-wide lease that drops the installed brain's tunnel
and kills the agents under it.

Also states the limit that is easy to assume away: --no-sync protects the sync
lease only. It does not isolate the database, so a dev brain still sees every
chat and task in every lane.

The desktop README additionally points at the browser preview as the answer to
"does this page look right", now that its mock reaches the panes that used to
be unreachable there.
The owner opened the Apple tool while the floating player showed the
device. The pane sat on "Connecting video" until he switched tabs, and
his phone kept playing the whole time.

- The pane and the floating player counted viewers in two buckets. The
  pane keyed its lease by its null runtime pin, and the player by the
  resolved window binding. So the last viewer of one bucket stopped a
  capture the other had just joined. Both now key by the resolved
  machine, and the last viewer's stop waits 1 s and is skipped if
  another viewer took a lease.
- Nothing made the pane ask again. The stream hook now reconnects on a
  stream-stopped or stream-error for its device, on a body that ends
  under a live viewer, and on a first-frame timeout (3 tries, backoff).
  A reader "stopped" with no address no longer drops a pending start to
  idle. The pane's 8 s re-check also reconnects "Connecting video".
- A desktop viewer's stop no longer ends a capture a phone or web
  viewer reads through the relay. The relay adopts it and stops it
  when its own last viewer leaves.

Checked in the dev app: pane opened over the player went live in about
1.5 s, one lease bucket, and no stream-stopped after the handover hold.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 235 files, which is 135 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: Repository: arul28/ADE/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: e7b511d2-5af6-4c15-87fb-c2f614ed43cc

📥 Commits

Reviewing files that changed from the base of the PR and between b183cd3 and 8336448.

⛔ Files ignored due to path filters (15)
  • AGENTS.md is excluded by !*.md
  • apps/ios/ADE.xcodeproj/project.pbxproj is excluded by !**/*.xcodeproj/project.pbxproj
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/development/local-development.md is excluded by !docs/**
  • docs/features/agents/tool-registration.md is excluded by !docs/**
  • docs/features/apple-device/README.md is excluded by !docs/**
  • docs/features/chat/README.md is excluded by !docs/**
  • docs/features/computer-use/README.md is excluded by !docs/**
  • docs/features/computer-use/artifact-broker.md is excluded by !docs/**
  • docs/features/computer-use/settings-and-readiness.md is excluded by !docs/**
  • docs/features/proof.md is excluded by !docs/**
  • docs/features/sdk/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
  • docs/features/terminals-and-sessions/README.md is excluded by !docs/**
📒 Files selected for processing (235)
  • .agents/skills/context/SKILL.md
  • .agents/skills/context/references/doc-map.md
  • .github/workflows/ci.yml
  • apps/account-directory/scripts/verify-deployment-config.mjs
  • apps/account-directory/src/activityRelay.ts
  • apps/account-directory/test/directory.test.ts
  • apps/ade-cli/README.md
  • apps/ade-cli/src/adeRpcServer.test.ts
  • apps/ade-cli/src/adeRpcServer.ts
  • apps/ade-cli/src/adeRpcServerProofExplicit.test.ts
  • apps/ade-cli/src/bootstrap.test.ts
  • apps/ade-cli/src/bootstrap.ts
  • apps/ade-cli/src/cli.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/cliBrowserGrammar.test.ts
  • apps/ade-cli/src/commands/setup.ts
  • apps/ade-cli/src/help/appleHelp.ts
  • apps/ade-cli/src/help/banner.ts
  • apps/ade-cli/src/lib/cliDelegation.test.ts
  • apps/ade-cli/src/lib/cliDelegation.ts
  • apps/ade-cli/src/lib/cliDelegationEntry.ts
  • apps/ade-cli/src/lib/cliGlobalArgs.ts
  • apps/ade-cli/src/services/account/cliRefreshBroker.ts
  • apps/ade-cli/src/services/proof/adeCaptureRegistry.ts
  • apps/ade-cli/src/services/runtime/adeCliShim.test.ts
  • apps/ade-cli/src/services/runtime/adeCliShim.ts
  • apps/ade-cli/src/services/runtime/brainHeartbeat.test.ts
  • apps/ade-cli/src/services/runtime/brainHeartbeat.ts
  • apps/ade-cli/src/services/sync/appleRemoteCommands.test.ts
  • apps/ade-cli/src/services/sync/appleRemoteCommands.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/services/workTools/workToolShowRequests.test.ts
  • apps/ade-cli/src/services/workTools/workToolShowRequests.ts
  • apps/ade-cli/src/services/workTools/workToolsStateService.ts
  • apps/ade-cli/src/tuiClient/connection.ts
  • apps/ade-cli/src/tuiClient/connectionPool.ts
  • apps/ade-cli/src/tuiClient/remoteLauncher.ts
  • apps/ade-cli/src/tuiClient/rightPaneFormatters.ts
  • apps/desktop/README.md
  • apps/desktop/native/ADESimHelper/Sources/ADESimHelperCore/DeviceSession.swift
  • apps/desktop/native/ADESimHelper/Sources/ADESimHelperCore/IdleGapCompressor.swift
  • apps/desktop/native/ADESimHelper/Sources/ADESimHelperCore/Protocol.swift
  • apps/desktop/native/ADESimHelper/Sources/ADESimHelperCore/RecordingSession.swift
  • apps/desktop/native/ADESimHelper/Sources/ADESimHelperCore/SimHelperRuntime.swift
  • apps/desktop/native/ADESimHelper/Tests/ADESimHelperCoreTests/IdleGapCompressorTests.swift
  • apps/desktop/native/ADESimHelper/Tests/ADESimHelperCoreTests/ProtocolTests.swift
  • apps/desktop/native/ADESimHelper/Tests/ADESimHelperCoreTests/RecordingTests.swift
  • apps/desktop/native/ADESimHelper/Tests/ADESimHelperCoreTests/SimHelperRuntimeTests.swift
  • apps/desktop/resources/ade-cli-help.txt
  • apps/desktop/resources/agent-skills/ade-apple/SKILL.md
  • apps/desktop/resources/agent-skills/ade-cli-control-plane/SKILL.md
  • apps/desktop/resources/agent-skills/ade-proof-artifacts/SKILL.md
  • apps/desktop/scripts/validate-win-artifacts.mjs
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/rendererCsp.test.ts
  • apps/desktop/src/main/rendererCsp.ts
  • apps/desktop/src/main/services/adeActions/actionInputContracts.ts
  • apps/desktop/src/main/services/adeActions/actionPolicy.test.ts
  • apps/desktop/src/main/services/adeActions/actionPolicy.ts
  • apps/desktop/src/main/services/adeActions/registry.test.ts
  • apps/desktop/src/main/services/adeActions/registry.ts
  • apps/desktop/src/main/services/ai/tools/ctoOperatorTools.ts
  • apps/desktop/src/main/services/ai/tools/ctoToolPacks.test.ts
  • apps/desktop/src/main/services/attention/remoteProjectIdentity.test.ts
  • apps/desktop/src/main/services/attention/remoteProjectIdentity.ts
  • apps/desktop/src/main/services/chat/agentChatService.test.ts
  • apps/desktop/src/main/services/chat/agentChatService.ts
  • apps/desktop/src/main/services/chat/laneAppleDeviceDirective.ts
  • apps/desktop/src/main/services/chat/pendingInputRecovery.ts
  • apps/desktop/src/main/services/cli/adeCliService.test.ts
  • apps/desktop/src/main/services/cli/adeCliService.ts
  • apps/desktop/src/main/services/computerUse/artifactByteRange.ts
  • apps/desktop/src/main/services/computerUse/artifactMediaServer.test.ts
  • apps/desktop/src/main/services/computerUse/artifactMediaServer.ts
  • apps/desktop/src/main/services/computerUse/artifactStreamProtocol.ts
  • apps/desktop/src/main/services/computerUse/computerUseArtifactBrokerService.test.ts
  • apps/desktop/src/main/services/computerUse/computerUseArtifactBrokerService.ts
  • apps/desktop/src/main/services/computerUse/mediaCreationTime.ts
  • apps/desktop/src/main/services/computerUse/proofFingerprint.ts
  • apps/desktop/src/main/services/ios/appleStreamRelay.ts
  • apps/desktop/src/main/services/ios/iosDeviceHub.test.ts
  • apps/desktop/src/main/services/ios/iosDeviceHub.ts
  • apps/desktop/src/main/services/ios/iosSimulatorService.test.ts
  • apps/desktop/src/main/services/ios/iosSimulatorService.ts
  • apps/desktop/src/main/services/ios/laneDeviceLifecycle.ts
  • apps/desktop/src/main/services/ios/laneDeviceRegistry.test.ts
  • apps/desktop/src/main/services/ios/laneDeviceRegistry.ts
  • apps/desktop/src/main/services/ios/recording/appleRecordingsStore.ts
  • apps/desktop/src/main/services/ios/recording/simRecordingService.test.ts
  • apps/desktop/src/main/services/ios/recording/simRecordingService.ts
  • apps/desktop/src/main/services/ios/simHelperClient.test.ts
  • apps/desktop/src/main/services/ios/simHelperClient.ts
  • apps/desktop/src/main/services/ios/simulatorPower.ts
  • apps/desktop/src/main/services/ipc/ipcTimeouts.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/ipc/runtimeBridge.ts
  • apps/desktop/src/main/services/lanes/laneService.test.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts
  • apps/desktop/src/main/services/localRuntime/localRuntimeTimeoutPolicy.ts
  • apps/desktop/src/main/services/remoteRuntime/pairedRuntimeBootstrap.ts
  • apps/desktop/src/main/services/remoteRuntime/pairedRuntimeErrors.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteBootstrap.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionService.test.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionService.ts
  • apps/desktop/src/main/services/remoteRuntime/runtimeRpcClient.ts
  • apps/desktop/src/main/services/remoteRuntime/syncRuntimeTransport.test.ts
  • apps/desktop/src/main/services/remoteRuntime/syncRuntimeTransport.ts
  • apps/desktop/src/main/services/runtime/projectRecoveryService.ts
  • apps/desktop/src/main/services/scenes/sceneSnapshotIngest.ts
  • apps/desktop/src/main/services/scenes/sceneStills.test.ts
  • apps/desktop/src/main/services/scenes/sceneStills.ts
  • apps/desktop/src/main/services/search/searchIndexDb.ts
  • apps/desktop/src/main/services/search/searchIndexFts5.test.ts
  • apps/desktop/src/main/services/search/searchService.ts
  • apps/desktop/src/main/services/shared/pathCompare.ts
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/browserMock.ts
  • apps/desktop/src/renderer/components/app/App.tsx
  • apps/desktop/src/renderer/components/app/App.workKeepAlive.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevice3DView.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevice3DView.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceMiniPlayer.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceMiniPlayer.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceModelCache.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevicePane.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevicePane.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevicePicker.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDevicePicker.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceStage.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceStatusStrip.test.tsx
  • apps/desktop/src/renderer/components/apple/AppleDeviceStatusStrip.tsx
  • apps/desktop/src/renderer/components/apple/AppleShutdownConfirm.tsx
  • apps/desktop/src/renderer/components/apple/LaneAppleDeviceMarker.tsx
  • apps/desktop/src/renderer/components/apple/appleDeviceModelLoader.ts
  • apps/desktop/src/renderer/components/apple/appleDeviceModels.test.ts
  • apps/desktop/src/renderer/components/apple/appleDeviceModels.ts
  • apps/desktop/src/renderer/components/apple/appleDeviceScene.ts
  • apps/desktop/src/renderer/components/apple/appleDeviceState.test.ts
  • apps/desktop/src/renderer/components/apple/appleDeviceState.ts
  • apps/desktop/src/renderer/components/apple/appleErrors.test.ts
  • apps/desktop/src/renderer/components/apple/appleErrors.ts
  • apps/desktop/src/renderer/components/apple/appleMiniPlayerStore.test.ts
  • apps/desktop/src/renderer/components/apple/appleMiniPlayerStore.ts
  • apps/desktop/src/renderer/components/apple/applePickerInventory.test.ts
  • apps/desktop/src/renderer/components/apple/applePickerInventory.ts
  • apps/desktop/src/renderer/components/apple/appleRecording.ts
  • apps/desktop/src/renderer/components/apple/appleStreamHandover.test.tsx
  • apps/desktop/src/renderer/components/apple/appleStreamLease.test.ts
  • apps/desktop/src/renderer/components/apple/appleStreamLease.ts
  • apps/desktop/src/renderer/components/apple/useAppleDeviceStartTracker.ts
  • apps/desktop/src/renderer/components/apple/useAppleDeviceStream.test.tsx
  • apps/desktop/src/renderer/components/apple/useAppleDeviceStream.ts
  • apps/desktop/src/renderer/components/apple/useAppleInspect.tsx
  • apps/desktop/src/renderer/components/apple/useAppleLaneDeviceCard.ts
  • apps/desktop/src/renderer/components/apple/useAppleLaneDeviceList.ts
  • apps/desktop/src/renderer/components/apple/useLaneAppleDevices.test.tsx
  • apps/desktop/src/renderer/components/apple/useLaneAppleDevices.ts
  • apps/desktop/src/renderer/components/chat/AgentChatMessageList.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/chat/ChatComputerUsePanel.test.tsx
  • apps/desktop/src/renderer/components/chat/ChatComputerUsePanel.tsx
  • apps/desktop/src/renderer/components/chat/pendingInput.test.ts
  • apps/desktop/src/renderer/components/chat/pendingInput.ts
  • apps/desktop/src/renderer/components/chat/sceneStillStore.ts
  • apps/desktop/src/renderer/components/chat/useArtifactPreview.ts
  • apps/desktop/src/renderer/components/chat/useChatPaneShowRequests.ts
  • apps/desktop/src/renderer/components/files/v2/viewers/MediaViewer.tsx
  • apps/desktop/src/renderer/components/terminals/SessionCard.tsx
  • apps/desktop/src/renderer/components/terminals/SessionListPane.test.tsx
  • apps/desktop/src/renderer/components/terminals/SessionListPane.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalsPage.test.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalsPage.tsx
  • apps/desktop/src/renderer/components/terminals/WorkSidebar.test.tsx
  • apps/desktop/src/renderer/components/terminals/WorkSidebar.tsx
  • apps/desktop/src/renderer/components/terminals/useWorkShowRequests.ts
  • apps/desktop/src/renderer/components/terminals/useWorkToolStatuses.ts
  • apps/desktop/src/renderer/components/terminals/workToolPanels.ios.test.tsx
  • apps/desktop/src/renderer/components/terminals/workToolPanels.tsx
  • apps/desktop/src/renderer/lib/chatMachineRouting.ts
  • apps/desktop/src/renderer/lib/playableMedia.test.ts
  • apps/desktop/src/renderer/lib/playableMedia.ts
  • apps/desktop/src/renderer/lib/workToolOnScreen.ts
  • apps/desktop/src/renderer/lib/workToolShowRequests.test.ts
  • apps/desktop/src/renderer/lib/workToolShowRequests.ts
  • apps/desktop/src/renderer/webclient/adapter/appleDevice.ts
  • apps/desktop/src/renderer/webclient/adapter/misc.ts
  • apps/desktop/src/shared/agentSkillRoots.test.ts
  • apps/desktop/src/shared/artifactStreamUrl.test.ts
  • apps/desktop/src/shared/artifactStreamUrl.ts
  • apps/desktop/src/shared/ipc.ts
  • apps/desktop/src/shared/pathCase.test.ts
  • apps/desktop/src/shared/pathCase.ts
  • apps/desktop/src/shared/pathContainment.test.ts
  • apps/desktop/src/shared/pathContainment.ts
  • apps/desktop/src/shared/pendingInputRequest.ts
  • apps/desktop/src/shared/permissionPolicy.ts
  • apps/desktop/src/shared/proofProvenance.test.ts
  • apps/desktop/src/shared/proofProvenance.ts
  • apps/desktop/src/shared/runtimeClientNames.ts
  • apps/desktop/src/shared/syncMobileCompatibility.ts
  • apps/desktop/src/shared/syntheticCallerId.ts
  • apps/desktop/src/shared/types/computerUseArtifacts.ts
  • apps/desktop/src/shared/types/iosSimulator.ts
  • apps/desktop/src/shared/types/pairedRuntime.ts
  • apps/desktop/src/shared/types/sync.ts
  • apps/desktop/src/shared/types/workToolShow.ts
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Account/AccountSignInView.swift
  • apps/ios/ADE/Views/Work/AppleDeviceStream.swift
  • apps/ios/ADE/Views/Work/AppleDeviceViewer.swift
  • apps/ios/ADE/Views/Work/WorkArtifactTerminalViews.swift
  • apps/ios/ADE/Views/Work/WorkChatScrollBench.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView+Timeline.swift
  • apps/ios/ADE/Views/Work/WorkChatSessionView.swift
  • apps/ios/ADE/Views/Work/WorkLaneToolChips.swift
  • apps/ios/ADE/Views/Work/WorkModels.swift
  • apps/ios/ADE/Views/Work/WorkPreviews.swift
  • apps/ios/ADE/Views/Work/WorkProofSheet.swift
  • apps/ios/ADE/Views/Work/WorkSessionDestinationView+Actions.swift
  • apps/ios/ADE/Views/Work/WorkSessionDestinationView.swift
  • apps/ios/ADE/Views/Work/WorkToolsRow.swift
  • apps/ios/ADE/Views/Work/WorkToolsSheet.swift
  • apps/ios/ADETests/ADETests.swift
  • apps/ios/ADETests/AppleDeviceStreamTests.swift
  • apps/ios/ADETests/WorkProofRowModelTests.swift
  • apps/ios/ADETests/WorkToolsContractTests.swift
  • scripts/dev-desktop.mjs
  • scripts/dev-detached.mjs
  • scripts/dev-shared.mjs
  • scripts/dev-shared.test.mjs

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel

vercel Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
ade Ignored Ignored Preview Sep 24, 2026 7:27am UTC

arul28 added a commit that referenced this pull request Sep 23, 2026
…1299

The Apple lane added the agent shim fix, proof integrity, `ade ui show`,
idle-cut recordings, the loopback proof video server, `apple key`, and
the pane/floating-player stream handover.

Two conflicts, both additive:
- adeRpcServer.ts: keep the mac_desktop scoping and the Apple lane's
  always-scoped work_tools actions and driving-action list.
- workToolsStateService.ts: dispose the Mac Desktop subscription and the
  show-request store.

The CLI grammar count rises from 110 to 112 for `ade ui show`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
arul28 and others added 17 commits September 23, 2026 16:10
…rer and iOS

Eight reviewers (correctness and maintainability, four areas) reported
about 110 findings. This commit fixes all of them except one rejected
policy item (the work_tools pass-through methods are the intended
boundary).

High
- Agents could label any file "Recorded/Captured by ADE": the label
  came from names the caller chose. The RPC server now keeps a short
  registry of files its own capture actions produced (path, SHA-256,
  15 min). Only an unchanged file from that registry keeps ADE's label.
- The phone downloaded every proof video in full as soon as its row
  showed. A video over 8 MiB now downloads only when played, in
  partial files, off the main actor, and a cancelled load retries.

Behavior fixes
- "Choose another device" on the off card releases the device from the
  lane (new deviceDetach) and never deletes a simulator.
- Agents can no longer delete installed simulators; only a user client.
- A device swap inside one viewer no longer stops the new stream.
- A show request is not replayed after the surface opened; the chat
  pane waits for the drawer to be on screen; lane-less shows use the
  pane's lane; the floating player checks the machine too.
- Picture in picture survives a stream hiccup; a hidden pane stops
  polling; an offline proof tile reloads when its machine returns.
- An unfiled recording can be attached in place; an ownerless ingest is
  refused before it is stored; a helper exit reports stopped
  recordings; a remote viewer's bitrate cap is lifted when it leaves.
- The phone's simulator chip requires the lane's own device.
- `apple key --device U return`, Windows delegation (hidden console,
  no %VAR% re-expansion), ADE_CLI_ENTRY_PATH in the agent env,
  case-safe lane and artifact path checks, the account-directory relay
  binding is required.

Structure
- Extracted: proofFingerprint, appleRecordingsStore, simulatorPower,
  fileRange, pendingInputRequest (shared), adeCaptureRegistry,
  syntheticCallerId, cliGlobalArgs, Apple CLI help (about 1,100 lines
  out of cli.ts), useAppleDeviceStartTracker, useArtifactPreview,
  useWorkShowRequests, useChatPaneShowRequests, appleDeviceModelLoader,
  and the iOS lane-tools presenter.
- One Range parser, one MIME table, one show-on-screen check, one
  lease key, one power map; dead code and stale comments removed.
- Docs and skills describe the loopback media server, the Common tasks,
  and the new rules. The cliBrowserGrammar count now names the three
  new argv readers, and the entry guard callback is a named function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…big files

A second review of the first pass found no Blocker, High or Medium
correctness issue. This commit fixes the Low items and the leftover
structure work.

Correctness
- An ADE capture keeps its label once: the registry entry is used up on
  a match, so attaching the same capture again is refused as a copy.
  The broker files a capture whose bytes changed after ADE hashed them
  as an attach.
- Deleting an installed simulator is a user-only action: a separate
  list, never shown to agents, refused to agents, automations and
  CLI/TUI processes that have no chat. The desktop's own connection
  stays a user client.
- deviceDetach follows deviceStop's owner rule; the off card passes the
  pane's owner setting.
- Proof owners: automation runs, PRs and issues count again, through
  one hasDrawerOwner rule shared by the server, CLI and broker.
- A held show answer keeps `opened`; lifting a bitrate cap no longer
  races a rejoining phone; the floating player's handover timer does
  not stop a second device on the lane.
- Artifact paths fold case only for Windows-shaped roots; a late phone
  download writes its own file, so it cannot delete a reopened chat's
  video.

Structure
- laneDeviceLifecycle.ts out of iosSimulatorService.ts; one power-off
  path (the registry uses it too); one recordings store; one file hash.
- AppleDevicePane.tsx 1143 -> 993 lines (useAppleLaneDeviceList,
  useAppleInspect); AppleDevice3DView.tsx 1230 -> 931 lines
  (appleDeviceScene.ts).
- Lease keys carry their lane scope; one laneOnMachineKey; one lane
  rule for show requests; shared pathCase, syntheticCallerId and
  artifactImageSrc; dead exports and orphaned comments removed.
- iOS: chip names live in the chip kind; one duration formatter
  mirrors the desktop; the eager-video branch that never ran is gone.

Checks: desktop, CLI and account-directory typechecks clean; 15,695
tests pass across main, renderer, CLI and account directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… gate

A third review of the second pass found one Medium and thirteen Low
items. This commit fixes all of them.

- deviceDelete checks the session owner first (on ignoreOwnership, not
  force). An agent in one chat can no longer detach or delete another
  chat's device through `delete --force`. `ade apple device-delete`
  now sends the caller's chat id and accepts --ignore-ownership.
- User-only verbs are allowed only for the desktop's own client names,
  kept in one list (isDesktopClientName) that the minting sites use
  too. An unknown or missing name, or any CLI name, is refused.
- A capture's registry claim is handed back when filing fails or the
  call is not a pure capture, so a retry keeps the ADE label once.
- Automation action lists hide user-only verbs.
- The pane's device callbacks list ignoreChatOwnership as a dependency;
  the device list reports booted reads to the start tracker without a
  ref cycle.
- The CLI's global value flags and their handlers are one typed table;
  the registry's delete no longer detaches (the lifecycle does); cap
  generations are explicit; provenance policy moved into the judge;
  leftover aliases and exports removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…filing

A fourth review of the third pass found eight Low items. This commit
fixes all of them.

- deviceDelete re-checks the session owner inside the device queue for
  a clone, so it cannot race another chat's start; the attached path
  hands the caller's own identity to deviceDetach, which re-checks in
  its queue. `force` never bypasses ownership.
- The broker files a multi-input ingest in one savepoint: a failure
  part-way rolls every row back, the staged files are discarded, and
  the capture claim can be handed back safely. Events go out after
  the commit.
- A delete refused because the device became attached while it waited
  now says so and points to device-detach.
- The desktop client names live in shared/runtimeClientNames.ts; a
  capture match carries its own release(); automations use one
  createAutomationAdeActionLookup; one readIgnoreOwnershipArg; dead
  exports made private. cliBrowserGrammar counts the new reader (111).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- deviceDelete reads the lane's device once, inside the lane's queue. An
  attached device is refused before the stream stops, or detached with force.
  The check before the queue and the hint rewrap are gone.
- deviceDetach and deviceDelete share detachStep.
- close-device reads the ownership flag inline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- deviceDelete passes the udid it read to the registry. The registry does
  nothing when the lane holds another device by then, because the standalone
  attach and create verbs are not queued.
- Tests pin the no-device early return and the unforced "read once" case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…scoped delete

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S and Windows parity

- computerUse: the byte-range and stream-protocol tests join the media
  server suite; the provenance and MP4 creation-time tests join the broker
  suite. The folder goes from 8 test files to 4.
- ios: the simulator-power and lane-device-lifecycle tests join the lane
  device registry suite. chat: the two single-parent helper suites join
  agentChatService.test.ts. No test case is lost (counted before and after).
- CLI: typed `ade apple device-detach`, a help page for `stop`, and help that
  matches the current flags.
- iOS: older-host range-read fallback helper and tests; laneDevice decoding.
- dev-detached.mjs finds npm on Windows, hides the console, and reports a
  failed start instead of printing "undefined".
- Windows CI runs the new path, media server, shim and delegation tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… suite

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e code

- Apple device: detach/delete decide in the lane queue; the user-only gate;
  the turn-time hint, `apple show`, `type --submit`, `key`; one source map
  row per file.
- Computer use: the IPC names are `ade.computerUse.*`; the media server,
  fingerprint and byte-range modules are in the source map.
- Proof, chat, terminals, agents, sync and ARCHITECTURE: the capture registry,
  pane show requests, CLI delegation and the new modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An automation's action list hides the user-only verbs it would refuse.
- A booted event shows the lane device card as running at once.
- A tool is on screen only when its own pane has a layout, on its own machine.
- Deleting a lane's clone powers it off once, through the power path.
- A proof whose only owner is a PR, an issue or an automation run is stored.
- iOS: the preview-versus-play video decision, the failed-load outcome and
  the chip symbol move into plain functions, with tests.
- Remove a duplicate type import that the test merge added.

Each new desktop and CLI test fails when its fix is reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved:
- account-directory: keep this lane's environmentSection reader and the
  manual-redirect assertion; take main's USAGE_RESEARCH_* warning test.
- adeCliService: main's cleanup of a missing bundled CLI, plus this lane's
  ADE_CLI_ENTRY_PATH rule when the CLI is present.
- laneService: keep both getLaneIdForPath and main's findLaneIdentity.
- AgentChatPane: main's proof section classes, with the show-request ref.
- agentChatService.test: keep both appended suites.
- cliBrowserGrammar: 112 argv readers (this lane's 111 plus main's
  buildChatLaunchPlan).
- iOS: WorkToolsRow.swift stays deleted; main's "pr" display name moves to
  WorkLaneToolChips.swift.
- The ade ui show proof test gives the chat one artifact, because main's
  drawer shows a proof section only when the chat has proof.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… no proof

After the merge of main, the chat actions drawer mounts the proof section
only when the chat has proof, and a visible grid tile that is not focused
keeps no proof snapshot. A show then waited 3 s and told the agent the
window was not in front, which was false.

- A show re-reads the chat's proof, mounts the proof section even with no
  proof, and says "This chat has no proof yet." when there is none.
- The drawer scrolls the proof section into view, so "shown" means visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urvives a held delivery

- The proof section a show mounts waits for the forced proof read, so
  "shown" and "This chat has no proof yet." both follow loaded data, and a
  tile that loses its snapshot never claims the chat has no proof.
- The drawer scrolls to the proof once per show, not on every capture.
- The pane resets its drawers for a new chat before the show handler
  registers, so a show held while the chat was out of view is not closed
  again in the same commit.
- The show tests clean up in finally and cover a read that has not returned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hing

- Each proof show takes a token; closing the drawer or changing chat
  invalidates it, so a read that returns later does not reopen the section.
- A failed read from a show keeps the proof already on screen.
- A test pins "scroll once per show" and the late read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed read writes nothing

- keepOnError keeps the snapshot only when it belongs to the chat being
  read, so a failed read after a chat switch never shows another chat's
  proof as this one's.
- A read that a newer read replaced neither writes its older data nor
  clears the newer snapshot.
- A test pins that a failed show re-read keeps the chat's proof on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arul28
arul28 marked this pull request as ready for review September 23, 2026 23:55
@cursor

cursor Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_f0f3716d-356d-4d0e-b431-d2baea52f687)

…; annotate a case-folding gate

- resolveCliDelegationTarget and canonical use path.win32 or path.posix
  for the platform they are given, not the host's. On the Windows runner
  the darwin-layout test joined POSIX paths with backslashes and missed the
  packaged bin/ade -> ../cli.cjs sibling.
- laneService.test.ts: the case-insensitive lookup test carries a
  WINDOWS-GATE note, because the Windows fold is pinned by pathCase and
  pathCompare in windows-foundation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
arul28 added a commit that referenced this pull request Sep 24, 2026
…1299

The Apple lane brought its quality passes (split files, a capture
registry for proof, a new show-request hook, a lane-tool presenter on
iOS), a merge of main (the PR tool, router usage, activity detail), and
proof-read fixes.

45 conflict blocks in 23 files. Each keeps both sides; where the parent
moved or split code, this lane's Mac Desktop additions moved onto the new
structure:
- CLI: the capture registry gets mac_desktop.screenshot, so `mac-desktop
  proof` still files as an ADE capture; show labels and workToolShowPlan
  follow the parent's move; Apple and Mac Desktop share one activity type.
- Renderer: Mac Desktop show handling moves into useWorkShowRequests; the
  floating player reports "shown" through the key-based check; both the
  PR tool and Mac Desktop are Work tools; the backdrop keeps main's newest
  recipe (header slice) plus this lane's perf savings.
- iOS: the Mac Desktop chip follows the parent's data-carrying chip kind,
  and the lane-tool presenter opens MacDesktopViewer.

Grammar count 112 → 113 (buildMacDesktopPlan). Type-checks clean; Swift
150 passed; desktop renderer 10,056 and main 10,807 passed; CLI 4,996
passed. Under full-run load, a stdio daemon test and two Codex watchdog
tests failed; each passes alone, and the chat test file passes 1,277 of
1,277 twice. The old "mobile sync host is still retrying" test still fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…review-and-live-stream

AgentChatPane: this lane moved the session reset effect above the show
handler, and main changed its body to keep the pending bubble of the chat
just selected. Main's body now lives in the moved effect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@arul28
arul28 merged commit ed21a34 into main Sep 24, 2026
38 of 39 checks passed
@cursor

cursor Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_39979aac-abf4-4c6b-b57b-96c802f19390)

arul28 added a commit that referenced this pull request Sep 24, 2026
#1299 (the Apple device tool, this lane's former parent) was squash-merged
into main as ed21a34. Its content equals the parent tip 02c8cf8 that
this lane already contains, plus a merge of #1301 (send-handoff
animation). 27 files conflicted only because the squash commit and this
lane's history carry the same parent changes: for the 26 files main did
not change after the parent tip, this lane's version is kept; for
AgentChatPane.tsx, #1301's change is applied on top. The merge adds
exactly main's diff since the parent tip (checked file by file).

Type-checks clean; chat, terminals, apple, RPC, work-tools and CLI
mac-desktop suites pass.

Co-Authored-By: Claude Opus 5.5 (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