feat(gates): WP-02 — harden check:tokens to whole-app colour guard
- Move the guard to scripts/check-tokens.sh; regex now catches hex + rgb()/hsl() (was hex-only) across ALL src/app components (was three ui/layout dirs). `token-ok` marker suppresses justified false positives; px stays out of scope (documented in the script). - Zero exclusions: debug-state's dark code-editor palette moves to --app-devpanel-* tokens in styles.scss (the one exempt file), dropping its --exclude hole. - Tokenize remaining hits: site-footer border via color-mix; three brief border widths via --rhc-border-width-* (new --rhc-border-width-lg: 3px). Verified: planted violation fails the guard; GREEN + test-storybook:ci. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -41,7 +41,7 @@ for its existing violations, so every WP ends green.
|
||||
| WP | Title | Phase | Status |
|
||||
|----|-------|-------|--------|
|
||||
| [WP-01](WP-01-axe-ci-gate.md) | Axe-on-every-story CI gate | 0 · gates | done |
|
||||
| [WP-02](WP-02-check-tokens.md) | Harden `check:tokens` + fix what it catches | 0 · gates | todo |
|
||||
| [WP-02](WP-02-check-tokens.md) | Harden `check:tokens` + fix what it catches | 0 · gates | done |
|
||||
| [WP-03](WP-03-contracts-purity.md) | Boundaries I: contracts purity + ApiClient confinement | 0 · gates | todo |
|
||||
| [WP-04](WP-04-ui-not-infrastructure.md) | Boundaries II: `ui ↛ infrastructure` + showcase sanction | 0 · gates | todo |
|
||||
| [WP-05](WP-05-parse-boundaries.md) | Parse-don't-validate closure + MDX | 1 · FP/DDD | todo |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# WP-02 — Harden `check:tokens` + fix what it then catches
|
||||
|
||||
Status: todo
|
||||
Status: done (pending commit)
|
||||
Phase: 0 — enforcement & gates
|
||||
|
||||
## Why
|
||||
@@ -47,10 +47,10 @@ cover the whole app before the CIBG work (WP-10…13) leans on it.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Script scans all contexts and matches hex + rgb()/hsl().
|
||||
- [ ] Zero exclusions; any `token-ok` marker has a reason.
|
||||
- [ ] site-footer, debug-state, and the three brief components are tokenized.
|
||||
- [ ] A planted violation provably fails `npm run check:tokens`.
|
||||
- [x] Script scans all contexts and matches hex + rgb()/hsl().
|
||||
- [x] Zero exclusions; any `token-ok` marker has a reason. (No `token-ok` markers were needed.)
|
||||
- [x] site-footer, debug-state, and the three brief components are tokenized.
|
||||
- [x] A planted violation provably fails `npm run check:tokens`.
|
||||
|
||||
## Verification
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@
|
||||
"build-storybook": "ng run atomic-design-poc:build-storybook",
|
||||
"test-storybook": "test-storybook",
|
||||
"test-storybook:ci": "concurrently -k -s first -n sb,axe \"http-server storybook-static -p 6006 --silent\" \"wait-on tcp:127.0.0.1:6006 && test-storybook --url http://127.0.0.1:6006\"",
|
||||
"check:tokens": "if grep -rnE '#[0-9a-fA-F]{3,6}' src/app/registratie/ui src/app/shared/ui src/app/shared/layout --include='*.component.ts' --exclude='debug-state.component.ts'; then echo 'FAIL: hardcoded hex colors found (use design tokens)'; exit 1; else echo 'OK: no hardcoded hex colors in wizard/atoms/chrome'; fi"
|
||||
"check:tokens": "bash scripts/check-tokens.sh"
|
||||
},
|
||||
"private": true,
|
||||
"packageManager": "npm@11.12.1",
|
||||
|
||||
20
scripts/check-tokens.sh
Executable file
20
scripts/check-tokens.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# WP-02 token guard: fail if any *.component.ts hardcodes a colour (hex/rgb/hsl)
|
||||
# instead of a --rhc-*/--app-* design token. Palette values live ONLY in the
|
||||
# styles.scss token bridge (the one exempt file — it IS the bridge).
|
||||
#
|
||||
# px/rem are deliberately NOT grepped: too many false positives (font sizes,
|
||||
# transforms, media queries). Raw border widths are fixed by hand and mapped to
|
||||
# --rhc-border-width-*; see docs/backlog/WP-02-check-tokens.md.
|
||||
#
|
||||
# Escape hatch: put a `token-ok` marker + reason on a line to suppress a justified
|
||||
# false positive (a colour word inside a comment or a data-URI). Keep the bar high.
|
||||
set -uo pipefail
|
||||
|
||||
hits=$(grep -rnE '#[0-9a-fA-F]{3,8}\b|rgba?\(|hsla?\(' src/app --include='*.component.ts' | grep -v 'token-ok' || true)
|
||||
if [ -n "$hits" ]; then
|
||||
echo "$hits"
|
||||
echo 'FAIL: hardcoded colours in components (use --rhc-*/--app-* tokens, or add a `token-ok` marker + reason)'
|
||||
exit 1
|
||||
fi
|
||||
echo 'OK: no hardcoded colours in components'
|
||||
@@ -11,7 +11,7 @@ import { LetterBlock } from '@brief/domain/brief';
|
||||
imports: [RichTextEditorComponent, ButtonComponent],
|
||||
styles: [`
|
||||
:host{display:block}
|
||||
.block{border-inline-start:3px solid var(--rhc-color-border-subtle);padding-inline-start:var(--rhc-space-max-md)}
|
||||
.block{border-inline-start:var(--rhc-border-width-lg) solid var(--rhc-color-border-subtle);padding-inline-start:var(--rhc-space-max-md)}
|
||||
.meta{display:flex;justify-content:space-between;align-items:center;gap:var(--rhc-space-max-md);margin-block-end:var(--rhc-space-max-sm)}
|
||||
.controls{display:flex;gap:var(--rhc-space-max-sm)}
|
||||
`],
|
||||
|
||||
@@ -37,7 +37,7 @@ const SAMPLE_VALUES: Record<string, string> = {
|
||||
styles: [`
|
||||
:host{display:block}
|
||||
.toolbar{display:flex;justify-content:flex-end;margin-block-end:var(--rhc-space-max-sm)}
|
||||
.letter{background:var(--rhc-color-wit);border:1px solid var(--rhc-color-border-default);border-radius:var(--rhc-border-radius-md);padding:var(--rhc-space-max-2xl)}
|
||||
.letter{background:var(--rhc-color-wit);border:var(--rhc-border-width-sm) solid var(--rhc-color-border-default);border-radius:var(--rhc-border-radius-md);padding:var(--rhc-space-max-2xl)}
|
||||
section{margin-block-end:var(--rhc-space-max-xl)}
|
||||
p{margin:0 0 var(--rhc-space-max-sm)}
|
||||
ul,ol{margin:0 0 var(--rhc-space-max-sm);padding-inline-start:1.4em}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LibraryPassage } from '@brief/domain/brief';
|
||||
selector: 'app-passage-picker',
|
||||
imports: [FormsModule, CheckboxComponent, ButtonComponent],
|
||||
styles: [`
|
||||
:host{display:block;border:1px solid var(--rhc-color-border-default);border-radius:var(--rhc-border-radius-md);padding:var(--rhc-space-max-md)}
|
||||
:host{display:block;border:var(--rhc-border-width-sm) solid var(--rhc-color-border-default);border-radius:var(--rhc-border-radius-md);padding:var(--rhc-space-max-md)}
|
||||
ul{list-style:none;margin:0 0 var(--rhc-space-max-md);padding:0;display:grid;gap:var(--rhc-space-max-sm)}
|
||||
.scope{color:var(--rhc-color-foreground-subtle)}
|
||||
`],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Component } from '@angular/core';
|
||||
.links a:hover{text-decoration:underline}
|
||||
/* CIBG's vendored .meta class (unrelated component, coincidental name) sets a
|
||||
dark grey — override rather than rename to keep the CIBG-mirroring class name. */
|
||||
.meta{inline-size:100%;border-block-start:var(--rhc-border-width-sm) solid rgb(255 255 255 / 0.25);margin-block-start:var(--rhc-space-max-xl);padding-block-start:var(--rhc-space-max-lg);font-size:var(--rhc-text-font-size-sm);opacity:.85;color:var(--rhc-color-foreground-on-primary)}
|
||||
.meta{inline-size:100%;border-block-start:var(--rhc-border-width-sm) solid color-mix(in srgb, var(--rhc-color-foreground-on-primary) 25%, transparent);margin-block-start:var(--rhc-space-max-xl);padding-block-start:var(--rhc-space-max-lg);font-size:var(--rhc-text-font-size-sm);opacity:.85;color:var(--rhc-color-foreground-on-primary)}
|
||||
`],
|
||||
template: `
|
||||
<footer class="bar">
|
||||
|
||||
@@ -12,19 +12,22 @@ import { maskBsn, redactProfile } from './mask';
|
||||
* product feature: the whole template is gated by isDevMode(), so it does not
|
||||
* render and is unreachable in a production build.
|
||||
*
|
||||
* ponytail: dev tool — intentionally off-theme (not Rijkshuisstijl) and exempt
|
||||
* from the design-token convention; plain values keep it visually distinct.
|
||||
* ponytail: dev tool — intentionally off-theme (a dark code-editor look, not
|
||||
* CIBG). Its palette lives as --app-devpanel-* tokens in styles.scss so this file
|
||||
* stays hex-free (check:tokens has no holes), while still reading as a tool.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-debug-state',
|
||||
imports: [JsonPipe],
|
||||
styles: [`
|
||||
.fab{position:fixed;bottom:1rem;right:1rem;z-index:9999;font:600 12px/1 monospace;
|
||||
background:#1e1e1e;color:#9cdcfe;border:1px solid #444;border-radius:4px;
|
||||
background:var(--app-devpanel-bg);color:var(--app-devpanel-accent);
|
||||
border:var(--rhc-border-width-sm) solid var(--app-devpanel-border);border-radius:4px;
|
||||
padding:.5rem .75rem;cursor:pointer}
|
||||
.panel{position:fixed;bottom:3.25rem;right:1rem;z-index:9999;width:min(90vw,28rem);
|
||||
max-height:70vh;overflow:auto;background:#1e1e1e;color:#d4d4d4;border:1px solid #444;
|
||||
border-radius:6px;box-shadow:0 6px 24px rgba(0,0,0,.4)}
|
||||
max-height:70vh;overflow:auto;background:var(--app-devpanel-bg);color:var(--app-devpanel-fg);
|
||||
border:var(--rhc-border-width-sm) solid var(--app-devpanel-border);
|
||||
border-radius:6px;box-shadow:0 6px 24px var(--app-devpanel-shadow)}
|
||||
.panel pre{margin:0;padding:.75rem;font:12px/1.5 monospace;white-space:pre-wrap;
|
||||
word-break:break-word}
|
||||
`],
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
--rhc-border-radius-round: 50%;
|
||||
--rhc-border-width-sm: 1px;
|
||||
--rhc-border-width-md: 2px;
|
||||
--rhc-border-width-lg: 3px;
|
||||
|
||||
/* — typography — */
|
||||
--rhc-text-font-size-sm: 0.875rem;
|
||||
@@ -94,6 +95,16 @@ body { background: var(--rhc-color-wit); color: var(--rhc-color-foreground-defau
|
||||
--app-content-max: 67rem; /* readable page column */
|
||||
--app-form-narrow: 32rem; /* page-shell narrow variant */
|
||||
--app-skip-link-offset: -999px; /* off-screen skip link */
|
||||
|
||||
/* Dev-only state inspector (debug-state.component.ts, isDevMode-gated, never in
|
||||
prod): a dark code-editor palette kept deliberately OFF the CIBG design system
|
||||
so it reads as a tool, not product chrome. Defined here (the one exempt file)
|
||||
so the component itself stays colour-token-only. */
|
||||
--app-devpanel-bg: #1e1e1e;
|
||||
--app-devpanel-fg: #d4d4d4;
|
||||
--app-devpanel-accent: #9cdcfe;
|
||||
--app-devpanel-border: #444;
|
||||
--app-devpanel-shadow: rgb(0 0 0 / 0.4);
|
||||
}
|
||||
|
||||
/* App utility classes: centralise the repeated inline layout idioms so components stay
|
||||
|
||||
Reference in New Issue
Block a user