Make snapshot membership loss attributable: scroll clip authority + ejection dispositions
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Aptitud para principiantes
- 25/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- swift, typescript
Línea de trabajo
Start with ADR 0026 and the ownership logic in packages/capture-kit/src/ios-snapshot-engine/scroll.ts, then run the existing differential and conformance tests. Trace properties.test.ts, projection.ts, SnapshotPresentationProjection.swift, semantic-index.ts, and the named suppression rules before surveying captured trees. Done means the presented TextView and WebView cases pass, eligibility parity and typed ejections are enforced, the wrapper result is recorded, and the specified affected, conformance, and differential suites pass.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Current continuation (2026-09-25)
Architecture umbrella: #2803. #2974 now owns the remaining implementation of step 2, the runner-presented interactive differential. Its self-contained contract supersedes this issue's older step-2 requirement that both authored TextView/WebView cases fail on current main: TextView is already a green control; the missing proof must kill the specified ancestor-ownership regression mutation. Do not duplicate this work or change the clipping policy to make the test pass.
#2755, #2758 and #2759 are merged. The original requirement that steps 1 and 2 land together is historical; it must not block this residual test work or imply its coverage already exists. The broader captured-tree survey and typed ejection-disposition work stay owned here; #2974 does not claim those complete. Close step 2 only after checking its merged PR and actual runner-stage coverage.
The original scope follows for context.
Purpose
snapshot -i lost every list row after a row holding selectable text (#2214 in 0.21.0, patched for that
one type by #2740). The type-specific fix closed one instance. The class stays open: presentation still
finds a scroll indicator's owner by walking up past its parent, so any host that scrolls but publishes
as a non-scroll type (WebView, a map view, a paged cell) misattributes its indicator to the enclosing
list and clips that list to the host's band. Measured: a synthetic Element(58) row with its own indicator
drops Reply on origin/main.
Design record: ADR 0026 — Scroll Clip Authority: Ownership Is the Parent Edge (proposed, #2755).
Required behavior
Step 1 — ownership is read from the parent edge. The owner of an indicator is its parent, and a band
derives only when that parent is a scroll type. Delete the ancestor walk and isTextViewType from
packages/capture-kit/src/ios-snapshot-engine/scroll.ts. Evidence already collected on a scratch
branch: pnpm test:unit → 10,959 passed / 1 skipped with this substitution, and the Element(58) shape
above stops dropping rows. Land it with the red→green cases from step 2.
Step 2 — extend the differential to the runner stage. assertProjectionSubsets
(packages/capture-kit/src/ios-snapshot-engine/properties.test.ts) asserts interactive ⊆ regular ⊆ raw
but builds acquired inputs only; the Swift differential drops interactive cases. Add a stage: 'presented'
arm plus authored cases for a TextView row (#2740's shape) and a WebView row. Each must fail on
origin/main first, per docs/agents/testing.md. Assert source-level membership, not literally
interactive ⊆ full: the full projection skips semantic compaction.
Step 3 — survey real captured trees for under-clipping. Removing the walk can lose a band when a
real tree places an indicator under a labelled wrapper instead of directly under its scroll view, letting
content scrolled under the chrome survive — the leak class #1784/#1797 removed. Survey captured runner
trees before landing step 1, and record the count of indicators whose parent is not a scroll type. If the
shape is real, decide on evidence whether to skip unlabeled Other wrappers — do not add the tolerance
speculatively.
Step 4 — pair the two eligibility lists, and settle ScrollArea for macOS.
REGULAR_ELIGIBLE_TYPES (ios-snapshot-engine/projection.ts) and eligibleInteractiveTypes
(apple/snapshot-presentation/Sources/AgentDeviceSnapshotPresentation/SnapshotPresentationProjection.swift)
are one fact in two languages and no test currently references either. They are what makes the parent edge
survive projection: an ineligible wrapper is re-parented to the nearest presented ancestor, which is the
scroll host. Add a shared-table or dual-assertion guard. Note the pair excludes scrollarea while
isScrollableSnapshotType includes it: the iOS runner never emits that type, but macOS desktop capture
reaches these rules (snapshot-desktop-surface.ts → ios-snapshot-runtime.ts → publishIosSnapshot) and
its helper emits ScrollArea. Decide whether macOS needs it eligible before the rule is reused there —
do not resolve it by editing a set for iOS's sake.
Step 5 — inventory ejections, then make them typed. ~23 suppressNode call sites across ten rules
(rows, web, transitions, noise-search, noise-redundancy, noise-structural, noise-overlay,
noise-viewport, action-shelf, scroll); at least two also rewrite rects. Extend the existing
presentedIndexesBySourceIndex (ios-snapshot-engine/semantic-index.ts) into a typed disposition — every
source ends presented with representatives, or removed with at least one reason. If eject requires a
reason the ledger is complete by construction, so no separate gate is needed. Internal evidence only: no
suppression vocabulary on the snapshot wire.
Observable completion conditions
- A
stage: 'presented'case for both theTextViewandWebViewrow shapes fails onorigin/mainand
passes with the change. - No band can derive from anything other than a parent that is a scroll type; grep proves no ancestor walk
remains inscroll.ts. - The wrapper survey result is recorded on this issue with tree counts, and any tolerance it justifies
carries a captured-tree fixture. - Eligibility parity is enforced, not asserted in prose, and the
ScrollAreaquestion is answered for
macOS surfaces — either decided with a case, or recorded as out of scope with the reason. pnpm check:affected --runon the exact pushed head, plus the engine conformance and differential suites.
New ownership cases joincontracts/fixtures/ios-snapshot-engine-conformance.json; they join the Swift
fixture only once Swift implements the same behavior.
Non-goals
- A producer-reported ownership field or capture-local index remapping: the parent edge already carries it.
- Producer-computed visible bands.
- A scroll-capability matrix, or adding
TextView/WebViewto a universal scroll-container set. - A rule-graph/effect framework, or a two-API reshape/eject split before step 5's inventory.
- Treating the unexplained 74 → 67 node delta as an acceptance baseline without a captured artifact.
Dependencies and sequencing
- Blocked by: maintainer decision on ADR 0026 (#2755).
- Steps 1 and 2 land together. Step 3's survey gates step 1's merge. Step 4 is independent. Step 5 last.
runner-presentation.test.tspins the band contract, so the band must not be deleted to dodge the
problem.
Related: #2214 (introduced), #2740 (type-specific fix), ADR 0004 §host-side ownership boundary and the
#2638 regression note ("an absent fact is not a negative answer").
- Lenguaje dominante
- TypeScript
- Estrellas
- 4.8k
- Forks
- 315
- Merge medio
- 11 h 8 min
- PR fusionados (30 d)
- 551
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de callstack/agent-device
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 82/100
callstack/agent-device#3062 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
callstack/agent-device#1869 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 20/100
callstack/agent-device#3106 ·
Los mantenedores suelen responder en 1 día
-
fix(daemon-client): timeout reset clears daemon.json and daemon.lock without proving ownershipAbierto
Dificultad 3/5 1-2 días Aptitud para principiantes 68/100
callstack/agent-device#3105 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
callstack/agent-device#3104 ·
Los mantenedores suelen responder en 1 día
Todos los issues de callstack/agent-device
Issues similares
-
area/core status/need-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
google-gemini/gemini-cli#29602 ·
Los mantenedores suelen responder en 1 día
-
area: backend enhancement priority: low
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
snapotter-hq/SnapOtter#1879 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Tencent/BrowserSkill#390 ·
Los mantenedores suelen responder en 1 día
-
good first issue status: needs triaging type: bug version: 2.0
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
medusajs/medusa#17094 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
Los mantenedores suelen responder en 1 día