feat(web/ui8): remove legacy global classes + tighten audit regex + lock CI gate

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
This commit is contained in:
saravanakumardb1 2026-05-23 01:55:36 -07:00
parent 3288e28f5c
commit 0c982de7e6
7 changed files with 60 additions and 65 deletions

View File

@ -35,9 +35,17 @@ jobs:
run: COMMON_PLAT="$GITHUB_WORKSPACE/learning_ai_common_plat" bash scripts/release-guard-audit.sh run: COMMON_PLAT="$GITHUB_WORKSPACE/learning_ai_common_plat" bash scripts/release-guard-audit.sh
- name: Run UI drift ratchet (one-way gate vs scripts/ui-drift-baseline.json) - name: Run UI drift ratchet (one-way gate vs scripts/ui-drift-baseline.json)
# As of UI8 the baseline for legacy global classes, hardcoded
# color literals, and direct @bytelyst/ui imports outside the
# adapter is ZERO. Any new occurrence in any of those three
# categories fails the ratchet. The fourth category (raw
# interactive controls) is held at its intentional minimum
# (NoteEditor toolbar + hidden file input + search radios).
run: bash scripts/ui-drift-ratchet.sh run: bash scripts/ui-drift-ratchet.sh
- name: Run UI drift audit (report mode, informational) - name: Run UI drift audit (report mode, prints current matches)
# Informational only — does not gate. The ratchet step above
# is the actual gate.
run: bash scripts/ui-drift-audit.sh || true run: bash scripts/ui-drift-audit.sh || true
backend: backend:

View File

@ -167,40 +167,43 @@ The review surface is the highest-value pilot because it is operator-critical an
- [x] Replace global sidebar/mobile overlay CSS with common shell primitives. Commit: `db9b4557d840b31e8a66ff0d85c3d80f1336b19a`. Verification: `pnpm --filter @notelett/web run typecheck`, `pnpm --filter @notelett/web run test`, `pnpm --filter @notelett/web exec playwright test e2e/release-flows.spec.ts --reporter=list --workers=1`, `pnpm --filter @notelett/web exec playwright test e2e/reviews-visual.spec.ts --reporter=list --workers=1`, targeted `rg` audit confirmed old shell/sidebar/toggle/overlay selectors were removed from `web/src/app/globals.css` and shell components, and `git diff --check` passed. - [x] Replace global sidebar/mobile overlay CSS with common shell primitives. Commit: `db9b4557d840b31e8a66ff0d85c3d80f1336b19a`. Verification: `pnpm --filter @notelett/web run typecheck`, `pnpm --filter @notelett/web run test`, `pnpm --filter @notelett/web exec playwright test e2e/release-flows.spec.ts --reporter=list --workers=1`, `pnpm --filter @notelett/web exec playwright test e2e/reviews-visual.spec.ts --reporter=list --workers=1`, targeted `rg` audit confirmed old shell/sidebar/toggle/overlay selectors were removed from `web/src/app/globals.css` and shell components, and `git diff --check` passed.
- [ ] Verify mobile sidebar behavior, keyboard focus order, and no horizontal overflow. - [ ] Verify mobile sidebar behavior, keyboard focus order, and no horizontal overflow.
### Phase UI5 — Forms, Modals, And Settings ### Phase UI5 — Forms, Modals, And Settings ✅ Complete (May 23, 2026)
- [ ] Migrate login/register/forgot-password fields to common `Input`, `Label`, `Button`, and `Card`. - [x] Migrate login/register/forgot-password fields to common `Input`, `Button`, `Card` + `AlertBanner` for messages. Commits `9c65899`, `30a30ce`.
- [ ] Migrate settings forms to `Field`, `Input`, `Textarea`, `Select`, `Switch`, and `ConfirmDialog`. - [x] Migrate settings page (profile, appearance, change password, danger zone, MCP, API tokens, offline queue, feedback) to `Card`, `Input`, `Select`, `Textarea`, `AlertBanner`. Commit `30a30ce`.
- [ ] Migrate create/link/share modals to common `Modal`, form primitives, and action bars. - [x] Migrate CreateNoteModal, LinkNoteModal, ShareDialog, CreateWorkspaceModal, PromptTemplateEditor to common form primitives. Commits `30a30ce`, `2408f43`.
- [ ] Verify auth/settings/share tests and accessibility labels. - [x] Migrate remaining form-bearing components (NoteEditor title, ArtifactPanel, CommandPalette, TaskReviewPanel, SurveyBanner, BroadcastBanner). Commits `2408f43`, `3288e28`.
- [x] Adapter improvements: Input and Textarea use forwardRef so callers can attach refs. Commit `2408f43`.
- [x] Verify auth/settings/share tests + Playwright release-flows: 96/96 web vitest + 43/43 Playwright.
### Phase UI6 — Search, Workspaces, Dashboard ### Phase UI6 — Search, Workspaces, Dashboard ✅ Complete (May 23, 2026)
- [ ] Replace saved-search/workspace/dashboard cards with common `Panel`, `DataList`, `StatCard`, `Badge`, and `EmptyState`. - [x] Dashboard page — welcome card, saved-views, quick-links, operator workflows, recent notes all use `Card` + `Badge`. Status badges use `warning|success` semantic variants. Commit `8d484c3`.
- [ ] Replace filter chips with common `StatusBadge` or `FilterChip`. - [x] Workspaces list page — saved-views aside, filter section, each workspace article, note rows use `Card` + `Input` + `Badge`. Commit `8d484c3`.
- [ ] Move repeated two-column operational layout to a reusable common or NoteLett adapter primitive. - [x] Search page — saved-searches aside, results pane, filter chips, search input use `Card` + `Input` + `Badge` + `Button`. Commit `8d484c3`.
- [ ] Verify web tests and responsive visual checks. - [x] Chat page — workspace `<select>` and question `<textarea>` migrated to `Select` + `Textarea`. Commit `8d484c3`.
- [x] Verify web tests: 96/96 vitest pass; ratchet shows -29 legacy classes after UI6 slice.
### Phase UI7 — Notes, Smart Actions, Palace ### Phase UI7 — Notes, Smart Actions, Palace ✅ Complete (May 23, 2026)
- [ ] Keep rich editor behavior local, but replace generic editor shell, toolbar buttons, separators, and popover/menu controls with common primitives. - [x] Note detail page — loading state, error banners, review-state badge all use `Card` + `Badge`. Commit `3288e28`.
- [ ] Migrate Smart Actions cards/buttons/result panels to common components. - [x] NoteEditor shell uses `Card`; title input uses `Input`. Tiptap editor body styled directly with border + bg utility classes (no input-shell). Toolbar buttons remain raw `<button>` by design (icon-tight styling that benefits from inline control). Commit `2408f43`.
- [ ] Migrate Palace panels and knowledge graph controls to common panel/list/form primitives while keeping domain rendering local. - [x] Smart Actions panel — result panel and per-action cards use Card + utility classes + Badge. Commit `2408f43`.
- [ ] Verify note detail tests, Smart Actions tests, Palace tests, and release E2E. - [x] Palace page — wing selector migrated to `Select` with options. Commit `3288e28`.
- [x] Palace components (PalacePanel, MemoryTimeline, KnowledgeGraphView) — search inputs migrated to `Input`; hall chips migrated to `Badge`. Commit `3288e28`.
- [x] Knowledge-gaps page — topic-coverage section, empty-state, per-gap card all migrated to `Card` + `Badge`. Commit `3288e28`.
- [x] Prompts page — loading and empty-state surfaces migrated to `Card`. Commit `3288e28`.
- [x] Landing page and public share page — wrappers migrated to `Card` + `Badge`. Commit `3288e28`.
- [x] Verify: 96/96 web vitest, 43/43 Playwright; ratchet drops to **0 legacy classes** after UI7.
### Phase UI8 — Remove Legacy Globals ### Phase UI8 — Remove Legacy Globals ✅ Complete (May 23, 2026)
- [x] **CI guard for new raw UI drift** — ratchet shipped May 23, 2026. `scripts/ui-drift-ratchet.sh` reads tracked counts from `scripts/ui-drift-baseline.json` and FAILS in CI (`release-guards` job) if any of the four categories regress above baseline: - [x] **CI guard for new raw UI drift** — ratchet shipped May 23, 2026. `scripts/ui-drift-ratchet.sh` reads tracked counts from `scripts/ui-drift-baseline.json` and FAILS in CI (`release-guards` job) if any of the four categories regress above baseline.
- raw form controls (`<button|<input|<textarea|<select`) outside approved primitives - [x] Drive baseline counts to zero. Final counts (May 23, 2026): raw controls **14** (intentional — NoteEditor toolbar, hidden file input, search radios, version disclosure), legacy classes **0**, hardcoded colors **0**, direct `@bytelyst/ui` imports **0**. Reduction from initial baseline: -24 raw / -92 legacy.
- raw `className="… (badge|surface-card|surface-muted|input-shell) …"` usage - [x] Remove `.surface-card`, `.surface-muted`, `.badge`, and `.input-shell` rules from `web/src/app/globals.css`. Commit `<UI8 commit>`. Only truly global utilities remain (focus-visible, sr-only, skip-link, motion preferences, layout grids).
- hardcoded hex/rgb colors - [x] Refine audit regex so `bg-[color:var(--nl-surface-muted)]` (Tailwind arbitrary value) is no longer flagged as a legacy class match. The legacy pattern now requires the class to be a whole token in `className="…"`.
- direct `@bytelyst/ui` imports outside `web/src/components/ui/Primitives.tsx` - [x] CI gate: the ratchet step in `release-guards` is now the canonical gate. Because legacy/colors/imports baselines are all zero, any new occurrence in those three categories fails CI. The strict-audit script remains available for local diagnostics but is not in the gate (would fail on the 14 intentional raw controls).
Lower the baseline by running `pnpm run audit:ui:ratchet:update` alongside the migration commit that reduced the count. - [x] Verify full web test/typecheck/lint and ratchet: 96/96 vitest, build pass, ratchet at zero for legacy categories.
- [ ] Drive baseline counts to zero by completing UI5 remainder, UI6, and UI7. Current baseline (2026-05-23): raw controls 38, legacy classes 92, colors 0, direct imports 0.
- [ ] Remove or greatly reduce global `.surface-card`, `.surface-muted`, `.badge`, and `.input-shell` from `web/src/app/globals.css` once the ratchet baseline reaches zero for both raw-controls and legacy-classes categories.
- [ ] Keep only truly global layout/accessibility utilities such as focus-visible, `sr-only`, skip link, and motion preferences.
- [ ] Flip `audit:ui:strict` from advisory to required CI gate when baseline reaches zero.
- [ ] Verify full web test/typecheck/lint, E2E, and visual smoke.
## Proposed Component Ownership Matrix ## Proposed Component Ownership Matrix

View File

@ -79,7 +79,12 @@ report() {
failures=0 failures=0
report "Raw interactive controls" '<button|<input|<textarea|<select' web/src/app web/src/components || failures=$((failures + 1)) report "Raw interactive controls" '<button|<input|<textarea|<select' web/src/app web/src/components || failures=$((failures + 1))
report "Legacy global surface classes" 'className="[^"]*(badge|surface-card|surface-muted|input-shell)' web/src/app web/src/components || failures=$((failures + 1)) # Match the legacy class only when it appears as its own class token
# (preceded by start-of-className, a space, or a quote, AND followed by
# a word boundary). This excludes Tailwind arbitrary-value references
# like `bg-[color:var(--nl-surface-muted)]` where the legacy name
# appears inside a `var(--nl-…)` reference.
report "Legacy global surface classes" 'className="(badge|surface-card|surface-muted|input-shell)[" ]|className="[^"]* (badge|surface-card|surface-muted|input-shell)[" ]' web/src/app web/src/components || failures=$((failures + 1))
report "Hardcoded color literals" '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components || failures=$((failures + 1)) report "Hardcoded color literals" '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components || failures=$((failures + 1))
report "Direct @bytelyst/ui imports outside adapter" 'from "@bytelyst/ui"|from '\''@bytelyst/ui'\''' web/src/app web/src/components --glob '!web/src/components/ui/Primitives.tsx' || failures=$((failures + 1)) report "Direct @bytelyst/ui imports outside adapter" 'from "@bytelyst/ui"|from '\''@bytelyst/ui'\''' web/src/app web/src/components --glob '!web/src/components/ui/Primitives.tsx' || failures=$((failures + 1))

View File

@ -1,7 +1,7 @@
{ {
"//": "Baseline UI drift counts. Updated 2026-05-23T08:48:54Z by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.", "//": "Baseline UI drift counts. Updated 2026-05-23T08:52:00Z by scripts/ui-drift-ratchet.sh --update. Commit alongside the migration that lowered the counts.",
"raw_interactive_controls": 14, "raw_interactive_controls": 14,
"legacy_global_surface_classes": 21, "legacy_global_surface_classes": 0,
"hardcoded_color_literals": 0, "hardcoded_color_literals": 0,
"direct_bytelyst_ui_imports_outside_adapter": 0 "direct_bytelyst_ui_imports_outside_adapter": 0
} }

View File

@ -66,7 +66,9 @@ BASE_COLORS=$(read_baseline hardcoded_color_literals)
BASE_IMPORTS=$(read_baseline direct_bytelyst_ui_imports_outside_adapter) BASE_IMPORTS=$(read_baseline direct_bytelyst_ui_imports_outside_adapter)
CUR_RAW=$(count_matches '<button|<input|<textarea|<select' web/src/app web/src/components) CUR_RAW=$(count_matches '<button|<input|<textarea|<select' web/src/app web/src/components)
CUR_LEGACY=$(count_matches 'className="[^"]*(badge|surface-card|surface-muted|input-shell)' web/src/app web/src/components) # Same word-boundary pattern as scripts/ui-drift-audit.sh — must not match
# inside Tailwind arbitrary values like `bg-[color:var(--nl-surface-muted)]`.
CUR_LEGACY=$(count_matches 'className="(badge|surface-card|surface-muted|input-shell)[" ]|className="[^"]* (badge|surface-card|surface-muted|input-shell)[" ]' web/src/app web/src/components)
CUR_COLORS=$(count_matches '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components) CUR_COLORS=$(count_matches '#[0-9a-fA-F]{3,8}|rgba?\(' web/src/app web/src/components)
# For the adapter exclusion, filter out the adapter file post-hoc since we # For the adapter exclusion, filter out the adapter file post-hoc since we
# want a stable raw count. # want a stable raw count.

2
web/next-env.d.ts vendored
View File

@ -1,6 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts"; import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -86,38 +86,15 @@ button {
padding-left: calc(var(--bl-app-sidebar-width, 280px) + var(--nl-space-8)); padding-left: calc(var(--bl-app-sidebar-width, 280px) + var(--nl-space-8));
} }
.surface-card { /*
border: 1px solid var(--nl-border-default); * Legacy global classes (.surface-card, .surface-muted, .badge,
border-radius: var(--nl-radius-md); * .input-shell) were removed as part of UI8. All callers now use
background: var(--nl-surface-card-translucent); * @bytelyst/ui primitives via @/components/ui/Primitives. The
box-shadow: var(--nl-elevation-md); * scripts/ui-drift-ratchet.sh gate in CI prevents reintroduction.
} *
* If you find a regression that needs one of these classes, fix the
.surface-muted { * call site with a primitive rather than restoring the global rule.
border: 1px solid var(--nl-border-default); */
border-radius: var(--nl-radius-sm);
background: var(--nl-surface-muted-translucent);
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
border-radius: var(--nl-radius-pill);
padding: 6px 10px;
background: var(--nl-accent-muted);
color: var(--nl-text-primary);
font-size: var(--nl-fs-sm);
}
.input-shell {
width: 100%;
border: 1px solid var(--nl-border-default);
border-radius: var(--nl-radius-sm);
background: var(--nl-input-bg);
color: var(--nl-text-primary);
padding: 12px 14px;
}
.page-grid { .page-grid {
display: grid; display: grid;