Skip to content

Modernize the frontend: tests, MUI 7, agent rules, new theme with dark mode - #434

Merged
pashidlos merged 29 commits into
masterfrom
claude/project-update-plan-a2da97
Oct 24, 2026
Merged

pashidlos merged 29 commits into
masterfrom
claude/project-update-plan-a2da97

Conversation

@pashidlos

@pashidlos pashidlos commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

What and why

Prepares the frontend for the next stretch of work in four steps, in this order: pin today's behaviour with tests, move tooling and UI libraries to current versions, write down the project's rules for contributors and coding agents, then restyle the app on MUI. Functionality is unchanged except one crash fix (below).

Tests

  • Unit and component tests: 47 → 541. Line coverage, measured the same way before and after: 6.5% → 51.8%. CI enforces the reached level, and the thresholds can only go up.
    • Unit: services (URLs, methods, bodies), helpers, context reducers.
    • Components: forms with their validation, debounced search, bulk actions, user list, header, approve/reject with hotkeys, comments, socket updates.
  • Playwright: 123 → 293 (this count includes the tests master gained in the meantime).
    • New areas: image-comparison engine forms, ignore areas, comments, dialog hotkeys, matching variations, zoom toolbar, colour mode.
    • A visual contract, screens.spec.ts: a screenshot of every view and notable state (25) in light and dark, 50 baselines in all.
  • Jest is replaced by Vitest on the Vite config, and the suite runs in seconds.

Tooling and libraries

  • MUI 5 → 7.
    • Grid now uses GridLegacy, so the old layouts stay pixel-identical.
    • @mui/styles is removed in favour of sx/styled. The screenshots stayed identical.
  • MUI X Data Grid 6 → 8 (new selection model), react-material-ui-form-validator 3 → 5.
  • react-debounce-input is replaced by DebouncedTextField, which has the same semantics and is pinned by tests.
  • ESLint 9 flat config enforces the layering: services, types, helpers and constants never import UI; fetch is allowed only in src/services; @mui/styles and MUI internals are banned.
  • Routes are lazy-loaded. The main chunk drops from 1.6 MB to 137 KB, and the largest chunk is now 480 KB.
  • husky 9 runs a real pre-commit hook; the husky v4 block did nothing.
  • CI now runs lint, typecheck, tests with coverage and the build, on Node 22.
  • 25 type errors are fixed with type-only changes. test-docker.sh no longer overwrites the host's node_modules.

Rules for contributors and agents

  • AGENTS.md covers the layout, import flow, conventions and test organisation. It also lists the ids, labels and class names that page objects depend on, and the definition of done.
  • Also added: CLAUDE.md, CONTRIBUTING.md, a PR template, and .claude/settings.json (check commands allowed, Prettier on edit).

Redesign (MUI, option A)

  • Design tokens in src/theme:
    • primary is the logo's cyan, with slate neutrals;
    • Inter, bundled with the app, so screenshots do not depend on a CDN;
    • softer radius and shadows.
  • Dark mode. It follows the system and remembers the user's choice.
  • Status colours are semantic everywhere: new is info, unresolved is warning, failed is error, passed and approved are success. A test keeps their contrast at WCAG AA in both modes.
  • New header, centred sign-in and registration, the project list as a card grid, and the review dialog themed.
  • Second pass:
    • variations list and details, with cards, tag chips and baseline history;
    • profile and users with page headers;
    • empty states: no builds, no build selected, no projects, no variations;
    • a themed guided tour.

Fixed along the way

How it was checked

  • npm run lint and npm run typecheck
  • npm test with coverage thresholds
  • npm run test:pw:docker: 293 passed. Screenshots were regenerated in Docker only, and I looked at every changed PNG.

Known behaviours, pinned by tests and left for follow-ups

  • Escape does not close the details dialog, because the MUI Dialog swallows it before the document-level hotkey.
  • After the diff switch is clicked it keeps focus, and D and the arrow keys stop working.
  • testVariation.merge does not URL-encode branch names.
  • CommentsPopper has no catch on a failing save.

Follow-ups

  • React 19 (needs react-joyride 3 and react-konva 19 together).
  • TanStack Query for server state.
  • react-hook-form + zod for the forms.
  • Move the remaining GridLegacy screens to Stack/Grid.
  • ESLint 10, once eslint-plugin-react supports it.

🤖 Generated with Claude Code

pashidlos pushed a commit that referenced this pull request Oct 24, 2026
- the chunk-reload guard allows one reload a minute and survives blocked
  storage, instead of being cleared on every load (could loop)
