Visual review

Unit specs render a component on its own, in jsdom: they never see a page that stopped scrolling, a washed-out dark theme or a button overflowing on mobile. Each of the four front-end apps therefore has a gallery: it serves the production build, drives it with Playwright and captures every page.

cd front/application        # or landing, docs, playground
npm run build && npm run gallery

The captures and the contact sheets — every page in a grid, one per theme — land in tools/.gallery/. Set CHROMIUM_PATH if Playwright cannot find a browser.

When to run it

After any change to a layout, a theme token, the shell or @blouppy/ui: those touch every page at once.

The app is signed in, so its gallery answers for the back end itself: tools/gallery-fixtures.mjs serves the responses and Playwright fulfils the requests — nothing is started. It captures two lists, declared in tools/gallery-scenes.mjs:

  • PAGES — every route the router has. Keep it exhaustive: a route missing from here is a page nobody looks at.
  • SCENES — the states a URL cannot reach: an open dialog, a toast, a scrolled page. Each names the invariant it holds down.

Both go through both themes at three widths. Fixtures are shaped to make layout fail visibly — long names, many rows — rather than to be realistic, and every page is expected to render full: a capture showing an empty state is a missing fixture. GALLERY_TRACE=1 names the endpoint that fell through.

Narrow a run while iterating:

npm run gallery -- --page=workspaces --theme=dark --width=mobile --no-sheet

Contrast

The playground also measures it: npm run contrast in front/playground/ checks the design tokens against the contrast floors DESIGN.md sets. The gallery shows what a change looks like; contrast says whether it is legible.