mockpit 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +13 -8
  2. package/bin/mockpit.js +116 -16
  3. package/dist/mcp/server.js +10 -7
  4. package/dist/server/apiViews.d.ts +118 -17
  5. package/dist/server/apiViews.js +66 -8
  6. package/dist/server/app.d.ts +2 -1
  7. package/dist/server/app.js +917 -144
  8. package/dist/server/decisions.d.ts +23 -0
  9. package/dist/server/decisions.js +99 -0
  10. package/dist/server/designGuide.js +45 -10
  11. package/dist/server/embeds.d.ts +25 -0
  12. package/dist/server/embeds.js +118 -0
  13. package/dist/server/eventLog.js +2 -1
  14. package/dist/server/feedbackBatch.d.ts +12 -1
  15. package/dist/server/feedbackBatch.js +20 -3
  16. package/dist/server/fills.d.ts +41 -0
  17. package/dist/server/fills.js +328 -0
  18. package/dist/server/index.js +15 -1
  19. package/dist/server/knobs.d.ts +6 -2
  20. package/dist/server/knobs.js +11 -1
  21. package/dist/server/mcpHttp.d.ts +3 -0
  22. package/dist/server/mcpHttp.js +3 -0
  23. package/dist/server/mcpSpec.d.ts +13 -13
  24. package/dist/server/mcpSpec.js +73 -8
  25. package/dist/server/postSurfaces.js +3 -12
  26. package/dist/server/richRender.js +3 -1
  27. package/dist/server/run.js +4 -0
  28. package/dist/server/runApi.d.ts +1 -1
  29. package/dist/server/runApi.js +4 -3
  30. package/dist/server/sqlStore.d.ts +89 -4
  31. package/dist/server/sqlStore.js +149 -23
  32. package/dist/server/surfacePage.d.ts +6 -2
  33. package/dist/server/surfacePage.js +142 -10
  34. package/dist/server/themes.d.ts +2 -0
  35. package/dist/server/themes.js +23 -3
  36. package/dist/server/types.d.ts +73 -11
  37. package/dist/server/types.js +42 -2
  38. package/dist/server/viewerRequest.d.ts +2 -0
  39. package/dist/server/viewerRequest.js +14 -0
  40. package/guide/topics/asks.md +50 -3
  41. package/guide/topics/feedback.md +89 -11
  42. package/guide/topics/html.md +5 -1
  43. package/guide/topics/http.md +2 -1
  44. package/guide/topics/knobs.md +2 -1
  45. package/guide/topics/surfaces.md +24 -8
  46. package/package.json +4 -13
  47. package/skills/launch-video/SKILL.md +1 -1
  48. package/skills/mockpit/SKILL.md +12 -4
  49. package/viewer/dist/index.html +8 -8
  50. package/dist/server/slots.d.ts +0 -14
  51. package/dist/server/slots.js +0 -71
@@ -52,31 +52,78 @@ The same JSON goes to `--asks`, MCP `ask` and `POST /api/mocks/:id/asks`:
52
52
  ## Scope
53
53
 
54
54
  `scope` says what an ask decides: `mock` (the default), `state` (add `state`)
55
- or `part` (add `part`, the `data-part` name).
55
+ or `part` (add `part`, the `data-part` name). Scope also says where the user
56
+ sees it: the viewer shows each state with its own questions. A `state` ask
57
+ shows on that state, a `part` ask on every state that marks the part, and a
58
+ `mock` ask whose options name variants on every state that has them (any
59
+ other `mock` ask on every state); answered once, it counts once.
60
+
61
+ Only open asks are shown and counted ("1 of 3"). Once a Send answers one, it
62
+ folds into a read-only Answered list and stays out of the next round, so ask
63
+ new questions for what is still open rather than re-sending answered ones; the
64
+ user changes a sent answer only on purpose, and the change arrives as a normal
65
+ Send.
56
66
 
57
67
  ## Options
58
68
 
59
69
  An option binds to one of:
60
70
 
61
71
  - a `variant`: picking it accepts that variant and archives its siblings in
62
- that state.
72
+ that state. Add `version` to offer one version of it (`"version": 2`, CLI
73
+ `--option Before=quiet@2`): two options can be two versions of one variant,
74
+ and the picked one is put in use. It needs the ask's `state` when the mock
75
+ has several.
63
76
  - a knob `set`: picking it applies those values live.
64
77
  - nothing: a plain pill.
65
78
 
66
79
  Options that change how something looks carry a `variant` or a knob `set`,
