@takazudo/zdtp 0.4.13 → 0.4.15

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 (113) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/PORTABLE-CONTRACT.md +366 -94
  3. package/README.md +218 -96
  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-CfL2VUe1.js → autoload-state-7BQYXI7Y.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 +22 -25
  24. package/dist/controls/actions-menu-popover.d.ts +2 -0
  25. package/dist/controls/role-button.d.ts +5 -2
  26. package/dist/controls/tooltip.d.ts +8 -4
  27. package/dist/element-inspect/element-inspect-context.d.ts +22 -0
  28. package/dist/element-inspect/element-inspect-orchestrator.d.ts +17 -0
  29. package/dist/element-inspect/element-inspect-overlay.d.ts +16 -0
  30. package/dist/element-inspect/element-inspect-toggle-button.d.ts +3 -0
  31. package/dist/element-inspect/element-inspect-view.d.ts +6 -0
  32. package/dist/element-inspect/find-tokens-for-element.d.ts +23 -0
  33. package/dist/element-inspect/index.d.ts +8 -0
  34. package/dist/highlight/find-elements.d.ts +14 -3
  35. package/dist/highlight/highlight-orchestrator.d.ts +7 -3
  36. package/dist/highlight/highlight-state.d.ts +3 -2
  37. package/dist/highlight/highlight-toggle-button.d.ts +3 -0
  38. package/dist/highlight/walk-css-rules.d.ts +2 -0
  39. package/dist/history/buttons.d.ts +28 -0
  40. package/dist/history/index.d.ts +3 -0
  41. package/dist/history/rail.d.ts +13 -0
  42. package/dist/history/snapshots.d.ts +57 -0
  43. package/dist/host/host-mutations.d.ts +23 -0
  44. package/dist/index-BOOmFtJh.js +12165 -0
  45. package/dist/{index-BAY1JOUl.js → index-C2DIYD0d.js} +2 -2
  46. package/dist/index.js +14 -14
  47. package/dist/load-routing-BtCE1hGI.js +547 -0
  48. package/dist/{manifest-DCReQE0k.js → manifest-DvuKi7I4.js} +11 -7
  49. package/dist/{panel-config-COHesW2B.js → panel-config-CRf-ONSV.js} +404 -308
  50. package/dist/picker/alt-click-picker.d.ts +10 -2
  51. package/dist/picker/arming-coordinator.d.ts +2 -0
  52. package/dist/picker/index.d.ts +2 -2
  53. package/dist/search/command-palette.d.ts +21 -0
  54. package/dist/search/contribution.d.ts +7 -0
  55. package/dist/search/fuzzy.d.ts +10 -0
  56. package/dist/search/index.d.ts +4 -0
  57. package/dist/search/match-bar.d.ts +14 -0
  58. package/dist/search/search-header.d.ts +7 -0
  59. package/dist/search/token-search.d.ts +36 -0
  60. package/dist/server/create-apply-handler.d.ts +33 -0
  61. package/dist/server/index.d.ts +2 -1
  62. package/dist/server/index.js +1 -1
  63. package/dist/shell/dock-mode-switch.d.ts +6 -0
  64. package/dist/shell/footer.d.ts +1 -0
  65. package/dist/shell/ghost-idle.d.ts +24 -0
  66. package/dist/shell/header.d.ts +5 -0
  67. package/dist/shell/layer-activity.d.ts +6 -0
  68. package/dist/shell/mini-pill.d.ts +21 -0
  69. package/dist/shell/regions.d.ts +26 -0
  70. package/dist/shell/shortcut-dispatcher.d.ts +17 -0
  71. package/dist/shell/tab-bar.d.ts +17 -0
  72. package/dist/shell/tab-overflow-popover.d.ts +13 -0
  73. package/dist/specimen/on-page-specimen.d.ts +19 -0
  74. package/dist/specimen/preview-glyphs.d.ts +2 -0
  75. package/dist/specimen/specimen-state.d.ts +12 -0
  76. package/dist/specimen/specimen-tab-body.d.ts +18 -0
  77. package/dist/specimen/specimen-toolbar.d.ts +10 -0
  78. package/dist/specimen/specimen-values.d.ts +9 -0
  79. package/dist/state/history.d.ts +64 -0
  80. package/dist/state/persist.d.ts +10 -14
  81. package/dist/state/transaction.d.ts +24 -0
  82. package/dist/state/tweak-state.d.ts +16 -0
  83. package/dist/styles/z-index-tokens.d.ts +2 -0
  84. package/dist/tabs/color-tab.d.ts +10 -1
  85. package/dist/tabs/flat/flat-tab.d.ts +27 -0
  86. package/dist/tabs/flat/index.d.ts +8 -0
  87. package/dist/tabs/flat/scroll-to-token-row.d.ts +3 -0
  88. package/dist/tabs/flat/tier-section.d.ts +17 -0
  89. package/dist/tabs/flat/token-controller.d.ts +23 -0
  90. package/dist/tabs/flat/token-row.d.ts +12 -0
  91. package/dist/tabs/flat/types.d.ts +25 -0
  92. package/dist/tabs/font-tab.d.ts +13 -12
  93. package/dist/tabs/generic-tab.d.ts +8 -38
  94. package/dist/tabs/palette/palette-check-view.d.ts +5 -1
  95. package/dist/tabs/palette/palette-edit-view.d.ts +9 -1
  96. package/dist/tabs/palette/palette-tab.d.ts +12 -1
  97. package/dist/tabs/size-tab.d.ts +7 -10
  98. package/dist/tabs/spacing-tab.d.ts +7 -13
  99. package/dist/testing.js +3 -3
  100. package/dist/tokens/tier-model.d.ts +6 -0
  101. package/dist/{tweak-state-BKNxtHzb.js → tweak-state-BGp_7Tfh.js} +473 -423
  102. package/dist/utils/numeric-transform.d.ts +32 -0
  103. package/dist/utils/token-diff.d.ts +25 -0
  104. package/dist/utils/token-graph.d.ts +23 -0
  105. package/dist/utils/token-index.d.ts +30 -0
  106. package/dist/zdtp.css +1 -1
  107. package/package.json +3 -1
  108. package/dist/controls/pill-slider-row.d.ts +0 -36
  109. package/dist/controls/select-row.d.ts +0 -25
  110. package/dist/controls/slider-row.d.ts +0 -29
  111. package/dist/controls/text-row.d.ts +0 -27
  112. package/dist/index-U7Ol7tgA.js +0 -7591
  113. 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
 
