@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
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @takazudo/zdtp
2
2
 
3
- A live-tweak design-token panel for Astro sites. Drop a single `<DesignTokenPanelHost>` component into your layout, hand it a `PanelConfig`, and your users get an in-page UI for adjusting CSS custom properties (spacing, typography, sizing, color palette + semantic roles). Changes apply to `:root` instantly, persist to `localStorage`, and survive view transitions and hard reloads.
3
+ A live-tweak design-token panel for Astro sites. Drop a single `<DesignTokenPanelHost>` component into your layout, hand it a `PanelConfig`, and your users get an in-page UI for adjusting CSS custom properties (spacing, typography, sizing, color palette + semantic roles). Changes apply instantly to the default `:root` target (or to a configured `applySink`), persist to `localStorage`, and survive view transitions and hard reloads.
4
4
 
5
- The package is portable: every project-specific identifier is driven by the host's `PanelConfig`. Storage keys, console namespace, modal class prefix, schema id, and the entire tab configuration (tiers, items, color cluster extras) are all configured by the consumer. Every config field is JSON-serializable so the configuration crosses the Astro frontmatter → client island boundary without losing fidelity.
5
+ The package is portable: every project-specific identifier is driven by the host's `PanelConfig`. Storage keys, console namespace, modal class prefix, schema id, and the entire tab configuration (tiers, items, color cluster extras) are all configured by the consumer. Every config field except the function-valued `applySink` is JSON-serializable, so the configuration crosses the Astro frontmatter → client island boundary without losing fidelity.
6
6
 
7
7
  The panel uses an **abstract token tier model**: all token categories — spacing, typography, size, and color — are expressed as `TabConfig` / `TierConfig` / `TierItem` structures on `PanelConfig.tabs`. A "ref tier" mechanism lets semantic tokens reference base tokens, and the apply pipeline emits `var(--base-cssvar)` for ref-tier items. See `PORTABLE-CONTRACT.md` §3 for the full tier model spec.
8
8
 
@@ -10,23 +10,53 @@ The authoritative API spec is [`PORTABLE-CONTRACT.md`](./PORTABLE-CONTRACT.md).
10
10
 
11
11
  ---
12
12
 
13
+ ## Features
14
+
15
+ - Token tabs and tiers for spacing, typography, sizing, color, and host-defined
16
+ token families, with reference-tier aliases and kind-specific editors.
17
+ - Fuzzy token search, cross-tab counts, and a `Cmd/Ctrl+K` command palette.
18
+ - Changed-row markers, per-row revert, tab badges, **Changed only**, **Copy
19
+ diff**, and **Revert all**.
20
+ - Undo/redo history plus persisted A/B snapshots.
21
+ - Numeric ramp selection and bulk multiply, add, round-to-step, or set-all
22
+ operations.
23
+ - Resolution-chain popovers with dependents, ramp siblings, blast radius, and
24
+ on-demand used-on-page counts.
25
+ - Ten-slot token-user highlights, element inspect, element path, and the
26
+ optional DOM Tweaker, coordinated around one Alt+click owner.
27
+ - In-panel type, line-height, family, weight, bar, radius, and duration
28
+ previews, plus a host-page specimen rendered with the site's real font.
29
+ - Float, right-dock, bottom-dock, mini-pill, ghost-idle, and optional
30
+ body-margin reflow modes.
31
+ - Debounced apply previews with file digests and hunks, stale-write protection,
32
+ partial reconciliation, and JSON export/import when disk apply is not
33
+ configured.
34
+ - Owner autoload for developer-only loading across page visits.
35
+
36
+ See the [Panel UX tour](/docs/recipes/panel-ux-tour) for a feature-by-feature
37
+ walkthrough and the shortcut table.
38
+
39
+ ---
40
+
13
41
  ## 0. Architecture at a glance
14
42
 
15
- The design-token panel is a browser-based UI that writes token overrides to `:root`, with an optional **apply pipeline** for persisting those overrides back to disk source files.
43
+ The design-token panel is a browser-based UI that writes token overrides to
44
+ `:root` by default (or through `PanelConfig.applySink`), with an optional
45
+ **apply pipeline** for persisting those overrides back to disk source files.
16
46
 