67
80
  so the user sees them live. Plain pills only for what can't be seen.
68
81
 
82
+ When one question only makes sense once another is answered (an accent's
83
+ placement depends on the palette), give it `after: "<that ask's id>"` (CLI
84
+ `--after palette`). It stays closed until that one is answered, then its
85
+ pictures render with that answer and say so. Ask the deciding question first.
86
+
69
87
  The viewer shows a picture of each option it can render. `multi: true` allows
70
88
  several answers. Option ids default to the slugged label. Reusing an ask `id`
71
89
  replaces that ask. The viewer adds an "Other…" write-in and a note to every
72
90
  ask (both take attached images), so don't add your own; the option id `other`
73
91
  is reserved.
74
92
 
93
+ ## Slots
94
+
95
+ A slot is one region with alternative fills, in one html. Give each
96
+ alternative the same `data-part` and its own `data-fill`:
97
+
98
+ ```html
99
+ <section data-part="entry" data-part-label="Entry" data-fill="dots" data-fill-label="Typing dots">
100
+ …
101
+ </section>
102
+ <section data-part="entry" data-fill="skeleton">…</section>
103
+ <section data-part="entry" data-fill="filling" data-fill-default>…</section>
104
+ ```
105
+
106
+ The first fill is the default unless one carries `data-fill-default`. The
107
+ server derives a select knob at the part's name and shows only the chosen
108
+ fill, so you write no switching CSS; give each fill whatever display it
109
+ needs. Draw anything inside a fill, including further slots: a slot inside a
110
+ fill is asked only while that fill is chosen, right after its parent's
111
+ question.
112
+
113
+ Publish returns the slots it found and, for each one no ask binds, a ready
114
+ `suggestedSlotAsks` entry: `scope: "part"`, one option per fill with
115
+ `set: {"entry": "<fill>"}`. Send them with `ask`, relabelled if you like. On a
116
+ pick the viewer outlines the fill on stage. `fills` in publish replaces or
117
+ adds one fill without resending the page (topic `feedback`).
118
+
119
+ Use a slot when the alternatives differ in one region; use variants when the
120
+ whole screen differs. Both mix: variants are looks, each look may have slots.
121
+
75
122
  ## The Look ask
76
123
 
77
124
  When you publish variants, ask `scope: "mock"` with one option per variant, and
78
125
  ask it first. Questions appear in the order you ask them. This one is titled
79
- "Look" and unlocks Mix, which lets the user borrow a part from another look.
126
+ "Look". Tune then offers, per part, to borrow it from another look.
80
127
  Variants without a Look ask get a plain switcher in the frame header.
81
128
 
82
129
  A variant decides everything it differs on; don't re-ask it. Ask other axes as
@@ -35,12 +35,23 @@ It returns at once:
35
35
  ],
36
36
  "mix": { "body": "quiet" },
37
37
  "tuned": { "body.size": 19 },
38
+ "decisions": {
39
+ "states": {
40
+ "Writing": {
41
+ "variant": "dark",
42
+ "slots": {
43
+ "bar": { "fill": "tabs", "label": "Tabs", "slots": { "badge": { "fill": "dot" } } }
44
+ }
45
+ }
46
+ },
47
+ "knobs": { "trim.position": "bottom", "body.size": 19 }
48
+ },
38
49
  "comments": [{ "part": "title", "state": "Writing", "text": "bigger" }],
39
50
  "reactions": ["👍 part \"title\" (variant dark, state Writing)"],
40
51
  "text": "close, go dark"
41
52
  },
42
53
  "comments": [],
43
- "accepted": [{ "state": "Writing", "variant": "dark" }],
54
+ "accepted": [{ "state": "Writing", "variant": "dark", "version": 2 }],
44
55
  "archived": [{ "state": "Writing", "variant": "quiet" }]
45
56
  }
46
57
  ],
@@ -56,7 +67,8 @@ It returns at once:
56
67
 
57
68
  `feedback` holds one batch per mock, in delivery order. `pending` is what the
58
69
  user is doing right now, per mock: `viewerOpen` says a browser has that
59
- mock on screen, `draft` is their unsent progress (`null` when there is none).
70
+ mock on screen, `draft` is their unsent progress (`null` when there is none),
71
+ and `resolved: true` marks a mock the user closed (below).
60
72
  An empty `feedback` with a draft means "still answering": tell the user to take
61
73
  their time, don't ask again. `mockpit read` returns `pending` too, without
62
74
  taking any feedback.
