Skip to content

feat(ui): adopt Seven design system across the frontend#65

Merged
aabrius merged 12 commits into
developfrom
feat/frontend-seven-ds
Jun 18, 2026
Merged

feat(ui): adopt Seven design system across the frontend#65
aabrius merged 12 commits into
developfrom
feat/frontend-seven-ds

Conversation

@aabrius

@aabrius aabrius commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

What

Migrates the entire frontend to the Seven design system (@etus/seven-react) so the boilerplate is a clean DS base for derived products. Every UI element now comes from Seven — no raw HTML controls or local UI primitives remain.

Changes

Foundation

  • index.css: drop tokens/palettes/utilities that collided with Seven; JetBrains Mono per DESIGN.md; Seven owns color/radius/spacing/typography.
  • index.html: load Inter + JetBrains Mono.

Shell

  • AppSidebar rewritten on Seven's Sidebar system (collapsible icon rail, ⌘B, mobile sheet); _authenticated shell = SidebarProvider + SidebarInset.
  • Seven Toaster in main.tsx.

Screens (logic preserved, presentation = Seven)

  • settings / account / team / integrations: TextInput, Divider, Switch, Checkbox, Table, Dialog.
  • dashboard: hand-rolled stat cards → DashboardCard.
  • login: centered card → split-screen matching Seven's curated /login (<main>+<aside> landmarks, mint OAuth button, brand panel).
  • workspaces: super-admin banner → Callout; empty-state → Empty.

Guard-rails & docs

  • ESLint bans raw <button>/<input>/<select>/<textarea> in routes (proven to fire).
  • CLAUDE.md documents the "UI = Seven" rule.
  • Deleted the 4 local UI primitives (input/separator/skeleton/sonner) once unused.

Notable fix

The rewrite surfaced a real bug: the sidebar user menu never opened because SidebarAvatar doesn't forward props (so it couldn't be a DropdownMenuTrigger). Switched to the canonical SidebarMenuButton.

Testing

  • 230 client unit tests pass; coverage 89.7% stmts / 84.5% branch / 94.3% funcs.
  • typecheck + lint + build green.
  • Every screen validated in the browser against the published showcase (seven.etus.io).

Known limitation (not addressed here)

Public-route bundle: /login's own chunk is 4.6 kB, but every route loads a shared 2.2 MB chunk because @etus/ui ships a monolithic, non-tree-shakeable index.js. sideEffects:false + cleared cache produced a byte-identical build, so the fix is upstream in Seven (per-component ESM + sideEffects). Documented in the migration plan (U4.2).

Out of scope

E2E (Playwright) selectors not yet updated for the new UI.

🤖 Generated with Claude Code

aabrius and others added 12 commits June 16, 2026 01:12
chore: promote develop to master (gateway account roles + routing fixes)
Foundation: reconcile index.css so @etus/tokens wins (drop colliding
radius/shadow/breakpoint/text tokens, the 88 color palettes, and the
decorative utilities banned by DESIGN.md); load Inter + JetBrains Mono.

Shell: sidebar.tsx becomes AppSidebar built on Seven's Sidebar system
(single mode, collapsible icon, SidebarAvatar); _authenticated.tsx mounts
SidebarProvider + SidebarInset.

Screens: settings/account/team/integrations move to TextInput, Divider,
Switch and Checkbox; main.tsx uses Seven's Toaster; error-boundary uses
Button; loading-skeleton uses SkeletonLoader. The local input, separator,
skeleton and sonner components now have zero consumers.

Validated in the browser. Plan:
docs/plans/2026-06-16-001-migrate-frontend-seven-ds-plan.md
Delete the now-orphaned local UI primitives (input, separator, skeleton,
sonner) - all consumers moved to @etus/seven-react.

Add an ESLint guard-rail: raw button/input/select/textarea are banned in
src/client/routes and the sidebar, pointing to the Seven equivalents
(proven to trigger on a probe file).

Document the "UI = Seven" rule in CLAUDE.md (components, tokens, brand,
guard-rail, validate against seven.etus.io).
The user menu in the sidebar footer never opened: SidebarAvatar does not
forward props, so it could not act as a DropdownMenuTrigger. Use a
SidebarMenuButton (the canonical pattern) instead.

Restore the client test suite after the Seven migration: make the jsdom
ResizeObserver a real constructor and add Radix pointer-capture shims (the
suite errored without them), drop tests for the deleted local primitives,
and update the shell tests (sidebar, authenticated-layout, error-components,
dashboard, integrations) to the new structure. 230 tests pass, coverage 89%.
… components

These screens were already Seven-only but hand-rolled their layouts from Card.
Swap them for the design system's purpose-built components so derived products
compose from Seven rather than rebuilding it:

- dashboard: stat Cards -> DashboardCard (title/value/valueDescription/icon).
  KPICard was rejected: it hides all sub-text when no delta is given, and the
  template's placeholder metrics have no trend data to invent.
- login: centered Card -> split-screen matching Seven's curated /login (form in
  <main>, brand panel in <aside>, mint primary OAuth button). The landmarks also
  restore the <main> that root-layout asserts.
- workspaces: super-admin Card -> Callout variant="success"; empty-state Card ->
  Empty/EmptyMedia/EmptyTitle/EmptyDescription.

Validated each in the browser against the published showcase. 230 client tests
pass unchanged; coverage 89.7%/84.5%/94.3%.
…undle)

Production build measurement: route-splitting already works (/login chunk is
4.6 kB), but every route loads a shared 2.2 MB index chunk because @etus/ui
ships a monolithic 1.7 MB index.js that inlines all components + recharts.
sideEffects:false on the packages + a cleared vite cache produced a
byte-identical build, so consumer-side tree-shaking cannot help. The real fix
is upstream (Seven shipping per-component ESM). Experiments reverted.
The sidebar and login render the logo as a mint rounded-square plate with the
icon; the 404, invite, and landing pages used a bare icon, so the brand mark
looked different depending on the entry point. Use the same plate everywhere.
Includes the bhono-app template mirror.
Audit vs the published showcase: all 11 screens are faithful Seven. One cosmetic
drift was fixed (brand mark on 404/invite/landing). One upstream finding: the
sidebar active item renders mint-strong (the real SidebarMenuButton behavior)
instead of the Figma subtle gray, because Seven only exposes data-mode (which
gates the Figma styling) in dual mode. A data-mode fix was applied to the Seven
working tree; the active-bg cascade residual was handed to the Seven team. Full
writeup: seven/docs/audits/sidebar-single-mode-styling-bug.md.
Seven's app shells separate the header from the content via the content tuck
(rounded-tl border on the inset), not a header border-b. Drop the border-b so
the topbar floats like the Seven reference. Includes the bhono-app template mirror.
From the multi-reviewer pass on the Seven migration:
- test(sidebar): the theme-toggle test now asserts the persisted theme change
  instead of just rendering (was vacuous — Rule 6).
- chore(lint): widen the raw-control guard-rail from routes/** + sidebar.tsx to
  all of src/client/components/** (except icons.tsx) — closes the gap where a raw
  <button> in error-boundary.tsx would pass unguarded.
- refactor: drop the unused SidebarSkeleton (+ its tests) and the dead
  ?? 'dashboard' breadcrumb fallback (this layout never renders at /).
- test(toaster): extract AppToaster to its own module and cover that it forwards
  the resolved theme to Seven's Toaster.
- nits: the user-menu dropdown header shows the name (not a second copy of the
  email); narrow the error/pending layout assertions to the intent class.

typecheck + lint + 226 client tests green; coverage 89.7%/84.6%/94.3%.
@aabrius aabrius changed the base branch from master to develop June 18, 2026 06:08
@aabrius aabrius merged commit af7c004 into develop Jun 18, 2026
1 check failed
@aabrius aabrius deleted the feat/frontend-seven-ds branch June 18, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants