@hotelfriendag/design-tokens 0.3.2 → 0.3.4

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/README.md CHANGED
@@ -1,574 +1,243 @@
1
1
  # HotelFriend Design System
2
2
 
3
- Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules.
3
+ Cross-project design foundation: tokens, generated outputs for every stack (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), the `.hf-*` component layer, and AI-tool rules. Distributed as the public npm package **`@hotelfriendag/design-tokens`**.
4
4
 
5
- > **Extracted on 2026-05-25** from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to make this the single source of truth that other HotelFriend projects consume.
5
+ > **Source repo:** `HotelFriendAG/design-system` (private). Extracted on 2026-05-25 from [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) `docs/portable-design/` to be the single source of truth other HotelFriend projects consume.
6
6
  >
7
- > **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe prefixing + drift CI). Versioned package distribution (`@hotelfriendag/design-tokens` via GitHub Packages) is the next step see `RFC-0001-cross-project-design-system.md` §9 for the full status checklist.
7
+ > **Status:** RFC-0001 Phase 1 complete (semantic three-tier model + collision-safe `hf-` prefix + drift CI). Published on npmjs.com via Trusted Publishing. See [`ROADMAP.md`](ROADMAP.md) for remaining items and [`CHANGELOG.md`](CHANGELOG.md) for phase history.
8
8
 
9
- ## File hierarchy (read in this order)
10
-
11
- ```
12
- portable-design/
13
-
14
- ├── components.html ← PRIMARY · canonical visual reference (open in browser)
15
- ├── UI_DESIGN.md ← NARRATIVE · rationale, anatomy, decisions (AI reads this first)
16
- ├── tokens.figma.json ← TOKENS · atomic Tokens Studio export → feeds Figma + generators
17
-
18
- ├── pre-built/ ← GENERATED · drop one into your stack's build pipeline
19
- │ ├── tailwind.css · Tailwind v4 @theme block (recommended)
20
- │ ├── tailwind.preset.js · Tailwind v3 preset (legacy)
21
- │ ├── tokens.css · vanilla CSS custom properties
22
- │ ├── _tokens.scss · SCSS variables
23
- │ ├── tokens.ts · TypeScript const
24
- │ ├── shadcn-tokens.css · shadcn/ui contract
25
- │ └── components.css · .hf-* component primitives (extracted from components.html)
26
-
27
- ├── states-canonical.json ← curated interactive states for new components (use this)
28
- ├── states.json ← raw portal extraction (160 KB — prefer states-canonical.json)
29
- ├── generate-tokens.cjs ← Node script (no deps) — turns tokens into Tailwind/CSS/SCSS/TS/shadcn
30
-
31
- ├── ai-rules/ ← drop ONE into the new project's root
32
- │ ├── CLAUDE.md · for Claude Code (autodetected)
33
- │ ├── cursorrules.template · rename to .cursorrules
34
- │ ├── github-copilot-instructions.md · put at .github/copilot-instructions.md
35
- │ └── system-prompt-compact.md · compact prompt for ChatGPT/v0/Lovable
36
-
37
- ├── portal-audit.html ← ARCHIVAL · legacy portal audit snapshot (NOT for new code)
38
- └── README.md ← you are here (quickstart)
39
- ```
40
-
41
- ### Precedence rule — when files disagree
42
-
43
- 1. **`components.html`** — canonical for **visual decisions** (colors, sizes, anatomy)
44
- 2. **`tokens.figma.json`** — canonical for **token values**. Regenerate `pre-built/*` after editing
45
- 3. **`UI_DESIGN.md`** — canonical for **WHY decisions were made** (history, trade-offs, portal drift notes)
46
- 4. **`pre-built/*`** — **generated**, never hand-edit. Re-run `generate-tokens.cjs` after JSON changes
47
- 5. **`portal-audit.html`** — **archival only**. Shows what the legacy portal currently looks like — useful for migration tracking, NOT for new product UI
48
-
49
- When `components.html` and `UI_DESIGN.md` disagree, **components.html wins** and `UI_DESIGN.md` is stale.
50
-
51
- ## 60-second quickstart
9
+ ## Install
52
10
 
53
11
  ```bash
54
- # 1. Copy this whole folder into the new project (anywhere; we suggest docs/)
55
- cp -r /path/to/portable-design ../new-project/docs/
56
-
57
- # 2. Wire the CSS into your build (pick ONE)
58
- # Tailwind v4: @import the pre-built tailwind.css
59
- # Vanilla CSS: link tokens.css + components.css
60
- # SCSS: @import _tokens.scss
61
-
62
- # 3. Wire AI to follow the system (pick ONE)
63
- cp docs/portable-design/ai-rules/CLAUDE.md ../../CLAUDE.md # Claude Code
64
- cp docs/portable-design/ai-rules/cursorrules.template ../../.cursorrules
65
- mkdir -p ../../.github && cp docs/portable-design/ai-rules/github-copilot-instructions.md ../../.github/copilot-instructions.md
12
+ pnpm add @hotelfriendag/design-tokens
66
13
  ```
67
14
 
68
- ## ⚠️ Existing-project integration
15
+ Public package — no `.npmrc`, no auth, no CI secrets. Works from any project, any CI.
69
16
 
70
- > **Status:** Solved in Phase 1A (RFC-0001 §4.2). All emitted tokens carry the `hf-` prefix INSIDE the category segment (`--color-hf-*`, `--text-hf-*`, `--radius-hf-*`, `--spacing-hf-*`, `--font-hf-*`, `--shadow-hf-*`). None of them can collide with Tailwind v4 defaults. The full `@import` is now safe in existing projects.
17
+ ## Wire it up (pick your stack)
71
18
 
