@takazudo/zdtp 0.4.14 → 0.5.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 (115) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/PORTABLE-CONTRACT.md +386 -91
  3. package/README.md +268 -95
  4. package/dist/apply/build-apply-overrides.d.ts +2 -2
  5. package/dist/apply/compute-hunks.d.ts +23 -0
  6. package/dist/apply/last-applied.d.ts +5 -0
  7. package/dist/apply/reconcile-applied.d.ts +19 -0
  8. package/dist/apply-modal.d.ts +2 -48
  9. package/dist/astro/host-adapter.js +3 -3
  10. package/dist/astro/index.js +2 -2
  11. package/dist/{autoload-state-IlkEci88.js → autoload-state-DmTY6tRy.js} +1 -1
  12. package/dist/bin/server.js +52 -50
  13. package/dist/bulk/bulk-actions.d.ts +45 -0
  14. package/dist/bulk/index.d.ts +2 -0
  15. package/dist/chain/index.d.ts +2 -0
  16. package/dist/chain/token-chain-context.d.ts +19 -0
  17. package/dist/chain/token-chain-popover.d.ts +22 -0
  18. package/dist/changed/contribution.d.ts +32 -0
  19. package/dist/changed/footer-content.d.ts +16 -0
  20. package/dist/changed/index.d.ts +4 -0
  21. package/dist/changed/tab-badge.d.ts +8 -0
  22. package/dist/changed/tab-filter.d.ts +8 -0
  23. package/dist/config/panel-config.d.ts +23 -7
  24. package/dist/constants.d.ts +66 -0
  25. package/dist/constants.js +30 -0
  26. package/dist/controls/actions-menu-popover.d.ts +2 -0
  27. package/dist/controls/role-button.d.ts +5 -2
  28. package/dist/controls/tooltip.d.ts +8 -4
  29. package/dist/element-inspect/element-inspect-context.d.ts +22 -0
  30. package/dist/element-inspect/element-inspect-orchestrator.d.ts +17 -0
  31. package/dist/element-inspect/element-inspect-overlay.d.ts +16 -0
  32. package/dist/element-inspect/element-inspect-toggle-button.d.ts +3 -0
  33. package/dist/element-inspect/element-inspect-view.d.ts +6 -0
  34. package/dist/element-inspect/find-tokens-for-element.d.ts +23 -0
  35. package/dist/element-inspect/index.d.ts +8 -0
  36. package/dist/highlight/find-elements.d.ts +14 -3
  37. package/dist/highlight/highlight-orchestrator.d.ts +7 -3
  38. package/dist/highlight/highlight-state.d.ts +3 -2
  39. package/dist/highlight/highlight-toggle-button.d.ts +3 -0
  40. package/dist/highlight/walk-css-rules.d.ts +2 -0
  41. package/dist/history/buttons.d.ts +28 -0
  42. package/dist/history/index.d.ts +3 -0
  43. package/dist/history/rail.d.ts +13 -0
  44. package/dist/history/snapshots.d.ts +57 -0
  45. package/dist/host/host-mutations.d.ts +23 -0
  46. package/dist/{index-EdhZv8Ru.js → index-By6zFdp4.js} +2 -2
  47. package/dist/index-CHzTi0y5.js +12184 -0
  48. package/dist/index.js +35 -34
  49. package/dist/load-routing-BtCE1hGI.js +547 -0
  50. package/dist/{manifest-DCReQE0k.js → manifest-DvuKi7I4.js} +11 -7
  51. package/dist/{panel-config-SgA84Uqe.js → panel-config-BSu6TUht.js} +391 -317
  52. package/dist/picker/alt-click-picker.d.ts +10 -2
  53. package/dist/picker/arming-coordinator.d.ts +2 -0
  54. package/dist/picker/index.d.ts +2 -2
  55. package/dist/search/command-palette.d.ts +21 -0
  56. package/dist/search/contribution.d.ts +7 -0
  57. package/dist/search/fuzzy.d.ts +10 -0
  58. package/dist/search/index.d.ts +4 -0
  59. package/dist/search/match-bar.d.ts +14 -0
  60. package/dist/search/search-header.d.ts +7 -0
  61. package/dist/search/token-search.d.ts +36 -0
  62. package/dist/server/create-apply-handler.d.ts +33 -0
  63. package/dist/server/index.d.ts +2 -1
  64. package/dist/server/index.js +1 -1
  65. package/dist/shell/dock-mode-switch.d.ts +6 -0
  66. package/dist/shell/footer.d.ts +1 -0
  67. package/dist/shell/ghost-idle.d.ts +24 -0
  68. package/dist/shell/header.d.ts +4 -0
  69. package/dist/shell/layer-activity.d.ts +6 -0
  70. package/dist/shell/mini-pill.d.ts +21 -0
  71. package/dist/shell/regions.d.ts +26 -0
  72. package/dist/shell/shortcut-dispatcher.d.ts +17 -0
  73. package/dist/shell/tab-bar.d.ts +17 -0
  74. package/dist/shell/tab-overflow-popover.d.ts +13 -0
  75. package/dist/specimen/on-page-specimen.d.ts +19 -0
  76. package/dist/specimen/preview-glyphs.d.ts +2 -0
  77. package/dist/specimen/specimen-state.d.ts +12 -0
  78. package/dist/specimen/specimen-tab-body.d.ts +18 -0
  79. package/dist/specimen/specimen-toolbar.d.ts +10 -0
  80. package/dist/specimen/specimen-values.d.ts +9 -0
  81. package/dist/state/history.d.ts +64 -0
  82. package/dist/state/persist.d.ts +10 -14
  83. package/dist/state/transaction.d.ts +24 -0
  84. package/dist/state/tweak-state.d.ts +16 -0
  85. package/dist/styles/z-index-tokens.d.ts +2 -0
  86. package/dist/tabs/color-tab.d.ts +10 -1
  87. package/dist/tabs/flat/flat-tab.d.ts +27 -0
  88. package/dist/tabs/flat/index.d.ts +8 -0
  89. package/dist/tabs/flat/scroll-to-token-row.d.ts +3 -0
  90. package/dist/tabs/flat/tier-section.d.ts +17 -0
  91. package/dist/tabs/flat/token-controller.d.ts +23 -0
  92. package/dist/tabs/flat/token-row.d.ts +12 -0
  93. package/dist/tabs/flat/types.d.ts +25 -0
  94. package/dist/tabs/font-tab.d.ts +13 -12
  95. package/dist/tabs/generic-tab.d.ts +8 -38
  96. package/dist/tabs/palette/palette-check-view.d.ts +5 -1
  97. package/dist/tabs/palette/palette-edit-view.d.ts +9 -1
  98. package/dist/tabs/palette/palette-tab.d.ts +12 -1
  99. package/dist/tabs/size-tab.d.ts +7 -10
  100. package/dist/tabs/spacing-tab.d.ts +7 -13
  101. package/dist/testing.js +3 -3
  102. package/dist/tokens/tier-model.d.ts +6 -0
  103. package/dist/{tweak-state-DGLrzIwq.js → tweak-state-B7ok3Ob3.js} +473 -423
  104. package/dist/utils/numeric-transform.d.ts +32 -0
  105. package/dist/utils/token-diff.d.ts +25 -0
  106. package/dist/utils/token-graph.d.ts +23 -0
  107. package/dist/utils/token-index.d.ts +30 -0
  108. package/dist/zdtp.css +1 -1
  109. package/package.json +8 -1
  110. package/dist/controls/pill-slider-row.d.ts +0 -36
  111. package/dist/controls/select-row.d.ts +0 -25
  112. package/dist/controls/slider-row.d.ts +0 -29
  113. package/dist/controls/text-row.d.ts +0 -27
  114. package/dist/index-Ds4bCeiC.js +0 -7591
  115. package/dist/load-routing-D4H2VOl5.js +0 -426
