This repository has no description
bez specs inline-atomic.md
12 kB


enabled: true touches: [crates/layout/src/inline_atomic.rs, crates/layout/src/boxtree.rs, crates/layout/src/fragment.rs, crates/layout/src/lib.rs, crates/layout/src/generated/inline_atomic.rs, crates/layout/src/generated/inline_atomic.provenance.json, crates/layout/src/generated/mod.rs, crates/dom/src/computed.rs, crates/harness/src/inline_atomic_cases.rs, crates/harness/src/lib.rs, crates/generation/, docs/model-first-cost.md, roadmap.md, corpus/inline-atomic-ground-truth/, crates/provenance/src/inline_atomic.rs, crates/provenance/src/lib.rs, docs/generated-functions.md, crates/layout/src/height.rs] depends-on: [fixed-positioning, transforms2d] #

inline-atomic: line boxes over atomic inlines, text-free #

The engine has no inline formatting context. crates/layout/src/boxtree.rs's module doc says every computed display other than none "generates exactly one box", and crates/dom/src/computed.rs has no display accessor beyond is_display_none and is_display_flow_root, so a display: inline-block box is stacked in normal flow like a block. The Web Features map (docs/web-features-map.json) lists vertical-align (98 WPT test files, css/CSS2/linebox) and line-height (111) as none. Line boxes are the substrate every text-bearing area, tables' cell baselines and flexbox's baseline alignment sit on. This spec builds them for the one kind of content that needs no font: atomic inlines with explicit sizes.

The provisional rule for features whose math an existing generated function already covers (lift the adjacent hand-written logic into a new generated function; the roster files it as an open decision) does not come into play: no existing generated function places anything on a line.

Scope. In: a block container whose in-flow children are all display: inline-block with explicit width and height (lengths only), laid out in line boxes (CSS 2.1 §9.4.2, §10.8); line breaking between atomic inlines when the next one does not fit, and an atomic inline wider than the line placed alone on its own line; vertical-align: baseline | top | bottom | middle | <length> | <percentage> (§10.8.1); line-height on the container as the strut's height; text-align: left | right | center on the container; margins, padding and borders on the atomic inlines; an inline-block's own baseline (§10.8.1: the baseline of its last in-flow line box, or its bottom margin edge when it has none or its overflow is not visible), which makes inline-blocks nested inside inline-blocks a category; the container's auto height as the sum of its line boxes (§10.6.3). Out: text and every font-dependent value (sub, super, text-top, text-bottom, line-height: normal on a non-zero font size); width: auto inline-blocks (shrink-to-fit belongs to min-max-content); a container mixing block-level and inline-level children (anonymous block boxes, §9.2.1.1); floats and absolutely positioned boxes inside the inline formatting context; text-align: justify; bidi and vertical writing modes; inline-table, inline-flex, replaced elements. Say so in the new module's doc.

Keeping the corpus text-free and font-free. The case builder in crates/harness/src/cases.rs (document(), write_body()) already writes no whitespace between tags, so no text node can appear between atomic inlines; the new case file must assert that for every case it produces. The strut still reads the container's font (§10.8.1: its ascent and descent place the baseline inside the line-height), so every inline formatting context container in the corpus sets font-size: 0 with an explicit line-height in px. With a zero-size font the strut's ascent and descent are zero, the baseline sits at the middle of the strut, and middle aligns on the baseline itself, so no case depends on a font's metrics. That choice holds until text-metrics-oracle has measured whether the three engines agree on real text; record the choice in the case file's module doc.

