@terreno/blocks 57.11.0 → 57.13.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 (58) hide show
  1. package/dist/errors.d.ts +2 -0
  2. package/dist/errors.js +2 -0
  3. package/dist/index.d.ts +7 -2
  4. package/dist/index.js +13 -1
  5. package/dist/interactive.d.ts +26 -0
  6. package/dist/interactive.js +62 -0
  7. package/dist/limits.d.ts +26 -0
  8. package/dist/limits.js +26 -0
  9. package/dist/lint.d.ts +18 -1
  10. package/dist/lint.js +186 -26
  11. package/dist/plainText.d.ts +17 -0
  12. package/dist/plainText.js +56 -0
  13. package/dist/prompt.d.ts +10 -1
  14. package/dist/prompt.js +73 -3
  15. package/dist/schema.d.ts +113 -2
  16. package/dist/schema.js +121 -5
  17. package/dist/validate.js +26 -2
  18. package/package.json +1 -1
  19. package/src/__snapshots__/prompt.test.ts.snap +530 -0
  20. package/src/errors.ts +4 -0
  21. package/src/fixtures/golden/sunday-roast.yaml +174 -0
  22. package/src/fixtures/invalid/card-eyebrow-empty.yaml +7 -0
  23. package/src/fixtures/invalid/card-eyebrow-too-long.yaml +7 -0
  24. package/src/fixtures/invalid/checklist-duplicate-item.yaml +9 -0
  25. package/src/fixtures/invalid/checklist-item-id-too-long.yaml +7 -0
  26. package/src/fixtures/invalid/checklist-reserved-id.yaml +16 -0
  27. package/src/fixtures/invalid/copy-missing-text-and-target.yaml +10 -0
  28. package/src/fixtures/invalid/copy-target.yaml +14 -0
  29. package/src/fixtures/invalid/gallery-caption-too-long.yaml +9 -0
  30. package/src/fixtures/invalid/gallery-image-host.yaml +10 -0
  31. package/src/fixtures/invalid/gallery-too-few.yaml +6 -0
  32. package/src/fixtures/invalid/list-image-host.yaml +12 -0
  33. package/src/fixtures/invalid/list-missing-title.yaml +6 -0
  34. package/src/fixtures/invalid/list-too-many.yaml +17 -0
  35. package/src/fixtures/invalid/stepper-decimals.yaml +14 -0
  36. package/src/fixtures/invalid/stepper-out-of-range.yaml +10 -0
  37. package/src/fixtures/invalid/stepper-reserved-id.yaml +19 -0
  38. package/src/fixtures/valid/card-eyebrow.yaml +8 -0
  39. package/src/fixtures/valid/checklist.yaml +29 -0
  40. package/src/fixtures/valid/copy.yaml +85 -0
  41. package/src/fixtures/valid/gallery.yaml +22 -0
  42. package/src/fixtures/valid/list.yaml +24 -0
  43. package/src/fixtures/valid/stepper.yaml +35 -0
  44. package/src/golden.test.ts +234 -0
  45. package/src/index.test.ts +9 -0
  46. package/src/index.ts +24 -0
  47. package/src/interactive.test.ts +141 -0
  48. package/src/interactive.ts +75 -0
  49. package/src/limits.ts +26 -0
  50. package/src/lint.test.ts +672 -0
  51. package/src/lint.ts +264 -33
  52. package/src/parse.test.ts +40 -0
  53. package/src/plainText.test.ts +158 -0
  54. package/src/plainText.ts +81 -0
  55. package/src/prompt.test.ts +325 -2
  56. package/src/prompt.ts +87 -2
  57. package/src/schema.ts +259 -6
  58. package/src/validate.ts +26 -2
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.blockPlainText = void 0;
4
+ const withUnit = (value, unit) => unit === undefined ? value : `${value} ${unit}`;
5
+ /** Matches the renderer: each amount is fixed to the item's `decimals`, so 2 at 1 is "2.0". */
6
+ const stepperText = (block) => [
7
+ `${block.label}: ${withUnit(String(block.value), block.unit)}`,
8
+ ...(block.items ?? []).map((item) => `${item.label}: ${withUnit(item.amount.toFixed(item.decimals ?? 0), item.unit)}`),
9
+ ].join("\n");
10
+ const checklistText = (block, checked) => block.items
11
+ .map((item) => `${(checked?.[item.id] ?? item.checked) === true ? "[x]" : "[ ]"} ${item.text}`)
12
+ .join("\n");
13
+ const listText = (block) => block.items
14
+ .map((item) => (item.text === undefined ? `- ${item.title}` : `- ${item.title}: ${item.text}`))
15
+ .join("\n");
16
+ const cellText = (value) => {
17
+ if (value === null || value === undefined) {
18
+ return "";
19
+ }
20
+ return String(value);
21
+ };
22
+ const tableText = (block, datasets) => {
23
+ const dataset = datasets?.[block.data];
24
+ if (dataset === undefined || dataset.source === "ref") {
25
+ return "";
26
+ }
27
+ const names = block.columns ?? dataset.columns.map((column) => column.name);
28
+ const indexes = names.map((name) => dataset.columns.findIndex((column) => column.name === name));
29
+ const rows = dataset.rows.map((row) => indexes.map((index) => (index < 0 ? "" : cellText(row[index]))).join("\t"));
30
+ // The header matches the table on screen, which shows underscores in column names as spaces.
31
+ const header = names.map((name) => name.replaceAll("_", " ")).join("\t");
32
+ return [header, ...rows].join("\n");
33
+ };
34
+ /**
35
+ * The text a copy action writes to the clipboard for its target block. Pass the block as it is
36
+ * shown now (a host override applied) and the current checklist ticks in `checked`. Blocks a
37
+ * copy action cannot target return an empty string, as does a table whose dataset is missing or
38
+ * an unresolved ref.
39
+ */
40
+ const blockPlainText = (block, options = {}) => {
41
+ switch (block.type) {
42
+ case "stepper":
43
+ return stepperText(block);
44
+ case "checklist":
45
+ return checklistText(block, options.checked);
46
+ case "list":
47
+ return listText(block);
48
+ case "table":
49
+ return tableText(block, options.datasets);
50
+ case "text":
51
+ return block.markdown;
52
+ default:
53
+ return "";
54
+ }
55
+ };
56
+ exports.blockPlainText = blockPlainText;
package/dist/prompt.d.ts CHANGED
@@ -1,9 +1,18 @@
1
1
  /**
2
2
  * The system-prompt section a host prepends when a reply must be one block document.
3
3
  * Every cap is read from `BLOCK_LIMITS` so the prompt, the schema, and the reference stay aligned.
4
+ * `richBlocks: false` keeps the prompt as it was before rich blocks, for clients that cannot
5
+ * render them yet. The stepper is offered only with a `stepperActions` entry, and the checklist
6
+ * names its callback only with a `checklistActions` entry.
4
7
  */
5
- export declare const blocksPromptSection: ({ allowHtml, hostActions, imageHosts, }?: {
8
+ export declare const blocksPromptSection: ({ allowHtml, checklistActions, hostActions, imageHosts, richBlocks, stepperActions, }?: {
6
9
  allowHtml?: boolean;
10
+ /** Host actions a checklist callback may name. Empty or omitted means ticks stay local. */
11
+ checklistActions?: readonly string[];
7
12
  hostActions?: readonly string[];
8
13
  imageHosts?: readonly string[];
14
+ /** Default `true`. `false` leaves every rich block out of the prompt. */
15
+ richBlocks?: boolean;
16
+ /** Host actions that handle a stepper. Without one, the prompt never mentions stepper. */
17
+ stepperActions?: readonly string[];
9
18
  }) => string;
package/dist/prompt.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.blocksPromptSection = void 0;
4
4
  const limits_1 = require("./limits");
5
+ const schema_1 = require("./schema");
5
6
  const DATA_EXAMPLE = `v: 1
6
7
  datasets:
7
8
  signups:
@@ -34,11 +35,74 @@ const PROSE_EXAMPLE = `v: 1
34
35
  blocks:
35
36
  - type: text
36
37
  markdown: I could not find any signups for that range. Try a wider date window.`;
38
+ /**
39
+ * Stepper instructions, offered only when the host registers an action that handles steppers,
40
+ * so the model never writes a stepper whose buttons would do nothing.
41
+ */
42
+ const stepperPromptLines = (stepperActions) => [
43
+ "stepper requires id, label, value, min, max, and callback (name and optional payload). It may set step (above 0, default 1), unit, itemsTitle, items, and note. min is below max, and value is between them.",
44
+ "Its decrease and increase buttons send the callback with payload.value set to the next value. The host returns the updated stepper, so write item amounts for the starting value.",
45
+ `A stepper callback name must be one of: ${stepperActions.join(", ")}.`,
46
+ "A stepper item requires label and amount. It may set unit, decimals, and round (nearest or up).",
47
+ `A stepper id is at most ${limits_1.BLOCK_LIMITS.stepperIdMaxLength} characters. No other id may be <id>_decrease or <id>_increase.`,
48
+ `A stepper label and itemsTitle are at most ${limits_1.BLOCK_LIMITS.stepperLabelMaxLength} characters, its unit ${limits_1.BLOCK_LIMITS.stepperUnitMaxLength}, and its note ${limits_1.BLOCK_LIMITS.stepperNoteMaxLength}.`,
49
+ `A stepper has at most ${limits_1.BLOCK_LIMITS.stepperItemsMax} items. An item label is at most ${limits_1.BLOCK_LIMITS.stepperLabelMaxLength} characters, its unit ${limits_1.BLOCK_LIMITS.stepperItemUnitMaxLength}, and decimals is 0 to ${limits_1.BLOCK_LIMITS.stepperDecimalsMax}.`,
50
+ ];
51
+ /**
52
+ * Checklist instructions, offered with rich blocks. A tick only reaches the host through a
53
+ * checklist action, so without one the model is told to leave callback out and ticks stay local.
54
+ */
55
+ const checklistPromptLines = (checklistActions) => [
56
+ "checklist requires id and items, and may set title. It shows a done count such as 2 of 8.",
57
+ "A checklist item requires id and text. It may set meta (a short label such as a time, shown under the text), detail (a muted line under that), and checked (true or false).",
58
+ checklistActions.length === 0
59
+ ? "Leave callback out of a checklist. Ticks stay on the device."
60
+ : `Set a checklist callback name to one of: ${checklistActions.join(", ")}. Its payload is optional. Each tick sends the callback with payload.itemId, payload.checked, and payload.state, and the host returns the updated checklist.`,
61
+ `A checklist id is at most ${limits_1.BLOCK_LIMITS.checklistIdMaxLength} characters and an item id ${limits_1.BLOCK_LIMITS.checklistItemIdMaxLength}. Item ids are unique in the checklist, and no other id may be <id>_<item id>.`,
62
+ `A checklist has ${limits_1.BLOCK_LIMITS.checklistItemsMin} to ${limits_1.BLOCK_LIMITS.checklistItemsMax} items. Its title is at most ${limits_1.BLOCK_LIMITS.checklistTitleMaxLength} characters, an item text ${limits_1.BLOCK_LIMITS.checklistItemTextMaxLength}, an item meta ${limits_1.BLOCK_LIMITS.checklistItemMetaMaxLength}, and an item detail ${limits_1.BLOCK_LIMITS.checklistItemDetailMaxLength}.`,
63
+ ];
64
+ /**
65
+ * Gallery instructions, offered with rich blocks. Tile srcs defer to the image src rules so the
66
+ * https guidance stays in the one line that knows the host's imageHosts.
67
+ */
68
+ const galleryPromptLines = () => [
69
+ `gallery requires images: ${limits_1.BLOCK_LIMITS.galleryImagesMin} to ${limits_1.BLOCK_LIMITS.galleryImagesMax} photos shown together. Each image requires src and alt, and may set caption. Each src follows the image src rules above.`,
70
+ `A gallery image alt is at most ${limits_1.BLOCK_LIMITS.galleryAltMaxLength} characters and its caption ${limits_1.BLOCK_LIMITS.galleryCaptionMaxLength}.`,
71
+ ];
72
+ /** Card eyebrow instruction, offered with rich blocks. */
73
+ const cardEyebrowPromptLines = () => [
74
+ `A card may set eyebrow, a short label shown small above its title. An eyebrow is at most ${limits_1.BLOCK_LIMITS.cardEyebrowMaxLength} characters.`,
75
+ ];
76
+ /**
77
+ * List instructions, offered with rich blocks. The word "list" names only this block in the
78
+ * prompt, so the model never reads it as a generic instruction. Item image srcs defer to the
79
+ * image src rules, and the alt cap is the image block's.
80
+ */
81
+ const listPromptLines = () => [
82
+ `list requires items: ${limits_1.BLOCK_LIMITS.listItemsMin} to ${limits_1.BLOCK_LIMITS.listItemsMax} entries stacked one under another. Each item requires title, and may set text (plain text, not markdown), meta (a short label such as a time or a price), and image (src and alt, a thumbnail). Each image src follows the image src rules above.`,
83
+ `A list item title is at most ${limits_1.BLOCK_LIMITS.listItemTitleMaxLength} characters, its text ${limits_1.BLOCK_LIMITS.listItemTextMaxLength}, its meta ${limits_1.BLOCK_LIMITS.listItemMetaMaxLength}, and its image alt ${limits_1.BLOCK_LIMITS.headingTextMaxLength}.`,
84
+ ];
85
+ /**
86
+ * Copy action instruction, offered with rich blocks. The stepper is named as a target only when
87
+ * the prompt offers it, so the prompt never mentions a stepper without a stepper action.
88
+ */
89
+ const copyPromptLine = (offersStepper) => {
90
+ const targets = schema_1.COPY_TARGET_TYPES.filter((type) => offersStepper || type !== "stepper");
91
+ const named = `${targets.slice(0, -1).join(", ")}, or ${targets.at(-1)}`;
92
+ return `action.kind may also be copy: it copies to the clipboard on the device and never reaches the host. A copy action sets exactly one of text (at most ${limits_1.BLOCK_LIMITS.copyTextMaxLength} characters, copied as written) or target (the id of a ${named} block in this document, whose current contents are copied).`;
93
+ };
37
94
  /**
38
95
  * The system-prompt section a host prepends when a reply must be one block document.
39
96
  * Every cap is read from `BLOCK_LIMITS` so the prompt, the schema, and the reference stay aligned.
97
+ * `richBlocks: false` keeps the prompt as it was before rich blocks, for clients that cannot
98
+ * render them yet. The stepper is offered only with a `stepperActions` entry, and the checklist
99
+ * names its callback only with a `checklistActions` entry.
40
100
  */
41
- const blocksPromptSection = ({ allowHtml = false, hostActions = [], imageHosts = [], } = {}) => {
101
+ const blocksPromptSection = ({ allowHtml = false, checklistActions = [], hostActions = [], imageHosts = [], richBlocks = true, stepperActions = [], } = {}) => {
102
+ const offersStepper = richBlocks && stepperActions.length > 0;
103
+ const richTypes = richBlocks
104
+ ? `${offersStepper ? ", stepper" : ""}, checklist, gallery, list`
105
+ : "";
42
106
  const callbacks = hostActions.length === 0
43
107
  ? "No callback names are registered. Do not emit kind callback."
44
108
  : `A callback name must be one of: ${hostActions.join(", ")}.`;
@@ -50,8 +114,8 @@ const blocksPromptSection = ({ allowHtml = false, hostActions = [], imageHosts =
50
114
  "The document is YAML or JSON. Keys, when present, are in this order: v, datasets, blocks.",
51
115
  "v is 1.",
52
116
  allowHtml
53
- ? "Block types: heading, text, metric, badge, divider, context, chart, table, actions, columns, card, html, callout, image, details."
54
- : "Block types: heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details. Do not emit type html.",
117
+ ? `Block types: heading, text, metric, badge, divider, context, chart, table, actions, columns, card, html, callout, image, details${richTypes}.`
118
+ : `Block types: heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details${richTypes}. Do not emit type html.`,
55
119
  `An html field is at most ${limits_1.BLOCK_LIMITS.htmlMaxBytes} bytes.`,
56
120
  ...(allowHtml
57
121
  ? [
@@ -62,11 +126,17 @@ const blocksPromptSection = ({ allowHtml = false, hostActions = [], imageHosts =
62
126
  "image requires alt and src. src is a data:image URL, a file: ref, or an https URL on an allowed host.",
63
127
  imageHostsLine,
64
128
  "details requires title and text.",
129
+ ...(offersStepper ? stepperPromptLines(stepperActions) : []),
130
+ ...(richBlocks ? checklistPromptLines(checklistActions) : []),
131
+ ...(richBlocks ? galleryPromptLines() : []),
132
+ ...(richBlocks ? listPromptLines() : []),
133
+ ...(richBlocks ? cardEyebrowPromptLines() : []),
65
134
  "heading requires text. text requires markdown. metric requires label and value. badge requires text. context requires text.",
66
135
  "chart kind is line, bar, area, or donut. Bind it with data, x, and y, or with points of label and value.",
67
136
  "table requires data, the name of a dataset. actions requires id and elements.",
68
137
  "An element is a button (id, text, action) or a segmented control (id, target, options of label and data).",
69
138
  "action.kind is reply (text), open (url or route, exactly one), select (target and data), or callback (name and optional payload).",
139
+ ...(richBlocks ? [copyPromptLine(offersStepper)] : []),
70
140
  "columns has 2 to 4 children. card has children and an optional title. Do not nest columns or card inside columns or card.",
71
141
  `At most ${limits_1.BLOCK_LIMITS.maxBlocks} blocks, including nested blocks.`,
72
142
  `Depth is at most ${limits_1.BLOCK_LIMITS.maxDepth}.`,
package/dist/schema.d.ts CHANGED
@@ -5,6 +5,9 @@ export declare const CALLOUT_STATUSES: readonly ["info", "warning", "alert"];
5
5
  export declare const BADGE_STATUSES: readonly ["info", "error", "warning", "success", "neutral", "active"];
6
6
  export declare const METRIC_TRENDS: readonly ["up", "down", "flat"];
7
7
  export declare const LAYOUT_BLOCK_TYPES: readonly ["columns", "card"];
8
+ export declare const STEPPER_ROUNDING: readonly ["nearest", "up"];
9
+ /** The block types a copy action's `target` may name. */
10
+ export declare const COPY_TARGET_TYPES: readonly ["stepper", "checklist", "list", "table", "text"];
8
11
  export interface HeadingBlock {
9
12
  id?: string;
10
13
  size?: (typeof HEADING_SIZES)[number];
@@ -103,7 +106,18 @@ export interface CallbackAction {
103
106
  name: string;
104
107
  payload?: Record<string, unknown>;
105
108
  }
106
- export type BlockAction = ReplyAction | OpenAction | SelectAction | CallbackAction;
109
+ /**
110
+ * Copies text to the device clipboard. Sets exactly one of `text` (copied as written) or
111
+ * `target` (the id of a block whose current contents `blockPlainText` turns into text).
112
+ * It never reaches the host.
113
+ */
114
+ export interface CopyAction {
115
+ kind: "copy";
116
+ /** The id of a `stepper`, `checklist`, `list`, `table`, or `text` block in this document. */
117
+ target?: string;
118
+ text?: string;
119
+ }
120
+ export type BlockAction = ReplyAction | OpenAction | SelectAction | CallbackAction | CopyAction;
107
121
  export interface ButtonElement {
108
122
  action: BlockAction;
109
123
  iconName?: string;
@@ -151,6 +165,101 @@ export interface DetailsBlock {
151
165
  title: string;
152
166
  type: "details";
153
167
  }
168
+ export interface StepperItem {
169
+ amount: number;
170
+ /** Digits after the decimal point, 0 to 3. Default 0. */
171
+ decimals?: number;
172
+ label: string;
173
+ /** How a scaled amount is rounded to `decimals`. Default `nearest`. */
174
+ round?: (typeof STEPPER_ROUNDING)[number];
175
+ unit?: string;
176
+ }
177
+ /** The host callback that − and + call with `{...payload, value}`. */
178
+ export interface StepperCallback {
179
+ name: string;
180
+ payload?: Record<string, unknown>;
181
+ }
182
+ /**
183
+ * A − value + control whose buttons call a host callback. The renderer reserves the element ids
184
+ * `<id>_decrease` and `<id>_increase`.
185
+ */
186
+ export interface StepperBlock {
187
+ callback: StepperCallback;
188
+ id: string;
189
+ items?: StepperItem[];
190
+ itemsTitle?: string;
191
+ label: string;
192
+ max: number;
193
+ min: number;
194
+ note?: string;
195
+ /** Default 1. */
196
+ step?: number;
197
+ type: "stepper";
198
+ unit?: string;
199
+ value: number;
200
+ }
201
+ export interface ChecklistItem {
202
+ checked?: boolean;
203
+ /** Muted line under the text and meta. */
204
+ detail?: string;
205
+ /** Unique within the checklist. The tick's element id is `<checklist id>_<item id>`. */
206
+ id: string;
207
+ /** A short label, such as a time, shown small and muted under the bold text. */
208
+ meta?: string;
209
+ text: string;
210
+ }
211
+ /**
212
+ * The host callback a tick sends with `{...payload, itemId, checked, state}`. Without one, or
213
+ * when its name is not a registered host action, ticks stay on the device.
214
+ */
215
+ export interface ChecklistCallback {
216
+ name: string;
217
+ payload?: Record<string, unknown>;
218
+ }
219
+ /**
220
+ * A list of tickable items with an "n of m" counter. The renderer reserves the element id
221
+ * `<id>_<item id>` for each item's tick.
222
+ */
223
+ export interface ChecklistBlock {
224
+ callback?: ChecklistCallback;
225
+ id: string;
226
+ items: ChecklistItem[];
227
+ title?: string;
228
+ type: "checklist";
229
+ }
230
+ export interface GalleryImage {
231
+ alt: string;
232
+ caption?: string;
233
+ /** Follows the `image` src rules, including `IMAGE_HOST_NOT_ALLOWED`. */
234
+ src: string;
235
+ }
236
+ /** A row of 2 to 6 photos with optional captions. */
237
+ export interface GalleryBlock {
238
+ id?: string;
239
+ images: GalleryImage[];
240
+ type: "gallery";
241
+ }
242
+ /** An optional thumbnail on a list item. Follows the `image` src and alt rules. */
243
+ export interface ListItemImage {
244
+ /** At most `headingTextMaxLength` characters, the same cap as an `image` block's alt. */
245
+ alt: string;
246
+ /** Follows the `image` src rules, including `IMAGE_HOST_NOT_ALLOWED`. */
247
+ src: string;
248
+ }
249
+ export interface ListItem {
250
+ image?: ListItemImage;
251
+ /** A short label, such as a time or a price. */
252
+ meta?: string;
253
+ /** Plain text, not markdown. */
254
+ text?: string;
255
+ title: string;
256
+ }
257
+ /** A stack of 1 to 12 rows, each with a title and optional text, meta, and thumbnail. */
258
+ export interface ListBlock {
259
+ id?: string;
260
+ items: ListItem[];
261
+ type: "list";
262
+ }
154
263
  export interface ColumnsBlock {
155
264
  children: Block[];
156
265
  id?: string;
@@ -158,11 +267,13 @@ export interface ColumnsBlock {
158
267
  }
159
268
  export interface CardBlock {
160
269
  children: Block[];
270
+ /** A short label shown small and muted above the title. */
271
+ eyebrow?: string;
161
272
  id?: string;
162
273
  title?: string;
163
274
  type: "card";
164
275
  }
165
- export type LeafBlock = HeadingBlock | TextBlock | MetricBlock | BadgeBlock | DividerBlock | ContextBlock | ChartBlock | TableBlock | ActionsBlock | HtmlBlock | CalloutBlock | ImageBlock | DetailsBlock;
276
+ export type LeafBlock = HeadingBlock | TextBlock | MetricBlock | BadgeBlock | DividerBlock | ContextBlock | ChartBlock | TableBlock | ActionsBlock | HtmlBlock | CalloutBlock | ImageBlock | DetailsBlock | StepperBlock | ChecklistBlock | GalleryBlock | ListBlock;
166
277
  export type Block = LeafBlock | ColumnsBlock | CardBlock;
167
278
  export interface BlocksDocument {
168
279
  blocks: Block[];
package/dist/schema.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wrapAsTextDocument = exports.blocksSchema = exports.BUTTON_VARIANTS = exports.DATASET_GRAINS = exports.DATASET_COLUMN_TYPES = exports.CHART_HEIGHTS = exports.CHART_KINDS = exports.LAYOUT_BLOCK_TYPES = exports.METRIC_TRENDS = exports.BADGE_STATUSES = exports.CALLOUT_STATUSES = exports.HTML_HEIGHTS = exports.HEADING_SIZES = void 0;
3
+ exports.wrapAsTextDocument = exports.blocksSchema = exports.BUTTON_VARIANTS = exports.DATASET_GRAINS = exports.DATASET_COLUMN_TYPES = exports.CHART_HEIGHTS = exports.CHART_KINDS = exports.COPY_TARGET_TYPES = exports.STEPPER_ROUNDING = exports.LAYOUT_BLOCK_TYPES = exports.METRIC_TRENDS = exports.BADGE_STATUSES = exports.CALLOUT_STATUSES = exports.HTML_HEIGHTS = exports.HEADING_SIZES = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const limits_1 = require("./limits");
6
6
  exports.HEADING_SIZES = ["sm", "md", "lg", "xl", "2xl"];
@@ -9,6 +9,9 @@ exports.CALLOUT_STATUSES = ["info", "warning", "alert"];
9
9
  exports.BADGE_STATUSES = ["info", "error", "warning", "success", "neutral", "active"];
10
10
  exports.METRIC_TRENDS = ["up", "down", "flat"];
11
11
  exports.LAYOUT_BLOCK_TYPES = ["columns", "card"];
12
+ exports.STEPPER_ROUNDING = ["nearest", "up"];
13
+ /** The block types a copy action's `target` may name. */
14
+ exports.COPY_TARGET_TYPES = ["stepper", "checklist", "list", "table", "text"];
12
15
  const visibleText = (maxLength) => zod_1.z
13
16
  .string()
14
17
  .min(1)
@@ -128,11 +131,20 @@ const selectActionSchema = zod_1.z
128
131
  target: zod_1.z.string().min(1).max(64),
129
132
  })
130
133
  .strict();
134
+ const callbackNameSchema = zod_1.z.string().regex(/^[a-z][A-Za-z0-9_]{0,63}$/);
135
+ const callbackPayloadSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
131
136
  const callbackActionSchema = zod_1.z
132
137
  .object({
133
138
  kind: zod_1.z.literal("callback"),
134
- name: zod_1.z.string().regex(/^[a-z][A-Za-z0-9_]{0,63}$/),
135
- payload: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
139
+ name: callbackNameSchema,
140
+ payload: callbackPayloadSchema.optional(),
141
+ })
142
+ .strict();
143
+ const copyActionSchema = zod_1.z
144
+ .object({
145
+ kind: zod_1.z.literal("copy"),
146
+ target: zod_1.z.string().min(1).max(64).optional(),
147
+ text: visibleText(limits_1.BLOCK_LIMITS.copyTextMaxLength).optional(),
136
148
  })
137
149
  .strict();
138
150
  const actionSchema = zod_1.z.discriminatedUnion("kind", [
@@ -140,6 +152,7 @@ const actionSchema = zod_1.z.discriminatedUnion("kind", [
140
152
  openActionSchema,
141
153
  selectActionSchema,
142
154
  callbackActionSchema,
155
+ copyActionSchema,
143
156
  ]);
144
157
  const buttonElementSchema = zod_1.z
145
158
  .object({
@@ -184,14 +197,55 @@ const calloutSchema = zod_1.z
184
197
  type: zod_1.z.literal("callout"),
185
198
  })
186
199
  .strict();
200
+ const imageSrcSchema = zod_1.z.string().min(1).max(limits_1.BLOCK_LIMITS.htmlMaxBytes);
201
+ /** The image block's alt cap, shared by list item thumbnails. */
202
+ const imageAltSchema = visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength);
187
203
  const imageSchema = zod_1.z
188
204
  .object({
189
205
  ...sharedBlockFields,
190
- alt: visibleText(limits_1.BLOCK_LIMITS.headingTextMaxLength),
191
- src: zod_1.z.string().min(1).max(limits_1.BLOCK_LIMITS.htmlMaxBytes),
206
+ alt: imageAltSchema,
207
+ src: imageSrcSchema,
192
208
  type: zod_1.z.literal("image"),
193
209
  })
194
210
  .strict();
211
+ const galleryImageSchema = zod_1.z
212
+ .object({
213
+ alt: visibleText(limits_1.BLOCK_LIMITS.galleryAltMaxLength),
214
+ caption: visibleText(limits_1.BLOCK_LIMITS.galleryCaptionMaxLength).optional(),
215
+ src: imageSrcSchema,
216
+ })
217
+ .strict();
218
+ const gallerySchema = zod_1.z
219
+ .object({
220
+ ...sharedBlockFields,
221
+ images: zod_1.z
222
+ .array(galleryImageSchema)
223
+ .min(limits_1.BLOCK_LIMITS.galleryImagesMin)
224
+ .max(limits_1.BLOCK_LIMITS.galleryImagesMax),
225
+ type: zod_1.z.literal("gallery"),
226
+ })
227
+ .strict();
228
+ const listItemSchema = zod_1.z
229
+ .object({
230
+ image: zod_1.z
231
+ .object({
232
+ alt: imageAltSchema,
233
+ src: imageSrcSchema,
234
+ })
235
+ .strict()
236
+ .optional(),
237
+ meta: visibleText(limits_1.BLOCK_LIMITS.listItemMetaMaxLength).optional(),
238
+ text: visibleText(limits_1.BLOCK_LIMITS.listItemTextMaxLength).optional(),
239
+ title: visibleText(limits_1.BLOCK_LIMITS.listItemTitleMaxLength),
240
+ })
241
+ .strict();
242
+ const listSchema = zod_1.z
243
+ .object({
244
+ ...sharedBlockFields,
245
+ items: zod_1.z.array(listItemSchema).min(limits_1.BLOCK_LIMITS.listItemsMin).max(limits_1.BLOCK_LIMITS.listItemsMax),
246
+ type: zod_1.z.literal("list"),
247
+ })
248
+ .strict();
195
249
  const detailsSchema = zod_1.z
196
250
  .object({
197
251
  ...sharedBlockFields,
@@ -200,6 +254,63 @@ const detailsSchema = zod_1.z
200
254
  type: zod_1.z.literal("details"),
201
255
  })
202
256
  .strict();
257
+ const stepperItemSchema = zod_1.z
258
+ .object({
259
+ amount: zod_1.z.number().finite(),
260
+ decimals: zod_1.z.number().int().min(0).max(limits_1.BLOCK_LIMITS.stepperDecimalsMax).optional(),
261
+ label: visibleText(limits_1.BLOCK_LIMITS.stepperLabelMaxLength),
262
+ round: zod_1.z.enum(exports.STEPPER_ROUNDING).optional(),
263
+ unit: visibleText(limits_1.BLOCK_LIMITS.stepperItemUnitMaxLength).optional(),
264
+ })
265
+ .strict();
266
+ const stepperSchema = zod_1.z
267
+ .object({
268
+ callback: zod_1.z
269
+ .object({
270
+ name: callbackNameSchema,
271
+ payload: callbackPayloadSchema.optional(),
272
+ })
273
+ .strict(),
274
+ id: blockIdSchema.max(limits_1.BLOCK_LIMITS.stepperIdMaxLength),
275
+ items: zod_1.z.array(stepperItemSchema).max(limits_1.BLOCK_LIMITS.stepperItemsMax).optional(),
276
+ itemsTitle: visibleText(limits_1.BLOCK_LIMITS.stepperLabelMaxLength).optional(),
277
+ label: visibleText(limits_1.BLOCK_LIMITS.stepperLabelMaxLength),
278
+ max: zod_1.z.number().finite(),
279
+ min: zod_1.z.number().finite(),
280
+ note: visibleText(limits_1.BLOCK_LIMITS.stepperNoteMaxLength).optional(),
281
+ step: zod_1.z.number().finite().optional(),
282
+ type: zod_1.z.literal("stepper"),
283
+ unit: visibleText(limits_1.BLOCK_LIMITS.stepperUnitMaxLength).optional(),
284
+ value: zod_1.z.number().finite(),
285
+ })
286
+ .strict();
287
+ const checklistItemSchema = zod_1.z
288
+ .object({
289
+ checked: zod_1.z.boolean().optional(),
290
+ detail: visibleText(limits_1.BLOCK_LIMITS.checklistItemDetailMaxLength).optional(),
291
+ id: blockIdSchema.max(limits_1.BLOCK_LIMITS.checklistItemIdMaxLength),
292
+ meta: visibleText(limits_1.BLOCK_LIMITS.checklistItemMetaMaxLength).optional(),
293
+ text: visibleText(limits_1.BLOCK_LIMITS.checklistItemTextMaxLength),
294
+ })
295
+ .strict();
296
+ const checklistSchema = zod_1.z
297
+ .object({
298
+ callback: zod_1.z
299
+ .object({
300
+ name: callbackNameSchema,
301
+ payload: callbackPayloadSchema.optional(),
302
+ })
303
+ .strict()
304
+ .optional(),
305
+ id: blockIdSchema.max(limits_1.BLOCK_LIMITS.checklistIdMaxLength),
306
+ items: zod_1.z
307
+ .array(checklistItemSchema)
308
+ .min(limits_1.BLOCK_LIMITS.checklistItemsMin)
309
+ .max(limits_1.BLOCK_LIMITS.checklistItemsMax),
310
+ title: visibleText(limits_1.BLOCK_LIMITS.checklistTitleMaxLength).optional(),
311
+ type: zod_1.z.literal("checklist"),
312
+ })
313
+ .strict();
203
314
  const actionsSchema = zod_1.z
204
315
  .object({
205
316
  elements: zod_1.z
@@ -224,6 +335,10 @@ const blockSchema = zod_1.z.lazy(() => zod_1.z.discriminatedUnion("type", [
224
335
  calloutSchema,
225
336
  imageSchema,
226
337
  detailsSchema,
338
+ stepperSchema,
339
+ checklistSchema,
340
+ gallerySchema,
341
+ listSchema,
227
342
  zod_1.z
228
343
  .object({
229
344
  ...sharedBlockFields,
@@ -235,6 +350,7 @@ const blockSchema = zod_1.z.lazy(() => zod_1.z.discriminatedUnion("type", [
235
350
  .object({
236
351
  ...sharedBlockFields,
237
352
  children: zod_1.z.array(blockSchema).min(1).max(limits_1.BLOCK_LIMITS.maxBlocks),
353
+ eyebrow: visibleText(limits_1.BLOCK_LIMITS.cardEyebrowMaxLength).optional(),
238
354
  title: visibleText(limits_1.BLOCK_LIMITS.cardTitleMaxLength).optional(),
239
355
  type: zod_1.z.literal("card"),
240
356
  })
package/dist/validate.js CHANGED
@@ -182,6 +182,16 @@ const mapIssue = (issue, root) => {
182
182
  }),
183
183
  ];
184
184
  }
185
+ if (issue.origin === "number" || issue.origin === "int") {
186
+ return [
187
+ error({
188
+ code: "OUT_OF_RANGE",
189
+ fix: `Set ${subject(path)} to ${maximum} or less.`,
190
+ message: `${subject(path)} is above ${maximum}.`,
191
+ path,
192
+ }),
193
+ ];
194
+ }
185
195
  if (issue.origin === "array") {
186
196
  return [
187
197
  error({
@@ -202,7 +212,21 @@ const mapIssue = (issue, root) => {
202
212
  ];
203
213
  }
204
214
  if (issue.code === "too_small") {
205
- if (issue.origin === "number" || issue.origin === "int") {
215
+ const isNumber = issue.origin === "number" || issue.origin === "int";
216
+ // A ref `limit` below 1 has always been INVALID_TYPE; every other numeric floor matches the
217
+ // OUT_OF_RANGE code its ceiling uses.
218
+ if (isNumber && !/^datasets\.[^.]+\.limit$/.test(path)) {
219
+ const minimum = String(issue.minimum);
220
+ return [
221
+ error({
222
+ code: "OUT_OF_RANGE",
223
+ fix: `Set ${subject(path)} to ${minimum} or more.`,
224
+ message: `${subject(path)} is below ${minimum}.`,
225
+ path,
226
+ }),
227
+ ];
228
+ }
229
+ if (isNumber) {
206
230
  return [
207
231
  error({
208
232
  code: "INVALID_TYPE",
@@ -262,7 +286,7 @@ const mapIssue = (issue, root) => {
262
286
  return [
263
287
  error({
264
288
  code: "INVALID_ENUM",
265
- fix: "Set type to heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details, or html.",
289
+ fix: "Set type to heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details, stepper, checklist, gallery, list, or html.",
266
290
  message: `${subject(path)} is not a supported block.`,
267
291
  path: path === "" ? "type" : `${path}.type`,
268
292
  }),
package/package.json CHANGED
@@ -56,5 +56,5 @@
56
56
  "test:coverage": "bun run ../scripts/check-coverage.ts"
57
57
  },
58
58
  "types": "dist/index.d.ts",
59
- "version": "57.11.0"
59
+ "version": "57.13.0"
60
60
  }