@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.
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
package/src/schema.ts CHANGED
@@ -8,6 +8,9 @@ export const CALLOUT_STATUSES = ["info", "warning", "alert"] as const;
8
8
  export const BADGE_STATUSES = ["info", "error", "warning", "success", "neutral", "active"] as const;
9
9
  export const METRIC_TRENDS = ["up", "down", "flat"] as const;
10
10
  export const LAYOUT_BLOCK_TYPES = ["columns", "card"] as const;
11
+ export const STEPPER_ROUNDING = ["nearest", "up"] as const;
12
+ /** The block types a copy action's `target` may name. */
13
+ export const COPY_TARGET_TYPES = ["stepper", "checklist", "list", "table", "text"] as const;
11
14
 
12
15
  const visibleText = (maxLength: number): z.ZodString =>
13
16
  z
@@ -141,7 +144,19 @@ export interface CallbackAction {
141
144
  payload?: Record<string, unknown>;
142
145
  }
143
146
 
144
- export type BlockAction = ReplyAction | OpenAction | SelectAction | CallbackAction;
147
+ /**
148
+ * Copies text to the device clipboard. Sets exactly one of `text` (copied as written) or
149
+ * `target` (the id of a block whose current contents `blockPlainText` turns into text).
150
+ * It never reaches the host.
151
+ */
152
+ export interface CopyAction {
153
+ kind: "copy";
154
+ /** The id of a `stepper`, `checklist`, `list`, `table`, or `text` block in this document. */
155
+ target?: string;
156
+ text?: string;
157
+ }
158
+
159
+ export type BlockAction = ReplyAction | OpenAction | SelectAction | CallbackAction | CopyAction;
145
160
 