72
- **Recommended setup for any project (greenfield or existing):**
19
+ The package exposes each generated file via a subpath export, e.g. `@hotelfriendag/design-tokens/tailwind.css`, `/components.css`, `/status.css`, `/_tokens.scss`, `/tokens.css`, `/tokens.ts`, `/shadcn-tokens.css`.
20
+
21
+ ### Tailwind v4 (recommended — e.g. `ui-hf`)
73
22
 
74
23
  ```css
75
- /* app/globals.css */
24
+ /* your main CSS entry, where @import "tailwindcss" lives */
76
25
  @import "tailwindcss";
77
- @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme — adds --color-hf-*, --text-hf-*, etc. */
78
- @import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
79
26
 
80
- /* Optional: exclude the bundle's own demo HTML from your Tailwind scan
81
- so legacy classes / bg-[#hex] from the showcase don't leak into your bundle. */
82
- @source not "./docs/portable-design/components.html";
83
- @source not "./docs/portable-design/portal-audit.html";
27
+ @import "@hotelfriendag/design-tokens/tailwind.css"; /* @theme tokens, hf- prefix */
28
+ @import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
29
+ @import "@hotelfriendag/design-tokens/status.css"; /* .status-{domain}-{state} */
84
30
  ```
85
31
 
86
- What this gives you:
87
-
88
- - `bg-hf-accent` (= `#24AFE8` — brand) — Tailwind's `bg-blue-500` keeps its default
89
- - `text-hf-base` (= 14px — body) — Tailwind's `text-base` keeps its default 16px
90
- - `rounded-hf-sm` (= 6px) — Tailwind's `rounded-sm` keeps its default 2px
91
- - `shadow-hf-modal` (= portal modal shadow)
92
- - … etc. Your existing utility usage is **untouched**.
32
+ Put the DS imports **after** any project-local `@theme {}` block so DS values win `--color-hf-*` collisions. Import `tailwind.css` only — `tailwind.additive.css` is byte-identical (the `hf-` prefix made the additive filter unnecessary), importing both duplicates every declaration.
93
33
 
94
- **For projects whose integration scripts ask for an "additive" target by name**, `--target=tailwind-v4-additive` is an explicit alias of `--target=tailwind-v4`. Identical output — the prefix made the additive filter unnecessary.
95
-
96
- ```bash
97
- node generate-tokens.cjs --target=tailwind-v4-additive > pre-built/tailwind.additive.css
98
- # (same bytes as --target=tailwind-v4)
34
+ ```html
35
+ <button class="bg-hf-accent hover:bg-hf-accent-hover text-white h-10 px-5 rounded-hf-sm text-hf-base font-semibold">Save</button>
36
+ <span class="hf-pill status-booking-confirmed">Confirmed</span>
99
37
  ```
100
38
 
101
- ## Stack-specific snippets
39
+ ### SCSS (Yii / Laravel / WP — e.g. `backend-hf`)
102
40
 
103
- ### React + Tailwind v4 (recommended, greenfield)
41
+ The package ships `pre-built/_tokens.scss` with 135 compile-time variables — `$colorAccent`, `$colorFg`, `$colorBorder`, `$radiusSm`, `$shadowModal`, … Semantic tokens are resolved to concrete values (e.g. `$colorAccent: #24AFE8;`), so no runtime cascade is needed. `node_modules` must be on your Sass load path.
104
42
 
105
- ```css
106
- /* app/globals.css */
107
- @import "tailwindcss";
108
- @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme tokens */
109
- @import "./docs/portable-design/pre-built/components.css"; /* .hf-* primitives */
110
- ```
43
+ ```scss
44
+ // Dart Sass (sass-embedded / dart-sass) — node_modules on loadPaths, then:
45
+ @use '@hotelfriendag/design-tokens/pre-built/tokens' as ds;
111
46
 
112
- ```jsx
113
- <button className="bg-hf-primary hover:bg-hf-primary-hover text-white h-10 px-5 rounded-hf text-hf-md font-semibold">
114
- Save
115
- </button>
116
- <span className="hf-pill status-booking-confirmed">Confirmed</span>
117
- <div className="hf-modal max-w-[500px]">
118
- <div className="hf-modal__header">
119
- <h2 className="hf-modal__title">Edit Guest</h2>
120
- <button className="hf-modal__close">✕</button>
121
- </div>
122
- <div className="hf-modal__body">…</div>
123
- <div className="hf-modal__footer"><button>Cancel</button><button>Save</button></div>
124
- </div>
47
+ .my-btn {
48
+ background: ds.$colorAccent; // #24AFE8
49
+ color: #fff;
50
+ border-radius: ds.$radiusSm; // 6px
51
+ box-shadow: ds.$shadowModal;
52
+ }
125
53
  ```
126
54
 
127
- ### React + Tailwind v3 (legacy)
55
+ ```scss
56
+ // webpack sass-loader — bare path resolves node_modules (older sass-loader: prefix with ~)
57
+ @import '@hotelfriendag/design-tokens/pre-built/tokens';
128
58
 
129
- ```js
130
- // tailwind.config.js
131
- const hfPreset = require('./docs/portable-design/pre-built/tailwind.preset.js');
132
- module.exports = {
133
- presets: [hfPreset],
134
- content: ['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}'],
135
- };
59
+ .my-btn { background: $colorAccent; border-radius: $radiusSm; }
136
60
  ```
137
61
 
138
- ```html
139
- <link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
140
- ```
62
+ For the `.hf-*` component primitives, also pull the generated CSS once (it references `var(--color-hf-*)` — emit `tokens.css` into the page too so the custom properties exist at runtime):
141
63
 
