@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 { MediaPickerParams } from "./types.js";
3
+ declare const MediaPickerBlock: import("svelte").Component<BlockProps<MediaPickerParams>, {}, "">;
4
+ type MediaPickerBlock = ReturnType<typeof MediaPickerBlock>;
5
+ export default MediaPickerBlock;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * media-picker — the review kit's pick-and-approve experience as a native
3
+ * artifact block (mos-vzame, epic mos-m1qrx).
4
+ *
5
+ * Params are DATA an agent publishes: groups of media items, each item
6
+ * offering one or more variants (image/video/audio by URL) the human chooses
7
+ * between, then approves, marks needs-work, or rejects — with an optional
8
+ * note. The viewer's decisions persist through `ctx.saveState` as
9
+ * `{ [itemId]: { verdict, variant, note, at, round?, history? } }`, the
10
+ * shape the artifact feedback channel (mos-41vpm) hands back to agents via
11
+ * `mos-artifact feedback` — the flat fields are the current round
12
+ * (mos-lsmve.6), so pre-rounds readers see exactly what they always did. Verdict vocabulary matches scripts/review-kit
13
+ * (mos-nj3am): "approve" | "redo" | "reject" — so a manifest can round-trip
14
+ * between the script kit and this block.
15
+ *
16
+ * Media is BY URL only (https:// or root-relative, shared params urlSchema) — never
17
+ * base64; the params cap (~256KB per block) and the feedback channel's 64KB
18
+ * state cap both assume it. 200 items max, matching the feedback API's
19
+ * MAX_FEEDBACK_ITEMS.
20
+ */
21
+ import type { ArtifactBlockDef } from "../../types.js";
22
+ import type { MediaPickerParams } from "./types.js";
23
+ declare const mediaPicker: ArtifactBlockDef<MediaPickerParams>;
24
+ export default mediaPicker;
@@ -0,0 +1,137 @@
1
+ import { mediaPickerSchema } from "./schema.js";
2
+ import MediaPickerBlock from "./MediaPickerBlock.svelte";
3
+ import { mediaRounds } from "./decisions.js";
4
+ import { itemLabelOf, itemsOf, mediaThumbOf, } from "../../review/itemReview.js";
5
+ const example = {
6
+ title: "Hero art — round 1",
7
+ intro: "Approve the hero you want shipped. Press ? for the keyboard map.",
8
+ groups: [
9
+ { id: "hero", title: "Hero illustration" },
10
+ { id: "loop", title: "Hover loops" },
11
+ ],
12
+ items: [
13
+ {
14
+ id: "hero-art",
15
+ group: "hero",
16
+ title: "Hero illustration",
17
+ subtitle: "warm still life vs. product panel",
18
+ meta: { placement: "home hero", aspect: "16/10" },
19
+ tags: ["round 1"],
20
+ variants: [
21
+ {
22
+ id: "a",
23
+ label: "still life",
24
+ kind: "image",
25
+ src: "https://media.marduk.app/demo/hero-a.png",
26
+ },
27
+ {
28
+ id: "b",
29
+ label: "panel",
30
+ kind: "image",
31
+ src: "https://media.marduk.app/demo/hero-b.png",
32
+ },
33
+ ],
34
+ },
35
+ {
36
+ id: "loop-toilet",
37
+ group: "loop",
38
+ title: "Toilet hover loop",
39
+ meta: { duration: "3s", codec: "webm" },
40
+ variants: [
41
+ {
42
+ id: "v1",
43
+ label: "v1",
44
+ kind: "video",
45
+ src: "https://media.marduk.app/demo/toilet-loop.webm",
46
+ poster: "https://media.marduk.app/demo/toilet-poster.png",
47
+ },
48
+ ],
49
+ },
50
+ {
51
+ id: "jingle",
52
+ group: "loop",
53
+ title: "Hold-music jingle",
54
+ variants: [
55
+ {
56
+ id: "take1",
57
+ label: "take 1",
58
+ kind: "audio",
59
+ src: "https://media.marduk.app/demo/jingle.mp3",
60
+ },
61
+ ],
62
+ },
63
+ {
64
+ id: "audit-bathroom",
65
+ group: "hero",
66
+ // EVIDENCE, not choices (mos-9d62g): the screenshots are EXAMPLES of
67
+ // the problem — the card renders them as a gallery with captions and
68
+ // the verdict stays a plain Approve of the card.
69
+ evidence: true,
70
+ title: "Bathroom-repairs page loads with no visible headline",
71
+ wrong: "The headline is invisible on the bathroom repairs page.",
72
+ ifApproved: "The page shows its headline again.",
73
+ links: [
74
+ {
75
+ label: "Open on site",
76
+ url: "https://pe.example.com/bathroom-repairs",
77
+ },
78
+ ],
79
+ variants: [
80
+ {
81
+ id: "ev1",
82
+ label: "top of the page",
83
+ kind: "image",
84
+ src: "https://media.marduk.app/demo/hero-a.png",
85
+ },
86
+ {
87
+ id: "ev2",
88
+ label: "after scrolling",
89
+ kind: "image",
90
+ src: "https://media.marduk.app/demo/hero-b.png",
91
+ },
92
+ ],
93
+ },
94
+ {
95
+ id: "primary-decision",
96
+ group: "hero",
97
+ title: "Decision: primary button fill",
98
+ subtitle: "A keeps the logo's ink + yellow on every buy button; B follows the no-solid-black rule with warm-grey text.",
99
+ // A CHOICE, not a review: pick replaces the single Approve. With one
100
+ // comparison image the options are named choices; with 2+ variants
101
+ // each variant gets its own "Pick this" button instead.
102
+ pick: true,
103
+ choices: ["Ink + yellow", "Warm grey"],
104
+ variants: [
105
+ {
106
+ id: "options",
107
+ label: "A vs B",
108
+ kind: "image",
109
+ src: "https://media.marduk.app/demo/primary-a-vs-b.png",
110
+ },
111
+ ],
112
+ },
113
+ ],
114
+ };
115
+ const mediaPicker = {
116
+ type: "media-picker",
117
+ title: "Media picker (review grid)",
118
+ description: "A pick-and-approve review grid for media (the Gallery review view): publish groups of items (each with image, video, or audio variants addressed BY URL — https, or the artifact's own private asset store (/a/<slug>/assets/…, uploaded with `mos-artifact.mjs asset` or built for you by scripts/review-kit/build.mjs); never base64) and the human marks each card Approve / Needs work / Reject with an optional note (Needs work and Reject open the note). VARIANTS: when an item offers more than one variant the Approve button names the one the reviewer is looking at ('Approve Bamboo') and the saved verdict shows 'Approved: Bamboo' on the card and in the zoom footer — approving pins THAT variant, browsing another never moves it (the Approve button, always naming the current view, moves it deliberately). CHOICES: set `pick: true` on an item that is a choice, not a review — each variant gets a per-variant 'Pick this' button instead of a single Approve (picking saves verdict + that variant, the same shape as any approve), and Needs work / Reject stay for 'neither'. For a decision whose options live in ONE comparison image (an 'A vs B' sheet), give the single variant `choices: ['A', 'B']` (2–6 labels): the card renders a Pick button per choice and the pick saves `variant: <slug of the choice label>`. INFORMATIONAL: set `informational: true` on an item that is CONTEXT ONLY (a live-site reference shot, a spec sheet) — it renders with a 'no decision needed' chip, no verdict chrome, and is excluded from the review census so it never blocks 'Send answers'. EVIDENCE (mos-9d62g): set `evidence: true` on an item whose variants are EXAMPLES of the problem (an audit card's screenshots) — the card renders them as a gallery with numbered captions (each opens the zoom) and the Approve stays PLAIN, saving no variant: the verdict approves the card, so it never reads 'Approve bathroom-repairs light'; a variant-less approve validates on evidence items and pulls back clean. CARD COPY (mos-9d62g): write `wrong` ('What is wrong:') and `ifApproved` ('If you approve:') — two short labeled lines under the title, in plain English for a reader who has never seen the app (scripts/review-kit lint warns on sentences over 20 words and on file paths, field names and severity codes at build time). LINKS (mos-9d62g): `links: [{label, url}]` renders 'Open on site' chips under the card and in the zoom footer (new tab, https or root-relative; label defaults to 'Open on site'). APPROVE ALL: a page-level 'Approve all N' button sits beside the filter chips and a per-section one beside each section title; they approve every undecided, non-choice, non-retest item in ONE save (choice items still need a deliberate pick; retests deserve eyes). Variants sharing a `version` are VIEWS of one version; `currentVersion` names the one under review and older versions are read-only. ROUNDS: a round is the version under review, or — when you set `team: {status: 'ready', readySince: <ISO>}` on an item after fixing it — that readySince. The first verdict in a new round locks the previous verdict + note into the item's history and the new round starts blank; `team: {status: 'in_progress'}` just shows the Team status. Images AND videos open a full-size zoom view (zoom/pan, frame-step, speed) with the verdict control and note in its footer, and ←/→ past the last view (or j/k) pages to the next card, so the reviewer decides without closing. Keys (the shared review map): j/k move, a approve, x needs work, r reject, u undo, n note, [ ] older/newer version, 1-9 view, Enter full size, f next item that needs you, ? help. A progress bar and filter chips with live counts sit on top; rejected cards sink. Viewer decisions persist as {[itemId]: {verdict, variant, note, at, round?, history?: [{round, verdict, note, at}]}} through the artifact feedback channel — the flat fields are the CURRENT round, so `mos-artifact feedback` reads exactly as before. Use it for any human-in-the-loop media decision: art rounds, hover-loop picks, hold-music choices. Tip: keep item ids stable across republishes — verdicts are keyed on them.",
119
+ schema: mediaPickerSchema,
120
+ component: MediaPickerBlock,
121
+ // mos-8fvt9: the section's Listen + comment sit on the heading row.
122
+ hostsTools: (p) => Boolean(p.title || p.intro),
123
+ example,
124
+ interactive: true,
125
+ itemReview: {
126
+ vocabulary: [
127
+ { id: "approve", label: "Approve", tone: "success" },
128
+ { id: "redo", label: "Needs work", tone: "warning" },
129
+ { id: "reject", label: "Reject", tone: "error" },
130
+ ],
131
+ name: "Media",
132
+ items: (params, state) => itemsOf("media", mediaRounds, params, state),
133
+ itemLabel: itemLabelOf,
134
+ itemThumb: mediaThumbOf,
135
+ },
136
+ };
137
+ export default mediaPicker;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Choice items for the media-picker (mos-lwbbt).
3
+ *
4
+ * A card can be a CHOICE rather than a review: "palm or bamboo?" — each
5
+ * variant is an option (`pick: true` renders a per-variant Pick button), or
6
+ * the options live inside ONE comparison image (`choices: ["A", "B"]` — the
7
+ * dirt-theme-review decision-primary case: a single "A vs B" sheet a bare
8
+ * Approve could never answer). Both save the SAME shape as any approve —
9
+ * `{verdict: "approve", variant: <id>}` — so the feedback channel, rounds
10
+ * and the review-kit pull read a choice exactly as they read a pick.
11
+ *
12
+ * A choice id is the slug of its label ("Ink + yellow" → "ink-yellow"),
13
+ * deterministic so a saved verdict keeps naming its choice across
14
+ * republishes. Pure: no DOM, no Svelte — the component, the schema check
15
+ * and the tests share it.
16
+ */
17
+ import type { MediaItem } from "./types.js";
18
+ /** The saved-state id for a named choice: label → lowercase slug. */
19
+ export declare function choiceId(label: string, index: number): string;
20
+ /** The choice a saved pick id names (its label), or null when it isn't one. */
21
+ export declare function choiceLabelOf(item: MediaItem, id: string): string | null;
22
+ /** Is this item a choice (per-variant Pick buttons or named choices)? */
23
+ export declare function isPick(item: MediaItem): boolean;
24
+ /** The choices as pickable options: id + display label, in listed order. */
25
+ export declare function choicesOf(item: MediaItem): {
26
+ id: string;
27
+ label: string;
28
+ }[];
@@ -0,0 +1,35 @@
1
+ /** The saved-state id for a named choice: label → lowercase slug. */
2
+ export function choiceId(label, index) {
3
+ const slug = label
4
+ .trim()
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/g, "-")
7
+ .replace(/^-+|-+$/g, "")
8
+ .slice(0, 64)
9
+ .replace(/^-+|-+$/g, "");
10
+ // ID_RE-compatible (leading a-z0-9); a label that slugs to nothing gets a
11
+ // positional fallback — stable as long as the choice order is.
12
+ if (slug && /^[a-z0-9]/.test(slug))
13
+ return slug;
14
+ return `choice-${index + 1}`;
15
+ }
16
+ /** The choice a saved pick id names (its label), or null when it isn't one. */
17
+ export function choiceLabelOf(item, id) {
18
+ if (!item.choices?.length)
19
+ return null;
20
+ for (let i = 0; i < item.choices.length; i++)
21
+ if (choiceId(item.choices[i], i) === id)
22
+ return item.choices[i];
23
+ return null;
24
+ }
25
+ /** Is this item a choice (per-variant Pick buttons or named choices)? */
26
+ export function isPick(item) {
27
+ return item.pick === true || (item.choices?.length ?? 0) > 0;
28
+ }
29
+ /** The choices as pickable options: id + display label, in listed order. */
30
+ export function choicesOf(item) {
31
+ return (item.choices ?? []).map((label, i) => ({
32
+ id: choiceId(label, i),
33
+ label,
34
+ }));
35
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Viewer decisions for the media-picker block (mos-vzame).
3
+ *
4
+ * Lives beside the component (not inside block.ts) so the component never
5
+ * imports the def module back — block.ts already imports the component, and
6
+ * a runtime cycle is exactly the kind of cleverness a registry imports
7
+ * eagerly on the server.
8
+ *
9
+ * The saved shape is the feedback channel's contract:
10
+ * `{ [itemId]: { verdict, variant, note, at?, round?, history? } }` — verdict
11
+ * vocabulary matches scripts/review-kit (mos-nj3am): "approve" | "redo" |
12
+ * "reject". The flat fields are the CURRENT round (mos-lsmve.6); `round` is
13
+ * the version (or team readySince) they answer and `history[]` holds the
14
+ * locked earlier rounds (../../review/rounds.ts). An entry saved before
15
+ * rounds existed has neither and reads exactly as it always did.
16
+ */
17
+ import { type RoundLine } from "../../review/rounds.js";
18
+ import { type NoteAttachment } from "../../review/attachments.js";
19
+ export type MediaVerdict = "approve" | "redo" | "reject";
20
+ export type MediaDecision = {
21
+ verdict?: MediaVerdict;
22
+ variant?: string | null;
23
+ note?: string;
24
+ /** The note's voice/video/screen/file attachments (mos-rccb5). */
25
+ attachments?: NoteAttachment[];
26
+ /** When this round's verdict/note was last saved (ISO). */
27
+ at?: string;
28
+ /** The round the flat fields answer (a version id or a readySince). */
29
+ round?: string;
30
+ /** Locked earlier rounds, newest first. */
31
+ history?: RoundLine[];
32
+ };
33
+ /** The media-picker's round model: `variant` (the approved pick) belongs to
34
+ * a round too — a new version starts with no pick. */
35
+ export declare const mediaRounds: {
36
+ roundOf: (entry: unknown) => string | null;
37
+ roundLabel: (round: string | null | undefined) => string;
38
+ sameRound: (a: string | null | undefined, b: string | null | undefined) => boolean;
39
+ isStale: (entry: unknown, current: string | null | undefined) => boolean;
40
+ parseHistory: (raw: unknown) => RoundLine[];
41
+ view: (raw: unknown, current: string | null | undefined, publisherHistory?: unknown) => import("../../review/rounds.js").RoundView;
42
+ save: (stored: unknown, patch: Record<string, unknown>, current: string | null | undefined) => Record<string, unknown>;
43
+ };
44
+ /** Defensively validate one saved-state entry (untrusted stored JSON). */
45
+ export declare function parseDecision(raw: unknown): MediaDecision | null;
46
+ /** An entry carrying nothing worth keeping — locked history counts: an Undo
47
+ * in a new round must never drop the rounds before it. A note's attachments
48
+ * count as content too (a photo-only note is a real answer, mos-rccb5). */
49
+ export declare function isEmptyDecision(d: MediaDecision | null | undefined): boolean;
50
+ /** Parse the whole saved-state blob into decisions for THIS picker. */
51
+ export declare function parseDecisions(state: Record<string, unknown> | null): Record<string, MediaDecision>;
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Viewer decisions for the media-picker block (mos-vzame).
3
+ *
4
+ * Lives beside the component (not inside block.ts) so the component never
5
+ * imports the def module back — block.ts already imports the component, and
6
+ * a runtime cycle is exactly the kind of cleverness a registry imports
7
+ * eagerly on the server.
8
+ *
9
+ * The saved shape is the feedback channel's contract:
10
+ * `{ [itemId]: { verdict, variant, note, at?, round?, history? } }` — verdict
11
+ * vocabulary matches scripts/review-kit (mos-nj3am): "approve" | "redo" |
12
+ * "reject". The flat fields are the CURRENT round (mos-lsmve.6); `round` is
13
+ * the version (or team readySince) they answer and `history[]` holds the
14
+ * locked earlier rounds (../../review/rounds.ts). An entry saved before
15
+ * rounds existed has neither and reads exactly as it always did.
16
+ */
17
+ import { useRounds } from "../../review/rounds.js";
18
+ import { readNoteAttachments, } from "../../review/attachments.js";
19
+ const MEDIA_VERDICTS = ["approve", "redo", "reject"];
20
+ /** The media-picker's round model: `variant` (the approved pick) belongs to
21
+ * a round too — a new version starts with no pick. */
22
+ export const mediaRounds = useRounds({
23
+ verdictKey: "verdict",
24
+ verdicts: MEDIA_VERDICTS,
25
+ roundFields: ["variant"],
26
+ });
27
+ /** Defensively validate one saved-state entry (untrusted stored JSON). */
28
+ export function parseDecision(raw) {
29
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
30
+ return null;
31
+ const r = raw;
32
+ const out = {};
33
+ if (typeof r.verdict === "string" &&
34
+ MEDIA_VERDICTS.includes(r.verdict)) {
35
+ out.verdict = r.verdict;
36
+ }
37
+ if (typeof r.variant === "string" && r.variant)
38
+ out.variant = r.variant;
39
+ if (typeof r.note === "string" && r.note)
40
+ out.note = r.note;
41
+ const attachments = readNoteAttachments(r.attachments);
42
+ if (attachments)
43
+ out.attachments = attachments;
44
+ if (typeof r.at === "string" && r.at)
45
+ out.at = r.at.slice(0, 64);
46
+ if (typeof r.round === "string" && r.round)
47
+ out.round = r.round.slice(0, 200);
48
+ const history = mediaRounds.parseHistory(r.history);
49
+ if (history.length > 0)
50
+ out.history = history;
51
+ return isEmptyDecision(out) ? null : out;
52
+ }
53
+ /** An entry carrying nothing worth keeping — locked history counts: an Undo
54
+ * in a new round must never drop the rounds before it. A note's attachments
55
+ * count as content too (a photo-only note is a real answer, mos-rccb5). */
56
+ export function isEmptyDecision(d) {
57
+ return (!d ||
58
+ (!d.verdict &&
59
+ !d.variant &&
60
+ !d.note &&
61
+ !d.attachments?.length &&
62
+ !d.history?.length));
63
+ }
64
+ /** Parse the whole saved-state blob into decisions for THIS picker. */
65
+ export function parseDecisions(state) {
66
+ const out = {};
67
+ if (!state)
68
+ return out;
69
+ for (const [id, raw] of Object.entries(state)) {
70
+ const d = parseDecision(raw);
71
+ if (d)
72
+ out[id] = d;
73
+ }
74
+ return out;
75
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Params schema for the media-picker block (the review kit's pick-and-approve
3
+ * grid). Imported by block.ts (def) and the tests — keeps them cycle-free.
4
+ */
5
+ import { z } from "zod";
6
+ import type { MediaPickerParams } from "./types.js";
7
+ export declare const mediaPickerSchema: z.ZodType<MediaPickerParams>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Params schema for the media-picker block (the review kit's pick-and-approve
3
+ * grid). Imported by block.ts (def) and the tests — keeps them cycle-free.
4
+ */
5
+ import { z } from "zod";
6
+ import { urlSchema } from "../../params.js";
7
+ import { itemTeamSchema } from "../../review/team.js";
8
+ import { choiceId } from "./choices.js";
9
+ const ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/;
10
+ const idSchema = z
11
+ .string()
12
+ .regex(ID_RE, "id must be lowercase a-z0-9_- (max 64, leading a-z0-9)");
13
+ const variantSchema = z.object({
14
+ id: idSchema,
15
+ label: z.string().max(80).optional(),
16
+ kind: z.enum(["image", "video", "audio"]),
17
+ src: urlSchema,
18
+ /** Poster frame URL for video variants (same rules as src). */
19
+ poster: urlSchema.optional(),
20
+ /** Round this variant belongs to; same-version variants are its views. */
21
+ version: z.string().min(1).max(40).optional(),
22
+ });
23
+ const historySchema = z.object({
24
+ version: z.string().min(1).max(40),
25
+ verdict: z.enum(["approve", "redo", "reject"]).optional(),
26
+ note: z.string().max(2000).optional(),
27
+ });
28
+ const itemSchema = z.object({
29
+ id: idSchema,
30
+ title: z.string().min(1, "title is required").max(200),
31
+ /** Group id from `groups[]` below. Omitted items flow to an implicit group. */
32
+ group: idSchema.optional(),
33
+ subtitle: z.string().max(300).optional(),
34
+ /** Small key:value lines shown under the title (e.g. size, owner). */
35
+ meta: z.record(z.string().max(60), z.string().max(120)).optional(),
36
+ tags: z.array(z.string().max(40)).max(8).optional(),
37
+ variants: z.array(variantSchema).min(1, "at least one variant").max(12),
38
+ /** The version under review (defaults to the first variant's version). */
39
+ currentVersion: z.string().min(1).max(40).optional(),
40
+ /** Read-only verdicts from earlier versions, shown when one is opened. */
41
+ history: z.array(historySchema).max(12).optional(),
42
+ /** Team status: in_progress, or ready + readySince (opens a new round). */
43
+ team: itemTeamSchema.optional(),
44
+ /** Choice item: each variant is an option, picked per variant. */
45
+ pick: z.boolean().optional(),
46
+ /** Named options of a decision shown in one comparison image (2–6). */
47
+ choices: z.array(z.string().min(1).max(80)).min(2).max(6).optional(),
48
+ /** The variants are EXAMPLES of the problem, not choices (mos-9d62g):
49
+ * gallery rendering, plain Approve, verdict saved without a variant. */
50
+ evidence: z.boolean().optional(),
51
+ /** Plain-English card copy: what is wrong / what happens if approved. */
52
+ wrong: z.string().min(1).max(300).optional(),
53
+ ifApproved: z.string().min(1).max(300).optional(),
54
+ /** "Open on site" chips (new tab), https or root-relative URLs. */
55
+ links: z
56
+ .array(z.object({
57
+ label: z.string().min(1).max(40),
58
+ url: urlSchema,
59
+ }))
60
+ .max(6)
61
+ .optional(),
62
+ /** Context only: no verdict asked, excluded from the review census. */
63
+ informational: z.boolean().optional(),
64
+ /** text: the card is words, not media — no zoom chrome; `text` renders
65
+ * on a themed surface in place of the image (mos-bk1k8). */
66
+ kind: z.enum(["media", "text"]).optional(),
67
+ text: z.string().min(1).max(4000).optional(),
68
+ });
69
+ const groupSchema = z.object({
70
+ id: idSchema,
71
+ title: z.string().min(1).max(120),
72
+ });
73
+ export const mediaPickerSchema = z
74
+ .object({
75
+ title: z.string().max(200).optional(),
76
+ /** Instructions shown above the grid: rendered as markdown when the text
77
+ * carries markdown syntax (paragraphs, lists, bold, links), plain text
78
+ * renders unchanged (mos-8vdg9). */
79
+ intro: z.string().max(2000).optional(),
80
+ /** Optional named sections, in display order. */
81
+ groups: z.array(groupSchema).max(20).optional(),
82
+ items: z.array(itemSchema).max(200),
83
+ })
84
+ .check((ctx) => {
85
+ // Cross-array rules zod's per-field checks can't express: unique item ids
86
+ // (state keys on them), unique variant ids inside an item, and every
87
+ // item's group must exist in groups[] (or be omitted).
88
+ const value = ctx.value;
89
+ const itemIds = new Set();
90
+ value.items.forEach((it, i) => {
91
+ if (itemIds.has(it.id)) {
92
+ ctx.issues.push({
93
+ code: "custom",
94
+ input: it,
95
+ path: ["items", i, "id"],
96
+ message: `duplicate item id "${it.id}"`,
97
+ });
98
+ }
99
+ itemIds.add(it.id);
100
+ const vIds = new Set();
101
+ it.variants.forEach((v, vi) => {
102
+ if (vIds.has(v.id)) {
103
+ ctx.issues.push({
104
+ code: "custom",
105
+ input: v,
106
+ path: ["items", i, "variants", vi, "id"],
107
+ message: `duplicate variant id "${v.id}" in item "${it.id}"`,
108
+ });
109
+ }
110
+ vIds.add(v.id);
111
+ });
112
+ if (it.currentVersion &&
113
+ !it.variants.some((v) => v.version === it.currentVersion)) {
114
+ ctx.issues.push({
115
+ code: "custom",
116
+ input: it,
117
+ path: ["items", i, "currentVersion"],
118
+ message: `currentVersion "${it.currentVersion}" is not the version of any variant in item "${it.id}"`,
119
+ });
120
+ }
121
+ if (it.choices) {
122
+ if (it.variants.length !== 1) {
123
+ ctx.issues.push({
124
+ code: "custom",
125
+ input: it,
126
+ path: ["items", i, "choices"],
127
+ message: `choices names the options of ONE comparison image — item "${it.id}" has ${it.variants.length} variants; give each option its own variant and pick: true instead`,
128
+ });
129
+ }
130
+ const cIds = new Set();
131
+ it.choices.forEach((c, ci) => {
132
+ const cid = choiceId(c, ci);
133
+ if (cIds.has(cid)) {
134
+ ctx.issues.push({
135
+ code: "custom",
136
+ input: it,
137
+ path: ["items", i, "choices", ci],
138
+ message: `duplicate choice id "${cid}" in item "${it.id}"`,
139
+ });
140
+ }
141
+ cIds.add(cid);
142
+ if (it.variants.some((v) => v.id === cid)) {
143
+ ctx.issues.push({
144
+ code: "custom",
145
+ input: it,
146
+ path: ["items", i, "choices", ci],
147
+ message: `choice "${cid}" collides with a variant id in item "${it.id}"`,
148
+ });
149
+ }
150
+ });
151
+ }
152
+ if (it.informational && (it.pick || it.choices)) {
153
+ ctx.issues.push({
154
+ code: "custom",
155
+ input: it,
156
+ path: ["items", i, "informational"],
157
+ message: `informational item "${it.id}" asks no decision — it cannot also be pick or carry choices`,
158
+ });
159
+ }
160
+ // mos-9d62g: an evidence card's verdict approves the CARD (the images
161
+ // are examples of the problem) — it cannot also be a choice.
162
+ if (it.evidence && (it.pick || it.choices)) {
163
+ ctx.issues.push({
164
+ code: "custom",
165
+ input: it,
166
+ path: ["items", i, "evidence"],
167
+ message: `evidence item "${it.id}" is not a choice — the examples show the problem and the Approve answers the card; move the options to pick/choices on a separate item`,
168
+ });
169
+ }
170
+ if (it.text && it.kind !== "text") {
171
+ ctx.issues.push({
172
+ code: "custom",
173
+ input: it,
174
+ path: ["items", i, "text"],
175
+ message: `item "${it.id}" carries text but is not kind: "text" — set kind: "text" so the card renders the words`,
176
+ });
177
+ }
178
+ if (it.group && !value.groups?.some((g) => g.id === it.group)) {
179
+ ctx.issues.push({
180
+ code: "custom",
181
+ input: it,
182
+ path: ["items", i, "group"],
183
+ message: `group "${it.group}" is not in groups[]`,
184
+ });
185
+ }
186
+ });
187
+ const groupIds = new Set();
188
+ value.groups?.forEach((g, i) => {
189
+ if (groupIds.has(g.id)) {
190
+ ctx.issues.push({
191
+ code: "custom",
192
+ input: g,
193
+ path: ["groups", i, "id"],
194
+ message: `duplicate group id "${g.id}"`,
195
+ });
196
+ }
197
+ groupIds.add(g.id);
198
+ });
199
+ });
@@ -0,0 +1,92 @@
1
+ /**
2
+ * media-picker param types (mos-vzame) — the hand-written mirror of the zod
3
+ * schema in block.ts. The def annotates `schema: z.ZodType<MediaPickerParams>`,
4
+ * so any drift between schema and these interfaces is a compile error, and
5
+ * the Svelte component can import types WITHOUT importing the def module
6
+ * (block.ts imports the component; the edge must stay one-way).
7
+ */
8
+ import type { ItemTeam } from "../../review/team.js";
9
+ import type { MediaDecision } from "./decisions.js";
10
+ export type MediaVariant = {
11
+ id: string;
12
+ label?: string | undefined;
13
+ kind: "image" | "video" | "audio";
14
+ src: string;
15
+ poster?: string | undefined;
16
+ /** Which round this variant belongs to (e.g. "v6"). Variants sharing a
17
+ * version are VIEWS of it (the loop, a strip, a map). mos-0wvol. */
18
+ version?: string | undefined;
19
+ };
20
+ /** A verdict recorded on an EARLIER version of an item (read-only). */
21
+ export type MediaVersionVerdict = {
22
+ version: string;
23
+ verdict?: "approve" | "redo" | "reject" | undefined;
24
+ note?: string | undefined;
25
+ };
26
+ export type MediaItem = {
27
+ id: string;
28
+ title: string;
29
+ group?: string | undefined;
30
+ subtitle?: string | undefined;
31
+ meta?: Record<string, string> | undefined;
32
+ tags?: string[] | undefined;
33
+ variants: MediaVariant[];
34
+ /** The version under review; defaults to the first variant's version. */
35
+ currentVersion?: string | undefined;
36
+ /** Verdicts the viewer left on earlier versions (review-kit history). */
37
+ history?: MediaVersionVerdict[] | undefined;
38
+ /** Team status (D7): `ready` + readySince opens a new review round. */
39
+ team?: ItemTeam | undefined;
40
+ /** This item is a CHOICE, not a review: each variant is an option and a
41
+ * per-variant "Pick this" button replaces the single Approve (mos-lwbbt).
42
+ * The pick saves exactly what an approve saves — verdict + variant. */
43
+ pick?: boolean | undefined;
44
+ /** Named options of a decision shown in ONE comparison image (the "A vs
45
+ * B" sheet): renders a Pick button per choice; the pick is saved as
46
+ * `variant: <choice id>` — the slug of the choice's label. Requires a
47
+ * single variant; multi-variant options belong in `variants[]` with
48
+ * `pick: true`. */
49
+ choices?: string[] | undefined;
50
+ /** The variants are EXAMPLES of the problem (an audit card's screenshots),
51
+ * not choices (mos-9d62g): the card renders them as a gallery with
52
+ * captions and the verdict stays a plain Approve — of the CARD, saved
53
+ * with NO variant, so it never reads "Approve <image name>". */
54
+ evidence?: boolean | undefined;
55
+ /** Plain-English card copy, one labeled line each (mos-9d62g): what is
56
+ * wrong, and what happens if you approve. Written for a reader who has
57
+ * never seen the app — the build lints both for length and jargon. */
58
+ wrong?: string | undefined;
59
+ ifApproved?: string | undefined;
60
+ /** "Open on site" chips under the card (mos-9d62g): where to see the
61
+ * thing being judged, in a new tab. Label defaults to "Open on site". */
62
+ links?: MediaItemLink[] | undefined;
63
+ /** Context only — no verdict is asked (a live-site reference shot, a
64
+ * spec sheet). Renders without any verdict chrome and is excluded from
65
+ * the page's review census, so it never blocks "Send answers"
66
+ * (mos-lwbbt, Joe's steer). */
67
+ informational?: boolean | undefined;
68
+ /** `text`: the card is WORDS (a transcript, a copy line), not media
69
+ * (mos-bk1k8). The full-size view drops the zoom chrome, and when `text`
70
+ * is given the words render on a themed surface instead of the image —
71
+ * never a white slab in dark mode. Default `media`. */
72
+ kind?: "media" | "text" | undefined;
73
+ /** The words of a `kind: "text"` card, shown in place of its image. */
74
+ text?: string | undefined;
75
+ };
76
+ export type MediaGroup = {
77
+ id: string;
78
+ title: string;
79
+ };
80
+ /** One "Open on site" chip: an https:// or root-relative URL (mos-9d62g). */
81
+ export type MediaItemLink = {
82
+ label: string;
83
+ url: string;
84
+ };
85
+ export type MediaPickerParams = {
86
+ title?: string | undefined;
87
+ intro?: string | undefined;
88
+ groups?: MediaGroup[] | undefined;
89
+ items: MediaItem[];
90
+ };
91
+ /** Re-exported for hosts that want one import surface. */
92
+ export type { MediaDecision };