@paramrig/web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +359 -0
- package/dist/index-L3Zm-CoX.js +512 -0
- package/dist/paramrig-web.js +533 -0
- package/dist/rigs/extended-types.d.ts +184 -0
- package/dist/rigs/types.d.ts +199 -0
- package/dist/web/contracts.d.ts +287 -0
- package/dist/web/geometry.d.ts +5 -0
- package/dist/web/sdk.d.ts +20 -0
- package/package.json +40 -0
- package/schemas/batch.schema.json +126 -0
- package/schemas/manifest.schema.json +83 -0
- package/schemas/response.schema.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Soheil Saheb-Jamii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# @paramrig/web
|
|
2
|
+
|
|
3
|
+
Expose a running development page's design decisions as controls, let a person tune
|
|
4
|
+
them in the ParamRig workbench, and read back what they approved as a file in your
|
|
5
|
+
repository.
|
|
6
|
+
|
|
7
|
+
This document is written for the agent that maintains the project being tuned. It
|
|
8
|
+
is self-contained: everything needed to instrument a project, keep it safe, and
|
|
9
|
+
answer the feedback is here. The workbench's own behaviour — selection, drawing,
|
|
10
|
+
review — is described in `docs/web-workspace.md` in the ParamRig repository.
|
|
11
|
+
|
|
12
|
+
The integration is **development only**. It is framework-neutral, depends on
|
|
13
|
+
nothing, and does nothing at all unless a workbench is framing the page.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install --save-dev @paramrig/web
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Releases are published from the ParamRig repository by its release workflow, on a
|
|
22
|
+
`web-v<version>` tag, with provenance. To try an unreleased build, run
|
|
23
|
+
`docker compose run --rm app npm run build:web-sdk` there and install the
|
|
24
|
+
resulting `packages/web-sdk` directory as a local dependency.
|
|
25
|
+
|
|
26
|
+
## 1. Describe the controls
|
|
27
|
+
|
|
28
|
+
Write `.paramrig/manifest.json` at the root of the project. It is the contract: it
|
|
29
|
+
says what can be tuned, where the page runs, and which source revision it describes.
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"version": 1,
|
|
34
|
+
"id": "fieldnotes",
|
|
35
|
+
"name": "Fieldnotes",
|
|
36
|
+
"revision": "study-1",
|
|
37
|
+
"origin": "http://localhost:3000",
|
|
38
|
+
"pages": [{ "id": "home", "name": "Home", "path": "/" }],
|
|
39
|
+
"groups": [{ "id": "brand", "label": "Brand" }],
|
|
40
|
+
"parameters": [
|
|
41
|
+
{ "id": "accent", "kind": "color", "label": "Accent", "group": "brand", "defaultValue": "#df7757" }
|
|
42
|
+
],
|
|
43
|
+
"bindings": [
|
|
44
|
+
{ "paramId": "accent", "scope": "global", "kind": "css-variable", "property": "--accent" }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Meaning |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `version` | Always `1`. |
|
|
52
|
+
| `id` | Stable identifier, `[A-Za-z0-9][A-Za-z0-9_-]{0,127}`. Never change it: the workbench's library, its saved drafts and every batch on disk are keyed by it. |
|
|
53
|
+
| `name` | What a person sees. |
|
|
54
|
+
| `revision` | The source revision this manifest describes: any string — a counter, a date, a short label — as long as each one is new. **Change it whenever you change the code the controls describe.** The workbench waits rather than applying a batch to code it does not match. |
|
|
55
|
+
| `origin` | The exact HTTP or HTTPS origin the development server answers on — scheme, host and port, nothing else. |
|
|
56
|
+
| `pages` | At least one, at most 100. Each has a unique `id`, a `name` and a `path` starting with `/`. |
|
|
57
|
+
| `groups` | Named sections for the controls. Each has an `id` and a `label`. |
|
|
58
|
+
| `parameters` | The controls themselves, at most 500, with unique `id`s. |
|
|
59
|
+
| `bindings` | What each control writes, and how far it reaches. |
|
|
60
|
+
|
|
61
|
+
`parseManifest` throws on anything it does not accept, and the message names what
|
|
62
|
+
is wrong. Call it yourself so the failure is yours rather than the workbench's.
|
|
63
|
+
|
|
64
|
+
### Control kinds
|
|
65
|
+
|
|
66
|
+
Every parameter has `id`, `label`, `group`, `kind` and `defaultValue`.
|
|
67
|
+
|
|
68
|
+
`number` · `color` · `select` · `curve` · `switch` · `gradient` · `text` ·
|
|
69
|
+
`vector` · `range` · `palette` · `points` · `radial` · `group` · `list` ·
|
|
70
|
+
`resource` · `gizmo2d` · `gizmo3d` · `camera` · `textureFrame` · `multiselect` ·
|
|
71
|
+
`action` · `preset`
|
|
72
|
+
|
|
73
|
+
Four of them require more:
|
|
74
|
+
|
|
75
|
+
- `number` requires `min`, `max` and a `step` greater than zero. Its `unit` is
|
|
76
|
+
what a person reads beside the control; the binding's `unit`, below, is what is
|
|
77
|
+
written to CSS. They are usually the same string.
|
|
78
|
+
- `select`, `multiselect` and `preset` require `options`, each `{ value, label }`.
|
|
79
|
+
- `group` requires `fields`, an array of parameters, nested at most 8 deep.
|
|
80
|
+
- `list` requires `item`, one parameter describing an element.
|
|
81
|
+
|
|
82
|
+
The others carry optional fields of their own — `text` has `maxLength` and
|
|
83
|
+
`multiline`, for instance — listed in the package's type declarations,
|
|
84
|
+
`dist/rigs/extended-types.d.ts`. All twenty-one exist because the same controls
|
|
85
|
+
serve ParamRig's vector and 3D rigs; on a web page, the useful ones are those
|
|
86
|
+
whose value maps onto a CSS value or onto an adapter you write.
|
|
87
|
+
|
|
88
|
+
Start with `number`, `color`, `select`, `switch` and `text`. They cover most of
|
|
89
|
+
what a person wants to move, and every one of them reads clearly in a review.
|
|
90
|
+
|
|
91
|
+
### Bindings
|
|
92
|
+
|
|
93
|
+
A binding names a `paramId`, a `scope`, a `kind` and a `property`.
|
|
94
|
+
|
|
95
|
+
**Scope** — how far a change reaches, declared by you and never changed by which
|
|
96
|
+
element a person happens to select:
|
|
97
|
+
|
|
98
|
+
- `global` — the whole application.
|
|
99
|
+
- `page` — one page; also declare `pageId`.
|
|
100
|
+
- `element` — one instrumented family; also declare `target: { id, instance? }`.
|
|
101
|
+
Leaving `instance` out is deliberate: it applies to every instance of that
|
|
102
|
+
family, and the workbench says so in the review.
|
|
103
|
+
|
|
104
|
+
**Kind** — where the value goes:
|
|
105
|
+
|
|
106
|
+
- `css-variable` — writes a CSS custom property. `property` is the property name,
|
|
107
|
+
`--accent`. Prefer this for design tokens: one binding moves everything that
|
|
108
|
+
reads the token. A string is written as it is: `Public Sans` needs no quotes
|
|
109
|
+
to reach `font-family` through `var()`.
|
|
110
|
+
- `style` — writes one declared CSS property on the target. `property` is the CSS
|
|
111
|
+
property, `font-size`. The original inline value and its priority are preserved
|
|
112
|
+
and restored.
|
|
113
|
+
- `adapter` — calls a `read`/`apply`/`restore` adapter you registered in the
|
|
114
|
+
application. `property` is the adapter's key. Use it for anything CSS cannot
|
|
115
|
+
express: copy, state, a canvas, a composite value.
|
|
116
|
+
|
|
117
|
+
`unit` is appended to numeric CSS values — `"unit": "px"`. Without it a number is
|
|
118
|
+
written bare, which is what `line-height` and `opacity` want.
|
|
119
|
+
|
|
120
|
+
`source` is optional: a file path shown beside the control, the same claim as
|
|
121
|
+
`data-paramrig-source` on an element. Nothing reads it as an instruction, and a
|
|
122
|
+
stale one is not an error, only a hint that lies.
|
|
123
|
+
|
|
124
|
+
**How the source value is read.** When the workbench pairs, the SDK reads what
|
|
125
|
+
the page shows rather than trusting the manifest: `read()` for an adapter, the
|
|
126
|
+
computed style for a CSS property or custom property. `defaultValue` is the
|
|
127
|
+
fallback when that read gives nothing. A computed length comes back in pixels
|
|
128
|
+
whatever the stylesheet declared, so the SDK converts it into the binding's
|
|
129
|
+
`unit` when the two are commensurable — `rem`, `em`, `vw`, `pt` and the other
|
|
130
|
+
lengths, `s` and `ms`, `deg` and `turn`. A custom property reads back as you
|
|
131
|
+
declared it, and the same conversion applies when it carries a unit. A
|
|
132
|
+
percentage cannot be read back, nor
|
|
133
|
+
can a bare number the browser reports with a unit, such as a unitless
|
|
134
|
+
`line-height`; there the `defaultValue` stands, so keep it true.
|
|
135
|
+
|
|
136
|
+
Callbacks never travel through a manifest, a batch or a message. Resource controls
|
|
137
|
+
carry metadata, not file bytes; expose a URL your project owns, or an adapter.
|
|
138
|
+
|
|
139
|
+
## 2. Name the elements
|
|
140
|
+
|
|
141
|
+
An element the workbench can talk about carries `data-paramrig-id`. Everything
|
|
142
|
+
else is optional.
|
|
143
|
+
|
|
144
|
+
```html
|
|
145
|
+
<article data-paramrig-id="story-card"
|
|
146
|
+
data-paramrig-instance="coast"
|
|
147
|
+
data-paramrig-label="Story card"
|
|
148
|
+
data-paramrig-source="src/StoryCard.tsx">
|
|
149
|
+
<h2 data-paramrig-id="story-title">Following the coastline</h2>
|
|
150
|
+
</article>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- `data-paramrig-id` — a stable semantic identifier. It is what a binding points
|
|
154
|
+
at, and what survives a refactor. Keep it when you move the markup.
|
|
155
|
+
- `data-paramrig-instance` — tells repeated components apart. Descendants inherit
|
|
156
|
+
it, so the title above belongs to `coast` without repeating it.
|
|
157
|
+
- `data-paramrig-label` — the name a person reads. Without it the identifier is
|
|
158
|
+
read as a sentence (`story-card` becomes **Story card**), then the accessible
|
|
159
|
+
name, then the words on screen, then `Unnamed article`.
|
|
160
|
+
- `data-paramrig-source` — a hint about where this comes from, shown beside the
|
|
161
|
+
element. It is your claim, not an inferred source map; keep it honest or omit it.
|
|
162
|
+
|
|
163
|
+
Instrument the elements most likely to receive feedback. Uninstrumented ones can
|
|
164
|
+
still be selected and commented on, but their reference is provisional and has to
|
|
165
|
+
be reattached after a reload.
|
|
166
|
+
|
|
167
|
+
## 3. Connect, in development only
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { connectWeb, parseManifest } from '@paramrig/web'
|
|
171
|
+
import manifestFile from './.paramrig/manifest.json'
|
|
172
|
+
|
|
173
|
+
const connection = connectWeb({
|
|
174
|
+
manifest: parseManifest(manifestFile),
|
|
175
|
+
adapters: {
|
|
176
|
+
headings: {
|
|
177
|
+
read: () => currentHeadingFont,
|
|
178
|
+
apply: value => setHeadingFont(String(value)),
|
|
179
|
+
restore: () => setHeadingFont(initialHeadingFont),
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
connection.dispose()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
React:
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
useEffect(() => {
|
|
191
|
+
if (!import.meta.env.DEV) return
|
|
192
|
+
const connection = connectWeb({ manifest: parseManifest(manifestFile), adapters })
|
|
193
|
+
return () => connection.dispose()
|
|
194
|
+
}, [])
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Always dispose.** On unmount, and on hot-module replacement — `import.meta.hot`
|
|
198
|
+
in Vite, `module.hot` in webpack. A second `connectWeb` without a `dispose` is
|
|
199
|
+
handled (it warns and replaces the first, so two overlays never coexist), but the
|
|
200
|
+
warning is telling you the cleanup is missing.
|
|
201
|
+
|
|
202
|
+
**Keep it out of production.** The example above is enough under Vite: the
|
|
203
|
+
package declares `sideEffects: false`, the guarded call is dead code once
|
|
204
|
+
`import.meta.env.DEV` is `false`, and the bundler drops the import with it. Keep
|
|
205
|
+
`parseManifest` and the manifest inside the guard too, or they stay in the
|
|
206
|
+
bundle. With another bundler, or `process.env.NODE_ENV`, put a dynamic
|
|
207
|
+
`import()` behind the same check.
|
|
208
|
+
|
|
209
|
+
`hostOrigin` is optional and takes one origin or a list. It defaults to
|
|
210
|
+
`['http://localhost:5174', 'http://127.0.0.1:5174']`, the two addresses the
|
|
211
|
+
workbench answers on. Name your own only if you run the workbench elsewhere.
|
|
212
|
+
|
|
213
|
+
### What it does when there is no workbench
|
|
214
|
+
|
|
215
|
+
`connectWeb` never throws, because a development integration that takes the
|
|
216
|
+
application off the screen is worse than one that is unavailable:
|
|
217
|
+
|
|
218
|
+
- **Not in a frame** — the page opened normally — it installs nothing. No
|
|
219
|
+
listener, no observer, no overlay, and nothing in the console.
|
|
220
|
+
- **The manifest's `origin` is not the page's origin**, or `hostOrigin` is not an
|
|
221
|
+
origin: one `console.warn` naming both, then nothing. A development server
|
|
222
|
+
usually answers to both `localhost` and `127.0.0.1`; opening the page by the
|
|
223
|
+
other name should cost a line, not a blank screen. Fix it by opening the page at
|
|
224
|
+
the manifest's origin, or by writing the origin you actually use.
|
|
225
|
+
|
|
226
|
+
`parseManifest` does throw. An invalid manifest is a programming error, and you
|
|
227
|
+
called it.
|
|
228
|
+
|
|
229
|
+
### Framing, and HTTPS
|
|
230
|
+
|
|
231
|
+
The workbench opens your page in a cross-origin iframe, so your development server
|
|
232
|
+
has to allow it:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
Content-Security-Policy: frame-ancestors 'self' http://localhost:5174 http://127.0.0.1:5174;
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
and it must not send a conflicting `X-Frame-Options`. Scope that header to the
|
|
239
|
+
development configuration. Never relax it in production, and never remove the
|
|
240
|
+
production header to make a local session work.
|
|
241
|
+
|
|
242
|
+
If your development server is HTTPS, the workbench must be reached over HTTPS as
|
|
243
|
+
well: a browser will not frame an `https:` page from an `http:` document without
|
|
244
|
+
complaint, and mixed content is refused outright.
|
|
245
|
+
|
|
246
|
+
The pairing itself is checked at every step. The SDK announces itself to its
|
|
247
|
+
parent; the workbench answers with `hello`; that reply pins the origin and the
|
|
248
|
+
session that every later message, in both directions, is checked against. Nothing
|
|
249
|
+
is listening before the reply, and the application's own clicks pass through.
|
|
250
|
+
|
|
251
|
+
## 4. What to commit in `.paramrig`
|
|
252
|
+
|
|
253
|
+
| File | Who writes it | Commit it? |
|
|
254
|
+
| --- | --- | --- |
|
|
255
|
+
| `manifest.json` | You | **Yes.** It is source: it describes your code. |
|
|
256
|
+
| `batches/<id>.json` | ParamRig | Your call. They are immutable and they are the record of what was asked. Committing them lets the next agent read the history; they can be large when they carry markup. |
|
|
257
|
+
| `responses/<id>.json` | You | Your call, and it should match `batches/`. |
|
|
258
|
+
| `draft.json` | ParamRig | **No.** It is rewritten continuously and is not approved instructions. |
|
|
259
|
+
| `captures/*.png` | ParamRig | **No.** Large, and reproducible from the batch. |
|
|
260
|
+
| `README.md` | ParamRig | Your call. It repeats what is here. |
|
|
261
|
+
| `.gitignore` | ParamRig | Yes — it is the two "no" rows above. |
|
|
262
|
+
|
|
263
|
+
ParamRig writes that `.gitignore` when there is none, covering `draft.json` and
|
|
264
|
+
`captures/`, and never writes over it. Everything else is yours to decide.
|
|
265
|
+
|
|
266
|
+
ParamRig writes nothing outside `.paramrig`. It does not start your application,
|
|
267
|
+
install anything, or touch your source.
|
|
268
|
+
|
|
269
|
+
## 5. The loop
|
|
270
|
+
|
|
271
|
+
A person tunes controls, comments on elements, draws on the page, then approves.
|
|
272
|
+
That writes one immutable file: `.paramrig/batches/<id>.json`. They will hand you
|
|
273
|
+
an instruction naming it.
|
|
274
|
+
|
|
275
|
+
**Read the batch.** It carries `values` (every control's value, not only the ones
|
|
276
|
+
that moved), `changes` (each with `before`, `after` and the bindings it writes),
|
|
277
|
+
and `tickets` (the comments, each with its targets, marks, page, viewport, scroll
|
|
278
|
+
context and captures). A batch restates every difference from your source, so the
|
|
279
|
+
newest one alone is the whole picture, and a control present in `values` but
|
|
280
|
+
absent from `changes` did not move. A ticket need not map to a change either:
|
|
281
|
+
when the approved values already do what it asks — one binding carrying a font
|
|
282
|
+
to every heading — say so in its message rather than look for a second edit.
|
|
283
|
+
|
|
284
|
+
**Apply it in the source.** Move the real values in the real files — the batch is
|
|
285
|
+
a request, not a patch. A value behind an adapter usually lives in three places
|
|
286
|
+
that move together: the initial state, `read` and `restore`. Preserve every
|
|
287
|
+
`data-paramrig-id` and `data-paramrig-instance`. Then, in `manifest.json`, set
|
|
288
|
+
each changed control's `defaultValue` to the value you applied, so the manifest
|
|
289
|
+
tells the truth even without a page to read, and change `revision`, because the
|
|
290
|
+
code the controls describe has changed.
|
|
291
|
+
|
|
292
|
+
**Write one response**, atomically: a temporary file, then a rename.
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"version": 1,
|
|
297
|
+
"id": "response-001",
|
|
298
|
+
"projectId": "fieldnotes",
|
|
299
|
+
"batchId": "the-approved-batch-id",
|
|
300
|
+
"sourceRevision": "study-1",
|
|
301
|
+
"resultRevision": "study-2",
|
|
302
|
+
"createdAt": "2026-09-05T12:00:00.000Z",
|
|
303
|
+
"summary": "Applied the approved palette and adjusted the hero.",
|
|
304
|
+
"tickets": [
|
|
305
|
+
{ "id": "the-ticket-id", "status": "implemented", "message": "Check the hero at mobile width." }
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
- `id` must be unique, and the file must be named `<id>.json`. Any name the
|
|
311
|
+
identifier pattern accepts will do — letters, digits, `-` and `_`, no dot —
|
|
312
|
+
`response-<uuid>` and a counter alike.
|
|
313
|
+
- `projectId` is the manifest's `id`; `batchId` names the batch you read.
|
|
314
|
+
- `sourceRevision` is the batch's own `sourceRevision`. `resultRevision` is the
|
|
315
|
+
new manifest revision. A response naming another revision is reported, not
|
|
316
|
+
applied.
|
|
317
|
+
- `tickets` answers the tickets of that batch, each its own way, and only tickets
|
|
318
|
+
that belong to it. `status` is `implemented` or `needs-info`.
|
|
319
|
+
- `message` is what a person reads: what changed, and how to check it.
|
|
320
|
+
|
|
321
|
+
Use `needs-info` when you cannot act. Say precisely what you need — a value, a
|
|
322
|
+
choice between two readings, a page you cannot reach. It reopens the ticket for
|
|
323
|
+
another round rather than closing it wrongly.
|
|
324
|
+
|
|
325
|
+
**A response is a claim, not an approval.** Write `summary` and each `message` as
|
|
326
|
+
what you did and how to check it, never as a certification. Only the person
|
|
327
|
+
validates a correction, in the workbench.
|
|
328
|
+
|
|
329
|
+
**One round, end to end.** The batch says `accent` moved from `#bc593d` to
|
|
330
|
+
`#336b72` through `--accent`, and `heading-font` from `Georgia` to `Public Sans`
|
|
331
|
+
through the `headings` adapter; one ticket on the hero asks for the coastal
|
|
332
|
+
palette. You change `--accent` in the stylesheet; you change the initial font,
|
|
333
|
+
`read` and `restore` in the adapter; you set both `defaultValue`s, move
|
|
334
|
+
`revision` from `study-1` to `study-2`, and write `responses/response-001.json`
|
|
335
|
+
naming the batch, `study-1` as `sourceRevision`, `study-2` as `resultRevision`,
|
|
336
|
+
and the ticket as `implemented` with a message saying where to look. The batch
|
|
337
|
+
itself you do not touch.
|
|
338
|
+
|
|
339
|
+
**What never changes.** Do not edit or delete a batch, `draft.json`, or a capture
|
|
340
|
+
file. Batches are immutable, and the workbench refuses a rewrite. A ticket that is
|
|
341
|
+
reopened leaves its batch and drops the answer that batch received, so a later
|
|
342
|
+
response to a batch a ticket has left does not reach it.
|
|
343
|
+
|
|
344
|
+
## Schemas
|
|
345
|
+
|
|
346
|
+
Machine-readable JSON Schema (draft 2020-12) for the three files, shipped with the
|
|
347
|
+
package and importable as `@paramrig/web/schemas/<name>.schema.json`:
|
|
348
|
+
|
|
349
|
+
- `schemas/manifest.schema.json`
|
|
350
|
+
- `schemas/batch.schema.json`
|
|
351
|
+
- `schemas/response.schema.json`
|
|
352
|
+
|
|
353
|
+
They are a second expression of the guards in the SDK, and a test in the ParamRig
|
|
354
|
+
repository fails the moment the two disagree. Validate against them in your own
|
|
355
|
+
checks; the guards remain the authority at runtime.
|
|
356
|
+
|
|
357
|
+
## Licence
|
|
358
|
+
|
|
359
|
+
MIT. See `LICENSE` in the ParamRig repository.
|