@@ -25,6 +25,11 @@ one page: call it with a distinct `storagePrefix` to register a new instance;
25
25
  call it with the same prefix and equal config for an idempotent no-op.
26
26
 
27
27
  ```ts
28
+ export interface PanelDockConfig {
29
+ /** Reserve host-document space for a right/bottom dock. Defaults to body-margin. */
30
+ reflow?: 'body-margin' | 'none';
31
+ }
32
+
28
33
  export interface PanelConfig {
29
34
  /** Base for every derived storage key. Also the instance id. See §2. */
30
35
  storagePrefix: string;
@@ -32,7 +37,7 @@ export interface PanelConfig {
32
37
  consoleNamespace: string;
33
38
  /** BEM-style prefix used by every modal in the panel (export / import / apply). */
34
39
  modalClassPrefix: string;
35
- /** `$schema` value emitted into export JSON and required on import. */
40
+ /** Display-only label returned by getDesignTokenSchema(); not used by built-in UI or serde validation. */
36
41
  schemaId: string;
37
42
  /** Default filename base — exports save as `${exportFilenameBase}.json`. */
38
43
  exportFilenameBase: string;
@@ -63,10 +68,9 @@ export interface PanelConfig {
63
68
  */
64
69
  colorPresets?: Record<string, ColorScheme>;
65
70
  /**
66
- * Optional dev-API endpoint URL. When the host wires the panel into a
67
- * project that ships its own design-tokens-apply route, supply the URL
68
- * here; the Apply button POSTs its diff payload to it. When `undefined`,
69
- * the Apply button stays disabled with a tooltip.
71
+ * Optional dev-API endpoint URL. When supplied together with a non-empty
72
+ * `applyRouting` map, the Apply button POSTs its diff payload to it. When
73
+ * either field is absent, the button stays disabled with a tooltip.
70
74
  */
71
75
  applyEndpoint?: string;
72
76
  /**
@@ -96,10 +100,12 @@ export interface PanelConfig {
96
100
  /** Optional host Tailwind v4 theme CSS used by the lazy side for suggestions. */
97
101
  themeCss?: string;
98
102
  };
103
+ /** Optional dock integration; omitted values use body-margin host reflow. */
104
+ dock?: PanelDockConfig;
99
105
  /**
100
106
  * Optional apply sink. Routes this instance's CSS-var writes and clears
101
107
  * through the caller-supplied object instead of `document.documentElement`.
102
- * See §3.5 for the full sink contract.
108
+ * See §3.6 for the full sink contract.
103
109
  *
104
110
  * NOTE: this field carries a function reference and is therefore NOT
105
111
  * JSON-serializable. It cannot pass through Astro's inline JSON config.
@@ -126,7 +132,7 @@ export interface PanelConfig {
126
132
 
127
133
  /**
128
134
  * Apply sink — routes CSS-var writes for one panel instance somewhere other
129
- * than the host `:root`. See §3.5.
135
+ * than the host `:root`. See §3.6.
130
136
  */
131
137
  export interface ApplySink {
132
138
  /** Upsert the given var name→value pairs on the sink target. */
@@ -270,9 +276,29 @@ derives the keys at runtime from this single base.
270
276
  | `state-v1` | `${storagePrefix}-state` | tweak-state (legacy) | Pre-v2 flat-state format (Color-only). When selected, it is written to `state-v3` and the v1 key is deleted, then the result is copied into v4. |
271
277
  | `open` | `${storagePrefix}-open` | panel | Mirror of the panel's `open` boolean state (so the next mount opens directly into the user's last state without a post-render toggle dispatch). |
272
278
  | `position` | `${storagePrefix}-position` | panel | Drag position (`{ top, left }`) so the panel reappears where the user left it. |
279
+ | `size` | `${storagePrefix}-size` | panel | Floating shell dimensions (`{ width, height }`) in pixels. |
280
+ | `dock` | `${storagePrefix}-dock` | panel | Presentation mode: `'float'`, `'right'`, `'bottom'`, or `'mini'`. |
281
+ | `dock-size` | `${storagePrefix}-dock-size` | panel | Right/bottom dock dimensions (`{ right, bottom }`), defaulting to `{ right: 440, bottom: 340 }`. |
282
+ | `density` | `${storagePrefix}-density` | panel | Tab-grid density preference (`0`, `1`, or `2`). |
283
+ | `ghost` | `${storagePrefix}-ghost` | shell | Ghost-when-idle preference (`'1'` when enabled). |
284
+ | `specimen` | `${storagePrefix}-specimen` | specimen | Font specimen toolbar JSON: `{ text, preset, overridden, width }`; width is clamped to 240–720. |
285
+ | `snapshot-a` | `${storagePrefix}-snapshot-a` | snapshots | Persisted A snapshot: `{ state, identity, savedAt, edits }`. |
286
+ | `snapshot-b` | `${storagePrefix}-snapshot-b` | snapshots | Persisted B snapshot: `{ state, identity, savedAt, edits }`. |
287
+ | `last-applied` | `${storagePrefix}-last-applied` | apply | Flat comparison baseline; a successful apply resets it to `{}` while unconfirmed overrides remain in live state. |
273
288
  | `visible` | `${storagePrefix}:visible` | adapter | Adapter-level visibility-intent flag, owned by the lazy-load gate (§6). |
274
289
  | `autoload` | `${storagePrefix}:autoload` | autoload-state | Owner-mode autoload flag. `'1'` (explicit, set by `enableAutoload()`) or `'auto'` (auto-remembered, set by opening the panel — see §6.2) both mean "load the panel bundle eagerly and mount CLOSED on every page load." `enableAutoload()` / `disableAutoload()` manage the explicit value; `disableAutoload()` clears either. See §6.2. |
290
+ | `elpath-enabled` | `${storagePrefix}-elpath-enabled` | element-path-state | Element-path picker enabled bit. |
275
291
  | `domtweaker-enabled` | `${storagePrefix}-domtweaker-enabled` | dom-tweaker-state | DOM Tweaker enabled bit. `'1'` means "mount the closed shell and load the DOM Tweaker lazy boundary." Only meaningful when `PanelConfig.domTweaker` is present. |
292
+ | `highlight-slots` | `${storagePrefix}-highlight-slots` | highlight-state | Ten highlight slot colors in local storage. |
293
+ | `highlight-outline-width` | `${storagePrefix}-highlight-outline-width` | highlight-state | Global highlight outline width in local storage, clamped to 1–20. |
294
+ | `highlight-active` | `${storagePrefix}-highlight-active` | highlight-state | Active CSS-variable-to-slot map in session storage. |
295
+
296
+ This is the current storage-key inventory, not a release-history table. The
297
+ published 0.4.14 bundle already wrote `${storagePrefix}-position`,
298
+ `${storagePrefix}-size`, `${storagePrefix}-density`, and all three
299
+ `${storagePrefix}-highlight-*` keys. Their appearance in the 0.4.15 contract
300
+ documentation records existing storage continuity; it does not make those keys
301
+ new in 0.4.15.
276
302
 
277
303
  **Constraint — colon, not dash, for `visible` and `autoload`.** Both adapter-
278
304
  level flags use a `:` separator; every other derived key uses `-`. The colon
@@ -290,9 +316,22 @@ myapp-design-token-panel-state-v2
290
316
  myapp-design-token-panel-state
291
317
  myapp-design-token-panel-open
292
318
  myapp-design-token-panel-position
319
+ myapp-design-token-panel-size
320
+ myapp-design-token-panel-dock
321
+ myapp-design-token-panel-dock-size
322
+ myapp-design-token-panel-density
323
+ myapp-design-token-panel-ghost
324
+ myapp-design-token-panel-specimen
325
+ myapp-design-token-panel-snapshot-a
326
+ myapp-design-token-panel-snapshot-b
327
+ myapp-design-token-panel-last-applied
293
328
  myapp-design-token-panel:visible
294
329
  myapp-design-token-panel:autoload
330
+ myapp-design-token-panel-elpath-enabled
295
331
  myapp-design-token-panel-domtweaker-enabled
332
+ myapp-design-token-panel-highlight-slots
333
+ myapp-design-token-panel-highlight-outline-width
334
+ myapp-design-token-panel-highlight-active # sessionStorage
296
335
  ```