146
161
  export interface ButtonElement {
147
162
  action: BlockAction;
@@ -194,6 +209,112 @@ export interface DetailsBlock {
194
209
  type: "details";
195
210
  }
196
211
 
212
+ export interface StepperItem {
213
+ amount: number;
214
+ /** Digits after the decimal point, 0 to 3. Default 0. */
215
+ decimals?: number;
216
+ label: string;
217
+ /** How a scaled amount is rounded to `decimals`. Default `nearest`. */
218
+ round?: (typeof STEPPER_ROUNDING)[number];
219
+ unit?: string;
220
+ }
221
+
222
+ /** The host callback that − and + call with `{...payload, value}`. */
223
+ export interface StepperCallback {
224
+ name: string;
225
+ payload?: Record<string, unknown>;
226
+ }
227
+
228
+ /**
229
+ * A − value + control whose buttons call a host callback. The renderer reserves the element ids
230
+ * `<id>_decrease` and `<id>_increase`.
231
+ */
232
+ export interface StepperBlock {
233
+ callback: StepperCallback;
234
+ id: string;
235
+ items?: StepperItem[];
236
+ itemsTitle?: string;
237
+ label: string;
238
+ max: number;
239
+ min: number;
240
+ note?: string;
241
+ /** Default 1. */
242
+ step?: number;
243
+ type: "stepper";
244
+ unit?: string;
245
+ value: number;
246
+ }
247
+
248
+ export interface ChecklistItem {
249
+ checked?: boolean;
250
+ /** Muted line under the text and meta. */
251
+ detail?: string;
252
+ /** Unique within the checklist. The tick's element id is `<checklist id>_<item id>`. */
253
+ id: string;
254
+ /** A short label, such as a time, shown small and muted under the bold text. */
255
+ meta?: string;
256
+ text: string;
257
+ }
258
+
259
+ /**
260
+ * The host callback a tick sends with `{...payload, itemId, checked, state}`. Without one, or
261
+ * when its name is not a registered host action, ticks stay on the device.
262
+ */
263
+ export interface ChecklistCallback {
264
+ name: string;
265
+ payload?: Record<string, unknown>;
266
+ }
267
+
268
+ /**
269
+ * A list of tickable items with an "n of m" counter. The renderer reserves the element id
270
+ * `<id>_<item id>` for each item's tick.
271
+ */
272
+ export interface ChecklistBlock {
273
+ callback?: ChecklistCallback;
274
+ id: string;
275
+ items: ChecklistItem[];
276
+ title?: string;
277
+ type: "checklist";
278
+ }
279
+
280
+ export interface GalleryImage {
281
+ alt: string;
282
+ caption?: string;
283
+ /** Follows the `image` src rules, including `IMAGE_HOST_NOT_ALLOWED`. */
284
+ src: string;
285
+ }
286
+
287
+ /** A row of 2 to 6 photos with optional captions. */
288
+ export interface GalleryBlock {
289
+ id?: string;
290
+ images: GalleryImage[];
291
+ type: "gallery";
292
+ }
293
+
294
+ /** An optional thumbnail on a list item. Follows the `image` src and alt rules. */
295
+ export interface ListItemImage {
296
+ /** At most `headingTextMaxLength` characters, the same cap as an `image` block's alt. */
297
+ alt: string;
298
+ /** Follows the `image` src rules, including `IMAGE_HOST_NOT_ALLOWED`. */
299
+ src: string;
300
+ }
301
+
302
+ export interface ListItem {
303
+ image?: ListItemImage;
304
+ /** A short label, such as a time or a price. */
305
+ meta?: string;
306
+ /** Plain text, not markdown. */
307
+ text?: string;
308
+ title: string;
309
+ }
310
+
311
+ /** A stack of 1 to 12 rows, each with a title and optional text, meta, and thumbnail. */
312
+ export interface ListBlock {
313
+ id?: string;
314
+ items: ListItem[];
315
+ type: "list";
316
+ }
317
+
197
318
  export interface ColumnsBlock {
198
319
  children: Block[];
199
320
  id?: string;
@@ -202,6 +323,8 @@ export interface ColumnsBlock {
202
323
 
203
324
  export interface CardBlock {
204
325
  children: Block[];
326
+ /** A short label shown small and muted above the title. */
327
+ eyebrow?: string;
205
328
  id?: string;
206
329
  title?: string;
207
330
  type: "card";
@@ -220,7 +343,11 @@ export type LeafBlock =
220
343
  | HtmlBlock
221
344
  | CalloutBlock
222
345
  | ImageBlock
223
- | DetailsBlock;
346
+ | DetailsBlock
347
+ | StepperBlock
348
+ | ChecklistBlock
349
+ | GalleryBlock
350
+ | ListBlock;
224
351
 
225
352
  export type Block = LeafBlock | ColumnsBlock | CardBlock;
226
353
 
@@ -342,11 +469,23 @@ const selectActionSchema = z
342
469
  })
343
470
  .strict();
344
471
 
472
+ const callbackNameSchema = z.string().regex(/^[a-z][A-Za-z0-9_]{0,63}$/);
473
+
474
+ const callbackPayloadSchema = z.record(z.string(), z.unknown());
475
+
345
476
  const callbackActionSchema = z
346
477
  .object({
347
478
  kind: z.literal("callback"),
348
- name: z.string().regex(/^[a-z][A-Za-z0-9_]{0,63}$/),
349
- payload: z.record(z.string(), z.unknown()).optional(),
479
+ name: callbackNameSchema,
480
+ payload: callbackPayloadSchema.optional(),
481
+ })
482
+ .strict();
483
+
484
+ const copyActionSchema = z
485
+ .object({
486
+ kind: z.literal("copy"),
487
+ target: z.string().min(1).max(64).optional(),
488
+ text: visibleText(BLOCK_LIMITS.copyTextMaxLength).optional(),
350
489
  })
351
490
  .strict();
352
491
 
@@ -355,6 +494,7 @@ const actionSchema = z.discriminatedUnion("kind", [
355
494
  openActionSchema,
356
495
  selectActionSchema,
357
496
  callbackActionSchema,
497
+ copyActionSchema,
358
498
  ]);
359
499
 
360
500
  const buttonElementSchema = z
@@ -406,15 +546,62 @@ const calloutSchema = z
406
546
  })
407
547
  .strict();
408
548
 
549
+ const imageSrcSchema = z.string().min(1).max(BLOCK_LIMITS.htmlMaxBytes);
550
+
551
+ /** The image block's alt cap, shared by list item thumbnails. */
552
+ const imageAltSchema = visibleText(BLOCK_LIMITS.headingTextMaxLength);
553
+
409
554
  const imageSchema = z
410
555
  .object({
411
556
  ...sharedBlockFields,
412
- alt: visibleText(BLOCK_LIMITS.headingTextMaxLength),
413
- src: z.string().min(1).max(BLOCK_LIMITS.htmlMaxBytes),
557
+ alt: imageAltSchema,
558
+ src: imageSrcSchema,
414
559
  type: z.literal("image"),
415
560
  })
416
561
  .strict();
417
562
 
563
+ const galleryImageSchema = z
564
+ .object({
565
+ alt: visibleText(BLOCK_LIMITS.galleryAltMaxLength),
566
+ caption: visibleText(BLOCK_LIMITS.galleryCaptionMaxLength).optional(),
567
+ src: imageSrcSchema,
568
+ })
569
+ .strict();
570
+
571
+ const gallerySchema = z
572
+ .object({
573
+ ...sharedBlockFields,
574
+ images: z
575
+ .array(galleryImageSchema)
576
+ .min(BLOCK_LIMITS.galleryImagesMin)
577
+ .max(BLOCK_LIMITS.galleryImagesMax),
578
+ type: z.literal("gallery"),
579
+ })
580
+ .strict();
581
+
582
+ const listItemSchema = z
583
+ .object({
584
+ image: z
585
+ .object({
586
+ alt: imageAltSchema,
587
+ src: imageSrcSchema,
588
+ })
589
+ .strict()
590
+ .optional(),
591
+ meta: visibleText(BLOCK_LIMITS.listItemMetaMaxLength).optional(),
592
+ text: visibleText(BLOCK_LIMITS.listItemTextMaxLength).optional(),
593
+ title: visibleText(BLOCK_LIMITS.listItemTitleMaxLength),
594
+ })
595
+ .strict();
596
+
597
+ const listSchema = z
598
+ .object({
599
+ ...sharedBlockFields,
600
+ items: z.array(listItemSchema).min(BLOCK_LIMITS.listItemsMin).max(BLOCK_LIMITS.listItemsMax),
601
+ type: z.literal("list"),
602
+ })
603
+ .strict();
604
+
418
605
  const detailsSchema = z
419
606
  .object({
420
607
  ...sharedBlockFields,
@@ -424,6 +611,67 @@ const detailsSchema = z
424
611
  })
425
612
  .strict();
426
613
 
614
+ const stepperItemSchema = z
615
+ .object({
616
+ amount: z.number().finite(),
617
+ decimals: z.number().int().min(0).max(BLOCK_LIMITS.stepperDecimalsMax).optional(),
618
+ label: visibleText(BLOCK_LIMITS.stepperLabelMaxLength),
619
+ round: z.enum(STEPPER_ROUNDING).optional(),
620
+ unit: visibleText(BLOCK_LIMITS.stepperItemUnitMaxLength).optional(),
621
+ })
622
+ .strict();
623
+
624
+ const stepperSchema = z
625
+ .object({
626
+ callback: z
627
+ .object({
628
+ name: callbackNameSchema,
629
+ payload: callbackPayloadSchema.optional(),
630
+ })
631
+ .strict(),
632
+ id: blockIdSchema.max(BLOCK_LIMITS.stepperIdMaxLength),
633
+ items: z.array(stepperItemSchema).max(BLOCK_LIMITS.stepperItemsMax).optional(),
634
+ itemsTitle: visibleText(BLOCK_LIMITS.stepperLabelMaxLength).optional(),
635
+ label: visibleText(BLOCK_LIMITS.stepperLabelMaxLength),
636
+ max: z.number().finite(),
637
+ min: z.number().finite(),
638
+ note: visibleText(BLOCK_LIMITS.stepperNoteMaxLength).optional(),
639
+ step: z.number().finite().optional(),
640
+ type: z.literal("stepper"),
641
+ unit: visibleText(BLOCK_LIMITS.stepperUnitMaxLength).optional(),
642
+ value: z.number().finite(),
643
+ })
644
+ .strict();
645
+
646
+ const checklistItemSchema = z
647
+ .object({
648
+ checked: z.boolean().optional(),
649
+ detail: visibleText(BLOCK_LIMITS.checklistItemDetailMaxLength).optional(),
650
+ id: blockIdSchema.max(BLOCK_LIMITS.checklistItemIdMaxLength),
651
+ meta: visibleText(BLOCK_LIMITS.checklistItemMetaMaxLength).optional(),
652
+ text: visibleText(BLOCK_LIMITS.checklistItemTextMaxLength),
653
+ })
654
+ .strict();
655
+
656
+ const checklistSchema = z
657
+ .object({
658
+ callback: z
659
+ .object({
660
+ name: callbackNameSchema,
661
+ payload: callbackPayloadSchema.optional(),
662
+ })
663
+ .strict()
664
+ .optional(),
665
+ id: blockIdSchema.max(BLOCK_LIMITS.checklistIdMaxLength),
666
+ items: z
667
+ .array(checklistItemSchema)
668
+ .min(BLOCK_LIMITS.checklistItemsMin)
669
+ .max(BLOCK_LIMITS.checklistItemsMax),
670
+ title: visibleText(BLOCK_LIMITS.checklistTitleMaxLength).optional(),
671
+ type: z.literal("checklist"),
672
+ })
673
+ .strict();
674
+
427
675
  const actionsSchema = z
