Skip to content

Phase 3: layout grid#31

Merged
intendednull merged 11 commits into
mainfrom
claude/v01-layout-grid
May 10, 2026
Merged

Phase 3: layout grid#31
intendednull merged 11 commits into
mainfrom
claude/v01-layout-grid

Conversation

@intendednull
Copy link
Copy Markdown
Owner

Summary

  • GridParams + GridItem ship the CSS Grid surface; reserved TrackSize::Subgrid + GridAutoFlow::Masonry warn-once and degrade.
  • Display::Grid / Display::InlineGrid now route to taffy::Display::Grid (Phase 1 routed both to Block).
  • 13 fluent setters on Style; Length::Fr lands grid-only with a non-grid warn-once gate. Breaking: Length gains Fr variant — exhaustive matches downstream must add an arm.
  • Decomposed-only convention preserved: GridParams joins Style's Bundle; GridItem does not (mirrors FlexItem / ScrollSnapItem).

Test plan

  • cargo test --workspace all green (Phase 1 + Phase 2 + new Phase 3 tests)
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps
  • CI: Lint / Doc / Deny / Test on ubuntu/macos/windows

References

  • Plan: docs/plans/2026-05-09-buiy-layout-grid.md
  • Spec: docs/specs/2026-05-08-buiy-layout-design/flex-and-grid.md § 2

Notes from final branch review

  • Phase 2 invariant intact: Changed<ScrollOffset> and Changed<ScrollSnapItem> still excluded from sync_styles trigger filter.
  • Spec divergences (all documented in CHANGELOG and source comments):
    • MinMax(Vec<TrackSize>) (vs Box<TrackSize>, Box<TrackSize>) — Bevy 0.18 Reflect doesn't support Box<T: Reflect>. Arity-2 invariant enforced at translate time.
    • RepeatCount::Count(u16) and GridLine::Start/Span/StartEnd widths use i16/u16 to match Taffy 0.10 directly (spec used i32/u32).
    • GridLine::Area(String) (spec used SmolStr) — avoids a new direct supply-chain dep.
    • Length::Fr outside grid templates falls back to 0 px in LengthPercentage contexts (Taffy type has no Auto), Auto in Dimension / LengthPercentageAuto. The warn-once gate is the visible signal.

🤖 Generated with Claude Code

intendednull and others added 11 commits May 9, 2026 21:25
Phase 3 ships GridParams + GridItem, TrackSize / GridLine / GridAreas
value types, flips Display::Grid to taffy::Display::Grid, and adds
warn-once stubs for reserved Subgrid + Masonry variants. Plan tagged
[active] in docs/README.md; flips to [landed] post-merge.
Three reviewer agents surfaced real issues; this revision addresses:

1. Task 1 instant build break — adding Length::Fr to types.rs needs
   atomic translate.rs Fr arms in length_to_dim/lp/lpa or cargo build
   fails E0004 on the three exhaustive matches. Task 1 now multi-file.
2. Wrong S type parameter — replaced &'static str with String everywhere
   (S = DefaultCheapStr = String; &'static str doesn't impl CheapCloneStr).
3. Wrong helper-method idiom — Task 5 helpers now use taffy::prelude
   free fns (auto, length, percent, fr, fit_content, min_content,
   max_content, minmax) with type inference from return type, instead
   of TrackSizingFunction::method() which requires trait imports.