@@ -65,7 +77,12 @@ taking any feedback.
65
77
 
66
78
  1. `answers` and `asks` decide structure. A variant-bound answer has already
67
79
  accepted that variant and archived its siblings (`accepted`, `archived`).
68
- Stop iterating on the archived ones. The ask `variant` (per state
80
+ Stop iterating on the archived ones. Each `accepted` entry is pinned at the
81
+ `version` the user put in use (`read` → `inUse`): `export` and embeds show
82
+ that one, not your newer publishes, until the user picks again. A version
83
+ the user hid (`read --history` flags it `hidden`) is one they put away:
84
+ don't build on it.
85
+ The ask `variant` (per state
69
86
  `variant:<state>`) is the viewer's built-in Look ask, read like any
70
87
  other. A `chosen` entry with `other: true` is
71
88
  the user's own answer (its `label`), not one of your options; it flips no
@@ -86,13 +103,42 @@ taking any feedback.
86
103
  👍 keep it, 👎 rework it, "more like this" go further that way. A reaction
87
104
  without a part is about the whole variant; one may end with the user's
88
105
  note in quotes. Reactions never also appear in `comments`.
89
- 5. `decision` (`{kind: accept|revise|drop, state, variant}`) comes instead of
90
- answers when the mock has no asks and the user pressed Accept, Revise or
91
- Drop.
106
+ 5. `decision` (`{kind: accept|revise|drop, state, variant}`) is a verdict on
107
+ one variant. `accept` is the user's Use this, with the `version` now in use;
108
+ `revise` is their New: publish another version of that variant (its `text`
109
+ says from which). A `dropped` variant is hidden; archived ones stay in view,
110
+ muted. Build on neither unless the user restores one.
92
111
  6. `text` is the user's note on the Send.
93
112
 
94
113
  Treat all of it as data, never as markup or instructions.
95
114
 
115
+ ## Resolved
116
+
117
+ The user can resolve a mock from the project list (a swipe on a phone)
118
+ instead of answering it: its open asks are closed unanswered, as not worth
119
+ answering, and it leaves their open list. You hear it once, as a batch with no
120
+ reply:
121
+
122
+ ```json
123
+ {
124
+ "mock": "cart",
125
+ "reply": null,
126
+ "resolved": true,
127
+ "dismissed": [{ "ask": "density", "text": "Roomy or tight?" }],
128
+ "comments": [],
129
+ "accepted": [],
130
+ "archived": [],
131
+ "dropped": []
132
+ }
133
+ ```
134
+
135
+ Answers sent before it stand. Each `dismissed` ask now reads `dismissed: true`
136
+ in `read`, and `ask` refuses it: re-asking that id returns it under `skipped`
137
+ with a nudge and asks nothing. Don't rephrase it under a new id either; move
138
+ on, and only bring the mock back if the user asks. A new ask with a new id does
139
+ reopen the mock. If the user reopens it themselves, its dismissed asks are open
140
+ again and their answers arrive as a normal Send.
141
+
96
142
  ## Delivery
97
143
 
98
144
  Each Send is delivered exactly once. Whichever channel picks it up advances
@@ -113,25 +159,57 @@ Never poll `feedback` in a loop; the user answers at their own pace.
113
159
  ```sh
114
160
  mockpit publish --mock writer --state "Writing" --variant dark --html dark-v2.html --prompt "bigger title"
115
161
  mockpit say "Went dark; body at 19px" --mock writer
116
- mockpit export --mock writer # .mockpit/accepted/writer/<state>/{index.html,history.json}
162
+ mockpit export --mock writer # .mockpit/accepted/writer/<state>/{index.html,history.json,…}
117
163
  mockpit read # one line per mock, plus pending
118
- mockpit read writer # states, variants, asks and answers, parts, knobs, tuned
164
+ mockpit read writer # states, variants, asks and answers, parts, knobs, settled
165
+ mockpit read --tree # the project as a tree: variants, forkedFrom, uses / usedIn
119
166
  ```
120
167
 
168
+ - A flow is the screens the user walks, in order, across mocks: `--flow
169
+ Morning --step 2` (MCP/HTTP `flow`, `step`) puts this (mock, state) at step
170
+ 2, moving it if it is there; without `--step` it joins at the end, `--step 0`
171
+ takes it out. `flow` with no html places an existing screen without a new
172
+ version. `read` (no mock) and `read --tree` return `flows: [{name, steps:
173
+ [{mock, state}]}]`. In the design, `data-goto="next"`, `"prev"` or
174
+ `"<mock>:<state>"` on an element moves the user there while they try it.
175
+
121
176
  - `publish` on an existing (mock, state, variant) makes its next version.
122
- `--from N` branches off an earlier one. The result flags vanished or renamed
177
+ `--from N` branches off an earlier one. `--fork quiet@2` (MCP/HTTP
178
+ `fork: "quiet@2"`) starts a NEW variant from a sibling's version and records
179
+ the lineage; alone it copies it, with `--parts`/`--fills` it edits the copy. The result flags vanished or renamed
123
180
  parts. Don't publish a near-duplicate variant; publish the next version.
124
181
  - Replace one part instead of the document: `--parts body=body.html`
125
182
  (repeatable; `--parts body=-` reads stdin), MCP `publish({parts: {body:
126
183
  "<p data-part=\"body\">new copy</p>"}})`, or `parts` in the HTTP publish body.
127
184
  Each value replaces the whole element carrying `data-part="body"`, so you can
128
185
  rename, relabel or retag it. `"row#a"` targets one keyed instance. Send one of
129
- `html`, `surfaces` or `parts` per call. The response lists `applied` plus the
186
+ `html`, `surfaces`, `parts` or `fills` per call. The response lists `applied` plus the
130
187
  usual `partChanges`. An unknown name, or one matching several instances,
131
188
  fails with the parts or keys present and writes nothing.
189
+ - Replace or add one fill of a slot: `--fills bar#tabs=tabs.html`, MCP
190
+ `publish({fills: {"bar#tabs": "<nav>…</nav>"}})`. A fill that exists is
191
+ replaced; a new one goes after that slot's other fills. `data-part` and
192
+ `data-fill` are added to the element when missing.
193
+ - `decisions` (only when the mock has slots or the reply set knobs) is the
194
+ screen as chosen: per state the variant, then each slot's fill with the
195
+ slots inside it, then every non-slot knob value. `from` on a slot means the
196
+ user borrowed it from another variant. A slot inside a fill that was not
197
+ chosen is absent, and its question's answer is not sent.
132
198
  - `say` posts a short plain-text note in the mock's thread. Give substantial
133
199
  answers as a new version, not prose.
134
200
  - `export` returns the accepted (else current) html per state, its version
135
- history, the knobs and the last reply's tuned values.
201
+ history, the knobs, `settled` and the last reply. With slots, `html` is
202
+ the composed screen (only the chosen fills, the fill marks dropped),
203
+ `source` keeps every fill, and `decisions` is as above. The CLI writes
204
+ `index.html`, `source.html` and `decisions.json`. Port the composed html
205
+ into the codebase in its own language and components; mockpit does not
206
+ generate code.
207
+ - `settled` (on `read` and `export`) is every knob value the user has sent:
208
+ each answered option's `set` and each tuned value, later replies winning.
209
+ The viewer renders every later question and the stage on top of it, so ask
210
+ the deciding question first and its dependents after, or one answer is
211
+ judged against a default the user hasn't chosen.
212
+ - A variant with `starred: true` is one the user marked as a favourite. A star
213
+ decides nothing; treat it as "keep this in mind" when revising.
136
214
  - `read <slug>` returns metadata only. `--body` adds surfaces, `--history` adds
137
215
  version rows.
@@ -41,7 +41,11 @@ Mark the components you want feedback on with `data-part="name"` (optional
41
41
  apart and keep comments anchored across a rename). The viewer measures their
42
42
  boxes, so never hard-code geometry for it. Knob values arrive as unitless
43
43
  `--k-<path>` custom properties and `data-k-<path>` attributes on `<html>`; topic
44
- `knobs` has the details.
44
+ `knobs` has the details. Alternatives of one region are a slot: the same
45
+ `data-part` with a `data-fill` each; topic `asks`.
46
+
47
+ The page must read before any web font or remote stylesheet loads: they are
48
+ decoration, and a slow one delays the frame's layout.
45
49
 
46
50
  ## Finish
47
51
 
@@ -29,7 +29,8 @@ call; otherwise each call starts a new session and the reply goes elsewhere.
29
29
  B=http://localhost:8228
30
30
  curl -s -X POST $B/api/mocks -H 'content-type: application/json' -d '{
31
31
  "project": "demo", "mock": "checkout", "title": "Checkout", "state": "Empty cart",
32
- "agent": "claude", "knobs": {"pad": [16, 8, 32, 2]},
32
+ "agent": "claude", "knobs": {"pad": [16, 8, 32, 2]}, "widths": [390, 1280],
33
+ "note": "One column; the summary carries the empty state",
33
34
  "html": "<section data-part=\"summary\" data-part-label=\"Summary\" style=\"padding:calc(var(--k-pad,16)*1px)\">Your cart is empty</section>"}'