@@ -504,12 +533,17 @@ default instance only.
504
533
  | `storagePrefix` | `string` | Base for every derived `localStorage` key. Also the instance id. See §9. |
505
534
  | `consoleNamespace` | `string` | Global object the package installs `showDesignPanel` / `hideDesignPanel` / `toggleDesignPanel` on (e.g. `consoleNamespace: 'myapp'` → `window.myapp.showDesignPanel`). |
506
535
  | `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. |
536
+ | `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
537
  | `exportFilenameBase` | `string` | Default download filename base — exports save as `${exportFilenameBase}.json`. |
509
538
  | `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
539
  | `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
540
  | `colorPresets` | `Record<string, ColorScheme>` (optional) | Optional named scheme presets surfaced in the Color tab "Scheme..." dropdown. Defaults to `{}`. See §7.5. |
541
+ | `applyEndpoint` | `string` (optional) | Browser POST target for apply previews and confirmed writes. See §3 and [Apply pipeline reference](/docs/reference/apply-pipeline). |
542
+ | `applyRouting` | `Record<string, string>` (optional) | CSS-var prefix-family to repo-relative CSS-file routing map used by the apply endpoint. |
512
543
  | `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. |
544
+ | `domTweaker` | `DomTweakerConfig` (optional) | Enables the development-only Tailwind class editor and its Alt+click owner. See [DOM Tweaker reference](/docs/reference/dom-tweaker). |
545
+ | `dock` | `PanelDockConfig` (optional) | Controls right/bottom host reflow. Defaults to `{ reflow: 'body-margin' }`; see [configurePanel](/docs/reference/configure-panel#dock--optional-host-reflow). |
546
+ | `legacyIdRenameMap` | `Record<string, string \| null>` (optional) | Maps renamed persisted ids to new ids, or drops ids with `null`, during load. |
513
547
  | `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
