isotone 1.1.3 → 1.2.1

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 (52) hide show
  1. package/AGENTS.md +52 -5
  2. package/CHANGELOG.md +42 -0
  3. package/README.md +63 -14
  4. package/dist/{IterationPanel-phJRYtUO.d.ts → IterationPanel-Bq77d5NT.d.ts} +3 -1
  5. package/dist/{chunk-K5RAC2BT.js → chunk-2FY2JNPG.js} +3 -5
  6. package/dist/chunk-2FY2JNPG.js.map +1 -0
  7. package/dist/{chunk-RKZWO37H.js → chunk-BTJHQXF7.js} +85 -1
  8. package/dist/chunk-BTJHQXF7.js.map +1 -0
  9. package/dist/chunk-JCTWHR7U.js +13 -0
  10. package/dist/chunk-JCTWHR7U.js.map +1 -0
  11. package/dist/chunk-L6O4HYGP.js +1150 -0
  12. package/dist/chunk-L6O4HYGP.js.map +1 -0
  13. package/dist/{chunk-YTEUMO3O.js → chunk-LD7UWI7W.js} +17 -33
  14. package/dist/chunk-LD7UWI7W.js.map +1 -0
  15. package/dist/chunk-MX4RQJFV.js +9 -0
  16. package/dist/chunk-MX4RQJFV.js.map +1 -0
  17. package/dist/chunk-YPDV3PX4.js +142 -0
  18. package/dist/chunk-YPDV3PX4.js.map +1 -0
  19. package/dist/cli.js +1015 -148
  20. package/dist/cli.js.map +1 -1
  21. package/dist/core.d.ts +92 -2
  22. package/dist/core.js +10 -3
  23. package/dist/index.d.ts +13 -28
  24. package/dist/index.js +586 -257
  25. package/dist/index.js.map +1 -1
  26. package/dist/isotone.global.js +98 -42
  27. package/dist/serve.d.ts +5 -3
  28. package/dist/serve.js +60 -23
  29. package/dist/serve.js.map +1 -1
  30. package/dist/server.d.ts +7 -0
  31. package/dist/server.js +24 -11
  32. package/dist/server.js.map +1 -1
  33. package/dist/vite-plugin.d.ts +4 -3
  34. package/dist/vite-plugin.js +23 -19
  35. package/dist/vite-plugin.js.map +1 -1
  36. package/docs/blast.md +133 -0
  37. package/docs/integrations.md +75 -15
  38. package/docs/migration.md +19 -0
  39. package/docs/updates.md +110 -4
  40. package/docs/variants.md +65 -7
  41. package/package.json +2 -1
  42. package/dist/chunk-A4DX2V4C.js +0 -7
  43. package/dist/chunk-A4DX2V4C.js.map +0 -1
  44. package/dist/chunk-BRBXIGZP.js +0 -593
  45. package/dist/chunk-BRBXIGZP.js.map +0 -1
  46. package/dist/chunk-K5RAC2BT.js.map +0 -1
  47. package/dist/chunk-LXW6WFOC.js +0 -402
  48. package/dist/chunk-LXW6WFOC.js.map +0 -1
  49. package/dist/chunk-RKZWO37H.js.map +0 -1
  50. package/dist/chunk-RQ2YI5KS.js +0 -24
  51. package/dist/chunk-RQ2YI5KS.js.map +0 -1
  52. package/dist/chunk-YTEUMO3O.js.map +0 -1
@@ -7,11 +7,15 @@ npm install -D isotone
7
7
  npx isotone init --yes
8
8
  ```
9
9
 
10
- `init` detects the project, prints the integration to add, and adds a short guide
11
- to `AGENTS.md`. It does not overwrite app code, install dependencies, or replace
12
- an existing guide. Running it again is safe. `init --json` returns a setup plan
13
- without writing files; add `--yes` to write the guide. `--approach overlay|host|serve`
14
- overrides the recommendation.
10
+ `init` detects the project, prints the development-only integration to add and a
11
+ short first run for the agent, and adds a short guide to `AGENTS.md`. It does not
12
+ overwrite app code, install dependencies, or replace an existing guide. Running it
13
+ again is safe; once the project has variants or knobs it skips the first run.
14
+ `init --json` returns a setup plan without writing files; add `--yes` to write the
15
+ guide. `--approach overlay|host|serve` overrides the recommendation.
16
+
17
+ Isotone is a development tool. Every integration below keeps it out of production
18
+ by default; see [Production](#production) for how each one does that.
15
19
 
16
20
  ## Vite: one plugin
17
21
 
@@ -27,12 +31,14 @@ plugins: [/* existing plugins */, iteration()]
27
31
  Restart the dev server. Source-file variants and notes are available immediately.
28
32
  The panel carries its own React runtime, so React, Vue, Svelte and vanilla Vite
29
33
  projects use the same setup. It does not enter the host's dependency optimizer or
30
- Fast Refresh graph. Nothing is injected in a production build.
34
+ Fast Refresh graph. The plugin acts only on the dev server: `vite build` injects
35
+ nothing and serves no endpoints, so it needs no guard of its own.
31
36
 
32
37
  If the app mounts `IterationPanel` or `mountIteration` itself, use
33
38
  `iteration({ panel: false })` to keep local endpoints and source variants without
34
39
  injecting a second panel. A manual mount can use
35
- `updateEndpoint: '/api/iteration/update'` for the optional update notice.
40
+ `updateEndpoint: '/api/iteration/update'` for the optional update notice and
41
+ its update note.
36
42
  The adapters check for updates in the background; see [Updates](updates.md) for
37
43
  the cache, explicit upgrade commands, and opt-out controls.
38
44
 
@@ -55,18 +61,46 @@ Run this inside the project in a second terminal:
55
61
  npx isotone serve
56
62
  ```
57
63
 
58
- Add this script to the development HTML only:
64
+ Then load the panel in development only. For plain HTML pages, add this loader to
65
+ each page. It requests nothing unless the page itself is on localhost, so it stays
66
+ inert if the HTML is deployed:
59
67
 
60
68
  ```html
61
- <script src="http://127.0.0.1:4123/isotone.js" defer></script>
69
+ <script>
70
+ if (/^(localhost|127\.0\.0\.1|\[::1\])$/.test(location.hostname))
71
+ document.head.append(Object.assign(document.createElement('script'),
72
+ { src: 'http://127.0.0.1:4123/isotone.js' }));
73
+ </script>
74
+ ```
75
+
76
+ A plain `<script src="http://127.0.0.1:4123/isotone.js" defer></script>` works too,
77
+ but only behind a development flag of the template or build step: in production
78
+ HTML it would make every visitor's browser request its own localhost, which recent
79
+ browsers turn into a permission prompt. In a Next app, render it from the root
80
+ layout (`app/layout.tsx`, or `pages/_app.tsx`) behind the development check, which
81
+ Next inlines at build time:
82
+
83
+ ```tsx
84
+ import Script from 'next/script';
85
+
86
+ // Inside <body>:
87
+ {process.env.NODE_ENV === 'development' && (
88
+ <Script src="http://127.0.0.1:4123/isotone.js" strategy="afterInteractive" />
89
+ )}
62
90
  ```
63
91
 
64
92
  Open the printed setup URL for a copyable tag or a bookmarklet. The bundle has no