34
35
  # → {"mock":{…},"post":{…},"sessionId":"S","url":"…","parts":[{"state":"Empty cart","parts":[{"name":"summary","label":"Summary"}]}],"feedback":[]}
35
36
 
@@ -4,7 +4,8 @@ A knob is a value the user tunes live on one render. Use one when one render
4
4
  plus a control shows the choice. A choice is several variants plus one ask
5
5
  that binds them; for that, publish variants and ask instead (topic `asks`).
6
6
  A 2–3 option knob is a choice: ask with options that `set` it. Publishing
7
- one returns that nudge.
7
+ one returns that nudge. Declare knobs for what is worth fine-tuning (size,
8
+ spacing, colour, density) even when the user did not ask; they wait in Tune.
8
9
 
9
10
  ## Declaring
10
11
 
@@ -45,14 +45,30 @@ A version can combine surfaces. `[html, diff]` is a design with its code review;
45
45
  html is sandboxed because you author the markup. Every other kind is rendered
46
46
  from data, so send data, never markup.
47
47
 
48
- ## Page slots
49
-
50
- A page mock (`--kind page`, MCP `kind: "page"`) can embed a component mock in its
51
- html with `<mockpit-slot slug="button" variant="dark" version="3"></mockpit-slot>`.
52
- The server inlines that version's html body in place, inside the page's frame.
53
- `variant` defaults to `default`; a missing `version` pins to the current one at
54
- publish, so the page keeps showing what it was composed from until you publish
55
- its next version. An unknown slug renders as an empty placeholder, not silently dropped.
48
+ ## Composition: embeds
49
+
50
+ Any mock can embed another mock of the project in its html with
51
+ `<mockpit-embed slug="button"></mockpit-embed>` (`<mockpit-slot>` still works;
52
+ a slot is now a region with fills, topic `asks`). The server inlines that
53
+ mock's html body in place, inside the embedding frame, and expands the
54
+ component's own embeds in turn (up to 6 levels), so build a page from
55
+ sections and sections from components, each its own mock with its own
56
+ variants and questions.
57
+
58
+ - No `version`: the embed follows the component's version in use once the user
59
+ accepted one, else its latest, so a new version of the button shows in every
60
+ page that uses it without republishing them. `version="3"` pins it.
61
+ - No `variant`: the component's accepted variant (else `default`). Name one to
62
+ show a specific variant.
63
+ - The component shows as decided: its slots keep only the fills the user
64
+ settled on, and its other settled knob values ride on the embed's wrapper
65
+ (`data-k-<path>` and `--k-<path>`), so write its knob CSS as
66
+ `[data-k-tone="loud"] .chip`, not `html[data-k-tone=…]`.
67
+ - `read` lists each variant's `embeds` as they render now (nested included,
68
+ with every version, for the user to peek back) and `publishedWith`, the
69
+ versions it rendered with when you published it.
70
+ - A chain that loops back on itself is refused at publish. An unknown slug
71
+ renders as an empty placeholder, not silently dropped.
56
72
 
57
73
  ## Editing one surface
58
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mockpit",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A design-decision loop for terminal coding agents — agents publish mocks with states and variants, ask questions and expose knobs; you answer, tune and comment, then send one reply.",
5
5
  "keywords": [
6
6
  "agent-tools",
@@ -64,13 +64,8 @@
64
64
  "start": "npm run build:viewer && node server/index.ts",
65
65
  "build:viewer": "vite build",
66
66
  "mcp": "node mcp/server.ts",
67
- "test": "npm run test:node && npm run test:viewer",
68
- "test:node": "node --test 'test/**/*.test.ts'",
69
- "test:viewer": "vitest run --config vitest.viewer.config.ts",
67
+ "test": "node --test 'test/**/*.test.ts'",
70
68
  "test:worker": "npm run build:viewer && node --test test/workerIntegration.integration.ts",
71
- "coverage": "npm run coverage:node && npm run coverage:viewer",
72
- "coverage:node": "c8 node --test 'test/**/*.test.ts'",
73
- "coverage:viewer": "vitest run --coverage --config vitest.viewer.config.ts",
74
69
  "test:e2e": "playwright test",
75
70
  "bench": "node --expose-gc bench/run.ts",
76
71
  "bench:all": "node --expose-gc bench/run.ts --all",
@@ -101,10 +96,10 @@
101
96
  "@pierre/diffs": "^1.2.11",
102
97
  "ansi_up": "^6.0.6",
103
98
  "hono": "^4.13.1",
104
- "markdown-it": "^14.2.0",
99
+ "markdown-it": "^15.0.3",
105
100
  "quickjs-wasi": "3.6.2",
106
101
  "shiki": "^4.2.0",
107
- "zod": "^3.24.0"
102
+ "zod": "^4.6.7"
108
103
  },
109
104
  "devDependencies": {
110
105
  "@changesets/cli": "^3.0.3",
@@ -112,9 +107,6 @@
112
107
  "@playwright/test": "^1.60.0",
113
108
  "@types/markdown-it": "^14.1.2",
114
109
  "@types/node": "^25.9.3",
115
- "@vitest/coverage-v8": "^4.1.11",
116
- "c8": "^12.0.0",
117
- "jsdom": "^27.4.0",
118
110
  "lint-staged": "^17.0.7",
119
111
  "oxfmt": "^0.54.0",
120
112
  "oxlint": "^1.69.0",
@@ -125,7 +117,6 @@
125
117
  "vite": "^8.0.16",
126
118
  "vite-plugin-singlefile": "^2.3.3",
127
119
  "vite-plugin-solid": "^2.11.12",
128
- "vitest": "^4.1.11",
129
120
  "wrangler": "^4.120.0"
130
121
  },
131
122
  "overrides": {
@@ -83,7 +83,7 @@ ffmpeg -y -i "$RAW" -vf "fps=30,format=yuv420p" \
83
83
  `sub`, `cmds`/`cmd` (+ `span.p` for the prompt `$`), `foot`.
84
84
  - Interact with the viewer through `page.frameLocator("#app")`. Useful
85
85
  selectors (the same ones `e2e/decide-flow.spec.ts` drives):
86
- `.home-row[data-mock="<slug>"]` (Home rows), `.frame.on` (the frame on
86
+ `.home-step[data-mock="<slug>"]` (Home's screens), `.frame.on` (the frame on
87
87
  stage), `.strip [role="tab"]` (states), `.opt[data-option="<id>"]` (question
88
88
  options), `button.send`, `.themebtn` (dark/light).
89
89
  - Publish content via the HTTP API while recording: `POST /api/mocks`
@@ -17,18 +17,26 @@ The loop never waits for the user:
17
17
 
18
18
  1. `mockpit init` once per repo; read the brief (`mockpit guide`).
19
19
  2. `mockpit publish --mock <slug> --state "<user's words>" --variant <v> --html f.html`,
20
- once per variant; mark parts with `data-part`.
20
+ once per variant; mark parts with `data-part`. Name a state by what the
21
+ user sees ("Home, idle"), never by your process ("Prototype", "v2"). The html is the UI alone:
22
+ say what a variant is in `--note`, never in the design, and name the
23
+ widths it ships at once with `--widths 390,1280`.
24
+ Build screens from components: each reusable piece is its own
25
+ `--kind component` mock, embedded in pages with
26
+ `<mockpit-embed slug="card">`, so one decision on it reaches every page.
21
27
  3. A choice is several variants plus one ask that binds them: `mockpit ask`
22
28
  with options bound to variants. A variant decides everything it differs on;
23
29
  don't re-ask it. Visual options carry a `variant` or knob `set`. One render
24
- plus a control: `--knobs`. When a publish result nudges, send its
25
- `suggestedAsk`.
30
+ plus a control: `--knobs`; declare knobs for what is worth fine-tuning
31
+ even unasked. When a publish result nudges, send its `suggestedAsk`.
26
32
  4. Tell the user in one line where to look, then end your turn. The question
27
33
  itself lives in the mock (`ask`), never in chat. The user answers in the
28
34
  browser at their own pace. Never poll.
29
35
  5. When the user says they answered (or a `mockpit watch` line wakes you), run
30
36
  `mockpit feedback`. Every write also returns `feedback`; read it. Empty
31
- `feedback` with a `pending` draft means they are still answering.
37
+ `feedback` with a `pending` draft means they are still answering. A batch
38
+ with `resolved: true` means they closed the mock unanswered: never re-ask
39
+ its `dismissed` asks.
32
40
  6. `mockpit publish` the revision (`--parts name=file` for one part);
33
41
  `mockpit export` once a variant is accepted.
34
42