@kondeio/kdf 0.1.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/CONTRIBUTING.md +57 -0
  3. package/LICENSE +21 -0
  4. package/README.md +374 -0
  5. package/SECURITY.md +53 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +58 -0
  8. package/dist/css-generator.d.ts +17 -0
  9. package/dist/css-generator.js +59 -0
  10. package/dist/index.d.ts +48 -0
  11. package/dist/index.js +62 -0
  12. package/dist/plugin.d.ts +30 -0
  13. package/dist/plugin.js +40 -0
  14. package/dist/postinstall.d.ts +1 -0
  15. package/dist/postinstall.js +48 -0
  16. package/dist/resolver.d.ts +11 -0
  17. package/dist/resolver.js +184 -0
  18. package/dist/types.d.ts +44 -0
  19. package/dist/types.js +1 -0
  20. package/docs/kdf-doc.md +653 -0
  21. package/docs/kdf-skill.md +374 -0
  22. package/example/bootstrap/globals.css +12 -0
  23. package/example/bootstrap/hero-section.tsx +38 -0
  24. package/example/bootstrap/kdf/homepage.json +35 -0
  25. package/example/bootstrap/kdf/shared/button.json +10 -0
  26. package/example/bootstrap/kdf/shared/card.json +4 -0
  27. package/example/bootstrap/kdf/shared/color.json +17 -0
  28. package/example/bootstrap/kdf/shared/layout.json +7 -0
  29. package/example/bootstrap/kdf/shared/typography.json +8 -0
  30. package/example/bootstrap/konde-server.css +3 -0
  31. package/example/bootstrap/konde.css +3 -0
  32. package/example/preview.ts +428 -0
  33. package/example/pure-css/hero-section.tsx +40 -0
  34. package/example/pure-css/kdf/homepage.json +20 -0
  35. package/example/pure-css/kdf/shared/button.json +10 -0
  36. package/example/pure-css/kdf/shared/card.json +4 -0
  37. package/example/pure-css/kdf/shared/color.json +11 -0
  38. package/example/pure-css/kdf/shared/typography.json +8 -0
  39. package/example/pure-css/konde-server.css +3 -0
  40. package/example/pure-css/konde.css +3 -0
  41. package/example/pure-css/styles.css +34 -0
  42. package/example/sample-pages/about.json +30 -0
  43. package/example/sample-pages/dashboard.json +12 -0
  44. package/example/sample-pages/pricing.json +28 -0
  45. package/example/shadcn/globals.css +15 -0
  46. package/example/shadcn/hero-section.tsx +34 -0
  47. package/example/shadcn/konde-server.css +3 -0
  48. package/example/shadcn/konde.css +3 -0
  49. package/example/tailwind/globals.css +5 -0
  50. package/example/tailwind/hero-section.tsx +34 -0
  51. package/example/tailwind/konde-server.css +3 -0
  52. package/example/tailwind/konde.css +3 -0
  53. package/kdf/homepage.json +25 -0
  54. package/kdf/shared/button.json +10 -0
  55. package/kdf/shared/card.json +4 -0
  56. package/kdf/shared/color.json +17 -0
  57. package/kdf/shared/layout.json +7 -0
  58. package/kdf/shared/typography.json +8 -0
  59. package/package.json +77 -0