297
336
 
298
337
  Unit tests in the package verify these derivations with literal-equality
@@ -387,14 +426,14 @@ public surface:
387
426
  ```ts
388
427
  // Value-kind discriminated union — describes how a tier item is edited.
389
428
  export type TierValueKind =
390
- | { kind: 'length'; step: number; unit: string }
391
- | { kind: 'number'; step: number }
429
+ | { kind: 'length'; step: number; unit: string; units?: readonly string[] }
430
+ | { kind: 'number'; step: number; unit?: string }
392
431
  | { kind: 'select'; options: readonly string[] }
393
432
  | { kind: 'text' }
394
433
  | { kind: 'cursor' }
395
434
  | { kind: 'content' }
396
435
  | { kind: 'mask-image' }
397
- | { kind: 'color' };
436
+ | { kind: 'color'; format?: 'hex' | 'oklch' };
398
437
 
399
438
  export interface PillSpec {
400
439
  value: string;
@@ -414,12 +453,10 @@ export type SemanticValue =
414
453
  export interface TierItem {
415
454
  /** Stable id used as the key in persisted state (e.g. `hsp-2xs`). */
416
455
  id: string;
417
- /** CSS custom property written to `:root` (e.g. `--myapp-spacing-hgap-2xs`). */
456
+ /** CSS custom property written to the default root or configured apply sink (e.g. `--myapp-spacing-hgap-2xs`). */
418
457
  cssVar: string;
419
458
  /** Display label shown in the panel row. */
420
459
  label: string;
421
- /** Optional manifest group — tab components use this for section headers. */
422
- group?: string;
423
460
  /** Default value as a CSS string (`0.125rem`, `12px`, etc.). */
424
461
  default: string;
425
462
  /** Discriminated union describing the control kind and its metadata. */
@@ -459,6 +496,10 @@ export interface TierConfig {
459
496
  * this is a multi-source allow-list for individual semantic mappings.
460
497
  */
461
498
  referencesRamps?: readonly { tab?: string; tier: string }[];
499
+ /** Optional visual treatment rendered for this tier's values. */
500
+ preview?: 'size' | 'line-height' | 'family' | 'weight' | 'bar' | 'radius' | 'duration';
501
+ /** CSS variable used as the font-size base for a preview, when supplied. */
502
+ previewBase?: string;
462
503
  }
463
504
 
464
505
  For the full `SemanticValue` mapping and emission behavior, see the maintained
@@ -477,38 +518,78 @@ export interface ColorClusterExtras {
477
518
  defaultShikiTheme: string;
478
519
  colorSchemes: Record<string, ColorScheme>;
479
520
  panelSettings: ClusterPanelSettings;
521
+ /** Optional semantic-item-id → SemanticValue defaults override map. */
522
+ semanticDefaults?: Record<string, SemanticValue>;
480
523
  }
481
524
 
482
525
  /** Top-level tab entry on PanelConfig.tabs. */
483
526
  export interface TabConfig {
484
- /** Stable id. Reserved ids: 'color' (primary color tab), 'color-secondary'. */
527
+ /** Stable id. Dedicated panel ids are 'color', 'font', 'spacing', 'size', 'palette', and 'notes'. */
485
528
  id: string;
486
529
  /** Display label rendered on the tab strip. */
487
530
  label: string;
488
531
  /** Ordered list of tiers within this tab. */
489
532
  tiers: readonly TierConfig[];
490
- /** Tier ids whose rows are hidden behind an Advanced <details> disclosure. */
491
- advancedTiers?: readonly string[];
492
533
  /**
493
- * Required on color tabs (id 'color' / 'color-secondary'). Carries the
534
+ * Required on color entries (id 'color' / 'color-secondary'). Carries the
494
535
  * structural metadata (base roles, scheme registry, panel settings) for the
495
536
  * color tab's palette picker and semantic table. Absent on non-color tabs.
496
537
  */
497
538
  colorExtras?: ColorClusterExtras;
539
+ /** Required on the reserved `notes` tab; forbidden on other tabs. */
540
+ notesExtras?: NotesExtras;
541
+ }
542
+
543
+ export interface NotesExtras {
544
+ title: string;
545
+ html: string;
498
546
  }
499
547
  ```
500
548
 
501
- ### 3.2 Reserved tab ids
549
+ ### 3.2 Preview matrix
550
+
551
+ `preview` is opt-in. Omitting it preserves the ordinary flat tier rows, and a
552
+ tier remains one visible heading; previews do not introduce a collapsed or
553
+ progressive-disclosure tier. The current values are:
554
+
555
+ | `preview` | Valid value kind | Runtime treatment |
556
+ | --- | --- | --- |
557
+ | `'size'` | `length`, or a `referencesTier` resolving to `length` | One type-size sample per item, sorted by resolved pixel size. |
558
+ | `'line-height'` | `number` | Paragraph sample with a leading guide; `previewBase` selects a font-size CSS variable when supplied. |
559
+ | `'family'` | `text` | The first item's font-family value supplies the style for every size and line-height sample. |
560
+ | `'weight'` | `select` or `number` | The first item's font-weight value supplies the style for every sample. |
561
+ | `'bar'` | `length` | Compact bar sized from the token value. |
562
+ | `'radius'` | `length` | Compact rounded-corner glyph. |
563
+ | `'duration'` | `length` or `number` with unit `ms` or `s` | Compact timing glyph using the token's duration value. |
564
+
565
+ The font specimen toolbar persists `{ text, preset, overridden, width }` under
566
+ `${storagePrefix}-specimen`; width is clamped to 240–720 pixels. **Render on
567
+ page** mounts a read-only specimen in the host document under
568
+ `.tokenpanel-on-page-specimen[data-zdtp-specimen]`, temporarily forces the
569
+ right dock, and restores the prior mode when the specimen is disabled, closed,
570
+ unmounted, or loses its dock claim. The specimen is excluded from token scans
571
+ and page pickers.
572
+
573
+ ### 3.3 Reserved tab ids
502
574
 
503
575
  | Tab id | Meaning |
504
576
  | ------------------ | -------------------------------------------------- |
505
577
  | `color` | Primary color tab — palette + base roles + semantics + scheme picker. Requires `colorExtras`. |
506
- | `color-secondary` | Secondary color tab (same shape as `color`). Requires `colorExtras`. |
578
+ | `font` | Dedicated typography tab with family/weight previews and the host-page specimen option. |
579
+ | `spacing` | Dedicated spacing tab with the numeric bulk editor. |
580
+ | `size` | Dedicated size tab with the numeric bulk editor. |
581
+ | `palette` | Dedicated palette editor for a generic palette tab. |
582
+ | `notes` | Dedicated notes tab; it renders content but carries no token state or apply/export overrides. |
507
583
 
