@terreno/blocks 57.12.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.
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/index.d.ts +7 -2
- package/dist/index.js +13 -1
- package/dist/interactive.d.ts +26 -0
- package/dist/interactive.js +62 -0
- package/dist/limits.d.ts +26 -0
- package/dist/limits.js +26 -0
- package/dist/lint.d.ts +18 -1
- package/dist/lint.js +186 -26
- package/dist/plainText.d.ts +17 -0
- package/dist/plainText.js +56 -0
- package/dist/prompt.d.ts +10 -1
- package/dist/prompt.js +73 -3
- package/dist/schema.d.ts +113 -2
- package/dist/schema.js +121 -5
- package/dist/validate.js +26 -2
- package/package.json +1 -1
- package/src/__snapshots__/prompt.test.ts.snap +530 -0
- package/src/errors.ts +4 -0
- package/src/fixtures/golden/sunday-roast.yaml +174 -0
- package/src/fixtures/invalid/card-eyebrow-empty.yaml +7 -0
- package/src/fixtures/invalid/card-eyebrow-too-long.yaml +7 -0
- package/src/fixtures/invalid/checklist-duplicate-item.yaml +9 -0
- package/src/fixtures/invalid/checklist-item-id-too-long.yaml +7 -0
- package/src/fixtures/invalid/checklist-reserved-id.yaml +16 -0
- package/src/fixtures/invalid/copy-missing-text-and-target.yaml +10 -0
- package/src/fixtures/invalid/copy-target.yaml +14 -0
- package/src/fixtures/invalid/gallery-caption-too-long.yaml +9 -0
- package/src/fixtures/invalid/gallery-image-host.yaml +10 -0
- package/src/fixtures/invalid/gallery-too-few.yaml +6 -0
- package/src/fixtures/invalid/list-image-host.yaml +12 -0
- package/src/fixtures/invalid/list-missing-title.yaml +6 -0
- package/src/fixtures/invalid/list-too-many.yaml +17 -0
- package/src/fixtures/invalid/stepper-decimals.yaml +14 -0
- package/src/fixtures/invalid/stepper-out-of-range.yaml +10 -0
- package/src/fixtures/invalid/stepper-reserved-id.yaml +19 -0
- package/src/fixtures/valid/card-eyebrow.yaml +8 -0
- package/src/fixtures/valid/checklist.yaml +29 -0
- package/src/fixtures/valid/copy.yaml +85 -0
- package/src/fixtures/valid/gallery.yaml +22 -0
- package/src/fixtures/valid/list.yaml +24 -0
- package/src/fixtures/valid/stepper.yaml +35 -0
- package/src/golden.test.ts +234 -0
- package/src/index.test.ts +9 -0
- package/src/index.ts +24 -0
- package/src/interactive.test.ts +141 -0
- package/src/interactive.ts +75 -0
- package/src/limits.ts +26 -0
- package/src/lint.test.ts +672 -0
- package/src/lint.ts +264 -33
- package/src/parse.test.ts +40 -0
- package/src/plainText.test.ts +158 -0
- package/src/plainText.ts +81 -0
- package/src/prompt.test.ts +325 -2
- package/src/prompt.ts +87 -2
- package/src/schema.ts +259 -6
- 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
|
-
?
|
|
54
|
-
:
|
|
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
|
-
|
|
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:
|
|
135
|
-
payload:
|
|
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:
|
|
191
|
-
src:
|
|
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
|
-
|
|
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