Skip to content

refactor(ios): build and prove the converged snapshot engine #2191

Description

@thymikee

Parent: #2188

Blocked by: #2190

Outcome

Implement and prove the deep IosSnapshotEngine module behind #2190's two-operation interface.
Swift and TypeScript geometric presentation must agree through independent evidence, and the current
XCTest output must cross the engine without changing live producer selection or observable behavior.

This issue owns one outcome but should land as narrow stacked PRs in the order below. Do not collapse
the stack into one cross-language diff.

Ordered implementation slices

A. Extract the pure Swift presenter

  • Move the reusable geometric presentation implementation into apple/snapshot-presentation/.
  • Make the existing runner target and a standalone conformance harness consume the same source;
    never copy the production implementation into the harness.
  • Keep macOS/iOS differences as explicit fold-policy input rather than backend branches.
  • Preserve current runner output and focused XCTest coverage.

B. Implement the TypeScript engine

  • Implement pure planning/publication under
    packages/capture-kit/src/ios-snapshot-engine/ with one package export.
  • Own viewport and cumulative-clip folding, regular/raw projection, scope, presented depth,
    eligibility, effective geometry, actionability narrowing, hidden-content hints, lineage,
    quality/residue output, and invariants.
  • Move existing iOS semantic compaction behind the engine while preserving row/noise/keyboard,
    transition, scroll, action-shelf, and representative behavior.
  • Keep pure vocabulary in contracts; no algorithms move back into @agent-device/contracts.

C. Establish conformance and enforcement

  • Add an independent golden corpus covering hint derivation, nested clips, viewport edges, missing
    rectangles, regular/raw, scope hit/miss, scoped and unscoped depth, wrapper reparenting, hidden
    content, interactive-only, unavailable hittability, malformed graphs, residue, truncation, and
    iOS/macOS fold-policy differences.
  • Add deterministic Swift/TypeScript differential fuzzing with shrink/replay artifacts.
  • Add properties for interactive ⊆ regular ⊆ raw, graph order, clipping, actionability,
    missing-fact neutrality, lineage, comparison identity, and runner-payload validation.
  • Add a structural gate proving every acquired input is presented exactly once, every presented
    input skips geometry exactly once, every runner payload crosses host invariants, and semantic
    compaction runs exactly once.
  • Observe the structural gate red against a real planted violation before relying on it.

D. Integrate the current runner without changing routing

Only after slice C's planted-red gate is observed failing and restored green:

  • Adapt the existing runner output as stage: 'presented' and pass it through host invariants and
    semantic compaction.
  • Carry runner payload plus optional unscoped qualityPayload; validate both, use the latter only
    for classification, and publish only payload.
  • Preserve current producer selection, fallback, public output, errors, warnings, timing ownership,
    and physical-device behavior.
  • Do not route any live source to a new producer in this issue. Phase exit is explicitly “no live
    routing changed.”
  • Amend ADR 0004 after the implementation exists so host snapshot behavior is owned by
    @agent-device/capture-kit; record the acquisition/presentation and eligibility/publication
    vocabulary without copying implementation paths into the ADR.

Differential lane contract

  • Catches: drift between Swift and TypeScript capture-hint derivation, geometry, projection,
    depth, scope, actionability, lineage, failure reasons, and stage invariants.
  • Evidence: independent golden expectations, deterministic seeded differential cases, shrunk
    reproducible failures, and an observed planted-red mismatch. Neither implementation may generate
    expected output for the other.
  • Cost: one path-selected macOS slot for the deterministic corpus and bounded seed budget on
    relevant PRs. Record runtime and artifact size before making it blocking. Keep wider seed rotation
    scheduled if measured cost is unsuitable for PRs.
  • Kill criterion: remove the dedicated lane only when the same conformance command, path
    selection, failure artifacts, and planted-red enforcement are absorbed by an existing macOS gate.
    Coverage may move; it may not disappear.

Non-goals

  • No Appium, Limrun, or Simulator bridge migration.
  • No runner-demand/open/relaunch change.
  • No public CLI flag, daemon RPC requirement, new workspace package, code generation, or physical
    device behavior change.
  • Do not delete generic scope, normalization, refs, quality, occlusion, or Android policy consumed
    by other platforms.

Acceptance and evidence

  • Swift and TypeScript agree on the complete independent corpus and agreed fuzz seed/time budget.
  • Every mismatch stores a minimal reproducible case and exact replay command.
  • stage: 'presented' malformed payload or quality payload fails typed before publication.
  • Missing/underivable viewport fails typed for regular projection; it never publishes an unfolded
    actionable tree.
  • Presentation compute and response-size measurements are reported against test(ios): establish snapshot convergence baselines and permanent evidence #2189's schema.
  • The runner integration is characterized before movement and produces no intentional output delta.
  • Focused TS, Swift host, XCTest-selection, structural, layering, and affected gates pass; native
    Simulator evidence remains a separate recorded lane.

Worker stop conditions

  • Stop if integration requires live backend selection or a public wire change; that belongs later.
  • Stop if golden expectations are derived from either implementation.
  • Stop if a rule needs a producer-name exception instead of typed evidence or fold policy.
  • Stop if the macOS lane's measured cost has no declared cadence or kill criterion.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions