From 57c5e3233b7212a688cca5a650e70a0ff9c29c9c Mon Sep 17 00:00:00 2001 From: stroblme Date: Sun, 23 Aug 2026 22:07:21 +0200 Subject: [PATCH] Draw the colour disc inside its tile rather than past it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The disc was sized against its parents in percentages, and every box between it and the tile is sized by what is in it — so the chain never resolved, the brightness slider grew, and the tile scrolled with half a disc showing. Container units are the tile's own height whatever sits in between, so the widget now fits exactly. Two guards behind it, since both are things a picture can fail silently: nothing on a panel of pictures may overflow its tile, and Touch has to actually make a control bigger while leaving what it publishes alone. --- NOTEPAD.md | 9 +--- .../src/components/Dashboard/ColorWidget.tsx | 24 +++++----- .../src/components/Dashboard/ui/core/core.css | 24 ++++++++++ .../components/Dashboard/ui/glass/Data.tsx | 2 +- .../components/Dashboard/ui/material/Data.tsx | 2 +- frontend/tests/widgets.spec.ts | 47 +++++++++++++++++++ 6 files changed, 86 insertions(+), 22 deletions(-) diff --git a/NOTEPAD.md b/NOTEPAD.md index 0c820b7..126ec04 100644 --- a/NOTEPAD.md +++ b/NOTEPAD.md @@ -100,13 +100,8 @@ Deferring because out of scope is fine, but don't mention deferring than. across all 16 columns. Another tile needs a rearrangement first — worth doing together with re-authoring `PAGES` as a single section. - CHORE/TEST: no Playwright coverage for the colour tile — `tests/widgets.spec.ts` covers the - other controls. Wants a spec that drags the hue ring and asserts one publish on release. -- CHORE/UI: the colour wheel's arrow keys move a fixed 5°, with no PageUp/PageDown or - Home/End. Fine for picking by eye; a panel that wants an exact hue has no coarse/fine step. -- CHORE/UI: the colour wheel reads the pointer's angle only, never its distance from the - centre (marked `ponytail:` in `ColorWidget.tsx`), so a colour takes two gestures — the ring, - then saturation. A saturation-by-radius disc would need a second axis on a control that can - announce one value. + other controls. Wants a spec that drags the disc and asserts one publish on release. The + maths is checked (`color.check.ts`); what is not is the gesture. - CHORE/DEMO: `seed_demo.py` still writes the Home dashboard as three titled sections ("Right now", "Energy and comfort", "Yield model"). The editor and the panel now read a page's sections as one grid, so those headings are no longer drawn and the first editor diff --git a/frontend/src/components/Dashboard/ColorWidget.tsx b/frontend/src/components/Dashboard/ColorWidget.tsx index 5701ee2..ae01e05 100644 --- a/frontend/src/components/Dashboard/ColorWidget.tsx +++ b/frontend/src/components/Dashboard/ColorWidget.tsx @@ -55,19 +55,17 @@ export function ColorWidget({ widget, dashboard }: WidgetProps) { // The pulse hangs off the frame, so it stays outside every box below. <> {pulse} -
- { - if (draft === null) return - send(encodeColor(draft, format)) - setDraft(null) - }} - /> -
+ { + if (draft === null) return + send(encodeColor(draft, format)) + setDraft(null) + }} + /> ) } diff --git a/frontend/src/components/Dashboard/ui/core/core.css b/frontend/src/components/Dashboard/ui/core/core.css index 5734fcc..47f9eaf 100644 --- a/frontend/src/components/Dashboard/ui/core/core.css +++ b/frontend/src/components/Dashboard/ui/core/core.css @@ -118,6 +118,29 @@ } } +/* + * The colour widget: a disc, and brightness beside it. + * + * Sized in container units rather than percentages. A percentage height only + * resolves against a parent whose own height is already known, and every box + * between here and the tile is sized by its contents — so the chain never + * resolved and the slider grew until the tile scrolled. `cqh` is the tile's + * height whatever is in between. + */ +.dui-color { + display: grid; + height: 100cqh; + min-height: 0; + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + justify-items: center; + gap: 0.75rem; +} + +.dui-color > .dui-slider { + height: 100cqh; +} + /* * The colour disc. * @@ -132,6 +155,7 @@ position: relative; width: min(100cqh, 100cqw - 3.5rem); max-width: 100%; + max-height: 100cqh; aspect-ratio: 1; border-radius: 50%; touch-action: none; diff --git a/frontend/src/components/Dashboard/ui/glass/Data.tsx b/frontend/src/components/Dashboard/ui/glass/Data.tsx index 9709987..63a667a 100644 --- a/frontend/src/components/Dashboard/ui/glass/Data.tsx +++ b/frontend/src/components/Dashboard/ui/glass/Data.tsx @@ -177,7 +177,7 @@ export function ColorDisk({ }) const [hue, saturation, brightness] = hsv return ( -
+
+
{ await expect(rows.nth(2)).toContainText(/grid/i) }) +test("a widget is drawn inside its tile rather than scrolled", async ({ + page, +}) => { + await openPanel(page) + + // A picture that overflows its tile is a picture nobody can see the rest of. + // Only the widgets that are text or a list may scroll, and this panel holds + // none of them — so nothing here should. + const overflowing = await page.evaluate(() => + [...document.querySelectorAll(".dui-frame-body")] + .filter((body) => body.scrollHeight > body.clientHeight + 1) + .map((body) => body.closest("[data-testid=widget-frame]")?.textContent), + ) + expect(overflowing, "these widgets overflow their tile").toEqual([]) +}) + test("the icon follows what the message says", async ({ page }) => { await openPanel(page) const glyph = page.getByTestId("icon-glyph") @@ -461,6 +477,37 @@ test("a chart's cursor follows the pointer", async ({ page }) => { ).toBeLessThan(3) }) +test("touch makes the controls bigger without changing what they do", async ({ + page, +}) => { + await openPanel(page) + const control = page.getByTestId("widget-frame").filter({ hasText: "Mode" }) + const pointer = (await control.getByRole("button").first().boundingBox())! + .height + + await setLook(page, dashboardName, { touch: { value: true } }) + await openPanel(page) + await expect(page.getByTestId("canvas-surface")).toHaveAttribute( + "data-touch", + "", + ) + const touched = (await control.getByRole("button").first().boundingBox())! + .height + expect( + touched, + `a segment is ${touched.toFixed(1)} touched and ${pointer.toFixed(1)} pointed at`, + ).toBeGreaterThan(pointer) + + // The control is the same control: it still publishes what it always did. + await control.getByRole("button", { name: "Boost" }).click() + await expect(control.getByRole("button", { name: "Boost" })).toHaveAttribute( + "aria-pressed", + "true", + ) + + await setLook(page, dashboardName, { touch: { value: false } }) +}) + /** * What a dashboard was told to wear, as a wall panel would be told. *