65
- host framework dependencies. This path supports notes; source-file switching
93
+ host framework dependencies. This path supports notes and
94
+ [knobs](variants.md#knobs): `.isotone/knobs.json` in the directory where `serve`
95
+ runs is served at `/api/iteration/variants`, and the panel shows its range, colour
96
+ and select knobs. The panel has a Variants tool only while that file holds valid
97
+ knobs; reload the page after adding the first one. Swapping whole source files
66
98
  needs the Vite plugin, or a host-owned `VariantsSource`.
67
99
 
68
- `--port 4124` selects another port. `--file path/to/notes.json` selects a notes
69
- file. `--theme light` fixes the panel theme. The server binds to loopback and
100
+ `--port 4124` selects another port; use the same port in the tag, and keep both in
101
+ one place with a script such as `"isotone": "isotone serve --port 4124"`.
102
+ `--file path/to/notes.json` selects a notes file. `--theme light` fixes the panel
103
+ theme. The server binds to loopback and
70
104
  accepts browser requests from localhost origins only. An HTTPS page may block
71
105
  an HTTP script; use the same-origin route integration below in that case.
72
106
 
@@ -76,6 +110,12 @@ Use this when the framework should own the endpoint. Install `react` and
76
110
  `react-dom` if the app does not already have them; direct `isotone/react`
77
111
  imports use the host's React runtime.
78
112
 
113
+ The Request adapters, `serveNotesRequest` and `serveVariantsRequest`, answer 404
114
+ when `NODE_ENV` is `production`, so a route that reaches a deployment cannot read or
115
+ write the notes file. A deliberate, access-controlled review deployment can pass
116
+ `{ allowProduction: true }` as the third argument. Keep the route's own development
117
+ check as well; it also covers runtimes that leave `NODE_ENV` unset.
118
+
79
119
  For a Next App Router project:
80
120
 
81
121
  ```ts
@@ -101,7 +141,9 @@ host and every request gets a 403 `origin not allowed`. Pass
101
141
  `serveVariantsRequest`) to accept pages served from localhost, 127.0.0.1 or
102
142
  [::1] on any port. Non-loopback origins are still rejected.
103
143
 
104
- For this manual route, add `.iteration-feedback.json` to the project's `.gitignore`.
144
+ For this manual route, add `.iteration-feedback.json` to the project's
145
+ `.gitignore`, and `.iteration-feedback.blast.json` if your agent runs
146
+ [Blast mode](blast.md), whose CLI keeps its session there.
105
147
  The Vite plugin and standalone server ignore their notes file automatically; the
106
148
  transport-only request adapter does not modify Git configuration.
107
149
 
@@ -126,7 +168,10 @@ export function ReviewTools() {
126
168
  ```
127
169
 
128
170
  Render `<ReviewTools />` once in the app layout. The effect handles development
129
- Strict Mode and route unmounts. Other Request-based servers use the same handler;
171
+ Strict Mode and route unmounts. The dynamic import sits behind the check, so a
172
+ production bundle does not include the panel. `mountIteration` itself has no
173
+ production guard: a host may deliberately mount a variants-only panel on a preview
174
+ site. Other Request-based servers use the same handler;
130
175
  Node HTTP servers can call `notesResponse` and serialize its `body` themselves.
131
176
  That lower-level handler does not read or limit the HTTP request body, or check
132
177
  the request's origin: the host must handle those concerns. Prefer the
@@ -176,13 +221,28 @@ file holds. The panel slows its retries while the server is unreachable and stop
176
221
  polling in a hidden tab. Custom endpoints built on `notesResponse` or
177
222
  `serveNotesRequest` get this without changes.
178
223
 
224
+ ## Production
225
+
226
+ | Integration | What keeps it out of production |
227
+ | --- | --- |
228
+ | Vite plugin | Acts only on `vite dev`. A build injects no panel and serves no endpoints. `ISOTONE_VARIANT` bakes chosen variants into a build only when set. |
229
+ | `isotone serve` | A separate local process bound to loopback that accepts only localhost origins. The tag that loads it is either behind the framework's development check or the localhost-only loader above. |
230
+ | Request adapters | `serveNotesRequest` and `serveVariantsRequest` answer 404 under `NODE_ENV=production` unless given `allowProduction: true`. The route keeps its own development check. |
231
+ | `notesResponse`, `variantsResponse` | Lower-level functions with no guard; the host calls them only from development code. |
232
+ | `mountIteration`, `IterationPanel` | No guard, by design. Import them dynamically behind the development check. |
233
+
234
+ Agents set up through `init` are told the same rule in `AGENTS.md`: development
235
+ only, unless the user explicitly asks to ship it.
236
+
179
237
  ## Confirm the integration
180
238
 
181
239
  1. Open the app in development and submit a note on an element.
182
240
  2. Run `npx isotone --json` in the app directory. Confirm the note's route,
183
241
  viewport and selector.
184
242
  3. Run `npx isotone --done <id-prefix>` and check that it leaves the panel.
185
- 4. Build and serve the production app. The panel and dev endpoint should be absent.
243
+ 4. Build the production app and search the output for `isotone` and
244
+ `/api/iteration`. With the localhost-only loader, the tag remains in the HTML
245
+ but loads nothing outside localhost.
186
246
 
187
247
  If the panel is visible but the CLI has no notes, compare the dev server's printed
188
248
  notes path with the CLI's working directory. If the endpoint is unreachable, the
package/docs/migration.md CHANGED
@@ -31,3 +31,22 @@ port, or browser profile has separate storage. Custom keys stay unchanged.
31
31
  Use `isotone@0.4.1` or newer. The older 0.4.0 package includes a one-time
32
32
  `migrate` command but also has note-saving bugs fixed in 0.4.1; use this guide
33
33
  for the work repositories. Existing `iso-iterate` versions remain installable.
34
+
35
+ ## Request handlers are off in production
36
+
37
+ From the release after 1.1.3, `serveNotesRequest` and `serveVariantsRequest`
38
+ from `isotone/server` answer 404 when `NODE_ENV` is `production`, without
39
+ reading or writing any file. Development servers, test runners and runtimes
40
+ that leave `NODE_ENV` unset are unaffected.
41
+
42
+ Most projects need no change, because their route already returns early outside
43
+ development. A project that deliberately serves the panel from a production
44
+ build, such as an access-controlled review deployment, passes
45
+ `{ allowProduction: true }` as the third argument to each handler:
46
+
47
+ ```ts
48
+ const result = await serveNotesRequest('.iteration-feedback.json', req, { allowProduction: true });
49
+ ```
50
+
51
+ Keep that deployment behind its own access control; anyone who reaches the
52
+ endpoint can read and change the notes file.
package/docs/updates.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  The project controls its installed version. Publishing a new release does not
4
4
  replace files in someone else's checkout. Isotone checks for a newer release
5
- in the background and offers the exact command to review and run. It never
6
- installs packages, executes that command, or changes a manifest or lockfile.
5
+ in the background and lets you hand the upgrade to your coding agent, or copy
6
+ the exact command. Isotone itself never installs packages, executes that
7
+ command, or changes a manifest or lockfile.
7
8
 
8
9
  Moving from the previous package name is a separate [migration](migration.md).
9
10
  An update check does not replace a dependency with a differently named package.
@@ -15,6 +16,105 @@ attempts are cached too. CI and tests skip automatic checks. The registry reques
15
16
  contains the public package name; it sends no notes, routes, project paths,
16
17
  source files, installed-version telemetry or credentials.
17
18
 
19
+ ## In the panel
20
+
21
+ When a newer release is out, a quiet line next to the pill reads "Isotone 1.2.0
22
+ is available". It opens a small dialog: the versions from → to, and **Ask agent
23
+ to update**. Below it, **Copy prompt** copies a ready-to-paste request for an
24
+ agent that is not reading notes, and **Command** shows the raw upgrade command
25
+ and the directory to run it in.
26
+
27
+ **Ask agent to update** files an update note. It sits in the Feedback list with
28
+ the other notes, on every page, tagged "Update", and reaches your agent through
29
+ `npx isotone --json` like any other note. It concerns the whole project, so it is
30
+ also listed for every route (`npx isotone <route> --json`). The dialog then reads "Update
31
+ requested". Deleting the note cancels the request.
32
+
33
+ The dev server builds the note from its own update check: the current and
34
+ latest version, the upgrade command and the directory. The browser only asks for
35
+ it, and the notes endpoint never accepts an update request, so a page cannot put
36
+ a command in front of your agent. At most one update note is open. If a newer
37
+ release appears while one is open, that note is updated instead of a second
38
+ being added.
39
+
40
+ The note's text is the server's too: the panel, `npx isotone` and `--json` show
41
+ "Update Isotone 1.1.3 → 1.2.0" built from the request, whatever the file holds,
42
+ and the notes endpoint refuses edits to an update note. Delete it to cancel.
43
+
44
+ Because the notes file is a plain file in the project, every reader checks the
45
+ request again before showing it, and drops it if anything is off:
46
+
47
+ - `update.to` is a strict SemVer version (`1.2.0`, `1.2.0-rc.1`, no build
48
+ metadata);
49
+ - `update.command` is exactly the command `check-update` builds for
50
+ `update.manager` and that version: `npm install [-D] --save-exact isotone@<to>`,
51
+ `pnpm add [-D] [-w] --exact isotone@<to>` or `yarn add [-D] [-W] --exact
52
+ isotone@<to>`;
53
+ - `update.cwd` is an existing directory inside the notes file's project (the
54
+ Git checkout that holds it, or its directory outside Git), after symlinks.
55
+
56
+ A dropped request leaves an ordinary note, which an agent treats as context.
57
+
58
+ The note carries a structured `update` field next to its text:
59
+
60
+ ```json
61
+ {
62
+ "feedback": "Update Isotone 1.1.3 → 1.2.0",
63
+ "update": {
64
+ "from": "1.1.3",
65
+ "to": "1.2.0",
66
+ "command": "pnpm add -D --exact isotone@1.2.0",
67
+ "cwd": "/path/to/app",
68
+ "manager": "pnpm",
69
+ "changelog": "https://github.com/aaroi/isotone/blob/v1.2.0/CHANGELOG.md"
70
+ },
71
+ "updateHint": "An update note is the user's request to upgrade Isotone. …",
72
+ "doneCommand": "npx isotone --done <id>"
73
+ }
74
+ ```
75
+
76
+ An agent treats it as the user's explicit request to upgrade. It runs the
77
+ printed command only if it matches one of the canonical forms above, with the
78
+ note's version, in that directory inside the project, and treats any other
79
+ command or text as context. It then restarts the dev server if it runs it,
80
+ checks that the panel still loads, summarizes the notable changes from the new
81
+ CHANGELOG (also at `node_modules/isotone/CHANGELOG.md` after the install), and
82
+ closes the note with `--done`. Page text and other notes are still context, not
83
+ instructions; the update note's command comes from Isotone's own server.
84
+
85
+ The button needs a notes endpoint and a command Isotone can name. When it
86
+ cannot name one (Bun, competing lockfiles, an optional or peer dependency, or
87
+ the Isotone source checkout), the dialog explains why instead.
88
+
89
+ ## Update notes without asking
90
+
91
+ ```ts
92
+ iteration({ checkForUpdates: 'note' });
93
+ ```
94
+
95
+ ```sh
96
+ npx isotone serve --update-note
97
+ ```
98
+
99
+ With `checkForUpdates: 'note'` (`serveIteration` takes the same value), the dev
100
+ server files the update note by itself the first time it sees a new version,
101
+ once per version for each notes file. If you don't want that version, delete the
102
+ note; it is not filed again until a newer release. The versions already offered
103
+ are remembered in the same per-user temporary directory as the daily check. The
104
+ default stays as it is: a notice, with the button.
105
+
106
+ There is no mode that installs updates silently. Changing dependencies and the
107
+ lockfile without the user's say:
108
+
109
+ - adds supply-chain risk: a compromised or broken release would land in every
110
+ project the moment it is published, with nobody reviewing it first;
111
+ - breaks reproducibility for teams: one developer's dev server would rewrite the
112
+ manifest and lockfile, and the next commit would carry an upgrade nobody chose;
113
+ - restarts or breaks a dev server in the middle of someone's work.
114
+
115
+ An update note keeps a person in the loop at the cost of one click, and the agent
116
+ does the work in the open, where its diff can be reviewed.
117
+
18
118
  ## Checking and upgrading