508
- Any other id dispatches to `GenericTab`, which renders the tab's `tiers`
509
- using kind-appropriate editors.
584
+ The panel always prepends a synthetic `Inspect` tab for element inspection; it
585
+ is not supplied in `PanelConfig.tabs`. A configured `color-secondary` entry is
586
+ the companion color-cluster data source consumed by the primary Color tab (and
587
+ requires `colorExtras`); it is not an additional dedicated body dispatcher.
510
588
 
511
- ### 3.3 Validation rules
589
+ Any other configured id dispatches to `GenericTab`, which renders the tab's
590
+ `tiers` using kind-appropriate editors.
591
+
592
+ ### 3.4 Validation rules
512
593
 
513
594
  `assertValidPanelConfig` enforces these structural rules at the host-adapter
514
595
  trust boundary:
@@ -523,7 +604,7 @@ trust boundary:
523
604
  - `referencesTier` must name an existing tier in the same tab, and the
524
605
  referencing tier's kind must match the referenced tier's kind.
525
606
 
526
- ### 3.4 Apply behaviour for ref-tier items
607
+ ### 3.5 Apply behaviour for ref-tier items
527
608
 
528
609
  When a `TierConfig` carries `referencesTier`, the apply pipeline treats each
529
610
  item's persisted value as the id of an item in the referenced tier. The
@@ -532,9 +613,9 @@ emitted CSS override is `var(--target-cssvar)` where `target-cssvar` is the
532
613
 
533
614
  By default the write target is `:root` (`document.documentElement`). When a
534
615
  `PanelConfig.applySink` is configured for the instance, writes are routed
535
- through the sink instead — see §3.5.
616
+ through the sink instead — see §3.6.
536
617
 
537
- ### 3.5 `applySink` — optional CSS-var write target
618
+ ### 3.6 `applySink` — optional CSS-var write target
538
619
 
539
620
  When `PanelConfig.applySink` is set, all CSS-var writes and clears for that
540
621
  panel instance route through the sink rather than `document.documentElement`.
@@ -596,7 +677,7 @@ const handle = configurePanel({
596
677
  });
597
678
  ```
598
679
 
599
- ### 3.6 Helpers (re-exported from the package root)
680
+ ### 3.7 Helpers (re-exported from the package root)
600
681
 
601
682
  ```ts
602
683
  export function isLengthKind(v: TierValueKind): boolean;
@@ -609,6 +690,23 @@ export function isContentKind(v: TierValueKind): boolean;
609
690
  export function isMaskImageKind(v: TierValueKind): boolean;
610
691
  ```
611
692
 
693
+ ### 3.8 Canonical state transaction
694
+
695
+ All panel mutations use one transaction path, including ordinary row edits,
696
+ bulk actions, imports, snapshot restore, undo, redo, and reset. A transaction
697
+ applies the new state and CSS-variable writes, saves the persisted envelope,
698
+ updates component state, and then records the history entry in that order.
699
+ History is identity-aware and held in memory only; the persisted A/B snapshots
700
+ and token envelope are separate storage concerns. After a disk apply, the
701
+ implementation resets the last-applied baseline to `{}` and reconciles only
702
+ variables confirmed as written, so retained or unrouted overrides stay dirty.
703
+ Because disk Apply does not write base-role variables, a confirmed semantic
704
+ write derived from an unchanged `bg` or `fg` alias also reconciles that alias's
705
+ role-index dependency. Before resetting the dependency, other unwritten
706
+ semantic aliases using the same role are materialized to their resolved
707
+ numeric palette index. Their emitted value and dirty state are preserved for a
708
+ later Apply.
709
+
612
710
  ---
613
711
 
614
712
  ## 4. Color tab contract
@@ -659,9 +757,11 @@ export interface ColorClusterExtras {
659
757
  * on init. Set to `false` to disable scheme-to-`data-theme` binding; this
660
758
  * does not disable per-mode literal editing or emitted `light-dark(...)`
661
759
  * values.
662
- */
760
+ */
663
761
  colorMode: false | { defaultMode: 'light' | 'dark'; lightScheme: string; darkScheme: string };
664
762
  };
763
+ /** Optional semantic-item-id → SemanticValue defaults override map. */
764
+ semanticDefaults?: Record<string, SemanticValue>;
665
765
  }
666
766
  ```
667
767
 
@@ -676,8 +776,8 @@ export interface ColorScheme {
676
776
  cursor: ColorRef;
677
777
  selectionBg: ColorRef;
678
778
  selectionFg: ColorRef;
679
- palette: readonly string[]; // length must match the palette tier's item count
680
- shikiTheme: string;
779
+ palette: readonly string[]; // the public type requires exactly 16 entries
780
+ shikiTheme?: string;
681
781
  semantic?: Record<string, ColorRef>;
682
782
  }
683
783
  ```
@@ -744,13 +844,19 @@ The apply pipeline for color tabs:
744
844
 
745
845
  - For each palette `TierItem` in the palette tier, write
746
846
  `item.cssVar` ← `palette[i]` from the active scheme / user override.
747
- - For each `(roleKey, cssName)` in `colorExtras.baseRoles`, write
748
- `cssName` ← `palette[state[roleKey]]`.
847
+ - For each `(roleKey, cssName)` in `colorExtras.baseRoles`, the live DOM apply
848
+ path writes `cssName` ← `palette[state[roleKey]]`.
749
849
  - For each semantic `TierItem`, resolve
750
850
  `state.semanticMappings[key] ?? colorExtras.semanticDefaults[key]`
751
- through `resolveMapping` and write `item.cssVar` ← resolved hex.
851
+ through `resolveMapping` and write the emitted CSS value: `var(...)` for a
852
+ palette/reference mapping, a literal string for a literal mapping, or
853
+ `light-dark(light, dark)` for a per-mode literal.
752
854
  - `clearAppliedStyles()` removes every property the cluster could have set.
753
855
 
856
+ The disk `buildApplyOverrides` payload intentionally emits palette and
857
+ semantic CSS variables only; base-role values are runtime wiring and are not
858
+ included in source-file rewrites.
859
+
754
860
  ### 4.6 `applyEndpoint` and `applyRouting`
755
861
 
756
862
  The Apply modal's button is gated on two `PanelConfig` fields:
@@ -773,6 +879,13 @@ The **bin server** is the reference implementation for the apply contract.
773
879
  ### 5.1 Request & response envelopes
774
880
 
775
881
  The Apply button POSTs to `PanelConfig.applyEndpoint` with a flat JSON diff.
882
+ The client sends only changed tokens. A token is changed exactly when the CSS
883
+ value it would emit differs from the value its baseline would emit. Therefore
884
+ an empty flat override or one equal to its manifest default is omitted;
885
+ semantic role aliases and palette indices compare by their resolved palette
886
+ slot, while literal and ref mappings compare structurally. Palette slots use
887
+ the active color-identity baseline. The Apply payload may also contain a
888
+ secondary color cluster, diffed against that cluster's configured defaults.
776
889
 
777
890
  **Request**
778
891
 
@@ -788,6 +901,60 @@ Content-Type: application/json
788
901
  }
789
902
  ```
790
903
 
904
+ The same endpoint accepts two optional coordination fields:
905
+
906
+ - `dryRun: true` computes a preview without creating a temporary file,
907
+ renaming a file, or otherwise mutating disk. A dry run may mix routed and
908
+ unrouted tokens; unrouted entries are diagnostics in the successful preview
909
+ rather than a whole-request error.
910
+ - `expectDigests` maps the repo-relative `file` values from a preceding preview
911
+ to their SHA-256 `digest` values. On a real write, every supplied digest is
912
+ checked after all files have been read and before any file is written.
913
+
914
+ **Response 200 (dry run)**
915
+
916
+ ```json
917
+ {
918
+ "ok": true,
919
+ "dryRun": true,
920
+ "files": [
921
+ {
922
+ "file": "src/styles/tokens.css",
923
+ "blockKind": "root",
924
+ "digest": "<64 lowercase SHA-256 hex characters>",
925
+ "changed": ["--myapp-spacing-md"],
926
+ "unchanged": ["--myapp-spacing-lg"],
927
+ "unknown": [],
928
+ "unknownOutsideBlock": [],
929
+ "hunks": [
930
+ {
931
+ "cssVar": "--myapp-spacing-md",
932
+ "line": 12,
933
+ "before": " --myapp-spacing-md: 1rem;",
934
+ "after": " --myapp-spacing-md: 2rem;",
935
+ "context": {
936
+ "before": [" --myapp-spacing-sm: 0.5rem;"],
937
+ "after": [" --myapp-spacing-lg: 3rem;"]
938
+ }
939
+ }
940
+ ]
941
+ }
942
+ ],
943
+ "rejected": ["--unrouted-token"],
944
+ "rejectedReasons": ["--unrouted-token: no route configured for prefix family (...)"]
945
+ }
946
+ ```
947
+
948
+ `blockKind` identifies the scanned block containing the requested declaration;
949
+ `:root` wins when a request changes declarations in both supported block kinds.
950
+ For an unknown-only file result, the first available kind (`root`, then
951
+ `theme`) is reported. `line` is one-based. `before` and `after` are complete
952
+ declaration lines, while each context array contains the adjacent proposed-file
953
+ line on that side. `hunks` contains exactly one entry per changed cssVar; two
954
+ declarations on one physical line therefore produce two independently-keyed
955
+ hunks with the same line number. Unknown and unchanged tokens do not produce
956
+ hunks.
957
+
791
958
  **Response 200 (success)**
792
959
 
793
960
  ```json
@@ -798,11 +965,13 @@ Content-Type: application/json
798
965
  "file": "src/styles/tokens.css",
799
966
  "changed": ["--myapp-spacing-md"],
800
967
  "unchanged": ["--myapp-spacing-lg"],
801
- "unknown": []
968
+ "unknown": [],
969
+ "unknownOutsideBlock": []
802
970
  }
803
971
  ],
804
972
  "unknownCssVars": [],
805
- "unchangedCssVars": ["--myapp-spacing-lg"]
973
+ "unchangedCssVars": ["--myapp-spacing-lg"],
974
+ "unknownOutsideBlockCssVars": []
806
975
  }
807
976
  ```
808
977
 
@@ -817,8 +986,10 @@ Content-Type: application/json
817
986
  ```
818
987
 
819
988
  Returned for: malformed JSON, missing `tokens` field, empty tokens map,
820
- invalid token names (no `--` prefix, spaces, slashes), unsupported CSS-var
821
- prefix, path escape attempts.
989
+ invalid token names (no `--` prefix, spaces, slashes), or path escape attempts.
990
+ For a real write, an unsupported CSS-var prefix is also a 400 error. A dry run
991
+ keeps unsupported prefixes in its `rejected` / `rejectedReasons` diagnostics
992
+ so the caller can preview the rest of the request.
822
993
 
823
994
  **Response 403 (Forbidden)**
824
995
 
@@ -833,9 +1004,30 @@ Empty body, `Allow: POST, OPTIONS` header.
833
1004
  **Response 409 (Conflict)**
834
1005
 
835
1006
  ```json
836
- { "ok": false, "error": "No top-level :root { ... } block in <file>" }
1007
+ { "ok": false, "error": "No top-level :root { ... } or @theme { ... } block in <file>" }
837
1008
  ```
838
1009
 
1010
+ The handler scans only the first top-level `:root` block and the first
1011
+ top-level `@theme` block (bare or with one modifier). `:root` wins when the
1012
+ same variable is declared in both. Later blocks and nested blocks are not
1013
+ rewritable. A dry run can succeed with only unrouted tokens; those tokens are
1014
+ listed in `rejected` and `rejectedReasons`, while a file with no supported
1015
+ block is a `409`.
1016
+
1017
+ A real write whose current file content does not match a supplied preview
1018
+ digest returns the following envelope before any target is written:
1019
+
1020
+ ```json
1021
+ {
1022
+ "ok": false,
1023
+ "reason": "stale-file",
1024
+ "files": ["src/styles/tokens.css"]
1025
+ }
1026
+ ```
1027
+
1028
+ The client must request a new dry run and ask the user to review the refreshed
1029
+ hunks. Omitting `expectDigests` preserves the legacy write behaviour.
1030
+
839
1031
  **Response 500 (Internal server error)**
840
1032
 
841
1033
  ```json
@@ -863,14 +1055,19 @@ Hosts physically unable to spawn Node.js must:
863
1055
  the routing map, reject prefixes not in the map.
864
1056
  3. Path safety — resolve each target path to an absolute path, verify it sits
865
1057
  within `writeRoot`, reject path-escape attempts.
866
- 4. Read & parse — load each CSS file, find the `:root { ... }` block (fail
867
- 409 if missing), parse the existing variable values.
868
- 5. Compute rewrite — compute `changed` / `unchanged` / `unknown`, build the
869
- updated `:root` block.
870
- 6. Atomic write — keep the original file content in memory. Write updated
1058
+ 4. Read & parse — load each CSS file, find the first top-level `:root { ... }`
1059
+ or first top-level `@theme { ... }` block (bare or with one modifier; fail
1060
+ 409 if neither exists), parse the existing variable values.
1061
+ 5. Compute rewrite — compute `changed` / `unchanged` / `unknown` and
1062
+ `unknownOutsideBlock`, build the updated `:root` / `@theme` content, its
1063
+ per-cssVar hunks, and the SHA-256 of the original bytes.
1064
+ 6. Preview/stale gate — for `dryRun: true`, return the preview immediately. For
1065
+ a real write with `expectDigests`, compare every supplied digest and return
1066
+ the stale-file 409 before the first mutation when any target differs.
1067
+ 7. Atomic write — keep the original file content in memory. Write updated
871
1068
  content to a temp file. Atomically rename temp to target. If any write
872
1069
  fails, restore every previously-written file.
873
- 7. Respond — return the exact JSON envelope shapes pinned in §5.1.
1070
+ 8. Respond — return the exact JSON envelope shapes pinned in §5.1.
874
1071
 
875
1072
  ### 5.4 Routing config — single source of truth
876
1073
 
@@ -904,8 +1101,17 @@ This is the reason the JSON-serializable constraint in §4.2 is non-negotiable.
904
1101
 
905
1102
  ### 6.2 Lazy-load gate
906
1103
 
907
- The host adapter fires one eager `loadPanelModule()` call when any of the
908
- following signals is present in `localStorage` at page load:
1104
+ The machine-readable metadata for these signals is exported from the
1105
+ side-effect-free `@takazudo/zdtp/constants` subpath. Use
1106
+ `EAGER_LOAD_GATE_KEY_SUFFIXES` for the five fixed suffix descriptors
1107
+ (`:visible`, `-open`, `:autoload`, `-elpath-enabled`, and
1108
+ `-domtweaker-enabled`), and `EAGER_LOAD_GATE_STATE_FAMILY` for the versioned
1109
+ `-state` family. These exports are the source of truth for consumers that
1110
+ mirror the gate. Each fixed descriptor supplies `acceptedValues` and
1111
+ `requiredConfig`; presence alone is insufficient, and a named required config
1112
+ property must be defined. The prose below explains the runtime meaning. The host adapter fires one eager
1113
+ `loadPanelModule()` call when any of these signals activates in `localStorage`
1114
+ at page load:
909
1115
 