428
676
  .object({
429
677
  elements: z
@@ -450,6 +698,10 @@ const blockSchema: z.ZodType<Block> = z.lazy(() =>
450
698
  calloutSchema,
451
699
  imageSchema,
452
700
  detailsSchema,
701
+ stepperSchema,
702
+ checklistSchema,
703
+ gallerySchema,
704
+ listSchema,
453
705
  z
454
706
  .object({
455
707
  ...sharedBlockFields,
@@ -461,6 +713,7 @@ const blockSchema: z.ZodType<Block> = z.lazy(() =>
461
713
  .object({
462
714
  ...sharedBlockFields,
463
715
  children: z.array(blockSchema).min(1).max(BLOCK_LIMITS.maxBlocks),
716
+ eyebrow: visibleText(BLOCK_LIMITS.cardEyebrowMaxLength).optional(),
464
717
  title: visibleText(BLOCK_LIMITS.cardTitleMaxLength).optional(),
465
718
  type: z.literal("card"),
466
719
  })
package/src/validate.ts CHANGED
@@ -218,6 +218,16 @@ const mapIssue = (issue: z.core.$ZodIssue, root: unknown): BlockError[] => {
218
218
  }),
219
219
  ];
220
220
  }
221
+ if (issue.origin === "number" || issue.origin === "int") {
222
+ return [
223
+ error({
224
+ code: "OUT_OF_RANGE",
225
+ fix: `Set ${subject(path)} to ${maximum} or less.`,
226
+ message: `${subject(path)} is above ${maximum}.`,
227
+ path,
228
+ }),
229
+ ];
230
+ }
221
231
  if (issue.origin === "array") {
222
232
  return [
223
233
  error({
@@ -238,7 +248,21 @@ const mapIssue = (issue: z.core.$ZodIssue, root: unknown): BlockError[] => {
238
248
  ];
239
249
  }
240
250
  if (issue.code === "too_small") {
241
- if (issue.origin === "number" || issue.origin === "int") {
251
+ const isNumber = issue.origin === "number" || issue.origin === "int";
252
+ // A ref `limit` below 1 has always been INVALID_TYPE; every other numeric floor matches the
253
+ // OUT_OF_RANGE code its ceiling uses.
254
+ if (isNumber && !/^datasets\.[^.]+\.limit$/.test(path)) {
255
+ const minimum = String(issue.minimum);
256
+ return [
257
+ error({
258
+ code: "OUT_OF_RANGE",
259
+ fix: `Set ${subject(path)} to ${minimum} or more.`,
260
+ message: `${subject(path)} is below ${minimum}.`,
261
+ path,
262
+ }),
263
+ ];
264
+ }
265
+ if (isNumber) {
242
266
  return [
243
267
  error({
244
268
  code: "INVALID_TYPE",
@@ -298,7 +322,7 @@ const mapIssue = (issue: z.core.$ZodIssue, root: unknown): BlockError[] => {
298
322
  return [
299
323
  error({
300
324
  code: "INVALID_ENUM",
301
- fix: "Set type to heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details, or html.",
325
+ fix: "Set type to heading, text, metric, badge, divider, context, chart, table, actions, columns, card, callout, image, details, stepper, checklist, gallery, list, or html.",
302
326
  message: `${subject(path)} is not a supported block.`,
303
327
  path: path === "" ? "type" : `${path}.type`,
304
328
  }),