You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8c61aea
Browse filesBrowse the repository at this point in the historyBrowse files
docs(adr): repair 0004 paths, record the RUNNER_BUSY asymmetry, settle 0026 status (#2909)
* docs(adr): fix retired paths, record RUNNER_BUSY asymmetry, settle status
ADR 0004 cited `src/snapshot/**`, retired by the capture-kit move (#2385).
The ownership and boundary sentences now point at the `snapshot-policy`
declaration in scripts/layering/architecture-ownership.ts, and the three
policy bullets name the paths that exist.
ADR 0005 documented runner occupancy and drain but never recorded the
shipped asymmetry: a read-only request waits out `RUNNER_BUSY` and is
resent, while a mutating one is attempted once with nothing replayed. Both
decisions are keyed on the daemon-side `RUNNER_COMMAND_TRAITS` read-only
trait, not on a runner-side classification. The close-and-pooling sentence
keeps its scope so it no longer reads as contradicting that policy.
ADR 0026 was still `Proposed` after #2758 and #2759 shipped its ownership
decision. It is `Accepted`, with the two open #2754 steps named as open
work rather than as the decision.
Closes#2892
* docs(adr): mark the unshipped ADR 0026 rules and narrow two claims
Recording ADR 0026 as Accepted made two unshipped rules read as current: the
"every removed source" row and the ejection ledger in Contracts. Both now name
#2754 step 5 as the open contract, and the `ScrollArea` passages record the
decision #2759 shipped instead of asking for one.
ADR 0004's boundary sentence said the gate covers the facet; it covers only the
roots `snapshot-policy` declares, so the overlay modules beside that tree are
now named as outside it. ADR 0005's keying sentence claimed the runner reports
nothing; it is the runner-side classification of the command that plays no part,
while the refusal code selects the budget.
Copy file name to clipboardExpand all lines: docs/adr/0026-scroll-clip-authority.md
+30-19Lines changed: 30 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,8 +2,12 @@
2
2
3
3
## Status
4
4
5
-
Proposed (2026-09-22). Refines ADR 0004's fact/interpretation boundary for scroll geometry; does not
6
-
supersede it. Implementation contract: #2754.
5
+
Accepted (2026-09-22). The ownership decision is shipped: #2758 reads an indicator's owner off the
6
+
parent edge, climbing only same-frame ancestors — the tolerance the #2754 step 3 survey justified on
7
+
real captures — and #2759 pairs the two eligibility lists and settles `ScrollArea` for macOS. Two
8
+
#2754 steps stay open work rather than part of this decision: the runner-stage differential arm, and
9
+
the typed ejection disposition the "every removed source" row below names. Refines ADR 0004's
10
+
fact/interpretation boundary for scroll geometry; does not supersede it.
7
11
8
12
An interactive iOS snapshot of a list dropped every row after a row holding selectable text. A
9
13
`UITextView` is a UIScrollView and XCTest publishes its scroll indicator inside the text, so the
@@ -22,7 +26,7 @@ walking up past the indicator's parent, and that guess decided which nodes exist
22
26
| Removing a node **because a scroll band hides it**| Requires evidence: the clip fold's result, or a band whose owner is the indicator's parent. A guess about ownership may not do it |
23
27
| Removing a node by semantic delegation — a collapsed row, a duplicate label, a wrapper's content | Out of scope here. That is compaction, and it keeps its own authority |
24
28
| Weaker evidence than a parent edge | Directional hints (`hiddenContentAbove` / `hiddenContentBelow`) and nothing that changes membership |
25
-
| Every removed source |Ends with a typed disposition. Internal evidence, never wire vocabulary |
29
+
| Every removed source |Must end with a typed disposition — the open #2754 step 5 contract, not shipped code. Internal evidence, never wire vocabulary |
26
30
27
31
## Contracts
28
32
@@ -47,25 +51,30 @@ neither eligible set contains it, and the iOS runner never emits it — it origi
47
51
`AXScrollArea` mapping. That helper's trees do reach these rules, through `snapshot-desktop-surface.ts`
48
52
→ `ios-snapshot-runtime.ts` → `publishIosSnapshot`, so on that surface a `ScrollArea` host can be dropped
49
53
by eligibility while its children re-parent past it, which leaves a parent-edge lookup with no owner.
50
-
Reusing this rule for macOS therefore needs a `ScrollArea` eligibility decision made there, not carried
51
-
over from the iOS claim above.
54
+
Reusing this rule for macOS therefore needed a `ScrollArea` eligibility decision made there, not
55
+
carried over from the iOS claim above, and #2759 made it with cases rather than by admitting the
56
+
type for iOS's sake: `scrollarea` stays out of both eligible sets, a `ScrollArea` carrying content
57
+
survives eligibility on those trees and owns its band, and a label-less one is dropped, leaving its
58
+
indicator with no owner to band — which under-clips (safe) instead of mis-clipping the enclosing
59
+
list (`eligibility-parity.test.ts`).
52
60
53
61
**Why the band exists at all.** XCTest reports a scroll view's frame spanning the bars and the safe
54
62
area, not the visible track. In the pinned Settings tree the `CollectionView` frame is the whole screen,
55
63
0–874, while its indicator track is 116–812 (`runner-presentation.test.ts`). The frame alone therefore
56
64
cannot express visibility, and deleting indicator clipping would return content that is scrolled under
57
65
the chrome. The band is necessary; only *who owns it* was ever in question.
58
66
59
-
**Visibility ejection needs evidence, and every ejection is recorded.** Most of the engine's 23 removal
60
-
sites delegate semantics or drop decoration — a collapsed row, a duplicate label, a wrapper's content, a
61
-
system indicator — and this ADR does not touch their authority. It governs the four visibility-driven
62
-
sites among them, the scroll band's one and the keyboard band's three, alongside the clip fold's own
63
-
inclusion decision in `geometry-policy.ts`. Of those, the scroll band is the one a weak signal can reach,
64
-
and directional hints are its safe outlet when nothing is the indicator's parent. Every source index ends
65
-
either presented, with its representatives, or removed with a typed reason, which makes the ledger complete
66
-
by construction rather than gated. The existing `presentedIndexesBySourceIndex` in
67
-
`ios-snapshot-engine/semantic-index.ts` is the shape to extend; a second parallel ledger would be a second
68
-
source of truth.
67
+
**Visibility ejection needs evidence, and every ejection must be recorded.** Most of the engine's 23
68
+
removal sites delegate semantics or drop decoration — a collapsed row, a duplicate label, a
69
+
wrapper's content, a system indicator — and this ADR does not touch their authority. It governs the
70
+
four visibility-driven sites among them, the scroll band's one and the keyboard band's three,
71
+
alongside the clip fold's own inclusion decision in `geometry-policy.ts`. Of those, the scroll band
72
+
is the one a weak signal can reach, and directional hints are its safe outlet when nothing is the
73
+
indicator's parent. Every source index must end either presented, with its representatives, or
74
+
removed with a typed reason, which makes the ledger complete by construction rather than gated;
75
+
#2754 step 5 still owes that reason, and `presentedIndexesBySourceIndex` in
76
+
`ios-snapshot-engine/semantic-index.ts` records representatives and no removal reason today. That
77
+
ledger is the shape to extend; a second parallel ledger would be a second source of truth.
69
78
70
79
## Refuted alternatives
71
80
@@ -118,10 +127,12 @@ source of truth.
118
127
upward. So `findScrollIndicatorContainer` returns null when the node is itself a scroll type;
119
128
`runner-presentation.test.ts` pins a scroll host labelled as an indicator leaving its parent list's
120
129
band intact.
121
-
-**macOS is already a second consumer of these rules.** Desktop capture runs the engine through
122
-
`snapshot-desktop-surface.ts` → `ios-snapshot-runtime.ts` → `publishIosSnapshot`, and its trees carry
123
-
`ScrollArea`, which neither eligible set admits. The parent-edge rule needs its own decision there
124
-
before it is reused, not a carry-over from the iOS claim.
130
+
-**macOS is already a second consumer of these rules, and #2759 settled its `ScrollArea`
131
+
question.** Desktop capture runs the engine through `snapshot-desktop-surface.ts` →
132
+
`ios-snapshot-runtime.ts` → `publishIosSnapshot`, and its trees carry `ScrollArea`, which neither
133
+
eligible set admits: a content-bearing one survives eligibility and owns its band; a label-less
134
+
one is dropped and leaves its indicator with no owner, under-clipping instead of mis-clipping the
135
+
list. `eligibility-parity.test.ts` pins both outcomes and the TS/Swift eligibility pair.
125
136
-**Ejection inventory precedes any API change.** 23 suppression sites across ten rules, and two of them
126
137
(`scroll`, `transitions`) already rewrite rects on other nodes in the same pass that ejects — the
127
138
combination the proposed API split claimed to make impossible.
Copy file name to clipboardExpand all lines: docs/adr/README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,7 +27,7 @@
27
27
|[0023 End-State Entry-to-Platform Hop Trace](0023-end-state-hop-trace.md)| the hop counting definition, the ordered `press`/Android and dual-arm `snapshot`/iOS chains, per-hop roles, and the deletion test behind the numbers ADR 0019 and ADR 0022 quote |
28
28
|[0024 Screen Recording — One Coordinator, Two Facts (Proposed)](0024-screen-recording-provable-signal.md)|`record start`/`stop` lifecycle, the export-exists / recorder-stopped split, backend stop-and-collect contract, recovery through the coordinator, the ADR 0019 §5 amendment and the failed-finish cleanup fix |
29
29
|[0025 Foldable Apple Panels — Capture the Lit Panel](0025-foldable-apple-panels.md)| iPhone Duo outer/inner panels, CoreDevice display info as the only screen authority, why `simctl`'s implicit display default captures a black panel, derived (never asserted) pose, and why no pose-control command exists |
30
-
|[0026 Scroll Clip Authority — Ownership Is the Parent Edge (Proposed)](0026-scroll-clip-authority.md)| iOS scroll indicators and the visible band, why ownership is read from the parent edge instead of an ancestor walk, what may remove a node from an interactive snapshot, and ejection dispositions |
30
+
|[0026 Scroll Clip Authority — Ownership Is the Parent Edge](0026-scroll-clip-authority.md)| iOS scroll indicators and the visible band, why ownership is read from the parent edge instead of an ancestor walk, what may remove a node from an interactive snapshot, and ejection dispositions |
31
31
|[0027 Descriptor Root Size vs Eager-Closure Budget (Proposed)](0027-descriptor-root-vs-eager-closure-budget.md)| splitting `packages/command-registry/src/registry.ts`, the ADR-0019 eager-closure module-count budget, and why a byte-neutral hub split is currently unshippable |
32
32
|[0028 Capability-Family Cell Vocabulary — One Runtime Source (Proposed)](0028-capability-family-cell-vocabulary.md)| adding a capability operation family, `UnavailablePlatformRuntimeFacts` / `UNAVAILABLE_CELLS`, `INTERACTOR_OPERATIONS`, and why an eight-package fan-out recurs |
0 commit comments