@joewinke/jatui 0.36.3 → 0.38.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 (271) hide show
  1. package/dist/artifact-blocks/AttachmentChip.svelte +66 -0
  2. package/dist/artifact-blocks/AttachmentChip.svelte.d.ts +7 -0
  3. package/dist/artifact-blocks/BlockComments.svelte +309 -0
  4. package/dist/artifact-blocks/BlockComments.svelte.d.ts +33 -0
  5. package/dist/artifact-blocks/BlockRenderer.svelte +674 -0
  6. package/dist/artifact-blocks/BlockRenderer.svelte.d.ts +55 -0
  7. package/dist/artifact-blocks/BlockVerdict.svelte +151 -0
  8. package/dist/artifact-blocks/BlockVerdict.svelte.d.ts +13 -0
  9. package/dist/artifact-blocks/BlocksArtifactView.svelte +1616 -0
  10. package/dist/artifact-blocks/BlocksArtifactView.svelte.d.ts +113 -0
  11. package/dist/artifact-blocks/CONTRACT.md +225 -0
  12. package/dist/artifact-blocks/CommentPill.svelte +49 -0
  13. package/dist/artifact-blocks/CommentPill.svelte.d.ts +16 -0
  14. package/dist/artifact-blocks/FoldedRounds.svelte +95 -0
  15. package/dist/artifact-blocks/FoldedRounds.svelte.d.ts +9 -0
  16. package/dist/artifact-blocks/Glyph.svelte +54 -0
  17. package/dist/artifact-blocks/Glyph.svelte.d.ts +10 -0
  18. package/dist/artifact-blocks/HistoryScrubber.svelte +500 -0
  19. package/dist/artifact-blocks/HistoryScrubber.svelte.d.ts +15 -0
  20. package/dist/artifact-blocks/Markdown.svelte +186 -0
  21. package/dist/artifact-blocks/Markdown.svelte.d.ts +30 -0
  22. package/dist/artifact-blocks/NoteComposer.svelte +31 -0
  23. package/dist/artifact-blocks/NoteComposer.svelte.d.ts +8 -0
  24. package/dist/artifact-blocks/PlainComposer.svelte +200 -0
  25. package/dist/artifact-blocks/PlainComposer.svelte.d.ts +15 -0
  26. package/dist/artifact-blocks/SectionTools.svelte +102 -0
  27. package/dist/artifact-blocks/SectionTools.svelte.d.ts +19 -0
  28. package/dist/artifact-blocks/adapter.d.ts +13 -0
  29. package/dist/artifact-blocks/adapter.js +31 -0
  30. package/dist/artifact-blocks/autoSend.d.ts +49 -0
  31. package/dist/artifact-blocks/autoSend.js +103 -0
  32. package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte +557 -0
  33. package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte.d.ts +48 -0
  34. package/dist/artifact-blocks/blocks/actions/block.d.ts +8 -0
  35. package/dist/artifact-blocks/blocks/actions/block.js +74 -0
  36. package/dist/artifact-blocks/blocks/actions/requests.d.ts +62 -0
  37. package/dist/artifact-blocks/blocks/actions/requests.js +26 -0
  38. package/dist/artifact-blocks/blocks/actions/schema.d.ts +168 -0
  39. package/dist/artifact-blocks/blocks/actions/schema.js +253 -0
  40. package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte +779 -0
  41. package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte.d.ts +40 -0
  42. package/dist/artifact-blocks/blocks/callout/block.d.ts +7 -0
  43. package/dist/artifact-blocks/blocks/callout/block.js +39 -0
  44. package/dist/artifact-blocks/blocks/callout/schema.d.ts +72 -0
  45. package/dist/artifact-blocks/blocks/callout/schema.js +115 -0
  46. package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte +875 -0
  47. package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte.d.ts +59 -0
  48. package/dist/artifact-blocks/blocks/chart/block.d.ts +9 -0
  49. package/dist/artifact-blocks/blocks/chart/block.js +70 -0
  50. package/dist/artifact-blocks/blocks/chart/schema.d.ts +171 -0
  51. package/dist/artifact-blocks/blocks/chart/schema.js +498 -0
  52. package/dist/artifact-blocks/blocks/chart/thumb.d.ts +2 -0
  53. package/dist/artifact-blocks/blocks/chart/thumb.js +201 -0
  54. package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte +1048 -0
  55. package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte.d.ts +16 -0
  56. package/dist/artifact-blocks/blocks/checklist/Icon.svelte +105 -0
  57. package/dist/artifact-blocks/blocks/checklist/Icon.svelte.d.ts +17 -0
  58. package/dist/artifact-blocks/blocks/checklist/block.d.ts +10 -0
  59. package/dist/artifact-blocks/blocks/checklist/block.js +63 -0
  60. package/dist/artifact-blocks/blocks/checklist/itemReview.d.ts +16 -0
  61. package/dist/artifact-blocks/blocks/checklist/itemReview.js +63 -0
  62. package/dist/artifact-blocks/blocks/checklist/labels.d.ts +62 -0
  63. package/dist/artifact-blocks/blocks/checklist/labels.js +96 -0
  64. package/dist/artifact-blocks/blocks/checklist/retest.d.ts +43 -0
  65. package/dist/artifact-blocks/blocks/checklist/retest.js +68 -0
  66. package/dist/artifact-blocks/blocks/checklist/schema.d.ts +84 -0
  67. package/dist/artifact-blocks/blocks/checklist/schema.js +91 -0
  68. package/dist/artifact-blocks/blocks/code/CodeBlock.svelte +128 -0
  69. package/dist/artifact-blocks/blocks/code/CodeBlock.svelte.d.ts +12 -0
  70. package/dist/artifact-blocks/blocks/code/block.d.ts +8 -0
  71. package/dist/artifact-blocks/blocks/code/block.js +31 -0
  72. package/dist/artifact-blocks/blocks/code/highlight.d.ts +34 -0
  73. package/dist/artifact-blocks/blocks/code/highlight.js +280 -0
  74. package/dist/artifact-blocks/blocks/code/schema.d.ts +32 -0
  75. package/dist/artifact-blocks/blocks/code/schema.js +39 -0
  76. package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte +290 -0
  77. package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte.d.ts +29 -0
  78. package/dist/artifact-blocks/blocks/collage/block.d.ts +9 -0
  79. package/dist/artifact-blocks/blocks/collage/block.js +29 -0
  80. package/dist/artifact-blocks/blocks/collage/schema.d.ts +52 -0
  81. package/dist/artifact-blocks/blocks/collage/schema.js +86 -0
  82. package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte +484 -0
  83. package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte.d.ts +25 -0
  84. package/dist/artifact-blocks/blocks/compare/block.d.ts +9 -0
  85. package/dist/artifact-blocks/blocks/compare/block.js +32 -0
  86. package/dist/artifact-blocks/blocks/compare/schema.d.ts +74 -0
  87. package/dist/artifact-blocks/blocks/compare/schema.js +148 -0
  88. package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte +392 -0
  89. package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte.d.ts +30 -0
  90. package/dist/artifact-blocks/blocks/diagram/block.d.ts +9 -0
  91. package/dist/artifact-blocks/blocks/diagram/block.js +50 -0
  92. package/dist/artifact-blocks/blocks/diagram/constants.d.ts +48 -0
  93. package/dist/artifact-blocks/blocks/diagram/constants.js +53 -0
  94. package/dist/artifact-blocks/blocks/diagram/schema.d.ts +161 -0
  95. package/dist/artifact-blocks/blocks/diagram/schema.js +621 -0
  96. package/dist/artifact-blocks/blocks/diagram/thumb.d.ts +2 -0
  97. package/dist/artifact-blocks/blocks/diagram/thumb.js +157 -0
  98. package/dist/artifact-blocks/blocks/diagram/types.d.ts +74 -0
  99. package/dist/artifact-blocks/blocks/diagram/types.js +1 -0
  100. package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte +81 -0
  101. package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte.d.ts +10 -0
  102. package/dist/artifact-blocks/blocks/diff/block.d.ts +7 -0
  103. package/dist/artifact-blocks/blocks/diff/block.js +36 -0
  104. package/dist/artifact-blocks/blocks/diff/schema.d.ts +18 -0
  105. package/dist/artifact-blocks/blocks/diff/schema.js +35 -0
  106. package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte +1055 -0
  107. package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte.d.ts +5 -0
  108. package/dist/artifact-blocks/blocks/diff-review/block.d.ts +15 -0
  109. package/dist/artifact-blocks/blocks/diff-review/block.js +64 -0
  110. package/dist/artifact-blocks/blocks/diff-review/decisions.d.ts +57 -0
  111. package/dist/artifact-blocks/blocks/diff-review/decisions.js +80 -0
  112. package/dist/artifact-blocks/blocks/diff-review/schema.d.ts +13 -0
  113. package/dist/artifact-blocks/blocks/diff-review/schema.js +84 -0
  114. package/dist/artifact-blocks/blocks/diff-review/types.d.ts +35 -0
  115. package/dist/artifact-blocks/blocks/diff-review/types.js +1 -0
  116. package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte +145 -0
  117. package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte.d.ts +5 -0
  118. package/dist/artifact-blocks/blocks/explainer/block.d.ts +8 -0
  119. package/dist/artifact-blocks/blocks/explainer/block.js +21 -0
  120. package/dist/artifact-blocks/blocks/explainer/schema.d.ts +26 -0
  121. package/dist/artifact-blocks/blocks/explainer/schema.js +69 -0
  122. package/dist/artifact-blocks/blocks/group-content.d.ts +10 -0
  123. package/dist/artifact-blocks/blocks/group-content.js +16 -0
  124. package/dist/artifact-blocks/blocks/group-data.d.ts +10 -0
  125. package/dist/artifact-blocks/blocks/group-data.js +7 -0
  126. package/dist/artifact-blocks/blocks/group-review.d.ts +10 -0
  127. package/dist/artifact-blocks/blocks/group-review.js +12 -0
  128. package/dist/artifact-blocks/blocks/group-visual.d.ts +17 -0
  129. package/dist/artifact-blocks/blocks/group-visual.js +14 -0
  130. package/dist/artifact-blocks/blocks/map/MapBlock.svelte +413 -0
  131. package/dist/artifact-blocks/blocks/map/MapBlock.svelte.d.ts +53 -0
  132. package/dist/artifact-blocks/blocks/map/block.d.ts +9 -0
  133. package/dist/artifact-blocks/blocks/map/block.js +62 -0
  134. package/dist/artifact-blocks/blocks/map/schema.d.ts +123 -0
  135. package/dist/artifact-blocks/blocks/map/schema.js +113 -0
  136. package/dist/artifact-blocks/blocks/map/thumb.d.ts +2 -0
  137. package/dist/artifact-blocks/blocks/map/thumb.js +69 -0
  138. package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte +33 -0
  139. package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte.d.ts +15 -0
  140. package/dist/artifact-blocks/blocks/markdown/block.d.ts +7 -0
  141. package/dist/artifact-blocks/blocks/markdown/block.js +28 -0
  142. package/dist/artifact-blocks/blocks/markdown/render.d.ts +17 -0
  143. package/dist/artifact-blocks/blocks/markdown/render.js +115 -0
  144. package/dist/artifact-blocks/blocks/markdown/schema.d.ts +10 -0
  145. package/dist/artifact-blocks/blocks/markdown/schema.js +23 -0
  146. package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte +2112 -0
  147. package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte.d.ts +5 -0
  148. package/dist/artifact-blocks/blocks/media-picker/block.d.ts +24 -0
  149. package/dist/artifact-blocks/blocks/media-picker/block.js +137 -0
  150. package/dist/artifact-blocks/blocks/media-picker/choices.d.ts +28 -0
  151. package/dist/artifact-blocks/blocks/media-picker/choices.js +35 -0
  152. package/dist/artifact-blocks/blocks/media-picker/decisions.d.ts +51 -0
  153. package/dist/artifact-blocks/blocks/media-picker/decisions.js +75 -0
  154. package/dist/artifact-blocks/blocks/media-picker/schema.d.ts +7 -0
  155. package/dist/artifact-blocks/blocks/media-picker/schema.js +199 -0
  156. package/dist/artifact-blocks/blocks/media-picker/types.d.ts +92 -0
  157. package/dist/artifact-blocks/blocks/media-picker/types.js +1 -0
  158. package/dist/artifact-blocks/blocks/media-picker/versions.d.ts +51 -0
  159. package/dist/artifact-blocks/blocks/media-picker/versions.js +118 -0
  160. package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte +213 -0
  161. package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte.d.ts +5 -0
  162. package/dist/artifact-blocks/blocks/mockup/block.d.ts +26 -0
  163. package/dist/artifact-blocks/blocks/mockup/block.js +52 -0
  164. package/dist/artifact-blocks/blocks/mockup/doc.d.ts +24 -0
  165. package/dist/artifact-blocks/blocks/mockup/doc.js +44 -0
  166. package/dist/artifact-blocks/blocks/mockup/sandbox.d.ts +21 -0
  167. package/dist/artifact-blocks/blocks/mockup/sandbox.js +27 -0
  168. package/dist/artifact-blocks/blocks/mockup/schema.d.ts +7 -0
  169. package/dist/artifact-blocks/blocks/mockup/schema.js +90 -0
  170. package/dist/artifact-blocks/blocks/mockup/types.d.ts +36 -0
  171. package/dist/artifact-blocks/blocks/mockup/types.js +17 -0
  172. package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte +359 -0
  173. package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte.d.ts +32 -0
  174. package/dist/artifact-blocks/blocks/program/block.d.ts +35 -0
  175. package/dist/artifact-blocks/blocks/program/block.js +68 -0
  176. package/dist/artifact-blocks/blocks/program/schema.d.ts +132 -0
  177. package/dist/artifact-blocks/blocks/program/schema.js +268 -0
  178. package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte +315 -0
  179. package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte.d.ts +24 -0
  180. package/dist/artifact-blocks/blocks/stats/block.d.ts +7 -0
  181. package/dist/artifact-blocks/blocks/stats/block.js +49 -0
  182. package/dist/artifact-blocks/blocks/stats/schema.d.ts +82 -0
  183. package/dist/artifact-blocks/blocks/stats/schema.js +43 -0
  184. package/dist/artifact-blocks/blocks/stats/thumb.d.ts +2 -0
  185. package/dist/artifact-blocks/blocks/stats/thumb.js +102 -0
  186. package/dist/artifact-blocks/blocks/table/TableBlock.svelte +490 -0
  187. package/dist/artifact-blocks/blocks/table/TableBlock.svelte.d.ts +28 -0
  188. package/dist/artifact-blocks/blocks/table/block.d.ts +9 -0
  189. package/dist/artifact-blocks/blocks/table/block.js +100 -0
  190. package/dist/artifact-blocks/blocks/table/schema.d.ts +143 -0
  191. package/dist/artifact-blocks/blocks/table/schema.js +278 -0
  192. package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte +347 -0
  193. package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte.d.ts +42 -0
  194. package/dist/artifact-blocks/blocks/timeline/block.d.ts +9 -0
  195. package/dist/artifact-blocks/blocks/timeline/block.js +96 -0
  196. package/dist/artifact-blocks/blocks/timeline/constants.d.ts +26 -0
  197. package/dist/artifact-blocks/blocks/timeline/constants.js +27 -0
  198. package/dist/artifact-blocks/blocks/timeline/schema.d.ts +102 -0
  199. package/dist/artifact-blocks/blocks/timeline/schema.js +405 -0
  200. package/dist/artifact-blocks/blocks/timeline/thumb.d.ts +2 -0
  201. package/dist/artifact-blocks/blocks/timeline/thumb.js +117 -0
  202. package/dist/artifact-blocks/blocks/timeline/types.d.ts +66 -0
  203. package/dist/artifact-blocks/blocks/timeline/types.js +1 -0
  204. package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte +169 -0
  205. package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte.d.ts +26 -0
  206. package/dist/artifact-blocks/blocks/transcript/block.d.ts +9 -0
  207. package/dist/artifact-blocks/blocks/transcript/block.js +86 -0
  208. package/dist/artifact-blocks/blocks/transcript/schema.d.ts +114 -0
  209. package/dist/artifact-blocks/blocks/transcript/schema.js +126 -0
  210. package/dist/artifact-blocks/builtins.d.ts +40 -0
  211. package/dist/artifact-blocks/builtins.js +22 -0
  212. package/dist/artifact-blocks/contractBlocks.d.ts +92 -0
  213. package/dist/artifact-blocks/contractBlocks.js +279 -0
  214. package/dist/artifact-blocks/history.d.ts +140 -0
  215. package/dist/artifact-blocks/history.js +363 -0
  216. package/dist/artifact-blocks/index.d.ts +40 -0
  217. package/dist/artifact-blocks/index.js +46 -0
  218. package/dist/artifact-blocks/listen/context.d.ts +30 -0
  219. package/dist/artifact-blocks/listen/context.js +30 -0
  220. package/dist/artifact-blocks/liveData.d.ts +78 -0
  221. package/dist/artifact-blocks/liveData.js +137 -0
  222. package/dist/artifact-blocks/liveDataPoller.d.ts +41 -0
  223. package/dist/artifact-blocks/liveDataPoller.js +140 -0
  224. package/dist/artifact-blocks/openNav.d.ts +46 -0
  225. package/dist/artifact-blocks/openNav.js +106 -0
  226. package/dist/artifact-blocks/pageReviewKeys.d.ts +17 -0
  227. package/dist/artifact-blocks/pageReviewKeys.js +115 -0
  228. package/dist/artifact-blocks/params.d.ts +98 -0
  229. package/dist/artifact-blocks/params.js +159 -0
  230. package/dist/artifact-blocks/registry.d.ts +27 -0
  231. package/dist/artifact-blocks/registry.js +83 -0
  232. package/dist/artifact-blocks/review/BlockIcon.svelte +99 -0
  233. package/dist/artifact-blocks/review/BlockIcon.svelte.d.ts +17 -0
  234. package/dist/artifact-blocks/review/BulkRejectForm.svelte +94 -0
  235. package/dist/artifact-blocks/review/BulkRejectForm.svelte.d.ts +11 -0
  236. package/dist/artifact-blocks/review/BulkUndoBar.svelte +40 -0
  237. package/dist/artifact-blocks/review/BulkUndoBar.svelte.d.ts +9 -0
  238. package/dist/artifact-blocks/review/ItemNote.svelte +193 -0
  239. package/dist/artifact-blocks/review/ItemNote.svelte.d.ts +33 -0
  240. package/dist/artifact-blocks/review/NoteToggle.svelte +52 -0
  241. package/dist/artifact-blocks/review/NoteToggle.svelte.d.ts +12 -0
  242. package/dist/artifact-blocks/review/TeamChip.svelte +71 -0
  243. package/dist/artifact-blocks/review/TeamChip.svelte.d.ts +8 -0
  244. package/dist/artifact-blocks/review/attachments.d.ts +10 -0
  245. package/dist/artifact-blocks/review/attachments.js +37 -0
  246. package/dist/artifact-blocks/review/bulk.d.ts +52 -0
  247. package/dist/artifact-blocks/review/bulk.js +61 -0
  248. package/dist/artifact-blocks/review/itemReview.d.ts +33 -0
  249. package/dist/artifact-blocks/review/itemReview.js +112 -0
  250. package/dist/artifact-blocks/review/roundLines.d.ts +14 -0
  251. package/dist/artifact-blocks/review/roundLines.js +20 -0
  252. package/dist/artifact-blocks/review/rounds.d.ts +86 -0
  253. package/dist/artifact-blocks/review/rounds.js +302 -0
  254. package/dist/artifact-blocks/review/saver.d.ts +23 -0
  255. package/dist/artifact-blocks/review/saver.js +48 -0
  256. package/dist/artifact-blocks/review/team.d.ts +31 -0
  257. package/dist/artifact-blocks/review/team.js +42 -0
  258. package/dist/artifact-blocks/review/visualOrder.d.ts +29 -0
  259. package/dist/artifact-blocks/review/visualOrder.js +66 -0
  260. package/dist/artifact-blocks/review.d.ts +180 -0
  261. package/dist/artifact-blocks/review.js +716 -0
  262. package/dist/artifact-blocks/thumb-kit.d.ts +80 -0
  263. package/dist/artifact-blocks/thumb-kit.js +149 -0
  264. package/dist/artifact-blocks/types.d.ts +513 -0
  265. package/dist/artifact-blocks/types.js +1 -0
  266. package/dist/components/Button.svelte +8 -0
  267. package/dist/components/TextInput.svelte +39 -6
  268. package/dist/components/TextInput.svelte.d.ts +15 -7
  269. package/dist/components/basic/Checkbox.svelte +52 -14
  270. package/dist/components/basic/Checkbox.svelte.d.ts +11 -1
  271. package/package.json +11 -2
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Params schema for the mockup block (agent HTML in sandboxed device frames).
3
+ * Imported by block.ts (def) and the tests — keeps them cycle-free.
4
+ */
5
+ import { z } from "zod";
6
+ /**
7
+ * Block-level `device` is author shorthand (mos-659rz): `device: "desktop"`
8
+ * on the params means "every variant that does not name its own device
9
+ * renders as desktop". It is not a MockupParams field, so zod would
10
+ * otherwise silently strip it — the Pilot's shorthand passed validation while
11
+ * the device intent never reached the renderer. Inherit it into device-less
12
+ * variants BEFORE parsing (so the variant default never preempts it), copy
13
+ * the objects (never mutate the caller's input — publish stores the spec
14
+ * verbatim), and drop the block-level key. A variant's own device wins; a
15
+ * non-enum shorthand value falls through to the variant enum and is
16
+ * rejected loudly, like any other bad device.
17
+ */
18
+ function inheritBlockDevice(raw) {
19
+ if (typeof raw !== "object" || raw === null)
20
+ return raw;
21
+ const params = { ...raw };
22
+ const shorthand = params.device;
23
+ if (typeof shorthand !== "string" || !Array.isArray(params.variants))
24
+ return params;
25
+ delete params.device;
26
+ params.variants = params.variants.map((v) => typeof v === "object" &&
27
+ v !== null &&
28
+ v.device === undefined
29
+ ? { ...v, device: shorthand }
30
+ : v);
31
+ return params;
32
+ }
33
+ const ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
34
+ const idSchema = z
35
+ .string()
36
+ .regex(ID_RE, "id must be lowercase a-z0-9_- (max 64, leading a-z0-9)");
37
+ /** Per-variant HTML cap — a device-frame mockup is chrome + layout, not data. */
38
+ const MAX_VARIANT_HTML_BYTES = 48 * 1024;
39
+ /** Whole-block HTML budget — the contract's ~256KB-per-block params cap. */
40
+ const MAX_TOTAL_HTML_BYTES = 256 * 1024;
41
+ const MAX_VARIANTS = 6;
42
+ function utf8Bytes(s) {
43
+ return new TextEncoder().encode(s).length;
44
+ }
45
+ const variantSchema = z.object({
46
+ id: idSchema,
47
+ label: z.string().max(80).optional(),
48
+ device: z.enum(["phone", "tablet", "desktop"]).default("phone"),
49
+ html: z
50
+ .string()
51
+ .min(1, "html is required")
52
+ .refine((h) => utf8Bytes(h) <= MAX_VARIANT_HTML_BYTES, `html exceeds ${Math.round(MAX_VARIANT_HTML_BYTES / 1024)}KB per variant`),
53
+ });
54
+ const mockupSchemaBase = z
55
+ .object({
56
+ title: z.string().max(200).optional(),
57
+ intro: z.string().max(2000).optional(),
58
+ /** Allow scripts inside the sandboxed frame. Default false. */
59
+ allowScripts: z.boolean().default(false),
60
+ variants: z
61
+ .array(variantSchema)
62
+ .min(1, "at least one variant")
63
+ .max(MAX_VARIANTS),
64
+ })
65
+ .check((ctx) => {
66
+ const value = ctx.value;
67
+ const ids = new Set();
68
+ let total = 0;
69
+ value.variants.forEach((v, i) => {
70
+ if (ids.has(v.id)) {
71
+ ctx.issues.push({
72
+ code: "custom",
73
+ input: v,
74
+ path: ["variants", i, "id"],
75
+ message: `duplicate variant id "${v.id}"`,
76
+ });
77
+ }
78
+ ids.add(v.id);
79
+ total += utf8Bytes(v.html);
80
+ if (total > MAX_TOTAL_HTML_BYTES) {
81
+ ctx.issues.push({
82
+ code: "custom",
83
+ input: v,
84
+ path: ["variants", i, "html"],
85
+ message: `html exceeds the ${Math.round(MAX_TOTAL_HTML_BYTES / 1024)}KB whole-block budget`,
86
+ });
87
+ }
88
+ });
89
+ });
90
+ export const mockupSchema = z.preprocess(inheritBlockDevice, mockupSchemaBase);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * mockup param types (mos-vzame) — the hand-written mirror of the zod schema
3
+ * in block.ts. The def annotates `schema: z.ZodType<MockupParams>`, so drift
4
+ * between schema and these interfaces is a compile error, and the Svelte
5
+ * component imports types WITHOUT importing the def module (block.ts imports
6
+ * the component; the edge must stay one-way).
7
+ */
8
+ /** Display widths, device-pixel viewports, and the largest on-screen frame. */
9
+ export type MockupDims = {
10
+ w: number;
11
+ h: number;
12
+ maxFrameW: number;
13
+ };
14
+ export type MockupDevice = "phone" | "tablet" | "desktop";
15
+ /**
16
+ * Device-pixel viewport each frame renders. The frame scales the iframe down
17
+ * to its on-screen width (see MockupBlock.svelte), so the layout INSIDE the
18
+ * frame is laid out at the real device width, not the column width.
19
+ */
20
+ export declare const MOCKUP_DIMS: Record<MockupDevice, MockupDims>;
21
+ export type MockupVariant = {
22
+ id: string;
23
+ label?: string | undefined;
24
+ /** Which device frame this variant renders in. Default "phone". */
25
+ device: MockupDevice;
26
+ /** HTML fragment or full document, rendered verbatim in the sandbox. */
27
+ html: string;
28
+ };
29
+ export type MockupParams = {
30
+ title?: string | undefined;
31
+ intro?: string | undefined;
32
+ /** Allow scripts inside the sandboxed frame. Default false. */
33
+ allowScripts?: boolean | undefined;
34
+ /** One or more mockups, rendered side by side. */
35
+ variants: MockupVariant[];
36
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * mockup param types (mos-vzame) — the hand-written mirror of the zod schema
3
+ * in block.ts. The def annotates `schema: z.ZodType<MockupParams>`, so drift
4
+ * between schema and these interfaces is a compile error, and the Svelte
5
+ * component imports types WITHOUT importing the def module (block.ts imports
6
+ * the component; the edge must stay one-way).
7
+ */
8
+ /**
9
+ * Device-pixel viewport each frame renders. The frame scales the iframe down
10
+ * to its on-screen width (see MockupBlock.svelte), so the layout INSIDE the
11
+ * frame is laid out at the real device width, not the column width.
12
+ */
13
+ export const MOCKUP_DIMS = {
14
+ phone: { w: 390, h: 844, maxFrameW: 320 },
15
+ tablet: { w: 834, h: 1112, maxFrameW: 460 },
16
+ desktop: { w: 1440, h: 900, maxFrameW: 760 },
17
+ };
@@ -0,0 +1,359 @@
1
+ <script lang="ts">
2
+ /**
3
+ * ProgramBlock — the daily newspaper/podcast surface: a brief (audio or
4
+ * video) whose chapter rail plays live on the right while the active
5
+ * chapter's visual renders between the player and the transcript.
6
+ *
7
+ * The player owns the hard parts (jatui VideoPlayer ≥0.32): the timeline
8
+ * band with chapter flags, the click-to-seek transcript, and the opt-in
9
+ * `chapterRail: 'side'` pane — active row tracking the playhead, progress
10
+ * fill, played checks, replay affordance, listNav keys. This block adds
11
+ * only what the page knows:
12
+ *
13
+ * - the visual panel in the player's `belowStage` slot: the shown
14
+ * chapter's nested block instance (any registered type) + its detail
15
+ * markdown, swapped with a keyed fade (reduced-motion safe);
16
+ * - FOLLOW mode (default on) tracks the active chapter as audio plays;
17
+ * the panel header says "Now playing". Clicking a rail row pins that
18
+ * chapter, and a "Back to now playing" button returns to auto;
19
+ * - ONE timeline: `transport="band"` puts play/pause, time, speed and
20
+ * volume on the band (no native control bar), and audio renders no
21
+ * stage at all (`kind="audio"`, jatui ≥0.33);
22
+ * - nested interactive state (a decision/checklist/picker visual's
23
+ * picks) persists through THIS block's ctx.saveState under a
24
+ * `visuals` map keyed by the nested block id, and seeds the nested
25
+ * BlockRenderer on load;
26
+ * - deep links: `?t=` via the player's syncUrlTime; `#chapter-<id>`
27
+ * seeks through the player's seekToTime() and shows the chapter.
28
+ *
29
+ * Every string renders as text or through the shared sanitized markdown
30
+ * renderer; media is by URL (https:// or root-relative, validated by
31
+ * urlSchema).
32
+ */
33
+ import { onDestroy, onMount } from "svelte"
34
+ import VideoPlayer, { type VideoChapter } from "../../../components/VideoPlayer.svelte"
35
+ import BlockRenderer from "../../BlockRenderer.svelte"
36
+ import type { BlockProps, BlockState } from "../../types.js"
37
+ import {
38
+ chapterAt,
39
+ programSchema,
40
+ STATUS_DOT,
41
+ STATUS_LABEL,
42
+ toVideoChapters,
43
+ toVideoSegments,
44
+ type ProgramParams,
45
+ } from "./schema.js"
46
+ import { renderBlockMarkdown } from "../markdown/render.js"
47
+ import { formatClock } from "../transcript/schema.js"
48
+
49
+ /** SSR vs browser, without a SvelteKit import (jatui is host-agnostic). */
50
+ const browser = typeof window !== "undefined"
51
+
52
+ let { params: rawParams, ctx }: BlockProps<ProgramParams> = $props()
53
+
54
+ /** Parsed params (defaults filled); corrupt params → empty state. The host
55
+ * validates at publish, so this is belt-and-braces for direct mounts. */
56
+ const EMPTY_PARAMS: ProgramParams = {
57
+ media: { src: "https://example.invalid/none.mp3", type: "audio" },
58
+ segments: [],
59
+ chapters: [],
60
+ }
61
+ const parsed = $derived(programSchema.safeParse(rawParams))
62
+ const params = $derived(parsed.success ? parsed.data : EMPTY_PARAMS)
63
+
64
+ const playable = $derived(
65
+ params.segments.length > 0 && params.chapters.length > 0,
66
+ )
67
+ /** Audio renders NO stage (there is no picture): the player is the band
68
+ * plus its transport row. Video keeps its stage at its intrinsic ratio. */
69
+ const kind = $derived<"audio" | "video">(
70
+ params.media.type === "video" ? "video" : "audio",
71
+ )
72
+ const vSegments = $derived(toVideoSegments(params.segments))
73
+ const vChapters = $derived(toVideoChapters(params.chapters, params.segments))
74
+ /** Rail-row index → chapter: the player reports the SORTED index. */
75
+ const sortedChapters = $derived(
76
+ params.chapters.slice().sort((a, b) => a.start - b.start),
77
+ )
78
+ const hasSrc = $derived(params.media.src !== "" && playable)
79
+ const firstChapter = $derived(sortedChapters[0] ?? null)
80
+
81
+ const resumeKey = $derived(`artifact:${ctx.artifactId}:${ctx.blockId}`)
82
+
83
+ // ── follow / pin ─────────────────────────────────────────────────────
84
+ let follow = $state(true)
85
+ let pinnedId = $state<string | null>(null)
86
+ let currentTime = $state(0)
87
+ /** The player instance — bandState/seekToTime exports live here. */
88
+ let player: ReturnType<typeof VideoPlayer> | null = $state(null)
89
+
90
+ const activeChapter = $derived(chapterAt(params.chapters, currentTime))
91
+ /** The chapter the panel shows: the active one while following, else the pin. */
92
+ const shownChapter = $derived(
93
+ follow || !pinnedId
94
+ ? activeChapter
95
+ : (params.chapters.find((c) => c.id === pinnedId) ?? activeChapter),
96
+ )
97
+ const shownId = $derived(shownChapter?.id ?? null)
98
+
99
+ /** Rail row click: pin the picked chapter (the rail itself keeps
100
+ * time-tracking — the pin only affects the visual panel). */
101
+ function selectChapter(_ch: VideoChapter, index: number) {
102
+ const c = sortedChapters[index]
103
+ if (!c) return
104
+ pinnedId = c.id
105
+ follow = false
106
+ }
107
+
108
+ function toggleFollow() {
109
+ if (follow) {
110
+ // Turning follow off pins what is on screen right now.
111
+ pinnedId = activeChapter?.id ?? pinnedId
112
+ follow = false
113
+ } else {
114
+ follow = true
115
+ pinnedId = null
116
+ }
117
+ }
118
+
119
+ // ── deep links (?t= rides the player's syncUrlTime) ─────────────────
120
+ function chapterFromHash(): string | null {
121
+ if (!location.hash.startsWith("#chapter-")) return null
122
+ return decodeURIComponent(location.hash.slice("#chapter-".length)) || null
123
+ }
124
+
125
+ function seekToChapterId(id: string | null): boolean {
126
+ if (!id) return false
127
+ const ch = params.chapters.find((c) => c.id === id)
128
+ if (!ch) return false
129
+ currentTime = ch.start
130
+ player?.seekToTime?.(ch.start)
131
+ return true
132
+ }
133
+
134
+ function onHashChange() {
135
+ seekToChapterId(chapterFromHash())
136
+ }
137
+
138
+ onMount(() => {
139
+ if (!browser) return
140
+ const hashId = chapterFromHash()
141
+ if (hashId) {
142
+ seekToChapterId(hashId)
143
+ } else {
144
+ const t = new URL(location.href).searchParams.get("t")
145
+ if (t != null && t !== "") {
146
+ const sec = Number(t)
147
+ if (Number.isFinite(sec) && sec >= 0) currentTime = sec
148
+ }
149
+ }
150
+ window.addEventListener("hashchange", onHashChange)
151
+ })
152
+ onDestroy(() => {
153
+ if (browser) window.removeEventListener("hashchange", onHashChange)
154
+ })
155
+
156
+ // ── nested visual state (picks persist through THIS block's state) ──
157
+ interface ProgramState extends BlockState {
158
+ visuals?: Record<string, BlockState>
159
+ }
160
+ let visualState = $state<Record<string, BlockState>>({})
161
+ // Seed once from the viewer's saved state (only when there is any). The
162
+ // outer renderer's state arrives as a $state proxy — snapshot before the
163
+ // structuredClone.
164
+ let seeded = false
165
+ $effect(() => {
166
+ if (seeded) return
167
+ const saved = (ctx.state as ProgramState | null)?.visuals
168
+ if (saved && typeof saved === "object") {
169
+ visualState = structuredClone($state.snapshot(saved))
170
+ seeded = true
171
+ }
172
+ })
173
+
174
+ /** The nested BlockRenderer's save hook: merge into this block's state. */
175
+ async function saveVisualState(payload: {
176
+ kind: "blocks"
177
+ blocks: Record<string, unknown>
178
+ }) {
179
+ visualState = { ...visualState, ...payload.blocks }
180
+ // Snapshot before handing it up: a $state proxy must never cross the
181
+ // renderer boundary (the DataCloneError class of bug — mos-yntqm); the
182
+ // outer renderer stores $state.snapshot of what it receives.
183
+ const next: ProgramState = { visuals: $state.snapshot(visualState) }
184
+ await ctx.saveState?.(next)
185
+ }
186
+
187
+ const nestedId = $derived(shownId ? `visual-${shownId}` : null)
188
+ const nestedSpec = $derived.by(() => {
189
+ const v = shownChapter?.visual
190
+ if (!v || !nestedId) return null
191
+ return {
192
+ version: 1 as const,
193
+ title: "",
194
+ blocks: [{ id: nestedId, type: v.type, params: v.params }],
195
+ }
196
+ })
197
+
198
+ /** Nested state flows through only when this surface can save at all. */
199
+ const nestedCanSave = $derived(!ctx.readonly && ctx.saveState != null)
200
+
201
+ const detailHtml = $derived(
202
+ shownChapter?.detail ? renderBlockMarkdown(shownChapter.detail) : null,
203
+ )
204
+ </script>
205
+
206
+ <section class="artifact-program w-full">
207
+ {#if params.title || params.note}
208
+ <header class="mb-3">
209
+ {#if params.title}
210
+ <h2 class="text-base font-semibold text-base-content">
211
+ {params.title}
212
+ </h2>
213
+ {/if}
214
+ {#if params.note}
215
+ <p class="mt-1 max-w-prose text-sm text-base-content/70">
216
+ {params.note}
217
+ </p>
218
+ {/if}
219
+ </header>
220
+ {/if}
221
+
222
+ {#if hasSrc}
223
+ <VideoPlayer
224
+ bind:this={player}
225
+ src={params.media.src}
226
+ {kind}
227
+ transport="band"
228
+ poster={params.media.poster || null}
229
+ aspect={null}
230
+ transcript={vSegments}
231
+ chapters={vChapters}
232
+ transcriptPosition="below"
233
+ transcriptMaxHeight="24rem"
234
+ chapterRail="side"
235
+ onChapterSelect={selectChapter}
236
+ onTimeUpdate={(v) => (currentTime = v.currentTime)}
237
+ syncUrlTime
238
+ {resumeKey}
239
+ belowStage={panel}
240
+ />
241
+
242
+ {#snippet panel()}
243
+ {#if shownChapter}
244
+ <div
245
+ class="program-panel rounded-box border border-base-content/10 bg-base-100"
246
+ role="region"
247
+ aria-label="Chapter: {shownChapter.title}"
248
+ >
249
+ <header
250
+ class="flex flex-wrap items-center gap-x-2 gap-y-1 px-4 pt-3 pb-2"
251
+ >
252
+ {#if shownChapter.status}
253
+ <span
254
+ class="size-2 shrink-0 rounded-full {STATUS_DOT[
255
+ shownChapter.status
256
+ ]}"
257
+ aria-hidden="true"
258
+ ></span>
259
+ <span class="sr-only">{STATUS_LABEL[shownChapter.status]}:</span>
260
+ {/if}
261
+ <span class="text-xs tabular-nums text-base-content/60">
262
+ {formatClock(shownChapter.start)}
263
+ </span>
264
+ <h3 class="min-w-0 flex-1 text-sm font-semibold text-base-content">
265
+ {shownChapter.title}
266
+ </h3>
267
+ {#if follow}
268
+ <span
269
+ class="program-follow flex items-center gap-1.5 text-xs text-base-content/60"
270
+ >
271
+ <span
272
+ class="program-live size-1.5 rounded-full bg-primary"
273
+ aria-hidden="true"
274
+ ></span>
275
+ Now playing
276
+ </span>
277
+ {:else}
278
+ <button
279
+ type="button"
280
+ class="program-follow btn btn-ghost btn-xs rounded-field text-primary"
281
+ onclick={toggleFollow}
282
+ >
283
+ Back to now playing
284
+ </button>
285
+ {/if}
286
+ </header>
287
+ {#if shownId}
288
+ {#key shownId}
289
+ <div class="program-panel-body px-4 pb-4">
290
+ {#if detailHtml}
291
+ <!-- Sanitized by renderBlockMarkdown — the contract's one {@html} path. -->
292
+ <div
293
+ class="program-detail prose prose-sm max-w-prose text-base-content/80"
294
+ >
295
+ {@html detailHtml}
296
+ </div>
297
+ {:else if !nestedSpec && shownChapter.summary}
298
+ <p class="max-w-prose text-sm text-base-content/70">
299
+ {shownChapter.summary}
300
+ </p>
301
+ {/if}
302
+ {#if nestedSpec}
303
+ <div class={detailHtml ? "mt-3" : ""}>
304
+ <BlockRenderer
305
+ spec={nestedSpec}
306
+ artifactId={ctx.artifactId}
307
+ version={ctx.version}
308
+ savedBlocks={$state.snapshot(visualState)}
309
+ onSaveBlock={nestedCanSave ? saveVisualState : undefined}
310
+ readonly={ctx.readonly}
311
+ data={ctx.data}
312
+ />
313
+ </div>
314
+ {/if}
315
+ </div>
316
+ {/key}
317
+ {/if}
318
+ </div>
319
+ {:else if firstChapter}
320
+ <p class="px-1 text-sm text-base-content/60">
321
+ The first chapter, {firstChapter.title}, starts at {formatClock(
322
+ firstChapter.start,
323
+ )}.
324
+ </p>
325
+ {/if}
326
+ {/snippet}
327
+ {:else if !playable}
328
+ <p class="text-sm text-base-content/60">
329
+ No chapters published for this program block.
330
+ </p>
331
+ {/if}
332
+ </section>
333
+
334
+ <style>
335
+ /* The keyed panel swap: a short opacity/lift-in. Every state also reads
336
+ statically (content is simply there), so reduced motion drops the move
337
+ and keeps the meaning. */
338
+ .program-panel-body {
339
+ animation: program-panel-in var(--motion-fast, 150ms)
340
+ var(--motion-glide, ease-out);
341
+ }
342
+
343
+ @keyframes program-panel-in {
344
+ from {
345
+ opacity: 0;
346
+ transform: translateY(3px);
347
+ }
348
+ to {
349
+ opacity: 1;
350
+ transform: none;
351
+ }
352
+ }
353
+
354
+ @media (prefers-reduced-motion: reduce) {
355
+ .program-panel-body {
356
+ animation: none;
357
+ }
358
+ }
359
+ </style>
@@ -0,0 +1,32 @@
1
+ import type { BlockProps } from "../../types.js";
2
+ declare const ProgramBlock: import("svelte").Component<BlockProps<{
3
+ media: {
4
+ src: string;
5
+ type?: "audio" | "video" | undefined;
6
+ poster?: string | undefined;
7
+ };
8
+ segments: {
9
+ start: number;
10
+ end: number;
11
+ speaker: string;
12
+ text: string;
13
+ chapter?: string | undefined;
14
+ }[];
15
+ chapters: {
16
+ id: string;
17
+ title: string;
18
+ start: number;
19
+ end?: number | undefined;
20
+ status?: "warn" | "info" | "good" | "bad" | undefined;
21
+ summary?: string | undefined;
22
+ detail?: string | undefined;
23
+ visual?: {
24
+ type: string;
25
+ params: unknown;
26
+ } | undefined;
27
+ }[];
28
+ title?: string | undefined;
29
+ note?: string | undefined;
30
+ }>, {}, "">;
31
+ type ProgramBlock = ReturnType<typeof ProgramBlock>;
32
+ export default ProgramBlock;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * program block — the daily newspaper/podcast surface: an audio (or video)
3
+ * brief with a live chapter rail and a per-chapter visual.
4
+ *
5
+ * Joe 2026-10-09 on /a/morning-2026-10-09: move the chapter list to a RIGHT
6
+ * column, keep the transcript in the center, highlight the active chapter as
7
+ * it plays, click a chapter to jump or hear it again, and show each chapter's
8
+ * accompanying visual (chart/diagram/picker/decision) while it plays.
9
+ *
10
+ * Everything hard is the player's: the rail is jatui VideoPlayer's opt-in
11
+ * `chapterRail: 'side'` pane (active row tracks the playhead with a progress
12
+ * fill, played rows check off, click seeks + plays, replay affordance on the
13
+ * active row, listNav j/k + Enter, sticky column at lg+, chip strip on a
14
+ * phone) — NOT re-implemented here. This block adds what only the page knows:
15
+ *
16
+ * - the VISUAL PANEL: the selected chapter's nested block instance +
17
+ * detail markdown, rendered in the player's `belowStage` slot (under the
18
+ * stage, above the transcript) with a keyed swap;
19
+ * - FOLLOW mode (default on): the panel shows the ACTIVE chapter as audio
20
+ * plays; clicking a rail row pins that chapter and a "Back to now
21
+ * playing" button returns to auto;
22
+ * - nested interactive state: a decision/checklist/picker visual saves the
23
+ * viewer's picks through THIS block's ctx.saveState (a `visuals` map
24
+ * keyed by the nested block's id) and reads them back on load;
25
+ * - deep links: `?t=` seeds the player and the panel; `#chapter-<id>`
26
+ * seeks to the chapter and shows it.
27
+ *
28
+ * The nested visual is validated through the registry like a top-level block
29
+ * (errors report at chapters[i].visual.*); a program cannot nest a program
30
+ * and a scripted mockup is refused.
31
+ */
32
+ import type { ArtifactBlockDef } from "../../types.js";
33
+ import { type ProgramParams } from "./schema.js";
34
+ declare const def: ArtifactBlockDef<ProgramParams>;
35
+ export default def;
@@ -0,0 +1,68 @@
1
+ import ProgramBlock from "./ProgramBlock.svelte";
2
+ import { programSchema } from "./schema.js";
3
+ const EXAMPLE = {
4
+ title: "The weekly program",
5
+ note: "Two numbers moved this week, and one decision is waiting on you.",
6
+ media: {
7
+ // A stable public sample so the catalog example is playable as
8
+ // published; real programs point at the team's own media URLs.
9
+ src: "https://upload.wikimedia.org/wikipedia/commons/c/c8/Example.ogg",
10
+ type: "audio",
11
+ },
12
+ segments: [
13
+ {
14
+ start: 0,
15
+ end: 12,
16
+ speaker: "Host",
17
+ text: "Welcome back to the program.",
18
+ chapter: "intro",
19
+ },
20
+ {
21
+ start: 12,
22
+ end: 28,
23
+ speaker: "Host",
24
+ text: "Two numbers moved this week, and one needs your call.",
25
+ chapter: "numbers",
26
+ },
27
+ { start: 28, end: 30, speaker: "Host", text: "That's the show." },
28
+ ],
29
+ chapters: [
30
+ {
31
+ id: "intro",
32
+ title: "Cold open",
33
+ start: 0,
34
+ status: "info",
35
+ summary: "What this week's program covers.",
36
+ detail: "A one-minute orientation before the numbers.",
37
+ },
38
+ {
39
+ id: "numbers",
40
+ title: "The numbers",
41
+ start: 12,
42
+ status: "good",
43
+ summary: "Two moved up; one decision open.",
44
+ // A core block, so the example renders wherever jatui does; any
45
+ // registered block type works here (stats, chart, table…).
46
+ visual: {
47
+ type: "markdown",
48
+ params: {
49
+ markdown: [
50
+ "| Metric | This week | Change |",
51
+ "|---|---:|---:|",
52
+ "| Read rate | 81.5% | +2.1 |",
53
+ "| Replies | 12 | −3 |",
54
+ ].join("\n"),
55
+ },
56
+ },
57
+ },
58
+ ],
59
+ };
60
+ const def = {
61
+ type: "program",
62
+ title: "Program (brief + chapter rail + per-chapter visual)",
63
+ description: "The daily newspaper/podcast surface: an audio (or video) brief whose chapter rail plays live on the right while each chapter's visual renders between the player and the transcript. media: {src (https:// or root-relative URL), type 'audio'|'video' (default audio), poster?}. segments: [{start, end, speaker, text, chapter?}] — chapter? tags the line with a chapter id and lets that chapter's end derive from its last line. chapters (1–30): [{id (lowercase a-z0-9_-, the #chapter-<id> deep-link key), title, start (seconds), end?, status? good|warn|bad|info (the rail's dot), summary? (one line in the rail), detail? (markdown for the visual panel), visual? (ONE nested block instance {type, params} of any registered type except program and a scripted mockup — validated through the registry, errors report at chapters[i].visual.*)}]. Follow mode shows the active chapter's visual as audio plays; clicking a chapter pins it; nested decision/checklist/picker visuals save the viewer's picks per chapter. Deep links: ?t= and #chapter-<id> select + seek. Tip: give EVERY chapter a visual (stats, table, media-picker, callout decision, timeline — the rail reads like a table of contents only when the rows lead somewhere); keep summaries to one line; lead the spec with this block, context blocks after.",
64
+ schema: programSchema,
65
+ component: ProgramBlock,
66
+ example: EXAMPLE,
67
+ };
68
+ export default def;