142
- ### Next.js + shadcn/ui
64
+ ```scss
65
+ @use '@hotelfriendag/design-tokens/pre-built/tokens'; // SCSS variables
66
+ ```
67
+ ```css
68
+ @import "@hotelfriendag/design-tokens/tokens.css"; /* :root custom properties */
69
+ @import "@hotelfriendag/design-tokens/components.css"; /* .hf-* primitives */
70
+ @import "@hotelfriendag/design-tokens/status.css"; /* status pills */
71
+ ```
143
72
 
144
- Append `pre-built/shadcn-tokens.css` to your `app/globals.css`. shadcn components automatically pick up `--primary`, `--background`, `--ring`, etc.
73
+ > Why both? SCSS `$variables` are compile-time (good for your own rules); the `.hf-*` component CSS resolves `var(--color-hf-*)` at runtime, so the page needs `tokens.css` loaded for those custom properties to exist.
145
74
 
146
- ### Vue 3 / Nuxt
75
+ ### Vue 3 / Nuxt (vanilla CSS variables)
147
76
 
148
77
  ```ts
149
78
  // nuxt.config.ts
150
79
  export default defineNuxtConfig({
151
80
  css: [
152
- '~/docs/portable-design/pre-built/tokens.css',
153
- '~/docs/portable-design/pre-built/components.css',
81
+ '@hotelfriendag/design-tokens/tokens.css', // :root custom properties
82
+ '@hotelfriendag/design-tokens/components.css', // .hf-* primitives
83
+ '@hotelfriendag/design-tokens/status.css',
154
84
  ],
155
85
  });
156
86
  ```
157
87
 
158
- Use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
88
+ Then use `var(--color-hf-accent)`, `var(--font-size-hf-base)`, or `.hf-modal` / `.hf-pill .status-booking-confirmed` in any template.
159
89
 
160
- ### SCSS-based (Yii / Laravel / WP)
90
+ ### TypeScript / CSS-in-JS
161
91
 
162
- ```scss
163
- // _app.scss
164
- @import 'docs/portable-design/pre-built/tokens';
165
- @import 'docs/portable-design/pre-built/components.css';
92
+ ```ts
93
+ import { tokens } from '@hotelfriendag/design-tokens'; // compiled tokens.js + .d.ts
166
94
 
167
- .my-btn {
168
- background: $colorPrimaryDefault;
169
- height: $sizeBtnDefault;
170
- border-radius: $borderRadiusSm;
171
- }
95
+ const Button = styled.button`
96
+ background: ${tokens.color.accent.default}; // #24AFE8 (semantic + primitive share tokens.color.*)
97
+ border-radius: ${tokens.radius.sm}; // 6px
98
+ `;
172
99
  ```
173
100
 
174
- ### TS / CSS-in-JS
101
+ ### Next.js + shadcn/ui
175
102
 
176
- ```ts
177
- import { tokens } from './docs/portable-design/pre-built/tokens';
103
+ Append `shadcn-tokens.css` to `app/globals.css` (after `@import "tailwindcss"`). shadcn components pick up `--primary`, `--background`, `--ring`, etc.
178
104
 
179
- const Button = styled.button`
180
- background: ${tokens.color.primary.default};
181
- height: ${tokens.size.btnDefault};
182
- border-radius: ${tokens.borderRadius.sm};
183
- `;
105
+ ```css
106
+ @import "@hotelfriendag/design-tokens/shadcn-tokens.css";
184
107
  ```
185
108
 
186
109
  ### Vanilla / static HTML
187
110
 
188
111
  ```html
189
- <link rel="stylesheet" href="docs/portable-design/pre-built/tokens.css">
190
- <link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
112
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/tokens.css">
113
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/components.css">
114
+ <link rel="stylesheet" href="node_modules/@hotelfriendag/design-tokens/pre-built/status.css">
191
115
 
192
116
  <span class="hf-pill status-booking-confirmed">Confirmed</span>
193
- <button class="hf-modal__close">✕</button>
194
117
  ```
195
118
 
119
+ ### No-npm fallback
120
+
121
+ If a consumer can't reach npmjs.com, the repo also works as a **git submodule** (relative `@import` paths). See the appendix in [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md).
122
+
123
+ ## What you get
124
+
125
+ - `bg-hf-accent` = `#24AFE8` (brand) — Tailwind's `bg-blue-500` keeps its default
126
+ - `text-hf-base` = 14px — Tailwind's `text-base` keeps its default 16px
127
+ - `rounded-hf-sm` = 6px — Tailwind's `rounded-sm` keeps its default 2px
128
+ - `shadow-hf-modal`, `text-hf-fg`, `border-hf-border`, `bg-hf-bg-surface`, … — full semantic set
129
+ - Your existing utility usage is **untouched** (collision-safe prefix).
130
+
131
+ App code should reference **semantic** tokens (`accent`, `fg`, `bg-*`, `border`, `status-*`) — primitives (`hf-blue-500`, `hf-gray-300`) are an implementation detail the theming layer can swap.
132
+
196
133
  ## Component primitives shipped in `components.css`
197
134
 
198
135
  | Class | Anatomy | See |
199
136
  |---|---|---|
200
137
  | `.hf-pill` + `.status-{domain}-{state}` | Status badge — 6px radius, 15% bg + 100% text + 1px border | components.html#status |
