…tation
Both iOS snapshot backends carried their own copy of the visibility fold: the
tree walker and the private-AX serializer each computed viewport-and-scroll-clip
intersection, ancestor projection, hidden-content hints, and collapsed depth
during acquisition. Hand-synchronized copies of that interpretation are what
produced the scroll-overflow leak class (#1784), and C1 (fact-availability
neutrality) could not hold while acquisition decided what a screen shows.
Acquisition backends are now fact serializers: every traversed node is emitted
at raw traversal depth with its reported frame, and SnapshotAcquisition carries
the viewport. presentRegular runs the one clip fold for every backend --
viewport ∩ scroll clip, the ancestor cursor (an out-of-clip Cell or scroll
container hides its clamped descendants), the sub-pixel decoration rule,
scroll hints booked onto anchors, reparenting with collapsed depth -- and
narrows the emitted hittable to the clip: nothing outside its clip, and nothing
without geometry, is ever hittable, whatever the backend reported. Platform
differences are a SnapshotFoldPolicy input to the shared algorithm (iOS
cursor-projected; macOS/tvOS plain viewport), never a backend exception.
The private-AX backend collapses to ONE serializer for both projections, and
the flat filter-decision family dies with the acquisition gates it fed.
Three intentional edge deltas, each toward one backend-neutral rule: sub-pixel
content-free decorations now drop on every backend (was private-AX only);
labeled offscreen Application/Window carriers survive on every backend (was
tree only), never hittable; query-sweep regular without -i is viewport-folded.
Declared acquisition residues: the traversal-depth budget cut, the sweep's
frameless-element drop, the private-AX bridge's device-side cap.
Refs #1797 (migration step 3, clip-fold delta).
Summary
Next #1797 delta (after the raw/regular split, #1926): the clip fold moves into presentation. Both iOS backends carried their own copy of the visibility fold — the tree walker and the private-AX serializer each computed viewport∩scroll-clip intersection, ancestor projection, hidden-content hints, and collapsed depth during acquisition. Hand-synchronized copies of that interpretation are the mechanism behind the scroll-overflow leak class (#1784 was fixed by making one copy call the other's helpers — correct, but nothing stopped the next drift), and C1 (fact-availability neutrality) cannot hold while acquisition decides what a screen shows.
After this PR, acquisition backends are fact serializers. Every traversed node is emitted at raw traversal depth with its reported frame;
SnapshotAcquisitioncarries the viewport;presentRegularruns the one fold for every backend:Cell/scroll container hides descendants whose clamped frames would otherwise leak back into the viewport — fix: stabilize private AX settle snapshots #1784's shape, now owned by one implementation),hittableis narrowed to the clip — nothing outside its clip, and nothing without geometry, is ever hittable in a regular snapshot, whatever the backend reported.Platform differences enter as a
SnapshotFoldPolicyinput to the shared algorithm (iOS cursor-projected; macOS/tvOS plain viewport intersection) — per the issue's rule that macOS is a platform policy, never a backend exception. The private-AX backend collapses to one serializer for both projections, and the flat filter-decision family (FlatSnapshotFilterNode/FlatSnapshotVisibilityPolicy) is deleted with the acquisition gates it fed.Behavior
Live A/B, byte-identical. Settings on iPhone 16 Pro / iOS 26.2, this branch's runner vs a runner built from
main, each on its own state dir and daemon, compared on full node signatures (type, label, identifier, value, rect, depth, parentIndex, hittable, both hidden-content hints):snapshotsnapshot -isnapshot --actionssnapshot -i --actionsThree intentional edge deltas, each replacing a per-backend rule with one backend-neutral rule (none fired on the live screen above):
Application/Windowcarriers now survive on every backend (was tree only) — still never hittable.-iis now viewport-folded like everything else (it used to list offscreen elements withhittable: false).Declared acquisition residues (budget/fidelity, not membership): the traversal-depth cut (regular presents collapsed depth — the open visible-depth frontier obligation, unchanged), the sweep's frameless-element drop (a flat query has no hierarchy for geometryless semantics to attach to), and the private-AX bridge's device-side node cap. Two mechanical notes: the private-AX backend-level sparse gate (
nodes.count <= 1) now counts serialized rather than folded nodes, so a fully-clipped tree surfaces through the plan's sparse classifier on the presented payload instead of a backendnil— same terminal outcome, slightly different verdict path; and children of a dropped duplicate inherit the ancestor cursor rather than the duplicate's own transition (observable only for a duplicate offscreen owned-container whose children aren't themselves duplicates).Evidence
presentRegular, plus the feat(ios): split raw and regular snapshot projections behind one capture hint (#1797) #1926 suite. macOS bundle compiles with unit tests (host lane doesn't run on this machine — environmental, verified both ways on feat(ios): split raw and regular snapshot projections behind one capture hint (#1797) #1926; CI covers it).presentRegularbypassingfoldRegularVisibility, exactly the six fold-dependent tests fail (both new fold tests and both private-AX regular tests — the drawer fixture reproduces the leak: "Admin settings" from the offscreen drawer is presented) while eligibility and scope tests stay green. Restored before commit.pnpm check:xctest-selectiongreen (172 declared, 0 unreachable); new tests added to the iOS PR lane;check:affected --rungreen; oxfmt/oxlint clean.Docs
ADR 0004: the fold as the fourth semantic layer, the policy input, and the declared residues.
CONTEXT.md: clip fold vocabulary. CHANGELOG: user-facing entry with the three edge deltas.What #1797 still needs after this
hittable= geometric actionability — the tree backend still computes its occlusion scan at acquisition (computedSnapshotHittable); moving occlusion wholly to the daemon annotator is the next delta, with the ~118-referencehittable-consumer inventory.treepin + penalty-clear hook, parity ratchets.RawAXNode → Presentedconformance suite (Android native-a11y → snapshot: conform to the acquire/present engine contracts (sibling of #1797) #1832 step 9).Refs #1797 (migration step 3, clip-fold delta).