diff --git a/docs/design/DESIGN_REVIEW.md b/docs/design/DESIGN_REVIEW.md index 81826e3..f16d6df 100644 --- a/docs/design/DESIGN_REVIEW.md +++ b/docs/design/DESIGN_REVIEW.md @@ -58,6 +58,28 @@ The first screen of each key page, before and after, at one scale: The whole gallery is reproduced by `pnpm run shots` at any commit; the before set is the tree of `v1.0.0`. Two measures of the change on a phone: the person page went from 8,111 px to 4,113 px of scrolling, the session page from 10,563 px to 9,702 px; the settings page grew by a card (the account) and a row (the sections). The accessibility sweep of the gallery stays at zero findings. +## Second pass, 2026-10-05 + +Every screen again, at full size this time, on a phone and on a desktop (and, from this pass on, on a tablet: the gallery photographs three devices). What the first pass had left: + +| Screen | Found | Changed | +| --- | --- | --- | +| Home | The due strip, a second-level heading, sat above the page's title. | The title and "Add a person" first; then what is due and the coming appointments; then the persons. | +| Mark | The reports kept a second primary button on the page; the chart's metric control overflowed the phone's width ("Colour contrast with the skin" cut off); the facts grid broke "since" into a narrow column on a desktop. | Reports fold under their title, as on the person page; the metric control wraps and the label is "Colour contrast"; the facts' columns are wider. | +| Visit | The quality note had landed after the controls and the privacy note, far from the photograph; three primary buttons (take a photo, measure, save); one long column on a desktop. | The note is the photograph's caption; the primary action follows the state (take a photo until there is one, measure until there is a measurement, save when something changed); from 900 px the photographs sit on the left, sticky, and the measurements and notes on the right. | +| Session | Eighteen regions in one column on a desktop. | Two columns from 900 px; the next region spans both. | +| Session zone | Photo, marks and panels in one column on a desktop. | From 900 px the photograph sits on the left, sticky, the marks and their panels on the right. | +| Settings | Fourteen cards in one column, 13,900 px on a phone; the administrator's groups the longest. | The occasional and administrative groups fold under their titles with a hint (second factor, reference card, your data, accounts, email delivery, webhooks, backups, instance); the list of sections opens the one it points to; account, appearance and reminders stay open. | +| Sign in | The wordmark larger than in the shell. | The shell's size. | + +New rules from this pass: the primary action is a function of the page's state, not a fixed button; a control with several options wraps rather than overflows; a group of settings used once a season folds like a tool. + +The five screens that changed most, after the pass, on the three devices the gallery photographs: + +![Second pass, three devices](review/board-second-pass.png) + +On a phone the settings page went from 13,873 px to 6,846 px of scrolling. The accessibility sweep stays at zero findings on all three devices. + ## Deferred, for a decision The brief proposed a bottom navigation bar on phones (Map, Due, Add, Visit). The interface has a top bar (Persons, Settings) and the person page as the hub, and this pass keeps it: the hub carries the one primary action within the thumb's reach, and a bottom bar would duplicate what the person page now does. The question for the Product Owner, with a default: diff --git a/docs/design/review/board-second-pass.png b/docs/design/review/board-second-pass.png new file mode 100644 index 0000000..bbfb083 Binary files /dev/null and b/docs/design/review/board-second-pass.png differ diff --git a/frontend/e2e/gallery.spec.ts b/frontend/e2e/gallery.spec.ts index 8ed1ffd..e3b6281 100644 --- a/frontend/e2e/gallery.spec.ts +++ b/frontend/e2e/gallery.spec.ts @@ -80,6 +80,7 @@ test("every screen, for a look at the design", async ({ page }, info) => { await measuredVisit(page, false); await page.getByRole("link", { name: /Back to Chest mark/ }).click(); await expect(page.getByRole("slider", { name: /Longest diameter at 2 visits/ })).toBeVisible(); + await openTool(page, "mark-reports", "Reports"); await page.getByRole("button", { name: "Make the record of this mark", exact: true }).click(); await expect(page.getByRole("link", { name: /Download the PDF/ })).toBeVisible({ timeout: 60_000 }); await snap("08-mark"); @@ -150,6 +151,7 @@ test("every screen, for a look at the design", async ({ page }, info) => { await page.getByRole("link", { name: "Settings", exact: true }).click(); await expect(page.getByRole("heading", { name: "Settings", exact: true })).toBeVisible(); await snap("17-settings"); + await openTool(page, "second-factor", "Second factor"); await page.getByRole("button", { name: "Set up a second factor", exact: true }).click(); const dialog = page.getByRole("dialog"); await expect(dialog).toBeVisible(); @@ -164,6 +166,7 @@ test("every screen, for a look at the design", async ({ page }, info) => { await expect(page.getByRole("region", { name: "Recovery codes" })).toBeVisible(); await snap("20-settings-recovery-codes"); await page.getByRole("button", { name: "I have saved them", exact: true }).click(); + await openTool(page, "instance", "Instance (administrator)"); await page.getByRole("link", { name: /Evaluation set/ }).click(); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); await snap("21-evaluation"); diff --git a/frontend/e2e/journey.spec.ts b/frontend/e2e/journey.spec.ts index c0db54a..299c0c5 100644 --- a/frontend/e2e/journey.spec.ts +++ b/frontend/e2e/journey.spec.ts @@ -63,6 +63,7 @@ test("from a fresh instance to a measured mark, accessibly", async ({ page }) => await expect(chart).toBeVisible(); // The mark's record as a PDF, rendered by the background worker. + await openTool(page, "mark-reports", "Reports"); await page.getByRole("button", { name: "Make the record of this mark", exact: true }).click(); const pdf = page.getByRole("link", { name: /Download the PDF/ }); await expect(pdf).toBeVisible({ timeout: 60_000 }); @@ -129,6 +130,7 @@ test("a full-body session: regions photographed or skipped, a mark pointed at, a test("the second factor: set up in the settings, then a sign-in that needs the code", async ({ page }) => { await signIn(page); await page.getByRole("link", { name: "Settings", exact: true }).click(); + await openTool(page, "second-factor", "Second factor"); await page.getByRole("button", { name: "Set up a second factor", exact: true }).click(); const dialog = page.getByRole("dialog"); await dialog.getByLabel("Password").fill(PASSWORD); @@ -179,9 +181,11 @@ test("the second factor: set up in the settings, then a sign-in that needs the c // Leave the account as the other tests expect it. await page.getByRole("link", { name: "Settings", exact: true }).click(); + await openTool(page, "second-factor", "Second factor"); await expect(page.getByText("The second factor is on. 9 recovery codes left.")).toBeVisible(); await page.getByRole("button", { name: "Turn it off", exact: true }).click(); await dialog.getByLabel("Password").fill(PASSWORD); await dialog.getByRole("button", { name: "Confirm", exact: true }).click(); + await openTool(page, "second-factor", "Second factor"); await expect(page.getByRole("button", { name: "Set up a second factor", exact: true })).toBeVisible(); }); diff --git a/frontend/playwright.gallery.config.ts b/frontend/playwright.gallery.config.ts index b25caa1..27d882f 100644 --- a/frontend/playwright.gallery.config.ts +++ b/frontend/playwright.gallery.config.ts @@ -6,6 +6,16 @@ import { defineConfig, devices } from "@playwright/test"; const stamp = Date.now(); const SERVERS = [ { name: "phone", port: 8791, device: devices["Pixel 7"] }, + { + name: "tablet", + port: 8793, + device: { + ...devices["Desktop Chrome"], + viewport: { width: 820, height: 1180 }, + isMobile: true, + hasTouch: true, + }, + }, { name: "desktop", port: 8792, diff --git a/frontend/src/design-system/components/Disclosure.tsx b/frontend/src/design-system/components/Disclosure.tsx index ef4b504..7aeee9d 100644 --- a/frontend/src/design-system/components/Disclosure.tsx +++ b/frontend/src/design-system/components/Disclosure.tsx @@ -17,6 +17,15 @@ export function Disclosure({ id, title, hint, defaultOpen = false, children }: P useEffect(() => { if (defaultOpen) setOpen(true); // something became live: show it; never fold on the person's behalf }, [defaultOpen]); + useEffect(() => { + // A link to the tool (#id) opens it, on arrival and when the hash changes. + const sync = () => { + if (window.location.hash === `#${id}`) setOpen(true); + }; + sync(); + window.addEventListener("hashchange", sync); + return () => window.removeEventListener("hashchange", sync); + }, [id]); return (
- -

{t("admin.emailTitle")}

+

{t("admin.emailIntro")}

@@ -138,14 +137,10 @@ export function AdminSettings() { {test.error && {test.error.message}} {test.isSuccess && {t("admin.testSent")}} -
- - - - {t("admin.evaluationLink")} - - -

{t("admin.instanceTitle")}

+ + + +