@hotelfriendag/design-tokens 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,329 @@
1
+ # Changelog — HotelFriend Design System
2
+
3
+ Phase history of the token/component pipeline. For the current consumption
4
+ instructions see [`README.md`](README.md); for remaining work see
5
+ [`ROADMAP.md`](ROADMAP.md).
6
+
7
+ > Versions `0.2.x` were briefly on GitHub Packages while the registry decision was pending. `0.3.0+` is published exclusively to npmjs.com as `@hotelfriendag/design-tokens`. See README → Publishing for the release flow.
8
+
9
+ ## v0.8.0 — Nav/layout primitives + ui-hf integration feedback (2026-07-10)
10
+
11
+ Closes the first of the Blueprint §1 nav/layout gaps with unambiguous shared primitives: `.hf-breadcrumb` (navigation), `.hf-container` (layout), and — harvested from web-hf — `.hf-section-header` and `.hf-accordion`. **The nav/layout primitives are additive** — new selectors plus one additive token (`size.contentNarrow`). This release **also** carries the full ui-hf first-consumer integration feedback series (GitHub issues #7–#14, waves 1–4 documented at the end of this section), which includes a few **0.x-breaking** class renames, an export removal, generator fixes that change some token outputs, the 4px grid normalization, and the new opt-in `base.css` — so v0.8.0 is a 0.x minor bump that is **not** purely additive.
12
+
13
+ **New primitives** (`src/components.css` → `pre-built/components.css`)
14
+
15
+ - `.hf-breadcrumb` + `__item` / `__link` / `__sep` (`__item--active`) — a CSS-only breadcrumb trail rendered on a `<nav><ol>`. The chevron separator is drawn **purely in CSS** — a 6×6 rotated bordered square on `.hf-breadcrumb__item + .hf-breadcrumb__item::before` — so the component has **no image/svg asset dependency**. `__link` is muted, accent on `:hover`, with the canonical `:focus-visible` outline (`2px solid --color-hf-accent` + 2px offset); `__item--active` (current/trailing crumb) is `--color-hf-fg` + `--font-weight-hf-medium` and carries `aria-current="page"` in markup. An optional explicit `.hf-breadcrumb__sep` element is provided as an alternative to the `::before` pseudo (use one or the other, not both). Binds to existing tokens only — **no new breadcrumb token**.
16
+ - `.hf-container` (`--narrow`, `--fluid`) — a neutral block max-width wrapper (`display:block`, NOT flex, so it composes with any inner layout). Default caps at `--size-hf-content-max` (1440px), centers with `margin-inline:auto`, and keeps a `--spacing-hf-4` (16px) horizontal gutter. `--narrow` → `--size-hf-content-narrow` (1200px, reading/forms); `--fluid` → `max-width:none` (full-bleed, keeps the gutter).
17
+
18
+ **Harvested from web-hf** (`src/components.css` → `pre-built/components.css`) — two more CSS-only, token-bound primitives selected by the web-hf harvest audit. **Zero new tokens** — both bind to existing semantic/spacing/type/radius/motion tokens.
19
+
20
+ - `.hf-section-header` + `__title` / `__description` / `__title-accent` (`--start`) — a centered (default) or left-aligned (`--start`) title + subtitle block for section / page intros. **The title binds to the existing `--font-size-hf-page` token (no hardcoded size, no new clamp token)** — and because `[data-theme="web"]` already overrides that token to a fluid `clamp()` (see `pre-built/web.css`), the **same** `.hf-section-header` **auto-scales** to the marketing rhythm under the web theme, dense in the portal, with no separate variant. `__title-accent` is an inline span hook for highlighting a word in `--color-hf-accent`.
21
+ - `.hf-accordion` + `__item` / `__header` / `__body` / `__arrow` (state `.is-open` on `__item`) — a single-open disclosure list. The DS ships the **CSS-only visual + collapse** (the `grid-template-rows: 0fr → 1fr` trick with an inner `overflow:hidden` wrapper; a pure-CSS rotated-border chevron, same technique as the breadcrumb separator) and the `.is-open` state class. The **single-open state machine and ARIA wiring (`aria-expanded` / `aria-controls`) are the consumer's JS** — the DS ships the class contract + the state class the consumer toggles. `__header` renders on a `<button>`. The grid-rows + arrow transitions are added to the `@media (prefers-reduced-motion: reduce)` block (no animation when reduced). On adoption this **retires web-hf's local `.hf-toggle-block`**.
22
+
23
+ **New token** (`tokens.figma.json` → all token targets)
24
+
25
+ - `size.contentNarrow` = `1200px` → `--size-hf-content-narrow`. Narrow content width for forms/reading (web-hf's real container cap is 1200/1280; decision 2026-06-17). Additive — does **not** change `contentMax` (1440px). Emitted across the token outputs (`tokens.css`, `_tokens.scss`, `tokens.ts/js/d.ts`, `tailwind.css`/`tailwind.additive.css`/`tailwind.preset.js`).
26
+
27
+ **Scope note (decisions now recorded)** — a shared layout grid is **permanently declined** (founder decision **D-grid**, 2026-06-17, approval `8339036a`): the DS ships **no grid**; products keep their own (web-hf keeps its local non-`hf-`-prefixed `_gridnew.scss` → no namespace violation). Revisit only if a 2nd non-Tailwind class consumer needs one, under a non-colliding name `.hf-grid` (never `.hf-col-*`). The `.hf-page` shell is **deferred to v0.9.0** (founder decision **D-page**) — not added here.
28
+
29
+ **Docs & demo synced**
30
+
31
+ - `components.html`: new `#breadcrumb`, `#container`, `#section-header`, and `#accordion` demo sections + nav anchors, with the new CSS mirrored into the embedded plain-`<style>` component layer (per the file's token-duplication design). The accordion demo wires a tiny single-open toggle inline (the JS is the consumer's responsibility, not DS-shipped); a `.hf-section-header` is also placed inside the `[data-theme="web"]` rhythm demo so its title visibly enlarges under the web theme.
32
+ - `README.md` component table + breadcrumb/container/section-header/accordion usage snippets; `UI_DESIGN.md` §5.1 entries for all four; `ADOPTION.md` reserved `.hf-*` layout-name note + collision guidance for web-hf (delete legacy `_hf-breadcrumbs.scss`; kill `_grid.scss` system B before adopting the block `.hf-container`; `.hf-toggle-block` retired by `.hf-accordion` on adoption), plus the D-grid pointer. `CONTRACT.md` §3 records the normative no-grid line + the D-grid disposition.
33
+
34
+ **Consumer-feedback fixes — ui-hf integration wave 1**
35
+
36
+ - **Breaking (0.x):** `.hf-select` / `.hf-select-wrap` / `.hf-select--error` renamed to `.hf-native-select` / `.hf-native-select-wrap` / `.hf-native-select--error` — eliminates the collision with consumer-owned `.hf-select` components (ui-hf owns a `.hf-select` component root class; precedent **D-grid**: the DS must pick non-colliding names). `components.css` is now safe for ui-hf to import. Closes #7.
37
+ - **Fixed:** the six `.typography-hf-*` utility classes emitted a literal, invalid `font-family: fontFamily.primary` (the generator stripped the `{…}` braces but not the `fontFamily.` category prefix). They now emit `font-family: var(--font-family-hf-primary)`. Closes #9.
38
+ - **Fixed:** `tokens.js` / `tokens.ts` exported the composite `typography.*` tokens as raw reference strings (`"{fontFamily.primary}"`, `"{fontWeight.semibold}"`, …) because `nestTokens()` passed object values through unresolved. Sub-value refs are now resolved inline, so JS/TS consumers get usable values (`"Roboto"`, `"600"`, `"26px"`, `"1.2"`). `tokens.d.ts` is unchanged (leaves stay `string`). Closes #9.
39
+ - **Fixed / New:** `tailwind.css` now emits a plain `:root { … }` compat block after `@theme` carrying the non-color tiers that Tailwind gives no matching utility namespace — `--font-family-hf-*`, `--font-size-hf-*`, `--line-height-hf-*`, `--motion-hf*`, `--opacity-hf-*`, `--size-hf-*`, `--z-index-hf-*` (+ `--z-hf-*` aliases) — under their canonical `tokens.css` names, with literal values byte-identical to `tokens.css`. The README Tailwind recipe imports `tailwind.css` + `components.css` but not `tokens.css`, so 13 var names `components.css` references previously fell back to hardcoded literals and **token changes never reached Tailwind-recipe consumers** — now they do. Closes #9.
40
+ - **Changed:** z-index moved OUT of the `@theme` block into the `:root` compat block — `z` is not a Tailwind v4 theme namespace (it generates no utilities and compiled v4 prunes unused `@theme` vars). The false `--z-* → z-*` line in the header comment is removed and the compat block is documented; use `z-[var(--z-hf-modal-dialog)]` or plain CSS. Closes #9.
41
+ - **Validator:** new **check 9** — every `var(--*)` in `components.css` / `status.css` must resolve within each shipped recipe independently (Tailwind: `tailwind.css` + `components.css` + `status.css`; vanilla: `tokens.css` + `components.css` + `status.css`), not merely the union of all four. This is the guard that would have caught the cross-channel fallbacks above. Closes #9.
42
+ - **Breaking (0.x):** the `tailwind.additive.css` export and its `tailwind-v4-additive` build target are **removed** — the file was byte-identical to `tailwind.css` since the initial import (the `hf-` prefix makes every token additive). Import `tailwind.css`. Closes #9.
43
+ - **Breaking (0.x):** the only bare, unprefixed component class `.skeleton` is renamed to `.hf-skeleton` (the `hf-shimmer` keyframe was already prefixed). Every `.hf-*` component now sits behind the namespace. Closes #10.
44
+ - **Changed (visual):** the `fontFamily.primary` token now carries a fallback stack — `Roboto, system-ui, sans-serif` (was bare `Roboto`) — at the source, so all channels inherit it (`tokens.css` `--font-family-hf-primary`, `tailwind.css` `--font-hf-sans` + compat, the v3 preset array, `tokens.js/ts`, and shadcn's `--font-sans`, which is now **derived** from the token instead of hardcoded so it can't drift). **Visual callout:** environments without Roboto installed/loaded now render `system-ui` (the OS UI font) instead of the browser default serif. Closes #10.
45
+ - **Docs:** clarified in `ADOPTION.md` that the `.is-active` / `.is-disabled` / `.is-open` state classes ship compound-only (always with a `.hf-*` class, per `states-canonical.json` `_meta.namingConvention`) and that `[data-density]` selectors only re-declare `--hf-*` vars — so neither is a global cascade leak (half-refutes the #10 concern). Closes #10.
46
+
47
+ **Consumer-feedback fixes — ui-hf integration wave 2**
48
+
49
+ - **Fixed:** the npm artifact didn't ship `CHANGELOG.md`, `ROADMAP.md`, or `INTEGRATION-ui-hf.md`, even though the shipped `README.md` links all three (phase history, remaining work, first-consumer playbook + git-submodule appendix) — a consumer integrating from npm hit dead links. `package.json` `files` now includes all three. `CONTRACT.md` / `AGENTS.md` remain intentionally unshipped. Closes #8.
50
+ - **Fixed:** the unpkg `components.html` example pinned a stale `@0.5.0` in its "pin a version" aside while the package had already moved two minors ahead. Dropped the hardcoded pin — the section now only shows the version-less URL, which always tracks latest. Closes #8.
51
+ - **Docs:** README's Tailwind v4 section now explains `@source not` — not needed on the npm path (Tailwind v4 excludes `node_modules` by default, so `components.html`'s demo classes can't leak into a consumer build), only relevant on the git-submodule fallback, where `INTEGRATION-ui-hf.md`'s appendix has the exclusion to add. RFC-0001 §9 row 4.2 evidence now points at that section instead of a bare "documented in README" claim. Closes #8.
52
+
53
+ **Consumer-feedback fixes — ui-hf integration wave 3**
54
+
55
+ - **Changed (visual, 1–2px shifts) — 4px-grid normalization of dimension tokens:** control heights are equalized at **40px** — `size.inputDefault` 39→40 (+ the density default `--hf-field-h` is now 40px) and `size.btnSearch` 38→40, which kills the 38/39/40 control-height mismatch consumers hit when mixing search buttons, inputs, and default buttons in one row. Also `spacing.7` 30→28 (28 keeps it on the grid; 32 would duplicate `spacing.8` — founder approval `6c6b1b42`) and `size.sidebarExpanded` 215→216. The `.hf-switch` geometry snaps to the grid: 40×**24** track, **20**px knob, checked translate **16**px (was 40×22 / 18 / 18). `fontSize.hero` stays 30px — it is a font size, not a dimension token. Closes #11.
56
+ - **Validator:** new **check 10** — every `tokens.figma.json` token of type `sizing`/`spacing` with a `<N>px` value must have N divisible by 4. borderRadius/fontSizes are out of the type filter (radius 6/9/99px + the type scale are legitimately off-grid). `GRID_ALLOWLIST` holds founder-approved exceptions and currently starts **empty**.
57
+ - **Migration note:** ui-hf should re-verify any pixel values it quotes for these controls (39/38 → 40) after upgrading; padding-derived heights don't change, but explicit `h-[39px]`/`h-[38px]` and `size`/`--hf-field-h` reads now resolve to 40.
58
+ - **New — per-size line-height pairs across all channels (issue #12):** the `lineHeight` group gains one pair per fontSize name (`xs:16 · sm/base/md:20 · lg:24 · xl/2xl/h2:28 · page:32 · hero:36`, all px, all on the 4px grid), riding the existing `lineHeights` type so every channel emits them with **zero new plumbing** — `--line-height-hf-{size}` in `tokens.css` + the `tailwind.css` `:root` compat block, `--leading-hf-{size}` in `@theme`, and the scss/js/ts bundles. Tailwind now also emits the native pairing `--text-hf-{size}--line-height`, so `text-hf-{size}` utilities set **both** font-size and line-height; the v3 preset emits the tuple form `fontSize: { name: [size, { lineHeight }] }`. The three ratio tokens (`headings` 1.2 / `body` 1.5 / `tight` 1) are **unchanged** and remain the semantic tier for consumers.
59
+ - **Changed (visual) — components re-based onto snapped pairs:** the `.typography-hf-*` composites and `.hf-btn` / `.hf-textarea` / `.hf-alert` (`__title` / `__text`) / `.hf-dropdown-menu__item` / `.hf-section-header` (`__title` / `__description`) move off the ratio tokens (and off stray `1.3` / `1.4` literals) onto the per-size pairs, so their resolved line-heights now land on the 4px grid — e.g. 14px body text 21→20px, the 18px alert title 23.4→28px. The `tight` (1) usages (pills / tabs / counts / breadcrumb — explicit geometry) stay as-is. Ratio tokens are untouched.
60
+ - **Validator:** new **check 11** — every `fontSize.{name}` has a matching on-grid px `lineHeight.{name}` pair, and every `typography.*` composite references an on-grid px pair. Base channel only (the `[data-theme="web"]` clamp() overrides are out of scope). Closes #12.
61
+ - **ai-rules:** new **explicit-control-heights** rule — set a control's height directly (`height` / `--hf-btn-h` / `--hf-field-h`), never derive it from `py-*` padding (padding-emergent heights inherit line-height drift — the ui-hf 47px-input case), added across `CLAUDE.md` + the three compact rule files, alongside the per-size line-height documentation.
62
+
63
+ **Consumer-feedback fixes — ui-hf integration wave 4**
64
+
65
+ - **New:** `base.css` export — an opt-in base layer shipping the DS's ONLY element selector, a `body` rule setting the canonical font-family + grayscale font smoothing (`-webkit-font-smoothing: antialiased` / `-moz-osx-font-smoothing: grayscale`). macOS's default subpixel AA visually thickens Roboto 500/600 versus the Figma source (which renders with grayscale AA) — this is the deliberate, documented exception to the `.hf-*` namespace-hygiene contract (issue #10): a separate opt-in import, never pulled in by `tokens.css`/`components.css`. Trade-off: low-DPI (1x) screens render small text slightly lighter — accepted cost. New `--target=base-css` generator target (fallback font-family derived from the `fontFamily.primary` token, never hardcoded) + `build:tokens:base` script + `./base.css` export. Closes #14.
66
+ - **Docs — consolidated typography bootstrap:** README's Tailwind + vanilla recipes now show the optional `base.css` import, and a new README subsection ties `base.css`, the Google Fonts weights link (400/500/600), and the fallback-stack note (shipped since 0.8.0) into one canonical setup instead of three separate discoveries. `UI_DESIGN.md` §2 records the canon decision + rationale + low-DPI trade-off; `ADOPTION.md` notes `base.css` as opt-in with a migration line for ui-hf's local `@layer base` block (added 2026-07-10, referencing this issue); `components.html` applies the same body rule as the canonical rendering. `ai-rules/CLAUDE.md` + the three compact rule files point at `base.css` as the canonical bootstrap. Closes #14.
67
+ - **Validator:** `pre-built/base.css` added to check 1's var-resolution set and check 7's gzip budget (1024 B ceiling); also added to check 9's per-recipe consumer set — its single `var(--font-family-hf-primary, …)` reference resolves under both shipped recipes (the name is defined in both `tokens.css` and `tailwind.css`'s compat block). Closes #14.
68
+
69
+ ## v0.7.0 — Web/marketing theme + non-color theming axis (2026-06-17)
70
+
71
+ Extends the `[data-theme]` variation axis from color-only to the **non-color semantic tiers** (type / radius / elevation), and ships the first theme that uses it. **All changes are additive** (new theme, new build target, new validator check, new export) → minor bump. The portal default outputs (`tokens.css`, `components.css`, `tailwind.css`, the SCSS/TS/JS bundles) are **byte-identical** — only `themes.json` expands and `web.css` is new.
72
+
73
+ **The `[data-theme]` axis now spans non-color tiers**
74
+
75
+ - A theme was previously understood as ~15 themeable **color** semantic tokens. It now also covers `--font-size-hf-*`, `--radius-hf-*`, and `--shadow-hf-*` (and the other type tiers — weights, line-heights, font family). This works with **zero emitter changes**: the theme emitter already passed raw override values through unchanged (`themeDeclValue`), and `.hf-*` components already read `var(--font-size-hf-*)` / `var(--radius-hf-*)` / `var(--shadow-hf-*)`, so a `[data-theme]` block re-declaring those vars re-styles components automatically — no fork, no rebuild. Founder decision **D5** ([approval `8f6baafb`](#)).
76
+
77
+ **New theme — `web` (marketing rhythm)**
78
+
79
+ - `pre-built/web.css` (`./web.css`) — `[data-theme="web"]` for the marketing surface (`web-hf`, hotelfriend.com), emitted from a new `$theme-overrides.web` section in `tokens.figma.json` via a new `--target=web-css` (reuses the existing parameterized `emitThemeCss('web')` — the dark emitter, not a fork). It is the **same brand** as the portal — **zero color overrides** (accent `#24AFE8`, neutrals, Roboto unchanged) — and forks only the rhythm: larger fluid type (`clamp()` page/hero), rounder radius (8/12/16/24px), and softer layered shadows.
80
+ - `themes.json` auto-expands: `themes.web` appears and `themeable[]` broadens to the union of all themes' keys (14 → 28). No manual manifest edit.
81
+ - **web-hf's pricing-tier sub-brand stays LOCAL to web-hf** — the Core / Flex / Signature gold-and-navy palette is a one-product sub-brand, not a DS theme, and is deliberately **not** added here. Founder decision **D6** ([approval `dbf6f1f2`](#)).
82
+
83
+ **Validator check #8 — themeable allowlist gate**
84
+
85
+ - `scripts/validate-tokens.cjs` adds an 8th check that turns `themes.json`'s `themeable[]` from documentation into an **enforced contract boundary** (CONTRACT §3): for every theme (dark + web), each `$theme-override` key must be (a) a real defined `--*` var in `tokens.css`/`tailwind.css` (catches typos / nonexistent vars) and (b) within an allowed themeable semantic tier (`--color-hf-` semantic roles, `--font-size-hf-`, `--font-weight-hf-`, `--line-height-hf-`, `--font-hf-`/`--font-family`, `--radius-hf-`, `--shadow-hf-`). A primitive palette ramp like `--color-hf-blue-500` is rejected even though it shares the `--color-hf-` prefix; an out-of-scope tier like `--spacing-*` / `--z-*` is rejected. A theme may only touch themeable tiers.
86
+
87
+ **Docs & demo synced**
88
+
89
+ - `components.html`: a "web marketing rhythm" demo + page toggle next to the dark toggle, with the web overrides mirrored into the embedded plain-`<style>` block (per the file's token-duplication design) so the demo visibly shows rounder cards, softer shadows, and larger headings.
90
+ - `ADOPTION.md`: web-hf row's Theme? flipped to **yes (web)**; new "Web / marketing theme" subsection; D6 note that the pricing sub-brand stays local. `CONTRACT.md` §3: theme axis amended to span non-color tiers, recorded disposition for D5/D6, check #8 cited. `README.md`: `web.css` added to the exports list + a "Web/marketing theme" usage note.
91
+
92
+ ## v0.6.0 — Component wave 1: table, toast variants, empty states, a11y fixes (2026-06-12)
93
+
94
+ Closes the Blueprint §1 UI gaps — the recipes every dashboard page kept re-rolling by hand (tables, empty states) plus the accessibility holes left in the v0.4/v0.5 primitives. **All component changes are additive** (new classes, new selectors, no renames/removals) → minor bump. The only non-additive change is the AA contrast fix below (a token-value change, not a renamed/removed API), which adds one primitive (`blue.900`) and repoints the `on-accent` semantic to it.
95
+
96
+ **Visual change**
97
+
98
+ - **Primary-button (and other on-accent) text: white → `#0B2F46`** (AA fix, founder decision D1 / approval-d6a0b05d). The brand accent `#24AFE8` fails WCAG AA with white text (2.51:1); dark `#0B2F46` passes (5.55:1 on accent, 13.91:1 on white). Implemented as a new `blue.900` primitive with the `on-accent` semantic repointed to it — so every text-on-accent surface (`.hf-btn--primary`, `bg-primary` + `text-hf-on-accent` utilities) flips automatically. White-on-error (`.hf-btn--danger`), the switch knob, checkbox/radio indicators, and the multi-variant alert icon/banner deliberately stay white (rebound to `--color-hf-white`).
99
+
100
+ **New primitives** (`src/components.css` → `pre-built/components.css`)
101
+
102
+ - `.hf-table` — style a plain `<table>` via element selectors (thead/th/td): `thead` = `--color-hf-bg-section`, uppercase steel headers, 1px `--color-hf-border-subtle` row dividers, warm row hover `--color-hf-menu-bg-hover` (the legacy portal `$table__hover`, promoted in v0.4 precisely for this). Helpers `.hf-table__num` (right-align) and `--static` (no hover). Replaces the per-page hand-rolled table recipe.
103
+ - `.hf-empty` + `__icon` / `__title` / `__text` / `__action`, modifiers `--compact` (in-card) and `--row` (inside an `.hf-table` colspan cell). Promotes the three `components.html` empty-state recipes into shipped CSS. The documented usage rule (empty when the request **succeeds with no results**; skeleton while **in flight**) is kept as a CSS comment.
104
+
105
+ **`.hf-toast` semantic variants**
106
+
107
+ - `.hf-toast--success` / `--error` / `--warn` / `--info`, bound to the `--color-hf-status-*` families and structured like `.hf-alert--*`: the modifier sets the accent color (tinting the `__icon` via `currentColor`) and adds a 3px left edge; message text stays `--color-hf-fg`. This replaces the demo-local `text-ok`/`text-bad` utilities that faked toast states.
108
+
109
+ **Accessibility**
110
+
111
+ - **Standalone invalid inputs.** `[aria-invalid="true"]` on `.hf-input` / `.hf-textarea` / `.hf-select` (plus alias classes `.hf-input--error` etc.) now apply the same red border + red focus ring that the `.hf-form-field--error` parent applied to child controls — so framework form wrappers (Angular) can flag a control without the `.hf-form-field` wrapper.
112
+ - **Missing `:focus-visible`.** Added the canonical focus outline to `.hf-tab`, `.hf-pill-tab`, `.hf-pagination__item`, `.hf-check`, `.hf-radio` (these two use `appearance:none`, which removed the native ring with no replacement), `.hf-pill--dd`, and the four close buttons (`.hf-alert__close` / `.hf-toast__close` / `.hf-modal__close` / `.hf-drawer__close`). The canonical convention is now documented as the **outline** style already shipped on `.hf-btn` (`outline: 2px solid var(--color-hf-accent)` + `outline-offset: 2px`); `states-canonical.json` `_meta.focusRingRule` was corrected to match (it previously prescribed a box-shadow ring, contradicting the shipped CSS — inputs legitimately keep their box-shadow ring).
113
+ - **Reduced motion.** One `@media (prefers-reduced-motion: reduce)` block stops the infinite `hf-shimmer` (skeleton keeps a flat muted block) and `hf-spin` animations, and neutralizes the drawer slide-in + its transitions.
114
+
115
+ **Docs synced with reality**
116
+
117
+ - `components.html`: new `#table` section + nav anchor, toast variant demos (replacing the `text-ok`/`text-bad` hack), the `#empty` recipes switched to `.hf-empty`, a standalone `aria-invalid` input example, and the new CSS mirrored into the embedded `@layer components` block (per the file's token-duplication design).
118
+ - `README.md` component table + usage snippets (table, empty, toast variants, standalone invalid); `UI_DESIGN.md` §5.1 entries for `.hf-table` / `.hf-empty` + toast-variant / invalid-input / reduced-motion notes.
119
+
120
+ **New build targets / exports**
121
+
122
+ - `pre-built/dark.css` (`./dark.css`) — `[data-theme="dark"]` override of the ~15 semantic tokens, emitted from a new `$theme-overrides.dark` section in `tokens.figma.json` (var() chains to primitives — theming stays alias-based, not inlined hex). Lifts the ADOPTION.md dark template into a shipped artifact.
123
+ - `pre-built/utilities.css` (`./utilities.css`) — atomic utility classes (`.bg-hf-*`, `.text-hf-*`, `.border-hf-*`, `.shadow-hf-*`) for the semantic tier, for non-Tailwind (Angular/SCSS) consumers.
124
+ - `pre-built/_ionic.scss` (`./_ionic.scss`) — Ionic theme partial mapping `--ion-color-*` to DS values with COMPUTED `-rgb`/`-contrast`/`-shade`/`-tint` (replaces the hand-maintained snippet + "use the Ionic color generator" caveat in INTEGRATION-angular.md). Unblocks concierge.
125
+ - `pre-built/themes.json` (`./themes.json`) — machine-readable variation contract (`themeable` var list + per-theme override maps); the validation mechanism for the theming model. The concierge theme (founder decision D4) is added during its adoption.
126
+
127
+ The generator gained four targets: `dark-css`, `utilities-css`, `ionic-scss`, `themes-manifest`.
128
+
129
+ **Governance & docs**
130
+
131
+ - **`CONTRACT.md` (new, repo root)** — the normative design-system contract: versioning &
132
+ breaking-change policy, the governed variation model (density / theme / brand), error-UX mapping,
133
+ the accessibility responsibility split, i18n posture (LTR-only decision), performance budgets,
134
+ the consumer build pipeline, Figma/token governance, the stack matrix + proposed default, and the
135
+ proposal process. Consolidates the previously-scattered rules into one home, cross-linked, and
136
+ records founder decisions D1–D4 (approvals, 2026-06-12). It is repo-first (not in the package
137
+ `files` list); consumers get the distilled subset via `ai-rules/`.
138
+ - **Accessibility required-markup block in `ai-rules/*`** — added the per-component ARIA/keyboard
139
+ checklist (modal/drawer dialog semantics + focus trap + Escape, tabs/dropdown roles + arrow keys,
140
+ native switch/checkbox/radio, icon-only `aria-label`, pagination `<nav>`, toast `aria-live`,
141
+ table `<th scope>`) to `ai-rules/CLAUDE.md`, with a distilled version mirrored into
142
+ `system-prompt-compact.md`, `cursorrules.template`, and `github-copilot-instructions.md`. The DS
143
+ stays CSS-only (founder decision D2), so the JS behaviors are documented as consumer
144
+ responsibilities.
145
+ - **`AGENTS.md` (new, thin)** — entrypoint pointing agents at `CLAUDE.md` (repo work), `ai-rules/`
146
+ (consumer work), and `CONTRACT.md` (the contract); also the ADev `which='agents'` entrypoint.
147
+ - **`ROADMAP.md`** — added a "Standing decisions" section (LTR-only i18n posture; the D2 CSS-only
148
+ interaction-layer decision); `README.md` gained a top-level "Contract" section linking
149
+ `CONTRACT.md`.
150
+
151
+ ## v0.5.0 — Cross-project adoption: density mode, brand reconciliation, Angular path (2026-06-03)
152
+
153
+ Acts on a read-only audit of 4 sibling frontends ([`AUDIT-RESULTS.md`](AUDIT-RESULTS.md): `pos`, `marketplace-web-hf`, `web-hf`, `concierge-app`). Verdict: the brand is **shared** across pos/web-hf/marketplace (one design language built three times by hand; only the `#24AFE8` vs legacy `#26ADE4` accent differs), while `concierge-app` is the outlier. 3 of 4 are Angular/SCSS; `pos` is the touch consumer. All changes additive / backwards-compatible → minor bump.
154
+
155
+ **Density / touch mode (new)**
156
+ - Buttons and form controls now read height from a `[data-density]` scope: default desktop (40/32/39 — unchanged), `comfortable` (44), `touch` (48 + 44px tap). Vars `--hf-btn-h` / `--hf-btn-h-sm` / `--hf-field-h` in `components.css`; set `data-density="touch"` on `<html>` or any container. POS/mobile get larger controls with no fork and no per-component change.
157
+
158
+ **Bug fix — v3 Tailwind preset emitted unresolved alias strings**
159
+ - `pre-built/tailwind.preset.js` shipped semantic colors as literal `"{primitive.color.blue.500}"` refs (34 of them), so `bg-accent` / `text-fg` / `bg-status-*` were dead utilities. `emitTailwind()` now resolves refs inline (like the scss/ts emitters) → `accent` = `#24AFE8`, `fg` = `#2B2B2B`, etc. Only primitives worked before; the whole semantic layer of the v3 preset is now functional.
160
+
161
+ **Brand reconciliation**
162
+ - `#26ADE4` formally recorded as a recognized legacy alias of canonical `#24AFE8` in `tokens.figma.json`; find-replace codemod documented in `ADOPTION.md`.
163
+
164
+ **New docs**
165
+ - [`ADOPTION.md`](ADOPTION.md) — per-project adoption tiers + Brand reconciliation, Density, Theme-override mechanism (CSS-var map incl. a dark template), and the `.hf-*` namespace contract + shadowing checklist (web-hf/marketplace already define `.hf-modal`/`.hf-card` locally).
166
+ - [`INTEGRATION-angular.md`](INTEGRATION-angular.md) — Angular/SCSS consumption (CSS-vars + `_tokens.scss` routes) and an Ionic `--ion-color-*` mapping for concierge.
167
+ - `ROADMAP.md` #9 (cross-project adoption) + `components.html` density demo.
168
+
169
+ ## v0.4.0 — Component library expansion + bug fixes (2026-06-02)
170
+
171
+ Driven by an integration audit ([`FEEDBACK.md`](FEEDBACK.md)): tokens and the heavy primitives were production-ready, but the forms side was empty (no buttons, inputs, toggle, field wrapper) so every consumer re-rolled the same utility recipes. This release ships those primitives and fixes two reported bugs. **All changes are additive / backwards-compatible** (the dropdown rename keeps the old class names as aliases) → minor bump.
172
+
173
+ **Bug fixes**
174
+
175
+ - **stylelint `box-shadow` rule was dead code.** `pre-built/stylelint-design-system.cjs` declared `declaration-property-value-disallowed-list` twice in one object; the second (Tailwind arbitrary-hex) silently overwrote the first (box-shadow). Merged into a single rule (severity `error`) so both fire. Added a structural regression guard as **check 4** in `scripts/validate-tokens.cjs`.
176
+ - **Dropdown API ↔ docs mismatch.** `ai-rules/*` promised BEM children (`.hf-dropdown-menu__item` …) but the CSS shipped flat names (`.hf-dropdown-item` …), so docs-following code silently lost hover/focus/danger styles. Resolved both ways (see below).
177
+
178
+ **New component primitives** (`src/components.css` → `pre-built/components.css`)
179
+
180
+ - `.hf-btn` + `--primary` / `--danger` / `--outline-primary` / `--outline-default` / `--cancel`, sizes `--sm` / `--icon` / `--block`. Consolidates 25+ portal button recipes.
181
+ - `.hf-input` / `.hf-textarea` / `.hf-select` (+ `.hf-select-wrap` for a token-colored caret) with an accessible accent focus ring.
182
+ - `.hf-switch` — token-driven toggle (styled `<input type="checkbox">`).
183
+ - `.hf-form-field` + `__label` / `__hint` / `__error` (`--required`, `--error`).
184
+ - `.hf-card` + `__header` / `__title` / `__description` / `__body` / `__footer` (`--flat`).
185
+ - `.hf-drawer` + `__backdrop` / `__panel` / `__header` / `__body` / `__footer` (`--left`).
186
+
187
+ **Dropdown — renamed to BEM, aliases kept**
188
+
189
+ - Canonical: `.hf-dropdown-menu__header` / `__item` / `__item-icon` / `__shortcut` / `__divider` and `__item--danger` (consistent with `.hf-modal__*` / `.hf-alert__*`).
190
+ - The pre-0.4 flat names (`.hf-dropdown-header` / `-item` / `-divider`, `.hf-dropdown-item__icon` / `__shortcut`, `.hf-dropdown-item.danger`) remain as **deprecated aliases** — removal planned for v1.0.
191
+
192
+ **New tokens** (`tokens.figma.json`)
193
+
194
+ | Token | Value | Used by |
195
+ |---|---|---|
196
+ | `primitive.color.red.800` | `#C9302C` | darkest red |
197
+ | `--color-hf-status-error-strong-hover` | `var(--color-hf-red-800)` | `.hf-btn--danger:hover` |
198
+ | `--color-hf-bg-inverse` | `var(--color-hf-gray-950)` (#252F4A) | dark surfaces — bulk-action bar, tooltip, sticky bar (Tailwind utility `bg-hf-bg-inverse`) |
199
+
200
+ **Docs synced with reality**
201
+
202
+ - All `ai-rules/*` (CLAUDE / Copilot / Cursor / compact), `README.md`, and `UI_DESIGN.md` §5.1/§6 updated: new primitives listed, button references changed from the non-existent `.btn-*` to `.hf-btn--*`, `.form-control` → `.hf-input`, dropdown children → canonical BEM, and the stale `--color-badge-*` status-token references corrected to the real `--color-hf-status-{role}` + `status-map.json` mechanism.
203
+
204
+ **Still open** (tracked in [`ROADMAP.md`](ROADMAP.md)): `.hf-table`, `dark.css` export, `utilities.css` for non-Tailwind consumers, `.hf-avatar` / `.hf-stat-tile` / `.hf-pill--sm`, `status-loyalty-*`, DatePicker.
205
+
206
+ ## Phase 3 — Distribution + governance scaffolding (2026-05-21)
207
+
208
+ Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
209
+
210
+ - **Phase 3A** — `components.html` Phase 2D reconciliation closed. Embedded `@layer components` stays standalone (Tailwind v4 browser CDN doesn't support nested `@import`). The @theme block already mirrors `pre-built/tailwind.css` (canonical hf-prefixed) + legacy aliases for demo continuity.
211
+ - **Phase 3B** — `pre-built/stylelint-design-system.cjs` ships as a shareable Stylelint config. Forbids raw hex, named CSS colors, literal `box-shadow` declarations, and arbitrary-value Tailwind hex. Consumer projects extend it in their `.stylelintrc.cjs`.
212
+ - **Phase 3C** — `scripts/pre-commit.sh` ships as a Git pre-commit hook scaffold (husky-compatible). Runs `validate-tokens.cjs` when bundle files are staged. Skips silently when no bundle changes are present.
213
+ - **Phase 3D** — `package.json` defines the npm package skeleton:
214
+ - Exports map for every generated file + ai-rules + JSON sources
215
+ - `scripts.build` chains all generator targets
216
+ - `scripts.validate` runs the drift detector
217
+ - `scripts.prepublishOnly` re-builds + validates before publish
218
+
219
+ ## Phase 2 — Component-tier + status externalization + verification (2026-05-21)
220
+
221
+ Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases — all shipped.
222
+
223
+ **Phase 2A — Status mapping externalized (`status-map.json`)**
224
+
225
+ The domain→semantic-status mapping (which `.status-booking-confirmed` resolves to → semantic `info`, etc.) lived as a hardcoded block in `components.css`. That re-introduced the drift RFC-0001 §5b warned about. Now:
226
+
227
+ - **New file:** `status-map.json` — JSON dictionary `{domain.state}` → `semantic-status-name`
228
+ - **New target:** `node generate-tokens.cjs --target=status-css > pre-built/status.css` emits the `.status-{domain}-{state}` CSS rules with `var(--color-hf-status-*)` references
229
+ - **`pre-built/components.css`** — the `.status-*` block REMOVED; now you `@import` both files
230
+ - Adding a new status is JSON-only — no CSS edit
231
+
232
+ **Phase 2B — 4 component-tier tokens promoted**
233
+
234
+ Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
235
+
236
+ | Token | Value | Used by |
237
+ |---|---|---|
238
+ | `--color-hf-tab-fg-inactive` | `var(--color-hf-gray-700)` (#485B78) | `.hf-tab` default, `.hf-pill-tab` |
239
+ | `--color-hf-tab-count-fg` | `var(--color-hf-gray-700)` | `.hf-tab__count` (inactive tab) |
240
+ | `--color-hf-pagination-fg-inactive` | `var(--color-hf-gray-700)` | `.hf-pagination__item` default |
241
+ | `--color-hf-pagination-fg-active` | `var(--color-hf-gray-950)` (#252F4A) | `.hf-pagination__item.is-active` |
242
+ | `--color-hf-input-border` | `#DBDFE9` (raw — between gray-200 and gray-300) | `.hf-check`, `.hf-radio` |
243
+ | `--color-hf-menu-bg-hover` | `#F5F5F5` (raw — portal `$table__hover`) | `.hf-dropdown-item:hover/:focus-visible` |
244
+
245
+ Naming convention: `{component}.{slot}` under `semantic.color` in `tokens.figma.json`. Generator emits `--color-hf-{component}-{slot}` which gives Tailwind utilities `bg-hf-tab-fg-inactive` etc.
246
+
247
+ **Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
248
+
249
+ Drift-detection script. Three checks:
250
+ 1. Every `var(--*)` in `pre-built/*.css` resolves to a defined CSS variable
251
+ 2. No bare hex literals in `components.css` / `status.css` (outside comments + `var()` fallbacks; `#fff`/`#000` allowed)
252
+ 3. Every token reference in doc code-blocks exists in `pre-built/*` (anti-RFC-0001 §2.2 drift)
253
+
254
+ Exits 1 on any failure — suitable for pre-commit hook or CI step.
255
+
256
+ **Phase 2D — `components.html` reconciled with Phase 1B+2 tokens**
257
+
258
+ The embedded `@theme {}` block now mirrors `pre-built/tailwind.css` (canonical hf-prefixed three-tier model) + adds legacy aliases for the demo's existing class refs. Result: BOTH `bg-primary` AND `bg-hf-accent` resolve to `#24AFE8` in the demo.
259
+
260
+ **What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
261
+
262
+ - ✅ ~~Fully *generate* `pre-built/components.css` from sources~~ — done 2026-05-27 (ROADMAP #4): source moved to `src/components.css`, `--target=components-css` added to the generator, drift gate covers regeneration.
263
+ - Rewrite `components.html` demo HTML to use the new prefixed class refs (`bg-hf-accent` everywhere) and drop legacy aliases entirely
264
+ - Add ESLint/Stylelint plugin (RFC-0001 §4.6 forbid raw hex)
265
+ - ✅ ~~Publish as `@hotelfriendag/design-tokens` npm package~~ — done 2026-05-27 (ROADMAP #2): public on npmjs.com via Trusted Publishing.
266
+
267
+ ## Phase 1B — RFC-0002 semantic tier (2026-05-21)
268
+
269
+ Introduces the three-tier model: **primitive → semantic → (Phase 2) component**. Per RFC-0002. App code should now reference SEMANTIC tokens (`var(--color-hf-accent)`, `var(--color-hf-fg)`, etc.) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail. Themes swap the semantic layer without touching primitives.
270
+
271
+ **Breaking changes (consumers must update):**
272
+
273
+ | Phase 1A name | Phase 1B replacement |
274
+ |---|---|
275
+ | `--color-hf-primary*` | `--color-hf-accent` / `-hover` / `-subtle` / `-subtler` |
276
+ | `--color-hf-text-primary` | `--color-hf-fg` |
277
+ | `--color-hf-text-secondary` | `--color-hf-fg-muted` |
278
+ | `--color-hf-text-useful` | `--color-hf-fg-subtle` |
279
+ | `--color-hf-text-placeholder` | `--color-hf-fg-faint` |
280
+ | `--color-hf-neutral-light-grey` | `--color-hf-bg-page` |
281
+ | `--color-hf-neutral-bg-accent` | `--color-hf-bg-section` |
282
+ | `--color-hf-neutral-very-light` | `--color-hf-bg-muted` |
283
+ | `--color-hf-neutral-border` | `--color-hf-border` |
284
+ | `--color-hf-neutral-light-blue-gray` | `--color-hf-border-subtle` |
285
+ | `--color-hf-neutral-border-hover` | `--color-hf-border-strong` |
286
+ | `--color-hf-status-success` etc. | unchanged ✓ (already canonical) |
287
+ | `--color-hf-badge-{domain}-{state}-*` | **removed** — replaced by status-map |
288
+
289
+ **New: SEMANTIC status colors** (replace per-domain `badge.*` tokens):
290
+
291
+ ```
292
+ --color-hf-status-success / -bg / -strong
293
+ --color-hf-status-warning / -bg / -strong
294
+ --color-hf-status-error / -bg / -strong / -alt
295
+ --color-hf-status-info / -bg
296
+ --color-hf-status-cancel / -bg
297
+ --color-hf-status-coral / -bg (portal-specific)
298
+ --color-hf-status-violet / -bg (portal-specific)
299
+ --color-hf-status-olive / -bg (portal-specific)
300
+ --color-hf-status-orange-deep / -bg (portal-specific)
301
+ ```
302
+
303
+ **New: tier-aware generator** (`generate-tokens.cjs`):
304
+ - Recognises `primitive.*` and `semantic.*` top-level groups in `tokens.figma.json`
305
+ - Resolves Tokens Studio `{path.to.token}` references — CSS targets emit `var()` chain (so theming works at runtime), SCSS/TS/JS inline the resolved value
306
+ - All existing `--prefix=` / `--target=` flags unchanged
307
+
308
+ ## Phase 1A — RFC-0001 collision-safe prefixing (2026-05-21)
309
+
310
+ Solves the biggest RFC-0001 critique (§2.1, §4.2) — namespace collisions with Tailwind v4 defaults that silently resized utilities used thousands of times in `ui-hf`.
311
+
312
+ **Breaking change for `ui-hf` and any other consumer:** all token names now carry the `hf-` prefix INSIDE the category segment.
313
+
314
+ - `--color-primary` → `--color-hf-primary` (Tailwind utility: `bg-hf-primary`)
315
+ - `--text-base` → `--text-hf-base` (utility: `text-hf-base` = 14px; Tailwind's default `text-base` keeps 16px — no more silent override)
316
+ - `--radius-sm` → `--radius-hf-sm` (utility: `rounded-hf-sm` = 6px; default `rounded-sm` stays 2px)
317
+ - `--spacing-7` → `--spacing-hf-7` (utility: `p-hf-7` = 30px; default `p-7` stays 28px)
318
+ - `--font-sans` → `--font-hf-sans` (utility: `font-hf-sans` = Roboto)
319
+ - `--shadow-modal` → `--shadow-hf-modal` (utility: `shadow-hf-modal`)
320
+
321
+ **Why the prefix goes INSIDE the category** (e.g. `--color-hf-primary`, not `--hf-color-primary`):
322
+ Tailwind v4 only generates utility classes from CSS variables that start with its known utility-prefixes (`--color-*`, `--text-*`, `--spacing-*`, `--radius-*`, `--shadow-*`, `--font-*`, etc.). A variable like `--hf-color-primary` would NOT generate any utility. The current convention preserves the utility-class derivation while keeping the namespace conflict-free.
323
+
324
+ ## Phase 0 — RFC-0001 quick wins (2026-05-21)
325
+
326
+ - **ESM compatibility** — renamed `generate-tokens.js` → `generate-tokens.cjs` so the script runs under Node in `"type":"module"` projects (Vite/Vue 3/Next).
327
+ - **Token name fix** — generator strips trailing `default`/`DEFAULT` so `color.primary.default` emits `--color-primary` (utility `bg-primary`), not `--color-primary-default`.
328
+ - **Shadow token namespace** — JSON group `boxShadow` → `shadow` so emit is `--shadow-modal` (was `--box-shadow-modal`).
329
+ - **`components.css` — RFC §5 conformance:** no hardcoded hex (all via `var()` with fallbacks), one namespace (dropped parallel `:root { --status-* }`), class names match token paths (`.status-clean-*` → `.status-room-item-cleaning-*`, `.status-fd-*` → `.status-front-desk-*`, `.status-order-action` → `.status-order-action-required`).
@@ -0,0 +1,142 @@
1
+ # Integration playbook — `ui-hf` (first consumer)
2
+
3
+ This is the concrete step-by-step record of how **ROADMAP #1** (first consumer integration) was satisfied with `ui-hf` (Vue 3 + Tailwind v4) as the first consumer.
4
+
5
+ The goal of #1 is to prove the system end-to-end: a token change in this repo propagates into a real project, and AI agents in that project pick up the same rules. The mechanism is the **npm package `@hotelfriendag/design-tokens`** (public on npmjs.com — see ROADMAP #2). The earlier git-submodule approach is kept as a fallback at the bottom for consumers that can't pull from npm.
6
+
7
+ > **Status: ✅ done.** `ui-hf` consumes the package via `pnpm`, its CSS imports resolve from `node_modules`, and a production build carries the DS `.hf-*` classes + `--color-hf-*` tokens (DS values override the legacy project palette).
8
+
9
+ > All commands marked `[in ui-hf]` are run in the `ui-hf` checkout, NOT here.
10
+ > Commands marked `[here]` are run in `hf-design-system`.
11
+
12
+ ## Prerequisites
13
+
14
+ - `@hotelfriendag/design-tokens` is published to npmjs.com (verified: `0.3.3` is `latest`).
15
+ - You have a feature branch in `ui-hf`.
16
+ - `npm run build && npm run validate` is green here (CI also enforces this).
17
+
18
+ ## Step 1 — Add the dependency
19
+
20
+ ```bash
21
+ # [in ui-hf]
22
+ pnpm add @hotelfriendag/design-tokens
23
+ ```
24
+
25
+ Result: the package is a normal dependency in `package.json` + `pnpm-lock.yaml`. No `.npmrc`, no auth — it's a public package.
26
+
27
+ > pnpm may add the freshly-published version to `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` (its "don't auto-install very new releases" guard). That entry is harmless and can stay.
28
+
29
+ ## Step 2 — Wire the CSS
30
+
31
+ In `ui-hf`'s main CSS entry (`src/assets/tailwind.css`, where `@import "tailwindcss"` lives), import the three DS files by package specifier:
32
+
33
+ ```css
34
+ @import "tailwindcss";
35
+
36
+ @import "@hotelfriendag/design-tokens/tailwind.css"; /* @theme tokens with hf- prefix */
37
+ @import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
38
+ @import "@hotelfriendag/design-tokens/status.css"; /* .status-{domain}-{state} */
39
+ ```
40
+
41
+ **Import order matters.** Put the DS imports **after** the project's own `@theme {}` block so DS values win any `--color-hf-*` collision (e.g. `--color-hf-blue-500` resolves to the canonical DS `#24AFE8`, not a legacy project value). Project-only tokens the DS doesn't define stay intact.
42
+
43
+ **Two notes:**
44
+ - Import `tailwind.css` — it is the only Tailwind v4 token file the package ships (the `hf-` prefix makes every token additive, so no separate `additive` variant exists).
45
+ - No `@source not` exclusions are needed. The package ships demo HTML (`components.html`, `portal-audit.html`) but it lives under `node_modules`, which Tailwind v4 doesn't scan — so its legacy `bg-[#26ADE4]` swatches can't leak into the app bundle.
46
+
47
+ ## Step 3 — Wire AI rules
48
+
49
+ The canonical DS rules ship inside the package at `node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md`. Two ways to surface them to agents:
50
+
51
+ - **Reference (lightweight):** if the consumer already has its own root `CLAUDE.md` (as `ui-hf` does), add a short "Design System — external SSOT" section that points agents at the package rules and restates the hard invariants (no bare hex, no new component CSS, status badges via `.hf-pill .status-*`, tokens read-only). This is what `ui-hf` does.
52
+ - **Copy (fresh project):** `cp node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md CLAUDE.md`. Re-copy after a version bump (extend the `design-system:update` script).
53
+
54
+ For Cursor / Copilot, mirror with `ai-rules/cursorrules.template` → `.cursorrules` and `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`.
55
+
56
+ ## Step 4 — Render one component to prove it works
57
+
58
+ Pick any view in `ui-hf` and drop a single button + a pill:
59
+
60
+ ```vue
61
+ <button class="bg-hf-accent hover:bg-hf-accent-hover text-white h-10 px-5 rounded-hf-sm text-hf-base font-semibold">
62
+ Save
63
+ </button>
64
+ <span class="hf-pill status-booking-confirmed">Confirmed</span>
65
+ ```
66
+
67
+ Run the dev server. Verify in DevTools:
68
+
69
+ - Button background computes to `rgb(36, 175, 232)` ≡ `#24AFE8`.
70
+ - Button text size is **14px** (not Tailwind's default 16px).
71
+ - Button corner radius is **6px** (not Tailwind's default 2px).
72
+ - Pill renders with brand-blue tint background and matching text/border.
73
+
74
+ A quick build-level check (what was used to verify this integration): after `pnpm build:fast`, the main CSS bundle (the one referenced by `dist/index.html`) contains `.hf-pill`, `status-booking-*`, and `--color-hf-accent`, and `--color-hf-blue-500` resolves to `#24afe8` (the DS value, confirming the cascade order).
75
+
76
+ ## Step 5 — Smoke-test propagation (the actual "proves the point" check)
77
+
78
+ ```bash
79
+ # [here] change one token + publish
80
+ # edit tokens.figma.json: bump primitive.color.blue.500 to a noticeable value (e.g. #FF0000)
81
+ npm run build
82
+ npm version patch && git push --follow-tags # OIDC release publishes the new version
83
+ ```
84
+
85
+ ```bash
86
+ # [in ui-hf]
87
+ pnpm design-system:update # bumps to the new published version
88
+ # Rebuild ui-hf, reload the page — the button is now red.
89
+ ```
90
+
91
+ Then revert the temp token change here, publish again, and `pnpm design-system:update` once more. The button goes blue again. That confirms the propagation loop is live.
92
+
93
+ > A faster sanity check that doesn't require touching tokens: clone/checkout this repo and run `npm install && npm run smoke`. Green means the bundle rebuilds reproducibly, the validator passes, every `exports`-mapped file exists, and the headline `--color-hf-*` / `.hf-*` symbols are present.
94
+
95
+ ## Step 6 — Lock the update workflow
96
+
97
+ `ui-hf`'s `package.json` has:
98
+
99
+ ```json
100
+ {
101
+ "scripts": {
102
+ "design-system:update": "pnpm update @hotelfriendag/design-tokens && pnpm ls @hotelfriendag/design-tokens"
103
+ }
104
+ }
105
+ ```
106
+
107
+ Anyone on the team runs `pnpm design-system:update` to pull the latest published tokens. Commit-message convention: `chore: bump design-system to <version>`.
108
+
109
+ > If you went with the **copy** path for AI rules in Step 3, extend the script:
110
+ > `... && cp node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md CLAUDE.md && git add CLAUDE.md`
111
+
112
+ ## Step 7 — Setup-PR
113
+
114
+ PR title: `feat: adopt @hotelfriendag/design-tokens`
115
+
116
+ Body checklist (copy into the PR description):
117
+
118
+ - [ ] `@hotelfriendag/design-tokens` added to `package.json` dependencies (+ lockfile).
119
+ - [ ] Main CSS entry imports `tailwind.css` + `components.css` + `status.css` by package specifier, after the project `@theme` block.
120
+ - [ ] Root `CLAUDE.md` references the package rules (or `.cursorrules` / `.github/copilot-instructions.md` mirror them).
121
+ - [ ] One demo component renders with `bg-hf-accent` resolving to `#24AFE8` (or the build-bundle check in Step 4 passes).
122
+ - [ ] Propagation smoke-test passed (Step 5).
123
+ - [ ] `package.json` has the `pnpm`-based `design-system:update` script.
124
+ - [ ] No bare `#hex` in new/changed code — all colors go through `var(--color-hf-*)` or `bg-hf-*` utilities.
125
+
126
+ That checklist IS the acceptance criterion for ROADMAP #1.
127
+
128
+ ## What this unlocks
129
+
130
+ With #1 green, ROADMAP #3 (cleanup of `backend-hf/docs/portable-design/`) becomes safe — there's a working consumer pulling from the package to fall back on if the duplicate is removed too eagerly.
131
+
132
+ ## Appendix — git submodule fallback (legacy)
133
+
134
+ Before #2 (npm publish) landed, the planned mechanism was a git submodule. It still works for consumers that can't reach npmjs.com:
135
+
136
+ ```bash
137
+ # [in ui-hf]
138
+ git submodule add git@github.com:HotelFriendAG/design-system.git design-system
139
+ git submodule update --init --recursive
140
+ ```
141
+
142
+ Then import via relative paths (`@import "../../design-system/pre-built/tailwind.css"`), add `@source not` exclusions for the demo HTML (which now sits inside the checkout, not node_modules), and update with `git submodule update --remote design-system`. The npm path above is preferred — zero setup, version-pinned, no `.gitmodules` to maintain.