@hotelfriendag/design-tokens 0.3.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/README.uk.md ADDED
@@ -0,0 +1,377 @@
1
+ # HotelFriend Design System
2
+
3
+ Спільна дизайн-основа для всіх проєктів: токени, згенеровані файли під кожен стек (CSS / SCSS / TS / Tailwind v3 + v4 / shadcn), шар компонентів `.hf-*` та правила для AI-інструментів.
4
+
5
+ > **Виокремлено 2026-05-25** із [`hotelfriend/backend-hf`](https://bitbucket.org/hotelfriend/backend-hf) (`docs/portable-design/`), щоб цей репозиторій став єдиним джерелом істини для решти проєктів HotelFriend.
6
+ >
7
+ > **Статус:** RFC-0001 Фаза 1 завершена (семантична трирівнева модель + префіксація без колізій + CI-гейт на дрейф). Наступний крок — версійований npm-пакет (`@hotelfriend/design-tokens` через GitHub Packages). Повний чек-лист — у `RFC-0001-cross-project-design-system.md` §9.
8
+
9
+ ## Ієрархія файлів (читати в цьому порядку)
10
+
11
+ ```
12
+ portable-design/
13
+
14
+ ├── components.html ← ОСНОВНЕ · канонічний візуальний референс (відкрити у браузері)
15
+ ├── UI_DESIGN.md ← НАРАТИВ · обґрунтування, анатомія, рішення (AI читає це першим)
16
+ ├── tokens.figma.json ← ТОКЕНИ · атомарний експорт Tokens Studio → живить Figma + генератори
17
+
18
+ ├── pre-built/ ← ЗГЕНЕРОВАНІ · підставляєте у свій білд-пайплайн
19
+ │ ├── tailwind.css · Tailwind v4 @theme-блок (рекомендовано)
20
+ │ ├── tailwind.preset.js · Tailwind v3 preset (легасі)
21
+ │ ├── tokens.css · звичайні CSS custom properties
22
+ │ ├── _tokens.scss · SCSS-змінні
23
+ │ ├── tokens.ts · TypeScript const
24
+ │ ├── shadcn-tokens.css · контракт shadcn/ui
25
+ │ └── components.css · примітиви `.hf-*` (витяг із components.html)
26
+
27
+ ├── states-canonical.json ← курований набір інтерактивних станів (використовуйте цей)
28
+ ├── states.json ← сирий експорт із порталу (160 КБ — краще брати states-canonical.json)
29
+ ├── generate-tokens.cjs ← Node-скрипт (без залежностей) — перетворює токени на Tailwind/CSS/SCSS/TS/shadcn
30
+
31
+ ├── ai-rules/ ← покладіть ОДИН файл у корінь нового проєкту
32
+ │ ├── CLAUDE.md · для Claude Code (підхоплюється автоматично)
33
+ │ ├── cursorrules.template · перейменуйте на .cursorrules
34
+ │ ├── github-copilot-instructions.md · покласти в .github/copilot-instructions.md
35
+ │ └── system-prompt-compact.md · компактний промпт для ChatGPT/v0/Lovable
36
+
37
+ ├── portal-audit.html ← АРХІВ · знімок аудиту старого порталу (НЕ для нового коду)
38
+ └── README.md ← ви тут (швидкий старт)
39
+ ```
40
+
41
+ ### Правило пріоритету — коли файли суперечать одне одному
42
+
43
+ 1. **`components.html`** — канон для **візуальних рішень** (кольори, розміри, анатомія)
44
+ 2. **`tokens.figma.json`** — канон для **значень токенів**. Після редагування — перегенеруйте `pre-built/*`
45
+ 3. **`UI_DESIGN.md`** — канон для **ЧОМУ було прийнято рішення** (історія, компроміси, нотатки про дрейф порталу)
46
+ 4. **`pre-built/*`** — **згенеровані**, руками не правити. Після зміни JSON запускайте `generate-tokens.cjs`
47
+ 5. **`portal-audit.html`** — **тільки архів**. Показує поточний вигляд легасі-порталу — корисно для трекінгу міграції, НЕ для нового UI
48
+
49
+ Якщо `components.html` і `UI_DESIGN.md` суперечать одне одному — **виграє `components.html`**, а `UI_DESIGN.md` вважається застарілим.
50
+
51
+ ## Швидкий старт за 60 секунд
52
+
53
+ ```bash
54
+ # 1. Скопіювати цю папку у новий проєкт (куди завгодно; рекомендуємо docs/)
55
+ cp -r /path/to/portable-design ../new-project/docs/
56
+
57
+ # 2. Підключити CSS у білд (оберіть ОДНЕ)
58
+ # Tailwind v4: @import pre-built/tailwind.css
59
+ # Vanilla CSS: підключити tokens.css + components.css
60
+ # SCSS: @import _tokens.scss
61
+
62
+ # 3. Підключити AI до правил системи (оберіть ОДНЕ)
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
66
+ ```
67
+
68
+ ## ⚠️ Інтеграція у наявний проєкт
69
+
70
+ > **Статус:** ✅ Вирішено у Фазі 1A (RFC-0001 §4.2). Усі згенеровані токени мають префікс `hf-` ВСЕРЕДИНІ категорії (`--color-hf-*`, `--text-hf-*`, `--radius-hf-*`, `--spacing-hf-*`, `--font-hf-*`, `--shadow-hf-*`). Жоден не може зіткнутися з дефолтами Tailwind v4. Повний `@import` у наявному проєкті — безпечний.
71
+
72
+ **Рекомендована конфігурація для будь-якого проєкту (новий чи наявний):**
73
+
74
+ ```css
75
+ /* app/globals.css */
76
+ @import "tailwindcss";
77
+ @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme — додає --color-hf-*, --text-hf-* тощо */
78
+ @import "./docs/portable-design/pre-built/components.css"; /* примітиви .hf-* */
79
+
80
+ /* Опційно: виключити демо-HTML із пакета зі сканування Tailwind,
81
+ щоб легасі-класи / bg-[#hex] із showcase не потрапили у ваш бандл. */
82
+ @source not "./docs/portable-design/components.html";
83
+ @source not "./docs/portable-design/portal-audit.html";
84
+ ```
85
+
86
+ Що ви отримуєте:
87
+
88
+ - `bg-hf-accent` (= `#24AFE8` — бренд) — `bg-blue-500` із Tailwind лишається дефолтним
89
+ - `text-hf-base` (= 14px — основний текст) — `text-base` із Tailwind лишається 16px
90
+ - `rounded-hf-sm` (= 6px) — `rounded-sm` із Tailwind лишається 2px
91
+ - `shadow-hf-modal` (= тінь модалки порталу)
92
+ - … тощо. Ваші наявні утиліти **не змінюються**.
93
+
94
+ **Для проєктів, чиї інтеграційні скрипти просять "additive"-таргет за назвою**, `--target=tailwind-v4-additive` — явний alias для `--target=tailwind-v4`. Вихідний файл ідентичний — префікс зробив additive-фільтр непотрібним.
95
+
96
+ ```bash
97
+ node generate-tokens.cjs --target=tailwind-v4-additive > pre-built/tailwind.additive.css
98
+ # (байт-у-байт як --target=tailwind-v4)
99
+ ```
100
+
101
+ ## Сніпети під конкретні стеки
102
+
103
+ ### React + Tailwind v4 (рекомендовано, новий проєкт)
104
+
105
+ ```css
106
+ /* app/globals.css */
107
+ @import "tailwindcss";
108
+ @import "./docs/portable-design/pre-built/tailwind.css"; /* @theme токени */
109
+ @import "./docs/portable-design/pre-built/components.css"; /* примітиви .hf-* */
110
+ ```
111
+
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
+ Зберегти
115
+ </button>
116
+ <span className="hf-pill status-booking-confirmed">Підтверджено</span>
117
+ <div className="hf-modal max-w-[500px]">
118
+ <div className="hf-modal__header">
119
+ <h2 className="hf-modal__title">Редагувати гостя</h2>
120
+ <button className="hf-modal__close">✕</button>
121
+ </div>
122
+ <div className="hf-modal__body">…</div>
123
+ <div className="hf-modal__footer"><button>Скасувати</button><button>Зберегти</button></div>
124
+ </div>
125
+ ```
126
+
127
+ ### React + Tailwind v3 (легасі)
128
+
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
+ };
136
+ ```
137
+
138
+ ```html
139
+ <link rel="stylesheet" href="docs/portable-design/pre-built/components.css">
140
+ ```
141
+
142
+ ### Next.js + shadcn/ui
143
+
144
+ Допишіть `pre-built/shadcn-tokens.css` у `app/globals.css`. Компоненти shadcn автоматично підхоплять `--primary`, `--background`, `--ring` тощо.
145
+
146
+ ### Vue 3 / Nuxt
147
+
148
+ ```ts
149
+ // nuxt.config.ts
150
+ export default defineNuxtConfig({
151
+ css: [
152
+ '~/docs/portable-design/pre-built/tokens.css',
153
+ '~/docs/portable-design/pre-built/components.css',
154
+ ],
155
+ });
156
+ ```
157
+
158
+ У шаблонах використовуйте `var(--color-hf-accent)`, `var(--font-size-hf-base)` або `.hf-modal` / `.hf-pill .status-booking-confirmed`.
159
+
160
+ ### SCSS-стек (Yii / Laravel / WP)
161
+
162
+ ```scss
163
+ // _app.scss
164
+ @import 'docs/portable-design/pre-built/tokens';
165
+ @import 'docs/portable-design/pre-built/components.css';
166
+
167
+ .my-btn {
168
+ background: $colorPrimaryDefault;
169
+ height: $sizeBtnDefault;
170
+ border-radius: $borderRadiusSm;
171
+ }
172
+ ```
173
+
174
+ ### TS / CSS-in-JS
175
+
176
+ ```ts
177
+ import { tokens } from './docs/portable-design/pre-built/tokens';
178
+
179
+ const Button = styled.button`
180
+ background: ${tokens.color.primary.default};
181
+ height: ${tokens.size.btnDefault};
182
+ border-radius: ${tokens.borderRadius.sm};
183
+ `;
184
+ ```
185
+
186
+ ### Ванільний / статичний HTML
187
+
188
+ ```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">
191
+
192
+ <span class="hf-pill status-booking-confirmed">Підтверджено</span>
193
+ <button class="hf-modal__close">✕</button>
194
+ ```
195
+
196
+ ## Примітиви компонентів у `components.css`
197
+
198
+ | Клас | Анатомія | Дивись |
199
+ |---|---|---|
200
+ | `.hf-pill` + `.status-{domain}-{state}` | Статус-бейдж — радіус 6px, фон 15% + текст 100% + рамка 1px | components.html#status |
201
+ | `.hf-tab` / `.hf-tab--sm` / `.hf-pill-tabs` | Таби з підкресленням та сегментовані | components.html#tabs |
202
+ | `.hf-pagination` + `__item` / `__ellipsis` | Активний — стриманий сірий (НЕ primary!) — 34×34, радіус 8px | components.html#pagination |
203
+ | `.hf-modal` + `__header/__title/__body/__footer/__close` | Радіус 6px, у футера немає верхньої рамки | components.html#modal |
204
+ | `.hf-alert` + `--success/--info/--warn/--error` | Білий фон + 3px акцентна смуга зверху + квадратна іконка 26×26 | components.html#alerts |
205
+ | `.hf-alert--tinted` / `--banner` / `--compact` | Модифікатори — тонований фон / на всю ширину / компактний у картці | components.html#alerts |
206
+ | `.hf-toast` | Плаваюче сповіщення (правий нижній кут) — радіус 9px | components.html#alerts |
207
+ | `.hf-check` / `.hf-radio` | Кастомні чекбокс та радіо — 18×18, активний — заливка primary | components.html#inputs |
208
+ | `.hf-dropdown-menu` + `__item / __header / __shortcut / __icon / __divider` | Дропдаун з радіусом 9px та тінню `0 1px 10px rgba(0,0,0,.1)` | components.html#dropdown |
209
+ | `.skeleton` + `.hf-spin` | Примітиви станів завантаження (шимер + обертання) | components.html#empty |
210
+
211
+ ## Як цим користується AI
212
+
213
+ Після того як ви поклали один із файлів `ai-rules/*` у корінь нового проєкту:
214
+
215
+ 1. **Claude Code / Cursor / Copilot** автоматично додають його до кожного чату. Агент знає:
216
+ - канонічну палітру (primary `#24AFE8`, кольори статусів, нейтральні)
217
+ - типографіку (Roboto, розміри 11/13/14/15/16/18/20/22/26/30, ваги 400/500/600)
218
+ - шкалу відступів (кратно 4)
219
+ - шкалу радіусів (6/8/9/12/99)
220
+ - набір тіней (default/subtle/wrapper/card/modal/outline/hover)
221
+ - бібліотеку компонентів (`.hf-modal`, `.hf-alert`, `.hf-pill`, `.hf-tab`, `.hf-pagination`, `.hf-check`, `.hf-dropdown-menu` тощо)
222
+ - жорсткі правила ("ніколи не зашивати hex", "завжди робити hover/focus/disabled", "не мішати іконкові набори")
223
+
224
+ 2. **Візуальний референс для нових проєктів:** відкрийте `components.html` у браузері — це канон, де відрендерені всі патерни `.hf-*`.
225
+
226
+ 3. **Для чат-AI** (ChatGPT, v0, Lovable): вставте `ai-rules/system-prompt-compact.md` або JSON-довідник токенів із `UI_DESIGN.md` §9 як system prompt перед запитом UI-завдання.
227
+
228
+ ## Оновлення згенерованих файлів
229
+
230
+ Коли змінюється `tokens.figma.json`, перегенеруйте:
231
+
232
+ ```bash
233
+ cd docs/portable-design
234
+ node generate-tokens.cjs --target=tailwind-v4 > pre-built/tailwind.css # v4 (рекомендовано)
235
+ node generate-tokens.cjs --target=tailwind > pre-built/tailwind.preset.js # v3 (легасі)
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
+ ```
241
+
242
+ > `pre-built/components.css` поки **не генерується** — він вручну витягнутий із `components.html`. Якщо ви змінюєте блок `<style type="text/tailwindcss">` / `@layer components` у `components.html`, синхронізуйте витягнутий файл вручну. Заголовок `pre-built/components.css` містить координати джерела.
243
+
244
+ Або вбудуйте регенерацію в `package.json`:
245
+
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
+ ## Чек-ліст валідації
257
+
258
+ Після перенесення у новий проєкт перевірте:
259
+
260
+ - [ ] `cat CLAUDE.md` (або `.cursorrules`) показує правила дизайн-системи.
261
+ - [ ] `node docs/portable-design/generate-tokens.cjs --target=css` відпрацьовує чисто і дає стабільний вивід.
262
+ - [ ] AI-агент на питання "який primary color?" відповідає `#24AFE8`.
263
+ - [ ] Перша згенерована AI кнопка відповідає `.btn-primary` (40px / 15px / 600 / `#24AFE8` / радіус 6px).
264
+ - [ ] Бейджі статусів резолвляться через токени `--status-{domain}-{state}-color`.
265
+ - [ ] `.hf-modal`, `.hf-alert`, `.hf-pagination` рендеряться коректно, коли підвантажений `pre-built/components.css`.
266
+
267
+ ## Що НЕ переноситься
268
+
269
+ Ці файли у батьківському `docs/` — **специфічні для порталу HotelFriend** (легасі-аудит / детект дрейфу), копіювати їх НЕ треба:
270
+
271
+ - `docs/migration-plan.md` — черга прибирання старого SCSS (тільки портал на Yii)
272
+ - `docs/design-tokens-audit.md` — звіт про дрейф
273
+ - `docs/ui-elements-catalog.*` — спостережені компоненти у порталі
274
+ - `docs/icon-audit.*` — карта міграції FA→Lucide
275
+ - `docs/component-anatomy.json` — обміри легасі-модалок
276
+ - `scratch/` — Playwright-воркери, що сканують портал
277
+
278
+ Вони лежать у вихідному репо для господарських потреб; новому проєкту вони не потрібні.
279
+
280
+ `portal-audit.html` всередині цього пакета — єдиний легасі-артефакт, що подорожує разом із portable-папкою — залиште його як історичний референс і для трекінгу міграції. Новий код **ніколи** не повинен копіювати патерни з `portal-audit.html`.
281
+
282
+ ---
283
+
284
+ ## Детект дрейфу та інтеграція з CI
285
+
286
+ ### Pre-commit hook (рекомендовано для будь-якого проєкту, що чіпає цей пакет)
287
+
288
+ ```bash
289
+ # З husky (рекомендовано)
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
+ # Без husky (сирий git-hook)
296
+ cp docs/portable-design/scripts/pre-commit.sh .git/hooks/pre-commit
297
+ chmod +x .git/hooks/pre-commit
298
+ ```
299
+
300
+ Хук запускає `validate-tokens.cjs`, коли в staging є будь-які файли з tokens / pre-built / docs / components.html. Падає на дрейфі. Мовчить на успіху.
301
+
302
+ ### Конфіг Stylelint (рекомендовано для проєктів-споживачів)
303
+
304
+ Сумісний Stylelint-конфіг, який забороняє сирий hex / іменовані кольори / літеральні `box-shadow`:
305
+
306
+ ```js
307
+ // .stylelintrc.cjs
308
+ module.exports = {
309
+ extends: [
310
+ 'stylelint-config-standard',
311
+ './node_modules/@hotelfriend/design-tokens/pre-built/stylelint-design-system.cjs'
312
+ // — або, якщо підключено через копіювання папки:
313
+ // './docs/portable-design/pre-built/stylelint-design-system.cjs'
314
+ ],
315
+ overrides: [
316
+ // У згенерованих файлах hex допустимий (fallback-и, примітиви) — opt-out
317
+ { files: ['**/pre-built/*.css'], rules: { 'color-no-hex': null } },
318
+ ],
319
+ };
320
+ ```
321
+
322
+ ### Самостійний валідатор (одноразова перевірка у CI)
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
+ ```
330
+
331
+ Використовуйте у CI поруч із лінтом та тестами. Виходить із ненульовим кодом, якщо:
332
+ 1. `var(--*)` посилається на CSS-змінну, яка ніде не визначена
333
+ 2. У `components.css` / `status.css` є сирий hex (поза коментарями та fallback-ами `var()`; `#fff`/`#000` дозволені)
334
+ 3. У код-блоці документації згадано токен, якого нема в `pre-built/*`
335
+
336
+ ## NPM-пакет (приватний реєстр)
337
+
338
+ Пакет публікується як `@hotelfriend/design-tokens` у приватний реєстр GitHub Packages:
339
+
340
+ ```bash
341
+ # У проєкті-споживачі:
342
+ echo "@hotelfriend:registry=https://npm.pkg.github.com" >> .npmrc
343
+ pnpm add @hotelfriend/design-tokens
344
+ ```
345
+
346
+ Імпорт під свій стек:
347
+
348
+ ```css
349
+ /* CSS / Tailwind v4 */
350
+ @import "@hotelfriend/design-tokens/tailwind.css";
351
+ @import "@hotelfriend/design-tokens/components.css";
352
+ @import "@hotelfriend/design-tokens/status.css";
353
+ ```
354
+
355
+ ```ts
356
+ // TypeScript
357
+ import { tokens } from '@hotelfriend/design-tokens/tokens.ts';
358
+ ```
359
+
360
+ ```scss
361
+ // SCSS
362
+ @import '@hotelfriend/design-tokens/_tokens';
363
+ ```
364
+
365
+ ### Публікація (для мейнтейнера)
366
+
367
+ ```bash
368
+ cd docs/portable-design
369
+ pnpm version patch # або minor / major
370
+ pnpm publish # запускає prepublishOnly → build + validate
371
+ ```
372
+
373
+ `prepublishOnly` перезбирає та валідує, тож опублікований пакет завжди узгоджений.
374
+
375
+ ---
376
+
377
+ Зібрано на основі HotelFriend Design System v0.1.0 — JSON-довідник токенів див. у `UI_DESIGN.md` §9, канонічний візуальний референс — у `components.html`. Англомовна версія цього файлу — [`README.md`](README.md), повний журнал фаз і змін — там же.
@@ -0,0 +1,273 @@
1
+ # RFC-0001 — Evolving the HotelFriend Design System into a cross-project foundation
2
+
3
+ > **Status:** Draft / proposal
4
+ > **Author:** (fill in)
5
+ > **Created:** 2026-05-20
6
+ > **Reviewed against:** `portable-design_v2` (2026-05-21) — see §2.1
7
+ > **Scope:** `docs/portable-design` bundle (tokens + generator + docs + AI rules)
8
+ > **Supersedes:** nothing yet — first RFC for this bundle.
9
+
10
+ ## 1. Goal
11
+
12
+ Make this design system usable as **one shared foundation for both new (greenfield) and
13
+ existing projects**, so that several HotelFriend codebases (e.g. the Vue 3 / Tailwind v4
14
+ `ui-hf` portal and the legacy Yii / SCSS portal) render with the **same visual design** and
15
+ can be **updated from a single source of truth**.
16
+
17
+ ### Non-goals (for this RFC)
18
+
19
+ - Rewriting the actual portal UI.
20
+ - Picking a specific component framework. We discuss component strategy but defer the choice.
21
+ - Visual redesign — this is about distribution, naming, and architecture, not new aesthetics.
22
+
23
+ ## 2. Why now / evidence
24
+
25
+ This RFC is grounded in a real integration of the bundle into the `ui-hf` Tailwind v4 project.
26
+ Concrete friction observed (these are the problems to fix):
27
+
28
+ 1. **Namespace collisions with framework defaults.** The generated `pre-built/tailwind.css`
29
+ redefines keys that Tailwind v4 (and the consuming project) already own — `--text-xs` (11px
30
+ vs default), `--text-sm` (13px vs 14px), `--text-xl` (18px vs the project's 26px), `--text-2xl`
31
+ (20px vs 24px), `--spacing-7` (30px vs 28px), `--radius-sm` (6px vs 2px), `--font-sans`.
32
+ A naïve `@import` silently resized utilities used **thousands** of times (`text-xs` ×1093,
33
+ `text-sm` ×113, `text-xl` ×57). Integration required a hand-written filter that imports
34
+ only the additive, non-colliding subset.
35
+ 2. **Token-name drift between docs and the generator.** None of the README examples compile
36
+ as-is:
37
+ - `ai-rules/CLAUDE.md` says the primary is CSS var `--color-primary-default`; the generator
38
+ emits `--color-primary`.
39
+ - README shows `bg-badge-booking-confirmed/15` + `text-badge-booking-confirmed` (and
40
+ `--status-{domain}-{state}-color`); the generator emits `--color-badge-booking-confirmed-color`
41
+ and `--color-badge-booking-confirmed-bg`, i.e. utilities are `bg-badge-booking-confirmed-bg` /
42
+ `text-badge-booking-confirmed-color`.
43
+ 3. **Generator breaks in modern toolchains.** `generate-tokens.js` is CommonJS (`require`); any
44
+ project with `"type": "module"` (Vite/Vue/most new repos) fails with
45
+ `ReferenceError: require is not defined`. We had to run a temporary `.cjs` copy.
46
+ 4. **Awkward generated utility names.** `--color-text-primary` yields the class `text-text-primary`.
47
+ 5. **Bundle is incomplete as a sole source.** No primitive ramps (gray/blue 25–950), no fluid
48
+ spacing, no column widths / table min-widths, no component utility classes (`btn-*`, `input-*`).
49
+ It can only *augment* an existing token layer, never replace it.
50
+ 6. **Copy-folder distribution guarantees drift.** Each project owns a divergent copy the moment
51
+ it is pasted; there is no mechanism keeping N projects on the same version.
52
+ 7. **The component layer re-hardcodes values and forks the token namespace** (introduced in v2 —
53
+ see §2.1). `pre-built/components.css` defines its own `--status-{domain}-{state}-color/-bg` set
54
+ with raw `rgb(...)` literals instead of binding to the canonical `--color-badge-*` tokens, and
55
+ contains ~56 hardcoded hex values (e.g. `#24AFE8`, `#50627E`) — violating the bundle's own
56
+ "never hardcode hex" rule. Change a token in `tokens.figma.json` and the components do not follow.
57
+
58
+ ### 2.1 Status against `portable-design_v2` (reviewed 2026-05-21)
59
+
60
+ `portable-design` was archived as `portable-design_v1` and a new `portable-design_v2` was added.
61
+ What v2 changed, mapped to the problems above and the proposal below:
62
+
63
+ **Progress (mostly on §5 — components):**
64
+ - **New `components.html`** — a canonical visual reference, declared authoritative ("when
65
+ `components.html` and `UI_DESIGN.md` disagree, components.html wins"). Genuine progress on the
66
+ §5 anatomy/visual-reference goal.
67
+ - **New `pre-built/components.css`** — a real `.hf-*` primitive layer (pill, tab, …) wired into
68
+ the README setup snippets for every stack.
69
+ - **New `portal-audit.html`** — partial progress toward the §4.6 drift audit.
70
+ - Minor token additions: split `--color-text-placeholder` (`#AEBCCF`) vs `-legacy` (`#99A1B7`);
71
+ extra shadows (`subtle`, `modal`, `outline`); badge `cancel` / `deleted` + cancellation aliases.
72
+
73
+ **Still open (verified unchanged in v2):**
74
+ - §2.1 collisions — `--text-{xs,sm,base,lg,xl,2xl}`, `--spacing-7`, `--radius-sm`, `--font-sans`
75
+ remain unprefixed. No `--target=*-additive`, no `@source not` doc. (→ §4.2)
76
+ - §2.2 doc/token drift — `ai-rules/CLAUDE.md` still says `--color-primary-default`; generator emits
77
+ `--color-primary`. (→ §4.3, §8)
78
+ - §2.3 generator ESM — still `require()`; `node generate-tokens.js` still fails under `"type":"module"`.
79
+ - `--color-text-primary` → still `text-text-primary`.
80
+ - No semantic tier (§4.1); tokens remain flat/primitive-only.
81
+
82
+ **New problems v2 introduced:**
83
+ - §2 item 7 above (component layer hardcodes + forks namespace). There are now **three** color
84
+ namespaces: `--color-badge-*` (tailwind.css/tokens.css), `--status-*` (components.css), plus the
85
+ doc references in `ai-rules`.
86
+ - `components.css` is **hand-maintained** ("extracted from components.html"), not generated —
87
+ another drift source. `generate-tokens.js` has no knowledge of components.
88
+ - **Component class names diverge from token names:** `.status-clean-*` vs token
89
+ `room-item-cleaning-*`; `.status-order-action` vs `order-action-required`.
90
+ - **Two competing visual SSOTs:** `components.html` is now authoritative and `UI_DESIGN.md` is
91
+ declared "stale" — a doc-rot risk rather than a single source of truth.
92
+
93
+ ## 3. Core thesis
94
+
95
+ Cross-project consistency breaks in two places: **distribution** (copy-folder → instant drift)
96
+ and **token naming** (collides in real projects). Everything else is downstream. The two highest-
97
+ leverage changes are:
98
+
99
+ 1. A **semantic token layer** that both new and existing projects bind to.
100
+ 2. **Versioned package distribution** that is **collision-safe by construction**.
101
+
102
+ ## 4. Proposal
103
+
104
+ ### 4.1 Three-tier token architecture (the bridge between new ↔ existing)
105
+
106
+ Adopt the W3C **DTCG** / Tokens Studio model with three explicit tiers:
107
+
108
+ | Tier | Example | Used directly by app code? |
109
+ |------|---------|----------------------------|
110
+ | **Primitive** | `--hf-blue-500: #24AFE8`, `--hf-gray-300` | No |
111
+ | **Semantic (role)** | `--hf-color-accent`, `--hf-color-bg-surface`, `--hf-color-fg-default`, `--hf-color-fg-muted`, `--hf-color-status-success` | Yes |
112
+ | **Component** (optional) | `--hf-button-bg`, `--hf-input-border` | Yes |
113
+
114
+ Why this serves both scenarios:
115
+
116
+ - **New project** consumes semantic tokens directly.
117
+ - **Existing project** aliases its legacy names onto the semantic layer — exactly the pattern
118
+ already used in `ui-hf` (`--color-hf-blue: var(--hf-color-accent)`). One semantic layer keeps
119
+ both worlds consistent.
120
+ - **Theming / multi-brand** becomes trivial: override the semantic layer per brand/mode, leave
121
+ primitives untouched. Encode modes via Tokens Studio `$themes` (light/dark, per-brand).
122
+
123
+ ### 4.2 Collision-safe by construction
124
+
125
+ This is the biggest practical fix.
126
+
127
+ - **Prefix every token** (`--hf-text-*`, `--hf-radius-*`, `--hf-space-*`, `--hf-color-*`) so it can
128
+ never shadow a Tailwind/framework default or a host-project key.
129
+ - Ship an explicit **`--target=tailwind-v4-additive`** that emits only safe keys (the filtering
130
+ `ui-hf` currently does by hand in `scripts/sync-design-system.mjs` belongs in the bundle).
131
+ - Document, for Tailwind v4 consumers, the `@source not "<path>"` rule so the bundle's own
132
+ `ui-kit.html` showcase does not leak utilities (e.g. legacy `bg-[#26ADE4]`) into the app bundle.
133
+
134
+ ### 4.3 One SSOT, generated targets, zero doc drift
135
+
136
+ - Replace the hand-rolled `generate-tokens.js` with **Style Dictionary** (mature, DTCG-native,
137
+ all targets out of the box) — and fix the ESM breakage along the way.
138
+ - **Generate or test the doc examples.** Add a snapshot test asserting every token/utility shown
139
+ in README and `ai-rules/CLAUDE.md` actually exists in the generated output. This kills the
140
+ doc-drift class of bugs (§2.2) permanently.
141
+
142
+ ### 4.4 Versioned package distribution (the mechanism that keeps N projects in sync)
143
+
144
+ - Publish `@hotelfriend/design-tokens` (and optionally `@hotelfriend/tailwind-preset`) to a private
145
+ registry, **SemVer**. Every project `pnpm update`s to the same version — *this* is what enforces
146
+ sameness.
147
+ - Keep the copy-folder bundle only as a **generated fallback** for non-npm stacks (Yii/PHP), stamped
148
+ with version + origin so its lag is visible.
149
+ - Maintain a changelog + migration notes per minor.
150
+
151
+ ### 4.5 Adapter + codemod for existing projects
152
+
153
+ Formalize the manual `ui-hf` work:
154
+
155
+ - A generated **compat layer** per project (`legacy → semantic` aliases).
156
+ - A **codemod** "hardcoded hex → token" (done by hand across ~25 files in `ui-hf`; should be a script).
157
+
158
+ ### 4.6 Conformance / drift CI (or consistency rots within a month)
159
+
160
+ - A shareable **ESLint/Stylelint rule**: forbid raw hex and arbitrary `bg-[#…]`, allow only DS tokens.
161
+ Drop it into every project.
162
+ - A **drift audit** in CI comparing a project's computed values against the DS and failing on
163
+ divergence. The README mentions a Yii-only `design-tokens-audit.md` — generalize it to all projects.
164
+
165
+ ## 5. Do we need to describe components (buttons, inputs, …)? — Yes
166
+
167
+ > **v2 status: partially started.** `components.html` (visual reference) and `pre-built/components.css`
168
+ > (`.hf-*` CSS layer) now exist — the right *direction*. But the current implementation reproduces the
169
+ > exact drift this RFC warns against (see §2 item 7 / §2.1): it hardcodes hex, forks a `--status-*`
170
+ > namespace instead of binding to `--color-badge-*`, is hand-maintained rather than generated, and its
171
+ > class names diverge from token names. The criteria below are the bar v2's component layer must meet.
172
+
173
+ Tokens guarantee consistent **values** (color, spacing, radius). They do **not** guarantee a
174
+ consistent **look** — that comes from components. So components must be described, but choose the
175
+ *right layer* for a multi-framework reality (Vue portal + Yii/PHP):
176
+
177
+ **Hard criteria for the component layer (must all hold):**
178
+
179
+ - **(a) Bind to tokens, never hardcode.** Every declaration references a canonical token
180
+ (`var(--color-badge-…)` / `var(--hf-color-…)`); zero hex literals in `components.css`.
181
+ - **(b) Generated, not hand-extracted.** The CSS layer is emitted by the generator from a single
182
+ source, so a token change propagates automatically.
183
+ - **(c) One namespace.** Drop the parallel `--status-*` set; consume the canonical color tokens directly.
184
+ - **(d) Class names equal token names.** `.status-room-item-cleaning-*` (not `.status-clean-*`),
185
+ `.status-order-action-required` (not `.status-order-action`).
186
+
187
+ **Mandatory — framework-agnostic component contracts** (the portable, highest-value layer):
188
+
189
+ - **Anatomy** — the parts of each component (button: container / label / icon / spinner).
190
+ - **Token bindings** — which token each part uses (`button.bg = --hf-color-accent`,
191
+ `button.bg-hover = --hf-color-accent-hover`, `button.radius = --hf-radius-md`). This is what makes
192
+ a button identical across frameworks.
193
+ - **All states** — default / hover / focus-visible / active / disabled / loading, with exact
194
+ declarations. `states-canonical.json` already does part of this; make it the normative source and
195
+ cover every primitive.
196
+ - **Variants & sizes** — primary / secondary / outline / danger; sm / md / lg.
197
+
198
+ **Recommended — a portable CSS component layer** (`.btn-*`, `.hf-card-*`, `.input-*`, `.hf-pill`) built
199
+ on the semantic tokens. Both a Vue app and a PHP template can apply the same class and get the same
200
+ result with zero framework lock-in. The `ai-rules` already reference these class names — make them
201
+ real and generated.
202
+
203
+ **Optional — per-framework component libraries** (Vue/React) that consume the same tokens + CSS layer.
204
+ Justified only when projects share a framework. Bridge design↔code with **Figma Code Connect**
205
+ (`.figma.ts` mappings) so the canonical component in Figma points at the canonical code.
206
+
207
+ **Recommended priority for components:** contracts (anatomy + token bindings + states) first →
208
+ portable CSS layer → framework libraries last.
209
+
210
+ Minimum set to specify first (covers ~80% of screens): Button, Input/Select, Checkbox/Radio, Card,
211
+ Modal/Dialog, Tabs, Table/DataGrid row, Status pill, Toast, Form field + error.
212
+
213
+ ## 6. Phased rollout
214
+
215
+ 1. **Phase 1 — foundation (unblocks everything):** semantic token tier (§4.1) + prefixing /
216
+ additive target (§4.2). Without these, the rest is held together with tape.
217
+ 2. **Phase 2 — distribution:** versioned npm package (§4.4); copy-folder becomes a generated artifact.
218
+ 3. **Phase 3 — integrity:** Style Dictionary + doc-example tests (§4.3) + drift CI & lint rule (§4.6).
219
+ 4. **Phase 4 — components:** contracts → CSS layer → (optional) framework libs (§5). Runs in parallel
220
+ with Phase 3. *(Partially started in v2 via `components.html` + `components.css`; must be reworked
221
+ to meet the §5 hard criteria — token-bound, generated, single namespace, matching class names.)*
222
+ 5. **Phase 5 — existing-project onboarding:** compat-layer generator + hex→token codemod (§4.5).
223
+
224
+ ## 7. Open questions
225
+
226
+ - Private registry choice for the npm package (GitHub Packages / Verdaccio / npm org)?
227
+ - Single multi-brand DS, or one base + per-product themes?
228
+ - Component layer: ship CSS-first now, or wait for a shared framework decision?
229
+ - Who owns the SSOT (`tokens.figma.json`) — Figma (Tokens Studio push) or repo, and which direction
230
+ is authoritative?
231
+
232
+ ## 8. Appendix — concrete bundle fixes (independent of the larger plan)
233
+
234
+ These are small, shippable, and worth doing regardless of how the RFC lands. Checked against v2 —
235
+ none are addressed yet:
236
+
237
+ - [ ] Align token names across README / `ai-rules/CLAUDE.md` / `pre-built/*` (`--color-primary` vs
238
+ `--color-primary-default`; `badge-*-color/-bg` vs README examples).
239
+ - [ ] Make `generate-tokens.js` run under ESM (rename to `.cjs`, or rewrite ESM, or `createRequire`).
240
+ - [ ] Rename text-color tokens to avoid `text-text-primary` (e.g. `--hf-color-fg`, `--hf-color-fg-muted`).
241
+ - [ ] Add a `--target=tailwind-v4-additive`.
242
+ - [ ] Document the `@source not` exclusion for Tailwind v4 consumers.
243
+ - [ ] Add an "existing project" setup section to the README, separate from greenfield, warning about
244
+ collisions.
245
+ - [ ] **Fix `components.css` to bind to canonical tokens** — drop the self-defined `--status-*` set
246
+ and its raw `rgb(...)`/hardcoded hex; reference `--color-badge-*` (or the §4.1 semantic tokens).
247
+ - [ ] **Normalize component class names to token names** (`.status-clean-*` → `room-item-cleaning-*`,
248
+ `.status-order-action` → `order-action-required`).
249
+ - [ ] **Generate `components.css` from the source** instead of hand-extracting it from `components.html`.
250
+ - [ ] **Resolve the two visual SSOTs** — either fold `UI_DESIGN.md` into `components.html` or mark it
251
+ explicitly non-normative, so there is one authoritative visual reference.
252
+
253
+ ## 9. Implementation status (verified 2026-05-22)
254
+
255
+ Status of each proposal against the shipped bundle. Legend: ✅ done · 🟡 partial · ⏸ deferred (needs an external decision / low ROI).
256
+
257
+ | § | Item | Status | Evidence / note |
258
+ |---|------|--------|-----------------|
259
+ | 4.1 | Three-tier token model (primitive → semantic → component) | ✅ | `tokens.figma.json` + generated `pre-built/*`; semantic `--color-hf-{fg,bg,border,…}`, component `--color-hf-input-border` |
260
+ | 4.2 | Collision-safe prefixing + additive target | ✅ | All emitted tokens `--*-hf-*`; `pre-built/tailwind.additive.css`; `@source not` documented in README |
261
+ | 4.3 | SSOT generated, zero doc drift | 🟡 | Doc-example token test ✅ (`validate-tokens.cjs` check 3). Style Dictionary migration ⏸ — custom generator covers all targets |
262
+ | 4.4 | Versioned npm package | 🟡 | `package.json` (`@hotelfriend/design-tokens`) + `publishConfig` scaffolded. Actual publish ⏸ — pending registry decision (GitHub Packages) |
263
+ | 4.5 | Adapter + hex→token codemod | ⏸ | Legacy portal unlikely to adopt; revisit if it does |
264
+ | 4.6 | Conformance / drift CI + lint rule | ✅ | `stylelint-design-system.cjs` + `validate-tokens.cjs` + `.github/workflows/design-system.yml` (build → drift check → validate). `portal-audit.html` = computed-value audit |
265
+ | 5a | Component layer binds to tokens, no hex | ✅ | `validate-tokens.cjs` check 2 enforces no bare hex |
266
+ | 5b | Component layer generated, not hand-extracted | ❌ | `components.css` is still hand-maintained; drift caught by validator, not prevented |
267
+ | 5c | One namespace (drop forked `--status-*`) | ✅ | No `--status-*` declarations; status colors come from canonical tokens |
268
+ | 5d | Class names equal token names | ✅ | `.status-room-item-cleaning-*`, `.status-order-action-required` in `status.css` |
269
+ | 8 | Appendix concrete fixes | ✅ 9/10 | Only "auto-generate components.css" (= 5b) outstanding |
270
+
271
+ **Phases:** 1 (foundation) ✅ · 2 (npm publish) ⏸ · 3 (Style Dictionary ⏸ / doc-drift test ✅ / drift CI ✅) · 4 (components: started, not yet generated) · 5 (onboarding) ⏸.
272
+
273
+ **True remaining work (intentional, needs external decisions):** GitHub Packages publish (§4.4 — registry decision), Figma Code Connect (§5 optional — needs designer's Figma file), Style Dictionary (§4.3 — premature), legacy codemod (§4.5 — low ROI), auto-generate `components.css` (§5b — validator covers drift).