mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 08:09:27 +00:00
## What
Deletes `web/scripts/design-debt-baseline.json` — **not by relaxing the
rules, by fixing the debt**. Every design-token category is now
zero-tolerance, SCSS is gone from the app, and 144 hand-written shadow
values collapse into one scale.
## The guard was under-reporting (three separate holes)
1. **Comment contamination.** `check-design-consistency.mjs` fed comment
text into its exemption checks, so a `keep()` comment that merely
*named* `:deep(` / `::after` / `@keyframes` silenced the rule after it —
and a `;` or `{` inside a comment corrupted the offsets, so stripping
comments afterwards couldn't help. Comments are now blanked
(offset-preserving) before the scan. **Uncovered 9 occurrences in 6
files.** Real debt was 110, not 101.
2. **`arbShadow` only scanned the template.** `box-shadow:` in a
`<style>` block and `boxShadow:` in a JS/`:style` object were invisible
— 40 literals sat there while the guard reported zero. It now scans all
three syntaxes across `.vue` and `.ts`.
3. **`rawVarInTemplate` missed the fallback form.**
`border-(--color-x,rgba(…))` and `bg-[var(--color-x,#fff)]` slipped
through. The token is always defined, so the fallback can never fire —
it only hid the read.
## stylelint had never run on 14 files
`postcss-scss` isn't installed, so `postcss-html` **silently skipped**
every `<style lang="scss">` block. The CI step named *"Lint styles
(stylelint — bans legacy --o2-* tokens)"* was a no-op for those files —
including `color-no-hex` and the `.body--dark` selector ban. All 14 are
plain CSS now and `vue/block-lang` errors on `scss`/`sass`/`less`, so it
cannot silently recur.
## What moved
| | before | after |
|---|---|---|
| `rawVarInComponent` (in `<style>`) | 110 | **0** |
| template `x-[var(--color-*)]`, utility exists | 225 | **0** |
| template reads, no utility | 27 | **0** |
| `style` / `:style` colour literals | 29 | **0** |
| `color-mix(token N%, transparent)` | 160 | **0** |
| hand-written shadows | 233 (144 distinct) | **0** |
| SFCs using SCSS | 14 | **0** |
Guard categories: 22 → 24. `rawVarInTemplate` and `arbShadow` are new;
`arbPx` was deleted because main's `local/no-hardcoded-px` ESLint rule
(#13541) now owns px across every file type.
## Shadows are a system now
Stripped of colour, the 144 values were the same handful of geometries
rewritten in px vs rem, with and without an explicit `0` spread.
```
--shadow-xs / sm / md / lg elevation
--shadow-sticky-left / -right directional (sticky columns)
--shadow-rail inset status rail
--shadow-glow-sm / -md / -lg coloured, 3 intensities
--shadow-scroll-top / -bottom / -both scroll affordance
--shadow-ring-hairline 1px ring where a utility can't reach
--shadow-ai-focus AI composer focus glow (flips in dark)
```
Colour is a **separate axis**. Tailwind rewrites a `--shadow-*` token as
`<geometry> var(--tw-shadow-color, <default>)` — verified by compiling a
probe, and it holds for multi-layer tokens — so a template writes
`shadow-md shadow-ai-accent/35`. A plain stylesheet can't add a utility,
so the role and glow tokens read `--glow-color`; that keeps the pipeline
canvas colour-coding nodes by type from CSS and lets the status rail be
painted per row from JS.
Also reclassified: `0 0 0 2px <colour>` is a **ring**, not a shadow, and
`0 1px 0 0 <colour>` is a **border**.
## color-mix, analysed by intent
All 331 occurrences were classified rather than treated as one problem:
- **160** mixed a token with `transparent` — only an alpha change, so
it's Tailwind's `/N` modifier written the long way (verified: `/N`
compiles to `color-mix(in oklab, <token> N%, transparent)`, and mixing
with `transparent` is premultiplied, so the interpolation space doesn't
matter)
- **81** blended two colours — a derived colour, so it's a token now
(~50 minted)
- **75** were shadow/ring geometry, handled by the scale
- **8 gradients + 1 `calc()`-driven percentage** genuinely have no
utility form and remain; the guard exempts gradient/mask/filter
contexts, matching what the style-block counter already exempts
## Merge with main
Main was running the same migration in parallel (**#13541 "px to rem"**
removed all SCSS, added `no-hardcoded-px`, and built its own shadow
scale), so the merge collided in 68 files / 155 hunks. Resolved by
taking main's source files and re-applying this branch on top, with the
guards driving convergence (429 → 0) rather than hand-resolving hunks.
On shadows, **main's structure wins** — geometry in `semantic.css`
`@theme`, colours as `--shadow-*-c*` sub-tokens, wrapped in its px
disable; its `--shadow-sm/md/lg`, `--shadow-scroll-*` and
`--shadow-glow` keep their definitions. This branch adds what main
didn't have: `xs`, the directional/inset roles, and the
elevation-matched coloured glows.
## New tokens
No new *colours* — each names a value a component already hardcoded,
because a registered palette utility (`text-orange-500`) is banned by
`rawPalette`, so "just register the ramp" was never available.
`--color-splitter-hover`, `--color-status-flaky` (+`-text`),
`--color-chat-bubble-user-border`, `--color-thread-avatar-bg`,
`--color-thread-accent-strong`, `--color-menu-badge-ring`,
`--color-progress-track-fill`, `--color-ingest-accent-*` (5), ~50
derived blend tokens, plus `--color-border-strong` /
`--color-border-subtle` / `--color-warning-300` registered for utility
use.
## Visual deltas to review
Everything else is value-preserving. These are not:
- **4 hover accents** move from a fixed `primary-600` to `accent` — 600
→ 400 **in dark only** (2 chevrons, an empty-state icon fill, one link)
- **sticky table header** in `TraceDetailsSidebar` was a fixed light
grey that never flipped; now `border-default`, so it themes
- **shadow canonicalization** snaps near-identical geometries together
and moves ~40 hardcoded `rgba(0,0,0,x)` onto the scale's token colour
Highest-surface single file: `AlertSettingsHelpDrawer.vue` — a whole
style block deleted with ~44 class names moved into template utilities.
## Verification
`lint:design` · `lint:tokens` · `lint:token-purity` · `lint:styles` ·
`format:check` · `lint:ci` · `vue-tsc` — all clean.
**94 spec files covering every file this branch touches: 4395 passing, 0
failing.**
Not run: the full 1236-file unit suite, Playwright/E2E, and any browser
visual check.
---------
Co-authored-by: ktx-vaidehi <vaidehi.akhani@kiara.tech>
Co-authored-by: Abhay Padamani <141122205+ktx-abhay@users.noreply.github.com>
Co-authored-by: ktx-abhay <abhay.padamani@kiara.tech>
Co-authored-by: ktx-vaidehi <134508096+ktx-vaidehi@users.noreply.github.com>
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>
205 lines
6.6 KiB
CSS
205 lines
6.6 KiB
CSS
@import "tailwindcss";
|
|
@import "tw-animate-css";
|
|
/* Clear Tailwind's built-in colour palette BEFORE our tokens load, so only
|
|
registered design tokens generate colour utilities (see the file header). */
|
|
@import "./palette-reset.css";
|
|
/* Design tokens — these 4 CSS files are the sole source. */
|
|
@import "../lib/styles/tokens/base.css";
|
|
@import "../lib/styles/tokens/semantic.css";
|
|
@import "../lib/styles/tokens/component.css";
|
|
/* Element reset/typography layer. Imported here so its cascade order is kept. */
|
|
@import "./base-elements.css";
|
|
@import "../lib/styles/tokens/dark.css";
|
|
/* App-wide @keyframes referenced from template `[animation:…]` arbitrary
|
|
utilities — must be global, as Vue's scoped rewriter can't follow them. */
|
|
@import "./keyframes.css";
|
|
/* App-level `@utility` classes for treatments a template utility can't express
|
|
(pseudo-elements, non-utility CSS properties) but that are not one
|
|
component's private look. See the file header for when a rule belongs here. */
|
|
@import "./utilities.css";
|
|
|
|
@plugin "@tailwindcss/typography";
|
|
|
|
/* Bind `dark:` to the app's `.dark` class (set by utils/theme.ts), not the OS
|
|
`prefers-color-scheme` — Tailwind v4's default — so it follows the app theme. */
|
|
@custom-variant dark (&:where(.dark, .dark *));
|
|
|
|
@source "../**/*.{vue,js,ts}";
|
|
|
|
/*
|
|
* Light↔dark mode switch — soft curtain sweep (View Transitions API, started
|
|
* by switchThemeMode in utils/theme.ts). The outgoing theme holds fully opaque
|
|
* underneath while the new theme sweeps top→bottom behind a tall feathered
|
|
* mask edge (~half a viewport of blend), so the change reads as a soft light
|
|
* pass rather than a hard wipe or a flash.
|
|
* Mask math: the mask box is 250% of the viewport tall — solid for the first
|
|
* 40%, feathering to transparent by 60%. Sliding mask-position 100%→0% walks
|
|
* that feathered band across the full viewport height.
|
|
* REVERT: delete this block (browsers fall back to their default cross-fade)
|
|
* and reduce switchThemeMode to `applyChanges()` for a fully instant switch.
|
|
*/
|
|
::view-transition-old(root) {
|
|
animation: none;
|
|
}
|
|
::view-transition-new(root) {
|
|
animation: o2-theme-sweep 0.45s ease-in-out both;
|
|
mask-image: linear-gradient(to bottom, black 0%, black 40%, transparent 60%);
|
|
mask-size: 100% 250%;
|
|
mask-repeat: no-repeat;
|
|
}
|
|
@keyframes o2-theme-sweep {
|
|
from {
|
|
mask-position: 0% 100%;
|
|
}
|
|
to {
|
|
mask-position: 0% 0%;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* While a theme switch is in flight, suppress per-element color transitions
|
|
* (inputs, buttons, table action icons carry their own `transition-colors`)
|
|
* so nothing animates a beat behind the page cross-fade. The class is added
|
|
* and removed by switchThemeMode in utils/theme.ts.
|
|
*/
|
|
html.theme-switching *,
|
|
html.theme-switching *::before,
|
|
html.theme-switching *::after {
|
|
transition: none !important;
|
|
}
|
|
|
|
/*
|
|
* Navbar theme-toggle icon swap (sun ↔ moon): rotate + scale out-in. Driven by
|
|
* the <Transition name="theme-icon"> wrapper in ThemeSwitcher.vue. Uses CSS
|
|
* animations rather than transitions so the `theme-switching` freeze above
|
|
* doesn't suppress it — it plays live inside the theme sweep.
|
|
*/
|
|
.theme-icon-enter-active {
|
|
animation: theme-icon-in 0.3s ease-out;
|
|
}
|
|
.theme-icon-leave-active {
|
|
animation: theme-icon-out 0.15s ease-in forwards;
|
|
}
|
|
@keyframes theme-icon-in {
|
|
from {
|
|
transform: rotate(-90deg) scale(0.6);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: rotate(0deg) scale(1);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@keyframes theme-icon-out {
|
|
to {
|
|
transform: rotate(90deg) scale(0.6);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When reka-ui opens a modal dialog it sets `body { pointer-events: none }`
|
|
* and re-enables events only on the DialogContent DismissableLayer node.
|
|
* Any portaled floating element (dropdown, select, combobox, date-picker …)
|
|
* that is teleported to <body> as a sibling would inherit `pointer-events: none`,
|
|
* making items un-clickable and showing a disabled cursor.
|
|
* Force pointer-events back to auto for all known portal wrappers so they
|
|
* remain fully interactive while a modal is open.
|
|
*/
|
|
[data-reka-popper-content-wrapper] {
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
/*
|
|
* Query editor placeholder — the ONE definition for every page that overlays
|
|
* a placeholder on the Monaco query editor (logs, traces, RUM sessions,
|
|
* RUM error tracking, alerts). Mirrors Monaco's rendered text (monospace,
|
|
* same line height) so the placeholder reads as the future typed query.
|
|
* The placeholder text token is theme-paired, so no dark override is needed.
|
|
*/
|
|
.query-editor-placeholder-overlay .query-editor-placeholder-typewriter {
|
|
font-family: var(--font-mono);
|
|
font-size: var(--text-sm);
|
|
line-height: 1.3125rem;
|
|
color: var(--color-text-placeholder);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
@layer base {
|
|
/* Tailwind's v3-compat default border colour, token-backed so it follows the
|
|
theme (a raw hex would paint light-grey borders in dark mode on any element
|
|
that sets a border-width without a colour). */
|
|
*,
|
|
:before,
|
|
:after {
|
|
border-color: var(--color-border-default);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reduced-motion: users who ask for less motion get a plain, near-instant
|
|
* fade instead of the slide/zoom enter-exit animations (tw-animate-css
|
|
* `animate-in` / `animate-out`). Keeps overlays usable without the movement.
|
|
*/
|
|
@media (prefers-reduced-motion: reduce) {
|
|
[data-reka-popper-content-wrapper] *,
|
|
[data-o2-dialog],
|
|
[data-o2-drawer],
|
|
[data-test="o-dialog-overlay"],
|
|
[data-test="o-drawer-overlay"] {
|
|
animation-duration: 0.01ms !important;
|
|
animation-timing-function: linear !important;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clip-path "reveal" for popups (dropdown / select / combobox).
|
|
* The menu renders at full size and is progressively unveiled with a
|
|
* `clip-path` wipe — content never scales or squishes, it just gets
|
|
* revealed from the trigger edge. `-down-*` wipes top→bottom (bottom-placed
|
|
* menus), `-up-*` wipes bottom→top (top-placed menus, via `data-[side=top]`).
|
|
* `round 0.5rem` keeps the corners rounded throughout the wipe.
|
|
*/
|
|
@keyframes o2-reveal-down-in {
|
|
from {
|
|
opacity: 0;
|
|
clip-path: inset(0 0 100% 0 round 0.5rem);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
clip-path: inset(0 0 0 0 round 0.5rem);
|
|
}
|
|
}
|
|
@keyframes o2-reveal-down-out {
|
|
from {
|
|
opacity: 1;
|
|
clip-path: inset(0 0 0 0 round 0.5rem);
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
clip-path: inset(0 0 100% 0 round 0.5rem);
|
|
}
|
|
}
|
|
@keyframes o2-reveal-up-in {
|
|
from {
|
|
opacity: 0;
|
|
clip-path: inset(100% 0 0 0 round 0.5rem);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
clip-path: inset(0 0 0 0 round 0.5rem);
|
|
}
|
|
}
|
|
@keyframes o2-reveal-up-out {
|
|
from {
|
|
opacity: 1;
|
|
clip-path: inset(0 0 0 0 round 0.5rem);
|
|
}
|
|
to {
|
|
opacity: 0;
|
|
clip-path: inset(100% 0 0 0 round 0.5rem);
|
|
}
|
|
}
|