- the avatar menu's user block is a ListSubheader, so opening the menu
  focuses its first item rather than the name
- ColorModeToggle moves to components (the theme layer no longer imports
  UI) and holds its place until the colour mode is known
- the white canvas and thumbnail backgrounds are named tokens
  (imageSurface), per AGENTS.md
- AppBar and DataGrid overrides work under a theme without CSS variables
- page object nits from SonarCloud (.at(-1), String.raw)
- the superpowers plan leaves the repo: it was a working document for this
  PR; what lasts is in AGENTS.md and the PR description

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pavlo Strunkin and others added 27 commits October 24, 2026 12:50
- tsc: type-only fixes for the 25 errors in src and the e2e project;
  typecheck now covers integration_tests and runs in CI
- eslint: root config, so a nested checkout does not pick up the parent's
- husky 9 with a real pre-commit hook (the husky v4 block did nothing)
- CI on Node 22 (.nvmrc), actions v4, lint/typecheck/test/build gates
- test-docker.sh keeps the Linux node_modules in a volume instead of
  overwriting the host's platform binaries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Vitest 5 on the Vite config: the 47 tests run in ~1s instead of ~8s
- Testing Library 16 and user-event 14; renderWithProviders wraps a
  component in the app's providers for component tests
- the theme moves to src/theme so tests and App share it
- Node 22 in the Dockerfile and @types/node 22 (Vitest 5 needs them)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md describes the layout, the one-way import flow, conventions, how
tests are organised and which ids/labels/class names page objects depend
on. CLAUDE.md points at it; .claude/settings.json allows the project's
check commands and formats edited files. CONTRIBUTING.md and a PR template
carry the same definition of done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Giving the fixtures an updatedAt to satisfy the type added a 'Last run'
line to the screenshots; the components render it only when present.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mui/styles is deprecated and does not work with MUI 7. Same CSS values;
the screenshot baselines match unchanged in Docker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ESLint 9 + typescript-eslint 8, react, react-hooks 5.2 (7.x adds React
  Compiler rules the current code does not meet), jsx-a11y, prettier
- no-restricted-imports keeps services/types/_helpers/constants out of
  the UI layers and bans @mui/styles and MUI internals; fetch only in
  src/services
- lint-staged runs eslint --fix before prettier
- tsconfig: bundler module resolution, automatic JSX runtime;
  Router.jsx becomes Router.tsx

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The single 1.6 MB chunk becomes per-route chunks; the login screen no
longer waits for Konva, the data grid and the review UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ching variations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MatchingVariationsDialog took three icons as default imports from the
per-icon files; the production bundle hands those over as module objects
and React failed with error #130, unmounting the app. Named imports, as
everywhere else. The e2e test that pinned the crash now checks the view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the approved-run hotkey test repeats its round until the dialog's
  hotkeys are bound; the full-size test no longer trips strict mode
- Testing Library waits up to 5s and Vitest 15s per test: under load the
  default 1s findBy timeout failed MUI dialogs at random

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- selection is { type, ids } now: the table converts it both ways, and
  the header box's exclude list resolves against the rows on show
- valueGetter takes (value, row); custom toolbars need showToolbar;
  custom cells ask for display: flex to stay vertically centred
- the user list reads the selection with useGridSelector
- Vitest transforms the grid's ESM build (it imports a directory)
- two baselines regenerated in Docker: sub-pixel cell and sort-icon
  differences, nothing moved

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same behaviour as the library at the three call sites, pinned by the
component tests: reports after 300 ms, ignores input shorter than
minLength while typing but reports a deletion below it as empty, Enter
and blur report a pending change at once, an outside value (reset) is
shown unless a change is pending, and a pending change survives unmount.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Grid becomes GridLegacy, which lays out exactly as MUI 5's Grid did;
  pages move to the new Grid/Stack as the redesign rewrites them
- icons only carry their data-testid in development builds now, and e2e
  runs on the production bundle: the icons page objects find keep it
  explicitly
- Switch drops the deprecated inputProps in favour of slotProps.input, so
  aria-labels go there; switches have role switch, pagination marks the
  current page with aria-current=page (page objects follow)
- six baselines regenerated in Docker: chip label text moves by a pixel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- design tokens in src/theme: the VRT logo's cyan as primary, slate
  neutrals, Inter (bundled), 10px radius, quiet component overrides
- light and dark colour schemes on CSS variables; the header toggle
  follows the system until the viewer picks, and remembers the pick
  (localStorage vrt-color-mode)
- one meaning per status colour everywhere: new info, unresolved warning,
  failed error, passed/approved success; a theme test keeps their text at
  WCAG AA contrast in both modes
