--- date: 2026-10-20 pr: pending feature: Chat surface shadow tokenization and CYBER color guard impact: The fork divider pill in the chat message list now paints its shadow from the shared --shadow-sm token instead of a hardcoded light-theme slate wash, so it reads as a lifted surface on the dark background. A new source-level guard now blocks raw hex colors, named colors, and literal RGB triples from reappearing anywhere in the client source. Purely visual; no message shapes, socket events, or emission timing changed. --- # Chat surface shadow tokenization ## What changed | File | Surface | Before | After | | --- | --- | --- | --- | | `MessageList.vue` | fork divider pill | `box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05)` | `box-shadow: var(--shadow-sm)` | `rgba(15, 23, 42, 0.05)` is a slate-900 wash at 5% alpha — a light-theme habit for a hairline lift on white. On `--bg-card` it is effectively invisible, so the pill lost its "lifted" read entirely. `--shadow-sm` is the dark-theme equivalent (`0 2px 8px oklch(0 0 0 / 0.28)`). The same lint also flagged a stale shadow in `PetdexView.vue` (`rgba(127, 127, 127, 0.08)` checkerboard → `rgba(var(--text-muted-rgb), 0.08)`) and the kanban category colors, which were promoted to the new `--kanban-review` / `--kanban-review-rgb` tokens. Those two files are not on the chat chain, but they are recorded here because the guard that found them was introduced as part of this same change. ## New guard `tests/client/style-tokens.test.ts` scans every `.vue` / `.scss` / `.css` / `.ts` file under `packages/client/src` and fails on: 1. raw hex colors (`#rrggbb`) 2. named color keywords (`color: red`) 3. literal `rgba(0,0,0,` shadows 4. literal RGB triples in `rgba()` (`rgba(15, 23, 42, 0.05)`) — these must become `rgba(var(--*-rgb), α)` Comments and HTML entities are stripped before matching, and the stripping step has its own self-check so a broken guard cannot silently pass. The exemption list is explicit: chart palette (`ui-baseline.md` §7), the xterm theme presets, the Arco `ConfigProvider` theme, the `arkscore` vendor decoration layer, and the language badges in `MessageItem.vue`. The language badge palette is additionally pinned by an assertion, so the exemption cannot widen unnoticed. ## Not changed Message rendering, streaming, tool-call grouping, and the `/chat-run` event contract. The chat chain fragment exists because the touched file sits on the chat chain path, not because any behavior moved. ## Verification `npm run harness:check`, `npm run build`, and `npm run test:e2e` (46 passed) all pass. The existing `tests/e2e/chat-cyber-semantics.spec.ts` still passes unchanged.