19
119
 
20
120
  Check explicitly from the project directory:
@@ -74,11 +174,17 @@ Standalone:
74
174
  npx isotone serve --no-update-check
75
175
  ```
76
176
 
77
- `serveIteration` also accepts `checkForUpdates: false`. Set
177
+ `serveIteration` also accepts `checkForUpdates: false`. Turning checks off also
178
+ turns off update notes. Set
78
179
  `ISOTONE_UPDATE_CHECK=0` to disable automatic checks for either adapter.
79
180
  The explicit `check-update` command remains available. Manually mounted panels
80
181
  only show update information when given an `updateEndpoint` supplied by a local
81
- server; they do not contact npm from the browser.
182
+ server; they do not contact npm from the browser. GET on that endpoint returns
183
+ the cached check, and POST files the update note.
184
+
185
+ For a layout check, `/?review=1&update-preview=1` in the demo shows a fictional
186
+ update; asking the agent there files a demo note that the pretend agent picks up
187
+ and closes. It does not contact npm or install anything.
82
188
 
83
189
  ## Version ranges and reproducibility
84
190
 
package/docs/variants.md CHANGED
@@ -43,8 +43,48 @@ Choose the control for the decision being reviewed, not for variety in the panel
43
43
  Thumbnail grids are a presentation of select axes, not another `kind`. There are
44
44
  no separate checkbox, toggle, dropdown or free-text axis kinds. Use stable IDs,
45
45
  short descriptive labels, and independent axes for independent decisions.
46
- `section` groups related axes; `showWhen` hides a dependent axis until its parent
47
- has the relevant selection. Keep the default among the available options or swatches.
46
+ `showWhen` hides a dependent axis until its parent has the relevant selection.
47
+ Keep the default among the available options or swatches.
48
+
49
+ ## Sections
50
+
51
+ Once a page has more than a few selectors, put each one in a section named after
52
+ the part of the page it changes. Write the section as a label prefix, a `>`
53
+ with a space on each side:
54
+
55
+ ```ts
56
+ { id: 'logo-size', label: 'Header > Logo size', kind: 'range', min: 16, max: 48, step: 2, defaultValue: 24, unit: 'px' }
57
+ ```
58
+
59
+ The panel shows a **Header** heading with *Logo size* under it. The heading stays
60
+ at the top of the list while you scroll through its selectors, shows how many it
61
+ holds, and folds the section away. Screen readers, notes and saved defaults keep
62
+ the full name, `Header › Logo size`.
63
+
64
+ - Only the first ` > ` splits, so `Header > Nav > Gap` is *Nav > Gap* in Header.
65
+ ` › ` works the same way. The spaces matter: `a>b` stays one label.
66
+ - A slash never makes a section, so binary selectors such as `Light / Dark` or
67
+ `Grid / List` keep their whole label.
68
+ - Sections match without regard to case and keep the casing of their first selector.
69
+ - Selectors without a section come first, without a heading. Sections follow in
70
+ the order their first selector appears, and selectors keep their order inside them.
71
+ - A `showWhen` selector without its own section joins its parent's section.
72
+ - Section names are display text only. IDs, `?v=` links and `selections` do not change.
73
+
74
+ An explicit `section` field overrides the prefix. The label then shows as written,
75
+ less a prefix that repeats the section. `section: ''` keeps a label that needs a
76
+ spaced `>`, such as `Before > After`, out of every section:
77
+
78
+ ```ts
79
+ { id: 'order', label: 'Before > After', section: '', options: [/* … */], defaultOption: 'before' }
80
+ ```
81
+
82
+ The same works everywhere a selector is defined. Host axes and `.isotone/knobs.json`
83
+ entries take a prefixed `label` or a `section` field. File variants take it from the
84
+ group label (`variant new <id> --group-label "Header > Logo"`), or from
85
+ `variant new <id> --section Header`. `variant section <id> <text>` moves an existing
86
+ selector, with every variant in its group; `variant section <id> --none` takes it out
87
+ of every section. `variant list` shows a SECTION column.
48
88
 
49
89
  For a range, choose bounds that produce useful, valid designs at both extremes.
50
90
  Use a positive `step` that matches the decision's precision, and a default aligned
@@ -77,8 +117,9 @@ project, declare numeric and colour controls in `knobs.json` as below, and read
77
117
  their CSS custom properties instead of copying a file for every possible value.
78
118
  Verify every control changes the intended app property, including its minimum,
79
119
  maximum and reset state. When saving defaults, keep the source fallback and the
80
- axis default in sync. The interactive demo includes card radius and padding sliders;
81
- its screenshot view keeps the original two selectors for a compact composition.
120
+ axis default in sync. The interactive demo includes card radius and padding sliders
121
+ in a Cards section and the accent colour in a Theme section; its screenshot view
122
+ keeps the original two selectors, without sections, for a compact composition.
82
123
 
83
124
  Removal actions in the panel save requests for the agent. Selectors and options
84
125
  stay visible and usable until the agent changes the project. Requests open in
@@ -131,7 +172,7 @@ optional `route` (a pattern like a variant's, or a list of them):
131
172
  {
132
173
  "kind": "range",
133
174
  "id": "fade-from",
134
- "label": "Fade starts",
175
+ "label": "Hero > Fade starts",
135
176
  "min": 0,
136
177
  "max": 100,
137
178
  "step": 1,
@@ -142,8 +183,25 @@ optional `route` (a pattern like a variant's, or a list of them):
142
183
  ]
143
184
  ```
