Skip to content

feat(ui): UI overhaul with shared uikit components and light/dark design tokens - #66

Merged
jayleaton merged 10 commits into
mainfrom
feat/ui-overhaul
Oct 22, 2026
Merged

jayleaton merged 10 commits into
mainfrom
feat/ui-overhaul

Conversation

@jayleaton

@jayleaton jayleaton commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Summary

Both apps get one design system: tokens with a light and a dark value each, a shared templ component set, and every page in both apps rebuilt on it. Re-theming is now a one-file change (packages/uikit/theme.css + make css).

packages/uikit/            (new; both UIs import it, depguard + probes keep it off apps/ and data/auth/secrets)
├── theme.css              every color = light-dark(<light>, <dark>), plus radius, shadows, fonts   ← edit to re-theme
├── base.css               tokens → Tailwind utilities (bg-surface, text-muted-foreground, border-input…), focus ring, reduced motion
├── shell.templ            Page (html, theme class, nonce), AppShell (skip link, header, nav, mobile menu), ThemeToggle, LocaleMenu, SignOutButton, AuthLayout
└── components.templ       Button/LinkButton/SubmitButton, Input/Textarea/Select/CheckboxField, Alert/Callout/Toast, Badge, StatusIcon,
                           Card/PageHeader/StatGrid/DetailList, TableWrap, ConfirmForm (<dialog>), Pagination, EmptyState, Skeletons
apps/web/ui, apps/admin/ui  view/ = layout glue only; every page rebuilt on uikit; old class constants deleted
  • Theme: light, dark, or the system setting (no cookie). The server renders <html class="light|dark"> from the theme cookie, so the first paint is right with no pre-paint script under the strict nonce CSP. The existing toggle is kept.
  • Branding: "Better Stack" / "Starter template" are gone. APP_NAME (default "My App") names the web app, ADMIN_APP_NAME the admin.
  • Accessibility: WCAG AA contrast in both themes, one visible focus ring for everything, an h1 on every page, a skip link, a keyboard-operable mobile menu (Esc closes it), and reduced motion respected.
  • Admin: Remove admin, ban and revoke-all ask in an accessible <dialog> instead of confirm(); without JS they still post directly. The Admins table no longer clips Remove at 1280px.
  • Guard: TestTemplatesUseThemeTokens (both apps) fails on raw palette classes (bg-blue-600…) or dark: variants outside icon swaps, so the one-file theming holds. Proven by planting bg-blue-500 dark:bg-red-900: it failed with 3 errors.
  • Datastar behaviour, patch-target ids, input names, test ids and en/ja completeness are unchanged (the i18n guard and the crawl, render and handler tests are green).

Evidence

Real servers (server + admin against a throwaway Postgres), seeded with 8 users, a ban, a role change and a pending invite. Headless Chromium took 96 before shots (origin/main @ fcc7578) and 100 after shots: every page in light + dark, desktop 1280 + mobile 390, en plus a ja sample, and the mobile menu open. Selected pairs:

Before After
Web sign-in, dark
Web dashboard, light
Web account, dark, mobile
Web mobile menu (no menu)
Admin users, light
Admin banned user, dark
Admin admins (Remove was clipped)
Admin audit, ja

axe-core 4.x (wcag2a/aa, wcag21a/aa, best-practice; 21 pages × light/dark, desktop, en; full tables: before · after):

rules violated serious moderate minor color-contrast nodes
Before 87 61 24 2 33 (all in dark mode)
After 0 0 0 0 0

Local checks before each push on the touched packages: gofmt -l (empty), go vet, golangci-lint run → 0 issues., check-test-boundaries.sh OK. go test ./apps/web/ui/... ./apps/admin/ui/...: ok. Coverage: web ui 74.0% (unchanged, floor 70), admin ui 88.5% (floor 80). Full make ci runs here in CI.

Merge Danger

Door: two-way. Only templates, CSS, docs and one new optional env var (APP_NAME) change; there are no migrations or data changes. A revert restores the old UI.

Blast Radius: UI

Every page of both browser UIs changes look. Anything scraping old class names or markup would break; the repo's own tests were updated without weakening behaviour checks. The JSON API, auth and the mobile app are untouched. Three web dashboard placeholders changed: a dead "Support" button and the redundant Home and "Back to dashboard" buttons were removed (the nav covers them). The theme uses light-dark() (Chrome 123+, Safari 17.5+, Firefox 120+).

🤖 Generated with Claude Code

jayleaton and others added 9 commits October 22, 2026 18:26
… components

theme.css holds every token as light-dark(<light>, <dark>), so re-theming is a
one-file change; base.css maps them to Tailwind utilities. Both apps' layouts
now render through uikit.Page and uikit.AppShell (skip link, responsive nav
with a mobile menu, theme toggle, language menu, sign out). depguard keeps
uikit from importing apps or data packages; the web app's name comes from
APP_NAME instead of the leftover "Better Stack" brand.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auth pages use the centered AuthLayout card (h1 per page), the dashboard and
Account page use cards, badges and a device list, and the welcome pages use
theme tokens only. The footer shows APP_NAME instead of a fixed brand. Old
view components and class constants are gone; patch target ids, input names
and Datastar behaviour are unchanged. New render/handler tests keep ui
coverage at 74.0%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove admin, ban and revoke-all ask in an accessible dialog (posting directly
without JS). The admins table no longer clips its actions, the sessions
table's action column has a label, empty lists show an empty state, and the
auth pages have an h1. Patch targets, input names and test ids are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TestTemplatesUseThemeTokens (both apps) scans the app and packages/uikit for
Tailwind palette utilities and dark: variants other than icon swaps, so a
theme stays a one-file change. TableWrap is relative so sr-only labels
inside a table cannot widen the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton
jayleaton marked this pull request as ready for review October 22, 2026 11:47
Importing apps/admin/internal/services from packages/uikit fails Go's internal
rule at typecheck, which aborted the whole probe run instead of being reported
by depguard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton
jayleaton merged commit 8b120a3 into main Oct 22, 2026
5 checks passed
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