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
187 lines
5.4 KiB
CSS
187 lines
5.4 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* Design tokens imported via @bytelyst/design-tokens/css/notelett in layout.tsx */
|
|
/* === END design tokens === */
|
|
|
|
:root {
|
|
--nl-on-accent: white;
|
|
--nl-overlay-scrim: color-mix(in srgb, var(--nl-bg-canvas) 64%, transparent);
|
|
--nl-overlay-scrim-strong: color-mix(in srgb, var(--nl-bg-canvas) 72%, transparent);
|
|
--nl-surface-sidebar: color-mix(in srgb, var(--nl-bg-canvas) 82%, transparent);
|
|
--nl-surface-card-translucent: color-mix(in srgb, var(--nl-surface-card) 86%, transparent);
|
|
--nl-surface-muted-translucent: color-mix(in srgb, var(--nl-surface-muted) 72%, transparent);
|
|
--nl-input-bg: color-mix(in srgb, var(--nl-bg-canvas) 88%, transparent);
|
|
--nl-accent-muted: color-mix(in srgb, var(--nl-accent-primary) 16%, transparent);
|
|
--nl-accent-muted-strong: color-mix(in srgb, var(--nl-accent-primary) 22%, transparent);
|
|
--nl-danger-muted: color-mix(in srgb, var(--nl-danger) 14%, transparent);
|
|
--nl-success-muted: color-mix(in srgb, var(--nl-status-success) 14%, transparent);
|
|
--nl-warning-muted: color-mix(in srgb, var(--nl-status-warning) 14%, transparent);
|
|
--nl-info-muted: color-mix(in srgb, var(--nl-accent-primary) 12%, transparent);
|
|
--nl-command-shadow: 0 24px 80px color-mix(in srgb, black 45%, transparent);
|
|
--bl-bg-canvas: var(--nl-bg-canvas);
|
|
--bl-bg-elevated: var(--nl-bg-elevated);
|
|
--bl-accent: var(--nl-accent-primary);
|
|
--bl-accent-foreground: var(--nl-on-accent);
|
|
--bl-accent-muted: var(--nl-accent-muted);
|
|
--bl-surface-card: var(--nl-surface-card);
|
|
--bl-surface-muted: var(--nl-surface-muted);
|
|
--bl-surface-sidebar: var(--nl-surface-sidebar);
|
|
--bl-text-primary: var(--nl-text-primary);
|
|
--bl-text-secondary: var(--nl-text-secondary);
|
|
--bl-text-tertiary: var(--nl-text-tertiary);
|
|
--bl-border: var(--nl-border-default);
|
|
--bl-overlay-scrim: var(--nl-overlay-scrim);
|
|
--bl-success: var(--nl-status-success);
|
|
--bl-success-muted: var(--nl-success-muted);
|
|
--bl-success-border: var(--nl-status-success);
|
|
--bl-warning: var(--nl-status-warning);
|
|
--bl-warning-muted: var(--nl-warning-muted);
|
|
--bl-warning-border: var(--nl-status-warning);
|
|
--bl-danger: var(--nl-danger);
|
|
--bl-danger-foreground: var(--nl-on-accent);
|
|
--bl-danger-muted: var(--nl-danger-muted);
|
|
--bl-danger-border: var(--nl-danger);
|
|
--bl-info: var(--nl-accent-primary);
|
|
--bl-info-muted: var(--nl-info-muted);
|
|
--bl-info-border: var(--nl-accent-primary);
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
background: var(--nl-bg-canvas);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
background: linear-gradient(180deg, var(--nl-bg-canvas) 0%, color-mix(in srgb, var(--nl-bg-canvas) 70%, black) 100%);
|
|
color: var(--nl-text-primary);
|
|
font-family: var(--nl-font-body);
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
textarea,
|
|
select {
|
|
font: inherit;
|
|
}
|
|
|
|
button {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.main-panel {
|
|
min-width: 0;
|
|
padding: var(--nl-space-8);
|
|
padding-left: calc(var(--bl-app-sidebar-width, 280px) + var(--nl-space-8));
|
|
}
|
|
|
|
/*
|
|
* Legacy global classes (.surface-card, .surface-muted, .badge,
|
|
* .input-shell) were removed as part of UI8. All callers now use
|
|
* @bytelyst/ui primitives via @/components/ui/Primitives. The
|
|
* scripts/ui-drift-ratchet.sh gate in CI prevents reintroduction.
|
|
*
|
|
* If you find a regression that needs one of these classes, fix the
|
|
* call site with a primitive rather than restoring the global rule.
|
|
*/
|
|
|
|
.page-grid {
|
|
display: grid;
|
|
gap: var(--nl-space-6);
|
|
}
|
|
|
|
.review-workflow-grid {
|
|
display: grid;
|
|
grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);
|
|
gap: var(--nl-space-4);
|
|
}
|
|
|
|
.review-workflow-grid > * {
|
|
min-width: 0;
|
|
}
|
|
|
|
@media (max-width: 980px) {
|
|
.main-panel {
|
|
padding: var(--nl-space-5);
|
|
}
|
|
|
|
.note-detail-grid,
|
|
.workspace-layout-grid,
|
|
.review-workflow-grid {
|
|
grid-template-columns: minmax(0, 1fr) !important;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 520px) {
|
|
.main-panel {
|
|
padding: var(--nl-space-4);
|
|
padding-top: var(--nl-space-8);
|
|
}
|
|
}
|
|
|
|
/* Focus-visible — keyboard accessibility */
|
|
*:focus-visible {
|
|
outline: 2px solid var(--nl-accent-primary);
|
|
outline-offset: 2px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
*:focus:not(:focus-visible) {
|
|
outline: none;
|
|
}
|
|
|
|
/* Screen-reader only utility */
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
padding: 0;
|
|
margin: -1px;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
white-space: nowrap;
|
|
border-width: 0;
|
|
}
|
|
|
|
/* Interactive hover/active states */
|
|
button, [role="button"], a {
|
|
transition: opacity 0.15s ease, background-color 0.15s ease;
|
|
}
|
|
button:hover:not(:disabled), [role="button"]:hover:not(:disabled) {
|
|
opacity: 0.85;
|
|
}
|
|
button:active:not(:disabled), [role="button"]:active:not(:disabled) {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* Skip-to-content link (keyboard accessibility) */
|
|
.skip-to-content {
|
|
position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; z-index: 9999;
|
|
}
|
|
.skip-to-content:focus {
|
|
position: fixed; top: 8px; left: 8px; width: auto; height: auto;
|
|
padding: 12px 24px; background: var(--nl-bg-elevated); color: var(--nl-accent-primary);
|
|
border: 2px solid var(--nl-accent-primary); border-radius: 8px; font-size: 14px; font-weight: 600; text-decoration: none;
|
|
}
|
|
|
|
/* Respect user motion preference */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*, *::before, *::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|