910
1116
  ```ts
911
1117
  if (
@@ -933,15 +1139,25 @@ if (
933
1139
  specific version key — an empty `{}` / `[]` / `null` / `''` does NOT trigger
934
1140
  it. (zdtp itself never writes such a value: `clearPersistedState()` removes
935
1141
  the `-state` keys outright, so this guard only covers envelopes written by
936
- hand or by another tool.) Overrides MUST be re-applied to `:root` even when
937
- the panel stays hidden, otherwise hard-nav produces a FOUT.
1142
+ hand or by another tool.) Overrides MUST be re-applied to the configured
1143
+ sink (or default `:root`) even when the panel stays hidden, otherwise
1144
+ hard-nav produces a FOUT.
1145
+
1146
+ `EAGER_LOAD_GATE_STATE_FAMILY.matchesKey(storagePrefix, key)` only recognizes
1147
+ the exact `${storagePrefix}-state` / `${storagePrefix}-state-vN` key shape. It
1148
+ does not read storage or perform the content check. The consumer must apply
1149
+ the accompanying `valueRules`: raw empty strings, JSON `null`, and empty
1150
+ objects/arrays do not activate; non-empty collections, every other parsed
1151
+ primitive (including `false`, `0`, and JSON `""`), and malformed JSON do
1152
+ activate.
938
1153
  - `shouldAutoload()` — reads `${storagePrefix}:autoload` (colon-form, §2).
939
1154
  Returns `true` when the flag is `'1'` (explicit, written by `enableAutoload()`)
940
1155
  OR `'auto'` (auto-remembered, written by opening the panel — see "Auto-remember
941
1156
  on open" below). This is the owner-mode signal: the panel bundle fetches
942
1157
  eagerly and mounts CLOSED so the element-path inspector is armed even though
943
- the panel UI is hidden. General visitors (no flag, or `'0'`) pay zero bundle
944
- cost. A downstream host that wants to distinguish the two populations can
1158
+ the panel UI is hidden. General visitors (no flag, or `'0'`) pay no
1159
+ panel-bundle cost; the small host adapter/config bootstrap still runs. A
1160
+ downstream host that wants to distinguish the two populations can
945
1161
  test `=== '1'` directly — see "Auto-remember on open" for the caveat.
946
1162
  - `loadElementPathEnabled()` — reads the element-path inspector's persistence
947
1163
  key. Returns `true` when the inspector was left enabled. Ensures the Preact
@@ -952,9 +1168,11 @@ if (
952
1168
  enabled. Ensures the Preact shell is mounted and the lazy boundary is
953
1169
  imported even when the panel UI is hidden.
954
1170
 
955
- When none of the five signals is present — the common case for first-time
1171
+ When none of the six signals is present — the common case for first-time
956
1172
  visitors and general site visitors on a public site with owner-autoload — the
957
- panel bundle is NOT fetched and the page is completely free of panel JS.
1173
+ panel bundle is NOT fetched, no panel stylesheet is injected, and no panel
1174
+ root is mounted. The small host adapter/config bootstrap still runs to make
1175
+ that decision.
958
1176
 
959
1177
  #### Storage-key table for §6.2 signals
960
1178
 
@@ -1006,7 +1224,9 @@ panel bundle is NOT fetched and the page is completely free of panel JS.
1006
1224
  #### Auto-remember on open
1007
1225
 
1008
1226
  Any action that shows the panel (`showDesignPanel()`, `toggleDesignPanel()`,
1009
- or the panel's header button) MUST also write
1227
+ the fixed-name `window.zdtp.show()` / `.toggle()` aliases, an instance
1228
+ handle's `open()` / `toggle()`, the panel header's open action, or an instance
1229
+ toggle event) MUST also write
1010
1230
  `${storagePrefix}:autoload = 'auto'` (auto-remembered provenance, distinct
1011
1231
  from the `'1'` that `enableAutoload()` writes) — implemented by
1012
1232
  `rememberAutoload()`. This ensures that once the owner has opened the panel
@@ -1034,13 +1254,26 @@ already hold `'1'`, and that provenance was never recorded, so it cannot be
1034
1254
  reclassified. The `=== '1'` discrimination applies only to opens made from
1035
1255
  this version onward.
1036
1256
 
1257
+ #### Shared Alt+click picker ownership
1258
+
1259
+ Element path, DOM Tweaker, and element inspect share one document-level
1260
+ Alt+click coordinator. Only one owner may be armed at a time; a new request
1261
+ revokes the previous owner's armed state before it starts. Panel-owned surfaces
1262
+ and the host-page specimen are excluded from all three pickers.
1263
+
1264
+ | Feature | Activation | Result |
1265
+ | --- | --- | --- |
1266
+ | Element path | Owner autoload or its panel toggle, then `Alt+click` | Copies an annotated selector/path block. |
1267
+ | DOM Tweaker | Configured feature toggle, then `Alt+click` | Opens the Tailwind class editor and live utility preview. |
1268
+ | Element inspect | Header toggle or `I`, then click; `Alt` also arms the coordinator | Opens the reserved inspect tab with computed and inherited token rows. |
1269
+
1037
1270
  ### 6.3 Astro view-transition lifecycle
1038
1271
 
1039
1272
  The adapter's existing `astro:before-swap` and `astro:page-load` listeners
1040
1273
  stay. They are Astro-specific and only register when `document` is available:
1041
1274
 
1042
1275
  - `astro:before-swap` → unmount the Preact tree, remove the host node, snapshot/restore visibility intent.
1043
- - `astro:page-load` → re-apply persisted overrides + re-materialise the shell when any of the four gate signals (§6.2) is true.
1276
+ - `astro:page-load` → re-apply persisted overrides + re-materialise the shell when any of the six gate signals (§6.2) is true.
1044
1277
 
1045
1278
  ### 6.4 Console API
1046
1279
 
@@ -1122,32 +1355,55 @@ window.zdtp.toggle = () => void | Promise<void>; // toggle the panel
1122
1355
 
1123
1356
  ### 7.1 Panel-private namespace
1124
1357
 
1125
- The panel ships its own bundled CSS. All panel-chrome variables use the
1126
- `--tokentweak-*` prefix, scoped to the panel shell + modal class prefix:
1358
+ The panel ships its own bundled CSS. Panel-private color, font, spacing,
1359
+ typography, and z-index variables use the `--tokentweak-*` prefix; the shared
1360
+ radius token is `--radius-tokentweak`. They are scoped to the panel shell,
1361
+ mini pill, modal class prefix, and the body-level popover/tooltip/inspector
1362
+ surfaces:
1127
1363
 
1128
1364
  ```css
1129
1365
  :where(.tokenpanel-shell, [data-design-token-panel-modal]) {
1130
- --tokentweak-pad-md: …;
1131
- --tokentweak-gap-sm: …;
1132
- --tokentweak-color-fg: #b8b8b8;
1366
+ /* base-0 is the darkest ground; stops ascend toward the foreground. */
1367
+ --tokentweak-palette-base-5: oklch(0.8 0 0);
1368
+ --tokentweak-color-fg: var(--tokentweak-palette-base-5);
1369
+ --tokentweak-color-accent-bar: #efb477;
1133
1370
  /* …every panel-chrome value lives here */
1134
1371
  }
1135
1372
  ```
1136
1373
 
1137
1374
  - **No Tailwind dependency.** The package builds and runs without Tailwind in
1138
1375
  the consumer.
1139
- - **Consumer import required.** The `./styles` sub-export must be imported
1140
- exactly once from the consumer's static module graph:
1376
+ - **Self-injected by the panel entry.** The panel calls `ensurePanelStyles()`
1377
+ when it first mounts, so a consumer does not need a CSS import. The `./styles`
1378
+ sub-export remains available when a host wants to pull the stylesheet into its
1379
+ own static CSS pipeline; importing it twice is unnecessary.
1141
1380
 
1142
1381
  ```ts
