UI8 closes the migration cycle started by UI0. The four legacy global
classes (.surface-card, .surface-muted, .badge, .input-shell) are
removed from web/src/app/globals.css and the CI ratchet now enforces
zero new occurrences across three of the four drift categories.
Changes:
1. Audit regex precision (scripts/ui-drift-audit.sh, scripts/ui-drift-ratchet.sh)
The previous pattern 'className="[^"]*(badge|surface-card|surface-muted|input-shell)'
matched the literal token anywhere inside className, which caused 21
false positives against Tailwind arbitrary values like
'bg-[color:var(--nl-surface-muted)]' where the legacy name appears
inside a 'var(--nl-...)' reference.
New pattern requires the legacy class to be a whole class token —
either at the start of className, or preceded by a space, and
followed by a space or closing quote. Result: 21 false positives
eliminated; the ratchet now reports an honest 0 for the legacy
category.
2. globals.css cleanup (web/src/app/globals.css)
Removed .surface-card, .surface-muted, .badge, .input-shell rules.
Only truly global utilities remain (typography, focus-visible,
sr-only, skip-link, motion preferences, layout grids). A header
comment documents that re-introductions should be solved at the
call-site with a primitive, not by restoring the global rule.
3. Ratchet baseline (scripts/ui-drift-baseline.json)
Final counts after UI5–UI8 across the session:
raw interactive controls 14 (was 38 at start)
legacy global surface classes 0 (was 92 at start)
hardcoded color literals 0 (no change, was already 0)
direct @bytelyst/ui imports 0 (no change, was already 0)
The 14 remaining raw controls are intentional and tracked:
NoteEditor toolbar buttons (10)
ArtifactPanel hidden file input (1)
search/page radio inputs (2)
NoteVersionsPanel disclosure button (1)
4. CI gate (.github/workflows/ci.yml release-guards job)
Documented that the ratchet is the canonical gate post-UI8: because
legacy/colors/imports baselines are 0, any new occurrence in those
three categories now fails CI. The strict-audit script is kept as
a local diagnostic tool but not wired as a gate (would fail on the
14 intentional raw controls).
5. Roadmap (docs/UI_UX_PLATFORM_CORE_ROADMAP.md)
Marked UI5, UI6, UI7, UI8 all complete with per-phase commit hashes
and explicit deliverables.
Cumulative migration impact (from initial baseline):
raw interactive controls 38 → 14 (-24, -63%)
legacy global surface classes 92 → 0 (-92, -100%)
Verified:
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97
- bash scripts/ui-drift-ratchet.sh: all four categories at baseline
- bash scripts/ui-drift-audit.sh: only "Raw interactive controls"
category has matches (intentional, tracked above)
- Live Docker stack at http://localhost:3050 still serves 200,
backend health 200
Audit of the full E2E suite (43 specs) surfaced four issues that were
hiding behind 'all 96/96 web unit tests pass' but actually meant the
browser-level coverage was broken end-to-end. All four are fixed and
the suite now passes 43/43.
1. Port conflict silently testing wrong app. playwright.config.ts hard-
coded baseURL=http://localhost:3000 with reuseExistingServer:true on
non-CI hosts. When the dev host had ANY service on :3000 (Grafana,
chronomind, etc), Playwright happily ran the entire E2E suite
against the wrong app and reported the unrelated failures as
'real'. Now honors NOTELETT_WEB_PORT env (default 3000) so a
contributor can opt into any free port and Playwright drives both
baseURL and the dev-server PORT consistently.
2. Missing test dependency. web/e2e/accessibility.spec.ts imports
@axe-core/playwright but web/package.json never declared it.
The accessibility coverage was DOA — every CI run that included
this spec would module-not-found-error before a single check ran.
Added @axe-core/playwright to devDependencies.
3. Mock that never fires. smart-actions.spec.ts 'history API mock
returns items' used page.route() to mock /api/note-prompts/history
then bypassed the mock entirely with page.request.get() (which uses
Playwright's separate request context, not the browser context that
page.route intercepts). The request went to the dev server and got
404. Replaced with page.goto + page.evaluate(fetch(...)) so the
browser-side fetch hits the page.route mock as intended.
4. Missing visual-regression baselines. visual-regression.spec.ts had
no committed baseline screenshots for dashboard / workspaces /
search. First run on a clean host always reported 'snapshot doesn't
exist, writing actual'. Generated and committed darwin baselines.
Verified end-to-end (NOTELETT_WEB_PORT=3050 against this host's free
port):
43 passed (34.8s)
Total test-tier counts on main now:
backend unit + integration (memory) 380/380
backend cosmos emulator (live) 4/4
web vitest 96/96
mobile vitest 97/97
web playwright e2e 43/43
---
TOTAL 620/620
Restores green build after the May 12 Docker/UI regression.
Root cause: pnpm-workspace.yaml referenced a sibling path
(../learning_ai/learning_ai_common_plat/...) that did not exist on
dev/CI hosts. .pnpmfile.cjs fell back to ../learning_ai_common_plat for
some packages but missed others, so @bytelyst/ui was pulled from a
stale Gitea 0.1.0 tarball with zero exports (breaking web typecheck +
26 tests) and @bytelyst/monitoring was never linked into node_modules
(breaking backend typecheck + 2 test suites).
Changes:
- pnpm-workspace.yaml now references ../learning_ai_common_plat/packages/* directly
- .pnpmfile.cjs swaps DEFAULT/LEGACY common-plat roots so the canonical
path is the default and the older nested path is the fallback
- scripts/docker-prep.sh, scripts/local-smoke.sh, scripts/release-guard-audit.sh
follow the same canonical-first / legacy-fallback pattern
- .github/workflows/ci.yml symlinks directly to ../learning_ai_common_plat
- pnpm-lock.yaml regenerated with @bytelyst/ui@0.1.9 and
@bytelyst/monitoring@0.1.5 linked to the local common-plat checkout
Verified:
- pnpm run verify: backend 373/373, web 96/96, mobile 97/97
- pnpm run audit:release-guards: passes
- backend, web, mobile lint all exit 0 (advisory warnings retained)