Ctrl K
ComunidadGuías…

evidence/DashboardColor.md

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

Archivo original ↗Preguntar a KiBot
# DashboardColor — coherent palette, AA status states, accent ramp

- **Base:** `c60d7469` (`master`)
- **Worktree:** `w130-dashboardcolor`
- **Branch:** `feat/dashboard-color`
- **Scope:** `src/dashboard/index.html` custom properties + colour-only declarations.
  No DOM restructure, no layout metrics, no copy/i18n strings.
- **Theme mechanism:** the existing `data-theme="dark"` attribute + `dashboard_theme`
  localStorage toggle is unchanged (the dashboard already has the state plumbing, so
  no second `prefers-color-scheme` path was added).

## 1. Audit — existing tokens are reused, not forked

Command required by the brief:

```bash
grep -o 'var(--[a-z-]*)' src/dashboard/index.html | sort | uniq -c
```

The audit found the dashboard already has one token system: `--surface` /
`--surface-2` / `--surface-3`, `--accent` / `--accent-hover` / `--accent-soft` /
`--on-accent`, the four status families, `--border` / `--border-strong`, and the
`--text-*` ladder. The ten most-used tokens before this pass:

| uses | token | role |
|---:|---|---|
| 97 | `--text-muted` | secondary label |
| 89 | `--text-dim` | tertiary label |
| 81 | `--accent` | brand fill / link (pre-pass) |
| 75 | `--border` | card / divider hairline |
| 70 | `--error` | danger fill / status |
| 54 | `--text` | body copy |
| 42 | `--warn` | warning fill / status |
| 41 | `--success` | success fill / status |
| 25 | `--surface` | card surface |
| 21 | `--border-strong` | strong divider / hover |

The new tokens are extensions of the **same** families — `--accent-{text,active,focus}`,
`--{success,warn,error,info}-text`, `--border-control`, `--gradient-accent` — not a
parallel palette. Every existing selector and class still resolves.

## 2. Palette table (before → after)

Hex values are the shipped token values. `—` means the token did not exist.
`rgba(...)` is shown with its resolved value on `--surface` where useful.

| token | role | light before | light after | dark before | dark after |
|---|---|---|---|---|---|
| `--bg` | page canvas | `var(--paper)` #f4f4f5 | `var(--paper)` #f4f4f5 | `var(--paper)` #080808 | `var(--paper)` #080808 |
| `--bg-elev` | field / inset step | `var(--paper)` #f4f4f5 | `var(--surface-2)` #ececee | `var(--paper-2)` #141414 | `var(--surface-2)` #1d1d1d |
| `--surface` | card surface | #ffffff | #ffffff | #141414 | #141414 |
| `--surface-2` | control / nested | `var(--paper-2)` #ececee | `var(--paper-2)` #ececee | #1d1d1d | #1d1d1d |
| `--surface-3` | raised / chips | `var(--paper-3)` #e2e2e5 | `var(--paper-3)` #e2e2e5 | #262626 | #262626 |
| `--border` | decorative hairline | rgba(24,24,27,.10) | rgba(24,24,27,.10) | rgba(255,255,255,.10) | rgba(255,255,255,.10) |
| `--border-strong` | strong divider / hover | rgba(24,24,27,.18) | rgba(24,24,27,.18) | rgba(255,255,255,.18) | rgba(255,255,255,.18) |
| `--border-control` | interactive boundary | — | rgba(24,24,27,.58) → #79797b | — | rgba(255,255,255,.42) → #777777 |
| `--accent` | brand fill / dot | #d06b49 | #d06b49 | #d97757 | #d97757 |
| `--accent-hover` | hover fill | #c86543 | #c86543 | #c86543 | #c86543 |
| `--accent-active` | pressed border | — | #b85738 | — | #b85f42 |
| `--accent-focus` | focus ring | — | #a84a2e | — | #e89a7d |
| `--accent-text` | AA link / label | — | #a3462b | — | #df8a6b |
| `--accent-soft` | accent wash | rgba(208,107,73,.14) | rgba(208,107,73,.14) | rgba(217,119,87,.20) | rgba(217,119,87,.20) |
| `--on-accent` | label on solid fills | #18181b | #18181b | #141414 | #141414 |
| `--gradient-accent` | header / summary wash | — | `linear-gradient(135deg, var(--accent-soft), transparent 72%)` | — | same recipe |
| `--success` | success fill / dot | #788c5d | #788c5d | #788c5d | #788c5d |
| `--success-soft` | success wash | rgba(120,140,93,.16) | rgba(120,140,93,.16) | rgba(120,140,93,.22) | rgba(120,140,93,.22) |
| `--success-text` | AA success label | — | #4f6b36 | — | #96b277 |
| `--warn` | warning fill / dot | #b07d1c | #b07d1c | #d9a257 | #d9a257 |
| `--warn-soft` | warning wash | rgba(217,162,87,.16) | rgba(217,162,87,.16) | rgba(217,162,87,.22) | rgba(217,162,87,.22) |
| `--warn-text` | AA warning label | — | #8a5a00 | — | #d9a95f |
| `--error` | danger fill / dot | #cc6658 | #cc6658 | #cc6658 | #cc6658 |
| `--error-soft` | danger wash | rgba(198,93,78,.14) | rgba(198,93,78,.14) | rgba(198,93,78,.22) | rgba(198,93,78,.22) |
| `--error-text` | AA danger label | — | #ad392c | — | #da8578 |
| `--info` | info fill / dot | #5b8cc0 | #5b8cc0 | #6a9bcc | #6a9bcc |
| `--info-soft` | info wash | rgba(106,155,204,.16) | rgba(106,155,204,.16) | rgba(106,155,204,.22) | rgba(106,155,204,.22) |
| `--info-text` | AA info label | — | #2a6599 | — | #7cb2ee |
| `--text` | body copy | `var(--ink)` #18181b | `var(--ink)` #18181b | `var(--ink)` #ededed | `var(--ink)` #ededed |
| `--text-muted` | secondary copy | #52525b | #52525b | #a1a1aa | #a1a1aa |
| `--text-dim` | tertiary copy | #626269 | #626269 | #90909a | #90909a |

