Repository navigation
Conversation
Size Report
Startup median (7 runs, lower is better):
|
There was a problem hiding this comment.
All reported issues were addressed across 6 files
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
aaaafd1 to
4f570d9
Compare
ef18cc2 to
6fa19c6
Compare
9f5a978 to
e7929c3
Compare
e7929c3 to
9af006d
Compare
|
Re-cut the layer boundary to fix the failing |
|
Thanks for the PR. Two things need to change before this can merge, at 9af006d. The presentation vocabulary now has two owners. engine.ts:277 declares This head publishes The thread on duplicated indexes from cubic-dev-ai (#3578 (comment)) is fixed at this head, because Both Smoke Tests jobs are still queued, not failed. This was a read-only review, so I did not run the parity tests, |
9af006d to
777d9b7
Compare
Add the rule that answers whether an iOS capture read a modal presentation over the app's own content, the golden table that pins it, and the publication that carries its answer, so the rule has a production consumer the moment it lands. The rule reads a dimming view without its enablement, unlike the occlusion cut that shares the same container class: a sheet resting at an undimmed detent keeps a dimming view that takes no touches and is still presented. `absent` is the strong answer and never the default, because a consumer arms a dismissal confirmation on it. It requires that the producer stated the capture was not capped, that every node carries the UIKit class name the rule reads, that each node is numbered once so the parent links the walk follows name the node they mean, and that the one content window under the app root holds exactly one presentation container. Positive evidence — the dimming view a presentation owns, an alert's own root class, or an element type that names a presentation — answers `presented` from a single node, so a capped capture still reports what it read. Producers that publish no class names, which is every XCTest runner tier, answer `presented` or `unknown` and never `absent`. The rule lives in `geometry-policy.ts` beside the occlusion cut rather than in `engine.ts`, because that module already owns the UIKit presentation vocabulary both readers consume and `geometry.ts` already imports it on the engine's eager path, so the placement costs nothing and leaves one module to change when UIKit does. Both readers now match class names through the same normalization, so a producer that spells a class with a module prefix cannot reach one answer in the occlusion cut and another here. Evidence is matched on the node occurrence a walk stood on, not on the number it carries, so a keyboard host's subtree cannot swallow an app window's own dimming view; only `absent`, a claim about every window the producer read, requires numbering that identifies each node once. The publication is in this layer rather than one up because `check:production-exports` refuses an exported rule with no production consumer, and a layer that ships a decision nobody can read is not a layer that passes. The Apple runner's Swift twin and its own consumer land in the next layer, where the runner acquires trees; both languages read the one fixture published here.
777d9b7 to
ecd8cb6
Compare
|
The code now looks right at ecd8cb6, but the live run from the earlier review (9af006d, #3578 (comment)) is still missing. All 19 checks pass at this head. The cubic-dev-ai thread on repeated numbers in Fixed since 9af006d: the repeated-number exclusion now keys on node occurrence, Still open is the live run. This head publishes I reviewed this read-only and did not run vitest, Not blocking: the PR body still says the TypeScript rule lives in Before merge, the live simulator run above needs to land in the PR. |
Summary
Adds the rule that decides whether an iOS capture read a modal presentation over the app's own content
(#3565), the golden table pinning its answers, and the publication that carries them. Layer 2 of 3. 8
files, +951/−2.
The rule and the publication are one layer on purpose:
check:production-exportsrefuses an exportedrule with no production consumer, and it is right to. A layer that computes a fact nobody can read is
not a shippable unit, so the first consumer is here rather than one layer up.
The TypeScript rule lives in
ios-snapshot-engine/engine.ts, not its own module. A new module theengine imports is a new eager edge that ADR 0027's no-growth rule refuses — thirteen carried entries
would have grown by one module and 70 eager statements, and the rule is pre-existing code, so no split
explains it. The engine is also the module that publishes this fact and already derives the
completeness statement the rule reads, so the placement is not a workaround for a count.
absentis never the default, because #1831 arms a dismissal confirmation on it. It requires that theproducer stated the capture was not capped, that every node carries the UIKit class name the rule
reads, that each node is numbered once so the parent links the walk follows name the node they mean,
and that the one content window under the app root holds exactly one
UITransitionView. Positiveevidence — a
UIDimmingView, an_UIAlertController*root class, or aSheet/Drawer/Alert/Dialogelement type under its name or its rawElement(<n>)spelling — answerspresentedfrom onenode, so a capped capture still reports what it read. Producers that publish no class names (every
XCTest runner tier) answer
presentedorunknown, neverabsent. Completeness arrives as'complete' | 'capped' | 'unstated'from the producer's truncation statement, never inferred from thetree. The rule reads a dimming view without its enablement, unlike the covering-presentation cut in
geometry-policy.tsthat shares the container class — no parallel classifier, the same vocabularyextended at that owner.
Evidence is matched on the node the walk stood on, not on the number it carries. Excluding a
keyboard host's subtree therefore removes the occurrences that walk reached, and an app window's own
dimming view that happens to share a number with one of them keeps its evidence. Numbering is only
required for the strong answer:
absentclaims something about every window the producer read, andparent links cannot tell two nodes of the same number apart, so a producer that numbered a node twice
cannot support that claim. The most a collision costs is the answer the rule declines to make.
Two choices worth a reviewer's eye: system windows are a denylist (an unlisted host carrying a
dimming view of its own would read
presented; none appears in the captures taken here) so an apphosting its scene in a
UIWindowsubclass stays readable; andElement(<n>)must be bare digits,because
Int("+7") == 7would otherwise let each language's parser decide the answer.The Swift twin, its parity test, and the runner's own reading of its acquisition are in layer 3, where
the runner acquires trees. Both languages read the one fixture, which is here, so the layer-3 parity
test cannot drift from it.
Evidence coverage (iPhone 17 simulator, RN test app)
Captured live through the stack's final state at
c06d8ec9a. Rows marked live were read off the device;the rest are golden cases, said so plainly. Every row here is produced by this layer's TypeScript engine,
which is the producer the CLI talks to on a simulator — the runner half lands in layer 3.
absent--rawand every projectionabsentpageSheetsheet — window holds 2 containers, dimming views at 4 and 134presentedpresentedunknown--actions, 0 class names published)unknownpresentedAlert.alert(own window, no dimming view)presentedElement(7)presentedunknown.sheetpresentedexpectedresolveBlockingSystemModalstamps.presented(layer 1)unknown, orpresentedon evidence it did readTwo things the live run is worth stating out loud. The dev-menu floating button puts a real
EXDevMenu.DevMenuFABWindowbeside the app window, and the honest answer for that screen isunknown—the four
absentrows above were taken with it disabled(
defaults write com.callstack.agentdevicelab EXDevMenuShowFloatingActionButton -bool NO), which is thesingle-content-window shape the rule reads absence from. And the interactive cut of the open sheet
publishes four nodes, none of them a dimming view, while the fact still answers
presented: that is thefailure mode this fact replaces, since a consumer reading the published tree would have had to answer
absenton a screen with a sheet over it.Validation
777d9b73a, verified at this head rather than at the stack top:check:production-exportspasses (thehead before this re-split failed it, reporting
collectPresentedSurfaceas an export with no productionconsumer),
swift buildis clean,pnpm buildandpnpm typecheckare clean, and vitest overpackages/capture-kit,packages/kernelandpackages/platform-appleis 2,963 green, eager-closurebudgets included. A repository-wide local gate is deliberately not claimed for this head: four other
worktrees on this host are running subprocess-heavy suites at the same time, and the failures that would
produce earlier in this stack — an ADB connect failure, a daemon startup timeout, one abort-timing case —
were contention from exactly that, each passing in isolation while failing under load. What the previous
head genuinely failed was
check:production-exports, which is the check above.The 18-case
contracts/fixtures/ios-presented-surface.jsongives each case a class-aware and aclass-blind answer, and both parity tests read that one file, so drift in either language or vocabulary
fails. Claims outside the vocabulary are pinned by cases and confirmed by mutation: the element-type
numbering is held at its owner (
tree.test.tsdecodes each raw value through the bridge decoder andcompares to the table); completeness is pinned on a class-aware tree, the only shape that reaches the
absence branch; a cycle case pins the visited guard (without it, classification spins forever); a
partially class-blind tree — the shape the bridge emits when it reads no class — pins the class-name
requirement (delete it and that case answers
absent); and three malformed-numbering cases pin theboundary just described — an app-side dimming view sharing a number with a node the keyboard-host walk
reached keeps its evidence and answers
presented(it had been assertingunknown, which was assertingthe bug), one hanging under the shared number answers
unknownbecause that one genuinely isambiguous, and neither can earn
absent. Publication is pinned byengine.test.ts, whose depth-1 rawcase now carries structural depths and asserts the cut really dropped both container and dimmer while
the fact still answers
presented, and byios-snapshot-runtime-presented-surface.test.ts, which failswhen the runtime seam drops the field.