@joewinke/jatui 0.37.0 → 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 (266) 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/package.json +11 -2
@@ -0,0 +1,5 @@
1
+ import type { BlockProps } from "../../types.js";
2
+ import type { DiffReviewParams } from "./types.js";
3
+ declare const DiffReviewBlock: import("svelte").Component<BlockProps<DiffReviewParams>, {}, "">;
4
+ type DiffReviewBlock = ReturnType<typeof DiffReviewBlock>;
5
+ export default DiffReviewBlock;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * diff-review — per-section before/after diffs with accept / modify /
3
+ * reject, for contract and copy reviews (mos-a3ivi). Joe 2026-10-10: a BAA
4
+ * pass as a table is hard to review; each section should be a +/- diff with
5
+ * its own verdict, in the media-picker's review UX.
6
+ *
7
+ * Viewer decisions persist through ctx.saveState as
8
+ * `{ [itemId]: { verdict: "approve"|"modify"|"reject", note?, edited?, at,
9
+ * round?, history? } }` (decisions.ts) — the shape `mos-artifact feedback
10
+ * --json` hands back; the flat fields are the current round (mos-lsmve.6).
11
+ */
12
+ import type { ArtifactBlockDef } from "../../types.js";
13
+ import type { DiffReviewParams } from "./types.js";
14
+ declare const diffReview: ArtifactBlockDef<DiffReviewParams>;
15
+ export default diffReview;
@@ -0,0 +1,64 @@
1
+ import DiffReviewBlock from "./DiffReviewBlock.svelte";
2
+ import { diffRounds } from "./decisions.js";
3
+ import { itemLabelOf, itemsOf } from "../../review/itemReview.js";
4
+ import { diffReviewSchema } from "./schema.js";
5
+ const example = {
6
+ title: "BAA simplification — pass 1",
7
+ intro: "Each section shows the current text against the proposed rewrite. Accept, modify (edit the proposed text yourself), or reject to keep the current text. Press ? for the keyboard map.",
8
+ groups: [
9
+ { id: "obligations", title: "Business associate obligations" },
10
+ { id: "term", title: "Term and termination" },
11
+ ],
12
+ items: [
13
+ {
14
+ id: "s4-2-safeguards",
15
+ group: "obligations",
16
+ title: "§4.2 Safeguards",
17
+ before: "Business Associate shall use appropriate safeguards, and comply, where applicable, with Subpart C of 45 C.F.R. Part 164 with respect to electronic protected health information, to prevent use or disclosure of the protected health information other than as provided for by the Agreement.",
18
+ after: "Business Associate shall use appropriate safeguards, and comply with Subpart C of 45 C.F.R. Part 164 for electronic protected health information, to prevent any use or disclosure not permitted by this Agreement.",
19
+ rationale: "Same obligation in plain words; drops 'where applicable' (Subpart C always applies to ePHI).",
20
+ risk: "low",
21
+ tags: ["plain language"],
22
+ },
23
+ {
24
+ id: "s4-5-breach-notice",
25
+ group: "obligations",
26
+ title: "§4.5 Breach notification window",
27
+ before: "Business Associate shall report to Covered Entity any Breach of Unsecured Protected Health Information without unreasonable delay and in no case later than sixty (60) calendar days after discovery.",
28
+ after: "Business Associate shall report to Covered Entity any Breach of Unsecured Protected Health Information without unreasonable delay and in no case later than ten (10) business days after discovery.",
29
+ rationale: "60 days is the regulatory ceiling for the covered entity, not a sensible window for the BA; 10 business days leaves the CE time to meet its own deadline.",
30
+ risk: "medium",
31
+ },
32
+ {
33
+ id: "s7-3-return",
34
+ group: "term",
35
+ title: "§7.3 Return or destruction (new)",
36
+ before: "",
37
+ after: "On termination, Business Associate shall return or destroy all protected health information within thirty (30) days and certify the destruction in writing.",
38
+ rationale: "The current agreement is silent on what happens to PHI at termination.",
39
+ risk: "high",
40
+ },
41
+ ],
42
+ };
43
+ const diffReview = {
44
+ type: "diff-review",
45
+ title: "Diff review (accept / modify / reject)",
46
+ description: "Section-by-section review of proposed text changes (the Redline review view) — contracts (BAA, MSA), policies, site copy, prompts. Each item is one section: {id, title, before (current text, '' for a new section), after (proposed text, '' to delete the section), rationale? (why — plain text), risk? low|medium|high, tags?, group?, team?}, optionally grouped via groups[]. Every item renders as a +/- diff through jatui HunkDiffView (word-level highlights; side by side when wide, unified on a phone — force with layout: 'split' | 'unified'), and the human marks it Accept / Modify / Reject with an optional note (Modify and Reject open the note). Modify opens an inline editor seeded with the proposed text; the reviewer's rewrite saves as `edited`. ROUNDS: after revising a section, republish it with `team: {status: 'ready', readySince: <ISO>}` — the first verdict in that new round locks the previous verdict + note into the section's history and the round starts blank (the old rewrite too); `team: {status: 'in_progress'}` just shows the Team status. Keys (the shared review map): j/k move, a accept (moves on), x modify, r reject, u undo, n note, e or Enter edit, f next section that needs you, ? help; decided sections sort to the end; a progress bar and filter chips with live counts sit on top. BULK ACCEPT: beside the filter chips, 'Accept all remaining (N)' (Shift+A) and — when any item carries a risk — 'Accept all low-risk (n)' accept every undecided, non-retest section the active filter shows, after one confirm; each group heading has 'Accept section (n)'. A bulk never overwrites a decided section, saves once, and offers Undo. So mark risk honestly: low-risk is the reviewer's one-click lane. Decisions persist per viewer as {[itemId]: {verdict: 'approve'|'modify'|'reject', note?, edited?, at, round?, history?: [{round, verdict, note, at}]}} — the flat fields are the CURRENT round; read them back with `mos-artifact feedback --slug <s> --json` — apply `after` for approve, `edited` (falling back to `after` when absent) for modify, keep `before` for reject (an `edited` under any other verdict is an unapplied draft). Limits: 100 items, 20,000 chars per side; a viewer's saved state shares the 64KB feedback cap, so keep sections to a clause or article, not the whole contract. Tip: quote both sides verbatim and keep item ids stable across republishes — verdicts are keyed on them.",
47
+ schema: diffReviewSchema,
48
+ component: DiffReviewBlock,
49
+ // mos-8fvt9: the section's Listen + comment sit on the heading row.
50
+ hostsTools: (p) => Boolean(p.title),
51
+ example,
52
+ interactive: true,
53
+ itemReview: {
54
+ vocabulary: [
55
+ { id: "approve", label: "Accept", tone: "success" },
56
+ { id: "modify", label: "Modify", tone: "warning" },
57
+ { id: "reject", label: "Reject", tone: "error" },
58
+ ],
59
+ name: "Changes",
60
+ items: (params, state) => itemsOf("diff", diffRounds, params, state),
61
+ itemLabel: itemLabelOf,
62
+ },
63
+ };
64
+ export default diffReview;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Viewer decisions for the diff-review block (mos-a3ivi).
3
+ *
4
+ * Saved shape (the feedback channel's contract, read back by agents with
5
+ * `mos-artifact feedback --json`):
6
+ *
7
+ * { [itemId]: { verdict?: "approve" | "modify" | "reject",
8
+ * note?: string,
9
+ * edited?: string } }
10
+ *
11
+ * `edited` is the reviewer's own rewrite of the proposed text. It is only
12
+ * authoritative when verdict is "modify"; under any other verdict it is a
13
+ * kept draft (an Undo or a change of mind never throws typing away).
14
+ *
15
+ * Rounds (mos-lsmve.6): the flat fields are the CURRENT round. When the team
16
+ * marks a section ready again (`team.readySince`) the first save locks the
17
+ * previous {verdict, note, at} into `history[]` and the round starts blank —
18
+ * `edited` too, since the proposal it rewrote has moved on
19
+ * (../../review/rounds.ts). `at` / `round` / `history` are optional; an entry
20
+ * saved before rounds existed reads exactly as it always did.
21
+ */
22
+ import { type RoundLine } from "../../review/rounds.js";
23
+ import { type NoteAttachment } from "../../review/attachments.js";
24
+ export type DiffVerdict = "approve" | "modify" | "reject";
25
+ export type DiffDecision = {
26
+ verdict?: DiffVerdict;
27
+ note?: string;
28
+ /** The note's voice/video/screen/file attachments (mos-rccb5). */
29
+ attachments?: NoteAttachment[];
30
+ edited?: string;
31
+ /** When this round was last saved (ISO). */
32
+ at?: string;
33
+ /** The round the flat fields answer (a team readySince). */
34
+ round?: string;
35
+ /** Locked earlier rounds, newest first. */
36
+ history?: RoundLine[];
37
+ };
38
+ export declare const diffRounds: {
39
+ roundOf: (entry: unknown) => string | null;
40
+ roundLabel: (round: string | null | undefined) => string;
41
+ sameRound: (a: string | null | undefined, b: string | null | undefined) => boolean;
42
+ isStale: (entry: unknown, current: string | null | undefined) => boolean;
43
+ parseHistory: (raw: unknown) => RoundLine[];
44
+ view: (raw: unknown, current: string | null | undefined, publisherHistory?: unknown) => import("../../review/rounds.js").RoundView;
45
+ save: (stored: unknown, patch: Record<string, unknown>, current: string | null | undefined) => Record<string, unknown>;
46
+ };
47
+ /** Cap on a saved rewrite — matches the schema's per-side cap. */
48
+ export declare const EDITED_MAX_CHARS = 20000;
49
+ /** Defensively validate one saved-state entry (untrusted stored JSON). */
50
+ export declare function parseDecision(raw: unknown): DiffDecision | null;
51
+ /** Parse the whole saved-state blob into decisions for this block. */
52
+ export declare function parseDecisions(state: Record<string, unknown> | null): Record<string, DiffDecision>;
53
+ /** True when a decision carries nothing worth saving. Locked history
54
+ * counts: an Undo in a new round must never drop the rounds before it. A
55
+ * note's attachments count as content too (a photo-only note is a real
56
+ * answer, mos-rccb5). */
57
+ export declare function isEmptyDecision(d: DiffDecision | null | undefined): boolean;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Viewer decisions for the diff-review block (mos-a3ivi).
3
+ *
4
+ * Saved shape (the feedback channel's contract, read back by agents with
5
+ * `mos-artifact feedback --json`):
6
+ *
7
+ * { [itemId]: { verdict?: "approve" | "modify" | "reject",
8
+ * note?: string,
9
+ * edited?: string } }
10
+ *
11
+ * `edited` is the reviewer's own rewrite of the proposed text. It is only
12
+ * authoritative when verdict is "modify"; under any other verdict it is a
13
+ * kept draft (an Undo or a change of mind never throws typing away).
14
+ *
15
+ * Rounds (mos-lsmve.6): the flat fields are the CURRENT round. When the team
16
+ * marks a section ready again (`team.readySince`) the first save locks the
17
+ * previous {verdict, note, at} into `history[]` and the round starts blank —
18
+ * `edited` too, since the proposal it rewrote has moved on
19
+ * (../../review/rounds.ts). `at` / `round` / `history` are optional; an entry
20
+ * saved before rounds existed reads exactly as it always did.
21
+ */
22
+ import { useRounds } from "../../review/rounds.js";
23
+ import { readNoteAttachments, } from "../../review/attachments.js";
24
+ const VERDICTS = ["approve", "modify", "reject"];
25
+ export const diffRounds = useRounds({
26
+ verdictKey: "verdict",
27
+ verdicts: VERDICTS,
28
+ roundFields: ["edited"],
29
+ });
30
+ /** Cap on a saved rewrite — matches the schema's per-side cap. */
31
+ export const EDITED_MAX_CHARS = 20_000;
32
+ /** Defensively validate one saved-state entry (untrusted stored JSON). */
33
+ export function parseDecision(raw) {
34
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
35
+ return null;
36
+ const r = raw;
37
+ const out = {};
38
+ if (typeof r.verdict === "string" &&
39
+ VERDICTS.includes(r.verdict))
40
+ out.verdict = r.verdict;
41
+ if (typeof r.note === "string" && r.note)
42
+ out.note = r.note;
43
+ const attachments = readNoteAttachments(r.attachments);
44
+ if (attachments)
45
+ out.attachments = attachments;
46
+ if (typeof r.edited === "string")
47
+ out.edited = r.edited.slice(0, EDITED_MAX_CHARS);
48
+ if (typeof r.at === "string" && r.at)
49
+ out.at = r.at.slice(0, 64);
50
+ if (typeof r.round === "string" && r.round)
51
+ out.round = r.round.slice(0, 200);
52
+ const history = diffRounds.parseHistory(r.history);
53
+ if (history.length > 0)
54
+ out.history = history;
55
+ return isEmptyDecision(out) ? null : out;
56
+ }
57
+ /** Parse the whole saved-state blob into decisions for this block. */
58
+ export function parseDecisions(state) {
59
+ const out = {};
60
+ if (!state)
61
+ return out;
62
+ for (const [id, raw] of Object.entries(state)) {
63
+ const d = parseDecision(raw);
64
+ if (d)
65
+ out[id] = d;
66
+ }
67
+ return out;
68
+ }
69
+ /** True when a decision carries nothing worth saving. Locked history
70
+ * counts: an Undo in a new round must never drop the rounds before it. A
71
+ * note's attachments count as content too (a photo-only note is a real
72
+ * answer, mos-rccb5). */
73
+ export function isEmptyDecision(d) {
74
+ return (!d ||
75
+ (!d.verdict &&
76
+ !d.note &&
77
+ !d.attachments?.length &&
78
+ d.edited === undefined &&
79
+ !d.history?.length));
80
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Params schema for the diff-review block (per-section before/after diffs
3
+ * with accept / modify / reject). Imported by block.ts and the tests.
4
+ *
5
+ * Untrusted input (CONTRACT.md): every string renders as text — the diffs go
6
+ * through jatui HunkDiffView's own DOM, never {@html}.
7
+ */
8
+ import { z } from "zod";
9
+ import type { DiffReviewParams } from "./types.js";
10
+ /** One side of one section. 20k chars is a long contract article; 100
11
+ * sections at a realistic ~1–2k per side stay inside the ~256KB budget. */
12
+ export declare const SIDE_MAX_CHARS = 20000;
13
+ export declare const diffReviewSchema: z.ZodType<DiffReviewParams>;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Params schema for the diff-review block (per-section before/after diffs
3
+ * with accept / modify / reject). Imported by block.ts and the tests.
4
+ *
5
+ * Untrusted input (CONTRACT.md): every string renders as text — the diffs go
6
+ * through jatui HunkDiffView's own DOM, never {@html}.
7
+ */
8
+ import { z } from "zod";
9
+ import { itemTeamSchema } from "../../review/team.js";
10
+ const ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
11
+ const idSchema = z
12
+ .string()
13
+ .regex(ID_RE, "id must be lowercase a-z0-9_- (max 64, leading a-z0-9)");
14
+ /** One side of one section. 20k chars is a long contract article; 100
15
+ * sections at a realistic ~1–2k per side stay inside the ~256KB budget. */
16
+ export const SIDE_MAX_CHARS = 20_000;
17
+ const itemSchema = z
18
+ .object({
19
+ id: idSchema,
20
+ title: z.string().min(1, "title is required").max(200),
21
+ group: idSchema.optional(),
22
+ before: z.string().max(SIDE_MAX_CHARS),
23
+ after: z.string().max(SIDE_MAX_CHARS),
24
+ rationale: z.string().max(2000).optional(),
25
+ risk: z.enum(["low", "medium", "high"]).optional(),
26
+ tags: z.array(z.string().max(40)).max(8).optional(),
27
+ /** Team status: in_progress, or ready + readySince (opens a new round). */
28
+ team: itemTeamSchema.optional(),
29
+ })
30
+ .check((ctx) => {
31
+ if (!ctx.value.before && !ctx.value.after)
32
+ ctx.issues.push({
33
+ code: "custom",
34
+ input: ctx.value,
35
+ path: ["after"],
36
+ message: "at least one of before / after must be non-empty",
37
+ });
38
+ });
39
+ const groupSchema = z.object({
40
+ id: idSchema,
41
+ title: z.string().min(1).max(120),
42
+ });
43
+ export const diffReviewSchema = z
44
+ .object({
45
+ title: z.string().max(200).optional(),
46
+ intro: z.string().max(2000).optional(),
47
+ groups: z.array(groupSchema).max(20).optional(),
48
+ items: z.array(itemSchema).max(100),
49
+ layout: z.enum(["auto", "split", "unified"]).optional(),
50
+ })
51
+ .check((ctx) => {
52
+ // Cross-array rules: unique item ids (state keys on them), unique group
53
+ // ids, and every item's group exists in groups[] (or is omitted).
54
+ const value = ctx.value;
55
+ const itemIds = new Set();
56
+ value.items.forEach((it, i) => {
57
+ if (itemIds.has(it.id))
58
+ ctx.issues.push({
59
+ code: "custom",
60
+ input: it,
61
+ path: ["items", i, "id"],
62
+ message: `duplicate item id "${it.id}"`,
63
+ });
64
+ itemIds.add(it.id);
65
+ if (it.group && !value.groups?.some((g) => g.id === it.group))
66
+ ctx.issues.push({
67
+ code: "custom",
68
+ input: it,
69
+ path: ["items", i, "group"],
70
+ message: `group "${it.group}" is not in groups[]`,
71
+ });
72
+ });
73
+ const groupIds = new Set();
74
+ value.groups?.forEach((g, i) => {
75
+ if (groupIds.has(g.id))
76
+ ctx.issues.push({
77
+ code: "custom",
78
+ input: g,
79
+ path: ["groups", i, "id"],
80
+ message: `duplicate group id "${g.id}"`,
81
+ });
82
+ groupIds.add(g.id);
83
+ });
84
+ });
@@ -0,0 +1,35 @@
1
+ /**
2
+ * diff-review param types (mos-a3ivi) — the hand-written mirror of the zod
3
+ * schema in schema.ts. The schema is annotated `z.ZodType<DiffReviewParams>`,
4
+ * so drift is a compile error, and the component imports types WITHOUT the
5
+ * def module (block.ts imports the component; the edge stays one-way).
6
+ */
7
+ import type { ItemTeam } from "../../review/team.js";
8
+ export type DiffReviewRisk = "low" | "medium" | "high";
9
+ export type DiffReviewItem = {
10
+ id: string;
11
+ title: string;
12
+ group?: string | undefined;
13
+ /** Current text ("" = a section the proposal adds). */
14
+ before: string;
15
+ /** Proposed text ("" = a section the proposal deletes). */
16
+ after: string;
17
+ /** Why the change is proposed (plain text). */
18
+ rationale?: string | undefined;
19
+ risk?: DiffReviewRisk | undefined;
20
+ tags?: string[] | undefined;
21
+ /** Team status (D7): `ready` + readySince opens a new review round. */
22
+ team?: ItemTeam | undefined;
23
+ };
24
+ export type DiffReviewGroup = {
25
+ id: string;
26
+ title: string;
27
+ };
28
+ export type DiffReviewParams = {
29
+ title?: string | undefined;
30
+ intro?: string | undefined;
31
+ groups?: DiffReviewGroup[] | undefined;
32
+ items: DiffReviewItem[];
33
+ /** auto (default): side by side when the block is wide, unified on a phone. */
34
+ layout?: "auto" | "split" | "unified" | undefined;
35
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ <script lang="ts">
2
+ /**
3
+ * explainer block — embeds an existing 3D explainer (the explorer3d
4
+ * engine) by NAME (a shipped spec id under static/explorers/) or by URL.
5
+ *
6
+ * The 3D shell is the HOST's (`ctx.services.explorer3d`, MOS's Explorer3D;
7
+ * jatui ships no three.js). The block fetches the JSON spec client-side
8
+ * (the same contract as the explorer_3d site section), validates it with
9
+ * the host's `services.parseExplorerSpec` when given, and hands the result
10
+ * to the shell as `{ spec, poster, posterAlt, height, booking: false }` —
11
+ * which brings the whole v2 story with
12
+ * it: chapters (chip dock, play/pause, scrubber) and the narration toggle
13
+ * (mos-489bj: muted by default, captions always, respects the sound store
14
+ * and reduced motion). No three.js is imported until the canvas scrolls
15
+ * near the viewport on a WebGL-capable device; without WebGL the poster,
16
+ * parts list and tour still teach the model.
17
+ *
18
+ * Fails soft: no shell on this host, an unreachable URL or an invalid spec
19
+ * shows the poster (when given) and a quiet "not available" line — never a
20
+ * broken page, matching
21
+ * the site section's contract. Read-only safe: nothing here saves state.
22
+ *
23
+ * "Book this fix" is off here (mos-cui3c.1): an artifact page has no
24
+ * booking panel and no tenant /book to fall back to; the spec's plain
25
+ * `cta` links still render.
26
+ */
27
+ import { onMount } from "svelte"
28
+ import type { Component } from "svelte"
29
+ import type { BlockProps } from "../../types"
30
+ import { resolveModelUrl, type ExplainerParams } from "./schema"
31
+ import SectionTools from "../../SectionTools.svelte"
32
+
33
+ let { params, ctx }: BlockProps<ExplainerParams> = $props()
34
+
35
+ /** What the host's spec parser returns (MOS parseExplorerSpec's shape). */
36
+ type SpecResult =
37
+ | { ok: true; spec: unknown }
38
+ | { ok: false; errors: string[] }
39
+
40
+ // The host's 3D shell + optional parser (HostAdapter.services). No shell →
41
+ // the poster and a "not available here" line; nothing is fetched.
42
+ const Explorer3D = $derived(
43
+ ctx?.services?.explorer3d as Component<Record<string, unknown>> | undefined,
44
+ )
45
+ const parseSpec = $derived(
46
+ ctx?.services?.parseExplorerSpec as
47
+ | ((json: unknown) => SpecResult)
48
+ | undefined,
49
+ )
50
+
51
+ const url = $derived(resolveModelUrl(params ?? {}))
52
+ let fetched = $state<SpecResult | null>(null)
53
+
54
+ onMount(() => {
55
+ if (!url || !Explorer3D) return
56
+ const parse = parseSpec
57
+ const ctrl = new AbortController()
58
+ fetch(url, { signal: ctrl.signal })
59
+ .then((r) => {
60
+ if (!r.ok) throw new Error(`HTTP ${r.status}`)
61
+ return r.json()
62
+ })
63
+ .then((json) => {
64
+ let result: SpecResult
65
+ try {
66
+ result = parse ? parse(json) : { ok: true, spec: json }
67
+ } catch (e) {
68
+ result = {
69
+ ok: false,
70
+ errors: [e instanceof Error ? e.message : "invalid spec"],
71
+ }
72
+ }
73
+ if (!result.ok) console.warn("[explainer] invalid spec", result.errors)
74
+ fetched = result
75
+ })
76
+ .catch((e: unknown) => {
77
+ if (ctrl.signal.aborted) return
78
+ fetched = {
79
+ ok: false,
80
+ errors: [e instanceof Error ? e.message : "failed to load"],
81
+ }
82
+ })
83
+ return () => ctrl.abort()
84
+ })
85
+
86
+ const height = $derived(`${params?.heightPx ?? 520}px`)
87
+ </script>
88
+
89
+ <figure class="artifact-explainer w-full">
90
+ {#if params?.title}
91
+ <figcaption
92
+ class="mb-2 flex items-center gap-2 text-sm font-medium text-base-content/70"
93
+ >
94
+ <span class="min-w-0">{params.title}</span>
95
+ <SectionTools {ctx} inline />
96
+ </figcaption>
97
+ {/if}
98
+
99
+ {#if fetched?.ok && Explorer3D}
100
+ <Explorer3D
101
+ spec={fetched.spec}
102
+ poster={params?.poster ?? ""}
103
+ posterAlt={params?.posterAlt ?? ""}
104
+ {height}
105
+ booking={false}
106
+ />
107
+ {:else if fetched || !Explorer3D}
108
+ <!-- Invalid spec or unreachable URL: the poster still teaches, the page
109
+ stands. The parse errors go to the console (the site section's rule). -->
110
+ {#if params?.poster}
111
+ <img
112
+ src={params.poster}
113
+ alt={params.posterAlt ?? ""}
114
+ class="max-h-[70vh] rounded-box"
115
+ loading="lazy"
116
+ decoding="async"
117
+ />
118
+ {/if}
119
+ <p class="mt-3 text-sm text-base-content/60">
120
+ {Explorer3D
121
+ ? "The interactive model is not available right now."
122
+ : "The interactive 3D model is not available here."}
123
+ </p>
124
+ {:else}
125
+ <div
126
+ class="grid min-h-64 place-items-center rounded-box border border-base-content/10 bg-base-200/50"
127
+ >
128
+ {#if params?.poster}
129
+ <img
130
+ src={params.poster}
131
+ alt={params.posterAlt ?? ""}
132
+ class="max-h-[60vh] rounded-box"
133
+ loading="lazy"
134
+ decoding="async"
135
+ />
136
+ {:else}
137
+ <p class="text-sm text-base-content/60">Loading the model</p>
138
+ {/if}
139
+ </div>
140
+ {/if}
141
+
142
+ {#if params?.caption}
143
+ <p class="mt-3 text-sm text-base-content/60">{params.caption}</p>
144
+ {/if}
145
+ </figure>
@@ -0,0 +1,5 @@
1
+ import type { Component } from "svelte";
2
+ import type { BlockProps } from "../../types";
3
+ declare const ExplainerBlock: Component<BlockProps<ExplainerParams>, {}, "">;
4
+ type ExplainerBlock = ReturnType<typeof ExplainerBlock>;
5
+ export default ExplainerBlock;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * explainer block — embeds an existing 3D explainer by id or URL through the
3
+ * Explorer3D shell (chapters + the narration toggle come with the shell).
4
+ */
5
+ import type { ArtifactBlockDef } from "../../types";
6
+ import { type ExplainerParams } from "./schema";
7
+ declare const def: ArtifactBlockDef<ExplainerParams>;
8
+ export default def;
@@ -0,0 +1,21 @@
1
+ import ExplainerBlock from "./ExplainerBlock.svelte";
2
+ import { explainerSchema } from "./schema";
3
+ const EXAMPLE = {
4
+ model: "water-heater",
5
+ title: "Inside your tank water heater",
6
+ caption: "Turn it, open the cutaway and play the story: sediment builds, the anode sacrifices itself, and a flush resets the clock.",
7
+ poster: "https://example.com/explorers/edu-inside-your-water-heater.webp",
8
+ posterAlt: "Labeled diagram of a tank water heater",
9
+ heightPx: 520,
10
+ };
11
+ const def = {
12
+ type: "explainer",
13
+ title: "3D explainer",
14
+ description: 'Embeds an existing 3D explainer — the interactive cutaway/exploded-view models shipped as JSON specs — by NAME or URL: model is a shipped explainer id ("water-heater", "burst-pipe", "drain-trap", "tankless", … — the specs under static/explorers/, resolved to /explorers/<id>.json on the host origin) and modelUrl is an explicit https:// or root-relative spec URL; set exactly one. The shell brings the whole experience: turn/zoom, cutaway, part cards, and for version-2 specs the chapter story (chip dock with play/pause and a scrubber) plus the narration toggle — muted by default, captions always visible, honors the viewer\'s sound settings and reduced motion. Optional title and caption frame it; poster/posterAlt (poster requires posterAlt) show a labeled still until 3D loads and forever without WebGL; heightPx is the stage height (default 520). The 3D shell is a host service (HostAdapter services.explorer3d, optional services.parseExplorerSpec); on a host without one the block shows the poster and a "not available here" notice. An unreachable or invalid spec fails soft to the poster and a quiet notice — never a broken page. Tip: let the spec tell the story — don\'t duplicate its chapter text in surrounding blocks; link to the service page the spec\'s CTA points at.',
15
+ schema: explainerSchema,
16
+ component: ExplainerBlock,
17
+ // mos-8fvt9: the section's Listen + comment sit on the heading row.
18
+ hostsTools: (p) => Boolean(p.title),
19
+ example: EXAMPLE,
20
+ };
21
+ export default def;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Params schema for the explainer block (embeds an existing 3D explainer
3
+ * spec through the Explorer3D shell). Imported by block.ts (def),
4
+ * ExplainerBlock.svelte (props typing) and the tests — keeps them cycle-free.
5
+ *
6
+ * Untrusted input rules (CONTRACT.md): the model reference is either a
7
+ * KNOWN explainer id (resolved to the root-relative /explorers/<id>.json
8
+ * the shell serves) or an explicit https:// / root-relative URL via the
9
+ * shared urlSchema — never any other scheme, never a data: blob. Text
10
+ * fields are length-capped; height is a validated number, not a CSS string.
11
+ */
12
+ import { z } from "zod";
13
+ export declare const explainerSchema: z.ZodObject<{
14
+ model: z.ZodOptional<z.ZodString>;
15
+ modelUrl: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
16
+ title: z.ZodOptional<z.ZodString>;
17
+ caption: z.ZodOptional<z.ZodString>;
18
+ poster: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
19
+ posterAlt: z.ZodOptional<z.ZodString>;
20
+ heightPx: z.ZodDefault<z.ZodNumber>;
21
+ }, z.core.$strip>;
22
+ /** The URL an explainer reference resolves to. */
23
+ export declare function resolveModelUrl(params: {
24
+ model?: string;
25
+ modelUrl?: string;
26
+ }): string | null;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Params schema for the explainer block (embeds an existing 3D explainer
3
+ * spec through the Explorer3D shell). Imported by block.ts (def),
4
+ * ExplainerBlock.svelte (props typing) and the tests — keeps them cycle-free.
5
+ *
6
+ * Untrusted input rules (CONTRACT.md): the model reference is either a
7
+ * KNOWN explainer id (resolved to the root-relative /explorers/<id>.json
8
+ * the shell serves) or an explicit https:// / root-relative URL via the
9
+ * shared urlSchema — never any other scheme, never a data: blob. Text
10
+ * fields are length-capped; height is a validated number, not a CSS string.
11
+ */
12
+ import { z } from "zod";
13
+ import { optionalUrlSchema, shortTextSchema } from "../../params";
14
+ /** Explainer ids are kebab-case (static/explorers/<id>.json). */
15
+ const EXPLAINER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
16
+ export const explainerSchema = z
17
+ .object({
18
+ /**
19
+ * A shipped explainer's id — resolves to /explorers/<id>.json on the MOS
20
+ * origin (e.g. "water-heater", "burst-pipe"). Exactly one of this or
21
+ * modelUrl.
22
+ */
23
+ model: z
24
+ .string()
25
+ .regex(EXPLAINER_ID_RE, "explainer id must be lowercase a-z0-9-")
26
+ .optional(),
27
+ /** An explicit spec URL (https:// or root-relative). Exactly one. */
28
+ modelUrl: optionalUrlSchema,
29
+ /** Optional heading above the explorer. */
30
+ title: shortTextSchema.optional(),
31
+ /** Optional line under the explorer. */
32
+ caption: shortTextSchema.optional(),
33
+ /** Poster shown until 3D loads (and without WebGL) — an image URL. */
34
+ poster: optionalUrlSchema,
35
+ /** Alt text for the poster (required when a poster is set). */
36
+ posterAlt: shortTextSchema.optional(),
37
+ /** Stage height in px (default 520). */
38
+ heightPx: z.number().int().min(320).max(900).default(520),
39
+ })
40
+ .superRefine((v, ctx) => {
41
+ const both = v.model !== undefined && v.modelUrl !== undefined;
42
+ const neither = v.model === undefined && v.modelUrl === undefined;
43
+ if (both)
44
+ ctx.addIssue({
45
+ code: "custom",
46
+ path: ["model"],
47
+ message: "set either model or modelUrl, not both",
48
+ });
49
+ if (neither)
50
+ ctx.addIssue({
51
+ code: "custom",
52
+ path: ["model"],
53
+ message: "set model (a shipped explainer id) or modelUrl",
54
+ });
55
+ if (v.poster && !v.posterAlt)
56
+ ctx.addIssue({
57
+ code: "custom",
58
+ path: ["posterAlt"],
59
+ message: "a poster requires posterAlt (it is the fallback's alt text)",
60
+ });
61
+ });
62
+ /** The URL an explainer reference resolves to. */
63
+ export function resolveModelUrl(params) {
64
+ if (params.model)
65
+ return `/explorers/${params.model}.json`;
66
+ if (params.modelUrl)
67
+ return params.modelUrl;
68
+ return null;
69
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The content blocks (block task jatui-zy9n5). This barrel is the ONLY shared
3
+ * file that task edits: add a block by importing its def and appending it to
4
+ * BLOCKS, and remove its type from PENDING in the same change. builtins.ts
5
+ * concatenates the four group barrels; nothing else lists blocks.
6
+ */
7
+ import type { ArtifactBlockDef } from "../types.js";
8
+ export declare const BLOCKS: ArtifactBlockDef<any>[];
9
+ /** This group's MOS block types still being ported. */
10
+ export declare const PENDING: readonly string[];