201
138
  | `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Underline + segmented tabs | components.html#tabs |
202
- | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT primary!) — 34×34, 8px radius | components.html#pagination |
139
+ | `.hf-pagination` + `__item` / `__ellipsis` | Subtle gray active (NOT accent!) — 34×34, 8px radius | components.html#pagination |
203
140
  | `.hf-modal` + `__header/__title/__body/__footer/__close` | 6px radius, footer no top border | components.html#modal |
204
141
  | `.hf-alert` + `--success/--info/--warn/--error` | White bg + 3px top accent bar + 26×26 squared icon | components.html#alerts |
205
142
  | `.hf-alert--tinted` / `--banner` / `--compact` | Modifiers — bg tint / full-width strip / compact-in-card | components.html#alerts |
206
- | `.hf-toast` | Floating notification (bottom-right) — 9px radius | components.html#alerts |
207
- | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled primary on check | components.html#inputs |
208
- | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown with portal-shadow `0 1px 10px rgba(0,0,0,.1)` | components.html#dropdown |
209
- | `.skeleton` + `.hf-spin` | Loading state primitives (shimmer + rotation keyframes) | components.html#empty |
210
-
211
- ## How the AI uses this
212
-
213
- After you drop one of the `ai-rules/*` files at the new project's root:
214
-
215
- 1. **Claude Code / Cursor / Copilot** automatically prepend it to every chat. The agent learns:
216
- - canonical palette (primary `#24AFE8`, status colors, neutrals)
217
- - typography (Roboto, sizes 11/13/14/15/16/18/20/22/26/30, weights 400/500/600)
218
- - spacing scale (multiples of 4)
219
- - radius scale (6/8/9/12/99)
220
- - shadow set (default/subtle/wrapper/card/modal/outline/hover)
221
- - component library (`.hf-modal`, `.hf-alert`, `.hf-pill`, `.hf-tab`, `.hf-pagination`, `.hf-check`, `.hf-dropdown-menu`, etc.)
222
- - hard rules ("never hard-code hex", "always provide hover/focus/disabled", "no mixed icon sets")
223
-
224
- 2. **Visual reference for new projects**: open `components.html` in a browser — canonical reference with all `.hf-*` patterns rendered.
225
-
226
- 3. **For chat-based AI** (ChatGPT, v0, Lovable): paste `ai-rules/system-prompt-compact.md` or `UI_DESIGN.md` §9 JSON token reference as a system prompt before asking for UI work.
227
-
228
- ## Refresh the bindings
229
-
230
- When `tokens.figma.json` changes, regenerate the bindings:
231
-
232
- ```bash
233
- cd docs/portable-design
234
- node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css # v4 (recommended)
235
- node generate-tokens.cjs --target=tailwind > pre-built/tailwind.preset.js # v3 (legacy)
236
- node generate-tokens.cjs --target=css > pre-built/tokens.css
237
- node generate-tokens.cjs --target=scss > pre-built/_tokens.scss
238
- node generate-tokens.cjs --target=ts > pre-built/tokens.ts
239
- node generate-tokens.cjs --target=shadcn > pre-built/shadcn-tokens.css
240
- ```
143
+ | `.hf-toast` | Floating notification — 9px radius | components.html#alerts |
144
+ | `.hf-check` / `.hf-radio` | Custom checkbox+radio — 18×18, filled accent on check | components.html#inputs |
145
+ | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | 9px radius dropdown, portal shadow | components.html#dropdown |
146
+ | `.skeleton` + `.hf-spin` | Loading-state primitives (shimmer + rotation keyframes) | components.html#empty |
241
147
 
242
- > `pre-built/components.css` is **not generated** — it's hand-extracted from `components.html`. If you change the `<style type="text/tailwindcss">` `@layer components` block in components.html, manually re-sync the extracted file. The `pre-built/components.css` file header has the source coordinates.
148
+ ## AI rules
243
149
 
244
- Or wire token regeneration into `package.json`:
150
+ The package ships agent rules under `ai-rules/`. Drop one into the consumer's root so Claude / Cursor / Copilot follow the same UI rules:
245
151
 
246
- ```json
247
- {
248
- "scripts": {
249
- "tokens:build:v4": "node docs/portable-design/generate-tokens.cjs --target=tailwind-v4 > tailwind.css",
250
- "tokens:build:v3": "node docs/portable-design/generate-tokens.cjs --target=tailwind > tailwind.preset.js",
251
- "prebuild": "npm run tokens:build:v3 && npm run tokens:build:v4"
252
- }
253
- }
254
- ```
255
-
256
- ## Validation checklist
257
-
258
- After porting to a new project, verify:
259
-
260
- - [ ] `cat CLAUDE.md` (or `.cursorrules`) shows the design-system rules.
261
- - [ ] `node docs/portable-design/generate-tokens.cjs --target=css` runs cleanly and produces stable output.
262
- - [ ] AI agent answers "what's the primary color?" with `#24AFE8`.
263
- - [ ] First button generated by AI matches `.btn-primary` (40px / 15px / 600 / `#24AFE8` / 6px radius).
264
- - [ ] Status badges resolve via `--status-{domain}-{state}-color` tokens.
265
- - [ ] `.hf-modal`, `.hf-alert`, `.hf-pagination` render correctly when `pre-built/components.css` is loaded.
266
-
267
- ## What's NOT portable
268
-
269
- These files in the parent `docs/` are **specific to the HotelFriend portal** (legacy audit / drift detection) and should NOT be copied:
270
-
271
- - `docs/migration-plan.md` — legacy SCSS cleanup queue (Yii portal only)
272
- - `docs/design-tokens-audit.md` — drift report
273
- - `docs/ui-elements-catalog.*` — observed components in the portal
274
- - `docs/icon-audit.*` — FA→Lucide migration map
275
- - `docs/component-anatomy.json` — measurements of legacy modals
276
- - `scratch/` — Playwright walkers that crawl the portal
152
+ - `ai-rules/CLAUDE.md` → consumer root `CLAUDE.md` (Claude Code, autodetected)
153
+ - `ai-rules/cursorrules.template` → `.cursorrules`
154
+ - `ai-rules/github-copilot-instructions.md` → `.github/copilot-instructions.md`
155
+ - `ai-rules/system-prompt-compact.md` compact prompt for ChatGPT / v0 / Lovable
277
156
 
278
- They live in the source repo for housekeeping; a new project doesn't need them.
157
+ If the consumer already has a `CLAUDE.md`, reference the package rules from it (e.g. point at `node_modules/@hotelfriendag/design-tokens/ai-rules/CLAUDE.md`) instead of overwriting. The agent learns the canonical palette, typography, spacing/radius/shadow scales, the `.hf-*` library, and the hard rules ("never hard-code hex", "always provide hover/focus/disabled").
279
158
 
280
- The `portal-audit.html` inside this bundle is the only legacy artifact that ships with the portable folder — keep it for historical reference and migration tracking. New code should never copy patterns from `portal-audit.html`.
281
-
282
- ---
283
-
284
- ## Drift detection & CI integration
285
-
286
- ### Pre-commit hook (recommended for any project that touches this bundle)
159
+ ## Keeping a consumer up to date
287
160
 
288
161
  ```bash
289
- # With husky (recommended)
290
- pnpm add -D husky
291
- pnpm husky init
292
- cp docs/portable-design/scripts/pre-commit.sh .husky/pre-commit
293
- chmod +x .husky/pre-commit
294
-
295
- # Without husky (raw git hook)
296
- cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
297
- chmod +x .git/hooks/pre-commit
162
+ pnpm update @hotelfriendag/design-tokens # pull the latest published version
298
163
  ```
299
164
 
300
- The hook runs `validate-tokens.cjs` when any tokens / pre-built / docs / components.html file is staged. Fails the commit if there's drift. Silent on success.
165
+ A convenient consumer script: `"design-system:update": "pnpm update @hotelfriendag/design-tokens && pnpm ls @hotelfriendag/design-tokens"`. Commit the bumped lockfile with `chore: bump design-system to <version>`.
301
166
 
302
- ### Stylelint config (recommended for consumer projects)
167
+ ## Drift detection & CI (for consumers)
303
168
 
304
- A shareable Stylelint config that forbids raw hex / named colors / literal `box-shadow` declarations:
169
+ **Stylelint config** forbids raw hex / named colors / literal `box-shadow`:
305
170
 
306
171
  ```js
307
172
  // .stylelintrc.cjs
308
173
  module.exports = {
309
174
  extends: [
310
175
  'stylelint-config-standard',
311
- './node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs'
312
- // — or, if pulled in via copy-folder:
313
- // './docs/portable-design/pre-built/stylelint-design-system.cjs'
176
+ './node_modules/@hotelfriendag/design-tokens/pre-built/stylelint-design-system.cjs',
314
177
  ],
315
178
  overrides: [
316
- // Generated files may contain hex (fallbacks, primitives) — opt-out
317
179
  { files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
318
180
  ],
319
181
  };
320
182
  ```
321
183
 
322
- ### Standalone validator (one-shot CI check)
323
-
324
- ```bash
325
- cd docs/portable-design
326
- node scripts/validate-tokens.cjs
327
- # ✓ validate-tokens: all checks passed
328
- # 168 CSS variables defined, all var() refs resolve, no bare hex.
329
- ```
184
+ **Pre-commit hook** — `scripts/pre-commit.sh` runs the token validator when bundle files are staged (husky-compatible).
330
185
 
331
- Use this in CI alongside lint/test. Exits non-zero on:
332
- 1. `var(--*)` reference that doesn't resolve to a defined CSS variable
333
- 2. Bare hex literal in `components.css` / `status.css` (outside comments + `var()` fallbacks)
334
- 3. Token reference in doc code-blocks that doesn't exist in `pre-built/*`
335
-
336
- ## NPM package (npmjs.com — public)
337
-
338
- The bundle ships as [`@hotelfriendag/design-tokens`](https://www.npmjs.com/package/@hotelfriendag/design-tokens) on the public npmjs.com registry — no `.npmrc`, no tokens, no CI secrets:
339
-
340
- ```bash
341
- # In any consumer project:
342
- pnpm add @hotelfriendag/design-tokens
343
- ```
344
-
345
- Then import per stack:
186
+ ---
346
187
 
347
- ```css
348
- /* CSS / Tailwind v4 */
349
- @import "@hotelfriendag/design-tokens/tailwind.css";
350
- @import "@hotelfriendag/design-tokens/components.css";
351
- @import "@hotelfriendag/design-tokens/status.css";
352
- ```
188
+ ## Maintainer reference
353
189
 
354
- ```ts
355
- // TypeScript / JavaScript (default export — uses compiled tokens.js + tokens.d.ts)
356
- import { tokens } from '@hotelfriendag/design-tokens';
190
+ ### Repo layout & precedence
357
191
 
