This repository has no description
bez tasks stacking-order.md
6.5 kB
Markdown
at main


enabled: true model: claude-fable-5-1 touches: [crates/layout/src/stacking.rs, crates/layout/src/lib.rs, crates/layout/src/generated/, crates/harness/src/stacking_cases.rs, crates/harness/src/lib.rs, crates/harness/src/trace.rs, crates/harness/src/dump.rs, crates/harness/src/bin/probe.rs, crates/dom/src/computed.rs, crates/generation/, oracle/oracle.mjs, docs/dump-format.md, docs/platform-areas.md, docs/model-first-cost.md, roadmap.md, corpus/**] depends-on: [] #

Stacking order: a paint-order seam with a hit-test oracle #

docs/platform-areas.md records painting as having no oracle at all, and every generated seam so far outputs geometry. Paint order (CSS 2.1 §9.9 and Appendix E: stacking contexts, z-index, the seven painting layers) is observable without pixels: document.elementsFromPoint(x, y) returns the boxes under a point in paint order, and all three engines implement it. This track adds that oracle kind and builds the first generated seam whose output is an ordering, not a rectangle, model-first the way the absolute seam was built (tasks/absolute-batches.md).

Scope. In: block-level boxes in normal flow and position: absolute boxes (the two the engine lays out), position with z-index: auto and integer values, stacking contexts established by positioned boxes with non-auto z-index and by the root, the Appendix E order within one context (background of the context, negative z-index children, in-flow blocks, positioned descendants with z-index: auto or 0 in tree order, positive z-index children), and opacity < 1 as a stacking-context trigger only. Out: transforms, isolation, mix-blend-mode, will-change, filters, floats, inline content, pointer-events, overflow clipping, visibility. Every box in the corpus is opaque, non-overflowing and untransformed, so elementsFromPoint reports paint order and nothing else. Say so in the seam's module doc.

Oracle first, in this order:

  1. oracle/oracle.mjs: a stackingProbe request that, for a document and a list of points, returns per point the ids of the elements document.elementsFromPoint lists, in order. Stdout keeps carrying only response JSON lines; diagnostics go to stderr. Document the request and response in docs/dump-format.md under a new "Stacking probe" heading, beside the trace protocol; the dump format itself is frozen and does not change.
  2. crates/harness/src/trace.rs and crates/harness/src/bin/probe.rs gain the corresponding client and a three-engine vote over orderings: a point's ordering is ground truth only when all three engines agree exactly; otherwise the point is dropped and counted. crates/harness/src/dump.rs gets the record type for a probed point, nothing else.

Then the seam, mirroring the absolute seam's set:

  • crates/layout/src/stacking.rs — StackingInputs (a flattened list of boxes with their id, tree order, parent, position, z-index, opacity, and rect), StackingOutputs (the ids in paint order, back to front), type StackingResolver, and a deliberately wrong resolve_baseline (for example tree order). The resolver reads the fragment tree layout() already produces; it does not change layout, and crates/layout/src/fragment.rs is not edited.
  • crates/dom/src/computed.rs: z_index and opacity accessors.
  • crates/harness/src/stacking_cases.rs — own Category enum with pub const ALL, Case, enumerated(), random(seed, count), built on cases::{Elem, Source, px, RESET} like absolute_cases.rs; each case names the probe points (one per overlapping region). Categories at least: overlapping siblings in tree order, positioned over static, negative z-index, positive z-index ordering, z-index trapped inside a child stacking context, opacity creating a context, nested contexts three deep. Never add to crates/harness/src/cases.rs RECIPES.
  • crates/generation/src/stacking_runner.rs comparing orderings (not rects, so it does not reuse compare::vote), the Seam variant, prompt::build_stacking_order, admit::record_for_stacking_order, and stacking_ground_truth_cache, stacking_generation_loop, stacking_real_model_run under crates/generation/src/bin/, plus one baseline and two seeded-wrong fixtures under crates/generation/fixtures/stacking-candidates/ (for example one that ignores stacking contexts and sorts globally by z-index, one that paints negative z-index above in-flow content).

The model endpoint and keys are read from the environment, and the binaries do not load .env themselves. Before every run: set -a; source .env; set +a in the worktree (.env is a symlink installed by tracks.json). Never print or commit any value from it.

Run, in order, each as cargo run --release -p generation --bin <name>:

  1. stacking_ground_truth_cache — fills corpus/stacking-ground-truth/; record the point count and the drops for engine disagreement.
  2. stacking_generation_loop — the baseline seeds the regression set; both seeded-wrong fixtures must be rejected. If one is admitted, fix the loop or the corpus, not the fixture, and record what was wrong.
  3. GENERA_PROVIDER=claude-code stacking_real_model_run — the loop stops on its own (two consecutive batches with no per-category improvement, or six batches). Never set GENERA_PROVIDER=anthropic: that path is metered and has run dry mid-batch.

When a candidate is admitted, commit it as crates/layout/src/generated/stacking.rs byte for byte with the header line generated/width.rs uses, stacking.provenance.json beside it, and pub mod stacking; in generated/mod.rs; a paint_order() entry point in crates/layout/src/lib.rs defaults to it. Record every batch in docs/model-first-cost.md → "Batch scores and token totals" (wall-clock per batch in place of token counts), append the outcome to roadmap.md → "Phase 1 — Engine bootstrap" step 4 in the form the other seams use, and change the painting row of docs/platform-areas.md from no oracle to this one, with its agreement rate. A hand-written candidate is allowed only after every batch has failed to admit one, and is recorded as an escalation with the batch count that preceded it.

cargo test --workspace must pass. fragment_check on the seeded-random recipe corpus (cargo run --release --bin fragment_check -p layout -- --random) must still report 216/227 with every category unchanged: layout is not touched here.