17
47
  ```
18
48
  ┌─ Your dev server (Astro / Vite / any host) ──────┐
19
49
  │ │
20
50
  │ Panel UI (browser) ←─────────────────────────> │ Host adapter (side-effect import)
21
51
  │ ↓ (user tweaks) │
22
- │ POST /apply (JSON diff) │ Apply endpoint (routes tokens to files)
52
+ │ POST /apply (preview, then confirmed write) │ Apply endpoint (routes tokens to files)
23
53
  │ ↓ │
24
54
  └──────────────────────────────────────────────────┘
25
55
  │
26
56
  │ (HTTP)
27
57
  ↓
28
58
  ┌─ design-token-panel bin server ───────────────┐
29
- │ Receives POST /apply with token diff │
59
+ │ Receives POST /apply preview/write requests │
30
60
  │ Validates tokens & paths │
31
61
  │ Rewrites source CSS files atomically │
32
62
  │ (respects --write-root sandbox) │
@@ -44,7 +74,7 @@ A Preact-rendered side panel that:
44
74
  - Reads a host-supplied **tab configuration** (`PanelConfig.tabs`) — an array of `TabConfig` entries where each tab owns one or more `TierConfig` objects, each holding an array of `TierItem` entries. Sliders (`length` / `number`), selects, text inputs, color pickers, and pill toggles are all supported via a discriminated `TierValueKind`.
45
75
  - Supports **abstract tier references**: a `TierConfig` can carry `referencesTier` to point at a base tier; the apply pipeline emits `var(--base-cssvar)` for each ref-tier item, encoding semantic → base token aliasing in the config data model.
46
76
  - Renders a **color tab** (id `'color'`) from the same tab model — palette and semantic tokens are `TierItem` arrays, and the structural metadata (base roles, scheme registry, panel settings) lives in `TabConfig.colorExtras`.
47
- - Writes every override to `document.documentElement.style.setProperty(...)` against the consumer-supplied CSS-var names — so your stylesheet can be plain CSS, CSS Modules, Tailwind, or anything else.
77
+ - Writes every override to the default `document.documentElement.style.setProperty(...)` target (or to the configured `applySink`) against the consumer-supplied CSS-var names — so your stylesheet can be plain CSS, CSS Modules, Tailwind, or anything else.
48
78
  - Persists state to `localStorage` under a host-chosen prefix and re-applies overrides synchronously on next page load (no FOUT — this is a hard requirement of the contract).
49
79
  - Exposes a small console API (`window.<namespace>.showDesignPanel()` etc.) so a developer can pop the panel without it being mounted on every page.
50
80
  - Plugs into Astro's view-transition lifecycle (`astro:before-swap` / `astro:page-load`) so soft navigation does not double-mount the panel.
@@ -111,13 +141,13 @@ pnpm exec zdtp-server --help
111
141
 
112
142
  | Flag | Required | Purpose | Default |
113
143
  |---|---|---|---|
114
- | `--routing <path>` | yes | Path to the routing JSON file (cluster id → repo-relative CSS file path). Absolute, or relative to `--root`. See §3.2. | — |
144
+ | `--routing <path>` | yes | Path to the routing JSON file (CSS-var prefix family → repo-relative CSS file path). Absolute, or relative to `--root`. See §3.2. | — |
115
145
  | `--write-root <dir>` | yes | Required write boundary: the only directory tree the bin is allowed to write into. Absolute, or relative to `--root`. | — |
116
146
  | `--root <dir>` | no | CWD/resolution base used to resolve `--routing` and `--write-root`. | `process.cwd()` |
117
147
  | `--port <number>` | no | TCP port to bind. `0` asks the OS for an ephemeral port (the bin logs the assigned port on startup). | `24681` |
118
148
  | `--host <addr>` | no | Bind address. Use `0.0.0.0` to expose on the LAN (off by default). | `127.0.0.1` |
119
149
  | `--allow-origin <origin>` | repeatable | Origin allowed to POST to `/apply` (scheme + host + port, no trailing slash). At least one is required for any browser to apply. | none (all origins denied) |
120
- | `--quiet` | no | Suppress the startup banner and the per-apply summary log line. | off |
150
+ | `--quiet` | no | Suppress the startup banner, no-allow-origin warning, and per-request summary logs. | off |
121
151
  | `--help`, `-h` | no | Print usage and exit 0. | — |
122
152
 
123
153
  Generic invocation, run from your consumer repo root:
@@ -133,7 +163,7 @@ The bin reads the routing JSON once at startup and does not hot-reload it. Resta
133
163
 
134
164
  ### 3.2 Routing configuration
135
165
 
136
- The routing JSON is a top-level object mapping **cluster id** → **repo-relative CSS file path**. Each path receives the apply pipeline's serialised writes for that cluster. The cluster ids must match the cluster ids used in your `PanelConfig.applyRouting` (the panel UI loads from this same JSON file — see §5).
166
+ The routing JSON is a top-level object mapping a **CSS-var prefix family** (without the leading `--` and trailing `-`) to a **repo-relative CSS file path**. Each path receives the apply pipeline's serialised writes for that prefix family. The keys must match the keys used in your `PanelConfig.applyRouting` (the panel UI loads from this same JSON file — see §5).
137
167
 
138
168
  Generic example:
139
169
 
@@ -181,7 +211,7 @@ While the bin is running it exposes a tiny HTTP surface:
181
211
 
182
212
  - **`GET /healthz`** — returns `200 OK` with `{"ok":true,"writeRoot":"…","routing":"…","port":…}` once the listener is up. Useful for dev-server readiness checks.
183
213
  - **`OPTIONS /apply`** — CORS preflight. Returns `204` with `Access-Control-Allow-{Origin,Methods,Headers,Max-Age}` headers when the request's `Origin` is on the allow-list, and `403` otherwise.
184
- - **`POST /apply`** — applies a token-overrides payload via the apply pipeline. The body is `application/json` with a top-level `tokens` object whose keys are CSS-var names (e.g. `--brand-primary`) and whose values are CSS values. See §6 for the full token-manifest schema and §6.6 for apply-time behaviour. A non-JSON content type returns `415`; an unallowed origin returns `403`.
214
+ - **`POST /apply`** — applies a token-overrides payload via the apply pipeline. The body is `application/json` with a top-level `tokens` object whose keys are CSS-var names (e.g. `--brand-primary`) and whose values are CSS values. See §6 for the full token-manifest schema and §6.5 for apply-time behaviour. A non-JSON content type returns `415`; an unallowed origin returns `403`.
185
215
  - **Anything else** — `404` for unknown paths and `405` for unsupported methods on `/apply`.
186
216
 
187
217
  The bin is intended to run as a subprocess of your dev server (`concurrently`, `npm-run-all`, a custom Node wrapper, etc.) and exits cleanly under host control:
@@ -260,7 +290,6 @@ const spacingTier: TierConfig = {
260
290
  id: 'spacing-md',
261
291
  cssVar: '--myapp-spacing-md',
262
292
  label: 'Spacing M',
263
- group: 'hsp',
264
293
  default: '1rem',
265
294
  type: { kind: 'length', step: 0.0625, unit: 'rem' },
266
295
  },
@@ -313,7 +342,7 @@ import { myPanelConfig } from '../lib/my-panel-config';
313
342
  </html>
314
343
 
315
344
  <script>
316
- // Required side-effect load — see §3.5 for the rationale. Use a dynamic
345
+ // Required side-effect load — see §12.1 for the rationale. Use a dynamic
317
346
  // `void import(...)` here, NOT a top-level `import '...';` statement.
318
347
  void import('@takazudo/zdtp/astro/host-adapter');
319
348
  </script>
@@ -377,7 +406,7 @@ The Astro recipe above shows the case where a host owns the config import and th
377
406
 
378
407
  **Worked example:** the [Astro](https://github.com/Takazudo/zudo-design-token-panel-example-astro), [Vite + React](https://github.com/Takazudo/zudo-design-token-panel-example-vite-react), and [Next.js](https://github.com/Takazudo/zudo-design-token-panel-example-nextjs) example repos prove the contract end-to-end. Each ships:
379
408
 
380
- - A host-side config file with deliberately different names (e.g. `--astro-palette-{n}`, `astro` namespace).
409
+ - A host-side config file with deliberately different names (e.g. explicit `--astro-palette-0` / `--astro-palette-1` items, `astro` namespace).
381
410
  - A routing JSON file at the example's root.
382
411
  - A bin invocation via `concurrently` in the dev script, pointing at that routing file.
383
412
 
@@ -385,6 +414,57 @@ Copy an example's structure when porting the panel into a new host.
385
414
 
386
415
  If you are building a **Rust SSG** or other non-Node host, the bin still runs as a sidecar Node.js subprocess (started by your host's build orchestration). The same routing JSON and host-adapter setup applies — the only difference is your host ships its own config format (not TypeScript) and you invoke the bin via your build system's subprocess spawner rather than npm scripts.
387
416
 
417
+ #### Pre-import activation from a custom host
418
+
419
+ The side-effect-free `@takazudo/zdtp/constants` subpath exposes
420
+ `DEFAULT_STORAGE_PREFIX`, `DEFAULT_TOGGLE_EVENT`, and
421
+ `resolveToggleEventName` without importing the panel. It also exports the
422
+ machine-readable eager-load metadata (`EAGER_LOAD_GATE_KEY_SUFFIXES` and
423
+ `EAGER_LOAD_GATE_STATE_FAMILY`); the exact gate contract lives in
424
+ [`PORTABLE-CONTRACT.md`](./PORTABLE-CONTRACT.md) §6.2.
425
+
426
+ A custom host can use those constants to register its toggle listener in the
427
+ initial bundle and fetch the panel only when activated. Keep the stylesheet
428
+ setup from §4.2 (or import `@takazudo/zdtp/styles` in your host stylesheet
429
+ pipeline):
430
+
431
+ ```ts
432
+ import type { PanelConfig } from '@takazudo/zdtp';
433
+ import { resolveToggleEventName } from '@takazudo/zdtp/constants';
434
+
435
+ // Call once with your complete config (for example myPanelConfig in §4.1.1).
436
+ export function installLazyPanel(config: PanelConfig): void {
437
+ const toggleEvent = resolveToggleEventName(config);
438
+ let loading: Promise<void> | undefined;
439
+
440
+ function loadOnToggle(): void {
441
+ if (loading) return;
442
+ loading = import('@takazudo/zdtp')
443
+ .then(({ configurePanel }) => {
444
+ configurePanel(config);
445
+ window.removeEventListener(toggleEvent, loadOnToggle);
446
+ window.dispatchEvent(new CustomEvent(toggleEvent));
447
+ })
448
+ .catch((error: unknown) => {
449
+ loading = undefined;
450
+ console.error('Failed to load @takazudo/zdtp', error);
451
+ });
452
+ }
453
+
454
+ window.addEventListener(toggleEvent, loadOnToggle);
455
+ }
456
+ ```
457
+
458
+ The default `DEFAULT_STORAGE_PREFIX` is
459
+ `'zudo-design-token-panel'`, and its `DEFAULT_TOGGLE_EVENT` is
460
+ `'toggle-design-token-panel'`. `resolveToggleEventName` preserves that
461
+ historical event even when a default-prefix config supplies `toggleEvent`; a
462
+ non-default prefix uses its supplied `toggleEvent`, or derives
463
+ `toggle-${storagePrefix}` when the override is omitted. The same config
464
+ resolves the listener event and configures the loaded panel. This activation-only
465
+ example does not implement the saved-state eager-load gate; see §6.2 of the
466
+ contract when building a complete host adapter.
467
+
388
468
  ### 4.3 Recipe — Rust SSG (zfb)
389
469
 
390
470
  Worked example for the case where the host is a Rust dev server (e.g. [zfb / zudo-front-builder](https://github.com/Takazudo/zudo-front-builder)) rather than a Node-based runner. The bin itself is unchanged — it remains a Node.js subprocess invoked as `node path/to/dist/bin/server.js ...`. The Rust host's only job is to spawn that Node process, forward shutdown signals to it, and configure `--allow-origin` so the browser POST from the panel UI is accepted.
@@ -504,12 +584,17 @@ default instance only.
504
584
  | `storagePrefix` | `string` | Base for every derived `localStorage` key. Also the instance id. See §9. |
505
585
  | `consoleNamespace` | `string` | Global object the package installs `showDesignPanel` / `hideDesignPanel` / `toggleDesignPanel` on (e.g. `consoleNamespace: 'myapp'` → `window.myapp.showDesignPanel`). |
506
586
  | `modalClassPrefix` | `string` | BEM root class for every modal the panel owns (export, import, apply). Emits `${prefix}__overlay`, `${prefix}__panel`, etc. |
507
- | `schemaId` | `string` | **Display-only** label surfaced in the Import modal's copy. `serialize()` always emits the canonical `SCHEMA_V2`/`SCHEMA_V3` and `deserialize()` always validates against the canonical `SCHEMA_V1`/`V2`/`V3` — neither reads this field. See §14's migration recipe. |
587
+ | `schemaId` | `string` | **Display-only** label returned by `getDesignTokenSchema()`. The built-in import/export UI does not read it; `serialize()` emits canonical `SCHEMA_V2`/`SCHEMA_V3` and `deserialize()` validates canonical `SCHEMA_V1`/`V2`/`V3`. See §14's migration recipe. |
508
588
  | `exportFilenameBase` | `string` | Default download filename base — exports save as `${exportFilenameBase}.json`. |
509
589
  | `toggleEvent` | `string` (optional) | Window-event name that toggles THIS instance. Defaults to `toggle-${storagePrefix}` for non-default instances; the default instance keeps `toggle-design-token-panel`. |
510
590
  | `tabs` | `readonly TabConfig[]` | **Required.** Tab strip data — each entry is a tab with one or more `TierConfig` objects. The color tab (id `'color'`) additionally requires `colorExtras`. See §6. |
511
591
  | `colorPresets` | `Record<string, ColorScheme>` (optional) | Optional named scheme presets surfaced in the Color tab "Scheme..." dropdown. Defaults to `{}`. See §7.5. |
592
+ | `applyEndpoint` | `string` (optional) | Browser POST target for apply previews and confirmed writes. See §3 and [Apply pipeline reference](/docs/reference/apply-pipeline). |
593
+ | `applyRouting` | `Record<string, string>` (optional) | CSS-var prefix-family to repo-relative CSS-file routing map used by the apply endpoint. |
512
594
  | `applySink` | `ApplySink` (optional) | Optional sink that routes this instance's CSS-var writes off `:root`. See §5.4. Not JSON-serializable — do not include in Astro inline config. |
595
+ | `domTweaker` | `DomTweakerConfig` (optional) | Enables the development-only Tailwind class editor and its Alt+click owner. See [DOM Tweaker reference](/docs/reference/dom-tweaker). |
596
+ | `dock` | `PanelDockConfig` (optional) | Controls right/bottom host reflow. Defaults to `{ reflow: 'body-margin' }`; see [configurePanel](/docs/reference/configure-panel#dock--optional-host-reflow). |
597
+ | `legacyIdRenameMap` | `Record<string, string \| null>` (optional) | Maps renamed persisted ids to new ids, or drops ids with `null`, during load. |
513
598
  | `autoRememberOnOpen` | `boolean` (optional) | Whether opening the panel (any of the auto-remember call sites — see §10.1) writes the `:autoload` flag with `'auto'` provenance. Defaults to `true`. Set `false` for a public site that wants a panel-open button visible to every visitor without arming owner-mode for whoever clicks it. `enableAutoload()`'s explicit `'1'` write is unaffected either way. See §10.1's Auto-remember footgun. |
514
599
 
515
600
  ### 5.4 `applySink` — optional write target
@@ -561,10 +646,10 @@ const handle = configurePanel({
561
646
  The Astro entry point (`<DesignTokenPanelHost>`) handles mounting for you. Internally:
562
647
 
563
648
  - The console API (`showDesignPanel` etc.) is **always installed eagerly**, even when the panel module has not loaded — calling them is what triggers the lazy import for cold-start users.
564
- - The panel module is **dynamically imported on first need**: when the user calls a console helper, OR when first-paint detects any of these gate signals in `localStorage` — `${storagePrefix}:visible` set to `1` or its `${storagePrefix}-open` mirror set to `1`, persisted overrides (a content check across the `${storagePrefix}-state` family — `-state` (v1) through every `-state-vN` — rather than a presence check on a specific version key; see §9), the owner-autoload flag (`${storagePrefix}:autoload` set to `'1'` or `'auto'`), or the element-path inspector enabled.
565
- - This gating keeps the panel out of the initial JS bundle for first-time visitors while still re-applying overrides on hard reload for users who have tweaked things. **General visitors** (none of these signals set) pay zero bundle cost.
649
+ - The panel module is **dynamically imported on first need**: when the user calls a console helper, OR when first-paint detects any of these gate signals in `localStorage` — `${storagePrefix}:visible` set to `1` or its `${storagePrefix}-open` mirror set to `1`, persisted overrides (a content check across the `${storagePrefix}-state` family — `-state` (v1) through every `-state-vN` — rather than a presence check on a specific version key; see §9), the owner-autoload flag (`${storagePrefix}:autoload` set to `'1'` or `'auto'`), or the element-path inspector enabled. `${storagePrefix}-domtweaker-enabled` is also a gate signal when `domTweaker` is configured.
650
+ - This gating keeps the panel bundle out of the initial JS payload for first-time visitors while still re-applying overrides on hard reload for users who have tweaked things. **General visitors** (none of these signals set) pay no panel-bundle cost; the small host adapter/config bootstrap still runs.
566
651
 
567
- For a Vite-only / non-Astro host, mount it yourself by importing the adapter module after `configurePanel(...)`. See §11.5.
652
+ For a Vite-only / non-Astro host, mount it yourself by importing the adapter module after `configurePanel(...)`. See §8.5.
568
653
 
569
654
  ### 5.6 First-open geometry
570
655
 
@@ -589,24 +674,22 @@ spec; this section is the consumer-oriented summary.
589
674
 
590
675
  ```ts