358
- // Or the TS source directly (Vite / Vue / Next handle this natively):
359
- import { tokens } from '@hotelfriendag/design-tokens/tokens.ts';
360
192
  ```
361
-
362
- ```scss
363
- // SCSS
364
- @import '@hotelfriendag/design-tokens/_tokens';
193
+ design-system/
194
+ ├── components.html PRIMARY · canonical visual reference (open in browser)
195
+ ├── UI_DESIGN.md NARRATIVE · rationale, anatomy, decisions
196
+ ├── tokens.figma.json TOKENS · Tokens Studio export — single source of truth
197
+ ├── status-map.json domain→semantic status mapping (feeds status.css)
198
+ ├── src/components.css SOURCE for .hf-* rules (edit here)
199
+ ├── generate-tokens.cjs Node generator (no deps)
200
+ ├── pre-built/ GENERATED — never hand-edit (CI drift gate)
201
+ └── ai-rules/ agent rule files
365
202
  ```
366
203
 
367
- ### Publishing (maintainer)
204
+ Precedence when files disagree:
205
+ 1. **`components.html`** — visual decisions (colors, sizes, anatomy)
206
+ 2. **`tokens.figma.json`** — token values (regenerate `pre-built/*` after edits)
207
+ 3. **`UI_DESIGN.md`** — WHY decisions were made
208
+ 4. **`pre-built/*`** — generated, never hand-edit
209
+ 5. **`portal-audit.html`** — archival only (legacy portal snapshot)
368
210
 
369
- Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag
370
- and the workflow builds, validates, then `npm publish`-es to npmjs.com via
371
- **Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions
372
- mints a short-lived OIDC token that npm exchanges for publish credentials.
211
+ ### Regenerating outputs
373
212
 
374
- `--provenance` is NOT used: npmjs rejects sigstore provenance from private
375
- source repositories, and this repo stays private (only the built artifact is
376
- public — see RFC-0001 for the rationale). The trade is a missing attestation
377
- badge on the npm page in exchange for keeping the source code private.
213
+ `tokens.figma.json` is the single source of truth; `src/components.css` is the source for the `.hf-*` layer. After editing either:
378
214
 
379
215
  ```bash
380
- # From the repo root, on main:
381
- npm version patch # or minor / major → updates package.json, creates v* tag, commits
382
- git push --follow-tags # workflow runs on the pushed tag and publishes
216
+ npm run build # regenerates all pre-built/* (tokens, tailwind v3+v4, scss, ts/js/dts, shadcn, status.css, components.css)
217
+ npm run validate # drift detector: every var() resolves, no bare hex
383
218
  ```
384
219
 
385
- The `prepublishOnly` script re-runs the generator + validator so the published package is always self-consistent. If you need to publish manually from a local machine (hotfix), `npm publish` works the same way after `npm version <bump>` and `npm login` — but Trusted Publishing is the canonical path.
386
-
387
- The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for:
220
+ CI runs `git diff --exit-code -- pre-built/` so outputs must be reproducible from source. Edit `src/`, never `pre-built/`.
388
221
 
389
- | Field | Value |
390
- |---|---|
391
- | Organization | `HotelFriendAG` |
392
- | Repository | `design-system` |
393
- | Workflow filename | `release.yml` |
222
+ ### Publishing
394
223
 
395
- Changing the workflow filename, repo name, or org requires updating the npmjs binding before the next release will succeed.
224
+ Releases are tag-triggered via `.github/workflows/release.yml` — push a `v*` tag and the workflow builds, validates, then `npm publish`-es to npmjs.com via **Trusted Publishing (OIDC)**. No `NPM_TOKEN` secret, no PATs: GitHub Actions mints a short-lived OIDC token that npm exchanges for publish credentials.
396
225
 
397
- > Historical: versions `0.2.x` were briefly published to GitHub Packages while the registry decision was pending. `0.3.0+` is published exclusively to npmjs.com; consumers should install from there. `0.3.0` itself was bootstrapped by a one-time manual publish from a maintainer's machine using a short-lived Granular Access Token (since Trusted Publishing requires the package to already exist on npm). The token was revoked immediately after; all subsequent versions use OIDC.
398
-
399
- ## Changelog
400
-
401
- ### Phase 3 — Distribution + governance scaffolding (2026-05-21)
402
-
403
- Closes the remaining RFC-0001 governance items. The bundle is now SHIPPABLE as a versioned npm package with drift-detection enforcement.
404
-
405
- - **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.
406
- - **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`.
407
- - **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.
408
- - **Phase 3D** — `package.json` defines the npm package skeleton:
409
- - Exports map for every generated file + ai-rules + JSON sources
410
- - `scripts.build` chains all generator targets (8 in total)
411
- - `scripts.validate` runs the drift detector
412
- - `scripts.prepublishOnly` re-builds + validates before publish
413
- - `publishConfig` points at GitHub Packages registry (private/restricted access)
414
-
415
- ### Phase 2 — Component-tier + status externalization + verification (2026-05-21)
416
-
417
- Closes the RFC-0001 §5 + RFC-0002 §Q7 refinement TODOs that Phase 1B left behind. Three sub-phases — all shipped.
418
-
419
- **Phase 2A — Status mapping externalized (`status-map.json`)**
420
-
421
- 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:
422
-
423
- - **New file:** `status-map.json` — JSON dictionary `{domain.state}` → `semantic-status-name`
424
- - **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
425
- - **`pre-built/components.css`** — the `.status-*` block REMOVED; now you `@import` both files
426
- - Adding a new status is JSON-only — no CSS edit
427
-
428
- **Phase 2B — 4 component-tier tokens promoted**
429
-
430
- Bare hex values that didn't fit a generic semantic slot but were used by exactly one component:
431
-
432
- | Token | Value | Used by |
433
- |---|---|---|
434
- | `--color-hf-tab-fg-inactive` | `var(--color-hf-gray-700)` (#485B78) | `.hf-tab` default, `.hf-pill-tab` |
435
- | `--color-hf-tab-count-fg` | `var(--color-hf-gray-700)` | `.hf-tab__count` (inactive tab) |
436
- | `--color-hf-pagination-fg-inactive` | `var(--color-hf-gray-700)` | `.hf-pagination__item` default |
437
- | `--color-hf-pagination-fg-active` | `var(--color-hf-gray-950)` (#252F4A) | `.hf-pagination__item.is-active` |
438
- | `--color-hf-input-border` | `#DBDFE9` (raw — between gray-200 and gray-300) | `.hf-check`, `.hf-radio` |
439
- | `--color-hf-menu-bg-hover` | `#F5F5F5` (raw — portal `$table__hover`) | `.hf-dropdown-item:hover/:focus-visible` |
440
-
441
- 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.
442
-
443
- **Phase 2C — Verifier (`scripts/validate-tokens.cjs`)**
444
-
445
- Drift-detection script. Three checks:
446
- 1. Every `var(--*)` in `pre-built/*.css` resolves to a defined CSS variable
447
- 2. No bare hex literals in `components.css` / `status.css` (outside comments + `var()` fallbacks; `#fff`/`#000` allowed)
448
- 3. Every token reference in doc code-blocks exists in `pre-built/*` (anti-RFC-0001 §2.2 drift)
449
-
450
- Exits 1 on any failure — suitable for pre-commit hook or CI step.
226
+ `--provenance` is intentionally NOT used: npmjs rejects sigstore provenance from private source repositories, and this repo stays private (only the built artifact is public).
451
227
 
452
228
  ```bash
453
- node scripts/validate-tokens.cjs
454
- # validate-tokens: all checks passed
455
- # 168 CSS variables defined, all var() refs resolve, no bare hex.
229
+ npm version patch # or minor / major → bumps package.json, creates v* tag, commits
230
+ git push --follow-tags # workflow runs on the tag and publishes
456
231
  ```
457
232
 
458
- **Phase 2D `components.html` reconciled with Phase 1B+2 tokens**
459
-
460
- 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. The demo continues to render without a 500+ class rewrite, and the canonical `hf-` naming is documented in the @theme source.
461
-
462
- **What was tracked as Phase 2 TODO but stays open for Phase 3 / future:**
463
-
464
- - Fully *generate* `pre-built/components.css` from sources (currently still hand-authored, but verified by `validate-tokens.cjs`)
465
- - Rewrite `components.html` demo HTML to use the new prefixed class refs (`bg-hf-accent` everywhere) and drop legacy aliases entirely
466
- - Add ESLint/Stylelint plugin (RFC-0001 §4.6 forbid raw hex)
467
- - Publish as `@hotelfriendag/design-tokens` npm package (RFC-0001 §4.4)
468
-
469
- ### Phase 1B — RFC-0002 semantic tier (2026-05-21)
470
-
471
- 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.
472
-
473
- **Breaking changes (consumers must update):**
474
-
475
- | Phase 1A name | Phase 1B replacement |
476
- |---|---|
477
- | `--color-hf-primary*` | `--color-hf-accent` / `-hover` / `-subtle` / `-subtler` |
478
- | `--color-hf-text-primary` | `--color-hf-fg` |
479
- | `--color-hf-text-secondary` | `--color-hf-fg-muted` |
480
- | `--color-hf-text-useful` | `--color-hf-fg-subtle` |
481
- | `--color-hf-text-placeholder` | `--color-hf-fg-faint` |
482
- | `--color-hf-neutral-light-grey` | `--color-hf-bg-page` |
483
- | `--color-hf-neutral-bg-accent` | `--color-hf-bg-section` |
484
- | `--color-hf-neutral-very-light` | `--color-hf-bg-muted` |
485
- | `--color-hf-neutral-border` | `--color-hf-border` |
486
- | `--color-hf-neutral-light-blue-gray` | `--color-hf-border-subtle` |
487
- | `--color-hf-neutral-border-hover` | `--color-hf-border-strong` |
488
- | `--color-hf-status-success` etc. | unchanged ✓ (already canonical) |
489
- | `--color-hf-badge-{domain}-{state}-*` | **removed** — see status-map note below |
490
-
491
- **New: SEMANTIC status colors** (replace per-domain `badge.*` tokens):
492
-
493
- ```
494
- --color-hf-status-success / -bg / -strong
495
- --color-hf-status-warning / -bg / -strong
496
- --color-hf-status-error / -bg / -strong / -alt
497
- --color-hf-status-info / -bg
498
- --color-hf-status-cancel / -bg
499
- --color-hf-status-coral / -bg (portal-specific)
500
- --color-hf-status-violet / -bg (portal-specific)
501
- --color-hf-status-olive / -bg (portal-specific)
502
- --color-hf-status-orange-deep / -bg (portal-specific)
503
- ```
233
+ The Trusted Publisher binding on npmjs (Package → Settings → Trusted publishing) is configured for org `HotelFriendAG`, repo `design-system`, workflow `release.yml`. Changing any of those requires updating the binding before the next release.
504
234
 
505
- Domain→semantic mapping for CSS classes (e.g. `.status-booking-confirmed` → uses `--color-hf-status-info`) currently lives **hardcoded in `pre-built/components.css`** with `TODO Phase 2` markers. RFC-0002 §Q7 refinement requires extraction to `status-map.json` consumed by the generator — tracked as Phase 2.
506
-
507
- **New: tier-aware generator** (`generate-tokens.cjs`):
508
- - Recognises `primitive.*` and `semantic.*` top-level groups in `tokens.figma.json`
509
- - Resolves Tokens Studio `{path.to.token}` references — CSS targets emit `var()` chain (so theming works at runtime), TS/SCSS/v3-preset inline the resolved value
510
- - All existing `--prefix=` / `--target=` flags unchanged
511
-
512
- **Files affected:**
513
- - `tokens.figma.json` — restructured: `primitive.color.{family}.{step}` + `semantic.color.{slot}.{variant}`
514
- - `generate-tokens.cjs` — alias resolution + tier path normalisation
515
- - `pre-built/*` — all regenerated; tokens.css now has primitives + semantic aliases
516
- - `pre-built/components.css` — fully rewritten; binds chrome to semantic, hardcodes status-map (TODO externalize)
517
- - `components.html` — **NOT touched** (still self-contained with its own pre-Phase 1B @theme). Phase 2 will reconcile.
518
-
519
- ### Phase 1A — RFC-0001 collision-safe prefixing (2026-05-21)
520
-
521
- 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`.
522
-
523
- **Breaking change for `ui-hf` and any other consumer:** all token names now carry the `hf-` prefix INSIDE the category segment.
524
-
525
- - `--color-primary` → `--color-hf-primary` (Tailwind utility: `bg-hf-primary`)
526
- - `--text-base` → `--text-hf-base` (utility: `text-hf-base` = 14px; Tailwind's default `text-base` keeps 16px — no more silent override)
527
- - `--radius-sm` → `--radius-hf-sm` (utility: `rounded-hf-sm` = 6px; default `rounded-sm` stays 2px)
528
- - `--spacing-7` → `--spacing-hf-7` (utility: `p-hf-7` = 30px; default `p-7` stays 28px)
529
- - `--font-sans` → `--font-hf-sans` (utility: `font-hf-sans` = Roboto)
530
- - `--shadow-modal` → `--shadow-hf-modal` (utility: `shadow-hf-modal`)
531
- - All `--color-badge-{domain}-{state}-*` → `--color-hf-badge-{domain}-{state}-*`
532
-
533
- **Why the prefix goes INSIDE the category** (e.g. `--color-hf-primary`, not `--hf-color-primary`):
534
- 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.
535
-
536
- **Files touched:**
537
- - `tokens.figma.json` — unchanged (the prefix is applied at generation time, not in source)
538
- - `generate-tokens.cjs` — new `--prefix=` argument; PREFIX defaults to `hf-`. Override with `--prefix=` (empty) for legacy unprefixed output
539
- - `pre-built/*.css/scss/ts/js` — all regenerated with `--hf-` prefix in CSS targets (Tailwind v3 / SCSS / TS / shadcn unchanged for now)
540
- - `pre-built/components.css` — all `var(--*)` refs updated to new prefixed names
541
- - Docs — all `var(--color-primary)` etc. updated to `var(--color-hf-primary)` form
542
-
543
- **What's NOT changed yet (Phase 1B):**
544
- - `components.html` `@theme {}` block still uses unprefixed names (it's a self-contained demo — no collision risk in standalone use). Phase 1B will reconcile when the semantic tier lands.
545
- - Tailwind v3 preset, SCSS, TS outputs stay unprefixed (legacy paths — less risk of collision in those ecosystems).
546
-
547
- ### Phase 0 — RFC-0001 quick wins (2026-05-21)
548
-
549
- Addresses critique from `RFC-0001-cross-project-design-system.md` §2 + §8 — small fixes that don't require architectural decisions:
550
-
551
- - **ESM compatibility** — renamed `generate-tokens.js` → `generate-tokens.cjs` so the script runs under Node in `"type":"module"` projects (Vite/Vue 3/Next).
552
- - **Token name fix** — `color.primary.default` JSON key no longer emits `--color-primary-default` (awkward `bg-primary-default` utility). Generator now strips trailing `default`/`DEFAULT` so emit is `--color-primary` → utility `bg-primary` works. Same logic applies to any future `.default` sub-keys.
553
- - **Shadow token namespace** — JSON group `boxShadow` → `shadow` so emit is `--shadow-modal` (was `--box-shadow-modal`). Matches what `components.html` and docs already reference.
554
- - **`components.css` — RFC §5 conformance:**
555
- - **No more hardcoded hex** (RFC §5a) — all values reference `var(--color-*)` / `var(--font-*)` / `var(--border-radius-*)` with hex fallbacks. The 4 remaining bare hex (`#fff`, `#252F4A`, `#DBDFE9`, `#F5F5F5`) are commented as Phase 1 TODOs for the semantic tier.
556
- - **One namespace** (RFC §5c) — dropped the parallel `:root { --status-* }` declarations; consumes canonical `--color-badge-*` tokens directly.
557
- - **Class names match token paths** (RFC §5d) — breaking renames:
558
- - `.status-clean-*` → `.status-room-item-cleaning-*`
559
- - `.status-fd-*` → `.status-front-desk-*`
560
- - `.status-order-action` → `.status-order-action-required`
561
- - **Doc/output snapshot** — added a smoke test verifying every `var(--*)` referenced in docs exists in `pre-built/tokens.css` or `pre-built/components.css`. Currently 0 missing.
562
- - **Existing-project integration warning** — new "⚠️ Existing-project integration" section above documents the Tailwind v4 collision issue + `@source not` exclusion until Phase 1 lands prefixed tokens.
563
-
564
- ### Still open (deferred to Phase 1+, per RFC roadmap)
565
-
566
- - Token namespace prefix (`--hf-*`) + `--target=tailwind-v4-additive` (RFC §4.2)
567
- - Three-tier token architecture: primitive → semantic → component (RFC §4.1)
568
- - `text-text-primary` awkward utility — fix when semantic tier introduces `--hf-color-fg` (RFC §8.3)
569
- - `pre-built/components.css` is hand-mirrored from `components.html` rather than generated (RFC §5b)
570
- - Two competing visual SSOTs (`components.html` vs `UI_DESIGN.md`) — Phase 2 will pick one
235
+ `prepublishOnly` re-runs the generator + validator so the published package is always self-consistent.
571
236
 
572
237
  ---
573
238
 
574
- Built from the HotelFriend Design System v0.1.0 — see `UI_DESIGN.md` §9 for the JSON token reference and `components.html` for the canonical visual reference.
239
+ - **Changelog / phase history:** [`CHANGELOG.md`](CHANGELOG.md)
240
+ - **Remaining work:** [`ROADMAP.md`](ROADMAP.md)
241
+ - **First-consumer playbook:** [`INTEGRATION-ui-hf.md`](INTEGRATION-ui-hf.md)
242
+ - **Architecture rationale:** [`RFC-0001`](RFC-0001-cross-project-design-system.md) · [`RFC-0002`](RFC-0002-semantic-tier-naming.md)
243
+ - **Visual reference:** `components.html` · **Token narrative:** `UI_DESIGN.md`