1143
- import '@takazudo/zdtp/styles';
1382
+ import '@takazudo/zdtp/styles'; // optional static-CSS path
1144
1383
  ```
1145
1384
 
1385
+ The semantic color layer includes `--tokentweak-color-fg`, `bg`, `muted`,
1386
+ `border`, `surface`, `accent`, `accent-bar`, `accent-hover`, `code-bg`,
1387
+ `code-fg`, `success`, `danger`, and `warning`, backed by the private
1388
+ `--tokentweak-palette-base-*` OKLCH ramp, plus `--tokentweak-font-mono`. Spacing,
1389
+ typography, radius, and stacking values use the same prefix (`pad-*`, `gap-*`,
1390
+ `text-*`, `--radius-tokentweak`, and `z-*`). The chrome does not read host
1391
+ `--color-*` or `--font-mono` variables; hosts that retheme it assign the
1392
+ `--tokentweak-*` names directly on one of the listed scopes.
1393
+
1394
+ Docking is a host-document contract rather than a panel-private token:
1395
+ `--zdtp-dock-inset-right` and `--zdtp-dock-inset-bottom` are published on the
1396
+ host root while the corresponding dock claim is active. The on-page specimen
1397
+ uses `.tokenpanel-on-page-specimen[data-zdtp-specimen]`; it inherits the host
1398
+ font/foreground, is excluded from token scans and page pickers, and is removed
1399
+ when the specimen is disabled, closed, unmounted, or loses its dock claim.
1400
+
1146
1401
  ### 7.2 Consumer's editable tokens
1147
1402
 
1148
1403
  The tokens the panel writes to (the `cssVar` field on each `TierItem`) are
1149
- entirely consumer-controlled. The package just writes them through `setProperty`
1150
- on `:root`.
1404
+ entirely consumer-controlled. The package writes them through the configured
1405
+ `applySink`, or through `setProperty` on the default `:root` target when no
1406
+ sink is supplied.
1151
1407
 
1152
1408
  - **Read:** the panel never reads consumer CSS variables (it carries its own
1153
1409
  defaults via `TierItem.default`).
@@ -1163,20 +1419,30 @@ rules on `[data-design-token-panel-modal]`.
1163
1419
 
1164
1420
  ### 7.4 Self-contained panel chrome palette (no host theme reads)
1165
1421
 
1166
- The panel-chrome color tokens are declared in `panel-tokens.css` as
1167
- concrete dark-palette values so the panel paints as a neutral dark surface
1422
+ The panel-chrome color tokens are declared in `panel-tokens.css` as semantic
1423
+ aliases onto a private OKLCH ramp so the panel paints as a neutral dark surface
1168
1424
  regardless of what the host's `--color-*` tokens resolve to:
1169
1425
 
1170
1426
  ```css
