Ctrl K
ComunidadGuías…

DESIGN.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Linux Doctor — design system

The desktop app and the dashboard it exports are **one product with two
faces**: the same single-file frontend (`src-gui/`, built by
`scripts/build-gui.mjs` into `src-gui/index.html`) serves the Tauri window, the
`--web` dashboard and the `--html` report. Both faces share every token and
component below.

This document describes what the interface is made of and the rules that keep
it coherent. It is written for whoever touches `src-gui/` next.

---

## 1. The world

A **professional Linux desktop tool**. The craft bar is three applications the
user named when pinning the direction:

| Product | What it sets the bar for |
|---|---|
| **Linear** | density with air, one accent, keyboard-first, restraint |
| **JetBrains Toolbox** | the list-of-tools-with-actions model, a dark professional chrome |
| **GNOME Settings / libadwaita** | the sidebar + rows grammar — what "recognizable as a Linux app" means |

The commitment is the canon, **executed at full fidelity — without irony and
without smuggled quirk.** No specimen plates, no emission lines, no clever
reinterpretation of a familiar control. Two earlier candidate directions were
built, seen rendered, and rejected; they are on record in
`internal/redesign/` and are not part of this system.

### The two rules that decide most arguments

1. **One accent with exactly three jobs**: the primary action, the selected
   row, and focus. Nothing else is accent-coloured.
2. **Severity is the only other colour on screen.** Red, amber, blue-grey —
   and only as an ink dot, a bar, or a word. If a new element needs colour and
   is neither the accent nor a severity, it gets the ink ladder instead.

---

## 2. Tokens

Defined once in `src-gui/css/base.css`. Every other stylesheet uses the
variables; a hard-coded colour outside `base.css` is a bug (the only exceptions
are `print.css`, where paper is always white, and `color-mix()` darkening).

### Surfaces

A single neutral ladder, tinted from one hue — no second temperature appears
anywhere.

| Token | Role |
|---|---|
| `--bg` | the window |
| `--bg2` | chrome: headerbar, rail, status bar |
| `--card` | rows and panels |
| `--card2` | controls: inputs, segmented tracks, pills |

### Ink

| Token | Role |
|---|---|
| `--text` | primary text |
| `--muted` | secondary text, row codes |
| `--muted2` | labels, helper text, status bar |

### Severity inks

`--red` (high), `--yellow` (medium), `--blue` (info), plus `--green` (fixed /
positive) and `--purple` (the NEW badge). Used for ink dots, progress fills and
severity words.

### On-fill inks

`--on-accent` and `--on-ok` are the text colours that sit **on** an accent or
green fill (primary button, toast action, recommended-action button). They are
per-theme on purpose: white passes on the light theme's darker accent, dark ink
passes on the dark and terminal themes' brighter ones. Never write `#fff` on a
fill.

### Geometry

`--radius: 10px` (panels), `--radius-sm: 6px` (rows, controls), `--radius-lg:
12px` (the command palette). Two values plus one. Shadows are one token, used
only for popovers.

---

## 3. Type

One UI sans (`--sans`) and one mono (`--mono`), the mono reserved for things
that are *identifiers or counts*: check codes, commands, freshness, versions,
PIDs, durations. Numbers are `font-variant-numeric: tabular-nums` so columns
line up. Identifiers shown in a view (kernel, hostname, version, profile,
desktop) are mono **and copyable in place** — the value is what you paste into
a bug report.

Scale, in use: `10` labels · `11` meta · `12.5` body/rows · `13.5` app title ·
`32` the health score. Nothing larger ships in the interface; the size
hierarchy is carried by weight and colour, not by display type.

---

## 4. Components

**Headerbar** (48px, full-bleed). App mark, then the window identity as two
lines — title over a mono `distro · kernel · cores · uptime · version`. Actions
on the right, one of them primary. A hairline highlight in `inset` gives the
chrome depth without a shadow.

**Rail** (236px, flush to the window edge, `≥1100px`). Top block is
**Health**: the score, a progress hairline, the headline, one counts line and
the freshness footer. Below it, the sections — Views, Why this score, Severity,
Categories — separated by hairlines, with the active row carrying a 16% accent
wash. Below 1100px the rail collapses to a segmented view strip and the Health
block stays at the top of the flow.

**Toolbar** (sticky under the headerbar). A segmented group-by control, severity
pill filters, a search field, then icon+label buttons. Toolbar icons are drawn
with **CSS masks** from one 13px single-stroke set (`--ic-rows`, `--ic-checks`,
`--ic-x`, `--ic-expand`, `--ic-pulse`), so a JS label change ("Expand all" →
"Collapse all") keeps the icon and the ink follows `currentColor`.

**Rows** are the finding unit — never a card. A row is: severity dot with a
soft halo · title · quiet mono code / NEW badge · chevron. Hover is a surface
change; opening reveals detail, evidence and the recommended next step in
place. Severity is carried by the dot, not a coloured left border.

**Group headings** are sentence case section labels with a hairline underline
and a count — not boxes.

