Files
crewli/dev-docs/CREWLI-DESIGN-TOKENS.md
bert.hausmans 8330e93fe5 docs(design): add CREWLI-DESIGN-TOKENS.md token inventory (Plan 2.5 Track B)
Per RFC-WS-PRIMEVUE-PLAN-2-5 Track B (§6). Inventories and classifies
the design tokens live in the codebase (Brand-essential / Bespoke /
Generic per RFC §6.2) and records the Typography decision register
(AD-2.5-T1) end-to-end — including the historical Public Sans
removal across both the CSS path (P2, commit 41af1801) and the
webfontloader JS path (P2-followup, commit 641ca513).

Inventory covers:
- Tailwind v4 @theme + :root (font tokens + dark variant selector)
- PrimeVue Aura preset (full 11-step Crewli teal primary palette +
  light/dark colorScheme bindings; everything else inherits Aura)
- PrimeVue runtime config (darkModeSelector='.dark', cssLayer=false,
  empty pt defaults scaffold)
- Iconify (Tabler set, dash-naming convention)
- useShellUiStore runtime writers (.dark class, data-density)
- Workspace gradient palette (8 pairs, deterministic per org id)
- Brand-square recipe (32px / rounded-lg / px-4 / centring equation)
- Density axis (comfortable | compact, axis present but no
  component-level reaction yet — backlog DENSITY-AWARE-SPACING)

Drift items flagged for Plan 4 (no fix in P7 — read-only audit):
- Workspace gradient palette uses Tailwind palette anchors, not
  derivations of Crewli's #0D9394
- User-avatar gradient hardcodes #f472b6 (Tailwind pink) + a
  fallback #0d9488 that's NOT Crewli's #0D9394
- --topbar-h referenced with fallback only, never declared in :root
- 'density' axis attribute set but no component spacing reacts to it

Remaining token decisions (surface tones, focus-ring, radius scale,
font-size scale, spacing rhythm, density-aware spacing, shadow scale,
secondary palette) explicitly deferred to Plan 4 per RFC §6.4.

Read-only audit: zero code files touched (verified via git status).
Foundation document for Plan 4's template-layer token work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 02:19:22 +02:00