@@ -0,0 +1,653 @@
1
+ # Konde Design Framework
2
+
3
+ Package: `@kondeio/kdf`
4
+ License: MIT
5
+
6
+ Konde Design Framework, or KDF, is a JSON-based design coordination layer for
7
+ Node/server-side web apps and agent-assisted UI work.
8
+
9
+ KDF does for design consistency what i18n does for translation consistency:
10
+ move repeatable UI decisions out of scattered component files and into a stable
11
+ source of truth that both users and agents can read.
12
+
13
+ ## Goal
14
+
15
+ Design control and consistency through JSON as the shared language between users
16
+ and agents.
17
+
18
+ - Users own design direction and approval.
19
+ - Users can edit JSON directly when they want precise control.
20
+ - Agents read KDF JSON and implement UI from it.
21
+ - Code renders the approved design.
22
+ - `data-kdf` maps DOM elements back to exact JSON paths.
23
+ - One source of truth keeps every page, component, and agent session consistent.
24
+
25
+ KDF should reduce chat iteration, not create a new design bureaucracy.
26
+
27
+ ## The Problem
28
+
29
+ Without KDF, classes live across many `.tsx` files:
30
+
31
+ ```tsx
32
+ <section className="mx-auto max-w-6xl px-6 py-20">
33
+ <h1 className="text-5xl font-semibold tracking-tight">
34
+ ```
35
+
36
+ That works for a single human editor, but it is fragile for agentic work:
37
+
38
+ - agents improvise colors, spacing, typography, and layout
39
+ - users correct the result through chat
40
+ - every page drifts slightly
41
+ - changes require searching component files
42
+ - repeated sessions lose design intent
43
+
44
+ The result is endless "bigger", "more blue", "move left" iteration.
45
+
46
+ ## The Solution
47
+
48
+ KDF makes design explicit:
49
+
50
+ ```json
51
+ {
52
+ "hero": {
53
+ "wrapper": "mx-auto max-w-6xl px-6 py-20",
54
+ "title": "text-5xl font-semibold tracking-tight"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ```tsx
60
+ const d = getDesign("homepage");
61
+
62
+ <section data-kdf="hero.wrapper" className={d("hero.wrapper")}>
63
+ <h1 data-kdf="hero.title" className={d("hero.title")}>
64
+ ```
65
+
66
+ The rendered UI still uses normal CSS. The difference is ownership:
67
+
68
+ - JSON defines the design.
69
+ - Code renders the design.
70
+ - Agents implement from JSON instead of guessing.
71
+ - Users adjust JSON instead of describing visual corrections repeatedly.
72
+
73
+ ## Why Not Just a Design Library?
74
+
75
+ A design library says:
76
+
77
+ ```text
78
+ This is a Button.
79
+ ```
80
+
81
+ KDF says:
82
+
83
+ ```text
84
+ This is homepage.hero.cta-primary, rendered as a Button, styled with this token.
85
+ ```
86
+
87
+ | Approach | What it says | Mapping |
88
+ | --- | --- | --- |
89
+ | Design library | "This is a Button" | Generic component, many possible overrides |
90
+ | KDF | "This is `hero.cta-primary` on `homepage`" | One element maps to one design key |
91
+
92
+ Design libraries provide component primitives. They do not always enforce which
93
+ style belongs to which element on which page. KDF adds that missing mapping.
94
+
95
+ ```text
96
+ Design library:
97
+ Button component -> generic styles
98
+ -> used in many places
99
+ -> every developer or agent can override differently
100
+ -> no exact map from element to design decision
101
+
102
+ KDF:
103
+ data-kdf="hero.cta-primary" -> d("hero.cta-primary") -> one JSON key
104
+ -> one element, one design token
105
+ -> scanner and agent can find the source
106
+ ```
107
+
108
+ KDF is not a replacement for a component library. It can sit above shadcn,
109
+ Bootstrap, Chakra, plain CSS, Tailwind, or a custom design system.
110
+
111
+ ## What KDF Does
112
+
113
+ KDF translates repeated design decisions from component files into JSON.
114
+
115
+ ```text
116
+ i18n:
117
+ hardcoded text in JSX -> language keys in JSON -> t("hero.headline")
118
+
119
+ KDF:
120
+ hardcoded classes in JSX -> design tokens in JSON -> d("hero.title")
121
+ ```
122
+
123
+ KDF stores:
124
+
125
+ - class names
126
+ - shared style references
127
+ - page section order
128
+ - CSS custom properties
129
+ - optional component identity metadata
130
+
131
+ KDF does not store:
132
+
133
+ - business logic
134
+ - event handlers
135
+ - data fetching
136
+ - permissions
137
+ - accessibility behavior
138
+ - component implementation
139
+
140
+ ## Architecture
141
+
142
+ Design tokens live in two places:
143
+
144
+ ```text
145
+ kdf/
146
+ shared/
147
+ button.json
148
+ card.json
149
+ color.json
150
+ layout.json
151
+ typography.json
152
+ homepage.json
153
+ konde-server.css
154
+ konde.css
155
+ ```
156
+
157
+ | File | Role |
158
+ | --- | --- |
159
+ | `kdf/shared/*.json` | reusable component and style defaults |
160
+ | `kdf/<page>.json` | page-specific composition and overrides |
161
+ | `kdf/konde-server.css` | critical first-paint CSS overrides |
162
+ | `kdf/konde.css` | non-critical user CSS overrides |
163
+
164
+ Symbols:
165
+
166
+ | Symbol | Meaning | Example |
167
+ | --- | --- | --- |
168
+ | `$` | component identity metadata | `"$": "Button"` |
169
+ | `className` | rendered class string | `"@button.cta shadow-xl"` |
170
+ | `@` | shared style reference | `"@button.cta"` |
171
+ | `$layout` | page section order and visibility | `["hero", "footer"]` |
172
+ | `css` | CSS custom properties | `{ "--kdf-accent": "#4F46E5" }` |
173
+
174
+ `$` is metadata for agents and host tooling. The runtime accessor `d(path)`
175
+ returns the resolved `className`, and `d.css(path)` returns the `css` object.
176
+
177
+ ## JSON Token Forms
178
+
179
+ ### Class string
180
+
181
+ Use a string when a token only needs classes:
182
+
183
+ ```json
184
+ {
185
+ "hero": {
186
+ "wrapper": "flex flex-col gap-8 lg:flex-row"
187
+ }
188
+ }
189
+ ```
190
+
191
+ ```tsx
192
+ <section data-kdf="hero.wrapper" className={d("hero.wrapper")} />
193
+ ```
194
+
195
+ ### Object token
196
+
197
+ Use an object when the token needs metadata, classes, or CSS custom properties:
198
+
199
+ ```json
200
+ {
201
+ "hero": {
202
+ "title": {
203
+ "$": "h1",
204
+ "className": "@typography.h1"
205
+ },
206
+ "cta-primary": {
207
+ "$": "Button",
208
+ "className": "@button.cta"
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ ### CSS custom properties
215
+
216
+ Use `css` when values should be applied as inline style variables:
217
+
218
+ ```json
219
+ {
220
+ "hero": {
221
+ "title": {
222
+ "className": "text-3xl",
223
+ "css": {
224
+ "--kdf-accent": "#4F46E5"
225
+ }
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+ ```tsx
232
+ <h1
233
+ data-kdf="hero.title"
234
+ className={d("hero.title")}
235
+ style={d.css("hero.title")}
236
+ />
237
+ ```
238
+
239
+ ## Shared References
240
+
241
+ Shared references are written with `@`:
242
+
243
+ ```json
244
+ {
245
+ "hero": {
246
+ "cta-primary": "@button.cta",
247
+ "cta-large": "@button.cta text-lg shadow-xl"
248
+ }
249
+ }
250
+ ```
251
+
252
+ `@button.cta` resolves from:
253
+
254
+ ```text
255
+ kdf/shared/button.json -> cta
256
+ ```
257
+
258
+ References can chain through other references. Resolution stops after a small
259
+ depth limit to avoid loops.
260
+
261
+ The component segment is validated. A ref like `@../../secret.value` is rejected
262
+ instead of escaping the `shared/` directory.
263
+
264
+ ## Multi-Level Shared Tokens
265
+
266
+ KDF supports a shared-token cascade for apps with multiple design directories.
267
+
268
+ ```text
269
+ designs/
270
+ shared/
271
+ button.json <- fallback for all templates
272
+ typography.json
273
+ lander/
274
+ shared/
275
+ button.json <- lander-only override
276
+ homepage.json
277
+ newlander/
278
+ homepage.json <- uses root shared fallback
279
+ ```
280
+
281
+ Resolution order for `@button.cta`:
282
+
283
+ 1. `<KDF_DIR>/shared/button.json`
284
+ 2. `<KDF_DIR>/../shared/button.json`
285
+ 3. current page tokens
286
+
287
+ This means a template can override only the parts it needs and inherit the rest.
288
+
289
+ ## Multi-Template Design
290
+
291
+ Like i18n can switch languages, KDF can switch design templates.
292
+
293
+ ```text
294
+ languages/en/ -> designs/lander/
295
+ languages/id/ -> designs/newlander/
296
+ activate language activate design template
297
+ ```
298
+
299
+ ```text
300
+ designs/
301
+ lander/
302
+ shared/
303
+ homepage.json
304
+ newlander/
305
+ shared/
306
+ homepage.json
307
+ ```
308
+
309
+ Same app, same components, same code. Point `KDF_DIR` to another design folder:
310
+
311
+ ```ts
312
+ export default withKDF({ dir: "./designs/lander" })(nextConfig);
313
+ ```
314
+
315
+ Switching templates should be a deliberate host-app decision. KDF provides the
316
+ folder model and resolver behavior.
317
+
318
+ ## Page Layout
319
+
320
+ `$layout` controls section order and visibility:
321
+
322
+ ```json
323
+ {
324
+ "$layout": ["hero", "features", "footer"],
325
+ "hero": {},
326
+ "features": {},
327
+ "footer": {}
328
+ }
329
+ ```
330
+
331
+ Rules:
332
+
333
+ - sections listed in `$layout` render
334
+ - sections missing from `$layout` are hidden by the host app
335
+ - section order follows array order
336
+ - section token data can remain even when a section is hidden
337
+
338
+ The application still decides how to render sections. KDF provides the design
339
+ and order data.
340
+
341
+ ## Why Nested Structure Matters
342
+
343
+ JSON nesting is functional, not just tidy.
344
+
345
+ 1. `$layout` uses top-level section keys for page order and visibility.
346
+ 2. Dot paths scope repeated names like `hero.title` and `footer.title`.
347
+ 3. `data-kdf` maps DOM nodes directly to JSON paths.
348
+ 4. Agents and scanners can locate the exact design source for an element.
349
+ 5. One section can be replaced without rewriting unrelated tokens.
350
+
351
+ Nested JSON turns page structure into data.
352
+
353
+ ## `data-kdf`
354
+
355
+ Every element using a KDF token must include the matching `data-kdf` path:
356
+
357
+ ```tsx
358
+ <h1 data-kdf="hero.title" className={d("hero.title")}>
359
+ ```
360
+
361
+ This enables:
362
+
363
+ - DOM to JSON path mapping
364
+ - Playwright assertions
365
+ - scanner validation
366
+ - agent review
367
+ - visual tooling that targets the exact design token
368
+
369
+ If an element uses `d("hero.title")` but lacks
370
+ `data-kdf="hero.title"`, treat it as invalid KDF usage.
371
+
372
+ ## Konde CSS Override Files
373
+
374
+ KDF creates two CSS files during initialization. Both are user-owned. KDF never
375
+ overwrites them after creation.
376
+
377
+ | File | Intended loading | Purpose |
378
+ | --- | --- | --- |
379
+ | `konde-server.css` | imported by the app for first paint | critical CSS variables and no-FOUC overrides |
380
+ | `konde.css` | loaded after framework/app CSS | non-critical tweaks, experiments, escape hatches |
381
+
382
+ Examples:
383
+
384
+ ```css
385
+ /* konde-server.css */
386
+ :root { --kdf-primary: #4F46E5; }
387
+ [data-kdf="hero.slider"] { display: none; }
388
+ ```
389
+
390
+ ```css
391
+ /* konde.css */
392
+ [data-kdf="hero.title"] { letter-spacing: -0.02em; }
393
+ [data-kdf="hero.wrapper"] { gap: 3rem; }
394
+ ```
395
+
396
+ The Next.js plugin exposes paths through environment values. It does not inject
397
+ CSS automatically. The host app wires imports or links explicitly.
398
+
399
+ ## Color Definitions
400
+
401
+ KDF JSON stores class names, not CSS engine internals. Color tokens can point to
402
+ whatever the host styling system understands.
403
+
404
+ Tailwind:
405
+
406
+ ```json
407
+ {
408
+ "primary-text": "text-[#4F46E5]",
409
+ "brand-bg": "bg-[var(--kdf-primary)]"
410
+ }
411
+ ```
412
+
413
+ Bootstrap:
414
+
415
+ ```json
416
+ {
417
+ "primary-text": "text-primary",
418
+ "cta": "btn btn-primary"
419
+ }
420
+ ```
421
+
422
+ Plain CSS:
423
+
424
+ ```json
425
+ {
426
+ "primary-text": "kdf-text-primary"
427
+ }
428
+ ```
429
+
430
+ CSS variables belong in app CSS:
431
+
432
+ ```css
433
+ :root { --kdf-primary: #4F46E5; }
434
+ ```
435
+
436
+ Then JSON can reference them through framework-compatible classes:
437
+
438
+ ```json
439
+ {
440
+ "title": "text-[var(--kdf-primary)]"
441
+ }
442
+ ```
443
+
444
+ ## CSS Framework Scanning
445
+
446
+ If a styling framework generates CSS by scanning source files, make it scan KDF
447
+ JSON files too. Otherwise classes stored in JSON may not be generated.
448
+
449
+ Tailwind v4:
450
+
451
+ ```css
452
+ @import "tailwindcss";
453
+ @source "../kdf/**/*.json";
454
+ ```
455
+
456
+ Tailwind v3:
457
+
458
+ ```ts
459
+ export default {
460
+ content: [
461
+ "./src/**/*.{ts,tsx}",
462
+ "./kdf/**/*.json"
463
+ ]
464
+ };
465
+ ```
466
+
467
+ ## Runtime API
468
+
469
+ KDF core runs in Node/server-side JavaScript because it reads JSON from disk.
470
+
471
+ ```ts
472
+ import { getDesign, cn, clearKdfCache } from "@kondeio/kdf";
473
+
474
+ const d = getDesign("homepage");
475
+ ```
476
+
477
+ Accessor behavior:
478
+
479
+ ```ts
480
+ d("hero.title"); // resolved className string
481
+ d.css("hero.title"); // CSS custom properties object
482
+ ```
483
+
484
+ Server-only rule:
485
+
486
+ - use `getDesign()` in server-rendered code
487
+ - do not call it directly inside browser-only Client Components
488
+ - resolve classes server-side and pass strings down when needed
489
+
490
+ Cache options:
491
+
492
+ ```ts
493
+ const d = getDesign("homepage", {
494
+ cache: "auto",
495
+ maxAgeMs: 250
496
+ });
497
+ ```
498
+
499
+ Cache modes:
500
+
501
+ ```text
502
+ auto production caches forever; development revalidates by mtime/size
503
+ always cache for the lifetime of the process
504
+ none bypass cache
505
+ ```
506
+
507
+ Use `clearKdfCache()` for explicit invalidation in custom tooling.
508
+
509
+ ## Class Composition
510
+
511
+ KDF includes small class composition helpers:
512
+
513
+ ```ts
514
+ import { cn, cx, composeClasses, dedupeClasses, createClassComposer } from "@kondeio/kdf";
515
+ ```
516
+
517
+ Use `cn()` when combining KDF tokens with conditional classes:
518
+
519
+ ```tsx
520
+ className={cn(d("button.base"), active && d("button.active"), className)}
521
+ ```
522
+
523
+ Helpers:
524
+
525
+ - `cn()` joins conditional class values, drops falsy values, normalizes
526
+ whitespace, and removes exact duplicate classes.
527
+ - `cx()` is an alias of `cn()`.
528
+ - `composeClasses()` is the same default composer with an explicit name.
529
+ - `dedupeClasses()` removes exact duplicates from an existing class string.
530
+ - `createClassComposer({ merge })` creates a composer with an app-defined merge
531
+ step.
532
+
533
+ KDF intentionally does not understand semantic utility conflicts. Apps that
534
+ need Tailwind-specific or project-specific merging can inject their own rules.
535
+
536
+ ## UI Library Compatibility
537
+
538
+ KDF is UI-library agnostic because it stores design tokens and metadata, not
539
+ component implementations.
540
+
541
+ shadcn-style metadata:
542
+
543
+ ```json
544
+ {
545
+ "cta": {
546
+ "$": "Button",
547
+ "variant": "default",
548
+ "className": "@button.cta"
549
+ }
550
+ }
551
+ ```
552
+
553
+ Bootstrap:
554
+
555
+ ```json
556
+ {
557
+ "cta": {
558
+ "$": "button",
559
+ "className": "btn btn-primary btn-lg"
560
+ }
561
+ }
562
+ ```
563
+
564
+ Plain CSS:
565
+
566
+ ```json
567
+ {
568
+ "cta": {
569
+ "$": "button",
570
+ "className": "my-btn my-btn--primary"
571
+ }
572
+ }
573
+ ```
574
+
575
+ KDF gives the host app stable design data. The host app still owns actual
576
+ component rendering.
577
+
578
+ ## Full Page Layout Example
579
+
580
+ KDF can describe a complete page structure:
581
+
582
+ ```json
583
+ {
584
+ "$layout": ["hero", "section-a", "footer"],
585
+ "page": {
586
+ "wrapper": "flex min-h-screen"
587
+ },
588
+ "hero": {
589
+ "wrapper": "flex flex-col gap-8 lg:flex-row lg:items-center",
590
+ "image-wrapper": "w-full lg:w-1/2 order-1",
591
+ "content": "w-full lg:w-1/2 order-2",
592
+ "title": "@typography.h1",
593
+ "description": "mt-4 @typography.body",
594
+ "actions": "mt-6 flex gap-3",
595
+ "cta-primary": "@button.base @button.cta @button.md",
596
+ "cta-secondary": "@button.base @button.outline @button.md"
597
+ },
598
+ "section-a": {
599
+ "wrapper": "mt-16 border-t pt-16",
600
+ "title": "@typography.h2",
601
+ "content": "mt-4 @typography.body max-w-2xl"
602
+ },
603
+ "footer": {
604
+ "wrapper": "mt-16 border-t py-8",
605
+ "nav": "flex flex-wrap gap-6 justify-center",
606
+ "nav-item": "text-sm text-gray-500 hover:text-gray-700"
607
+ }
608
+ }
609
+ ```
610
+
611
+ Design changes that can happen in JSON:
612
+
613
+ - hide `section-a` by removing it from `$layout`
614
+ - reorder page sections by changing `$layout`
615
+ - swap hero image/content order with classes
616
+ - change CTA style through shared `button.json`
617
+
618
+ ## Installation
619
+
620
+ Install the package:
621
+
622
+ ```bash
623
+ npm install @kondeio/kdf
624
+ pnpm add @kondeio/kdf
625
+ bun add @kondeio/kdf
626
+ ```
627
+
628
+ The install initializer creates starter files only when the target `kdf/`
629
+ folder does not already exist. Existing files are never overwritten.
630
+
631
+ Manual initialization:
632
+
633
+ ```bash
634
+ npx kdf init
635
+ ```
636
+
637
+ Custom design directory:
638
+
639
+ ```bash
640
+ KDF_DIR=./designs npx kdf init
641
+ ```
642
+
643
+ Next.js config:
644
+
645
+ ```ts
646
+ import withKDF from "@kondeio/kdf/plugin";
647
+
648
+ export default withKDF({ dir: "./designs" })(nextConfig);
649
+ ```
650
+
651
+ ## License
652
+
653
+ KDF uses MIT. The root `LICENSE` file is the source of truth.