diff --git a/AGENTS.md b/AGENTS.md index e703dec2..2b3d5428 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -220,6 +220,10 @@ The SDLC skills own the phases. What is specific to this repo: - **jsdom lays nothing out, and the dangerous half of that is the assertions it _passes_.** A width assertion against a 0-wide box fails loudly and is easy to diagnose. Its neighbour — `strip.scrollWidth <= strip.clientWidth + 1`, "nothing overflows" — evaluates `0 <= 1` and goes green having measured nothing at all. So the rule for any geometry claim in this repo, in a story or a `__tests__` spec, is that a passing one in jsdom is worth nothing: the runner's `BROWSER_ONLY` map is where such a story is named, with its reason, and the map is self-verifying (a listed story is still executed and must still throw). - **The dispatch guard is process-wide, so a hanging service call contaminates every test after it.** `src/services/guardedDispatch.ts` keeps its pending set at module scope on purpose — the guarantee is about a command reaching Home Assistant at most once, not about one component. A test that leaves a call unsettled (the workshop's `serviceCall: 'pending'` fixture, a mock that never resolves) leaves that command in flight, and the next identical command is **admitted as a success**. That is how `ActionCard/Activating` rendered the success check while asserting the in-flight spinner — a failure that looks exactly like a card defect and is not. `resetDispatchGuard()` in a `beforeEach` is the fix; reach for it before believing a cross-test result. + **Making a class of assertion executable for the first time breaks `main` on merge order alone, and the PR that does it is not the one at fault.** Every branch in flight is a source of assertions that have never run, so the enabling PR is qualified against the assertions that existed when it was tested, and each in-flight PR's assertions are qualified against a runner that ignored them. Neither can see the other; whichever lands second turns the pair red. It happened here within hours: [0037](docs/changes/0037-card-state-and-capability-correctness.md) PR 3 ([#322](https://github.com/fx/liebe/pull/322)) added a `CoverCard/UnknownEntity` story asserting `.liebe-card`, and 0040 PR 6 ([#323](https://github.com/fx/liebe/pull/323)) made play functions execute — each green alone, red together, `main` broken by neither one's content. + + Two things follow. **Re-run the full suite after merging `main`, and read the failure before assuming it is yours** — a detached worktree at `origin/main` with your branch absent settles ownership in one command, and here it showed the failure was main's. And **expect the first run of a newly-executable gate to fail on somebody else's assertion**: the assertion is usually right and the code usually wrong, which is the whole reason it was written and never checked, so the fix is to establish which — not to weaken the assertion so the gate goes quiet. Weakening it would be a defect in the change, and it also discards the finding the gate just bought. + 7. **Playwright's own two prerequisites** A workspace that has never run the suite is missing both the browser and the libraries it links against, and only the first says so plainly: diff --git a/docs/changes/0043-card-tile-control-semantics.md b/docs/changes/0043-card-tile-control-semantics.md index d59cb36f..43167728 100644 --- a/docs/changes/0043-card-tile-control-semantics.md +++ b/docs/changes/0043-card-tile-control-semantics.md @@ -86,7 +86,11 @@ Three decisions this change does take: - [ ] **PR 3 — Keyboard routes to the hold and double-tap actions**: implement what PR 1 settled for the two secondary gestures, so a `glance` card's detail dialog and a card's `doubleTapAction` are reachable without a pointer; coverage per route, including a card whose `holdAction` is `none` - [ ] **PR 4 — The detail dialog carries the failure, and `Dismiss`**: the shell passes the current failure message and the recovery actions to `EntityDetailDialog` while `isError` holds, and pressing the tile in the error state reaches it at every tier; `Dismiss` clears the error and dispatches nothing; the `title` tooltip is no longer what carries the message; coverage including an icon-only tile, whose accessible name already carries the message and whose press had nowhere to go - [ ] **PR 5 — `Retry` re-dispatches the command that failed**: for the service-call state only, per PR 1's scoping — the failed dispatch is retained so the recovery action can repeat it, through the confirmation gate and through the at-most-once guard rather than around either, implementing PR 1's boundary between a failure that releases the guard and an ambiguous one that does not; coverage for the gated case, the guard-refused case, the guard-admitted case and the ambiguous-outcome case -- [ ] **PR 6 — Reconcile the stand-in error tile with the shell's**: `ErrorDisplay variant="card"` implements the contract in a second place and its callsites then withhold the actions — no production callsite passes `onDismiss`, and the entity-not-found path passes no `onRetry` — so the tile renders a button onto a message the user cannot act on. Fold it onto the shell's, or record why a tile that stands in for a card is not a card tile; either way the rendered tile ends the PR actually offering the recovery actions, rather than merely being a component capable of rendering them +- [ ] **PR 6 — Reconcile the stand-in error tile with the shell's**: `ErrorDisplay variant="card"` implements the contract in a second place and its callsites then withhold the actions — no production callsite passes `onDismiss`, and the entity-not-found path passes no `onRetry` — so the tile renders a button onto a message the user cannot act on. Fold it onto the shell's, or record why a tile that stands in for a card is not a card tile; either way the rendered tile ends the PR actually offering the recovery actions, rather than merely being a component capable of rendering them. + + **This PR is closing a stated MUST, not choosing between two defensible treatments.** [design-system — size-adaptive layouts](../specs/design-system/index.md#size-adaptive-layouts) already requires it: "The states a card renders **instead of** itself — loading, error, unavailable — are tiles like any other and MUST take the tier of the card they stand in for". The two tiles that go through `ErrorDisplay variant="card"` — not-found and disconnected — stamp **no `data-tier` at all**, so they are not merely unthemed, they render at the wrong size in breach of that MUST. They also stamp no `liebe-card`, which is the theming handle; both attributes are missing together and for the same reason. + + **Keep those two claims apart, because conflating them is what produced an error here.** _Bypasses `GridCard`_ is true of **all three** lifecycle tiles — which is why none of them carries the shell's edit affordances, the finding this task exists for. _Lacks `liebe-card` and `data-tier`_ is true of **two**. The skeleton is the one already compliant: `SkeletonCard` renders `