591
676
  export type TierValueKind =
592
- | { kind: 'length'; step: number; unit: string }
593
- | { kind: 'number'; step: number }
677
+ | { kind: 'length'; step: number; unit: string; units?: readonly string[] }
678
+ | { kind: 'number'; step: number; unit?: string }
594
679
  | { kind: 'select'; options: readonly string[] }
595
680
  | { kind: 'text' }
596
681
  | { kind: 'cursor' }
597
682
  | { kind: 'content' }
598
683
  | { kind: 'mask-image' }
599
- | { kind: 'color' };
684
+ | { kind: 'color'; format?: 'hex' | 'oklch' };
600
685
 
601
686
  export interface TierItem {
602
687
  /** Stable id used as the key in persisted state (e.g. `hsp-2xs`). */
603
688
  id: string;
604
- /** CSS custom property written to `:root` (e.g. `--myapp-spacing-md`). */
689
+ /** CSS custom property written to the default root or configured apply sink (e.g. `--myapp-spacing-md`). */
605
690
  cssVar: string;
606
691
  /** Display label shown in the panel row. */
607
692
  label: string;
608
- /** Optional manifest group — tab components use this for section headers. */
609
- group?: string;
610
693
  /** Default value as a CSS string (`0.125rem`, `12px`, etc.). */
611
694
  default: string;
612
695
  /** Discriminated union describing the control kind and its metadata. */
@@ -631,16 +714,29 @@ export interface TierConfig {
631
714
  * apply pipeline emits `var(--target-cssvar)` for ref-tier items.
632
715
  */
633
716
  referencesTier?: string;
717
+ /** Marks a color tier as semantic data rather than a palette tier. */
718
+ semantic?: true;
719
+ /** Allowed ramp sources for per-row semantic references on color tabs. */
720
+ referencesRamps?: readonly { tab?: string; tier: string }[];
721
+ /** Optional visual preview rendered for this tier in the panel. */
722
+ preview?: 'size' | 'line-height' | 'family' | 'weight' | 'bar' | 'radius' | 'duration';
723
+ /** CSS variable used as the base for a preview, when supplied. */
724
+ previewBase?: string;
634
725
  }
635
726
 
636
727
  export interface TabConfig {
637
728
  id: string;
638
729
  label: string;
639
730
  tiers: readonly TierConfig[];
640
- /** Tier ids hidden behind an Advanced disclosure. */
641
- advancedTiers?: readonly string[];
642
731
  /** Required on color tabs (id 'color' / 'color-secondary'). */
643
732
  colorExtras?: ColorClusterExtras;
733
+ /** Required on the reserved 'notes' tab; forbidden on other tabs. */
734
+ notesExtras?: NotesExtras;
735
+ }
736
+
737
+ export interface NotesExtras {
738
+ title: string;
739
+ html: string;
644
740
  }
645
741
  ```
646
742
 
@@ -662,7 +758,6 @@ export const spacingTab: TabConfig = {
662
758
  id: 'spacing-md',
663
759
  cssVar: '--myapp-spacing-md',
664
760
  label: 'Spacing M',
665
- group: 'hsp',
666
761
  default: '1rem',
667
762
  type: { kind: 'length', step: 0.0625, unit: 'rem' },
668
763
  },
@@ -747,8 +842,8 @@ export interface ColorScheme {
747
842
  cursor: number | string;
748
843
  selectionBg: number | string;
749
844
  selectionFg: number | string;
750
- palette: readonly string[]; // length must match the palette tier's item count
751
- shikiTheme: string;
845
+ palette: readonly string[]; // the public type requires exactly 16 entries
846
+ shikiTheme?: string;
752
847
  semantic?: Record<string, number | string>;
753
848
  }
754
849
  ```
@@ -798,9 +893,13 @@ the JSON round-trip.
798
893
 
799
894
  For each palette `TierItem` in the palette tier, the panel writes
800
895
  `item.cssVar` ← `palette[i]` from the active scheme / user override. For
801
- each declared base role, it writes the role's CSS-var ← `palette[state[roleKey]]`.
802
- For each semantic `TierItem`, it resolves the mapping and writes
803
- `item.cssVar` ← resolved hex.
896
+ each declared base role, the live DOM apply path writes the role's CSS-var ←
897
+ `palette[state[roleKey]]`. The disk `buildApplyOverrides` payload
898
+ intentionally contains palette and semantic CSS variables only; base roles are
899
+ runtime wiring and are not emitted into source-file rewrites.
900
+ For each semantic `TierItem`, it resolves the mapping and writes the emitted
901
+ CSS value: `var(...)` for a palette/reference mapping, a literal string for a
902
+ literal mapping, or `light-dark(light, dark)` for a per-mode literal.
804
903
 
805
904
  Roles absent from `colorExtras.baseRoles` are not written, so a minimalist
806
905
  cluster (just `background` + `foreground`) is fine.
@@ -809,7 +908,7 @@ cluster (just `background` + `foreground`) is fine.
809
908
 
810
909
  The Color tab's "Scheme..." dropdown surfaces named `ColorScheme` entries. Two sources feed it:
811
910
 
812
- 1. **`colorCluster.colorSchemes`** — the cluster's bundled scheme registry. Always present, typically holds your default scheme(s) (`"Default"`, `"Default Light"` / `"Default Dark"`).
911
+ 1. **`colorExtras.colorSchemes`** — the color tab's bundled scheme registry. Always present, typically holds your default scheme(s) (`"Default"`, `"Default Light"` / `"Default Dark"`).
813
912
  2. **`PanelConfig.colorPresets`** — an optional, host-supplied preset map for an additional, larger preset library. Defaults to `{}` — the package itself ships zero presets.
814
913
 
815
914
  This split exists so a host that just wants the panel for a single scheme (zero or one cluster scheme) does not pay for a long preset blob, while a host that wants to ship a "playground" of curated schemes (Dracula / Solarized / Tokyo Night / etc.) drops them into a single config field.
@@ -829,7 +928,7 @@ const myPresets: Record<string, ColorScheme> = {
829
928
  selectionBg: '#44475a',
830
929
  selectionFg: '#ffffff',
831
930
  palette: [
832
- // 16 hex strings — length must match colorCluster.paletteSize
931
+ // 16 hex strings — the ColorScheme type's fixed palette length
833
932
  '#21222c',
834
933
  '#ff5555',
835
934
  '#50fa7b',
@@ -854,7 +953,7 @@ const myPresets: Record<string, ColorScheme> = {
854
953
  };
855
954
 
856
955
  export const myPanelConfig: PanelConfig = {
857
- // ... storagePrefix, tokens, colorCluster, etc.
956
+ // ... storagePrefix, tabs (including the color tab), etc.
858
957
  colorPresets: myPresets,
859
958
  };
860
959
  ```
@@ -868,7 +967,7 @@ export const myPanelConfig: PanelConfig = {
868
967
 
869
968
  **Key collision** — if a `colorPresets` key matches a `colorExtras.colorSchemes` key, the bundled scheme wins for `handleLoadPreset`. Rename one of the keys if you want both to be selectable.
870
969
 
871
- **JSON-serializable** — every `ColorScheme` is plain JSON, same as the rest of the config (§7.2). The host-supplied preset map crosses the Astro frontmatter → island boundary as part of the serialised `PanelConfig`.
970
+ **JSON-serializable** — every `ColorScheme` is plain JSON, same as the rest of the config (§8.2). The host-supplied preset map crosses the Astro frontmatter → island boundary as part of the serialised `PanelConfig`.
872
971
 
873
972
  > **Note on preset libraries.** The package ships zero baked-in scheme presets — the long preset blob (Dracula / Solarized / Tokyo Night / etc.) historically baked into earlier internal versions has been moved out of the package so consumers do not pay for a preset library they do not use. Hosts that want a curated preset list ship it themselves through `panelConfig.colorPresets`.
874
973
 
@@ -903,7 +1002,7 @@ Astro stringifies props at render time. **Functions, class instances, and `undef
903
1002
  When the consumer site renders Astro's `<ClientRouter />`, the panel's host adapter automatically wires:
904
1003
 
905
1004
  - `astro:before-swap` → unmount the Preact tree (`render(null, root)`), remove the host node, snapshot visibility intent so the remount decision survives the body swap.
906
- - `astro:page-load` → re-apply persisted overrides + re-materialise the shell when either the visibility flag or the persisted-overrides flag is set.
1005
+ - `astro:page-load` → re-apply persisted overrides + re-materialise the shell when any visibility, autoload, enabled-feature, or non-empty persisted-state signal is set.
907
1006
 
908
1007
  No additional wiring needed in your layout beyond importing `<ClientRouter />` from `astro:transitions`.
909
1008
 
@@ -962,10 +1061,24 @@ Behaviour notes:
962
1061
  | `state-v3` | `${storagePrefix}-state-v3` | Legacy pre-v4 format (flat, single-slot `color`). Migrated into `state-v4` on first load; the v3 key itself is left in place (not deleted) so a downgrade can still read it. |
963
1062
  | `state-v2` | `${storagePrefix}-state-v2` | Legacy pre-v3 format. Migrated into `state-v3` (and from there into `state-v4`) on first load, then deleted. |
964
1063
  | `state-v1` | `${storagePrefix}-state` | Legacy pre-v2 flat-state format (Color-only). Migrated into `state-v3` (and from there into `state-v4`) on first load, then deleted. |
965
- | `open` | `${storagePrefix}-open` | Mirror of the panel's `open` boolean (synchronous mount-time read — preserves user intent across reloads, fixes #1549). |
966
- | `position` | `${storagePrefix}-position` | Drag position `{ top, left }` so the panel reappears where the user left it. |
967
- | `visible` | `${storagePrefix}:visible` | Adapter-level visibility-intent flag, owned by the lazy-load gate. |
968
- | `autoload` | `${storagePrefix}:autoload` | Owner-mode autoload flag. Holds `'1'` (explicit — set by `enableAutoload()`) or `'auto'` (auto-remembered — set by opening the panel). Either value arms the panel bundle to fetch eagerly on every page load and mount CLOSED so the Alt+click element-path inspector is armed without opening the panel UI. Cleared by `disableAutoload()`. See §10.1. |
1064
+ | `open` | `${storagePrefix}-open` | Mirror of the panel's `open` boolean, read synchronously at mount so user intent survives reloads. |
1065
+ | `position` | `${storagePrefix}-position` | Drag position `{ top, left }` so the panel reappears where the user left it. |
1066
+ | `size` | `${storagePrefix}-size` | Floating shell dimensions `{ width, height }` in pixels. |
1067
+ | `dock` | `${storagePrefix}-dock` | Presentation mode: `'float'`, `'right'`, `'bottom'`, or `'mini'`. |
1068
+ | `dock-size` | `${storagePrefix}-dock-size` | Right/bottom dock dimensions `{ right, bottom }`, defaulting to `{ right: 440, bottom: 340 }`. |
1069
+ | `density` | `${storagePrefix}-density` | Tab-grid density preference (`0`, `1`, or `2`). |
1070
+ | `ghost` | `${storagePrefix}-ghost` | Ghost-when-idle preference (`'1'` when enabled). |
1071
+ | `specimen` | `${storagePrefix}-specimen` | Font specimen toolbar JSON `{ text, preset, overridden, width }`; width is clamped to 240–720. |
1072
+ | `snapshot-a` | `${storagePrefix}-snapshot-a` | Persisted A snapshot `{ state, identity, savedAt, edits }`. |
1073
+ | `snapshot-b` | `${storagePrefix}-snapshot-b` | Persisted B snapshot `{ state, identity, savedAt, edits }`. |
1074
+ | `last-applied` | `${storagePrefix}-last-applied` | Flat comparison baseline; a successful apply resets it to `{}` while unconfirmed overrides remain in live state. |
1075
+ | `visible` | `${storagePrefix}:visible` | Adapter-level visibility-intent flag, owned by the lazy-load gate. |
1076
+ | `autoload` | `${storagePrefix}:autoload` | Owner-mode autoload flag: `'1'` for explicit opt-in or `'auto'` for auto-remember. |
1077
+ | `elpath-enabled` | `${storagePrefix}-elpath-enabled` | Element-path picker enabled bit. |
1078
+ | `domtweaker-enabled` | `${storagePrefix}-domtweaker-enabled` | DOM Tweaker enabled bit; meaningful only when `domTweaker` is configured. |
1079
+ | `highlight-slots` | `${storagePrefix}-highlight-slots` | Ten highlight slot colors in local storage. |
1080
+ | `highlight-outline-width` | `${storagePrefix}-highlight-outline-width` | Global highlight outline width in local storage, clamped to 1–20. |
1081
+ | `highlight-active` | `${storagePrefix}-highlight-active` | Active CSS-variable-to-slot map in session storage. |
969
1082
 
970
1083
  For example, with `storagePrefix: 'myapp-design-token-panel'`:
971
1084
 
@@ -976,8 +1089,22 @@ myapp-design-token-panel-state-v2
976
1089
  myapp-design-token-panel-state
977
1090
  myapp-design-token-panel-open
978
1091
  myapp-design-token-panel-position
1092
+ myapp-design-token-panel-size
1093
+ myapp-design-token-panel-dock
1094
+ myapp-design-token-panel-dock-size
1095
+ myapp-design-token-panel-density
1096
+ myapp-design-token-panel-ghost
1097
+ myapp-design-token-panel-specimen
1098
+ myapp-design-token-panel-snapshot-a
1099
+ myapp-design-token-panel-snapshot-b
1100
+ myapp-design-token-panel-last-applied
979
1101
  myapp-design-token-panel:visible
980
1102
  myapp-design-token-panel:autoload
1103
+ myapp-design-token-panel-elpath-enabled
1104
+ myapp-design-token-panel-domtweaker-enabled
1105
+ myapp-design-token-panel-highlight-slots
1106
+ myapp-design-token-panel-highlight-outline-width
1107
+ myapp-design-token-panel-highlight-active # sessionStorage
981
1108
  ```
982
1109
 
983
1110
  ### Note: colon vs dash — `visible` and `autoload`
@@ -1008,11 +1135,11 @@ When the host dispatches a `color-scheme-changed` event (e.g. a light/dark toggl
1008
1135
  - If that identity already has a persisted slot, the slot's stored `color` (and `secondary`) state is loaded and re-applied — a per-scheme tweak now **survives** a round-trip through another scheme and back.
1009
1136
  - If the identity has no slot yet (never tweaked under this scheme before), the panel cold-seeds from that scheme's defaults instead — the pre-existing "adopt the new scheme's palette" behavior for a scheme you haven't touched.
1010
1137
 
1011
- Editing color under scheme A only ever overwrites scheme A's slot (`writeMergedV4` merge-saves: it reads the existing envelope, replaces just the active identity's slot, and preserves every other identity's slot untouched) — it never mutates scheme B's stored state. A full panel reset (Reset / Apply) still wipes every slice via `clearAppliedStyles`.
1138
+ Editing color under scheme A only ever overwrites scheme A's slot (`writeMergedV4` merge-saves: it reads the existing envelope, replaces just the active identity's slot, and preserves every other identity's slot untouched) — it never mutates scheme B's stored state. A full panel reset (Reset) still wipes every slice via `clearAppliedStyles`; a disk Apply only reconciles confirmed-written variables and does not reset other identities or slices.
1012
1139
 
1013
- **Migration.** On first load with no `state-v4` key, the legacy v1/v2/v3 chain runs unchanged (v3 wins over v2 over v1, exactly as before) and the resulting single flat `TweakState` is filed into `state-v4` under whichever identity is active at that moment — every subsequent load reads `state-v4` first. The legacy v3/v2/v1 keys are left in place by the v4 migration step (only the v1→v2→v3 chain deletes as it goes), so a host that needs to downgrade can still read them.
1140
+ **Migration.** On first load with no `state-v4` key, the legacy v1/v2/v3 chain runs unchanged (v3 wins over v2 over v1, exactly as before); v2 and v1 are normalised to v3 as that chain requires, and the resulting single flat `TweakState` is filed into `state-v4` under whichever identity is active at that moment — every subsequent load reads `state-v4` first. The legacy v3 key is left in place by the v4 migration step so a host that needs to downgrade can still read it; the lower v2/v1 keys follow the legacy chain's deletion rules.
1014
1141
 
1015
- **Host-owned `color-scheme` is never touched.** A host that manages its own `<html style="color-scheme">` (e.g. a site-level light/dark toggle) has that inline style tracked separately from the panel's own writes; the panel's mount/clear paths only ever remove a `color-scheme` value that the panel itself applied, never a host-owned one (#501).
1142
+ **Host-owned `color-scheme` is never touched.** A host that manages its own `<html style="color-scheme">` (e.g. a site-level light/dark toggle) has that inline style tracked separately from the panel's own writes; the panel's mount/clear paths only ever remove a `color-scheme` value that the panel itself applied, never a host-owned one.
1016
1143
 
1017
1144
  Two consequences worth knowing as a host integrator:
1018
1145
 
@@ -1074,7 +1201,7 @@ There is no default `consoleNamespace` exposed to consumers — the field is req
1074
1201
 
1075
1202
  ### 10.1 Owner-autoload — loading the panel only for you
1076
1203
 
1077
- **Use case:** you are deploying a public site and want the design-token panel available to yourself (the site owner) without loading any panel JS for general visitors. Once armed, every page load fetches the bundle eagerly and mounts the panel CLOSED — the Alt+click element-path inspector is ready immediately, and you can open the panel UI at any time.
1204
+ **Use case:** you are deploying a public site and want the design-token panel available to yourself (the site owner) without fetching the panel bundle or injecting its stylesheet for general visitors. The small host adapter/config bootstrap still runs. Once armed, every page load fetches the bundle eagerly and mounts the panel CLOSED — the Alt+click element-path inspector is ready immediately, and you can open the panel UI at any time.
1078
1205
 
1079
1206
  #### API surface
1080
1207
 
@@ -1083,14 +1210,14 @@ Both the Astro host-adapter (`window[consoleNamespace].*`) and the package-root
1083
1210
  | Call | Effect |
1084
1211
  |------|--------|
1085
1212
  | `window.myapp.enableAutoload()` | Sets `${storagePrefix}:autoload` to `'1'`, arms the element-path inspector (`-elpath-enabled`), loads the panel bundle, mounts CLOSED. |
1086
- | `window.myapp.disableAutoload()` | Clears `:autoload`, `:visible`, `-elpath-enabled`, and the open-state key; unmounts the panel. |
1213
+ | `window.myapp.disableAutoload()` | Removes `:autoload`, writes `'0'` to `:visible` and `-elpath-enabled`, removes the open-state key, and unmounts the panel. |
1087
1214
  | `enableAutoload()` (package-root export) | Same as the console form — for non-Astro hosts. |
1088
1215
  | `disableAutoload()` (package-root export) | Same as the console form — for non-Astro hosts. |
1089
1216
  | `shouldAutoload()` (package-root export) | Returns `true` iff the flag is currently set. |
1090
1217
 
1091
1218
  #### The `${storagePrefix}:autoload` flag
1092
1219
 
1093
- Stored in `localStorage` as `'1'` (explicit — set by `enableAutoload()`) or `'auto'` (auto-remembered — set by opening the panel; see "Auto-remember on open" below), removed or `'0'` otherwise. The host-adapter's lazy-load gate honours **either** value — when the flag is `'1'` or `'auto'`, the panel bundle loads eagerly (the same as when the panel was previously visible, via either the `:visible` flag or its `-open` mirror, or overrides are persisted, checked as a content check across the `${storagePrefix}-state` family rather than a presence check on a specific version key — see §9). **General visitors** who have never opened the panel or called `enableAutoload()` have no flag and pay zero bundle cost.
1220
+ Stored in `localStorage` as `'1'` (explicit — set by `enableAutoload()`) or `'auto'` (auto-remembered — set by opening the panel; see "Auto-remember on open" below), removed or `'0'` otherwise. The host-adapter's lazy-load gate honours **either** value — when the flag is `'1'` or `'auto'`, the panel bundle loads eagerly (the same as when the panel was previously visible, via either the `:visible` flag or its `-open` mirror, or overrides are persisted, checked as a content check across the `${storagePrefix}-state` family rather than a presence check on a specific version key — see §9). **General visitors** who have never opened the panel or called `enableAutoload()` have no flag and pay no panel-bundle cost; the small host adapter/config bootstrap still runs.
1094
1221
 
1095
1222
  **Downstream-host recipe.** A host that writes its own lazy-load probe (e.g. to decide whether to eagerly fetch something else alongside the panel) can read the flag directly and test `=== '1'` to match only the explicit-owner population, excluding visitors who merely opened the panel once.
1096
1223
 
@@ -1098,11 +1225,11 @@ Stored in `localStorage` as `'1'` (explicit — set by `enableAutoload()`) or `'
1098
1225
 
1099
1226
  #### Auto-remember on open
1100
1227
 
1101
- Opening the panel by any means — `showDesignPanel()`, `toggleDesignPanel()`, or clicking the panel's own header close button to re-open it — **automatically sets the `:autoload` flag to `'auto'`** (auto-remembered provenance, distinct from the `'1'` that `enableAutoload()` writes). This means once you open the panel you will be in owner-mode on subsequent page loads without calling `enableAutoload()` explicitly. An existing explicit `'1'` is never downgraded — an owner who armed autoload deliberately keeps that provenance even after opening the panel again.
1228
+ Opening the panel by any means — `showDesignPanel()`, `toggleDesignPanel()`, the instance handle's `open()` / `toggle()`, the fixed-name `zdtp.show()` / `zdtp.toggle()` global, or an instance toggle event — **automatically sets the `:autoload` flag to `'auto'`** (auto-remembered provenance, distinct from the `'1'` that `enableAutoload()` writes). This means once you open the panel you will be in owner-mode on subsequent page loads without calling `enableAutoload()` explicitly. An existing explicit `'1'` is never downgraded — an owner who armed autoload deliberately keeps that provenance even after opening the panel again.
1102
1229
 
1103
1230
  #### Element-path coupling
1104
1231
 
1105
- `enableAutoload()` arms the Alt+click element-path inspector by writing `-elpath-enabled = '1'`. The inspector runs inside the Preact shell; mounting the shell CLOSED (rather than unmounted) keeps the inspector functional even while the panel UI is hidden. You can still turn the inspector off via the in-panel toggle — `disableAutoload()` clears it as part of full teardown.
1232
+ `enableAutoload()` arms the Alt+click element-path inspector by writing `-elpath-enabled = '1'`. The inspector runs inside the Preact shell; mounting the shell CLOSED (rather than unmounted) keeps the inspector functional even while the panel UI is hidden. You can still turn the inspector off via the in-panel toggle — `disableAutoload()` writes `'0'` as part of full teardown.
1106
1233
 
1107
1234
  #### Auto-remember footgun
1108
1235
 
@@ -1116,48 +1243,95 @@ Because **any open trigger sets `:autoload`** (to `'auto'`), a visible "open pan
1116
1243
 
1117
1244
  ---
1118
1245
 
1246
+ ### 10.2 Shared Alt+click picker ownership
1247
+
1248
+ Element path, DOM Tweaker, and element inspect use one per-window coordinator
1249
+ for the Alt+click gesture. Enabling or arming one feature revokes the previous
1250
+ owner; the three features do not run simultaneously. Panel surfaces and the
1251
+ on-page specimen are excluded from picker hits.
1252
+
1253
+ | Feature | Activation | Result |
1254
+ | --- | --- | --- |
1255
+ | Element path | Owner autoload or its panel toggle, then `Alt+click` a host element | Copies the annotated selector/path block. |
1256
+ | DOM Tweaker | Enable the configured DOM Tweaker, then `Alt+click` a host element | Opens the Tailwind class editor and live utility preview. |
1257
+ | Element inspect | Toggle inspect or press `I`, then click a host element; `Alt` also arms the shared picker | Opens the reserved inspect tab with token-backed and inherited rows. |
1258
+
1259
+ The most recently armed feature owns the gesture and receives subsequent page
1260
+ clicks. Owner autoload's element-path arm therefore does not block a later DOM
1261
+ Tweaker or element-inspect arm.
1262
+
1263
+ ---
1264
+
1119
1265
  ## 11. Tailwind not required
1120
1266
 
1121
- The panel ships its own bundled CSS scoped under a panel-private namespace:
1267
+ The panel ships its own bundled CSS scoped under a panel-private namespace. The
1268
+ chrome palette is a self-contained OKLCH ramp; it does not read host
1269
+ `--color-*` or `--font-mono` variables. A host can opt into a different chrome
1270
+ theme by assigning the same `--tokentweak-*` variables on a listed scope.
1122
1271
 
1123
1272
  ```css
1124
1273
  :where(.tokenpanel-shell, [data-design-token-panel-modal]) {
1125
- --tokentweak-color-fg: var(--color-fg, #b8b8b8);
1126
- --tokentweak-color-bg: var(--color-bg, #181818);
1127
- --tokentweak-color-surface: var(--color-surface, #1c1c1c);
1128
- --tokentweak-color-accent: var(--color-accent, #d69a66);
1129
- --tokentweak-font-mono: var(--font-mono, Menlo, Monaco, Consolas, …);
1130
- --tokentweak-pad-md: …;
1131
- --tokentweak-gap-sm: …;
1132
- --tokentweak-text-body: …;
1133
- --radius-tokentweak: …;
1134
- /* …one custom property per panel-chrome value */
1274
+ /* base-0 is the darkest ground; stops ascend toward the foreground. */
1275
+ --tokentweak-palette-base-0: oklch(0.18 0 0);
1276
+ --tokentweak-palette-base-1: oklch(0.25 0 0);
1277
+ --tokentweak-palette-base-2: oklch(0.34 0 0);
1278
+ --tokentweak-palette-base-3: oklch(0.536 0 0);
1279
+ --tokentweak-palette-base-4: oklch(0.66 0 0);
1280
+ --tokentweak-palette-base-5: oklch(0.8 0 0);
1281
+ --tokentweak-palette-base-6: oklch(0.91 0 0);
1282
+ --tokentweak-color-fg: var(--tokentweak-palette-base-5);
1283
+ --tokentweak-color-bg: var(--tokentweak-palette-base-0);
1284
+ --tokentweak-color-muted: var(--tokentweak-palette-base-4);
1285
+ --tokentweak-color-border: var(--tokentweak-palette-base-3);
1286
+ --tokentweak-color-surface: var(--tokentweak-palette-base-1);
1287
+ --tokentweak-color-accent: #d69a66;
1288
+ --tokentweak-color-accent-bar: #efb477;
1289
+ --tokentweak-color-accent-hover: #a7c0e3;
1290
+ --tokentweak-color-code-bg: var(--tokentweak-palette-base-2);
1291
+ --tokentweak-color-code-fg: var(--tokentweak-palette-base-6);
1292
+ --tokentweak-color-success: #93bb77;
1293
+ --tokentweak-color-danger: #da6871;
1294
+ --tokentweak-color-warning: #dfbb77;
1295
+ --tokentweak-font-mono: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
1296
+ /* pad-*, gap-*, text-*, radius, and z-* tokens are also declared here. */
1135
1297
  }
1136
1298
  ```
1137
1299
 
1138
- - **Naming:** every panel-private CSS variable uses the `--tokentweak-*` prefix. Consumer-namespaced identifiers do not appear in the panel chrome — `panel.css` reads only `--tokentweak-*`.
1300
+ Hosts may override a `--tokentweak-palette-base-*` stop to move all semantic
1301
+ roles that alias it, or continue assigning a `--tokentweak-color-*` role
1302
+ directly. The direct semantic assignment still wins. Borders now use
1303
+ `--tokentweak-color-border`; a host that previously assigned
1304
+ `--tokentweak-color-muted` to recolor both secondary text and 1px dividers must
1305
+ also assign the border role.
1306
+
1307
+ - **Naming:** panel-private color, font, spacing, typography, and z-index variables use the `--tokentweak-*` prefix; the shared radius token is `--radius-tokentweak`. Consumer-namespaced identifiers do not appear in the panel chrome — `panel.css` reads only package-owned tokens and component-local layout hints.
1139
1308
  - **Files:** `panel.css` (chrome layout / typography / controls) + `panel-tokens.css` (the `--tokentweak-*` declarations). Both ship from the package and the consumer pulls them in via `sideEffects`.
1140
1309
  - **No Tailwind dependency in the consumer.** The panel chrome uses hand-authored CSS classes backed by `--tokentweak-*` variables. You can integrate the panel into a Tailwind site, a CSS Modules site, a vanilla CSS site, or anything in between.
1141
1310
 
1142
- ### 11.1 Two-layer override model for chrome colors
1143
-
1144
- The panel-chrome color tokens (`--tokentweak-color-fg`, `--tokentweak-color-bg`, `--tokentweak-color-muted`, `--tokentweak-color-surface`, `--tokentweak-color-accent`, `--tokentweak-color-accent-hover`, `--tokentweak-color-code-bg`, `--tokentweak-color-code-fg`, `--tokentweak-color-success`, `--tokentweak-color-danger`, `--tokentweak-color-warning`, `--tokentweak-font-mono`) are declared as a `var(--host, fallback)` ladder. This gives hosts two override layers and works out-of-the-box when neither is supplied:
1311
+ ### 11.1 Chrome scopes and host overrides
1145
1312
 
1146
- | Host declares | Outcome |
1147
- |---|---|
1148
- | Nothing | Built-in fallback paints the panel (a sensible neutral dark theme). |
1149
- | `--color-fg` (and friends) at `:root` | Host theme cascades into the panel — no panel-side change needed. |
1150
- | `--tokentweak-color-fg` directly | Panel-only override that bypasses the host's `--color-*` theme. Useful when you want the panel to look different from your site shell (e.g., a brand-neutral inspector overlay). |
1313
+ The declarations cover `.tokenpanel-shell`, `.tokenpanel-mini-pill`,
1314
+ `[data-design-token-panel-modal]`, the highlight-settings and chain popovers,
1315
+ the color picker, tooltip, element-path label/toast, and element-inspect box,
1316
+ label, and view. `:where()` keeps their specificity at zero, so a host can
1317
+ override a token with a single-class rule on the relevant surface. The host
1318
+ theme variables are never read by the panel chrome.
1151
1319
 
1152
- Because the fallback ladder is host-CSS-var-driven, a brand-new consumer can mount the panel without declaring any `--color-*` tokens and still get readable chrome.
1320
+ The CSS variables the panel **writes to** (the `cssVar` field on each
1321
+ `TierItem`, palette/base-role names, and semantic CSS names) are entirely
1322
+ consumer-controlled. The package only writes those variables through the
1323
+ default `document.documentElement` target or a configured `applySink`.
1153
1324
 
1154
- The CSS variables the panel **writes to** (the `cssVar` field on each `TokenDef`, the cluster's `paletteCssVarTemplate`, base-role names, and semantic CSS names) are entirely consumer-controlled. The package never reads those consumer CSS variables; it only writes through `setProperty` on `:root`.
1325
+ Dock integration publishes `--zdtp-dock-inset-right` and
1326
+ `--zdtp-dock-inset-bottom` on the host root while a claim is active. The
1327
+ page-level specimen uses the `.tokenpanel-on-page-specimen` class and
1328
+ `[data-zdtp-specimen]` marker; it is host-page content, not panel chrome.
1155
1329
 
1156
1330
  ### 11.2 Apply-to-disk for a Tailwind v4 `@theme` block
1157
1331
 
1158
1332
  The section above is about the panel's OWN chrome — it never needed Tailwind. This section is about the opposite direction: a Tailwind v4 **consumer** whose design tokens live inside `@theme { ... }` (so Tailwind can generate utility classes from them) rather than `:root`.
1159
1333
 
1160
- The apply-to-disk rewriter (§3.2) scans the first top-level `:root` block AND the first top-level `@theme` block, so a Tailwind v4 token file like this applies out of the box (issue #496's repro):
1334
+ The apply-to-disk rewriter (§3.2) scans the first top-level `:root` block AND the first top-level `@theme` block, so a Tailwind v4 token file like this applies out of the box:
1161
1335
 
1162
1336
  ```css
1163
1337
  :root {
@@ -1224,7 +1398,7 @@ Net effect: the consumer MUST own the host-adapter import in their wrapper layou
1224
1398
  </script>
1225
1399
  ```
1226
1400
 
1227
- This is the second half of the paired-unit contract from §3.2 (`<DesignTokenPanelHost>` AND the host-adapter `<script>` block — always together). If you forget it, the `<DesignTokenPanelHost>` JSON config payload still ships, but no JS reads it, so calling `window.<consoleNamespace>.showDesignPanel()` throws `ReferenceError`.
1401
+ This is the second half of the paired-unit contract from §4.1.2 (`<DesignTokenPanelHost>` AND the host-adapter `<script>` block — always together). If you forget it, the `<DesignTokenPanelHost>` JSON config payload still ships, but no JS reads it, so calling `window.<consoleNamespace>.showDesignPanel()` throws `ReferenceError`.
1228
1402
 
1229
1403
  For the regression-guard tests that pin this contract, see `package-exports.test.ts` under the package's test suite.
1230
1404
 
@@ -1236,25 +1410,25 @@ For the regression-guard tests that pin this contract, see `package-exports.test
1236
1410
 
1237
1411
  **Symptom:** on first paint after a hard reload, the page renders with the consumer's default token values for a beat before snapping to the user's saved overrides.
1238
1412
 
1239
- **Resolution:** the host adapter eagerly re-applies persisted overrides during the lazy-load gate (probes `${storagePrefix}-state-v2` and `${storagePrefix}:visible` synchronously from `localStorage`). If you still see a flash, your `<DesignTokenPanelHost>` is being rendered too late in the document (e.g. inside a deferred island) — move it to the layout's `<body>` and verify the inline `<script type="application/json" id="tokenpanel-config">` is in the initial HTML.
1413
+ **Resolution:** the host adapter eagerly re-applies persisted overrides during the lazy-load gate (it content-checks the exact `${storagePrefix}-state` / `${storagePrefix}-state-vN` family and the visibility, autoload, and enabled-feature signals synchronously from `localStorage`). If you still see a flash, your `<DesignTokenPanelHost>` is being rendered too late in the document (e.g. inside a deferred island) — move it to the layout's `<body>` and verify the inline `<script type="application/json" id="tokenpanel-config">` is in the initial HTML.
1240
1414
 
1241
1415
  ### 13.2 Auto-mount race on first reload
1242
1416
 
1243
1417
  **Symptom:** the panel does not re-open on the first reload after the user closed it, even though `${storagePrefix}-open` is set in `localStorage`.
1244
1418
 
1245
- **Resolution:** the open boolean is mirrored to `localStorage` synchronously and read at mount time so the next mount opens directly into the user's last state without a post-render toggle dispatch. If the symptom persists, confirm the storage key matches what the contract derives (§8) and that nothing else in the page is clearing the key on load.
1419
+ **Resolution:** the open boolean is mirrored to `localStorage` synchronously and read at mount time so the next mount opens directly into the user's last state without a post-render toggle dispatch. If the symptom persists, confirm the storage key matches what the contract derives (§9) and that nothing else in the page is clearing the key on load.
1246
1420
 
1247
1421
  ### 13.3 Live-apply regression test approach
1248
1422
 
1249
- **Symptom:** after a panel-package change, you want to confirm the live-apply pipeline (storage → adapter → `:root`) is unbroken end-to-end.
1423
+ **Symptom:** after a panel-package change, you want to confirm the live-apply pipeline (storage → adapter → default `:root` or `applySink`) is unbroken end-to-end.
1250
1424
 
1251
- **Resolution:** the canonical regression test is each external example repo's `apply-roundtrip.spec.ts` Playwright spec under `tests/e2e/`. It boots the example's preview build, seeds a v2 state under the example's storage prefix, hard-reloads, and asserts the adapter rehydrated and applied the override against the example's palette and semantic CSS variable names. The contract: storage prefix, palette template, semantic CSS names — change one of those and this spec fails first. See §15 for links to the five external example repos.
1425
+ **Resolution:** the canonical regression test is each external example repo's `apply-roundtrip.spec.ts` Playwright spec under `tests/e2e/`. It boots the example's preview build, seeds a compatible state envelope under the example's storage prefix, hard-reloads, and asserts the adapter rehydrated and applied the override against the example's palette and semantic CSS variable names. The contract: storage prefix, `TierItem.cssVar` names, semantic CSS names — change one of those and this spec fails first. See §15 for links to the five external example repos.
1252
1426
 
1253
1427
  ---
1254
1428
 
1255
1429
  ## 14. Migration recipe — adopting the panel into an existing consumer
1256
1430
 
1257
- This recipe walks through wiring the panel into a project that does not currently use it. If you previously consumed an internal pre-OSS snapshot of the panel where storage keys, console namespace, modal class prefix, and cluster identifiers were hardcoded literals, the same steps apply — your job is to lift those literals into a `PanelConfig` value.
1431
+ This recipe walks through wiring the panel into a project that does not currently use it. If you previously consumed an internal pre-OSS snapshot of the panel where storage keys, console namespace, modal class prefix, and token identifiers were hardcoded literals, the same steps apply — lift those literals into a `PanelConfig` value and a host-owned tab manifest.
1258
1432
 
1259
1433
  1. **Install the package.**
1260
1434
 
@@ -1264,9 +1438,9 @@ This recipe walks through wiring the panel into a project that does not currentl
1264
1438
 
1265
1439
  2. **Define your `PanelConfig` literals.**
1266
1440
 
1267
- Pick identifiers for `storagePrefix`, `consoleNamespace`, `modalClassPrefix`, `schemaId`, `exportFilenameBase`, and your palette CSS-var family (`paletteCssVarTemplate`). Pull these into a host-side config file (e.g. `src/lib/panel-config.ts`). If you are migrating from an internal snapshot fork and want to preserve users' saved state across the migration, keep the legacy values verbatim; otherwise pick fresh, neutral identifiers (e.g. `myapp-design-token-panel`).
1441
+ Pick identifiers for `storagePrefix`, `consoleNamespace`, `modalClassPrefix`, `schemaId`, and `exportFilenameBase`. Pull these into a host-side config file (e.g. `src/lib/panel-config.ts`). If you are migrating from an internal snapshot fork and want to preserve users' saved state across the migration, keep the legacy values verbatim; otherwise pick fresh, neutral identifiers (e.g. `myapp-design-token-panel`).
1268
1442
 
1269
- `schemaId` is display-only (see §5.3) — it never gates import/export, so picking any string here is safe. If you want the Import modal's copy to name the ACTUAL schema your export/import round-trips through, import `SCHEMA_V1` / `SCHEMA_V2` / `SCHEMA_V3` from the package root and set `schemaId` to one of them instead of an arbitrary string:
1443
+ `schemaId` is display-only (see §5.3) — it never gates import/export, so picking any string here is safe. If a host-facing label should name the actual schema your export/import round-trips through, import `SCHEMA_V1` / `SCHEMA_V2` / `SCHEMA_V3` from the package root and set `schemaId` to one of them instead of an arbitrary string:
1270
1444
 
1271
1445
  ```ts
1272
1446
  import { SCHEMA_V3 } from '@takazudo/zdtp';
@@ -1279,21 +1453,20 @@ This recipe walks through wiring the panel into a project that does not currentl
1279
1453
 
1280
1454
  3. **Author your token manifest in the host project.**
1281
1455
 
1282
- The package itself ships zero baked-in manifest data. Define `SPACING_TOKENS`, `FONT_TOKENS`, `SIZE_TOKENS`, `COLOR_TOKENS` (or whatever names you prefer) in your project and wire them up as `tokens.spacing`, `tokens.typography`, `tokens.size`, `tokens.color`. The host is the source of truth.
1283
-
1284
- 4. **Author your color cluster in the host project.**
1285
-
1286
- Build a `ColorClusterConfig` value with your palette, base roles, semantic table, and scheme registry. **The palette CSS-var name MUST be a string template, not a function:**
1456
+ The package itself ships zero baked-in manifest data. Define `TabConfig`
1457
+ values in your project and place them in `PanelConfig.tabs`. Every
1458
+ `TierItem.cssVar` is an explicit string, including one string per palette
1459
+ slot; there is no palette-name callback to survive the Astro JSON boundary.
1460
+ Add `preview` / `previewBase` to tiers that should render specimen or glyph
1461
+ previews.
1287
1462
 
1288
- ```ts
1289
- // wrong (function — does not survive Astro frontmatter → island handoff)
1290
- // paletteCssVar: (i) => `--myapp-p${i}`,
1291
-
1292
- // right (string template, JSON-serializable)
1293
- paletteCssVarTemplate: '--myapp-p{n}',
1294
- ```
1463
+ 4. **Author the color tab in the host project.**
1295
1464
 
1296
- Every other field on the cluster is a plain value already.
1465
+ Build a `TabConfig` with `id: 'color'`, palette and semantic `TierItem`
1466
+ entries, and the `colorExtras` structural metadata (base roles, scheme
1467
+ registry, and panel settings). If you need a secondary color cluster, add
1468
+ a companion `TabConfig` with `id: 'color-secondary'` and its own
1469
+ `colorExtras`. Keep all of this metadata plain JSON data.
1297
1470
 
1298
1471
  5. **Drop `<DesignTokenPanelHost>` into your layout.**
1299
1472
 
@@ -1310,11 +1483,11 @@ This recipe walks through wiring the panel into a project that does not currentl
1310
1483
 
1311
1484
  6. **Verify storage keys derive to the expected literals.**
1312
1485
 
1313
- Open devtools → Application → Local Storage. Confirm you see keys derived under your `storagePrefix` and that any pre-existing user state (under the legacy prefix, if you preserved it) is migrated forward through the v1 → v2 path on first load.
1486
+ Open devtools → Application → Local Storage. Confirm you see keys derived under your `storagePrefix` and that any pre-existing user state (under the legacy prefix, if you preserved it) is migrated through the v1/v2/v3 → v4 compatibility path on first load.
1314
1487
 
1315
1488
  7. **Run the live-apply e2e spec.**
1316
1489
 
1317
- Use any of the external example repos' Playwright spec at `tests/e2e/apply-roundtrip.spec.ts` (see §15 for the five repo links) as a template: seed a `${storagePrefix}-state-v2` payload, hard-reload, assert your palette and semantic CSS variables on `:root` reflect the seeded values.
1490
+ Use any of the external example repos' Playwright spec at `tests/e2e/apply-roundtrip.spec.ts` (see §15 for the five repo links) as a template: seed a compatible `${storagePrefix}-state-v4` payload, hard-reload, and assert your palette and semantic CSS variables on the default `:root` (or your configured `applySink`) reflect the seeded values.
1318
1491
 
1319
1492
  For edge cases hit during the migration, see CONTRIBUTING and the doc-site reference pages for each `PanelConfig` field.
1320
1493
 
@@ -1324,7 +1497,7 @@ For edge cases hit during the migration, see CONTRIBUTING and the doc-site refer
1324
1497
 
1325
1498
  The canonical worked examples live in five dedicated sibling repos. Each is an independent consumer app that demonstrates the panel against a different host framework. Each renders a tiny page with cards, buttons, and palette swatches whose styles reference its own demo CSS variables, and each ships a Playwright spec at `tests/e2e/apply-roundtrip.spec.ts` that asserts the live-apply pipeline.
1326
1499
 
1327
- - [`zudo-design-token-panel-example-astro`](https://github.com/Takazudo/zudo-design-token-panel-example-astro) — Astro + Preact island. Uses `storagePrefix: 'astro-example-tokens'`, `consoleNamespace: 'astro'`, `paletteCssVarTemplate: '--astro-palette-{n}'`.
1500
+ - [`zudo-design-token-panel-example-astro`](https://github.com/Takazudo/zudo-design-token-panel-example-astro) — Astro + Preact island. Uses `storagePrefix: 'astro-example-tokens'`, `consoleNamespace: 'astro'`, and explicit `TierItem.cssVar` names such as `--astro-palette-0`.
1328
1501
  - [`zudo-design-token-panel-example-vite-react`](https://github.com/Takazudo/zudo-design-token-panel-example-vite-react) — Vite + React (panel mounted as a Preact island, React tree untouched). Uses the `vr` namespace.
1329
1502
  - [`zudo-design-token-panel-example-nextjs`](https://github.com/Takazudo/zudo-design-token-panel-example-nextjs) — Next.js (App Router) + React, panel as a `'use client'` boundary. Uses the `nx` namespace.
1330
1503
  - [`zudo-design-token-panel-example-zfb`](https://github.com/Takazudo/zudo-design-token-panel-example-zfb) — [zfb](https://github.com/Takazudo/zudo-front-builder) (Preact host). Uses the `devMiddleware` plugin hook for the apply proxy.