@ziprack/sdk 0.1.2 → 0.2.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/dist/docs/README.md +52 -0
- package/dist/docs/activity.md +140 -0
- package/dist/docs/ai.md +159 -0
- package/dist/docs/applet-packages.md +118 -0
- package/dist/docs/attempts.md +184 -0
- package/dist/docs/auth.md +106 -0
- package/dist/docs/broadcast.md +226 -0
- package/dist/docs/conformance.md +102 -0
- package/dist/docs/database.md +176 -0
- package/dist/docs/extending.md +256 -0
- package/dist/docs/installation.md +185 -0
- package/dist/docs/introduction.md +124 -0
- package/dist/docs/manifest.md +209 -0
- package/dist/docs/media.md +85 -0
- package/dist/docs/protocol.md +121 -0
- package/dist/docs/publishing.md +127 -0
- package/dist/docs/reporting.md +146 -0
- package/dist/docs/sdk.md +258 -0
- package/dist/docs/security.md +127 -0
- package/dist/docs/storage.md +131 -0
- package/dist/docs/studio.md +425 -0
- package/dist/docs/versioning.md +101 -0
- package/dist/docs/ziprack-1-authoring.md +182 -0
- package/dist/mcp/bin.d.ts +2 -0
- package/dist/mcp/bin.js +3 -0
- package/dist/mcp/docs.d.ts +31 -0
- package/dist/mcp/docs.js +107 -0
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/rpc.d.ts +33 -0
- package/dist/mcp/rpc.js +86 -0
- package/dist/mcp/server.d.ts +23 -0
- package/dist/mcp/server.js +55 -0
- package/dist/mcp/tools.d.ts +20 -0
- package/dist/mcp/tools.js +106 -0
- package/package.json +10 -4
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# zipRACK Studio
|
|
2
|
+
|
|
3
|
+
- [What It Is](#what-it-is)
|
|
4
|
+
- [Getting Started](#getting-started)
|
|
5
|
+
- [The Document](#the-document)
|
|
6
|
+
- [Prop Values](#prop-values)
|
|
7
|
+
- [The Block Catalog](#the-block-catalog)
|
|
8
|
+
- [Theme Tokens](#theme-tokens)
|
|
9
|
+
- [The Derived Manifest](#the-derived-manifest)
|
|
10
|
+
- [Validation](#validation)
|
|
11
|
+
- [The Editor](#the-editor)
|
|
12
|
+
- [Custom Code](#custom-code)
|
|
13
|
+
- [Building and Packaging](#building-and-packaging)
|
|
14
|
+
- [The MCP Server](#the-mcp-server)
|
|
15
|
+
- [When Not To Use It](#when-not-to-use-it)
|
|
16
|
+
|
|
17
|
+
<a name="what-it-is"></a>
|
|
18
|
+
## What It Is
|
|
19
|
+
|
|
20
|
+
Studio is an optional visual builder for `ziprack/1` applets. A typed JSON document is the applet;
|
|
21
|
+
`index.html`, `manifest.json` and the bundle are compiled from it. The same renderer draws the editor
|
|
22
|
+
canvas and the built package, so what an author arranges is what a participant gets.
|
|
23
|
+
|
|
24
|
+
The load-bearing idea is that **the manifest is derived, not written**. Every block declares what it
|
|
25
|
+
contributes — an activity, a collection, an upload, a prompt, a capability — and the compiler walks the
|
|
26
|
+
document and merges those contributions. `undeclared_capability` at runtime, the most common way an
|
|
27
|
+
applet breaks, stops being possible to cause by omission: placing a question block is what declares its
|
|
28
|
+
activity, and there is no second step to forget.
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
applet.json ──► block registry ──► contributes() walk ──► manifest.json
|
|
32
|
+
│ │
|
|
33
|
+
├──► @ziprack/studio-runtime ──► editor canvas │
|
|
34
|
+
│ └─► dist/index.html ◄────────┘
|
|
35
|
+
└──► MCP server ──► an AI assistant
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Three packages, all installed as one dev dependency:
|
|
39
|
+
|
|
40
|
+
| Package | Contents |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `@ziprack/studio-core` | Document model, prop types, block registry, validator, manifest compiler, theme tokens. No DOM. |
|
|
43
|
+
| `@ziprack/studio-runtime` | The only renderer. Mounts a document into an element and calls `@ziprack/sdk`. |
|
|
44
|
+
| `@ziprack/studio` | The `ziprack-studio` CLI: editor, compiler, packager, MCP server. |
|
|
45
|
+
|
|
46
|
+
<a name="getting-started"></a>
|
|
47
|
+
## Getting Started
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
npm install --save-dev @ziprack/studio
|
|
51
|
+
npx ziprack-studio init --slug team-pulse
|
|
52
|
+
npx ziprack-studio edit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`init` writes `applet.json` — one screen with a heading, a rating question and a finish button — and a
|
|
56
|
+
`.gitignore` for the generated build inputs. `edit` opens the editor and the live canvas. `package`
|
|
57
|
+
produces the zip you upload:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
npx ziprack-studio package # dist-zip/team-pulse-0.1.0.zip
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Command | What it does |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `init [dir]` | Start an `applet.json`. `--slug`, `--name`, `--force`. |
|
|
66
|
+
| `edit` | Editor and canvas on `http://localhost:5273`. `--port`, `--host`, `--open`. |
|
|
67
|
+
| `validate` | Check the document and the manifest it derives. Exits non-zero on errors, and on warnings under `--strict`. |
|
|
68
|
+
| `manifest` | Print the derived `manifest.json`. |
|
|
69
|
+
| `build` | Validate, derive, bundle to `dist/`. `--out`, `--keep`. |
|
|
70
|
+
| `package` | Build, then write `dist-zip/{slug}-{version}.zip` with an SBOM. |
|
|
71
|
+
| `mcp` | Run the stdio MCP server. |
|
|
72
|
+
|
|
73
|
+
Every command takes `--project <dir>`; without it, the nearest `applet.json` in a parent directory wins.
|
|
74
|
+
|
|
75
|
+
<a name="the-document"></a>
|
|
76
|
+
## The Document
|
|
77
|
+
|
|
78
|
+
`applet.json` is a tree of nodes. Each node names a block, holds that block's props, and holds children
|
|
79
|
+
in **named slots**:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"schema": "ziprack-studio/1",
|
|
84
|
+
"applet": { "slug": "team-pulse", "name": "Team Pulse", "version": "0.1.0" },
|
|
85
|
+
"theme": { "preset": "clean", "tokens": { "color": { "accent": "#2563eb" } } },
|
|
86
|
+
"settings": { "segments": [], "metrics": [], "profile": [] },
|
|
87
|
+
"screens": [{
|
|
88
|
+
"id": "screen_a1b2c3",
|
|
89
|
+
"block": "layout.screen",
|
|
90
|
+
"version": "1",
|
|
91
|
+
"props": { "title": { "$$type": "string", "value": "Welcome" } },
|
|
92
|
+
"slots": {
|
|
93
|
+
"default": [{
|
|
94
|
+
"id": "rating_d4e5f6",
|
|
95
|
+
"block": "question.rating",
|
|
96
|
+
"version": "1",
|
|
97
|
+
"props": {
|
|
98
|
+
"activityId": { "$$type": "activity-id", "value": "confidence.pre" },
|
|
99
|
+
"label": { "$$type": "string", "value": "How confident do you feel?" },
|
|
100
|
+
"scale": { "$$type": "scale", "value": { "min": 1, "max": 6 } }
|
|
101
|
+
},
|
|
102
|
+
"slots": {},
|
|
103
|
+
"style": { "variant": "card", "spacing": "lg" },
|
|
104
|
+
"editor": { "label": "Confidence (pre)" }
|
|
105
|
+
}]
|
|
106
|
+
}
|
|
107
|
+
}]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Slots, rather than one child array, are what make structure checkable without a layout engine:
|
|
112
|
+
`layout.split` has `left` and `right`, and each slot declares which blocks it accepts. Ids are
|
|
113
|
+
prefixed by block kind and checked against every id already in the document, so they stay readable in
|
|
114
|
+
diffs and in error messages.
|
|
115
|
+
|
|
116
|
+
`editor.label` renames a node in the block tree without touching anything a participant sees — the way
|
|
117
|
+
you tell two rating questions apart at a glance.
|
|
118
|
+
|
|
119
|
+
<a name="prop-values"></a>
|
|
120
|
+
## Prop Values
|
|
121
|
+
|
|
122
|
+
Every prop value is an envelope, primitives included:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{ "$$type": "string", "value": "How confident do you feel?" }
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The uniformity buys two things: one generic parser for every prop, and an unambiguous discriminator
|
|
129
|
+
where a slot accepts more than one kind of value. A colour prop takes either a literal or a theme
|
|
130
|
+
token, and the envelope says which without guesswork:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{ "$$type": "color", "value": "#2563eb" }
|
|
134
|
+
{ "$$type": "token", "value": "color.accent" }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Four states are distinct, because "leave it alone" and "clear it" are different instructions:
|
|
138
|
+
|
|
139
|
+
| State | Written as | Means |
|
|
140
|
+
|---|---|---|
|
|
141
|
+
| Absent | the key is missing | Use the block's default. |
|
|
142
|
+
| Null | `"scale": null` | Explicit reset back to the default. |
|
|
143
|
+
| Partial null | `{ "$$type": "scale", "value": { "min": 1, "max": null } }` | Reset one leaf, keep the rest. |
|
|
144
|
+
| Disabled | `{ "$$type": "string", "value": "…", "disabled": true }` | Stored, but suppressed at render. |
|
|
145
|
+
|
|
146
|
+
You do not type envelopes by hand. The editor forms write them, and the MCP tools take plain values and
|
|
147
|
+
wrap them for you.
|
|
148
|
+
|
|
149
|
+
<a name="the-block-catalog"></a>
|
|
150
|
+
## The Block Catalog
|
|
151
|
+
|
|
152
|
+
Each block declares the capabilities it needs, and those are what end up in `requires.capabilities`.
|
|
153
|
+
|
|
154
|
+
| Block | Capability | Contributes |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `layout.screen` | — | One screen. Slot: `default`. |
|
|
157
|
+
| `layout.stack`, `layout.card` | — | Grouping. Slot: `default`. |
|
|
158
|
+
| `layout.split` | — | Slots: `left`, `right`. |
|
|
159
|
+
| `content.heading`, `content.prose`, `content.image` | — | — |
|
|
160
|
+
| `content.media` | — | `media[]` |
|
|
161
|
+
| `question.rating`, `question.scale`, `question.text`, `question.choice` | `activity@1` | One `activities[]` entry each |
|
|
162
|
+
| `flow.next`, `flow.complete` | `attempt@1` | Attempt state and completion |
|
|
163
|
+
| `flow.progress` | — | — |
|
|
164
|
+
| `data.form`, `data.list` | `db@1` | `collections[]`, with the JSON Schema and `indexed` paths |
|
|
165
|
+
| `upload.dropzone` | `storage@1` | `uploads[]` |
|
|
166
|
+
| `ai.analysis`, `ai.chat` | `ai@1` | `prompts[]`, with `vars` and `output_schema` |
|
|
167
|
+
| `room.presence`, `room.facilitator` | `broadcast@1` | `broadcast.channels`, `facilitator_events` |
|
|
168
|
+
| `room.board` | `broadcast@1`, `db@1` | Both of the above |
|
|
169
|
+
| `custom.code` | whatever you declare | Whatever you declare |
|
|
170
|
+
|
|
171
|
+
`ziprack-studio mcp` exposes the same list, and the editor's insert menu is generated from it, so there
|
|
172
|
+
is one catalog rather than three.
|
|
173
|
+
|
|
174
|
+
<a name="theme-tokens"></a>
|
|
175
|
+
## Theme Tokens
|
|
176
|
+
|
|
177
|
+
Styling is a token set plus a handful of per-block variants. It is deliberately not free-form CSS: the
|
|
178
|
+
constraint is what keeps an author's screen legible on a phone, which is where most participants are.
|
|
179
|
+
|
|
180
|
+
`theme.tokens.color.accent` compiles to `--zr-color-accent` on `.zr-root`, and a prop holding
|
|
181
|
+
`{ "$$type": "token", "value": "color.accent" }` renders `var(--zr-color-accent)`. Storage references
|
|
182
|
+
the path, so changing the accent is one edit rather than a document-wide find-and-replace.
|
|
183
|
+
|
|
184
|
+
| Group | Tokens |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `color` | `bg`, `surface`, `text`, `muted`, `border`, `accent`, `accent-text`, `success`, `danger` |
|
|
187
|
+
| `space` | `none`, `sm`, `md`, `lg`, `xl` |
|
|
188
|
+
| `font` | `body`, `heading` |
|
|
189
|
+
| `size` | `text-sm` … `text-2xl`, `measure` |
|
|
190
|
+
| `radius` | `sm`, `md`, `lg`, `pill` |
|
|
191
|
+
|
|
192
|
+
Presets `clean` and `warm` supply the defaults; `theme.tokens` overrides only what you name. Per-node
|
|
193
|
+
`style` carries `variant`, `spacing`, `align` and `width` — a small vocabulary shared by every block,
|
|
194
|
+
rather than keys each block invents.
|
|
195
|
+
|
|
196
|
+
<a name="the-derived-manifest"></a>
|
|
197
|
+
## The Derived Manifest
|
|
198
|
+
|
|
199
|
+
`manifest.json` is output. You never edit it, and there is nothing to keep in sync:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
npx ziprack-studio manifest
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Each block's `contributes()` returns manifest fragments; the compiler merges them and refuses
|
|
206
|
+
contradictions. Two blocks may share a collection only if their declarations match exactly; an activity
|
|
207
|
+
id used twice is an error, because ids are the column keys in the export. The compiler also records
|
|
208
|
+
which node produced each entry, which is how the editor jumps from a manifest problem to the block that
|
|
209
|
+
caused it.
|
|
210
|
+
|
|
211
|
+
`requires.capabilities` is the union of the blocks in the document. Delete the last question and
|
|
212
|
+
`activity@1` disappears on the next build — worth knowing, since a capability change is what
|
|
213
|
+
[publishing](publishing.md) parks in the review queue.
|
|
214
|
+
|
|
215
|
+
<a name="validation"></a>
|
|
216
|
+
## Validation
|
|
217
|
+
|
|
218
|
+
```sh
|
|
219
|
+
npx ziprack-studio validate
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Validation is per-key. One bad prop is reported as one bad prop; the rest of the document still
|
|
223
|
+
compiles, and the editor still renders. Every error names the node, the block, the prop path, and
|
|
224
|
+
**the JSON Schema that would have been valid** — the same schema the editor's form is generated from
|
|
225
|
+
and the same one the MCP tools return.
|
|
226
|
+
|
|
227
|
+
Two layers run. Document rules cover prop types, slot membership and structure. Manifest rules are the
|
|
228
|
+
`ziprack/1` rules from [The Manifest](manifest.md), applied to the derived output: segment references,
|
|
229
|
+
metric pre/post pairing, scope implies capability, the eight-indexed-path ceiling, the reserved `$room`
|
|
230
|
+
channel, prompt placeholders against declared vars. Those rules also live in PHP on the Rack, so the
|
|
231
|
+
two implementations are held to a shared fixture corpus that both test suites run — if they ever
|
|
232
|
+
disagree, a test fails rather than an upload.
|
|
233
|
+
|
|
234
|
+
<a name="the-editor"></a>
|
|
235
|
+
## The Editor
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
npx ziprack-studio edit
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
A panel and a canvas iframe. The panel holds the screen list, the block tree, a form generated from the
|
|
242
|
+
selected block's prop schemas, and the theme editor. The canvas runs the real runtime with the real
|
|
243
|
+
SDK in mock mode against the derived manifest, so answers are real statements, resume really resumes,
|
|
244
|
+
and two browser tabs really share a `BroadcastChannel` room. Clicking a block in the canvas selects it
|
|
245
|
+
in the tree; the two halves talk only over `postMessage`.
|
|
246
|
+
|
|
247
|
+
Every change — typing in a field, reordering a block, an MCP call — goes through one command bus with
|
|
248
|
+
pre-commit hooks that can refuse it. Slot rules, unique activity ids, scope-implies-capability and the
|
|
249
|
+
reserved channel are enforced in one place, so no entry point can get around them. Undo is a log of
|
|
250
|
+
named reversible operations rather than document snapshots, and a compound edit collapses to one step.
|
|
251
|
+
|
|
252
|
+
Saves are written back to `applet.json` as you work. If you edit the file directly, the editor reloads
|
|
253
|
+
it.
|
|
254
|
+
|
|
255
|
+
<a name="custom-code"></a>
|
|
256
|
+
## Custom Code
|
|
257
|
+
|
|
258
|
+
When no block fits, `custom.code` points at a module in your own project:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
// src/blocks/thermometer.ts
|
|
262
|
+
import { h, type RenderContext } from '@ziprack/studio-core';
|
|
263
|
+
|
|
264
|
+
export default function thermometer(ctx: RenderContext) {
|
|
265
|
+
return h('div', { class: 'thermometer' }, h('button', {
|
|
266
|
+
onclick: () => void ctx.rack.answer('mood.check', 5),
|
|
267
|
+
}, 'Warm'));
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
The editor shows a named, movable box; the compiler bundles the module. This is the one place
|
|
272
|
+
declaration is not automatic, because the compiler cannot see inside your code. Two props carry what it
|
|
273
|
+
cannot infer: `capabilities` lists what the module calls, and `declares` carries the manifest entries it
|
|
274
|
+
needs, in manifest shape.
|
|
275
|
+
|
|
276
|
+
```json
|
|
277
|
+
{
|
|
278
|
+
"code": { "module": "./src/blocks/thermometer.ts", "export": "default" },
|
|
279
|
+
"label": "Thermometer",
|
|
280
|
+
"capabilities": ["activity@1"],
|
|
281
|
+
"declares": { "activities": [{ "id": "mood.check", "type": "rating", "label": "Mood" }] }
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Both halves are enforced. An undeclared capability call is refused at runtime, and a capability declared
|
|
286
|
+
with nothing to go with it fails the build — `activity@1` with no activity is as invalid here as it
|
|
287
|
+
would be in a hand-written manifest. `declares` accepts `activities`, `media`, `collections`,
|
|
288
|
+
`uploads`, `prompts`, `broadcast` and `profile`, and each is checked by the same manifest rules as a
|
|
289
|
+
catalog block's contribution.
|
|
290
|
+
|
|
291
|
+
Because the box stays a node in the tree, an applet with hand-written blocks is still openable in the
|
|
292
|
+
editor. That is the difference between an escape hatch and a one-way export.
|
|
293
|
+
|
|
294
|
+
<a name="building-and-packaging"></a>
|
|
295
|
+
## Building and Packaging
|
|
296
|
+
|
|
297
|
+
```sh
|
|
298
|
+
npx ziprack-studio build # dist/
|
|
299
|
+
npx ziprack-studio package # dist-zip/{slug}-{version}.zip
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`build` validates, derives and checks the manifest, then emits `dist/` through Vite with `base: './'`,
|
|
303
|
+
because the Rack serves packages from a token-prefixed path and absolute URLs break there:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
dist/
|
|
307
|
+
├── index.html the generated shell
|
|
308
|
+
├── manifest.json derived
|
|
309
|
+
├── applet.json the document, so the package can be reopened in the editor
|
|
310
|
+
└── assets/… the runtime, your custom modules, and the compiled theme
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
That is an ordinary `ziprack/1` package, subject to the same
|
|
314
|
+
[installer rules](applet-packages.md) as a hand-written one.
|
|
315
|
+
|
|
316
|
+
`package` adds a CycloneDX `sbom.json`, falling back to `package-lock.json` if `npm sbom` fails. Bump
|
|
317
|
+
`applet.version` before every upload; a version string installs once per applet.
|
|
318
|
+
|
|
319
|
+
<a name="the-mcp-server"></a>
|
|
320
|
+
## The MCP Server
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
npx ziprack-studio mcp
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Line-delimited JSON-RPC 2.0 over stdio. Point your assistant's MCP configuration at it with the project
|
|
327
|
+
directory as the working directory:
|
|
328
|
+
|
|
329
|
+
```json
|
|
330
|
+
{
|
|
331
|
+
"mcpServers": {
|
|
332
|
+
"ziprack-studio": {
|
|
333
|
+
"command": "npx",
|
|
334
|
+
"args": ["ziprack-studio", "mcp", "--project", "."]
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
The point is not that an assistant can write the file — it could do that anyway. The point is that it
|
|
341
|
+
writes through the same registry, the same command bus and the same validator as the editor, so what it
|
|
342
|
+
produces is a document a non-technical colleague can then open and edit.
|
|
343
|
+
|
|
344
|
+
| Tool | Purpose |
|
|
345
|
+
|---|---|
|
|
346
|
+
| `ziprack_list_blocks` | The catalog: id, label, category, `requires`, slots, what it contributes. Start here. |
|
|
347
|
+
| `ziprack_get_block_schema` | One block's prop schemas and defaults, `allowedParents`, each slot's `allowedBlocks`, and guidance on when to use it. |
|
|
348
|
+
| `ziprack_read_document` | The tree, whole or one subtree, summarised below a given `depth`. |
|
|
349
|
+
| `ziprack_compose` | Build a tree from a structure-only skeleton plus content maps. |
|
|
350
|
+
| `ziprack_patch` | `create`, `delete`, `move`, `duplicate`, `props`, `style`, `label`, `settings`, `applet`, `theme`. |
|
|
351
|
+
| `ziprack_validate` | Per-key errors, each with the schema that would have been valid. |
|
|
352
|
+
| `ziprack_derive_manifest` | The manifest this document produces, plus a capability diff against the last build. |
|
|
353
|
+
|
|
354
|
+
The SDK's docs tools (`ziprack_guidelines`, `ziprack_list_docs`, `ziprack_search_docs`,
|
|
355
|
+
`ziprack_read_doc`) are served alongside these. See [the SDK's MCP server](sdk.md#the-mcp-server).
|
|
356
|
+
|
|
357
|
+
`compose` separates structure from content, which is what stops a long generation from corrupting a
|
|
358
|
+
tree. The skeleton is nesting only; props arrive in a map keyed by `configuration-id`:
|
|
359
|
+
|
|
360
|
+
```xml
|
|
361
|
+
<layout.screen configuration-id="intro">
|
|
362
|
+
<content.heading configuration-id="title" />
|
|
363
|
+
<question.rating configuration-id="confidence" />
|
|
364
|
+
<flow.next configuration-id="go" />
|
|
365
|
+
</layout.screen>
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
```json
|
|
369
|
+
{
|
|
370
|
+
"intro": { "title": "Welcome" },
|
|
371
|
+
"title": { "text": "How is the team feeling?" },
|
|
372
|
+
"confidence": { "activityId": "confidence.pre", "label": "How confident do you feel?", "scale": { "min": 1, "max": 6 } },
|
|
373
|
+
"go": { "label": "Start" }
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Use `slot="left"` on a child to place it in a named slot, `parent` to append inside an existing node,
|
|
378
|
+
and `replace: true` to rebuild the document from scratch.
|
|
379
|
+
|
|
380
|
+
Three properties hold for every mutating call:
|
|
381
|
+
|
|
382
|
+
- **`dry_run` everywhere.** Same answer, same validation, nothing written.
|
|
383
|
+
- **All or nothing.** One refused operation rolls the whole batch back. Half a composition is worse
|
|
384
|
+
than none.
|
|
385
|
+
- **Refusals carry schemas.** A wrong prop name comes back with the names the block does have; a wrong
|
|
386
|
+
value comes back with the schema that would have been accepted. Correction takes one round trip
|
|
387
|
+
rather than a read, a guess and another write.
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"applied": false,
|
|
392
|
+
"dry_run": false,
|
|
393
|
+
"errors": [{
|
|
394
|
+
"message": "Expected a string.",
|
|
395
|
+
"block": "content.heading",
|
|
396
|
+
"node": "heading_ozbhbm",
|
|
397
|
+
"prop": "level",
|
|
398
|
+
"schema": {
|
|
399
|
+
"type": "object",
|
|
400
|
+
"properties": {
|
|
401
|
+
"$$type": { "type": "string", "const": "level" },
|
|
402
|
+
"value": { "type": "string", "enum": ["1", "2", "3"] },
|
|
403
|
+
"disabled": { "type": "boolean" }
|
|
404
|
+
},
|
|
405
|
+
"required": ["$$type", "value"],
|
|
406
|
+
"additionalProperties": false,
|
|
407
|
+
"default": { "$$type": "level", "value": "1" }
|
|
408
|
+
}
|
|
409
|
+
}]
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Successful mutations answer with the validation state of the whole document, not just the part that
|
|
414
|
+
changed, because an edit that leaves the applet unpublishable is worth knowing about immediately.
|
|
415
|
+
|
|
416
|
+
The server and the editor can be open at once. Each picks up the other's writes to `applet.json`.
|
|
417
|
+
|
|
418
|
+
<a name="when-not-to-use-it"></a>
|
|
419
|
+
## When Not To Use It
|
|
420
|
+
|
|
421
|
+
Studio is one way to build an applet, not the way. It constrains layout and styling on purpose, and
|
|
422
|
+
that is the wrong trade when the interface *is* the work — a custom visualisation, an unusual
|
|
423
|
+
interaction, a design that has to match a brand exactly. For those, write the applet directly against
|
|
424
|
+
[the SDK](sdk.md) and start from `starter/`. A Rack cannot tell the difference: both produce a
|
|
425
|
+
`ziprack/1` package, and both are subject to the same manifest and the same installer.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Versioning
|
|
2
|
+
|
|
3
|
+
- [Three Axes](#three-axes)
|
|
4
|
+
- [The Format String](#the-format-string)
|
|
5
|
+
- [Capability Versions](#capability-versions)
|
|
6
|
+
- [Package Versions](#package-versions)
|
|
7
|
+
- [Version Pinning](#version-pinning)
|
|
8
|
+
- [Applet Versions](#applet-versions)
|
|
9
|
+
|
|
10
|
+
<a name="three-axes"></a>
|
|
11
|
+
## Three Axes
|
|
12
|
+
|
|
13
|
+
There are three independent version numbers in zipRACK, and coupling any two of them is a mistake.
|
|
14
|
+
|
|
15
|
+
| Axis | Example | Governs | Audience |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| **Format string** | `ziprack/1` | Manifest shape, envelope, installer rules | Applet authors and the validator |
|
|
18
|
+
| **Capability version** | `db@1` | The SDK surface an applet binds to | Applet authors |
|
|
19
|
+
| **Package semver** | `ziprack/db` 2.4.1 | The PHP API: classes, contracts, config keys, migrations | Whoever maintains a Rack |
|
|
20
|
+
|
|
21
|
+
`ziprack/db` can reach 4.0.0 — several breaking PHP refactors — while still implementing `db@1` and
|
|
22
|
+
requiring `ziprack/1`. An applet built two years ago keeps working, because none of what it binds to
|
|
23
|
+
has changed.
|
|
24
|
+
|
|
25
|
+
> A capability version changes **only** when the applet-facing surface changes. Package major
|
|
26
|
+
> versions are free to move independently and frequently.
|
|
27
|
+
|
|
28
|
+
<a name="the-format-string"></a>
|
|
29
|
+
## The Format String
|
|
30
|
+
|
|
31
|
+
`schema: "ziprack/1"` versions the envelope (`ns`, `v`, ack shape), the core manifest fields, and the
|
|
32
|
+
installer's file rules. A Rack refuses a manifest with any other schema, naming what it speaks.
|
|
33
|
+
|
|
34
|
+
`cn-sim/1` was a single monolithic schema version, which made every new surface a breaking change.
|
|
35
|
+
Splitting the format from the capabilities is what allows surfaces to ship incrementally and allows
|
|
36
|
+
different deployments to run different capability sets — which matters given standalone Racks per
|
|
37
|
+
engagement.
|
|
38
|
+
|
|
39
|
+
<a name="capability-versions"></a>
|
|
40
|
+
## Capability Versions
|
|
41
|
+
|
|
42
|
+
A manifest declares `id@version` references. A Rack implements a set of them, built by iterating
|
|
43
|
+
installed packages:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
php artisan ziprack:status
|
|
47
|
+
# format: ziprack/1
|
|
48
|
+
# capabilities: activity@1, ai@1, attempt@1, auth@1, broadcast@1, db@1, storage@1
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
An applet requesting something outside that set is refused at **install**, not at runtime, with a
|
|
52
|
+
message naming what is implemented.
|
|
53
|
+
|
|
54
|
+
A package may implement more than one version of its capability at once — `db@1` and `db@2` during a
|
|
55
|
+
transition — which is what makes migrating applets across a capability break survivable. The
|
|
56
|
+
versions a package implements are metadata the core reads at boot, never inferred from the package's
|
|
57
|
+
own semver.
|
|
58
|
+
|
|
59
|
+
<a name="package-versions"></a>
|
|
60
|
+
## Package Versions
|
|
61
|
+
|
|
62
|
+
Ordinary semver on the PHP API, and the thing a Rack maintainer upgrades:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
composer update ziprack/core
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The packages are authored in one monorepo and published as read-only subtree splits on tag — one
|
|
69
|
+
repository per package, which nobody commits to. A change spanning `ziprack/core`'s registry and the
|
|
70
|
+
`ziprack/db` package that registers into it is one PR, one test run, one tag.
|
|
71
|
+
|
|
72
|
+
What a maintainer needs to know about a fix landing everywhere is covered by
|
|
73
|
+
[Conformance](conformance.md) and `/ziprack/health`.
|
|
74
|
+
|
|
75
|
+
<a name="version-pinning"></a>
|
|
76
|
+
## Version Pinning
|
|
77
|
+
|
|
78
|
+
Pinning is the one behaviour that is not a capability, because it is a policy the Rack applies *to*
|
|
79
|
+
an applet rather than a surface the applet calls. Rather than declare it, zipRACK derives it:
|
|
80
|
+
|
|
81
|
+
> **Requesting `attempt@1` implies pinned. Its absence implies rolling.**
|
|
82
|
+
|
|
83
|
+
Attempts are the only reason pinning exists — activity ids cannot shift mid-attempt without
|
|
84
|
+
corrupting the export — and apps must roll, or people are stranded on stale builds. One rule, one
|
|
85
|
+
capability, nothing for an author to get wrong and no new manifest field. See
|
|
86
|
+
[Attempts](attempts.md#version-pinning) for the authoring consequences.
|
|
87
|
+
|
|
88
|
+
<a name="applet-versions"></a>
|
|
89
|
+
## Applet Versions
|
|
90
|
+
|
|
91
|
+
Your own `version` field is semver, unique per applet, and uploadable exactly once. Bump it for
|
|
92
|
+
every upload, including test builds — the installer refuses a duplicate rather than overwriting,
|
|
93
|
+
which is deliberate: a version is immutable, so what a participant ran is always recoverable.
|
|
94
|
+
|
|
95
|
+
Between versions:
|
|
96
|
+
|
|
97
|
+
- Changing an activity `id` splits an export column.
|
|
98
|
+
- Changing a `label` changes only the header.
|
|
99
|
+
- Adding a capability or a profile attribute blocks promotion until approved.
|
|
100
|
+
- Adding or removing `attempt@1` flips the applet between pinned and rolling, and is flagged
|
|
101
|
+
prominently in the diff.
|