- header: product name, colour mode, help and avatar menu with the
  signed-in user; login and register share a centred AuthLayout; the
  project list is a grid of cards with an 'Add project' button
- the layout is a flex column instead of a 10%/90% height split
- hard-coded colours in the review dialog, the tour and the variations
  dialog now come from the theme
- e2e: colour mode toggle, persistence, system preference and three dark
  screenshots; baselines regenerated in Docker and reviewed one by one;
  the wrap test gets a name long enough to wrap in any font

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…UI 7 move

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- reload once when a lazy page chunk is gone after a redeploy, instead of
  leaving a blank page
- the screenshot canvas stays white in dark mode, so transparent pixels
  look the same to every reviewer
- the table's selection model is memoised, so the grid does not re-sync
  on every render

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test matcher types

- both workflows cancel the previous run of a pull request on a new push;
  runs on master always finish
- test-docker.sh mounts npm's cache into the container (a host folder in
  CI, kept by actions/cache keyed on package-lock.json; a named volume
  locally) and installs with --prefer-offline
- jest-dom 7 and its vitest entry in tsconfig: 6.1 augmented
  @vitest/expect, which Vitest 5 no longer reads, so CI's typecheck saw
  no DOM matchers (the local check borrowed types from a parent folder)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the chunk-reload guard allows one reload a minute and survives blocked
  storage, instead of being cleared on every load (could loop)
- the avatar menu's user block is a ListSubheader, so opening the menu
  focuses its first item rather than the name
- ColorModeToggle moves to components (the theme layer no longer imports
  UI) and holds its place until the colour mode is known
- the white canvas and thumbnail backgrounds are named tokens
  (imageSurface), per AGENTS.md
- AppBar and DataGrid overrides work under a theme without CSS variables
- page object nits from SonarCloud (.at(-1), String.raw)
- the superpowers plan leaves the repo: it was a working document for this
  PR; what lasts is in AGENTS.md and the PR description

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
screens.spec.ts renders 25 views and states in both colour schemes (50
baselines, generated in Docker, stable over three repeats): sign-in
(password and SSO), registration, the project list (full and empty)
with its create/edit/delete dialogs, the project page as table, grid,
grouped, with a selection and without builds, the review dialog with an
ignore area, comments and the matching variations dialog, the variation
list and details, profile, users, both header menus, the guided tour and
an error toast. AGENTS.md makes it the place new views are added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ates, tour

- PageHeader and EmptyState components shared by the pages
- variations: page title with the project select, merge and filters as
  panels, cards with a framed thumbnail, tag chips and the branch;
  details: the name over its branch and axes, baseline history as cards
- profile and users get the page header, theme buttons and spacing; the
  API key reads as code
- empty states for the build list, the project page without a build,
  an empty project list and an empty variation list
- the guided tour takes the theme: radius, type, a filled next button
- screens.spec baselines regenerated in Docker and reviewed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the build menu's MoreVert icon keeps its data-testid explicitly, as the
  production bundle drops it under MUI 7 (see AGENTS.md)
- the six screenshots #435 added are regenerated in Docker for the new
  theme and reviewed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pashidlos
pashidlos force-pushed the claude/project-update-plan-a2da97 branch from df1b337 to 3f8202d Compare October 24, 2026 10:03
Pavlo Strunkin and others added 2 commits October 24, 2026 14:27
- BuildList: the loading/failed/empty states move to BuildListPlaceholder
  (cognitive complexity 19 → under 15)
- screens.spec names its extended fixture test, so Sonar sees the tests
  (S2187 'add some tests to this file' was a false positive)
- theme.ts imports the Data Grid theme augmentation as a side effect
  instead of an empty type import
- mocks and the fetch stub without await are plain functions returning
  their promise; canvas widths are read in parallel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- surfaces in three steps instead of navy: page #111318, panels #181b21,
  and menus, dialogs and popovers one step lighter (#20242c) with a hairline
  border where light mode uses a shadow
- the logo cyan toned down for dark (#5ec3d6), softer status hues; filled
  status chips become a tint with coloured text; selected rows a light
  tint; tooltips lifted off the page
- contrast stays at WCAG AA (theme test); dark baselines regenerated with
  --update-snapshots=all, since the 0.2 pixel threshold reads navy and
  graphite as the same colour

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@pashidlos
pashidlos merged commit 7de9398 into master Oct 24, 2026
4 checks passed
@pashidlos
pashidlos deleted the claude/project-update-plan-a2da97 branch October 24, 2026 12:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant