Route snapshot capture sources through one platform-neutral router
Mantenedores costumam responder em até 1 dia
Ninguém assumiu esta issue ainda.
Avaliação
- Dificuldade
- 5/5
- Tempo estimado
- Mais de uma semana
- Facilidade para iniciantes
- 5/100
- Tipo de issue
- Refatoração
- Clareza
- Razoavelmente clara
- Status de atividade
- Ativa
- Stack de tecnologia
- android, ios, macos, typescript
Direção de pesquisa
Start with the iOS route core in packages/platform-apple/src/snapshot-route.ts and its failure policy in snapshot-route-failure-policy.ts, then the constant ports in packages/platform-apple/src/runner-runtime.ts. The ADR decision on Android's system-UI annotation and SurfaceProof must land before any code moves, and the work is blocked by #4182 on unmerged runner layers. Done means one router in packages/capture-kit serves all channels, with no constant-filled ports and the existing route tests passing unchanged.
Escrita pelo modelo de indexação a partir do texto da issue.
Descrição
Purpose
Snapshot capture source selection must be one product rule for every platform and provider. Today the rule exists only for iOS, in packages/platform-apple/src/snapshot-route.ts, and each channel fills the route's ports with placeholders. Android, macOS, web, WebDriver and Revyl each have one hard-wired source with no shared fallback, refusal or proof vocabulary. Producer facts live in about six owners.
Current state (evidence)
- The iOS route core:
packages/platform-apple/src/snapshot-route.ts(probes, target resolution, acquisition, one fallback, identity stamping, warnings) andsnapshot-route-failure-policy.ts(refusal scope). - The runner channel (#4182) fills the route's ports with constants:
isDiscoveryPending: () => false,targetFreshness: 'cached',systemSurfacePresent: async () => 'unknown'(packages/platform-apple/src/runner-runtime.ts). The route reads them atsnapshot-route.ts(discovery wait, cached-target refresh, presence probe). - Eligibility is split:
isEligiblein the route, the observe-only bypass inrunner-runtime.tsbindBridgeSnapshot, and thepreferredBackendverdict pin inpackages/capture-kit/src/snapshot-quality-verdict.ts→stable-capture.ts. - Producer facts:
packages/contracts/src/ios-snapshot.ts, three producer-keyed tables inpackages/capture-kit/src/ios-snapshot-acquisition.ts,packages/kernel/src/snapshot.ts(SnapshotProvenance),packages/command-runtime/.../stable-capture.ts, producer switches inpackages/command-runtime/src/commands/capture/runtime/snapshot.ts. Revyl's Android tree is labeledappium-source, the same name WebDriver uses for iOS. - Single-source platforms: Android
packages/platform-android/src/snapshot-capture.ts, macOSpackages/platform-apple/src/os/macos/surface-snapshot.ts, Revylpackages/provider-revyl/src/snapshot.ts, WebDriverpackages/provider-webdriver/src/webdriver-ios-snapshot.ts. validateTargetis duplicated insnapshot-source/adapter.tsandrunner-snapshot-bridge.ts. Three fallback settlers exist:runFallback,runSurfaceFallback,captureOffRoute.- ADR 0004 is about 60 KB.
docs/adr/FORMAT.mdasks for about 8 KB.
Required behavior
-
A new ADR, "Snapshot capture source routing", states the rule for all platforms. ADR 0004 keeps only the iOS facts (bridge, runner plan, system-surface hosts) and drops its amendment history.
-
One platform-neutral router in
packages/capture-kitowns: the source plan (plan(input)returns the source and the reason), one fallback per capture, the per-target generation circuit, the refusal table (capture scope vs generation scope), occlusion proof, identity stamping, and warnings. It imports no platform package. -
Each platform or provider declares its channels. A channel declares only what it has. A channel never fills a port with a constant.
type SurfaceProof = (device, signal) => Promise<'clear' | Readonly<{ occluded: string }> | 'unproven'>; type SourceChannel<T> = Readonly<{ producer: SnapshotProducer; acquire(req: { target: T; hint; signal }): Promise<SourceOutcome>; resolveTarget(device, app, signal): Promise<T>; refreshTarget?(device, app, signal): Promise<T>; // replaces targetFreshness awaitDiscovery?(device, signal): Promise<void>; // replaces isDiscoveryPending proof: 'none' | SurfaceProof; // replaces the presence/foreground pair supports: { customActions: boolean; observeOnly: boolean; pinnedBackend: boolean }; }>; -
Owners with more than one channel route through the router: iOS local and iOS provider (Limrun) Simulators (bridge, then
apple-runner). Every one-channel owner (Android, macOS, web, WebDriver, Revyl, HarmonyOS, Linux, TV CDP, composing providers, physical iOS, tvOS, visionOS) does not route through the router. It stamps identity through the capture-kit helper and declares its registry row. An owner that gains a second channel moves to the router. -
One producer registry in
packages/contracts, declared withsatisfies Record<SnapshotProducer, ProducerFacts>(channel, truncation, residue, quiet time, ref-namespace owner). The producer union is platform-neutral. Every other producer table derives from the registry. -
The refusal fixture
contracts/fixtures/ios-snapshot-bridge-refusals.json(#4182) becomes the router's refusal table input for every channel that emits typed refusals.
Completion conditions
git grepfinds no route port filled by a constant (() => false,async () => 'unknown') and notargetFreshness.- A layering rule fails when the router module imports a platform or provider package.
- A repo guard fails when a producer in the registry has no production emitter.
- A repo guard fails when an ADR cites a file path or test name that does not exist.
- Existing route behavior tests pass unchanged against the router, for the guest and runner channels. No step deletes, skips, or loosens a behavior assertion to pass.
- Each step removes the path it replaces in the same PR. No dual routing, no compatibility shim, and no adapter that wraps the old route.
- Every router interface member has a real implementation in one of the two iOS Simulator channels. No member exists for a hypothetical owner.
- Each step leaves the touched code simpler: fewer owners of a rule, fewer settlement paths, or fewer producer tables. Reviewers judge this, not line count. A step that only moves code does not merge.
- Live check: local iOS Simulator, Limrun iOS, local Android emulator. Each capture names the same producer and fallback warnings as before the change.
- The new ADR is accepted, and ADR 0004 is under about 15 KB.
Migration plan (moved from the ADR 0038 proposal, #4255)
After acceptance, each small PR must merge alone:
- Move the provenance carriers and helpers from
packages/kernel/src/snapshot.tsto contracts. Migrate the registry consumers atomically. Keep the node and geometry primitives in kernel. Do not allow a kernel-to-contracts cycle. - Simplify in place first, inside
packages/platform-apple: remove the placeholder ports, merge the three fallback settlers into one settlement path, and give eligibility one owner. Keep the route behavior tests unchanged. - Lift the simplified route core into
packages/capture-kitas the router. Give a source one way to ask for a fresh target: todayrefreshTarget?andresolveTarget(..., 'refresh')both exist, and the stale-target fallback still calls the second (from the #4278 review). Decide this together with the local source's cached-target refresh, so that local behavior does not change by accident. Then lift, with its refusal-table input and the layering enforcement. Bind both iOS Simulator channels to it and delete the Apple route core in the same PR. If the lifted router is not simpler than the in-place result, stop and record why in this issue. - Move the one-channel owners to the identity-stamp helper and the registry rows. Rename the Revyl Android producer to
revyl-android-treetogether with its emitter and consumers. - Make ADR 0004 smaller than about 15 KB. First move its unique contracts to their owners. Keep the iOS rules.
Guard inventory
- Router layering enforcement, with a failing dependency control.
- Producer-emitter and refusal guards in
test/repo-guards/. They reject a reachable production emitter that has no registry row, an unlisted refusal code, and a scope disagreement. - An ADR-citation guard that rejects cited paths and test names that do not exist.
- Select these guards through affected checks, together with docs-prose, docs-links, and agent-guidance.
- Live producer and fallback parity on the local iOS Simulator, Limrun iOS, and the local Android emulator.
Design detail (moved out of ADR 0038)
ADR 0038 records only the decisions. The detailed design is in this issue comment. It covers the SourceChannel<T> members, planner inputs, circuit keys, target refresh and discovery, the per-owner source list, registry row facts, quiet times, and the Android systemSurfaceOnly classification. Move each item to its owning type or package doc when the router lands. Do not move them back into the ADR.
Dependencies
Blocked by: #4182 (runner-served bridge and refusal fixture), which is stacked on the unmerged Limrun runner layers (stack/limrun-native-runner).
Open question: whether Android's system-UI annotation fits the SurfaceProof port. Decide this in the ADR before code moves.
- Linguagem predominante
- TypeScript
- Estrelas
- 5.3k
- Forks
- 377
- Merge médio
- 13h 58min
- PRs com merge (30d)
- 654
Preparar o ambiente
- Sem Dockerfile nem arquivo Docker Compose
- Sem modelo de pull request
- Ler o guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de callstack/agent-device
-
iOS alert wait: the last poll can get ~30 ms and turns "no alert" into a failed readTalvez já em andamento @pcbeingused333 assumiu há 7 dias. Abertabug ready-for-agent
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 77/100
callstack/agent-device#4018 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
callstack/agent-device#1869 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
callstack/agent-device#4274 ·
Mantenedores costumam responder em até 1 dia
-
iOS simulator: in-flight `prepare ios-runner` keeps retrying the old runner port after the runner is relaunched on a new portTalvez já em andamento @twotnguyen assumiu há 1 dia. Aberta
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 35/100
callstack/agent-device#4265 ·
Mantenedores costumam responder em até 1 dia
-
Android smoke: 'keyboard dismissal should change emulator pixels' flakes on unrelated PRsTalvez já em andamento Um pull request vinculado a esta issue está aberto ou já foi mesclado. Abertabug ready-for-agent
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 48/100
callstack/agent-device#4264 · 1 comentário ·
Mantenedores costumam responder em até 1 dia
Todas as issues de callstack/agent-device
Issues semelhantes
-
area:frontend kind:bug priority:p2 ready
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
handsontable/hyperformula#1879 ·
Mantenedores costumam responder em até 1 dia
-
enhancement
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 66/100
RunestoneInteractive/rs#1661 ·
Mantenedores costumam responder em até 2 dias
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
capricorn86/happy-dom#2594 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia