@mccaskie-labs/workbench-config 0.1.31 → 0.1.32

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @mccaskie-labs/workbench-config
2
2
 
3
+ ## 0.1.32
4
+
5
+ ### Patch Changes
6
+
7
+ - Forms ships its client questions: an `offer` in the forms manifest (fields table, a choice's members, text lengths, money maximum, whole-number range, dates, code shape), and `npx workbench-forms generate [--check]`, which turns each finished `workbench/forms/<form>.json` into `src/forms.generated.ts` and the form's words in the base strings catalogue (`question.wbform.` and `enum.wbform.` keys), refusing drafts and anything the package would refuse. Config adds `offer.answers_format` and the settings pattern's draft rules (save as draft, carry on or start again).
8
+
3
9
  ## 0.1.31
4
10
 
5
11
  ### Patch Changes
@@ -16,6 +16,7 @@
16
16
  "whole",
17
17
  "date",
18
18
  "choice",
19
+ "code",
19
20
  "validate",
20
21
  "readForm",
21
22
  "embedConfig",
@@ -89,5 +90,242 @@
89
90
  "cost_hint_usd": 0,
90
91
  "note": "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call"
91
92
  }
92
- ]
93
+ ],
94
+ "offer": {
95
+ "pattern": "settings",
96
+ "when": "The client wants a form people fill in on the site: an order, booking, enquiry, sign-up, application or contact form, or wants to change the fields of one already built.",
97
+ "say": "I'll build this with Workbench forms, so every answer is checked the same way on the page and when it's sent. Here's what a form like this usually has; tell me what to change.",
98
+ "answers_file": "workbench/forms/<form>.json",
99
+ "per": "form",
100
+ "generator": "npx workbench-forms generate",
101
+ "answers_format": "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"form\": \"<name>\", \"title\": \"<what it is for>\", \"fields\": [...] }`, one entry in `fields` per field in the order shown, with `name`, `label` and `kind`, and only the settings that apply: `required` (true, or `{ \"when\": \"<field>\", \"is\": [...] }`), `show` (`{ \"when\": \"<earlier field>\", \"is\": [...] }`), `generic`, `min`, `max`, `scale`, `width`, `earliest`, `latest`, `alphabet`, `separators`, `members` (`[{ \"value\", \"label\" }]`), `multiple`, `allow_none`. Put `\"limit_status\": \"agreed\"` and the client's words as `reason` on a field whose limits the client answered; leave both out when they accepted the recommendation. Labels and member labels can't contain { or }. While any answers file is a draft the generator writes nothing.",
102
+ "questions": [
103
+ {
104
+ "id": "forms.fields",
105
+ "sets": [
106
+ "field.kind",
107
+ "field.label",
108
+ "field.required",
109
+ "field.show",
110
+ "field.multiple",
111
+ "field.allowNone",
112
+ "field.generic"
113
+ ],
114
+ "ask": "Here are the fields a form like this usually has, in order, each with its type, whether it must be filled in, and when it shows. Accept them, change some, or add or remove fields?",
115
+ "style": "table",
116
+ "note": "Propose the usual fields for what the client named (an order form: name, email, phone, product, quantity, delivery date, notes) with recommended settings. Types are text, long_text, email, phone, money, whole (a whole number), date, choice (a dropdown or tick boxes) and code (a product code, postcode or reference). A field shown only sometimes names an earlier field and the answers that show it. Mark a field generic only when the client says its failures must stay vague (a promo code or security answer)."
117
+ },
118
+ {
119
+ "id": "forms.members",
120
+ "sets": "field.list",
121
+ "ask": "Which options should people pick from for this field, in the order shown?",
122
+ "style": "table",
123
+ "for": [
124
+ "choice"
125
+ ],
126
+ "must_ask": true,
127
+ "other": {
128
+ "kind": "list",
129
+ "min": 2,
130
+ "max": 50,
131
+ "description": "Two to fifty options, each with the words people see; Claude makes each stored value from them (lower case, underscores)."
132
+ },
133
+ "note": "Propose the options from what the client said, and always show them for confirmation: only the client knows what they sell or offer."
134
+ },
135
+ {
136
+ "id": "forms.text-length",
137
+ "sets": [
138
+ "text.min",
139
+ "text.max"
140
+ ],
141
+ "ask": "Here is the longest answer each text field takes (and the shortest, where one is needed). Accept them, or change some?",
142
+ "style": "table",
143
+ "for": [
144
+ "text",
145
+ "long_text",
146
+ "email"
147
+ ],
148
+ "other": {
149
+ "kind": "whole",
150
+ "min": 1,
151
+ "max": 10000,
152
+ "description": "Characters, 1 to 10,000."
153
+ },
154
+ "note": "Recommend generous maximums: a name 80, a one-line answer 120, notes 1,000, an email address no limit (254). A minimum only where a short answer is meaningless."
155
+ },
156
+ {
157
+ "id": "forms.money-max",
158
+ "sets": "money.max",
159
+ "ask": "What is the largest amount anyone should be able to enter here?",
160
+ "style": "cards",
161
+ "for": [
162
+ "money"
163
+ ],
164
+ "must_ask": true,
165
+ "answers": [
166
+ {
167
+ "label": "1,000",
168
+ "value": 1000
169
+ },
170
+ {
171
+ "label": "10,000",
172
+ "value": 10000
173
+ },
174
+ {
175
+ "label": "100,000",
176
+ "value": 100000
177
+ }
178
+ ],
179
+ "other": {
180
+ "kind": "money",
181
+ "min": 1,
182
+ "max": 1000000000,
183
+ "description": "An amount in the site's currency, above zero."
184
+ },
185
+ "note": "Word the answers in the site's currency. Too low turns away a real order; too high lets a typing slip through. Write it as the field's `max`."
186
+ },
187
+ {
188
+ "id": "forms.whole-range",
189
+ "sets": [
190
+ "whole.min",
191
+ "whole.max"
192
+ ],
193
+ "ask": "What are the smallest and largest numbers allowed here?",
194
+ "style": "cards",
195
+ "for": [
196
+ "whole"
197
+ ],
198
+ "must_ask": true,
199
+ "answers": [
200
+ {
201
+ "label": "1 to 10",
202
+ "value": "1-10"
203
+ },
204
+ {
205
+ "label": "1 to 100",
206
+ "value": "1-100"
207
+ },
208
+ {
209
+ "label": "0 to 1,000",
210
+ "value": "0-1000"
211
+ }
212
+ ],
213
+ "other": {
214
+ "kind": "text",
215
+ "min": 3,
216
+ "max": 40,
217
+ "description": "Two whole numbers, smallest first, such as \"2 to 12\"."
218
+ },
219
+ "note": "Write the answer as the field's `min` and `max` (\"1 to 10\" is `\"min\": 1, \"max\": 10`)."
220
+ },
221
+ {
222
+ "id": "forms.date-range",
223
+ "sets": [
224
+ "date.earliest",
225
+ "date.latest"
226
+ ],
227
+ "ask": "Which dates can people pick?",
228
+ "style": "cards",
229
+ "for": [
230
+ "date"
231
+ ],
232
+ "answers": [
233
+ {
234
+ "label": "Today or later",
235
+ "value": "today-",
236
+ "means": "Bookings and deliveries"
237
+ },
238
+ {
239
+ "label": "Today or earlier",
240
+ "value": "-today",
241
+ "means": "Birthdays and past events"
242
+ },
243
+ {
244
+ "label": "Any date",
245
+ "value": "-"
246
+ }
247
+ ],
248
+ "other": {
249
+ "kind": "text",
250
+ "min": 1,
251
+ "max": 40,
252
+ "description": "An earliest and a latest date (YYYY-MM-DD or today), either may be open."
253
+ },
254
+ "note": "Write the answer as the field's `earliest` and `latest`: \"Today or later\" is `\"earliest\": \"today\"`, \"Today or earlier\" is `\"latest\": \"today\"`, \"Any date\" sets neither."
255
+ },
256
+ {
257
+ "id": "forms.code-shape",
258
+ "sets": [
259
+ "field.alphabet",
260
+ "field.separators",
261
+ "code.min",
262
+ "code.max"
263
+ ],
264
+ "ask": "What does a correct code look like here?",
265
+ "style": "cards",
266
+ "for": [
267
+ "code"
268
+ ],
269
+ "must_ask": true,
270
+ "answers": [
271
+ {
272
+ "label": "Letters and numbers",
273
+ "value": "letters_digits",
274
+ "means": "Like AB12-CD34"
275
+ },
276
+ {
277
+ "label": "Numbers only",
278
+ "value": "digits",
279
+ "means": "Like a postcode, 1000"
280
+ },
281
+ {
282
+ "label": "Letters only",
283
+ "value": "letters",
284
+ "means": "Like ABCD"
285
+ }
286
+ ],
287
+ "other": {
288
+ "kind": "text",
289
+ "min": 1,
290
+ "max": 80,
291
+ "description": "The characters allowed, whether dashes or spaces may be typed, and the length."
292
+ },
293
+ "note": "Then ask the length (the shortest and longest, not counting dashes or spaces) and whether people may type dashes or spaces in it. Letters are shown back in capitals. Write `alphabet`, `separators` (a list of \"-\" and \" \") and `min` and `max`."
294
+ }
295
+ ],
296
+ "not_asked": [
297
+ {
298
+ "sets": "field.name",
299
+ "why": "Claude makes it from the label: lower case letters, digits and underscores."
300
+ },
301
+ {
302
+ "sets": "field.limit",
303
+ "why": "The generator names each field's limits register entry <form>.<field>."
304
+ },
305
+ {
306
+ "sets": "limit.width",
307
+ "why": "Left to the page's layout; set only if the client asks for a narrower or wider box."
308
+ },
309
+ {
310
+ "sets": "limit.scale",
311
+ "why": "The currency's decimal places, from the project's country: write it as `scale` when that isn't 2 (0 for yen)."
312
+ },
313
+ {
314
+ "sets": "money.min",
315
+ "why": "Zero: an amount is never negative. Ask only if the client mentions a minimum order."
316
+ },
317
+ {
318
+ "sets": "limit.column",
319
+ "why": "Set when the form is stored in the database (a later step), not while it is being designed."
320
+ },
321
+ {
322
+ "sets": "limit.status",
323
+ "why": "Written by the generator: agreed where the client answered, proposed where they accepted the recommendation."
324
+ },
325
+ {
326
+ "sets": "limit.reason",
327
+ "why": "The client's own words for an answered limit, otherwise \"Workbench default, not yet confirmed\"."
328
+ }
329
+ ]
330
+ }
93
331
  }
@@ -435,6 +435,10 @@
435
435
  "type": "string",
436
436
  "minLength": 1
437
437
  },
438
+ "answers_format": {
439
+ "type": "string",
440
+ "minLength": 1
441
+ },
438
442
  "questions": {
439
443
  "minItems": 1,
440
444
  "type": "array",
@@ -1,4 +1,4 @@
1
- export declare const MANIFEST_VERSION: "0f2fa24af1ca";
1
+ export declare const MANIFEST_VERSION: "0608bbd8bb6f";
2
2
  export declare const capabilities: {
3
3
  readonly ai: {
4
4
  readonly $schema: "./schema.json";
@@ -1094,7 +1094,7 @@ export declare const capabilities: {
1094
1094
  readonly summary: "workbench-forms checks what people type into a form with one validator that runs the same on the page and at the endpoint. It returns every problem at once as permanent codes, the tidied value to show back in each field and the value to store, with limits from the app's own register and country shapes from workbench-country. It runs in the app itself, with no Gateway call and no cost.";
1095
1095
  readonly status: "built";
1096
1096
  readonly surfaces: {
1097
- readonly package: readonly ["defineForm", "text", "longText", "email", "phone", "money", "whole", "date", "choice", "validate", "readForm", "embedConfig", "inputAttributes", "messageKeys", "tidyLine", "tidyText", "CODES", "FormsSetupError"];
1097
+ readonly package: readonly ["defineForm", "text", "longText", "email", "phone", "money", "whole", "date", "choice", "code", "validate", "readForm", "embedConfig", "inputAttributes", "messageKeys", "tidyLine", "tidyText", "CODES", "FormsSetupError"];
1098
1098
  };
1099
1099
  readonly rules: readonly [{
1100
1100
  readonly id: "forms.endpoint-decides";
@@ -1148,6 +1148,174 @@ export declare const capabilities: {
1148
1148
  readonly cost_hint_usd: 0;
1149
1149
  readonly note: "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call";
1150
1150
  }];
1151
+ readonly offer: {
1152
+ readonly pattern: "settings";
1153
+ readonly when: "The client wants a form people fill in on the site: an order, booking, enquiry, sign-up, application or contact form, or wants to change the fields of one already built.";
1154
+ readonly say: "I'll build this with Workbench forms, so every answer is checked the same way on the page and when it's sent. Here's what a form like this usually has; tell me what to change.";
1155
+ readonly answers_file: "workbench/forms/<form>.json";
1156
+ readonly per: "form";
1157
+ readonly generator: "npx workbench-forms generate";
1158
+ readonly answers_format: "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"form\": \"<name>\", \"title\": \"<what it is for>\", \"fields\": [...] }`, one entry in `fields` per field in the order shown, with `name`, `label` and `kind`, and only the settings that apply: `required` (true, or `{ \"when\": \"<field>\", \"is\": [...] }`), `show` (`{ \"when\": \"<earlier field>\", \"is\": [...] }`), `generic`, `min`, `max`, `scale`, `width`, `earliest`, `latest`, `alphabet`, `separators`, `members` (`[{ \"value\", \"label\" }]`), `multiple`, `allow_none`. Put `\"limit_status\": \"agreed\"` and the client's words as `reason` on a field whose limits the client answered; leave both out when they accepted the recommendation. Labels and member labels can't contain { or }. While any answers file is a draft the generator writes nothing.";
1159
+ readonly questions: readonly [{
1160
+ readonly id: "forms.fields";
1161
+ readonly sets: readonly ["field.kind", "field.label", "field.required", "field.show", "field.multiple", "field.allowNone", "field.generic"];
1162
+ readonly ask: "Here are the fields a form like this usually has, in order, each with its type, whether it must be filled in, and when it shows. Accept them, change some, or add or remove fields?";
1163
+ readonly style: "table";
1164
+ readonly note: "Propose the usual fields for what the client named (an order form: name, email, phone, product, quantity, delivery date, notes) with recommended settings. Types are text, long_text, email, phone, money, whole (a whole number), date, choice (a dropdown or tick boxes) and code (a product code, postcode or reference). A field shown only sometimes names an earlier field and the answers that show it. Mark a field generic only when the client says its failures must stay vague (a promo code or security answer).";
1165
+ }, {
1166
+ readonly id: "forms.members";
1167
+ readonly sets: "field.list";
1168
+ readonly ask: "Which options should people pick from for this field, in the order shown?";
1169
+ readonly style: "table";
1170
+ readonly other: {
1171
+ readonly kind: "list";
1172
+ readonly min: 2;
1173
+ readonly max: 50;
1174
+ readonly description: "Two to fifty options, each with the words people see; Claude makes each stored value from them (lower case, underscores).";
1175
+ };
1176
+ readonly for: readonly ["choice"];
1177
+ readonly must_ask: true;
1178
+ readonly note: "Propose the options from what the client said, and always show them for confirmation: only the client knows what they sell or offer.";
1179
+ }, {
1180
+ readonly id: "forms.text-length";
1181
+ readonly sets: readonly ["text.min", "text.max"];
1182
+ readonly ask: "Here is the longest answer each text field takes (and the shortest, where one is needed). Accept them, or change some?";
1183
+ readonly style: "table";
1184
+ readonly other: {
1185
+ readonly kind: "whole";
1186
+ readonly min: 1;
1187
+ readonly max: 10000;
1188
+ readonly description: "Characters, 1 to 10,000.";
1189
+ };
1190
+ readonly for: readonly ["text", "long_text", "email"];
1191
+ readonly note: "Recommend generous maximums: a name 80, a one-line answer 120, notes 1,000, an email address no limit (254). A minimum only where a short answer is meaningless.";
1192
+ }, {
1193
+ readonly id: "forms.money-max";
1194
+ readonly sets: "money.max";
1195
+ readonly ask: "What is the largest amount anyone should be able to enter here?";
1196
+ readonly style: "cards";
1197
+ readonly answers: readonly [{
1198
+ readonly label: "1,000";
1199
+ readonly value: 1000;
1200
+ }, {
1201
+ readonly label: "10,000";
1202
+ readonly value: 10000;
1203
+ }, {
1204
+ readonly label: "100,000";
1205
+ readonly value: 100000;
1206
+ }];
1207
+ readonly other: {
1208
+ readonly kind: "money";
1209
+ readonly min: 1;
1210
+ readonly max: 1000000000;
1211
+ readonly description: "An amount in the site's currency, above zero.";
1212
+ };
1213
+ readonly for: readonly ["money"];
1214
+ readonly must_ask: true;
1215
+ readonly note: "Word the answers in the site's currency. Too low turns away a real order; too high lets a typing slip through. Write it as the field's `max`.";
1216
+ }, {
1217
+ readonly id: "forms.whole-range";
1218
+ readonly sets: readonly ["whole.min", "whole.max"];
1219
+ readonly ask: "What are the smallest and largest numbers allowed here?";
1220
+ readonly style: "cards";
1221
+ readonly answers: readonly [{
1222
+ readonly label: "1 to 10";
1223
+ readonly value: "1-10";
1224
+ }, {
1225
+ readonly label: "1 to 100";
1226
+ readonly value: "1-100";
1227
+ }, {
1228
+ readonly label: "0 to 1,000";
1229
+ readonly value: "0-1000";
1230
+ }];
1231
+ readonly other: {
1232
+ readonly kind: "text";
1233
+ readonly min: 3;
1234
+ readonly max: 40;
1235
+ readonly description: "Two whole numbers, smallest first, such as \"2 to 12\".";
1236
+ };
1237
+ readonly for: readonly ["whole"];
1238
+ readonly must_ask: true;
1239
+ readonly note: "Write the answer as the field's `min` and `max` (\"1 to 10\" is `\"min\": 1, \"max\": 10`).";
1240
+ }, {
1241
+ readonly id: "forms.date-range";
1242
+ readonly sets: readonly ["date.earliest", "date.latest"];
1243
+ readonly ask: "Which dates can people pick?";
1244
+ readonly style: "cards";
1245
+ readonly answers: readonly [{
1246
+ readonly label: "Today or later";
1247
+ readonly value: "today-";
1248
+ readonly means: "Bookings and deliveries";
1249
+ }, {
1250
+ readonly label: "Today or earlier";
1251
+ readonly value: "-today";
1252
+ readonly means: "Birthdays and past events";
1253
+ }, {
1254
+ readonly label: "Any date";
1255
+ readonly value: "-";
1256
+ }];
1257
+ readonly other: {
1258
+ readonly kind: "text";
1259
+ readonly min: 1;
1260
+ readonly max: 40;
1261
+ readonly description: "An earliest and a latest date (YYYY-MM-DD or today), either may be open.";
1262
+ };
1263
+ readonly for: readonly ["date"];
1264
+ readonly note: "Write the answer as the field's `earliest` and `latest`: \"Today or later\" is `\"earliest\": \"today\"`, \"Today or earlier\" is `\"latest\": \"today\"`, \"Any date\" sets neither.";
1265
+ }, {
1266
+ readonly id: "forms.code-shape";
1267
+ readonly sets: readonly ["field.alphabet", "field.separators", "code.min", "code.max"];
1268
+ readonly ask: "What does a correct code look like here?";
1269
+ readonly style: "cards";
1270
+ readonly answers: readonly [{
1271
+ readonly label: "Letters and numbers";
1272
+ readonly value: "letters_digits";
1273
+ readonly means: "Like AB12-CD34";
1274
+ }, {
1275
+ readonly label: "Numbers only";
1276
+ readonly value: "digits";
1277
+ readonly means: "Like a postcode, 1000";
1278
+ }, {
1279
+ readonly label: "Letters only";
1280
+ readonly value: "letters";
1281
+ readonly means: "Like ABCD";
1282
+ }];
1283
+ readonly other: {
1284
+ readonly kind: "text";
1285
+ readonly min: 1;
1286
+ readonly max: 80;
1287
+ readonly description: "The characters allowed, whether dashes or spaces may be typed, and the length.";
1288
+ };
1289
+ readonly for: readonly ["code"];
1290
+ readonly must_ask: true;
1291
+ readonly note: "Then ask the length (the shortest and longest, not counting dashes or spaces) and whether people may type dashes or spaces in it. Letters are shown back in capitals. Write `alphabet`, `separators` (a list of \"-\" and \" \") and `min` and `max`.";
1292
+ }];
1293
+ readonly not_asked: readonly [{
1294
+ readonly sets: "field.name";
1295
+ readonly why: "Claude makes it from the label: lower case letters, digits and underscores.";
1296
+ }, {
1297
+ readonly sets: "field.limit";
1298
+ readonly why: "The generator names each field's limits register entry <form>.<field>.";
1299
+ }, {
1300
+ readonly sets: "limit.width";
1301
+ readonly why: "Left to the page's layout; set only if the client asks for a narrower or wider box.";
1302
+ }, {
1303
+ readonly sets: "limit.scale";
1304
+ readonly why: "The currency's decimal places, from the project's country: write it as `scale` when that isn't 2 (0 for yen).";
1305
+ }, {
1306
+ readonly sets: "money.min";
1307
+ readonly why: "Zero: an amount is never negative. Ask only if the client mentions a minimum order.";
1308
+ }, {
1309
+ readonly sets: "limit.column";
1310
+ readonly why: "Set when the form is stored in the database (a later step), not while it is being designed.";
1311
+ }, {
1312
+ readonly sets: "limit.status";
1313
+ readonly why: "Written by the generator: agreed where the client answered, proposed where they accepted the recommendation.";
1314
+ }, {
1315
+ readonly sets: "limit.reason";
1316
+ readonly why: "The client's own words for an answered limit, otherwise \"Workbench default, not yet confirmed\".";
1317
+ }];
1318
+ };
1151
1319
  };
1152
1320
  readonly gateway: {
1153
1321
  readonly $schema: "./schema.json";
@@ -1,6 +1,6 @@
1
1
  // Generated by `npm run capabilities:build` from capabilities/*.json. Do not edit.
2
2
  // check:capabilities fails in Checks when this file is stale.
3
- export const MANIFEST_VERSION = "0f2fa24af1ca";
3
+ export const MANIFEST_VERSION = "0608bbd8bb6f";
4
4
  export const capabilities = {
5
5
  "ai": {
6
6
  "$schema": "./schema.json",
@@ -1495,6 +1495,7 @@ export const capabilities = {
1495
1495
  "whole",
1496
1496
  "date",
1497
1497
  "choice",
1498
+ "code",
1498
1499
  "validate",
1499
1500
  "readForm",
1500
1501
  "embedConfig",
@@ -1568,7 +1569,244 @@ export const capabilities = {
1568
1569
  "cost_hint_usd": 0,
1569
1570
  "note": "The probe page loads FORMS_SCRIPT and the form's embedConfig block, runs the same inputs in the browser and checks the verdict matches the Worker's; no Gateway call"
1570
1571
  }
1571
- ]
1572
+ ],
1573
+ "offer": {
1574
+ "pattern": "settings",
1575
+ "when": "The client wants a form people fill in on the site: an order, booking, enquiry, sign-up, application or contact form, or wants to change the fields of one already built.",
1576
+ "say": "I'll build this with Workbench forms, so every answer is checked the same way on the page and when it's sent. Here's what a form like this usually has; tell me what to change.",
1577
+ "answers_file": "workbench/forms/<form>.json",
1578
+ "per": "form",
1579
+ "generator": "npx workbench-forms generate",
1580
+ "answers_format": "`{ \"format\": 1, \"status\": \"draft\" | \"done\", \"form\": \"<name>\", \"title\": \"<what it is for>\", \"fields\": [...] }`, one entry in `fields` per field in the order shown, with `name`, `label` and `kind`, and only the settings that apply: `required` (true, or `{ \"when\": \"<field>\", \"is\": [...] }`), `show` (`{ \"when\": \"<earlier field>\", \"is\": [...] }`), `generic`, `min`, `max`, `scale`, `width`, `earliest`, `latest`, `alphabet`, `separators`, `members` (`[{ \"value\", \"label\" }]`), `multiple`, `allow_none`. Put `\"limit_status\": \"agreed\"` and the client's words as `reason` on a field whose limits the client answered; leave both out when they accepted the recommendation. Labels and member labels can't contain { or }. While any answers file is a draft the generator writes nothing.",
1581
+ "questions": [
1582
+ {
1583
+ "id": "forms.fields",
1584
+ "sets": [
1585
+ "field.kind",
1586
+ "field.label",
1587
+ "field.required",
1588
+ "field.show",
1589
+ "field.multiple",
1590
+ "field.allowNone",
1591
+ "field.generic"
1592
+ ],
1593
+ "ask": "Here are the fields a form like this usually has, in order, each with its type, whether it must be filled in, and when it shows. Accept them, change some, or add or remove fields?",
1594
+ "style": "table",
1595
+ "note": "Propose the usual fields for what the client named (an order form: name, email, phone, product, quantity, delivery date, notes) with recommended settings. Types are text, long_text, email, phone, money, whole (a whole number), date, choice (a dropdown or tick boxes) and code (a product code, postcode or reference). A field shown only sometimes names an earlier field and the answers that show it. Mark a field generic only when the client says its failures must stay vague (a promo code or security answer)."
1596
+ },
1597
+ {
1598
+ "id": "forms.members",
1599
+ "sets": "field.list",
1600
+ "ask": "Which options should people pick from for this field, in the order shown?",
1601
+ "style": "table",
1602
+ "other": {
1603
+ "kind": "list",
1604
+ "min": 2,
1605
+ "max": 50,
1606
+ "description": "Two to fifty options, each with the words people see; Claude makes each stored value from them (lower case, underscores)."
1607
+ },
1608
+ "for": [
1609
+ "choice"
1610
+ ],
1611
+ "must_ask": true,
1612
+ "note": "Propose the options from what the client said, and always show them for confirmation: only the client knows what they sell or offer."
1613
+ },
1614
+ {
1615
+ "id": "forms.text-length",
1616
+ "sets": [
1617
+ "text.min",
1618
+ "text.max"
1619
+ ],
1620
+ "ask": "Here is the longest answer each text field takes (and the shortest, where one is needed). Accept them, or change some?",
1621
+ "style": "table",
1622
+ "other": {
1623
+ "kind": "whole",
1624
+ "min": 1,
1625
+ "max": 10000,
1626
+ "description": "Characters, 1 to 10,000."
1627
+ },
1628
+ "for": [
1629
+ "text",
1630
+ "long_text",
1631
+ "email"
1632
+ ],
1633
+ "note": "Recommend generous maximums: a name 80, a one-line answer 120, notes 1,000, an email address no limit (254). A minimum only where a short answer is meaningless."
1634
+ },
1635
+ {
1636
+ "id": "forms.money-max",
1637
+ "sets": "money.max",
1638
+ "ask": "What is the largest amount anyone should be able to enter here?",
1639
+ "style": "cards",
1640
+ "answers": [
1641
+ {
1642
+ "label": "1,000",
1643
+ "value": 1000
1644
+ },
1645
+ {
1646
+ "label": "10,000",
1647
+ "value": 10000
1648
+ },
1649
+ {
1650
+ "label": "100,000",
1651
+ "value": 100000
1652
+ }
1653
+ ],
1654
+ "other": {
1655
+ "kind": "money",
1656
+ "min": 1,
1657
+ "max": 1000000000,
1658
+ "description": "An amount in the site's currency, above zero."
1659
+ },
1660
+ "for": [
1661
+ "money"
1662
+ ],
1663
+ "must_ask": true,
1664
+ "note": "Word the answers in the site's currency. Too low turns away a real order; too high lets a typing slip through. Write it as the field's `max`."
1665
+ },
1666
+ {
1667
+ "id": "forms.whole-range",
1668
+ "sets": [
1669
+ "whole.min",
1670
+ "whole.max"
1671
+ ],
1672
+ "ask": "What are the smallest and largest numbers allowed here?",
1673
+ "style": "cards",
1674
+ "answers": [
1675
+ {
1676
+ "label": "1 to 10",
1677
+ "value": "1-10"
1678
+ },
1679
+ {
1680
+ "label": "1 to 100",
1681
+ "value": "1-100"
1682
+ },
1683
+ {
1684
+ "label": "0 to 1,000",
1685
+ "value": "0-1000"
1686
+ }
1687
+ ],
1688
+ "other": {
1689
+ "kind": "text",
1690
+ "min": 3,
1691
+ "max": 40,
1692
+ "description": "Two whole numbers, smallest first, such as \"2 to 12\"."
1693
+ },
1694
+ "for": [
1695
+ "whole"
1696
+ ],
1697
+ "must_ask": true,
1698
+ "note": "Write the answer as the field's `min` and `max` (\"1 to 10\" is `\"min\": 1, \"max\": 10`)."
1699
+ },
1700
+ {
1701
+ "id": "forms.date-range",
1702
+ "sets": [
1703
+ "date.earliest",
1704
+ "date.latest"
1705
+ ],
1706
+ "ask": "Which dates can people pick?",
1707
+ "style": "cards",
1708
+ "answers": [
1709
+ {
1710
+ "label": "Today or later",
1711
+ "value": "today-",
1712
+ "means": "Bookings and deliveries"
1713
+ },
1714
+ {
1715
+ "label": "Today or earlier",
1716
+ "value": "-today",
1717
+ "means": "Birthdays and past events"
1718
+ },
1719
+ {
1720
+ "label": "Any date",
1721
+ "value": "-"
1722
+ }
1723
+ ],
1724
+ "other": {
1725
+ "kind": "text",
1726
+ "min": 1,
1727
+ "max": 40,
1728
+ "description": "An earliest and a latest date (YYYY-MM-DD or today), either may be open."
1729
+ },
1730
+ "for": [
1731
+ "date"
1732
+ ],
1733
+ "note": "Write the answer as the field's `earliest` and `latest`: \"Today or later\" is `\"earliest\": \"today\"`, \"Today or earlier\" is `\"latest\": \"today\"`, \"Any date\" sets neither."
1734
+ },
1735
+ {
1736
+ "id": "forms.code-shape",
1737
+ "sets": [
1738
+ "field.alphabet",
1739
+ "field.separators",
1740
+ "code.min",
1741
+ "code.max"
1742
+ ],
1743
+ "ask": "What does a correct code look like here?",
1744
+ "style": "cards",
1745
+ "answers": [
1746
+ {
1747
+ "label": "Letters and numbers",
1748
+ "value": "letters_digits",
1749
+ "means": "Like AB12-CD34"
1750
+ },
1751
+ {
1752
+ "label": "Numbers only",
1753
+ "value": "digits",
1754
+ "means": "Like a postcode, 1000"
1755
+ },
1756
+ {
1757
+ "label": "Letters only",
1758
+ "value": "letters",
1759
+ "means": "Like ABCD"
1760
+ }
1761
+ ],
1762
+ "other": {
1763
+ "kind": "text",
1764
+ "min": 1,
1765
+ "max": 80,
1766
+ "description": "The characters allowed, whether dashes or spaces may be typed, and the length."
1767
+ },
1768
+ "for": [
1769
+ "code"
1770
+ ],
1771
+ "must_ask": true,
1772
+ "note": "Then ask the length (the shortest and longest, not counting dashes or spaces) and whether people may type dashes or spaces in it. Letters are shown back in capitals. Write `alphabet`, `separators` (a list of \"-\" and \" \") and `min` and `max`."
1773
+ }
1774
+ ],
1775
+ "not_asked": [
1776
+ {
1777
+ "sets": "field.name",
1778
+ "why": "Claude makes it from the label: lower case letters, digits and underscores."
1779
+ },
1780
+ {
1781
+ "sets": "field.limit",
1782
+ "why": "The generator names each field's limits register entry <form>.<field>."
1783
+ },
1784
+ {
1785
+ "sets": "limit.width",
1786
+ "why": "Left to the page's layout; set only if the client asks for a narrower or wider box."
1787
+ },
1788
+ {
1789
+ "sets": "limit.scale",
1790
+ "why": "The currency's decimal places, from the project's country: write it as `scale` when that isn't 2 (0 for yen)."
1791
+ },
1792
+ {
1793
+ "sets": "money.min",
1794
+ "why": "Zero: an amount is never negative. Ask only if the client mentions a minimum order."
1795
+ },
1796
+ {
1797
+ "sets": "limit.column",
1798
+ "why": "Set when the form is stored in the database (a later step), not while it is being designed."
1799
+ },
1800
+ {
1801
+ "sets": "limit.status",
1802
+ "why": "Written by the generator: agreed where the client answered, proposed where they accepted the recommendation."
1803
+ },
1804
+ {
1805
+ "sets": "limit.reason",
1806
+ "why": "The client's own words for an answered limit, otherwise \"Workbench default, not yet confirmed\"."
1807
+ }
1808
+ ]
1809
+ }
1572
1810
  },
1573
1811
  "gateway": {
1574
1812
  "$schema": "./schema.json",
@@ -102,6 +102,7 @@ declare const offer: z.ZodObject<{
102
102
  answers_file: z.ZodString;
103
103
  per: z.ZodOptional<z.ZodString>;
104
104
  generator: z.ZodOptional<z.ZodString>;
105
+ answers_format: z.ZodOptional<z.ZodString>;
105
106
  questions: z.ZodArray<z.ZodObject<{
106
107
  id: z.ZodString;
107
108
  sets: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
@@ -301,6 +302,7 @@ export declare const capabilitySchema: z.ZodObject<{
301
302
  answers_file: z.ZodString;
302
303
  per: z.ZodOptional<z.ZodString>;
303
304
  generator: z.ZodOptional<z.ZodString>;
305
+ answers_format: z.ZodOptional<z.ZodString>;
304
306
  questions: z.ZodArray<z.ZodObject<{
305
307
  id: z.ZodString;
306
308
  sets: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
@@ -362,7 +364,7 @@ export type Offer = z.infer<typeof offer>;
362
364
  * 11 November 2026: every shared tool ships its questions). The list only
363
365
  * shrinks; a new component can't join it.
364
366
  */
365
- export declare const OFFER_PENDING: readonly ["ai", "analytics", "country", "database", "email", "email-inbound", "forms", "image-search", "images", "pdf", "strings", "ui"];
367
+ export declare const OFFER_PENDING: readonly ["ai", "analytics", "country", "database", "email", "email-inbound", "image-search", "images", "pdf", "strings", "ui"];
366
368
  export interface ManifestProblem {
367
369
  file: string;
368
370
  check: "schema" | "proof" | "ids" | "surfaces" | "errors" | "generated";
@@ -184,6 +184,8 @@ const offer = z.strictObject({
184
184
  answers_file: z.string().regex(/^workbench\/[a-z0-9<>/._-]+\.json$/, "a JSON file under workbench/"),
185
185
  per: z.string().min(1).optional(),
186
186
  generator: z.string().min(1).optional(),
187
+ /** How the answers file is laid out, when it isn't one entry per question (forms: a list of fields). */
188
+ answers_format: z.string().min(1).optional(),
187
189
  questions: z.array(question).min(1),
188
190
  not_asked: z.array(z.strictObject({ sets: z.string().min(1), why: z.string().min(1) })).min(1).optional(),
189
191
  drafts: drafts.optional(),
@@ -237,7 +239,7 @@ export const capabilitySchema = z.strictObject(baseShape).superRefine((m, ctx) =
237
239
  * 11 November 2026: every shared tool ships its questions). The list only
238
240
  * shrinks; a new component can't join it.
239
241
  */
240
- export const OFFER_PENDING = ["ai", "analytics", "country", "database", "email", "email-inbound", "forms", "image-search", "images", "pdf", "strings", "ui"];
242
+ export const OFFER_PENDING = ["ai", "analytics", "country", "database", "email", "email-inbound", "image-search", "images", "pdf", "strings", "ui"];
241
243
  function checkOffer(file, component, o, seen) {
242
244
  const problems = [];
243
245
  const named = new Map();
package/dist/skills.js CHANGED
@@ -42,6 +42,8 @@ const PATTERN_STEPS = {
42
42
  settings: [
43
43
  "Propose everything at once: a table of every setting with its recommended value. Then ask one question: \"Accept all (Recommended)\", \"Change some\" or \"Add or remove\".",
44
44
  "The client edits the list in plain words (\"delete this\", \"add a dropdown with these values, required\"). Update the answers file and show the table again until they accept it.",
45
+ "Save the answers file with `\"status\": \"draft\"` as soon as the first answer is in, and set `\"done\"` only when the client accepts the whole table and every \"Must ask\" question is answered. Never leave a draft in a commit.",
46
+ "If a draft is already there when this starts, show what it holds and ask: \"Carry on (Recommended)\" or \"Start again\". Starting again deletes the draft.",
45
47
  ],
46
48
  drafts: [
47
49
  "Ask what the thing is first, then offer examples to pick from (see Drafts below). Show them, let the client pick one or ask for another round, and keep only the one picked.",
@@ -100,7 +102,7 @@ export function renderSkill(m) {
100
102
  "- Where the client accepts every recommendation, still ask each \"Must ask\" question.",
101
103
  "- An \"Other\" answer must meet the rule under its question. When it doesn't, say why and ask again. Never store a value outside it.",
102
104
  "- Ask nothing that isn't listed here, and invent no setting. A need the questions don't cover goes back to the client as a plain question, and isn't written to the answers file.",
103
- `- Write the answers to \`${o.answers_file}\`${o.per ? `, one entry per ${o.per}` : ""}: for each question its id, the value, and the client's own words when they typed "Other" or changed a recommendation.${o.generator ? ` Then run \`${o.generator}\`, which writes the code and refuses an out-of-range answer. Never edit what it writes; change the answers file and run it again.` : ""}`,
105
+ `- Write the answers to \`${o.answers_file}\`${o.per ? `, one entry per ${o.per}` : ""}: ${o.answers_format ?? `for each question its id, the value, and the client's own words when they typed "Other" or changed a recommendation.`}${o.generator ? ` Then run \`${o.generator}\`, which writes the code and refuses anything the package can't apply (it doesn't check the "Other" rules: you do). It builds nothing while an answers file is a draft. Never edit what it writes; change the answers file and run it again.` : ""}`,
104
106
  "",
105
107
  "## Questions",
106
108
  "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mccaskie-labs/workbench-config",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Workbench app configuration: WORKBENCH_ENV, typed variables that fail by name, secrets that are never printed, and the capabilities manifest.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",