refactor: design token migration + CI guards (#13173)

# Design System Unification — one visual language, enforced end-to-end

Establishes **one source of truth for every visual decision**, wires the
whole app to it, and **locks it in with CI** so it can't drift back.

### Foundations
- **Design-token system ("one knob per decision").** Every colour, text
size, spacing, corner radius, and shadow is a named token defined in
exactly one place — change the token, every screen updates. Collapsed
hundreds of near-duplicate definitions and removed "fake" duplicates
that pretended to be different but weren't.
- **Shared component library (O2).** Buttons, tables, dialogs, drawers,
form fields, badges, etc. already know the correct colours, spacing, and
dark-mode behaviour; new screens compose them instead of hand-styling.
Added new primitives on this branch (`OContent`, `KpiCard`) and
standardized existing ones.
- **One page structure everywhere** (`PageLayout` / `AppPageHeader` /
`AppPage` / `OContent`) — titles, breadcrumbs, spacing, and content
edges line up identically across every screen.
- **One table implementation** — all tabular data uses the same table
(sorting, density, sticky headers, empty states).
- **Styling consolidated onto Tailwind; old systems removed** —
hand-written CSS driven to the practical minimum, the legacy **Quasar**
framework fully removed, surviving stylesheets required to justify
themselves.
- **Two-tier corner radius** — a tight **4px** for controls and a soft
**12px** for surfaces (dialogs, cards, the app shell), plus a
full-circle option — replacing five radius names that all secretly meant
the same thing.

### Detailed improvements across screens
**Page structure & alignment** — unified page shell rolled across ~40+
screens (IAM, Settings, Alerts, Dashboards, Functions, Pipelines, RUM,
Metrics, Synthetics, Billing, editors, lists); one header height; one
page-edge grid driving headers, tables, rails, and tabs; unified
side-rail widths; inset rail selection/hover pills; every tab strip on
the grid line; tightened title/subtitle spacing.

**Tables (cross-screen)** — action columns consistently centered (incl.
Organizations); unified row-number column with no "#" truncation;
non-standard tables restyled to match; loading skeletons made
geometrically identical to the loaded table; symmetric edge inset; IAM
first-column grid alignment fixed.

**Traces** — flame graph + span-detail correlation panel fixes;
correlated logs/metrics placeholder fit; sticky headers; removed a
double scrollbar + a 1px sliver; aligned table fonts/badges; polished
the service detail drawer; stream-selector + correlation alignment;
aligned loading spinners; floating overlay arrows for the
disabled-services strip.

**Logs** — field-list controls aligned (focus ring no longer clipped);
detail-drawer spacing + metrics-tab overflow fixed; Run Query toolbar
tidied; listing sizing + stray border fixed.

**Metrics** — search added to the "Type" facet rail and aligned with the
other rails; proper empty states for the explorer and facet rail.

**Dashboards** — removed inconsistent card shadows; tightened
borders/resizers/field-list spacing; aligned View Dashboard chrome to
the grid; chart colours via the token helper.

**Home** — redesigned the Usage tab; added reusable KPI stat cards so
summary strips stop drifting.

**Dark mode** — new theme-switch animation; fixed a 1px content shift on
toggle and a white flash on load; fixed AI-chat text and assorted
light/dark mismatches; completed the token-driven dark migration.

**Empty states, controls & small polish** — restored 3 broken
empty-state animations; adopted the standard empty state; added the
sliding indicator to the toggle control; fixed the Quality-page inset +
drawer badge; refreshed Search History / Schedulers; fixed the
alert-history drawer scrollbar + timeline hover; removed a stray
menu-bar border.

### Dead code & cruft removal
- **Deleted the entire legacy `utilities.css`** global stylesheet (well
over a thousand lines) — evacuated in waves, roughly half of it dead
once traced.
- **Removed Quasar entirely**, including dead `.q-*` styles and ~25
phantom `--q-*` variables that were silently breaking bits of the UI.
- Stripped hundreds of **dead CSS classes and tokens that compiled to
nothing** — e.g. 46 uses of a colour class with no backing token, a
no-op shadow class on 51 elements, dead layout/utility classes, a
leftover `card-container` block, a dead table-actions token, and a
mis-registered gradient that rendered nothing.
- Cut **unused keyframe animations (19 → 3)** and removed **unused
components** + a stray build artifact.
- Net effect: a much smaller, leaner styling surface — less to maintain,
fewer dead ends to copy.

## Safety & verification
Large but low-risk: this is overwhelmingly a **styling and structure**
change, not product behaviour or data. Guarded by the
design-token/consistency/type checks above plus the existing test suite,
and reviewed screen-by-screen.

---------

Co-authored-by: Kirtan Thakkar <kirtan@kiara.tech>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: ktx-mihir <mihir.patel@kiara.tech>
This commit is contained in:
ktx-vaidehi
2026-07-20 18:06:21 +00:00
committed by GitHub
co-authored by Kirtan Thakkar Claude Fable 5 ktx-mihir
parent 8aad20b0f3
commit f15e27e5fd
1341 changed files with 25080 additions and 73593 deletions
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env node
// Copyright 2026 OpenObserve Inc.
//
// Prove a CSS refactor is a no-op at the BUILT stylesheet level
// (O2_STYLE_MIGRATION_PLAN.md §7.2). Emits a stable, sorted, diffable summary of
// every selector in a built CSS bundle plus a hash of its declarations, so
// `diff before.txt after.txt` shows exactly what changed — and nothing else.
//
// Declarations are normalised (whitespace collapsed, declarations sorted) so a
// pure MOVE between files/layers hashes identically and produces no diff noise.
//
// Usage:
// npm run build-only
// node scripts/css-selector-diff.mjs dist/assets/*.css > /tmp/css-before.txt
// # ...on the migration branch...
// node scripts/css-selector-diff.mjs dist/assets/*.css > /tmp/css-after.txt
// diff /tmp/css-before.txt /tmp/css-after.txt
import { readFileSync } from "node:fs";
import { createHash } from "node:crypto";
const files = process.argv.slice(2).filter((a) => !a.startsWith("--"));
if (files.length === 0) {
console.error("Usage: node scripts/css-selector-diff.mjs <built.css>...");
process.exit(2);
}
function stripComments(css) {
return css.replace(/\/\*[\s\S]*?\*\//g, "");
}
/**
* Normalise a declaration body: sort declarations, collapse whitespace.
* Makes a moved rule hash identically to its original.
*/
function normaliseBody(body) {
return body
.split(";")
.map((d) => d.replace(/\s+/g, " ").trim())
.filter(Boolean)
.sort()
.join(";");
}
function hash(s) {
return createHash("sha1").update(s).digest("hex").slice(0, 10);
}
/**
* Recursively collect rules, tracking at-rule context (@media/@layer/@supports)
* so a rule that changes layer shows up as a changed context.
*/
function collect(css, context, out) {
let depth = 0;
let start = 0;
let braceStart = -1;
for (let i = 0; i < css.length; i++) {
const ch = css[i];
if (ch === "{") {
if (depth === 0) braceStart = i;
depth++;
} else if (ch === "}") {
depth--;
if (depth === 0) {
const prelude = css.slice(start, braceStart).replace(/\s+/g, " ").trim();
const body = css.slice(braceStart + 1, i);
if (prelude.startsWith("@")) {
const at = prelude.split(/\s|\(/)[0].toLowerCase();
if (at === "@media" || at === "@layer" || at === "@supports" || at === "@container") {
// nested context — recurse
collect(body, context ? `${context} > ${prelude}` : prelude, out);
} else {
// @keyframes / @font-face / @theme — hash body as a unit
out.push({ context, selector: prelude, hash: hash(body.replace(/\s+/g, " ").trim()) });
}
} else if (prelude) {
// Split grouped selectors so reordering a group isn't a false diff.
const norm = normaliseBody(body);
for (const sel of prelude.split(",").map((s) => s.replace(/\s+/g, " ").trim()).filter(Boolean)) {
out.push({ context, selector: sel, hash: hash(norm) });
}
}
start = i + 1;
}
} else if (ch === ";" && depth === 0) {
start = i + 1;
}
}
}
const rules = [];
for (const file of files) {
collect(stripComments(readFileSync(file, "utf8")), "", rules);
}
// Sort for a stable diff; dedupe identical (context, selector, hash) triples.
const seen = new Set();
const lines = [];
for (const r of rules) {
const key = `${r.context}|${r.selector}|${r.hash}`;
if (seen.has(key)) continue;
seen.add(key);
lines.push(`${r.context ? r.context + " :: " : ""}${r.selector} #${r.hash}`);
}
lines.sort();
console.log(lines.join("\n"));
console.error(`css-selector-diff: ${lines.length} unique rules from ${files.length} file(s).`);