The base status hues and `--accent` stay the **fill/dot** colours: they clear the
3:1 graphical floor and carry `--on-accent` labels on solid fills. The new
`-text` twins are the AA label colours for `--surface` and the matching `-soft`
wash. That split is required because a single hex cannot simultaneously be a
light fill with dark `--on-accent` text and an AA dark label on a white surface.

## 3. Contrast proof (new pairs)

Machine checks:

```bash
node scripts/check-dashboard-contrast.mjs   # ✓ every token pair clears its WCAG floor
node --test test/dashboard-color-system.test.js
# ✔ 6 tests, 0 fail — surface ramp, accent states, text twins, control borders,
#   on-accent fills, and token-routing assertions
```

The extended gate computes the new pairs with WCAG 2.2 sRGB math. Floors:
**4.5:1 text**, **3:1 UI border / graphical object**, **1.08 surface ladder**.

| pair | light | dark |
|---|---:|---:|
| `--accent-text` on `--surface` | 6.05 | 7.01 |
| `--accent-text` on `--surface-2` | 5.13 | 6.41 |
| `--accent-text` on `--surface-3` | 4.68 | 5.75 |
| `--accent-text` on `--accent-soft` | 5.17 | 5.28 |
| `--success-text` on `--surface` / `--surface-2` / `--surface-3` | 6.02 / 5.10 / 4.66 | 7.84 / 7.17 / 6.44 |
| `--success-text` on `--success-soft` | 5.07 | 5.87 |
| `--warn-text` on `--surface` / `--surface-2` / `--surface-3` | 5.93 / 5.02 / 4.58 | 8.59 / 7.86 / 7.06 |
| `--warn-text` on `--warn-soft` | 5.26 | 5.71 |
| `--error-text` on `--surface` / `--surface-2` / `--surface-3` | 6.18 / 5.24 / 4.78 | 6.67 / 6.11 / 5.48 |
| `--error-text` on `--error-soft` | 5.20 | 5.20 |
| `--info-text` on `--surface` / `--surface-2` / `--surface-3` | 6.14 / 5.20 / 4.75 | 8.30 / 7.59 / 6.82 |
| `--info-text` on `--info-soft` | 5.29 | 5.90 |
| `--border-control` on `--surface` / `--surface-2` / `--surface-3` | 4.34 / 4.12 / 4.00 | 4.10 / 4.03 / 3.89 |
| `--accent-focus` on `--surface` | 5.70 | 8.20 |
| `--accent-active` on `--surface` | 4.71 | 4.17 |
| `--on-accent` on `--accent` / `--accent-hover` | 4.96 / 4.55 | 5.90 / 4.74 |
| `--on-accent` on `--success` / `--warn` / `--error` / `--info` | 4.82 / 4.89 / 4.73 / 5.03 | 5.01 / 8.12 / 4.92 / 6.29 |