281 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Crewli Design Tokens — Audit & Decision Register
| Field | Value |
| ---------------------- | ------------------------------------------- |
| Source design system | crewli-starter / PrimeVue Aura |
| Aura preset version | `@primeuix/themes` 4.5.x |
| Tailwind version | v4 (CSS-first `@theme`, no `tailwind.config.ts`) |
| Audit date | 2026-05-21 |
| Phase-1 decided tokens | Typography (AD-2.5-T1) |
| Plan reference | RFC-WS-PRIMEVUE-PLAN-2-5 §6 (Track B) |
| Related docs | [PRIMEVUE_COMPONENTS.md](./PRIMEVUE_COMPONENTS.md), [RFC-WS-PRIMEVUE-PLAN-2-5.md](./RFC-WS-PRIMEVUE-PLAN-2-5.md) |
## 1. Purpose & scope
This document is the **inventory and decision register** for the design tokens currently live in the Crewli SPA. It is generated as **Track B** of `RFC-WS-PRIMEVUE-PLAN-2-5` and serves as the foundation for Plan 4's template-layer token work.
**What this doc does:**
- Inventories every token source (Tailwind v4 `@theme`, PrimeVue Aura preset, component-level CSS variables, store-managed runtime attributes).
- Classifies each token as **Brand-essential**, **Bespoke**, or **Generic** per the RFC §6.2 framework.
- Records the **Typography Decision Register** (AD-2.5-T1) end-to-end, including the historical Public Sans removal across both the CSS path and the webfontloader JS path.
- Flags drift, inconsistencies, and follow-up items for Plan 4.
**What this doc does NOT do:**
- Make new token decisions beyond Typography (per RFC §6.4, the rest is deferred to Plan 2.5b or absorbed into Plan 4).
- Refactor, consolidate, or move any token value. This is a read-only audit; the doc records state without changing it.
- Decide on density-aware component spacing, focus-ring tuning, surface-tone overrides, or radius-scale customization — all explicitly deferred.
If a token value changes after this audit date, update the relevant row + the **Audit date** field; do not silently let the inventory drift.
## 2. Classification scheme
Each token is one of (verbatim from RFC §6.2):
- **Brand-essential** — token carries Crewli brand identity. Keep current value. Examples: `--p-primary-color` (teal), gradient definitions, Crewli wordmark colors.
- **Bespoke** — token has a non-default value with deliberate design intent. Keep current value with rationale. Examples: the brand-square recipe (32px / `rounded-lg` / `px-4`), the density axis attribute, custom topbar height.
- **Generic** — token has a non-default value with no documented intent. Default decision: **revert to framework default**. Override requires explicit rationale.
## 3. Token sources
| Source | Type | Notes |
| ----------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| [`apps/app/src/assets/styles/tailwind.css`](../apps/app/src/assets/styles/tailwind.css) | Tailwind v4 `@theme` + `:root` | Single source for Tailwind tokens. Houses `--font-sans` (`@theme`), `--crewli-font-family` (`:root`), and the `@custom-variant dark` selector binding. |
| [`apps/app/src/plugins/primevue/theme.ts`](../apps/app/src/plugins/primevue/theme.ts) | PrimeVue Aura preset (programmatic) | `definePreset(Aura, {...})` — overrides ONLY the `semantic.primary` palette + the light/dark `colorScheme` bindings. Everything else inherits Aura defaults. |
| [`apps/app/src/plugins/primevue/index.ts`](../apps/app/src/plugins/primevue/index.ts) | PrimeVue runtime config | `darkModeSelector: '.dark'`, `cssLayer: false`, Dutch locale, empty `pt` defaults. |
| [`apps/app/src/plugins/iconify.ts`](../apps/app/src/plugins/iconify.ts) | Iconify runtime | Tabler set eager-loaded; icon name convention `tabler-<slug>` (dash, NOT Iconify standard colon). |
| [`apps/app/src/stores/useShellUiStore.ts`](../apps/app/src/stores/useShellUiStore.ts) | Pinia store (runtime DOM attribute writer) | Writes `<html class="dark">` (theme) and `<html data-density="...">` (density) via `applyDomAttributes()`. |
| [`apps/app/src/utils/v2/gradient.ts`](../apps/app/src/utils/v2/gradient.ts) | Component data | The 8-pair gradient palette consumed by the WorkspaceSwitcher avatar (per-org gradient via deterministic hash). |
| Component scoped CSS (`<style scoped>` blocks) | Per-component | Inset box-shadow recipe on the brand-square (RFC §7.4 last-resort — Tailwind has no inset directional utility). |
| `apps/app/src/@core/scss/template/libs/vuetify/_variables.scss` | Vuetify SCSS | Mirrors Inter via `$font-family-custom` so legacy Vuexy surfaces match the v2 shell during the F3F6 window. |
## 4. Color tokens
### 4.1 Primary palette (Brand-essential)
Set by `CrewliPreset` in `plugins/primevue/theme.ts`. The full 11-step ramp is derived from Crewli teal `#0D9394`.
| Token | Value | Aura default | Classification | Decision | Note |
| ---------------- | --------- | ------------ | --------------- | -------- | --------------------------------------------------------------------- |
| `primary.50` | `#E6F4F4` | emerald-50 | Brand-essential | Keep | Tinted teal scale; replaces Aura emerald. |
| `primary.100` | `#CCE9EA` | emerald-100 | Brand-essential | Keep | |
| `primary.200` | `#99D3D4` | emerald-200 | Brand-essential | Keep | |
| `primary.300` | `#66BDBE` | emerald-300 | Brand-essential | Keep | |
| `primary.400` | `#33A7A8` | emerald-400 | Brand-essential | Keep | |
| **`primary.500`**| **`#0D9394`** | emerald-500 | Brand-essential | Keep | **The Crewli teal**. Single source of brand identity in the color system. |
| `primary.600` | `#0B7F80` | emerald-600 | Brand-essential | Keep | Mirrors Vuetify's `staticPrimaryDarkenColor` (lock-stepped during F3F6). |
| `primary.700` | `#086B6C` | emerald-700 | Brand-essential | Keep | |
| `primary.800` | `#055758` | emerald-800 | Brand-essential | Keep | |
| `primary.900` | `#034344` | emerald-900 | Brand-essential | Keep | |
| `primary.950` | `#012F30` | emerald-950 | Brand-essential | Keep | |
### 4.2 Color-scheme bindings (Brand-essential)
| Token | Value | Classification | Note |
| ------------------------------ | ---------------------- | --------------- | ------------------------------------- |
| `light.primary.color` | `{primary.500}` | Brand-essential | Crewli teal as light-mode primary. |
| `light.primary.contrastColor` | `#ffffff` | Brand-essential | White text on teal. |
| `light.primary.hoverColor` | `{primary.600}` | Brand-essential | One step darker on hover. |
| `light.primary.activeColor` | `{primary.700}` | Brand-essential | |
| `dark.primary.color` | `{primary.400}` | Brand-essential | Lighter teal on dark surfaces. |
| `dark.primary.contrastColor` | `{surface.900}` | Brand-essential | Inherits Aura surface scale. |
| `dark.primary.hoverColor` | `{primary.300}` | Brand-essential | |
| `dark.primary.activeColor` | `{primary.200}` | Brand-essential | |
### 4.3 Dark-mode mechanism (Bespoke)
| Token | Value | Classification | Note |
| ---------------------------------------------- | -------------------------------------- | -------------- | --------------------------------------------------------------------------------------------- |
| PrimeVue `darkModeSelector` | `.dark` | Bespoke | Class-based, NOT `prefers-color-scheme`. AD-2.5-D1. |
| Tailwind `@custom-variant dark` | `(&:where(.dark, .dark *))` | Bespoke | Pins Tailwind `dark:` variants to the same class. Without this, PrimeVue and Tailwind would divergent (PrimeVue listens to class, Tailwind default listens to OS). |
| `useShellUiStore.applyDomAttributes()` write | `documentElement.classList.toggle('dark', theme==='dark')` | Bespoke | Runtime write — single source of truth for the class. |
### 4.4 Workspace gradient palette (Brand-essential pattern; values are data)
[`utils/v2/gradient.ts`](../apps/app/src/utils/v2/gradient.ts) exports `GRADIENT_PALETTE`, an 8-pair list keyed by deterministic hash of `org.id`. Each entry is a `[from, to]` tuple for a 135° linear gradient.
| Index | From | To | Tailwind palette anchor |
| ----- | --------- | --------- | ----------------------- |
| 0 | `#0d9488` | `#0f766e` | teal-600 → teal-700 |
| 1 | `#0891b2` | `#0e7490` | cyan-600 → cyan-700 |
| 2 | `#059669` | `#047857` | emerald-600 → emerald-700 |
| 3 | `#10b981` | `#059669` | emerald-500 → emerald-600 |
| 4 | `#0284c7` | `#0369a1` | sky-600 → sky-700 |
| 5 | `#14b8a6` | `#0d9488` | teal-500 → teal-600 |
| 6 | `#06b6d4` | `#0891b2` | cyan-500 → cyan-600 |
| 7 | `#34d399` | `#10b981` | emerald-400 → emerald-500 |
**Classification**: the pattern (per-org deterministic gradient) is Brand-essential. The specific palette values are **data**, not core design tokens — they sit in component code. **⚠ Drift**: the palette uses Tailwind palette anchors (teal/cyan/emerald/sky from Tailwind defaults) rather than derivations of Crewli's actual `#0D9394`. crewli-starter's reference markup uses pairs like `#0D9394, #075F60` (Crewli teal-500/-700 in the new ramp). Flagged for Plan 4 — see §10.
### 4.5 Avatar gradient leftover (drift)
`AppTopbar.vue` user avatar pt-style:
```
background: linear-gradient(135deg, #f472b6, var(--p-primary-500, #0d9488));
```
| Aspect | Value | Note |
| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------ |
| Hardcoded `#f472b6` | Tailwind pink-400 | Not a Crewli brand color. Vuexy-era leftover; mixed with brand teal in the gradient. |
| Fallback `#0d9488` | Tailwind teal-600 | NOT the Crewli teal `#0D9394`. Inert today (the `var()` resolves), but masks the brand color if the CSS var ever fails. |
| Classification | Generic (drift) | Flagged for Plan 4 cleanup; not in P7 scope to change. |
## 5. Typography tokens + Decision Register
### 5.1 Live state
| Token | Value | Source | Classification | Decision |
| ----------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------- | -------------- | -------- |
| Tailwind `--font-sans` (`@theme`) | `"Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif` | `apps/app/src/assets/styles/tailwind.css` | Brand-essential | Decided — AD-2.5-T1 |
| `:root --crewli-font-family` | same stack as above | `apps/app/src/assets/styles/tailwind.css` | Brand-essential | Decided — AD-2.5-T1 |
| `html, body { font-family }` | `var(--crewli-font-family)` | `apps/app/src/assets/styles/tailwind.css` | Brand-essential | Decided — AD-2.5-T1 |
| Vuetify `$font-family-custom` | `"Inter", ...` | `apps/app/src/@core/scss/template/libs/vuetify/_variables.scss` | Brand-essential | Decided — AD-2.5-T1 |
| Inter weights loaded | 400 / 500 / 600 / 700 (via `@fontsource/inter`) | `apps/app/src/main.ts` | Brand-essential | Decided — AD-2.5-T1 |
| Inter load mechanism | Local NPM package (`@fontsource/inter`) — no CDN | `apps/app/src/main.ts` | Brand-essential | Decided — AD-2.5-T1 |
### 5.2 Decision Register — AD-2.5-T1 (Inter via @fontsource/inter)
**Decision**: Inter is Crewli's sans-serif typography across PrimeVue, Tailwind, and Vuetify surfaces. Loaded locally via `@fontsource/inter` (weights 400 / 500 / 600 / 700). No Google Fonts CDN, no CDN of any kind for the body font.
**Status**: complete (decided + implemented + regression-locked).
**Rationale**:
1. Inter is PrimeVue's de-facto reference font — using it produces the cleanest visual default across the component set.
2. Local loading avoids external Google Fonts requests, which carry GDPR/privacy implications and add a third-party network dependency to first paint.
3. Single source — one font, three frameworks all reading from the same `--crewli-font-family` stack — prevents the dual-path drift that the initial Plan 2.5 P2 audit missed (see §5.3).
**History**:
| Date / commit | Event |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Pre-Plan 2.5 | Codebase loaded **Public Sans** via two independent paths: (a) CSS `font-family` declarations in `tailwind.css` and Vuexy `_variables.scss`; (b) a JS `webfontloader` plugin (`apps/app/src/plugins/webfontloader.ts`) that fetched `Public+Sans:...&display=swap` from the Google Fonts CDN at runtime. The two paths were independent; neither knew about the other. |
| Plan 2.5 P2 (commit `41af1801`) | CSS path reverted to Inter. `tailwind.css` `@theme --font-sans` + `:root --crewli-font-family` + the `html, body` cascade all changed to the Inter stack. Vuetify `$font-family-custom` matched. Regression-lock spec `tests/unit/styles/typography.spec.ts` added — but it only inspected CSS file contents. |
| P5 manual smoke (2026-05-20) | The live DOM still carried `wf-publicsans-n4-active wf-active` classes on `<html>`. webfontloader was still fetching Public Sans from Google Fonts. The CSS-only regression-lock had missed the JS path. |
| P2-followup (commit `641ca513`) | The whole `webfontloader.ts` plugin removed (it loaded only Public Sans — no other families). `webfontloader@1.6.28` + `@types/webfontloader@1.6.38` removed from `package.json`. Regression-lock strengthened: `typography.spec.ts` now also scans every file in `src/plugins/` for any reference to `webfontloader`, `WebFont.load`, `Public Sans` (any spelling), or `fonts.googleapis.com`. The plugin's auto-registration via Vuexy's `registerPlugins()` glob is severed by the file deletion alone — no manual unregister site to clean up. |
**Regression lock** (current state):
| Spec | Asserts |
| --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `tests/unit/styles/typography.spec.ts``Typography regression lock (AD-2.5-T1)` describe | `tailwind.css` declares Inter first in every direct font stack; Vuexy `$font-family-custom` declares Inter first; no `Public Sans` (any case/separator) remains in either CSS file. |
| `tests/unit/styles/typography.spec.ts``JS font-loading path (AD-2.5-T1 completion)` describe | No file under `src/plugins/` references `webfontloader`, `WebFont.load`, `Public Sans` (any case/separator), or `fonts.googleapis.com`; the `webfontloader.ts` plugin file does NOT exist. |
If a future PR re-introduces any of these (a Vuexy template port that loads a Google Font, a Storybook addon that lazy-loads a webfontloader path, etc.) the spec catches it before merge.
### 5.3 Why Typography is the only fully-decided Phase-1 token
Per RFC §6.4: font choice has the largest visual surface impact (every text element in the app), the regression-lock pattern needed an anchor reference, and reverting from Public Sans to Inter was already a clear correctness call. Other tokens (focus-ring width, surface tones, density-aware spacing) are localized and benefit from waiting until more pages exist so visual impact is observable.
## 6. Spacing / sizing / radius tokens
### 6.1 The brand-square recipe (Bespoke)
The most consequential bespoke layout invariant in the shell. Encoded across SidebarHeader + WorkspaceSwitcher in P6-styling commits.
| Property | Value | Note |
| -------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Square size | `w-8 h-8` | 32px × 32px. Applies to the SidebarHeader brand mark AND the WorkspaceSwitcher avatar. |
| Square radius | `rounded-lg` | 8px. Set after manual smoke rejected `rounded-xl` (12px) as too soft. |
| Wrapper height | `h-[56px]` | Matches the topbar height; applied to the SidebarHeader brand row AND the WorkspaceSwitcher wrapper in collapsed state. |
| Wrapper horizontal padding | `px-4` | 16px each side. Constant across both states for both wrappers (collapse no longer toggles `justify-center`/`px-0`). |
| Inset shadow | `inset 0 -2px 0 rgb(0 0 0 / 8%)` | Scoped CSS in both components — no Tailwind utility for inset directional shadow at this granularity (RFC §7.4 last-resort). |
**Centring equation**: `collapsed rail (64px) = square (32px) + 2 × px-4 (2 × 16px)`. A left-aligned 32px square inside a `px-4` row is *also* centred when the row is 64px wide. This is what keeps the brand logo stationary through the rail's width animation — no `justify-content` re-centring during the transition.
### 6.2 Sidebar rail widths (Bespoke)
| Token | Value | Classification | Note |
| ---------------------- | ---------- | -------------- | ------------------------------------------------------------------------------------------------- |
| Expanded rail width | `w-64` (256px) | Bespoke | Matches crewli-starter's `--sidebar-w`. |
| Collapsed rail width | `w-16` (64px) | Bespoke | Matches crewli-starter's `--sidebar-w-collapsed`. Locked to the brand-square recipe (see §6.1). |
| Width transition | `transition-[width] duration-200` | Bespoke | 200ms ease on the aside. |
| Aside overflow | `overflow-hidden` | Bespoke | Clips content during the width animation — prevents `whitespace-nowrap` wordmark overflow. |
### 6.3 Topbar height (Bespoke CSS var with fallback)
| Token | Value | Note |
| ------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `--topbar-h` | `var(--topbar-h, 56px)` | Consumed in AppTopbar's `pt.root` class. **⚠ Not declared in `:root` anywhere** — only used with fallback. Effectively a hard-coded 56px with the *option* of being overridden. Worth either declaring properly or removing the var indirection in Plan 4. |
### 6.4 Other component sizes (Generic — Tailwind defaults)
The shell uses Tailwind defaults for the rest: `h-[38px]` for icon buttons (search, density, dark, notifications) is a one-off literal; `w-7 h-7` for the inline collapse chevron; `gap-[10px]` between brand row children; etc. None of these carry brand intent — they're sized to match crewli-starter visual reference and otherwise inherit Tailwind's default scale. Classified Generic; Plan 4 may consolidate via a `--icon-btn-size` token if patterns recur.
### 6.5 PrimeVue surface tones, focus-ring, border-radius scale (DEFERRED)
Inherited from Aura defaults — no overrides in `CrewliPreset`. Surface scales (`{surface.0}``{surface.950}`), focus-ring width/style, default radius (`var(--p-border-radius)`) all come from `@primeuix/themes/aura` as-shipped. Plan 2.5 made no decisions here; deferred to Plan 4 (see §9).
## 7. Density tokens (Bespoke)
| Token | Value | Source | Note |
| ------------------------- | ------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `density` enum | `'comfortable' \| 'compact'` (NOT `comfy`) | `useShellUiStore.ts` | Binary. The earlier RFC draft referenced a `comfy` value — incorrect; the runtime type is `comfortable`. |
| `<html data-density>` | written by `applyDomAttributes()` | `useShellUiStore.ts` | Single DOM read for any consumer that wants to react to density. |
| `toggleDensity()` action | binary flip + sync `applyDomAttributes` | `useShellUiStore.ts` (P6 Fix 10) | Topbar button delegates here. |
| Component-level reaction | **none yet** | — | No component CSS currently keys off `[data-density=compact]`. The attribute is set; the component-level spacing-aware styles are deferred (backlog `DENSITY-AWARE-SPACING`). |
**Classification**: the axis itself is Bespoke (Crewli design intent — toggle between two interface densities). The component-level spacing recipes that respond to it are deferred and become Plan 4 work.
## 8. Icon tokens (Bespoke convention)
| Token | Value | Note |
| ------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Icon naming | `tabler-<slug>` | Dash-separated. Deviates from Iconify standard `tabler:<slug>` (colon) because Crewli's `Icon.vue` bridge expects dashes — a Vuexy-era convention preserved for the v1→v2 bridge. |
| Iconify collection | `@iconify-json/tabler` | Full set eager-loaded at app boot (`addCollection` in `plugins/iconify.ts`). |
| Default size | per-call (`:size="N"`) | No global icon-size token. Each call sets its own size. |
**Classification**: dash convention is Bespoke (a documented Crewli deviation); the set choice (Tabler) and the eager-load strategy are Bespoke decisions (size trade-off accepted for CSP compatibility — Iconify's runtime CDN is blocked).
## 9. Classification summary
| Bucket | Approximate count | What this means for Plan 4 |
| ----------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Brand-essential** | ~20 (full primary palette + scheme bindings + Typography stack) | Sacred. Plan 4 should not change these without explicit brand-team sign-off. Inter and `#0D9394` ARE Crewli. |
| **Bespoke** | ~10 (dark-mode mechanism, brand-square recipe, rail widths, density axis, icon-dash convention, topbar height) | Intentional Crewli deviations from defaults. Plan 4 may refactor / consolidate, but the rationale stays. Each Bespoke row in this doc carries a one-line "why". |
| **Generic** | ~everything else (full Aura surface scale, default border radius, focus ring, font-size scale, spacing scale, default shadows, default transitions) | These are framework defaults used as-is. Plan 4 decides which of these to override with Crewli-specific values. Default decision per RFC §6.2 is "revert to default" unless rationale exists. |
## 10. Drift + follow-up items for Plan 4
These are not P7 decisions; they're flagged here so they don't get lost.
| Item | Severity | Source | Recommended action |
| ---- | -------- | ------ | ------------------ |
| Workspace gradient palette uses Tailwind palette anchors (teal-500/600, cyan-500/600, emerald, sky) rather than derivations of Crewli's primary `#0D9394`. | Low (visual; data tier) | `utils/v2/gradient.ts` line 14 | Re-derive the 8 pairs from the Crewli primary ramp (`primary.500`/`primary.700` plus complementary tints from a Crewli secondary palette, when Plan 4 defines one). |
| User-avatar gradient hardcodes `#f472b6` (Tailwind pink-400) mixed with `var(--p-primary-500)`. | Medium (off-brand color baked into chrome) | `AppTopbar.vue` line 385 + 400 | Replace pink with a Crewli secondary/accent (Plan 4 decision). At minimum, drop the var fallback `#0d9488` which is NOT the Crewli teal (`#0D9394`). |
| `--topbar-h` referenced with fallback only, never declared in `:root`. | Low (correctness) | `AppTopbar.vue` line 225 | Either declare `:root { --topbar-h: 56px; }` in `tailwind.css`, or replace the `var(--topbar-h, 56px)` with the literal `h-14`. The current form is a half-finished abstraction. |
| `density` axis is set on `<html>` but no component spacing reacts to it. | Medium (feature incomplete) | `useShellUiStore.ts` + every shell component | Plan 4 picks the spacing surfaces that should compress in `compact` mode (typically row heights in tables, sidebar nav item padding, topbar height). Tracked as `DENSITY-AWARE-SPACING`. |
| PrimeVue `pt` defaults file is empty. | None (scaffold, by design) | `apps/app/src/plugins/primevue/defaults.ts` | F4 sub-packages populate as each Vuetify surface migrates. Not an immediate Plan 4 concern, but worth knowing this central override site exists. |
| The `--crewli-font-family` CSS variable duplicates the Tailwind `@theme --font-sans` stack. | Low (redundancy) | `tailwind.css` | Intentional twin — `@theme` feeds Tailwind utilities; `--crewli-font-family` is the cascade-level named alias for non-Tailwind consumers (PrimeVue, Vuetify body inheritance). Plan 4 could collapse to a single source if the two-step is no longer needed after Vuetify is removed. |
## 11. Deferred to Plan 4
Per RFC §6.4, Plan 2.5 deliberately stops Phase-1 at Typography. The following are NOT decided in this audit:
| Deferred area | Why deferred | Where it'll land |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| Surface tone scale (`--p-surface-*`) | Localized impact; needs page surfaces beyond the dashboard to evaluate. | Plan 4 (or Plan 2.5b if priority bumps). |
| Focus-ring width / color | Aura default works visually; needs accessibility audit before customization. | Plan 4 + accessibility pass. |
| Border-radius scale (`--p-border-radius` family) | Single-value change cascades through every rounded surface. Wait until more components are migrated. | Plan 4. |
| Font-size scale (root rem base) | Touches the typographic system; needs Plan 4's full type-scale design. | Plan 4. |
| Spacing rhythm (`--p-spacing-*`) | Plan 2.5 used Tailwind defaults; Plan 4 may introduce Crewli-specific spacing tokens. | Plan 4. |
| Density-aware component spacing | The axis exists (`<html data-density>`) but nothing reads it. Plan 4 picks the surfaces. | Plan 4 (`DENSITY-AWARE-SPACING`). |
| Shadow scale | Aura defaults; one bespoke inset-shadow exception on the brand square (RFC §7.4 last-resort). | Plan 4 if shadow recipes recur. |
| Secondary / accent color palette | Crewli's brand currently has only the teal primary defined. A secondary is needed for callouts (e.g., the off-brand pink in §10). | Plan 4 + brand-team input. |
| Workspace gradient palette re-derivation | See §10. | Plan 4. |
## 12. Maintenance
When a token decision lands in a future PR:
1. Find the row in this doc; update the **Decision** column and the **Note** column.
2. Add a new row to §5.2-style "Decision Register" if the token gets its own register entry.
3. Update the **Audit date** field at the top.
4. Add the corresponding regression-lock spec if the decision is enforceable in code (CSS file contents, computed style, store action). Cross-link the spec path here.
When a new token source appears (e.g., a `pt` default block populates):
1. Add the source to §3.
2. Inventory its tokens in the relevant section.
3. Classify each one.
Drift catches: if a follow-up audit finds a token in the codebase not listed here, that's the doc to fix, not the codebase.