1171
1427
  :where(.tokenpanel-shell, [data-design-token-panel-modal]) {
1172
- --tokentweak-color-fg: #b8b8b8;
1173
- --tokentweak-color-bg: #181818;
1174
- --tokentweak-color-muted: #888888;
1175
- --tokentweak-color-surface: #1c1c1c;
1428
+ /* base-0 is the darkest ground; stops ascend toward the foreground. */
1429
+ --tokentweak-palette-base-0: oklch(0.18 0 0);
1430
+ --tokentweak-palette-base-1: oklch(0.25 0 0);
1431
+ --tokentweak-palette-base-2: oklch(0.34 0 0);
1432
+ --tokentweak-palette-base-3: oklch(0.536 0 0);
1433
+ --tokentweak-palette-base-4: oklch(0.66 0 0);
1434
+ --tokentweak-palette-base-5: oklch(0.8 0 0);
1435
+ --tokentweak-palette-base-6: oklch(0.91 0 0);
1436
+ --tokentweak-color-fg: var(--tokentweak-palette-base-5);
1437
+ --tokentweak-color-bg: var(--tokentweak-palette-base-0);
1438
+ --tokentweak-color-muted: var(--tokentweak-palette-base-4);
1439
+ --tokentweak-color-border: var(--tokentweak-palette-base-3);
1440
+ --tokentweak-color-surface: var(--tokentweak-palette-base-1);
1176
1441
  --tokentweak-color-accent: #d69a66;
1442
+ --tokentweak-color-accent-bar: #efb477;
1177
1443
  --tokentweak-color-accent-hover: #a7c0e3;
1178
- --tokentweak-color-code-bg: #383838;
1179
- --tokentweak-color-code-fg: #e0e0e0;
1444
+ --tokentweak-color-code-bg: var(--tokentweak-palette-base-2);
1445
+ --tokentweak-color-code-fg: var(--tokentweak-palette-base-6);
1180
1446
  --tokentweak-color-success: #93bb77;
1181
1447
  --tokentweak-color-danger: #da6871;
1182
1448
  --tokentweak-color-warning: #dfbb77;
@@ -1193,9 +1459,17 @@ in a demo — MUST NOT bleed into the panel chrome.
1193
1459
  **Override surface for hosts:** a host that wants to retheme the panel
1194
1460
  chrome assigns directly to the `--tokentweak-color-*` /
1195
1461
  `--tokentweak-font-mono` names on `.tokenpanel-shell`,
1196
- `[data-design-token-panel-modal]`, or any ancestor (`:where()` keeps
1197
- specificity at 0). This single name layer is the entire host-override
1198
- contract for panel chrome — `--color-*` reads are not part of it.
1462
+ `.tokenpanel-mini-pill`, `[data-design-token-panel-modal]`, the highlight
1463
+ settings and chain popovers, the color picker, tooltip, element-path label and
1464
+ toast, or an element-inspect surface (or any ancestor). `:where()` keeps
1465
+ specificity at 0. This single name layer is the entire host-override contract
1466
+ for panel chrome — `--color-*` reads are not part of it.
1467
+
1468
+ Hosts may instead override a `--tokentweak-palette-base-*` stop to update all
1469
+ roles that alias it; a direct semantic `--tokentweak-color-*` assignment still
1470
+ wins. The border role is intentionally separate: `--tokentweak-color-muted`
1471
+ now recolors secondary text only. A host that previously used it for both text
1472
+ and 1px dividers must also assign `--tokentweak-color-border`.
1199
1473
 
1200
1474
  **Invariant:** the panel package MUST NOT read `--color-*` or
1201
1475
  `--font-mono` anywhere. Both `panel.css` and `panel-tokens.css` are pinned
@@ -1210,8 +1484,10 @@ grep -n 'var(--font-mono' src/styles/panel-tokens.css # → 0
1210
1484
 
1211
1485
  ### 7.5 Host-adapter side-effect import (paired-unit obligation)
1212
1486
 
1213
- Alongside the `./styles` import, the consumer MUST own a side-effect import
1214
- for the host-adapter, paired with `<DesignTokenPanelHost>`:
1487
+ The consumer MUST own a side-effect import for the host-adapter, paired with
1488
+ `<DesignTokenPanelHost>`. The `./styles` import is optional because the panel
1489
+ entry self-injects its stylesheet; use it only when the host wants a static CSS
1490
+ pipeline:
1215
1491
 
1216
1492
  ```astro
1217
1493
  <DesignTokenPanelHost config={myPanelConfig} />
@@ -1225,11 +1501,13 @@ for the host-adapter, paired with `<DesignTokenPanelHost>`:
1225
1501
 
1226
1502
  ## 8. Storage-key continuity & migration paths
1227
1503
 
1228
- ### 8.1 No default `PanelConfig`
1504
+ ### 8.1 Minimal fallback before configuration
1229
1505
 
1230
- The package ships **zero** baked-in identifiers. The host MUST configure the
1231
- panel explicitly. A package import without an explicit configure-call surfaces
1232
- a clear runtime error.
1506
+ The package ships **zero** host-specific identifiers. Before the first explicit
1507
+ `configurePanel` call, `getPanelConfig()` returns a minimal sentinel with empty
1508
+ token manifests and a stub color cluster so imports and adapter boot can remain
1509
+ safe; it is not a useful consumer configuration. Hosts MUST configure the
1510
+ panel explicitly to render their own tabs and token values.
1233
1511
 
1234
1512
  ### 8.2 Storage-key derivation is literal
1235
1513
 
@@ -1286,7 +1564,7 @@ The historical zdtp-internal map is exported as `ZDTP_LEGACY_TYPOGRAPHY_RENAME_M
1286
1564
 
1287
1565
  ---
1288
1566
 
1289
- ## 9. JSON export / import schema (serde v2)
1567
+ ## 9. JSON export / import schema (serde)
1290
1568
 
1291
1569
  ### 9.1 Schema versioning
1292
1570
 
@@ -1294,9 +1572,12 @@ The historical zdtp-internal map is exported as `ZDTP_LEGACY_TYPOGRAPHY_RENAME_M
1294
1572
  | ----------------------- | ------- | ------------------------------------------------------ |
1295
1573
  | `zudo-design-tokens/v1` | Legacy | Flat top-level `color`/`spacing`/`typography`/`size` keys |
1296
1574
  | `zudo-design-tokens/v2` | Current | `tabs` wrapper keyed by tab id; cssVar-keyed leaves |
1575
+ | `zudo-design-tokens/v3` | Current | v2 structure with object-valued semantic color mappings |
1297
1576
 
1298
- `serialize()` always emits v2. `deserialize()` accepts both v1 and v2 and
1299
- normalises to an internal `TweakState`.
1577
+ `serialize()` emits v2 for states whose semantic mappings are representable by
1578
+ the v2 shape, and upgrades to v3 when an object-valued semantic mapping needs
1579
+ the v3 shape. `deserialize()` accepts v1, v2, and v3 and normalises each to an
1580
+ internal `TweakState`.
1300
1581
 
1301
1582
  ### 9.2 v2 format
1302
1583
 
@@ -1332,7 +1613,14 @@ Key decisions:
1332
1613
 
1333
1614
  `serialize()` only emits tokens the user has changed relative to manifest
1334
1615
  defaults. Pass `includeDefaults: true` to dump the full state. A tab key is
1335
- omitted entirely when nothing in it differs.
1616
+ omitted entirely when nothing in it differs. "Changed" uses the canonical
1617
+ emitted-value definition in §5.1. Export parity covers tokens representable by
1618
+ the current schema: flat tabs and the primary color cluster. The secondary
1619
+ color cluster is Apply-only and is not added to the export schema here.
1620
+
1621
+ A sparse persisted override equal to today's manifest default remains stored
1622
+ but invisible to the UI, diff-only export, and Apply. It can intentionally
1623
+ resurface if a later manifest changes that default.
1336
1624
 
1337
1625
  ---
1338
1626
 
@@ -1342,10 +1630,12 @@ Items this contract deliberately does NOT pin down:
1342
1630
 
1343
1631
  - **Persist envelope internal shape** — frozen at the current shape so
1344
1632
  existing user state round-trips without migration.
1345
- - **Schema id versioning.** `schemaId` is a configure-time string; bumping
1346
- it is the host's responsibility.
1633
+ - **Schema id versioning.** `schemaId` is a configure-time display label
1634
+ returned by `getDesignTokenSchema()`; it does not select or version the
1635
+ serializer. The package-owned `SCHEMA_V1` / `SCHEMA_V2` / `SCHEMA_V3`
1636
+ constants govern export and import validation.
1347
1637
  - **Shadow-DOM scoping.** The panel writes to `:root` by default; hosts
1348
- that need scoped writes use `PanelConfig.applySink` (§3.5). The sink
1638
+ that need scoped writes use `PanelConfig.applySink` (§3.6). The sink
1349
1639
  target's lifecycle is owned by the host — not pinned here.
1350
1640
  - **Theme-API surface.** The panel does not expose a programmatic API for
1351
1641
  reading the current overrides outside the persist envelope.
@@ -1361,21 +1651,26 @@ Cross-reference table — what each section pins down.
1361
1651
  | `configurePanel({...})` signature, multi-instance, `PanelInstanceHandle`, per-instance toggle events | §1 |
1362
1652
  | Storage-key derivation | §2, §8 |
1363
1653
  | Default first-open geometry (coherent size+position, viewport containment, cascade, persisted-position precedence) | §2.1 |
1654
+ | `PanelDockConfig`, dock modes, body-margin reflow, edge claims, and dock storage | §1, §2, §7 |
1364
1655
  | `TabConfig` / `TierConfig` / `TierItem` / `TierValueKind` interfaces and apply behaviour | §3 |
1365
- | `applySink` — optional CSS-var write target (upsert / clear / Reset full set) | §3.5 |
1656
+ | `TierConfig.preview` / `previewBase` matrix and host-page specimen lifecycle | §3.2 |
1657
+ | `applySink` — optional CSS-var write target (upsert / clear / Reset full set) | §3.6 |
1366
1658
  | `ColorClusterExtras` shape and multi-cluster support | §4.1, §4.3 |
1367
1659
  | JSON-serializable constraint on color tab config | §4.2 |
1368
1660
  | `colorPresets` and `setPanelColorPresets()` lazy attachment | §4.4 |
1369
1661
  | Color apply behaviour | §4.5 |
1370
- | Apply pipeline request / response envelopes | §5.1 |
1662
+ | Apply pipeline request / response envelopes, dry-run hunks/digests, stale-write 409 | §5.1 |
1663
+ | Canonical transaction order and in-memory undo/redo history | §3.8 |
1371
1664
  | Reference-implementation algorithm + native-implementation guidance | §5.2, §5.3 |
1372
1665
  | Routing config single-source | §5.4 |
1373
- | Astro `<DesignTokenPanelHost>` prop, lazy-load gate (4-signal), owner-autoload, console API | §6 |
1666
+ | Astro `<DesignTokenPanelHost>` prop, lazy-load gate (6-signal), owner-autoload, console API | §6 |
1667
+ | Shared Alt+click owner for element path, DOM Tweaker, and element inspect | §6.2 |
1374
1668
  | Fixed-name global open API (`window.zdtp.show/hide/toggle`) | §6.5 |
1375
1669
  | `--tokentweak-*` namespace and Tailwind-free CSS contract | §7.1 |
1376
1670
  | Modal class prefix and `data-design-token-panel-modal` selector contract | §7.3 |
1377
1671
  | Self-contained panel chrome palette (no host theme reads) | §7.4 |
1378
1672
  | Host-adapter side-effect import (paired-unit obligation) | §7.5 |
1379
1673
  | v4 envelope precedence, v1/v2/v3 storage migration, and typography-id rename map | §2, §8.3, §8.4 |
1380
- | JSON export/import schema v2 (serde v2) | §9 |
1674
+ | JSON export/import schemas v1/v2/v3 (serde) | §9 |
1381
1675
  | Out-of-scope / deferred concerns | §10 |
1676
+ | Feature walkthrough and shortcut table | [Panel UX tour](/docs/recipes/panel-ux-tour) |