feat(cards): derive layout tiers from the effective grid span - #220
Conversation
Implements PR 1 of docs/changes/0011-layout-tiers.md: the derivation and the plumbing that carries it, with no per-card tier layouts yet. `deriveCardTier` partitions an effective span into glance/row/tall/full, and `scaleSpanToColumns` is the stored-to-effective mapping the grid already applied inline. `GridLayoutSection` now reads one expression for both the react-grid-layout layout and its child callback, so what a card is told about its size cannot drift from what the grid lays out, and it does that with arithmetic on the stored span rather than by measuring — `liebe-section`'s offsetWidth is untouched. `GridView` is the only place that derives a tier from a layout; cards take tier and span as props. The legacy `size` prop is gone from `CardProps` and `GridCard`, and with it the shell's three min-height stops, which become two: only the row count moves a height floor. Cards that scaled themselves off `size` are collapsed to a single rendering, and the inner min-height floors they carried are removed outright — the shell owns the floor now, keyed on the tier, so a one-cell glance tile is not propped open from the inside. This is a user-visible interim regression on main: between this change and its PRs 2 and 3, a card placed small renders larger than it did and one placed large renders smaller, because every size-scaled value is pinned to what `medium` produced. The per-card tier layouts restore it. Non-grid renderers migrate here too, since removing `size` breaks them: the configuration preview takes the effective span from whoever opens it, `QuickControlsWidget` states an explicit row tier, and the workshop's theme gallery derives each cell's tier from that cell's span. Skeleton and card-shaped error states are tier-aware — a glance placeholder is a small tile rather than a shorter large one, and a glance error omits its message and actions, keeping the message as the tile's tooltip. `data-tier` moves from Deferred to Stamped in the stable selector contract. The camera receives the stamp without consuming it, per this change's exemption; two of its tests now assert that exemption rather than the span-keyed behaviour it replaced.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 8 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (20)
📝 WalkthroughWalkthroughThe card layout system replaces legacy ChangesResponsive tier propagation
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GridLayoutSection
participant GridView
participant CardShell
participant CardConfig
GridLayoutSection->>GridView: provide effective span
GridView->>GridView: derive tier from span
GridView->>CardShell: render tier and span
GridView->>CardConfig: open preview with span
CardConfig->>CardShell: derive preview tier and render
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
Implements the first stage of layout tiers (change 0011) by deriving a card’s tier from its effective rendered grid span, plumbing that tier/span through the grid → cards → config previews, and stamping data-tier on the card shell to complete the stable selector contract.
Changes:
- Added
deriveCardTier+scaleSpanToColumnsutilities (with unit tests) and a namedgetEffectiveColumnsbreakpoint helper. - Updated grid rendering to compute an effective span once, derive
tierfrom it, and passtier+spanthrough to cards and configuration previews. - Replaced legacy
sizeplumbing with tier-aware shell attributes and updated cards/stories/tests accordingly (including tier-aware skeleton/error states).
Reviewed changes
Copilot reviewed 69 out of 69 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/cardTier.ts | Introduces tier derivation and stored→effective span scaling utilities. |
| src/utils/tests/cardTier.test.ts | Unit coverage for tier boundaries, span scaling, and effective column behavior. |
| src/theme/customCss.stories.tsx | Updates story usage from size to explicit tier. |
| src/components/widgets/QuickControlsWidget.tsx | Sidebar widget now passes explicit tier + span for non-grid rendering. |
| src/components/WeatherCard/WeatherCardModern.tsx | Migrates weather modern variant from size to tier and pins interim visuals. |
| src/components/WeatherCard/WeatherCardMinimal.tsx | Migrates weather minimal variant to tier and tier-aware skeleton/error. |
| src/components/WeatherCard/WeatherCardDetailed.tsx | Migrates weather detailed variant to tier and pins interim visuals. |
| src/components/WeatherCard/WeatherCardDefault.tsx | Migrates weather default variant to tier and pins interim visuals. |
| src/components/WeatherCard/WeatherCard.stories.tsx | Story controls/args migrated from size to tier. |
| src/components/WeatherCard/index.tsx | Ensures config preview receives the same span the card was given. |
| src/components/WeatherCard.test.tsx | Removes/condenses size-based tests; keeps meaningful data-point assertions. |
| src/components/ui/SkeletonCard.tsx | Makes skeletons tier-aware (composition + sizing keyed by tier). |
| src/components/ui/ErrorDisplay.tsx | Adds optional tier for card-variant degradation at glance. |
| src/components/ui/cardStates.test.tsx | Tests tier-aware loading and error placeholder behavior. |
| src/components/TextCard.tsx | Removes size prop and inner height floors to let shell/tier own sizing. |
| src/components/TextCard.stories.tsx | Removes size control/args from TextCard stories. |
| src/components/Separator.tsx | Removes unused size prop from separator component surface. |
| src/components/SensorCard.tsx | Migrates to tier, removes size-keyed inner floors, updates memo compare. |
| src/components/SensorCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/LightCard.tsx | Migrates to tier and threads span for card-owned config preview. |
| src/components/LightCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/InputTextCard.tsx | Migrates to tier and normalizes control sizing for interim state. |
| src/components/InputTextCard.test.tsx | Removes deleted size variants coverage. |
| src/components/InputTextCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/InputSelectCard.tsx | Migrates to tier and normalizes text sizing for interim state. |
| src/components/InputSelectCard.test.tsx | Removes deleted size variants coverage. |
| src/components/InputSelectCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/InputNumberCard.tsx | Migrates to tier and normalizes control sizing for interim state. |
| src/components/InputNumberCard.test.tsx | Removes deleted size variants coverage. |
| src/components/InputNumberCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/InputDateTimeCard.tsx | Migrates to tier and normalizes control sizing for interim state. |
| src/components/InputDateTimeCard.test.tsx | Removes deleted size variants coverage. |
| src/components/InputDateTimeCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/InputBooleanCard.tsx | Migrates to tier and normalizes switch sizing for interim state. |
| src/components/InputBooleanCard.test.tsx | Removes deleted size variants coverage. |
| src/components/InputBooleanCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/GridView.tsx | Derives tier from effective span and plumbs tier/span into cards + config modal. |
| src/components/GridLayoutSection.tsx | Exposes effective span via child callback and centralizes effective column logic. |
| src/components/GridCard.tsx | Replaces data-size with data-tier and updates context/props accordingly. |
| src/components/GridCard.stories.tsx | Updates shell story controls and renames size showcase to tier showcase. |
| src/components/GridCard.css | Moves shell floor sizing to tier-based min-block-size stops. |
| src/components/FanCard.tsx | Migrates to tier, removes size-based inner floors, updates memo compare. |
| src/components/FanCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/CoverCard.tsx | Migrates to tier, removes size-based inner floors, updates memo compare. |
| src/components/CoverCard.test.tsx | Removes deleted size variants coverage. |
| src/components/CoverCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/ClimateCard.tsx | Migrates to tier, removes size-based inner floors, pins interim visuals. |
| src/components/ClimateCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/cardRegistry.ts | Updates shared CardProps contract to tier + (effective) span. |
| src/components/CardConfig.tsx | Makes config preview tier-correct via passed effective span fallbacking to stored. |
| src/components/CameraCard/index.tsx | Switches to tier stamping only; keeps camera sizing behavior span-independent here. |
| src/components/CameraCard/CameraCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/CameraCard/tests/CameraCard.test.tsx | Adds tier-exemption guard assertions for camera overlays/matting. |
| src/components/ButtonCard.tsx | Migrates to tier and updates memo compare for tier changes. |
| src/components/ButtonCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/BinarySensorCard.tsx | Migrates to tier and threads span for card-owned config preview. |
| src/components/BinarySensorCard.stories.tsx | Migrates story controls/args from size to tier. |
| src/components/tests/LightCard.test.tsx | Updates shell contract assertion from data-size to data-tier. |
| src/components/tests/GridView.tier.test.tsx | End-to-end tests for effective-span tier derivation + config preview tier. |
| src/components/tests/GridLayoutSection.test.tsx | Tests the effective span callback matches what the grid actually receives. |
| src/components/tests/GridCard.test.tsx | Updates selector attribute stamping test to data-tier. |
| src/components/tests/cardLoadingTier.test.tsx | Table test ensuring every card passes tier into loading skeletons. |
| src/components/tests/CardConfig.test.tsx | Tests preview tier uses caller span and falls back to stored dimensions. |
| src/components/tests/ButtonCard.test.tsx | Removes size-decorative rerender loop; keeps meaningful assertion. |
| docs/specs/theming/index.md | Moves data-tier into the stamped stable selector contract and documents semantics. |
| docs/specs/design-system/index.md | Updates status to “tier derivation implemented” (per-tier layouts still pending). |
| docs/changes/0011-layout-tiers.md | Marks PR1 task complete; strengthens PR2/PR3 test obligations. |
| app/utils/responsive.ts | Adds getEffectiveColumns helper for breakpoint vs stored column selection. |
| .storybook/themeGallery.tsx | Gallery derives tier/span from its own tile dimensions to mirror GridView behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tion # Conflicts: # docs/specs/design-system/index.md # docs/specs/theming/index.md
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #220 +/- ##
==========================================
+ Coverage 72.45% 73.93% +1.48%
==========================================
Files 136 137 +1
Lines 5481 5482 +1
Branches 1761 1737 -24
==========================================
+ Hits 3971 4053 +82
+ Misses 1092 1052 -40
+ Partials 418 377 -41 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The text and separator branches of `GridView` opened the configuration modal without a span. The reasoning for that was that `TextCard` and `Separator` take no tier, so there was nothing for a span to feed — but the preview does not render those components bare. `CardConfig` wraps both in the card shell and stamps `data-tier` on it, derived from the span it is given, falling back to the item's stored dimensions when it is given none. On a narrow breakpoint that advertises a tier the item is not laid out at, and `data-tier` is under the stable selector contract, so a theme selecting on it styles the preview as a different card than the one behind it. Pass the span at both call sites, and make it a required parameter of `handleConfigureItem` so the next branch added there cannot quietly repeat this. The two leaves accept `onConfigure` and ignore it — neither has a configure affordance yet — so the new test stands in for them with stubs that do nothing but call the callback; the grid, the modal and the shell underneath are all real.
At `glance` the card-shaped error tile had room for the icon and the short title only, and kept the message in a `title` attribute. That is close to inaccessible on Liebe's primary surface: a wall tablet has no hover to reveal a tooltip, and `title` is exposed to assistive technology inconsistently even where hover exists. The tile also dropped `Retry` outright, which on a disconnected tile is the only way out of the state. The tile now carries the message as its accessible name and becomes a button onto the detail dialog, which holds the full message and the `Retry`/`Dismiss` actions. Both users reach the detail: assistive technology announces it from the name without opening anything, and a touch user presses the tile. The dialog's `Retry` is soft red rather than solid: white on `red-9` measures 3.91:1 and `Modal` forces `blue` (4.25:1) on an uncoloured primary action, so either would have added an AA contrast violation to the workshop's a11y baseline. Soft red is 4.54:1, and axe reports 0 violations on the affected stories in both appearances.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/InputTextCard.tsx (1)
183-221: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winIncrease these interactive hit areas.
TextField.Rootand the threeIconButtons are stillsize="2"; bump them tosize="3"or otherwise ensure the final touch targets are at least 44px.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/InputTextCard.tsx` around lines 183 - 221, Increase the touch targets in the InputTextCard editing and display controls: update TextField.Root and all three IconButton elements from size="2" to size="3", or apply equivalent styling that guarantees each final interactive target is at least 44px. Preserve their existing behavior and layout.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/changes/0011-layout-tiers.md`:
- Around line 45-46: Remove the behavioral error-tile accessibility and recovery
contract from docs/changes/0011-layout-tiers.md, retaining only rollout context
and a link to the single owning specification. Ensure the owning specification
documents tier-aware SkeletonCard/error/unavailable behavior, including the
glance accessible name, detail-dialog button, message, Retry, and Dismiss
actions.
In `@src/components/GridCard.css`:
- Around line 78-99: Define `--liebe-*` tier-floor custom properties in the base
layer for the 60px and 120px values, then update the
`[data-tier='glance']`/`[data-tier='row']` and
`[data-tier='tall']`/`[data-tier='full']` rules to use those tokens via
`var(...)`. Preserve the existing tier mappings and base values while enabling
theme and user overrides.
In `@src/components/InputDateTimeCard.tsx`:
- Around line 203-214: Restore compliant touch targets across
InputDateTimeCard.tsx lines 203-214 and 237-238, and InputNumberCard.tsx lines
209, 224, and 251-252: set each interactive input and
edit/decrement/increment/submit/cancel control to size="3" or larger, and ensure
each has a guaranteed minimum 44px touch target.
In `@src/components/ui/SkeletonCard.tsx`:
- Around line 97-109: Update SkeletonCard’s root Card rendering to preserve the
shared liebe-card shell and data-tier selector contract, while moving minHeight
into the layered tier styling rather than an inline-only value. Ensure loading
cards remain targetable by themes, and add a test covering the rendered shell
class/attribute and tier selector.
---
Outside diff comments:
In `@src/components/InputTextCard.tsx`:
- Around line 183-221: Increase the touch targets in the InputTextCard editing
and display controls: update TextField.Root and all three IconButton elements
from size="2" to size="3", or apply equivalent styling that guarantees each
final interactive target is at least 44px. Preserve their existing behavior and
layout.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6a845d21-7ca2-4eb9-b7c0-4ed366533837
📒 Files selected for processing (70)
.storybook/themeGallery.tsxapp/utils/responsive.tsdocs/changes/0011-layout-tiers.mddocs/specs/design-system/index.mddocs/specs/theming/index.mdsrc/components/BinarySensorCard.stories.tsxsrc/components/BinarySensorCard.tsxsrc/components/ButtonCard.stories.tsxsrc/components/ButtonCard.tsxsrc/components/CameraCard/CameraCard.stories.tsxsrc/components/CameraCard/__tests__/CameraCard.test.tsxsrc/components/CameraCard/index.tsxsrc/components/CardConfig.tsxsrc/components/ClimateCard.stories.tsxsrc/components/ClimateCard.tsxsrc/components/CoverCard.stories.tsxsrc/components/CoverCard.test.tsxsrc/components/CoverCard.tsxsrc/components/FanCard.stories.tsxsrc/components/FanCard.tsxsrc/components/GridCard.csssrc/components/GridCard.stories.tsxsrc/components/GridCard.tsxsrc/components/GridLayoutSection.tsxsrc/components/GridView.tsxsrc/components/InputBooleanCard.stories.tsxsrc/components/InputBooleanCard.test.tsxsrc/components/InputBooleanCard.tsxsrc/components/InputDateTimeCard.stories.tsxsrc/components/InputDateTimeCard.test.tsxsrc/components/InputDateTimeCard.tsxsrc/components/InputNumberCard.stories.tsxsrc/components/InputNumberCard.test.tsxsrc/components/InputNumberCard.tsxsrc/components/InputSelectCard.stories.tsxsrc/components/InputSelectCard.test.tsxsrc/components/InputSelectCard.tsxsrc/components/InputTextCard.stories.tsxsrc/components/InputTextCard.test.tsxsrc/components/InputTextCard.tsxsrc/components/LightCard.stories.tsxsrc/components/LightCard.tsxsrc/components/SensorCard.stories.tsxsrc/components/SensorCard.tsxsrc/components/Separator.tsxsrc/components/TextCard.stories.tsxsrc/components/TextCard.tsxsrc/components/WeatherCard.test.tsxsrc/components/WeatherCard/WeatherCard.stories.tsxsrc/components/WeatherCard/WeatherCardDefault.tsxsrc/components/WeatherCard/WeatherCardDetailed.tsxsrc/components/WeatherCard/WeatherCardMinimal.tsxsrc/components/WeatherCard/WeatherCardModern.tsxsrc/components/WeatherCard/index.tsxsrc/components/__tests__/ButtonCard.test.tsxsrc/components/__tests__/CardConfig.test.tsxsrc/components/__tests__/GridCard.test.tsxsrc/components/__tests__/GridLayoutSection.test.tsxsrc/components/__tests__/GridView.tier.nonEntity.test.tsxsrc/components/__tests__/GridView.tier.test.tsxsrc/components/__tests__/LightCard.test.tsxsrc/components/__tests__/cardLoadingTier.test.tsxsrc/components/cardRegistry.tssrc/components/ui/ErrorDisplay.tsxsrc/components/ui/SkeletonCard.tsxsrc/components/ui/cardStates.test.tsxsrc/components/widgets/QuickControlsWidget.tsxsrc/theme/customCss.stories.tsxsrc/utils/__tests__/cardTier.test.tssrc/utils/cardTier.ts
💤 Files with no reviewable changes (8)
- src/components/InputNumberCard.test.tsx
- src/components/InputSelectCard.test.tsx
- src/components/InputBooleanCard.test.tsx
- src/components/CoverCard.test.tsx
- src/components/InputTextCard.test.tsx
- src/components/TextCard.stories.tsx
- src/components/Separator.tsx
- src/components/InputDateTimeCard.test.tsx
Seven review findings on the tier-derivation PR. The skeleton violated the contract this change establishes: it rendered a plain Radix Card with no `liebe-card` class and no `data-tier`, while the selector contract now guarantees both on every rendered card. It stamps them itself rather than rendering through `GridCard` — the shell requires a truthful `domain` and carries the gesture controller, the more-info dialog and the edit-mode buttons, none of which a placeholder for an absent card may have. Its inline `minHeight` goes with it: the floor is now the sheet's, so it participates in the layered overrides an inline declaration outranked. Those floors were raw literals, unreachable through the token contract. They are now `--liebe-card-min-height-row` / `-tall`, catalogued in `src/theme/tokens.ts` and in the geometry table. The memo comparators of the three cards that accept a span ignored it. `span` rides beside `tier` precisely because the tier is lossy — a breakpoint change can move `row` 3x1 to `row` 4x1 — so a tier-only comparator holds the card at its last render and its `CardConfig.Modal` preview opens at the span it was memoized at. `isSameSpan` compares by value, since the grid builds a fresh object every render. The input helpers' controls resolved to Radix `2` after this change replaced the small-card `size="small"` (Radix `1`); the ones this change touched go to `size="3"`. 44px stays out of reach until #204. Finally, the glance error tile's rules — accessible name, tile-as-button, modal actions, no `title` — are behaviour, so they move from the change doc into the design-system spec that owns them.
…tion One conflict, and only in the design-system spec's changelog table: 0015 (#221) appended its "sparkline data source" row to main while this branch appended two 0011 rows. Kept every row — main's in place, this branch's as the tail, which is what a rebase would have produced. All three carry 2026-07-27, so the date alone does not order them. No source file conflicted.
Summary
Tier derivation and the plumbing that carries it to cards — the first task of
docs/changes/0011-layout-tiers.md.data-tieris now stamped, which closes the last deferred entry in the stable selector contract.Between this change and its PRs 2 and 3, a card placed small renders larger than it did, and a card placed large renders smaller. Every value that used to scale off
size— min-heights, the thermostat's arc radius, glyph sizes, button and text steps — is pinned to what themediumbranch produced, with a comment at each site naming the PR that owns the real tier layout.That is deliberate. Re-keying those tables onto tiers here would be doing PRs 2 and 3 badly, in a change whose scope is derivation and plumbing. But it is user-visible on
mainin the interim, so it belongs in the description rather than in the diff.Derivation
src/utils/cardTier.tsis pure — no React, no grid.deriveCardTier({width, height})partitions onwidth >= 2andheight >= 2intofull | row | tall | glance, total by construction withglanceas the floor.scaleSpanToColumnsmaps stored span to effective span (width only; rows do not scale with the breakpoint), floored at one cell.The card cannot be told something different from what the grid laid out.
GridLayoutSectioncomputeseffectiveSpanOf(item)once and reads the same expression twice — to build the react-grid-layoutlayout, and to callchildren(item, effectiveSpan). A test walks the rendered.react-grid-itemelements and asserts the callback's span equals thew/hthe grid received.offsetWidthis untouched: it still measures exactly what it measured before, and nothing was added toliebe-section— no padding, no border, no wrapper — so the constraint the theming spec records is preserved. The span plumbing is arithmetic on stored values, not measurement.tierdefaults torowrather than being required, because shells render outside grids (config preview, sidebar widget, stories) and the contract promisesdata-tieris present. A well-chosen default hides its own failure, so there is a test that a card inside a grid receives a derived tier and never the fallback — two spans the fallback cannot produce, asserted positively and againstrow. Severing the derivation fails it.The
sizeprop, and the tests that asserted itsizeis gone fromCardPropsandGridCard, which touches all 14 domain cards, the camera, the four weather variants,TextCardandSeparator. Non-grid consumers migrated: the config preview takes the effective span, the quick-controls widget passes an explicit 2×1, and the theme gallery derives each cell's tier from its own span.Twelve test blocks asserted the removed API. They were handled by shape, not uniformly:
size variantsblocks asserting only that the shell stamps a prop it was handed. That is shell behaviour, covered once in the shell's own test; seven per-card copies existed only becausesizewas a per-card prop. Translating them would have produced seven copies of one passthrough that look like per-card tier coverage while testing nothing card-specific.LightCard's, whose comment ties it to the shell's min-height stops that this PR replaces. That is a behaviour thread, not a passthrough.should render different sizes correctlyandshould show humidity in all sizesnever tested size; they asserted the same thing three times withsizeas decoration. The inner assertion is real, so each became one render and one assertion, with the misleading name dropped.data-size→data-tierin the shell's contract-attribute test.The condition on those deletions: the change document now requires PRs 2 and 3 to deliver a per-card tier assertion for each card, not only a story, naming that they replace the blocks removed here.
Floors, measured
The shell's three
data-sizemin-height stops became twodata-tierstops (60px glance/row, 120px tall/full), since height is the only thing a floor expresses and only the row count moves it. InnerminHeightfloors were removed from five cards — collapsing their size ternary tomediumhad left every card propped open from the inside at a height that would have stopped aglancetile ever being one cell tall, quietly defeating the change. The shell owns the floor now.Measured in Chromium against the shipped
dist/liebe.css, readingoffsetHeightand computedmin-block-size: the quick-controls strip is byte-identical at 72px (the floor never binds there), the config preview's 2×2 item grows 28px and a short TextCard preview shrinks 8px — both inside a panel that already reserves 200px and centres its content, so the dialog does not move.Testing
npm test— 1667 passed, 2 skippednpm run lint,npm run typecheck— cleannpm run build-storybook,npm run build:ha:prod— both succeedcr— no findings on the final state.codexunavailable (usage limit until 1 August)Found, not fixed
A pre-existing data-integrity bug surfaced while naming the scaling:
handleLayoutChangeinverse-scales every item on any drag, and the round-trip is lossy at narrow breakpoints — so moving one card on a phone silently rewrites the stored width and position of every other card. Untouched here; filed as #219, and worth fixing before PRs 2 and 3 have people dragging cards at every breakpoint.Scope
The per-card tier layouts are PRs 2 and 3; the change stays
draftuntil they land.Summary by CodeRabbit