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: aDisplayenum anddisplay()accessor withBlock,FlowRoot,InlineBlock,Noneand anOthercatch-all (keepis_display_flow_rootworking;tables-fixed-layoutanddisplay-contentsextend this enum), plusvertical_align(),line_height()andtext_align()accessors returning plain data. Add accessors only; do not change an existing one.boxtree.rs: eachBoxrecords 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, strutline-height,text-align; per atomic inline in document order its margin-box width and height, its baseline measured from its margin-box top, and itsvertical-alignwith a percentage already resolved against its ownline-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 wrongresolve_baselinethat 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'sHeightInputs::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 threelayout*entry points.crates/harness/src/inline_atomic_cases.rs— ownCategoryenum withpub const ALL,Case,enumerated(),random(seed, count), built oncases::{Elem, Source, px, RESET}likeabsolute_cases.rs. Categories at least: one line of equal-height atomic inlines, baseline alignment of different heights,top,bottom,middle, a length and a percentagevertical-align, a line break when the row overflows, an atomic inline wider than the container,text-align: rightandcenter, margins/padding/ borders on the atomic inlines, a strut taller than every atomic inline, an inline-block withoverflow: hidden(baseline at its bottom margin edge), nested inline-blocks. Never add tocrates/harness/src/cases.rsRECIPES.crates/generation/src/inline_atomic_runner.rs, aSeam::InlineAtomicvariant (candidate_crate.rs),prompt::build_inline_atomic,admit::record_for_inline_atomic, andinline_atomic_ground_truth_cache,inline_atomic_generation_loop,inline_atomic_real_model_runundercrates/generation/src/bin/, plus one baseline and two seeded-wrong fixtures undercrates/generation/fixtures/inline-atomic-candidates/(for example one that aligns every box's bottom edge on the baseline regardless ofvertical-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>:
inline_atomic_ground_truth_cache— fillscorpus/inline-atomic-ground-truth/from the three browsers viaoracle/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.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.inline_atomic_real_model_runagainst the folknet model gateway, withNEV_PROVIDERunset andNEV_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:1800set after sourcing.env. Each entry ismodel: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 wayoverflow_real_model_rundoes (score_share,conclude_batch,crate::orchestrateprint_per_model_summaryandbest_per_model, and themodel_oflookup at admission), so provenance records the winner's own model. Only if the gateway does not answer, fall back toNEV_PROVIDER=claude-code, and record that fallback and what the gateway returned as a Decision. Never setNEV_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).