feat(layout/render): I-paged inline fragment tree — per-page slice + continuation rebase (slice 4 / I-paged)#282
Merged
Merged
Conversation
…continuation rebase (slice 4 / I-paged)
Generalize `InlineFlow` from a single generation-stamped line set to
`{ fragments: Vec<InlineFragment> }` (`InlineFragment { generation, lines }`)
— the inline node of the project's fragment tree (docs §15.5: a 1-entity →
N-fragment relation fits a component). Drop the `frag_constraint.is_none()`
term from the persist gate for **paged** runs only (`fragmentation_type ==
Page`, threaded into `InlineFragConstraint` at both construction sites);
multicol (`Column`) stays gated to legacy until I-multicol.
New `LinePacker::slice_and_rebase_fragment` is the single source (F2) of the
per-page slice + continuation rebase: every block coordinate the packer
records (flow lines, entity rects → LayoutBox/InlineClientRects, abspos static
positions, positioned-atomic placements) is a snapshot of the same
`current_block_offset`, so the slice to `[skip_lines, effective_line_count)`
and the rebase to the fragmentainer block-start are applied once, before the
`content_origin` fold. Render's consume gate + `emit_inline_flow` iterate
fragments filtered by the page generation (shared `fragment_matches_page`).
Fixes the founding gap: paged multi-line inline now renders per fragment
(continuation fragments rebased, tail dropped after a break) instead of
routing to the legacy single-linear-pass path. Cap-neutral.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n test elidex-review Axis 4: the N:M entity↔layer "standalone structure" principle is §15.4.1 "Layer Tree as Independent Structure", not §15.5 (= "Display List") — correct both InlineFlow/InlineFragment docstrings. Axis 3: tie the inverted gate test's justification to this slice's slice_and_rebase_fragment (the in-PR root fix for the old gate's "not yet sliced per fragment" reason). Plus the /simplify consolidations: single-pass entity-bounds union + shared fragment_matches_page predicate (consume gate ⇄ emit filter parity). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Implements the slice 4 / I-paged portion of the render↔layout inline convergence by introducing an inline fragment-tree representation (InlineFlow → fragments: Vec<InlineFragment>) and enabling paged fragmentation to persist/consume per-page inline geometry via a single-source slice + continuation rebase pass.
Changes:
- Introduces
InlineFragment { generation, lines }and updatesInlineFlowto carry per-fragment line sets (common case remains a single fragment viaInlineFlow::single). - Adds
LinePacker::slice_and_rebase_fragment(skip_lines, effective_line_count)and wires it into inline layout for paged fragmentation only (multicol/Column remains gated). - Updates render consumption to filter persisted fragments by page
generation(sharedfragment_matches_pagepredicate) and updates/extends tests accordingly.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| crates/layout/elidex-layout-block/src/inline/pack.rs | Adds single-pass geometry slicing + continuation rebase for paged fragments. |
| crates/layout/elidex-layout-block/src/inline/mod.rs | Threads fragmentation type into the gate and invokes slice+rebase for paged runs before consumers. |
| crates/core/elidex-ecs/src/components.rs | Changes InlineFlow to hold Vec<InlineFragment> and adds InlineFlow::single helper. |
| crates/core/elidex-render/src/builder/inline.rs | Consumes persisted inline flows per-page by filtering fragments by generation. |
| crates/core/elidex-render/src/builder/tests/inline_flow.rs | Updates existing tests to build InlineFlow::single and adds generation-filter consume test. |
| crates/layout/elidex-layout-block/src/inline/tests/inline_flow.rs | Updates tests for fragment-based flow and adds paged I-paged behavior tests (persist gate, rebase, tail drop, clientRects SSoT). |
| crates/layout/elidex-layout-block/src/inline/tests/relpos_subflow.rs | Updates flow inspection to read from fragments[0].lines. |
| crates/layout/elidex-layout-block/src/block/children/shift.rs | Shifts inline flow geometry across all fragments during subtree shifts. |
| crates/layout/elidex-layout-block/src/block/children/helpers.rs | Threads fragmentation_type into InlineFragConstraint construction. |
| crates/layout/elidex-layout-block/src/block/mod.rs | Threads fragmentation_type into inline fragmentation constraint for block layout. |
| crates/layout/elidex-layout-block/src/block/tests/margin_collapse.rs | Updates test fixture construction to use InlineFlow::single and fragments[0]. |
| crates/layout/elidex-layout-block/src/block/tests/fragmentation_advanced.rs | Updates constraint construction to include fragmentation_type. |
| crates/core/elidex-ecs/src/lib.rs | Re-exports InlineFragment. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Owner
Author
Owner
Author
|
@codex[agent] review |
Owner
Author
|
@codex[agent] review deep |
CSS Fragmentation L3 §4.3 is "Forced Breaks"; orphans/widows is css-break-3 §3.3 "Breaks Between Lines: orphans, widows" (#widows-orphans). Webref-verified (`dfn css-break-3 orphans` → §3.3; `heading css-break-3 4.3` → Forced Breaks). Repo-wide sibling sweep confirms these are the only orphans/widows mis-cites; all other §4.3 references cite other specs (CSS Text/Lists/Selectors/Writing Modes, HTML/DOM/XHR/Streams) and are correct. Comment/doc only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
send
added a commit
that referenced
this pull request
Jun 6, 2026
Comment/doc-only sweep correcting CSS Fragmentation Level 3 (css-break-3) section citations in the block-layout fragmentation code. Surfaced during the PR #282 citation sweep but unrelated to orphans/widows, so split out here. No behavior or test-logic change. Every § re-verified with .claude/tools/webref against css-break-3. Verified spec structure (css-break-3): §3.1 break-before / break-after §3.1.1 Child→Parent Break Propagation §3.2 break-inside §3.3 orphans, widows (← NOT break-inside / classification) §3.4 page-break-* aliases (← NOT best-break selection) §4.1 Possible Break Points (Class A/B/C, monolithic) §4.5 Optimizing Unforced Breaks (best break selection, avoid penalty) §5.2 Adjoining Margins at Breaks §5.4 box-decoration-break Fixes (by drift cluster): - break-inside §3.3 → §3.2 (elidex-plugin) - box-decoration-break §4/§4.2 → §5.4 (elidex-plugin, doc plan) - break propagation §3.2 → §3.1.1 (lib.rs, children/mod.rs, doc plan) - break-point classification §3.3 → §4.1 (fragmentation.rs, float placement in stack.rs, doc plan) - best-break selection / avoid penalty §3.4 → §4.5 (fragmentation.rs, stack.rs, doc plan) - margins-not-collapsed-at-break §3.1 → §5.2 (stack.rs ×2, block/mod.rs) - section-header ranges in fragmentation.rs: §3.3-§3.4 → §3.1-§3.2 (avoid checks) and → §4.1, §4.5 (candidate selection) Left as-is (verified correct / defensible): break-before/after §3.1, forced-break detection §3.1, monolithic §4 (defined in §4.1), and the orphans/widows §4.3 cites (handled separately, out of this cluster). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
send
added a commit
that referenced
this pull request
Jun 6, 2026
…on (#285) Comment/doc-only sweep correcting CSS Fragmentation Level 3 (css-break-3) section citations in the block-layout fragmentation code. No behavior or test-logic change; every § re-verified with .claude/tools/webref. - break-inside §3.3 → §3.2 - break propagation §3.2 → §3.1.1 - break-point classification §3.3 → §4.1 - best-break / avoid penalty §3.4 → §4.5 - margins-at-break §3.1 → §5.2 - box-decoration-break §4 / §4.2 → §5.4 - break-before/after §3 → §3.1 (doc) - fragmentainer context §3 → §2 (doc) Surfaced during the #282 citation sweep but unrelated to orphans/widows. Copilot review found additional same-cluster instances (resolved in-PR). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Slice 4 / I-paged — inline fragment tree, first installment
The render↔layout inline-pipeline convergence's last structural gate was
frag_constraint.is_some()routing paged/multicol inline to the legacy single-linear-pass path. This is the I-paged installment of slice 4: the inline portion of a Z-ready fragment tree, scoped to paged fragmentation (multicol = I-multicol, a separate slice; bidi/justify/text-transform remain independent slices; legacy deletion is terminal).Plan-memo:
render-layout-inline-convergence-slice4-fragtree-plan(/elidex-plan-review GREEN, 2-round). Master:render-layout-inline-convergence-plan§6 slice 4.What changed
InlineFlow→{ fragments: Vec<InlineFragment> }(InlineFragment { generation, lines }) — the inline node of the project's fragment tree. A 1-entity→N-fragment relation fits an ECS component, unlike the N:M entity↔layer relation that forces the layer tree into a standalone structure (design §15.4.1). Non-fragmented runs keep a single generation-0 fragment.fragmentation_type(Page/Column) threaded intoInlineFragConstraintat both construction sites; the gate dropsfrag_constraint.is_none()forPageruns only. Multicol (Column) stays gated to legacy (its accumulate + column shift + balanced-fill probe-gating are I-multicol).LinePacker::slice_and_rebase_fragment: every block coordinate the packer records (flow lines, entity rects →LayoutBox/InlineClientRects, abspos static positions, positioned-atomic placements) is a snapshot of the samecurrent_block_offset, so the slice to[skip_lines, effective_line_count)and the rebase to the fragmentainer block-start are applied once, before thecontent_originfold — no cross-consumer desync.emit_inline_flowiterate fragments filtered by pagegeneration(sharedfragment_matches_pagepredicate keeps them in sync, D4).Why
Paged multi-line inline was broken on both the (gated-off) persisted path and the box path (continuation geometry was never rebased — the founding gate test said so). This makes a page-spanning paragraph render correctly per fragment: continuation fragments rebased to the page content top, the tail dropped after an orphans/widows break.
Tests
6 new (5 layout + 1 render): paged-persists / multicol-still-gated / continuation-rebase / tail-drop-after-break / clientRects==flow-lines single-source / render-generation-filter. All changed-crate tests + full
mise run ci(3-OS check + clippy + nextest + doc-tests + deny) green.Gate
Full 6-stage pre-push:
cargo fmt→mise run ci(green) →/code-review(high; 0 real bugs) →/simplify(single-pass union + shared predicate) →/review→/elidex-review(5-axis: 0 CRIT, fixed 1 IMP §15.4.1 citation + 1 IMP test-comment; 1 MINinline/mod.rs1103-line growth deferred consistent with prior slices). Cap-neutral (convergence/correctness refactor, no new#11-*slot).🤖 Generated with Claude Code