4. name: a.name.as_str().into() — replaced with a.name.clone() (no
   'static lifetime guarantee on runtime String).
5. Mixed flex-in-grid claim was false (named-areas test has no flex
   children) — Task 8 now adds a real fixture pinning composition.
6. Length::Fr non-grid fallback — added Decision item naming the
   Taffy-imposed deviation (LengthPercentage has no Auto variant).
7. Spec § 5 'Multi-column stub warns' missing — added Coverage row
   marking deferred to Phase 7.
8. Test count drift — components.rs has 10 tests, not 9.
9. Length::Fr is breaking — added ### Breaking section to CHANGELOG.
10. ChildOf already in Phase 2 filter — re-worded so Phase 3 widens
    only with the two grid Changed clauses.
11. Enumerated 6 existing translate::tests needing drive-by edits.
Atomic: types.rs adds Length::Fr, RepeatCount, TrackSize, GridLine,
GridAreas + NamedArea, GridAutoFlow, JustifyItems; translate.rs grows
a Length::Fr arm in length_to_dim / length_to_lp / length_to_lpa with
a single shared warn-once gate. Splitting these into two commits leaves
the lib uncompilable (E0004 on the three exhaustive matches).

Subgrid + Masonry land as reserved TrackSize / GridAutoFlow variants
(warn-once + fallback wired in Phase 3 Task 6). Width discipline:
RepeatCount::Count and GridLine::Start/Span/StartEnd use u16/i16 to
match Taffy 0.10 directly; GridLine::Area uses String to avoid a
smol_str dep. GridAreas::from_lines is the CSS-syntax convenience
parser that converts ["a a", "b ."] into NamedArea rectangles.

Note: Length gains a variant — this is a breaking change for
exhaustive Length matchers downstream. CHANGELOG flags this under
### Breaking in Task 9.

DIVERGENCE FROM PLAN (Task 1 Step 5):

  Plan specifies `MinMax(Box<TrackSize>, Box<TrackSize>)` for the
  recursive variant. Implemented as `MinMax(Vec<TrackSize>)` because
  bevy_reflect 0.18 has no `Reflect` impl for `Box<T>` (only Vec /
  IndexMap / SmallVec / BTreeMap are supported as reflectable
  containers). `#[reflect(no_field_bounds)]` is added to TrackSize
  to handle the recursive Reflect bound; the underlying Box-not-
  reflectable issue is independent of bounds. Vec preserves the
  recursion semantics; arity (must be 2) becomes a translation-time
  invariant — Task 5 should validate `len() == 2` and warn-once with
  Auto fallback otherwise. The doc comment on the variant flags the
  expected arity. Resolution alternative: drop `Reflect` from
  TrackSize entirely; that would force `#[reflect(ignore)]` on the
  GridParams template fields in Task 2 and lose registry visibility
  for inspectors/BSN — strictly worse for the same compile fix.

  Also added `#[allow(dead_code)]` on `GridAreas::new` / `area` /
  `from_lines` (mirroring the precedent at components.rs:168 for
  `Overflow::is_scroll_container`) because Task 1 introduces them
  pub but Task 7 (re-exports) lands later; without the allow,
  `cargo clippy --workspace --all-targets -- -D warnings` (the
  command Step 12 runs) fails with `dead_code` warnings.
GridParams (container, will join Style Bundle in Task 3) carries the
full CSS Grid surface: explicit/auto track templates, named areas,
auto-flow, alignment, gap. GridItem (decomposed-only, like FlexItem
and ScrollSnapItem) carries per-child placement and self-alignment.

Named-area resolution lives in translate.rs (Task 5) because Taffy
0.10 has no native named-area placement.
…tters

Adds 12 fluent setters (.grid, .inline_grid, .grid_template_columns,
.grid_template_rows, .grid_template_areas, .grid_auto_columns,
.grid_auto_rows, .grid_auto_flow, .grid_justify_items,
.grid_align_items, .grid_justify_content, .grid_align_content,
.grid_gap_px). GridItem stays decomposed-only per architecture.md
section 2.4, like FlexItem and ScrollSnapItem.
Phase 1 routed both to Block because GridParams hadn't shipped yet;
Phase 3 Task 2 shipped GridParams. Flip the arm so containers actually
get grid layout. Taffy 0.10 has no inline-grid variant — both Buiy
variants map to the single Taffy variant; Phase 4 (writing modes) may
revisit if line-box context differs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Atomic: extends StyleView with grid_params, grid_item, parent_areas;
populates taffy::Style.grid_* fields; resolves GridLine::Area via a
Buiy-side parent lookup (Taffy 0.10 has no native named-area
placement). Length::Fr outside grid templates warns once and falls
back to 0 px / Auto.

sync_styles now precomputes a parent-areas map for the changed set
and widens its Or filter to Changed<GridParams>+Changed<GridItem>.
ChildOf is also kept in the filter so re-parenting a grid item under
a different grid container picks up the new container's areas.

MinMax: stored as Vec<TrackSize> (Bevy 0.18 Reflect lacks a Box<T>
impl), arity-2 invariant checked at translate time — non-2 arity
warns once and degrades to Auto. Subgrid + Masonry warn-once gates
sit alongside the invalid-track-nesting + unresolved-area gates.

Note: this is one commit because StyleView is the bridge between
translate.rs and systems.rs — splitting would break the lib build
between commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…w::Masonry

Both reserved variants degrade to a sensible non-stub equivalent
(Auto / Row) and emit one warn! per session naming the limitation.
Tests assert the layout pipeline completes without panic when these
variants appear; the warn output is observable but not asserted (Bevy
log interception is out of scope).

Subgrid's warn-gate landed in Task 5; this commit adds the symmetric
warn_once_masonry + WARNED_MASONRY AtomicBool and routes
GridAutoFlow::Masonry through it inside map_grid_auto_flow.

Drive-by: re-export GridParams, GridAutoFlow, and TrackSize from
buiy_core::layout so the new integration test can import them. Task 7
will widen the re-export set further (GridItem, GridLine, GridAreas,
NamedArea, RepeatCount, JustifyItems) and register reflection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GridParams + GridItem registered for reflection; new value types
(TrackSize, GridLine, GridAreas + NamedArea, GridAutoFlow,
RepeatCount, JustifyItems) re-exported from buiy_core and the buiy
facade alongside the Phase 2 surface.
…eat, mixed)

Integration tests pin observable layout for: 1fr 2fr 1fr in a 400 px
row produces 100/200/100; named areas resolve child to correct cell
(GridLine::Area going through Buiy-side resolution); repeat(auto-fill,
100 px) in 350 px produces 3 columns with 50 px slack; and a grid cell
hosting a flex-row with two children pins spec § 5 "Mixed flex-in-grid"
composition.
@intendednull intendednull merged commit 46ffac8 into main May 10, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant