diff --git a/crates/rustmotion/skills/rules/card-flex-layout.md b/crates/rustmotion/skills/rules/card-flex-layout.md index e22eb345..c2908579 100644 --- a/crates/rustmotion/skills/rules/card-flex-layout.md +++ b/crates/rustmotion/skills/rules/card-flex-layout.md @@ -37,15 +37,37 @@ Children flow in the flexbox. Use `positioned` container for absolute positionin **Grid sizing:** `height: "auto"` on a grid container sizes correctly to content — you don't need an explicit `height` just to avoid stretching. See [rules/grid-card-height.md](rules/grid-card-height.md). -## 23 component types have no intrinsic size — they need explicit `width`/`height` +## 23 component types have no *intrinsic* size — they fall back to a documented default -Most components either measure their own content (`text`, `codeblock`, `counter`, `badge`, `table`, `terminal`, `caption`, `kbd`, `gradient_text`, `rich_text`) or get a computed fallback size from their own fields (`icon`-like shapes such as `avatar`, `divider`, `line`, `arrow`, `switch`, `slider`, `progress`, `list`, `timeline`, `notification`, `rating`, `qr_code`, `countdown`, `particle`, `cursor`, `connector`, `waveform`, `audio_spectrum`). The following **23 types have neither** (verified against `crates/rustmotion-components/src/box_builder.rs`'s `component_intrinsic` and `apply_intrinsic_overrides` — both are exhaustive `match`es and these fall through to their `_ => None` / `_ => {}` arms, and no component overrides `Painter::intrinsic_size` either): +Most components either measure their own content (`text`, `codeblock`, `counter`, `badge`, `table`, `terminal`, `caption`, `kbd`, `gradient_text`, `rich_text`) or get a computed fallback size from their own fields (`icon`-like shapes such as `avatar`, `divider`, `line`, `arrow`, `switch`, `slider`, `progress`, `list`, `timeline`, `notification`, `rating`, `qr_code`, `countdown`, `particle`, `cursor`, `connector`, `waveform`, `audio_spectrum`). The following **23 types have no intrinsic measurement** (they fall through `component_intrinsic`'s `_ => None` arm and don't override `Painter::intrinsic_size`), but every one of them gets a **default `width`/`height` applied by `apply_intrinsic_overrides`** in `crates/rustmotion-components/src/box_builder.rs` whenever the JSON doesn't already set `style.width`/`style.height` — an explicit size is an *override*, not a requirement: -`shape`, `image`, `icon`, `svg`, `video`, `gif`, `callout`, `chart`, `comparison`, `dot_map`, `gauge`, `heatmap`, `lottie`, `marquee`, `mockup`, `pill_nav`, `skeleton`, `sparkline`, `stat`, `stepper`, `tag_cloud`, `tooltip`, `treemap` +| Component | Default size | Where it comes from | +|---|---|---| +| `sparkline` | 120×40 | fixed convention | +| `stat` | 280×180 | fixed convention | +| `gauge` | square, `2·(88 + track_width/2 + 4)` | derived from its own `track_width` field | +| `dot_map` | 640×320 (2:1) | fixed (equirectangular aspect) | +| `comparison` | 520×280 | fixed convention | +| `treemap` | 416×368 | fixed convention | +| `chart` | 320×320 (pie/donut/radar/radial_bar) or 400×300 (other 8 types) | fixed, by chart shape | +| `mockup` | per `device` (e.g. 320×690 for iphone/android, 640×400 laptop, 640×360 browser) | fixed, by device aspect | +| `icon` | 64×64 | fixed convention | +| `svg` | 200×200 | fixed convention | +| `shape` | 80×80 | fixed convention | +| `image` | 400×300 (4:3) | fixed convention | +| `video`, `gif` | 400×225 (16:9) | fixed convention | +| `lottie` | 300×300 | fixed convention | +| `skeleton` | 400×200 (rectangle) / 64×64 (circle) / `240×(lines·line_height + gaps)` (text) | fixed, or derived from its own `lines`/`line_height`/`line_gap` for the `text` variant | +| `marquee` | 800×`2·font_size` | fixed width, height derived from its own `font_size` | +| `heatmap` | derived from `data` rows/cols and `cell_size`/`cell_gap` | fully content-derived | +| `callout`, `tooltip` | derived from the measured text width + padding + arrow | fully content-derived | +| `pill_nav` | derived from each item's measured label width + padding + gap | fully content-derived | +| `stepper` | derived from `node_size` and each step's label/description width | fully content-derived | +| `tag_cloud` | derived from each tag's weighted font size, wrapped at a conventional content width | fully content-derived | -As a flex/grid child with no explicit `style.width`/`style.height`, any of these lays out at **0×0 and renders nothing** — not a smaller-than-expected box, no pixels at all. Confirmed by rendering: three `stat`s in a flex-row card with no explicit size produce a blank frame. `rustmotion validate` does not flag this (a 0×0 box doesn't overflow anything). +A default only fills in the axis that's actually missing — `apply_default_size` respects an explicit `width` or `height` (and derives the other one from `style.aspect-ratio` when only one is set). Explicit `style.width`/`style.height` is still worth setting whenever the default doesn't match the layout you want (e.g. a `stat` narrower than 280px in a tight row), but omitting it no longer produces a blank frame — three `stat`s in a flex-row card with no explicit size now lay out at 280×180 each, confirmed by `box_builder.rs`'s own tests. -Always give these components explicit `style.width`/`style.height` (or a fixed size via their own dedicated `size` field where one exists, e.g. `qr_code`'s `size`) wherever they're a flow child of a `card`/`flex`/`grid` — not just when the parent has `height: "auto"`. If a component silently doesn't render, check this list before assuming a schema-field-placement bug (see [rules/component-field-placement.md](rules/component-field-placement.md) for that other, more common cause of invisible components). +If a component isn't showing up despite that, look at [rules/component-field-placement.md](rules/component-field-placement.md) first — schema-field misplacement is the more common cause of an invisible component. **GOOD** (icon + text row): ```json