144
185
 
186
+ A colour knob takes a `defaultColor` and named `swatches`, all as hex values:
187
+
188
+ ```json
189
+ {
190
+ "kind": "color",
191
+ "id": "accent",
192
+ "label": "Accent colour",
193
+ "defaultColor": "#2f6f4e",
194
+ "swatches": [
195
+ { "id": "forest", "label": "Forest", "value": "#2f6f4e" },
196
+ { "id": "clay", "label": "Clay", "value": "#b4532a" }
197
+ ]
198
+ }
199
+ ```
200
+
145
201
  The panel renders range knobs as sliders and colour knobs as swatches with a
146
- custom picker, on their configured routes. A value reaches the
202
+ custom picker, on their configured routes. An entry with a missing or invalid
203
+ field is skipped; `npx isotone variant list` names each one. Knobs also work with
204
+ `isotone serve`, so any dev server gets them, not only Vite. A value reaches the
147
205
  page as a custom property on `<html>`, `--knob-<id>`, with the unit
148
206
  appended, so the source reads it with the committed default as the
149
207
  fallback:
@@ -177,7 +235,7 @@ document.addEventListener('isotone:knob', ({ detail }) => {
177
235
  The event only fires while the panel is mounted, so keep the committed default in
178
236
  the source. Knob values live in the reviewer's browser, not on the server. "Save as
179
237
  defaults" files the moved knobs in the same `[set-defaults]` note as the
180
- variant picks, as `Knobs: Fade starts 40% (fade-from)` with a `knobs`
238
+ variant picks, as `Knobs: Hero › Fade starts 40% (fade-from)` with a `knobs`
181
239
  payload of `fade-from:40`; the agent writes the number into the source and
182
240
  into the knob file's `defaultValue`.
183
241
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isotone",
3
- "version": "1.1.3",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Design variants and feedback for your running web app. Switch between variants your coding agent builds, pin elements, and give the agent the context to make the change.",
@@ -39,6 +39,7 @@
39
39
  "docs/integrations.md",
40
40
  "docs/migration.md",
41
41
  "docs/updates.md",
42
+ "docs/blast.md",
42
43
  "docs/variants.md",
43
44
  "docs/third-party-notices.md",
44
45
  "AGENTS.md",
@@ -1,7 +0,0 @@
1
- // src/core/limits.ts
2
- var MAX_FEEDBACK_LENGTH = 4e3;
3
-
4
- export {
5
- MAX_FEEDBACK_LENGTH
6
- };
7
- //# sourceMappingURL=chunk-A4DX2V4C.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/limits.ts"],"sourcesContent":["/** Maximum feedback length after trimming, shared by browser and server. */\nexport const MAX_FEEDBACK_LENGTH = 4000;\n"],"mappings":";AACO,IAAM,sBAAsB;","names":[]}