**View header and section rule.** Every view that is not Overview opens the
same way: a `view-head` — a title and one line of purpose. Sections inside a
view use the report's own rule: a label, a hairline underneath, a count on
the right. Never a stack of competing cards.

**The four dedicated surfaces.** History, Checks, System and Schedule each
carry one idea in the first three seconds:

- **History** — *has the machine held or slipped?* The score, its direction
  chip and its sparkline are **one instrument**: the trace sits directly under
  the score (the same shape-from-data language as the rail's vitals pulse, not
  a hairline floating at the bottom of a column) and takes the column's full
  width. A flat series is drawn as **one mark per run**, pinned at the score's
  own height — twelve readings read as twelve readings, not as a stray rule,
  and the trace carries no second date axis because the run window is printed
  under the bar chart beside it. Each severity bar is one run; every bar has a
  full-height transparent hit target, so a six-pixel bar and a tall one are
  equally pointable, and the run's date leads its tooltip. The run ledger is a
  table with a header row (`when · score · Δ · findings`), mono timestamps and
  tabular numbers.
- **Checks** — *what did it look at, and what is not clean?* The whole
  registry as a table: a count per category, a sticky `check · result` column
  rule on desktop (it parks under the headerbar while the registry scrolls),
  a **fixed result vocabulary** (`clean`, `observation`, `warning`, `needs
  attention`, `not applicable`, `skipped`, `failed to run`) carrying the
  severity ink, and a *Problems only* filter that drops the clean rows. A row
  with findings still deep-links to its card. The column rule is built once,
  by the view — never by the shared body builder, or a second copy appears
  wherever else that body is rendered.
- **System** — *what is this machine?* One list of sections under section
  rules, grouped by how they are read — *Read once* (identity) vs *Read when
  something's wrong* (live state) — never four separate cards. Identifiers
  (version, kernel, hostname, profile, desktop) are mono and copyable in place.
- **Schedule** — *will it check itself tomorrow?* An instrument row answers
  **last run · next run · timer state**, with the two times in mono: a reading,
  not a sentence. "Nothing to do" is the instrument's **own footer band**, the
  outcome of the three readings above it, rather than a sentence floating in
  the empty half of the view. The cadence is the caption under the row. The
  commands are printed as recipes — the same accent-ruled mono slip as
  *Recommended next step* — so the command is the information, with a copy
  button beside it rather than a button that hides it; the two command sets sit
  side by side on desktop so the width is used. Below 560px the three readings
  become a label/value spec sheet and the outcome band closes it.


**Detail pane** (`≥1200px`): the selected row's body pins to the right; below
1200 the row expands inline. Same content, two presentations.

**Status bar**: current view, freshness, the read-only promise, the keyboard
map. It is persistent chrome only from 1100px up; below that it is content,
sitting in the flow with its own gutter rather than flush against the last row
of a view.

**Command palette** (`Ctrl`/`Cmd`+`K`): views, actions and the findings already
in memory, grouped and filtered as you type; ↑↓ move, ↵ runs, `Esc` closes.

**Skeletons** on load show the *shape* of the incoming content (bars in the
rail, rows in the list) rather than a spinner. **Empty states** always pair a
short sentence with a way out ("Nothing matches “x” · Clear filters").

---

## 5. Themes

Dark is the default. `light` and `terminal` re-derive the same tokens — **no
component CSS is theme-specific**. The terminal theme takes over `--sans` with
mono, sharpens the radii and flattens the shadows.

---

## 6. Accessibility

- **Contrast is verified, not assumed.** `scripts/check-contrast.mjs` computes the
  WCAG ratio of every ink/surface pair in all three themes; the current matrix
  has **0 failures** (body text ≥ 4.5:1, labels and non-text ≥ 3:1, on-fill ink
  ≥ 4.5:1). Re-run it after any palette change.
- **Focus** is `:focus-visible` only: a 2px accent outline, offset. Rows use
  `outline-offset: -2px` so it stays inside the list.
- **Motion** is short and directional (view switch, score count-up, hover). All
  of it is disabled under `prefers-reduced-motion`.
- **Keyboard**: `/` search · `Ctrl K` palette · `↑↓` navigate rows · `Enter`
  open · `1`–`5` views · `Esc` close/clear · `?` help.

---

## 7. Do / Don't

| Do | Don't |
|---|---|
| Use the token ladders | Hard-code a hex outside `base.css` |
| Carry severity in a dot/ink | Give a card a red left border |
| Let one accent do three jobs | Introduce a second accent for "variety" |
| Use `text-transform` for labels only | Shout a sentence in uppercase |
| Pair an empty state with an action | Leave a dead end |
| Keep toolbars icon **and** label | Ship icon-only buttons without a tooltip/aria-label |

---

## 8. Provenance

- Source: `src-gui/css/*.css`, `src-gui/js/*.js`; the markup lives in
  `scripts/build-gui.mjs`. Build with `npm run build:gui`.
- Screenshots shipped in `docs/screenshots/` are regenerated from the real
  dashboard (`node scripts/screenshot.mjs`) — never hand-edited.
- The direction, the rejected candidates and the review page live under
  `internal/redesign/` (local only, not shipped).
- Development is AI-assisted; see `README.md`.