548
 
515
549
  ### 5.4 `applySink` — optional write target
@@ -561,10 +595,10 @@ const handle = configurePanel({
561
595
  The Astro entry point (`<DesignTokenPanelHost>`) handles mounting for you. Internally:
562
596
 
563
597
  - 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.
598
+ - 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.
599
+ - 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
600
 
567
- For a Vite-only / non-Astro host, mount it yourself by importing the adapter module after `configurePanel(...)`. See §11.5.
601
+ For a Vite-only / non-Astro host, mount it yourself by importing the adapter module after `configurePanel(...)`. See §8.5.
568
602
 
569
603
  ### 5.6 First-open geometry
570
604
 
@@ -589,24 +623,22 @@ spec; this section is the consumer-oriented summary.
589
623
 
590
624
  ```ts
591
625
  export type TierValueKind =
592
- | { kind: 'length'; step: number; unit: string }
593
- | { kind: 'number'; step: number }
626
+ | { kind: 'length'; step: number; unit: string; units?: readonly string[] }
627
+ | { kind: 'number'; step: number; unit?: string }
594
628
  | { kind: 'select'; options: readonly string[] }
595
629
  | { kind: 'text' }
596
630
  | { kind: 'cursor' }
597
631
  | { kind: 'content' }
598
632
  | { kind: 'mask-image' }
599
- | { kind: 'color' };
633
+ | { kind: 'color'; format?: 'hex' | 'oklch' };
600
634
 
601
635
  export interface TierItem {
602
636
  /** Stable id used as the key in persisted state (e.g. `hsp-2xs`). */
603
637
  id: string;
604
- /** CSS custom property written to `:root` (e.g. `--myapp-spacing-md`). */
638
+ /** CSS custom property written to the default root or configured apply sink (e.g. `--myapp-spacing-md`). */
605
639
  cssVar: string;
606
640
  /** Display label shown in the panel row. */
607
641
  label: string;
608
- /** Optional manifest group — tab components use this for section headers. */
609
- group?: string;
610
642
  /** Default value as a CSS string (`0.125rem`, `12px`, etc.). */
611
643
  default: string;
612
644
  /** Discriminated union describing the control kind and its metadata. */
@@ -631,16 +663,29 @@ export interface TierConfig {
631
663
  * apply pipeline emits `var(--target-cssvar)` for ref-tier items.
632
664
  */
633
665
  referencesTier?: string;
666
+ /** Marks a color tier as semantic data rather than a palette tier. */
667
+ semantic?: true;
668
+ /** Allowed ramp sources for per-row semantic references on color tabs. */
669
+ referencesRamps?: readonly { tab?: string; tier: string }[];
670
+ /** Optional visual preview rendered for this tier in the panel. */
671
+ preview?: 'size' | 'line-height' | 'family' | 'weight' | 'bar' | 'radius' | 'duration';
672
+ /** CSS variable used as the base for a preview, when supplied. */
673
+ previewBase?: string;
634
674
  }
635
675
 
636
676
  export interface TabConfig {
637
677
  id: string;
638
678
  label: string;
639
679
  tiers: readonly TierConfig[];
640
- /** Tier ids hidden behind an Advanced disclosure. */
641
- advancedTiers?: readonly string[];
642
680
  /** Required on color tabs (id 'color' / 'color-secondary'). */
643
681
  colorExtras?: ColorClusterExtras;
682
+ /** Required on the reserved 'notes' tab; forbidden on other tabs. */
683
+ notesExtras?: NotesExtras;
684
+ }
685
+
686
+ export interface NotesExtras {
687
+ title: string;
688
+ html: string;
644
689
  }
645
690
  ```
646
691
 
@@ -662,7 +707,6 @@ export const spacingTab: TabConfig = {
662
707
  id: 'spacing-md',
663
708
  cssVar: '--myapp-spacing-md',
664
709
  label: 'Spacing M',
665
- group: 'hsp',
666
710
  default: '1rem',
667
711
  type: { kind: 'length', step: 0.0625, unit: 'rem' },
668
712
  },
@@ -747,8 +791,8 @@ export interface ColorScheme {
747
791
  cursor: number | string;
748
792
  selectionBg: number | string;
749
793
  selectionFg: number | string;
750
- palette: readonly string[]; // length must match the palette tier's item count
751
- shikiTheme: string;
794
+ palette: readonly string[]; // the public type requires exactly 16 entries
795
+ shikiTheme?: string;
752
796
  semantic?: Record<string, number | string>;
753
797
  }
754
798
  ```
@@ -798,9 +842,13 @@ the JSON round-trip.
798
842
 
799
843
  For each palette `TierItem` in the palette tier, the panel writes
800
844
  `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.
845
+ each declared base role, the live DOM apply path writes the role's CSS-var ←
846
+ `palette[state[roleKey]]`. The disk `buildApplyOverrides` payload
847
+ intentionally contains palette and semantic CSS variables only; base roles are
848
+ runtime wiring and are not emitted into source-file rewrites.
849
+ For each semantic `TierItem`, it resolves the mapping and writes the emitted
850
+ CSS value: `var(...)` for a palette/reference mapping, a literal string for a
851
+ literal mapping, or `light-dark(light, dark)` for a per-mode literal.
804
852
 
805
853
  Roles absent from `colorExtras.baseRoles` are not written, so a minimalist
806
854
  cluster (just `background` + `foreground`) is fine.
@@ -809,7 +857,7 @@ cluster (just `background` + `foreground`) is fine.
809
857
 
810
858
  The Color tab's "Scheme..." dropdown surfaces named `ColorScheme` entries. Two sources feed it:
811
859
 
812
- 1. **`colorCluster.colorSchemes`** — the cluster's bundled scheme registry. Always present, typically holds your default scheme(s) (`"Default"`, `"Default Light"` / `"Default Dark"`).
860
+ 1. **`colorExtras.colorSchemes`** — the color tab's bundled scheme registry. Always present, typically holds your default scheme(s) (`"Default"`, `"Default Light"` / `"Default Dark"`).
813
861
  2. **`PanelConfig.colorPresets`** — an optional, host-supplied preset map for an additional, larger preset library. Defaults to `{}` — the package itself ships zero presets.
814
862
 
815
863
  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 +877,7 @@ const myPresets: Record<string, ColorScheme> = {
829
877
  selectionBg: '#44475a',
830
878
  selectionFg: '#ffffff',
831
879
  palette: [
832
- // 16 hex strings — length must match colorCluster.paletteSize
880
+ // 16 hex strings — the ColorScheme type's fixed palette length
833
881
  '#21222c',
834
882
  '#ff5555',
835
883
  '#50fa7b',
@@ -854,7 +902,7 @@ const myPresets: Record<string, ColorScheme> = {
854
902
  };
855
903
 
856
904
  export const myPanelConfig: PanelConfig = {
857
- // ... storagePrefix, tokens, colorCluster, etc.
905
+ // ... storagePrefix, tabs (including the color tab), etc.
858
906
  colorPresets: myPresets,
859
907
  };
860
908
  ```
@@ -868,7 +916,7 @@ export const myPanelConfig: PanelConfig = {
868
916
 
869
917
  **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
918
 
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`.
919
+ **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
920
 
873
921
  > **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
922
 
@@ -903,7 +951,7 @@ Astro stringifies props at render time. **Functions, class instances, and `undef
903
951
  When the consumer site renders Astro's `<ClientRouter />`, the panel's host adapter automatically wires:
904
952
 
905
953
  - `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.
954
+ - `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
955
 
908
956
  No additional wiring needed in your layout beyond importing `<ClientRouter />` from `astro:transitions`.
909
957
 
@@ -962,10 +1010,24 @@ Behaviour notes:
962
1010
  | `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
1011
  | `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
1012
  | `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. |
1013
+ | `open` | `${storagePrefix}-open` | Mirror of the panel's `open` boolean, read synchronously at mount so user intent survives reloads. |
1014
+ | `position` | `${storagePrefix}-position` | Drag position `{ top, left }` so the panel reappears where the user left it. |
1015
+ | `size` | `${storagePrefix}-size` | Floating shell dimensions `{ width, height }` in pixels. |
1016
+ | `dock` | `${storagePrefix}-dock` | Presentation mode: `'float'`, `'right'`, `'bottom'`, or `'mini'`. |
1017
+ | `dock-size` | `${storagePrefix}-dock-size` | Right/bottom dock dimensions `{ right, bottom }`, defaulting to `{ right: 440, bottom: 340 }`. |
1018
+ | `density` | `${storagePrefix}-density` | Tab-grid density preference (`0`, `1`, or `2`). |
1019
+ | `ghost` | `${storagePrefix}-ghost` | Ghost-when-idle preference (`'1'` when enabled). |
1020
+ | `specimen` | `${storagePrefix}-specimen` | Font specimen toolbar JSON `{ text, preset, overridden, width }`; width is clamped to 240–720. |
1021
+ | `snapshot-a` | `${storagePrefix}-snapshot-a` | Persisted A snapshot `{ state, identity, savedAt, edits }`. |
1022
+ | `snapshot-b` | `${storagePrefix}-snapshot-b` | Persisted B snapshot `{ state, identity, savedAt, edits }`. |
1023
+ | `last-applied` | `${storagePrefix}-last-applied` | Flat comparison baseline; a successful apply resets it to `{}` while unconfirmed overrides remain in live state. |
1024
+ | `visible` | `${storagePrefix}:visible` | Adapter-level visibility-intent flag, owned by the lazy-load gate. |
1025
+ | `autoload` | `${storagePrefix}:autoload` | Owner-mode autoload flag: `'1'` for explicit opt-in or `'auto'` for auto-remember. |
1026
+ | `elpath-enabled` | `${storagePrefix}-elpath-enabled` | Element-path picker enabled bit. |
1027
+ | `domtweaker-enabled` | `${storagePrefix}-domtweaker-enabled` | DOM Tweaker enabled bit; meaningful only when `domTweaker` is configured. |
1028
+ | `highlight-slots` | `${storagePrefix}-highlight-slots` | Ten highlight slot colors in local storage. |
1029
+ | `highlight-outline-width` | `${storagePrefix}-highlight-outline-width` | Global highlight outline width in local storage, clamped to 1–20. |
1030
+ | `highlight-active` | `${storagePrefix}-highlight-active` | Active CSS-variable-to-slot map in session storage. |
969
1031
 
970
1032
  For example, with `storagePrefix: 'myapp-design-token-panel'`:
971
1033
 
@@ -976,8 +1038,22 @@ myapp-design-token-panel-state-v2
976
1038
  myapp-design-token-panel-state
977
1039
  myapp-design-token-panel-open
978
1040
  myapp-design-token-panel-position
1041
+ myapp-design-token-panel-size
1042
+ myapp-design-token-panel-dock
1043
+ myapp-design-token-panel-dock-size
1044
+ myapp-design-token-panel-density
1045
+ myapp-design-token-panel-ghost
1046
+ myapp-design-token-panel-specimen
1047
+ myapp-design-token-panel-snapshot-a
1048
+ myapp-design-token-panel-snapshot-b
1049
+ myapp-design-token-panel-last-applied
979
1050
  myapp-design-token-panel:visible
980
1051
  myapp-design-token-panel:autoload
1052
+ myapp-design-token-panel-elpath-enabled
1053
+ myapp-design-token-panel-domtweaker-enabled
1054
+ myapp-design-token-panel-highlight-slots
1055
+ myapp-design-token-panel-highlight-outline-width
1056
+ myapp-design-token-panel-highlight-active # sessionStorage
981
1057
  ```
982
1058
 
983
1059
  ### Note: colon vs dash — `visible` and `autoload`
@@ -1008,11 +1084,11 @@ When the host dispatches a `color-scheme-changed` event (e.g. a light/dark toggl
1008
1084
  - 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
1085
  - 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
1086
 
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`.
1087
+ 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
1088
 
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.
1089
+ **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
1090
 
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).
1091
+ **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
1092
 
1017
1093
  Two consequences worth knowing as a host integrator:
1018
1094
 
@@ -1053,7 +1129,7 @@ zdtp.toggle(); // toggle open/closed
1053
1129
  - **Available on both integration paths:**
1054
1130
  - Non-Astro hosts get it as soon as `@takazudo/zdtp`'s package-root module has loaded (it installs the alias at module init).
1055
1131
  - Astro hosts get it as soon as the host-adapter `<script>` has run — **before** the panel bundle itself has loaded. The first `zdtp.*` call lazy-imports the bundle, exactly like `window[consoleNamespace].*`.
1056
- - **Never clobbers a host-defined `window.zdtp`.** If your page already has its own `window.zdtp` for something unrelated, the package leaves it alone and logs a `console.warn` instead of overwriting it — including the edge case of choosing `consoleNamespace: 'zdtp'` yourself.
1132
+ - **Never clobbers a host-defined `window.zdtp`.** If the existing value exposes callable `show`, `hide`, and `toggle` methods, the package treats it as an intentional host alias and stays silent — enabling hosts to pre-claim the alias before lazily loading the panel bundle. Other existing values are still left untouched and produce a `console.warn`, including the edge case of choosing `consoleNamespace: 'zdtp'` yourself.
1057
1133
  - **Auto-remember applies too** — `zdtp.show()` arms the `:autoload` flag with `'auto'` provenance exactly like `showDesignPanel()` (§10.1's Auto-remember footgun note applies here as well).
1058
1134
 
1059
1135
  See `PORTABLE-CONTRACT.md` §6.5 for the full install-site and no-clobber/no-double-install contract.
@@ -1074,7 +1150,7 @@ There is no default `consoleNamespace` exposed to consumers — the field is req
1074
1150
 
1075
1151
  ### 10.1 Owner-autoload — loading the panel only for you
1076
1152
 
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.
1153
+ **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
1154
 
1079
1155
  #### API surface
1080
1156
 
@@ -1083,14 +1159,14 @@ Both the Astro host-adapter (`window[consoleNamespace].*`) and the package-root
1083
1159
  | Call | Effect |
1084
1160
  |------|--------|
1085
1161
  | `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. |
1162
+ | `window.myapp.disableAutoload()` | Removes `:autoload`, writes `'0'` to `:visible` and `-elpath-enabled`, removes the open-state key, and unmounts the panel. |
1087
1163
  | `enableAutoload()` (package-root export) | Same as the console form — for non-Astro hosts. |
1088
1164
  | `disableAutoload()` (package-root export) | Same as the console form — for non-Astro hosts. |
1089
1165
  | `shouldAutoload()` (package-root export) | Returns `true` iff the flag is currently set. |
1090
1166
 
1091
1167
  #### The `${storagePrefix}:autoload` flag
1092
1168
 
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.
1169
+ 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
1170
 
1095
1171
  **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
1172
 
@@ -1098,11 +1174,11 @@ Stored in `localStorage` as `'1'` (explicit — set by `enableAutoload()`) or `'
1098
1174
 
1099
1175
  #### Auto-remember on open
1100
1176
 
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.
1177
+ 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
1178
 
1103
1179
  #### Element-path coupling
1104
1180
 
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.
1181
+ `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
1182
 
1107
1183
  #### Auto-remember footgun
1108
1184
 
@@ -1116,48 +1192,95 @@ Because **any open trigger sets `:autoload`** (to `'auto'`), a visible "open pan
1116
1192
 
1117
1193
  ---
1118
1194
 
1195
+ ### 10.2 Shared Alt+click picker ownership
1196
+
1197
+ Element path, DOM Tweaker, and element inspect use one per-window coordinator
1198
+ for the Alt+click gesture. Enabling or arming one feature revokes the previous
1199
+ owner; the three features do not run simultaneously. Panel surfaces and the
1200
+ on-page specimen are excluded from picker hits.
1201
+
1202
+ | Feature | Activation | Result |
1203
+ | --- | --- | --- |
1204
+ | Element path | Owner autoload or its panel toggle, then `Alt+click` a host element | Copies the annotated selector/path block. |
1205
+ | DOM Tweaker | Enable the configured DOM Tweaker, then `Alt+click` a host element | Opens the Tailwind class editor and live utility preview. |
1206
+ | 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. |
1207
+
1208
+ The most recently armed feature owns the gesture and receives subsequent page
1209
+ clicks. Owner autoload's element-path arm therefore does not block a later DOM
1210
+ Tweaker or element-inspect arm.
1211
+
1212
+ ---
1213
+
1119
1214
  ## 11. Tailwind not required
1120
1215
 
1121
- The panel ships its own bundled CSS scoped under a panel-private namespace:
1216
+ The panel ships its own bundled CSS scoped under a panel-private namespace. The
1217
+ chrome palette is a self-contained OKLCH ramp; it does not read host
1218
+ `--color-*` or `--font-mono` variables. A host can opt into a different chrome
1219
+ theme by assigning the same `--tokentweak-*` variables on a listed scope.
1122
1220
 
1123
1221
  ```css
1124
1222
  :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 */
1223
+ /* base-0 is the darkest ground; stops ascend toward the foreground. */
1224
+ --tokentweak-palette-base-0: oklch(0.18 0 0);
1225
+ --tokentweak-palette-base-1: oklch(0.25 0 0);
1226
+ --tokentweak-palette-base-2: oklch(0.34 0 0);
1227
+ --tokentweak-palette-base-3: oklch(0.536 0 0);
1228
+ --tokentweak-palette-base-4: oklch(0.66 0 0);
1229
+ --tokentweak-palette-base-5: oklch(0.8 0 0);
1230
+ --tokentweak-palette-base-6: oklch(0.91 0 0);
1231
+ --tokentweak-color-fg: var(--tokentweak-palette-base-5);
1232
+ --tokentweak-color-bg: var(--tokentweak-palette-base-0);
1233
+ --tokentweak-color-muted: var(--tokentweak-palette-base-4);
1234
+ --tokentweak-color-border: var(--tokentweak-palette-base-3);
1235
+ --tokentweak-color-surface: var(--tokentweak-palette-base-1);
1236
+ --tokentweak-color-accent: #d69a66;
1237
+ --tokentweak-color-accent-bar: #efb477;
1238
+ --tokentweak-color-accent-hover: #a7c0e3;
1239
+ --tokentweak-color-code-bg: var(--tokentweak-palette-base-2);
1240
+ --tokentweak-color-code-fg: var(--tokentweak-palette-base-6);
1241
+ --tokentweak-color-success: #93bb77;
1242
+ --tokentweak-color-danger: #da6871;
1243
+ --tokentweak-color-warning: #dfbb77;
1244
+ --tokentweak-font-mono: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
1245
+ /* pad-*, gap-*, text-*, radius, and z-* tokens are also declared here. */
1135
1246
  }
1136
1247
  ```
1137
1248
 
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-*`.
1249
+ Hosts may override a `--tokentweak-palette-base-*` stop to move all semantic
1250
+ roles that alias it, or continue assigning a `--tokentweak-color-*` role
1251
+ directly. The direct semantic assignment still wins. Borders now use
1252
+ `--tokentweak-color-border`; a host that previously assigned
1253
+ `--tokentweak-color-muted` to recolor both secondary text and 1px dividers must
1254
+ also assign the border role.
1255
+
1256
+ - **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
1257
  - **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
1258
  - **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
1259
 
1142
- ### 11.1 Two-layer override model for chrome colors
1260
+ ### 11.1 Chrome scopes and host overrides
1143
1261
 
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:
1262
+ The declarations cover `.tokenpanel-shell`, `.tokenpanel-mini-pill`,
1263
+ `[data-design-token-panel-modal]`, the highlight-settings and chain popovers,
1264
+ the color picker, tooltip, element-path label/toast, and element-inspect box,
1265
+ label, and view. `:where()` keeps their specificity at zero, so a host can
1266
+ override a token with a single-class rule on the relevant surface. The host
1267
+ theme variables are never read by the panel chrome.
1145
1268
 
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). |
1269
+ The CSS variables the panel **writes to** (the `cssVar` field on each
1270
+ `TierItem`, palette/base-role names, and semantic CSS names) are entirely
1271
+ consumer-controlled. The package only writes those variables through the
1272
+ default `document.documentElement` target or a configured `applySink`.
1151
1273
 
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.
1153
-
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`.
1274
+ Dock integration publishes `--zdtp-dock-inset-right` and
1275
+ `--zdtp-dock-inset-bottom` on the host root while a claim is active. The
1276
+ page-level specimen uses the `.tokenpanel-on-page-specimen` class and
1277
+ `[data-zdtp-specimen]` marker; it is host-page content, not panel chrome.
1155
1278
 
1156
1279
  ### 11.2 Apply-to-disk for a Tailwind v4 `@theme` block
1157
1280
 
1158
1281
  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
1282
 
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):
1283
+ 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
1284
 
1162
1285
  ```css
1163
1286
  :root {
@@ -1224,7 +1347,7 @@ Net effect: the consumer MUST own the host-adapter import in their wrapper layou
1224
1347
  </script>
1225
1348
  ```
1226
1349
 
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`.
1350
+ 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
1351
 
1229
1352
  For the regression-guard tests that pin this contract, see `package-exports.test.ts` under the package's test suite.
1230
1353
 
@@ -1236,25 +1359,25 @@ For the regression-guard tests that pin this contract, see `package-exports.test
1236
1359
 
1237
1360
  **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
1361
 
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.
1362
+ **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
1363
 
1241
1364
  ### 13.2 Auto-mount race on first reload
1242
1365
 
1243
1366
  **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
1367
 
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.
1368
+ **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
1369
 
1247
1370
  ### 13.3 Live-apply regression test approach
1248
1371
 
1249
- **Symptom:** after a panel-package change, you want to confirm the live-apply pipeline (storage → adapter → `:root`) is unbroken end-to-end.
1372
+ **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
1373
 
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.
1374
+ **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
1375
 
1253
1376
  ---
1254
1377
 
1255
1378
  ## 14. Migration recipe — adopting the panel into an existing consumer
1256
1379
 
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.
1380
+ 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
1381
 
1259
1382
  1. **Install the package.**
1260
1383
 
@@ -1264,9 +1387,9 @@ This recipe walks through wiring the panel into a project that does not currentl
1264
1387
 
1265
1388
  2. **Define your `PanelConfig` literals.**
1266
1389
 
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`).
1390
+ 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
1391
 
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:
1392
+ `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
1393
 
1271
1394
  ```ts
1272
1395
  import { SCHEMA_V3 } from '@takazudo/zdtp';
@@ -1279,21 +1402,20 @@ This recipe walks through wiring the panel into a project that does not currentl
1279
1402
 
1280
1403
  3. **Author your token manifest in the host project.**
1281
1404
 
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.**
1405
+ The package itself ships zero baked-in manifest data. Define `TabConfig`
1406
+ values in your project and place them in `PanelConfig.tabs`. Every
1407
+ `TierItem.cssVar` is an explicit string, including one string per palette
1408
+ slot; there is no palette-name callback to survive the Astro JSON boundary.
1409
+ Add `preview` / `previewBase` to tiers that should render specimen or glyph
1410
+ previews.
1285
1411
 
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:**
1287
-
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
- ```
1412
+ 4. **Author the color tab in the host project.**
1295
1413
 
1296
- Every other field on the cluster is a plain value already.
1414
+ Build a `TabConfig` with `id: 'color'`, palette and semantic `TierItem`
1415
+ entries, and the `colorExtras` structural metadata (base roles, scheme
1416
+ registry, and panel settings). If you need a secondary color cluster, add
1417
+ a companion `TabConfig` with `id: 'color-secondary'` and its own
1418
+ `colorExtras`. Keep all of this metadata plain JSON data.
1297
1419
 
1298
1420
  5. **Drop `<DesignTokenPanelHost>` into your layout.**
1299
1421
 
@@ -1310,11 +1432,11 @@ This recipe walks through wiring the panel into a project that does not currentl
1310
1432
 
1311
1433
  6. **Verify storage keys derive to the expected literals.**
1312
1434
 
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.
1435
+ 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
1436
 
1315
1437
  7. **Run the live-apply e2e spec.**
1316
1438
 
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.
1439
+ 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
1440
 
1319
1441
  For edge cases hit during the migration, see CONTRIBUTING and the doc-site reference pages for each `PanelConfig` field.
1320
1442
 
@@ -1324,7 +1446,7 @@ For edge cases hit during the migration, see CONTRIBUTING and the doc-site refer
1324
1446
 
1325
1447
  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
1448
 
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}'`.
1449
+ - [`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
1450
  - [`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
1451
  - [`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
1452
  - [`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.