### Before: why the `-text` twins were necessary

Using the old base hues as text on their own soft wash failed AA in both themes:

| pair | light before | dark before |
|---|---:|---:|
| `--accent` on `--accent-soft` | 3.04 | 4.45 |
| `--success` on `--success-soft` | 3.10 | 3.77 |
| `--warn` on `--warn-soft` | 3.21 | 5.42 |
| `--error` on `--error-soft` | 3.14 | 3.84 |
| `--info` on `--info-soft` | 3.04 | 4.46 |
| old `--border-strong` on `--surface` (UI boundary) | 1.47 | 1.72 |

## 4. Surface / elevation ramp

The ramp is the existing `--paper` → `--surface` → `--surface-2` → `--surface-3`
ladder; `--bg-elev` is now the field step of that same ramp in both themes
(previously it was `--paper` in light and `--paper-2` in dark, so fields did not
sit on a consistent rung). Shadows pair with it: `--shadow-sm` (card) →
`--shadow` (hover/popover) → `--shadow-lg` (modal). Adjacent-step ratios:

| step | light | dark |
|---|---:|---:|
| `--surface` vs `--paper` | 1.10 | 1.09 |
| `--surface-2` vs `--surface` | 1.18 | 1.09 |
| `--surface-3` vs `--surface-2` | 1.10 | 1.11 |

## 5. Accent gradient for header / summary cards

- `.page-header::before` paints `var(--gradient-accent)` on an absolutely
  positioned pseudo-element (`inset: -10px -14px`, `z-index: -1`). It adds no
  box to the flow, so the header's 24px rhythm and all wrapping are unchanged.
- `.metrics-grid .card` gets the same wash via `background-image:
  var(--gradient-accent)`, layered over the existing `background: var(--surface)`
  and existing status left rails. No border, padding, grid or content changes.
- The recipe is one token built from the existing `--accent-soft`, so light and
  dark share the same definition and the gradient can never drift from the accent.

## 6. Checks

All run in the worktree on this branch:

```text
node scripts/check-dashboard-contrast.mjs
✓ dashboard palette: every token pair clears its WCAG floor in both themes

node scripts/check-dashboard-copy.mjs
✓ dashboard copy: account/usage scope is free of machine identifiers

node scripts/check-dashboard-scale.mjs
✓ dashboard scale: account/usage surfaces use the spacing + type tokens

node src/dashboard/check-i18n.js
✓ All i18n checks passed!

node --test --test-concurrency=1 test/dashboard-*.test.js
ℹ tests 619
ℹ pass 619
ℹ fail 0
```

The serial run includes `test/dashboard-color-system.test.js` (6 assertions over
the new pairs) and the existing `dashboard-ui-p2.test.js` contrast gate.

## 7. `git diff --stat`

```text
 evidence/DashboardColor.md                | 208 ++++++++++++++++++
 scripts/check-dashboard-contrast.mjs      |  64 +++++-
 src/dashboard/index.html                  | 335 +++++++++++++++++-------------
 test/dashboard-color-system.test.js       | 122 +++++++++++
 test/dashboard-devin-cards-render.test.js |   2 +-
 test/dashboard-escape-callsites.test.js   |  57 ++---
 6 files changed, 613 insertions(+), 175 deletions(-)
```

## 8. Layout / behaviour guard

- `src/dashboard/index.html`: the diff changes custom-property values, `color`
  declarations, `border-color`/`background-image` declarations, and adds one
  absolute pseudo-element. No `padding`, `margin`, `width`, `height`, `gap`,
  grid, font-size or DOM text changes.
- No i18n file changed; `check-i18n` and `check-dashboard-copy` are green.
- The existing light/dark toggle (`data-theme="dark"` + `dashboard_theme`) is
  untouched; both themes are covered by every new contrast pair.
- `test/dashboard-escape-callsites.test.js` line pins moved by the insertion
  (the guard is deliberately line-sensitive); the reviewed entries were
  re-anchored to the same live sites, and its five tests stay green.