Build the same set the absolute-positioning function has:

  • crates/dom/src/computed.rs: a Display enum and display() accessor with Block, FlowRoot, InlineBlock, None and an Other catch-all (keep is_display_flow_root working; tables-fixed-layout and display-contents extend this enum), plus vertical_align(), line_height() and text_align() accessors returning plain data. Add accessors only; do not change an existing one.
  • boxtree.rs: each Box records whether it is inline-level; a container whose in-flow children are all inline-level establishes an inline formatting context. Keep the hunk small: other specs edit this file.
  • crates/layout/src/inline_atomic.rs — InlineAtomicInputs (the container's content width, strut line-height, text-align; per atomic inline in document order its margin-box width and height, its baseline measured from its margin-box top, and its vertical-align with a percentage already resolved against its own line-height), InlineAtomicOutputs (each atomic inline's margin-box offset in the container's content box, each line box's top, height and baseline, the total height, and the last line box's baseline for an enclosing inline-block), type InlineAtomicResolver, and a deliberately wrong resolve_baseline that stacks the atomic inlines vertically the way the engine does today.
  • fragment.rs: an inline formatting context container lays out each inline-block as a block formatting context root at its explicit size, hands the results to the resolver, and places the fragments where it says. The container's HeightInputs::content_height_from_children (crates/layout/src/height.rs) is the resolver's total height. For margin collapsing the line boxes count as in-flow content with no adjoining child margins (§9.4.2, §8.3.1): an inline-block's margins never collapse. One more resolver argument on the three layout* entry points.
  • crates/harness/src/inline_atomic_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. Categories at least: one line of equal-height atomic inlines, baseline alignment of different heights, top, bottom, middle, a length and a percentage vertical-align, a line break when the row overflows, an atomic inline wider than the container, text-align: right and center, margins/padding/ borders on the atomic inlines, a strut taller than every atomic inline, an inline-block with overflow: hidden (baseline at its bottom margin edge), nested inline-blocks. Never add to crates/harness/src/cases.rs RECIPES.
  • crates/generation/src/inline_atomic_runner.rs, a Seam::InlineAtomic variant (candidate_crate.rs), prompt::build_inline_atomic, admit::record_for_inline_atomic, and inline_atomic_ground_truth_cache, inline_atomic_generation_loop, inline_atomic_real_model_run under crates/generation/src/bin/, plus one baseline and two seeded-wrong fixtures under crates/generation/fixtures/inline-atomic-candidates/ (for example one that aligns every box's bottom edge on the baseline regardless of vertical-align, one that never breaks a line).

Shared files. crates/layout/src/fragment.rs, crates/layout/src/lib.rs and crates/generation/** are edited by every spec that adds a generated function; such specs have landed concurrently before, so keep each hunk small and additive. crates/dom/src/computed.rs, crates/harness/src/lib.rs, crates/layout/src/generated/mod.rs, roadmap.md and docs/model-first-cost.md take additions only: new accessors, one pub mod line, appended records. boxtree.rs changes structurally, which is why this spec waits for fixed-positioning (it edits the same out-of-flow test there) and why tables-fixed-layout and display-contents wait for this one.

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. inline_atomic_ground_truth_cache — fills corpus/inline-atomic-ground-truth/ from the three browsers via oracle/oracle.mjs; record the case count and drops for engine disagreement. If more than a fifth of any category drops, record which engine disagrees and on what before going on: a font-size-zero strut that one engine treats differently is a finding, not a corpus bug to hide.
  2. inline_atomic_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. inline_atomic_real_model_run against the folknet model gateway, with NEV_PROVIDER unset and NEV_MODELS=qwen3.8-flash-next:16:16,qwen3.8-27b-fast:32:32,qwen3.8-27b-fast-2:32:16,deepseek-v4.1-flash:2:2:1800 set after sourcing .env. Each entry is model:count:concurrency[:timeout_secs], and the concurrencies match what each alias serves (docs/infrastructure.md → "Rig change log"). It stops on its own after two flat batches or six, whichever comes first. Score each model's share as it arrives and print per-model counts the way overflow_real_model_run does (score_share, conclude_batch, crate::orchestrate print_per_model_summary and best_per_model, and the model_of lookup at admission), so provenance records the winner's own model. Only if the gateway does not answer, fall back to NEV_PROVIDER=claude-code, and record that fallback and what the gateway returned as a Decision. Never set NEV_PROVIDER=anthropic.

When a candidate is admitted, commit it as crates/layout/src/generated/inline_atomic.rs byte for byte with the header line generated/width.rs uses, inline_atomic.provenance.json beside it, pub mod inline_atomic; in generated/mod.rs, and the three layout* entry points defaulting to it. Record every batch in docs/model-first-cost.md → "Batch scores and token totals", with per-model counts, and append the outcome to roadmap.md → "Phase 1 — Engine bootstrap" step 4 in the form the other generated functions use. 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.

A floor for this function in bench/seam-floor.sh is a hand change after landing: the landing script refuses a diff under bench/, and a score line in seam_floor_check with no cache rebuild behind it would fail every later landing. Say so in the track's handoff.

Files beyond the function's own. The spec text the function is generated against goes in a new provenance module whose file name contains inline_atomic (for example crates/provenance/src/css21_inline_atomic.rs), so the touches glob crates/provenance/src/*inline_atomic*.rs matches it, with its one pub mod line in crates/provenance/src/lib.rs, the way css21_containing_block.rs was added. The function's row in the table and its "Per function" entry go in docs/generated-functions.md. Do not update docs/feature-map.json, docs/dashboard.json or README.md here: docs/dashboard.md asks every spec that lands a generated function to, but those files belong to specs/dashboard-history-graph.md, and that instruction does not apply to an unattended track; the landing script refuses any file outside touches. In docs/*.md and roadmap.md prose, write "generated function", never the older word the root AGENTS.md forbids; crates/harness/tests/no_seam_word.rs fails cargo test --workspace otherwise (code identifiers and paths in backticks are exempt). The generated-function specs run one after another through depends-on, since each adds a resolver argument to the same layout* entry points and edits the same shared files.

Gates. cargo test --workspace passes. fragment_check --random stays 227/227 with every category unchanged: no recipe in cases.rs uses display: inline-block. bench/seam-floor.sh's floors hold (floats 109, stacking 105, relative positioning 104, block height 105).