@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,193 @@
1
+ <script lang="ts" module>
2
+ import type { VerdictOption } from "../../actions/reviewKeys"
3
+
4
+ /** The per-item note's ReviewNote props (MOS ReviewComposer note mode). */
5
+ export interface ItemNoteProps {
6
+ value?: string
7
+ verdict?: string | null
8
+ options?: readonly VerdictOption[]
9
+ round?: string | number | null
10
+ saveFailed?: boolean
11
+ onSave?: (
12
+ text: string,
13
+ ctx: { round: string | number | null },
14
+ ) => void | Promise<void>
15
+ onInput?: (text: string) => void
16
+ debounceMs?: number
17
+ returnFocusTo?: () => HTMLElement | null | undefined
18
+ id?: string
19
+ label?: string
20
+ placeholder?: string
21
+ disabled?: boolean
22
+ }
23
+ </script>
24
+
25
+ <script lang="ts">
26
+ /**
27
+ * ItemNote — the per-item verdict note of the item-review blocks
28
+ * (media-picker, diff-review). Ported from MOS ReviewComposer's `note`
29
+ * mode: jatui ReviewNote IS the text leg (its round model,
30
+ * open-on-negative-verdict and debounced save are unchanged), and an
31
+ * attach lane rides under it while the note is open.
32
+ *
33
+ * The upload leg is the HOST's (`HostAdapter.upload(artifactId)`): it
34
+ * stores the bytes first and returns the descriptor, which this hands to
35
+ * `onClipsChange` for the block to save on the item's entry. No upload
36
+ * leg → text-only, exactly like the plain composer. Recording (voice /
37
+ * video / screen) is the host composer's job, not this lane's.
38
+ */
39
+ import ReviewNote from "../../components/review/ReviewNote.svelte"
40
+ import AttachmentChip from "../AttachmentChip.svelte"
41
+ import { hostAdapter } from "../adapter.js"
42
+ import type { NoteAttachment } from "../types.js"
43
+
44
+ let {
45
+ artifactId,
46
+ note,
47
+ open = $bindable(false),
48
+ clips = [],
49
+ onClipsChange = undefined,
50
+ disabled = false,
51
+ maxClips = 10,
52
+ }: {
53
+ artifactId: string
54
+ note: ItemNoteProps
55
+ open?: boolean
56
+ /** The item's saved attachments (seed). */
57
+ clips?: NoteAttachment[]
58
+ onClipsChange?: (clips: NoteAttachment[]) => void
59
+ disabled?: boolean
60
+ maxClips?: number
61
+ } = $props()
62
+
63
+ const adapter = hostAdapter()
64
+ const upload = $derived(adapter.upload?.(artifactId))
65
+ const Chip = adapter.attachmentChip ?? AttachmentChip
66
+
67
+ // The local copy follows the saved list until this note changes it.
68
+ let local = $state<NoteAttachment[] | null>(null)
69
+ const shown = $derived(local ?? clips)
70
+ let uploading = $state(0)
71
+ let error = $state<string | null>(null)
72
+ let fileEl = $state<HTMLInputElement | null>(null)
73
+ const canAttach = $derived(Boolean(upload) && !disabled)
74
+
75
+ function setClips(next: NoteAttachment[]) {
76
+ local = next
77
+ onClipsChange?.(next)
78
+ }
79
+
80
+ async function addFiles(files: Iterable<File>) {
81
+ if (!upload) return
82
+ for (const f of files) {
83
+ if (shown.length + uploading >= maxClips) {
84
+ error = `At most ${maxClips} attachments per note.`
85
+ break
86
+ }
87
+ uploading++
88
+ error = null
89
+ try {
90
+ const clip = await upload(f, "file")
91
+ setClips([...shown, clip])
92
+ } catch (e) {
93
+ error =
94
+ e instanceof Error && e.message
95
+ ? e.message
96
+ : "Couldn't save that attachment. Please try again."
97
+ } finally {
98
+ uploading--
99
+ }
100
+ }
101
+ }
102
+
103
+ function onPaste(e: ClipboardEvent) {
104
+ if (!canAttach) return
105
+ const files = [...(e.clipboardData?.files ?? [])]
106
+ if (files.length === 0) return
107
+ e.preventDefault()
108
+ void addFiles(files)
109
+ }
110
+
111
+ function onDrop(e: DragEvent) {
112
+ if (!canAttach) return
113
+ const files = [...(e.dataTransfer?.files ?? [])]
114
+ if (files.length === 0) return
115
+ e.preventDefault()
116
+ void addFiles(files)
117
+ }
118
+ </script>
119
+
120
+ <div
121
+ role="group"
122
+ aria-label="Review note"
123
+ ondragover={(e) => canAttach && e.preventDefault()}
124
+ ondrop={onDrop}
125
+ onpaste={onPaste}
126
+ data-item-note
127
+ >
128
+ <ReviewNote
129
+ bind:open
130
+ value={note.value ?? ""}
131
+ verdict={note.verdict ?? null}
132
+ options={note.options ?? []}
133
+ round={note.round ?? null}
134
+ saveFailed={note.saveFailed ?? false}
135
+ onSave={note.onSave}
136
+ onInput={note.onInput}
137
+ debounceMs={note.debounceMs}
138
+ returnFocusTo={note.returnFocusTo}
139
+ label={note.label ?? "Note"}
140
+ placeholder={note.placeholder ?? "What needs to change?"}
141
+ id={note.id}
142
+ disabled={note.disabled ?? disabled}
143
+ />
144
+ {#if open}
145
+ {#if shown.length > 0 || uploading > 0}
146
+ <ul class="mt-2 flex flex-wrap items-center gap-1.5" aria-label="Attachments">
147
+ {#each shown as clip (clip.name)}
148
+ <li class="flex items-center gap-0.5">
149
+ <Chip {clip} />
150
+ {#if !disabled}
151
+ <button
152
+ type="button"
153
+ class="btn btn-ghost btn-xs btn-circle"
154
+ aria-label="Remove {clip.label ?? clip.name}"
155
+ onclick={() => setClips(shown.filter((c) => c !== clip))}>✕</button
156
+ >
157
+ {/if}
158
+ </li>
159
+ {/each}
160
+ {#if uploading > 0}
161
+ <li class="text-base-content/60 flex items-center gap-1.5 text-xs" role="status">
162
+ <span class="loading loading-spinner loading-xs"></span>Attaching…
163
+ </li>
164
+ {/if}
165
+ </ul>
166
+ {/if}
167
+ {#if error}
168
+ <p role="alert" class="text-error mt-1 text-xs">{error}</p>
169
+ {/if}
170
+ {#if canAttach}
171
+ <div class="mt-2 flex flex-wrap items-center gap-2">
172
+ <input
173
+ bind:this={fileEl}
174
+ type="file"
175
+ multiple
176
+ class="hidden"
177
+ onchange={(e) => {
178
+ const input = e.currentTarget as HTMLInputElement
179
+ void addFiles([...(input.files ?? [])])
180
+ input.value = ""
181
+ }}
182
+ />
183
+ <button
184
+ type="button"
185
+ class="btn btn-ghost btn-xs rounded-field"
186
+ onclick={() => fileEl?.click()}
187
+ >
188
+ Attach file
189
+ </button>
190
+ </div>
191
+ {/if}
192
+ {/if}
193
+ </div>
@@ -0,0 +1,33 @@
1
+ import type { VerdictOption } from "../../actions/reviewKeys";
2
+ /** The per-item note's ReviewNote props (MOS ReviewComposer note mode). */
3
+ export interface ItemNoteProps {
4
+ value?: string;
5
+ verdict?: string | null;
6
+ options?: readonly VerdictOption[];
7
+ round?: string | number | null;
8
+ saveFailed?: boolean;
9
+ onSave?: (text: string, ctx: {
10
+ round: string | number | null;
11
+ }) => void | Promise<void>;
12
+ onInput?: (text: string) => void;
13
+ debounceMs?: number;
14
+ returnFocusTo?: () => HTMLElement | null | undefined;
15
+ id?: string;
16
+ label?: string;
17
+ placeholder?: string;
18
+ disabled?: boolean;
19
+ }
20
+ import type { NoteAttachment } from "../types.js";
21
+ type $$ComponentProps = {
22
+ artifactId: string;
23
+ note: ItemNoteProps;
24
+ open?: boolean;
25
+ /** The item's saved attachments (seed). */
26
+ clips?: NoteAttachment[];
27
+ onClipsChange?: (clips: NoteAttachment[]) => void;
28
+ disabled?: boolean;
29
+ maxClips?: number;
30
+ };
31
+ declare const ItemNote: import("svelte").Component<$$ComponentProps, {}, "open">;
32
+ type ItemNote = ReturnType<typeof ItemNote>;
33
+ export default ItemNote;
@@ -0,0 +1,52 @@
1
+ <script lang="ts">
2
+ /**
3
+ * NoteToggle — the "Note" button beside a review item's VerdictControl
4
+ * (media-picker, diff-review). It shows / hides the item's jatui ReviewNote
5
+ * and carries a marker when the current round already has a note.
6
+ */
7
+ import BlockIcon from "./BlockIcon.svelte"
8
+
9
+ let {
10
+ open,
11
+ hasNote,
12
+ controls,
13
+ itemTitle,
14
+ /** mos-guqgs: a surface that cannot save renders the toggle visibly
15
+ * disabled — never clickable-but-useless. */
16
+ disabled = false,
17
+ onToggle,
18
+ }: {
19
+ open: boolean
20
+ hasNote: boolean
21
+ /** id of the note's textarea (ReviewNote `id`). */
22
+ controls: string
23
+ itemTitle: string
24
+ disabled?: boolean
25
+ onToggle: () => void
26
+ } = $props()
27
+ </script>
28
+
29
+ <button
30
+ type="button"
31
+ class="btn btn-ghost btn-sm ml-auto gap-1 rounded-field px-2 {open
32
+ ? 'text-base-content'
33
+ : 'text-base-content/75'}"
34
+ aria-expanded={open}
35
+ aria-controls={controls}
36
+ aria-label="Note for {itemTitle}"
37
+ aria-keyshortcuts="n"
38
+ {disabled}
39
+ data-has-note={hasNote ? "true" : undefined}
40
+ data-note-toggle
41
+ onclick={onToggle}
42
+ >
43
+ Note
44
+ {#if hasNote}
45
+ <BlockIcon name="sticky-note"
46
+ size={14}
47
+ stroke-width={2.25}
48
+ class="text-primary"
49
+ aria-hidden="true"
50
+ /><span class="sr-only">(has a note)</span>
51
+ {/if}
52
+ </button>
@@ -0,0 +1,12 @@
1
+ type $$ComponentProps = {
2
+ open: boolean;
3
+ hasNote: boolean;
4
+ /** id of the note's textarea (ReviewNote `id`). */
5
+ controls: string;
6
+ itemTitle: string;
7
+ disabled?: boolean;
8
+ onToggle: () => void;
9
+ };
10
+ declare const NoteToggle: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type NoteToggle = ReturnType<typeof NoteToggle>;
12
+ export default NoteToggle;
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ /**
3
+ * TeamChip — an item's team status (D7, ./team.ts), rendered like the
4
+ * checklist's Team column: quiet "Team" label, a tone icon, the words.
5
+ * `retest` adds the Retest chip: a new round is open (the team marked it
6
+ * ready, or a new version is up) and this viewer has not decided it yet.
7
+ * With no `team` only the Retest chip renders.
8
+ */
9
+ import BlockIcon from "./BlockIcon.svelte"
10
+ import { ITEM_TEAM_LABEL, type ItemTeam } from "./team.js"
11
+
12
+ let {
13
+ team,
14
+ retest = false,
15
+ }: {
16
+ team?: ItemTeam | undefined
17
+ retest?: boolean
18
+ } = $props()
19
+
20
+ const ICON = { ready: "rotate-ccw", in_progress: "circle-dot-dashed" } as const
21
+ const TONE = {
22
+ ready: "text-info",
23
+ in_progress: "text-base-content/70",
24
+ } as const
25
+ const since = $derived.by(() => {
26
+ if (!team?.readySince) return ""
27
+ const t = Date.parse(team.readySince)
28
+ return Number.isNaN(t)
29
+ ? ""
30
+ : new Date(t).toLocaleString("en-US", {
31
+ month: "short",
32
+ day: "numeric",
33
+ hour: "numeric",
34
+ minute: "2-digit",
35
+ })
36
+ })
37
+ </script>
38
+
39
+ <span class="inline-flex flex-wrap items-center gap-1.5 text-xs">
40
+ {#if team}
41
+ <span
42
+ class="inline-flex items-center gap-1"
43
+ data-team-status={team.status}
44
+ title="Team status: what the last publish recorded"
45
+ >
46
+ <span class="text-base-content/70"
47
+ >Team<span class="sr-only">{": "}</span></span
48
+ >
49
+ <BlockIcon
50
+ name={ICON[team.status]}
51
+ size={14}
52
+ stroke-width={2.25}
53
+ class={TONE[team.status]}
54
+ aria-hidden="true"
55
+ />
56
+ <span class="font-medium text-base-content"
57
+ >{ITEM_TEAM_LABEL[team.status]}</span
58
+ >
59
+ </span>
60
+ {/if}
61
+ {#if retest}
62
+ <span
63
+ class="badge badge-sm badge-info"
64
+ data-retest-chip
65
+ title={since
66
+ ? `Ready since ${since}: your earlier verdict is locked below, decide again`
67
+ : "A new round is up: your earlier verdict is locked below, decide again"}
68
+ >Retest</span
69
+ >
70
+ {/if}
71
+ </span>
@@ -0,0 +1,8 @@
1
+ import { type ItemTeam } from "./team.js";
2
+ type $$ComponentProps = {
3
+ team?: ItemTeam | undefined;
4
+ retest?: boolean;
5
+ };
6
+ declare const TeamChip: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type TeamChip = ReturnType<typeof TeamChip>;
8
+ export default TeamChip;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Reading saved note attachments back out of a block's state (untrusted
3
+ * stored JSON) — ported from MOS modules/artifacts/noteAttachments.ts
4
+ * (readNoteAttachments) for the item-review blocks. The descriptors were
5
+ * validated by the HOST on the write path; this only re-shapes them, and an
6
+ * entry that is not a descriptor drops the whole list (never half a note).
7
+ */
8
+ import type { NoteAttachment } from "../types.js";
9
+ export type { NoteAttachment };
10
+ export declare function readNoteAttachments(raw: unknown): NoteAttachment[] | undefined;
@@ -0,0 +1,37 @@
1
+ const ATTACHMENT_KINDS = [
2
+ "screen",
3
+ "voice",
4
+ "audio",
5
+ "video",
6
+ "image",
7
+ "file",
8
+ ];
9
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10
+ export function readNoteAttachments(raw) {
11
+ if (!Array.isArray(raw) || raw.length === 0)
12
+ return undefined;
13
+ const out = [];
14
+ for (const entry of raw) {
15
+ if (!isObj(entry))
16
+ return undefined;
17
+ if (typeof entry.name !== "string" || typeof entry.url !== "string")
18
+ return undefined;
19
+ out.push({
20
+ name: entry.name,
21
+ url: entry.url,
22
+ contentType: typeof entry.contentType === "string" ? entry.contentType : "",
23
+ sizeBytes: typeof entry.sizeBytes === "number" ? entry.sizeBytes : 0,
24
+ kind: ATTACHMENT_KINDS.includes(entry.kind)
25
+ ? entry.kind
26
+ : "file",
27
+ durationSeconds: typeof entry.durationSeconds === "number"
28
+ ? entry.durationSeconds
29
+ : null,
30
+ transcript: typeof entry.transcript === "string" ? entry.transcript : null,
31
+ ...(typeof entry.label === "string" && entry.label
32
+ ? { label: entry.label }
33
+ : {}),
34
+ });
35
+ }
36
+ return out;
37
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Bulk verdicts for the review blocks (mos-nv4eb, mos-u8i35): accept OR
3
+ * reject many items in ONE save, and keep what Undo needs to put them back.
4
+ *
5
+ * Joe 2026-10-19: "bulk accept OR reject, and then go through and flip the
6
+ * outliers either way." Which items a bulk may touch is the block's call
7
+ * (still undecided, not a retest, shown under the active filter); this module
8
+ * only writes the verdict through the block's own round model, so a bulk
9
+ * entry is byte-for-byte what a single click would have saved.
10
+ */
11
+ /** The `save` of a block's `useRounds()` binding (../review/rounds.ts). */
12
+ type RoundSave = (stored: unknown, patch: Record<string, unknown>, current: string | null | undefined) => Record<string, unknown>;
13
+ type Entry = {
14
+ verdict?: string;
15
+ at?: string;
16
+ };
17
+ export type BulkTarget = {
18
+ id: string;
19
+ /** The round the item is in (its team readySince / version). */
20
+ round: string | null;
21
+ /** Extra fields saved with the verdict (a pinned variant…). */
22
+ patch?: Record<string, unknown>;
23
+ };
24
+ export type BulkUndo<D> = {
25
+ /** The verdict the bulk cast. */
26
+ verdict: string;
27
+ /** The one `at` stamp every bulk-written entry carries. */
28
+ at: string;
29
+ /** Each touched item's entry before the bulk (undefined = it had none). */
30
+ prior: Record<string, D | undefined>;
31
+ };
32
+ /**
33
+ * Cast `verdict` on every target. A non-empty `note` is the shared note a
34
+ * bulk reject asks for ("Why? applies to all N"); it joins each target's own
35
+ * draft note rather than replacing it.
36
+ */
37
+ export declare function decideMany<D extends Entry>(decisions: Record<string, D>, targets: readonly BulkTarget[], opts: {
38
+ verdict: string;
39
+ at: string;
40
+ note?: string;
41
+ save: RoundSave;
42
+ }): {
43
+ decisions: Record<string, D>;
44
+ undo: BulkUndo<D>;
45
+ };
46
+ /**
47
+ * Put every bulk-decided item back exactly as it was. An item the reviewer
48
+ * changed AFTER the bulk (a flip, a note — anything that re-stamps `at`)
49
+ * keeps that later change: Undo reverts the bulk, not their work.
50
+ */
51
+ export declare function undoMany<D extends Entry>(decisions: Record<string, D>, undo: BulkUndo<D>): Record<string, D>;
52
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Bulk verdicts for the review blocks (mos-nv4eb, mos-u8i35): accept OR
3
+ * reject many items in ONE save, and keep what Undo needs to put them back.
4
+ *
5
+ * Joe 2026-10-19: "bulk accept OR reject, and then go through and flip the
6
+ * outliers either way." Which items a bulk may touch is the block's call
7
+ * (still undecided, not a retest, shown under the active filter); this module
8
+ * only writes the verdict through the block's own round model, so a bulk
9
+ * entry is byte-for-byte what a single click would have saved.
10
+ */
11
+ /** Join a shared bulk note onto a line's own draft note: typing is never
12
+ * thrown away, the shared reason is never lost. */
13
+ function joinNote(own, shared) {
14
+ const s = shared.trim();
15
+ if (!own?.trim())
16
+ return s;
17
+ return own.includes(s) ? own : `${own.trimEnd()}\n\n${s}`;
18
+ }
19
+ /**
20
+ * Cast `verdict` on every target. A non-empty `note` is the shared note a
21
+ * bulk reject asks for ("Why? applies to all N"); it joins each target's own
22
+ * draft note rather than replacing it.
23
+ */
24
+ export function decideMany(decisions, targets, opts) {
25
+ const next = { ...decisions };
26
+ const prior = {};
27
+ const shared = opts.note?.trim();
28
+ for (const { id, round, patch } of targets) {
29
+ const before = decisions[id];
30
+ prior[id] = before;
31
+ const own = before?.note;
32
+ next[id] = opts.save(before, {
33
+ ...patch,
34
+ verdict: opts.verdict,
35
+ ...(shared ? { note: joinNote(own, shared) } : {}),
36
+ at: opts.at,
37
+ }, round);
38
+ }
39
+ return {
40
+ decisions: next,
41
+ undo: { verdict: opts.verdict, at: opts.at, prior },
42
+ };
43
+ }
44
+ /**
45
+ * Put every bulk-decided item back exactly as it was. An item the reviewer
46
+ * changed AFTER the bulk (a flip, a note — anything that re-stamps `at`)
47
+ * keeps that later change: Undo reverts the bulk, not their work.
48
+ */
49
+ export function undoMany(decisions, undo) {
50
+ const next = { ...decisions };
51
+ for (const [id, before] of Object.entries(undo.prior)) {
52
+ const now = next[id];
53
+ if (!now || now.at !== undo.at || now.verdict !== undo.verdict)
54
+ continue;
55
+ if (before)
56
+ next[id] = before;
57
+ else
58
+ delete next[id];
59
+ }
60
+ return next;
61
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * The item-review hooks of media-picker and diff-review (jatui-n5z1x):
3
+ * what the core census, still-open walk and Send read through
4
+ * `ArtifactBlockDef.itemReview` (types.ts BlockItemReview).
5
+ *
6
+ * A faithful port of MOS modules/artifacts/feedbackReview.ts at bad16b324 —
7
+ * `reviewItemsOf` (the non-checklist branch), `itemRoundOf`, `itemLabelOf`
8
+ * and `mediaThumbOf` — so a page Send here and a desk Send in MOS count
9
+ * identically (CONTRACT.md "Item-review rules"). Params and state are the
10
+ * AUTHORED / stored JSON (untrusted): nothing here may throw.
11
+ */
12
+ import type { ReviewItem } from "../types.js";
13
+ type Rounds = {
14
+ isStale: (entry: unknown, current: string | null | undefined) => boolean;
15
+ };
16
+ /** An item's `team: { status: "ready", readySince }` round id, else null. */
17
+ export declare function teamReadySince(it: Record<string, unknown>): string | null;
18
+ /**
19
+ * The round an item is in: the team's readySince when it marked the item
20
+ * ready, else (media only) the version under review — `currentVersion`,
21
+ * else the first variant's `version`. null = the item has no rounds.
22
+ */
23
+ export declare function itemRoundOf(kind: "media" | "diff", it: Record<string, unknown>): string | null;
24
+ /** The items of one media-picker / diff-review block, in its order. */
25
+ export declare function itemsOf(kind: "media" | "diff", rounds: Rounds, params: unknown, state: unknown): ReviewItem[];
26
+ /** An item's title for a jump card; the item id when absent. */
27
+ export declare function itemLabelOf(params: unknown, itemId: string): string;
28
+ /**
29
+ * A media item's thumbnail: the round under review's first image (or a
30
+ * video's poster frame), else any image variant; undefined when none.
31
+ */
32
+ export declare function mediaThumbOf(params: unknown, itemId: string): string | undefined;
33
+ export {};
@@ -0,0 +1,112 @@
1
+ const MAX_NOTE = 2000;
2
+ const ITEM_VERDICTS = new Map([
3
+ ["approve", "approved"],
4
+ ["pass", "approved"],
5
+ ["accept", "approved"],
6
+ ["redo", "needs-work"],
7
+ ["modify", "needs-work"],
8
+ ["fail", "needs-work"],
9
+ ["blocked", "needs-work"],
10
+ ["reject", "rejected"],
11
+ ]);
12
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
13
+ /** An item's `team: { status: "ready", readySince }` round id, else null. */
14
+ export function teamReadySince(it) {
15
+ const team = isObj(it.team) ? it.team : null;
16
+ return team?.status === "ready" &&
17
+ typeof team.readySince === "string" &&
18
+ team.readySince
19
+ ? team.readySince
20
+ : null;
21
+ }
22
+ /**
23
+ * The round an item is in: the team's readySince when it marked the item
24
+ * ready, else (media only) the version under review — `currentVersion`,
25
+ * else the first variant's `version`. null = the item has no rounds.
26
+ */
27
+ export function itemRoundOf(kind, it) {
28
+ const ready = teamReadySince(it);
29
+ if (ready || kind !== "media")
30
+ return ready;
31
+ if (typeof it.currentVersion === "string" && it.currentVersion)
32
+ return it.currentVersion;
33
+ for (const v of Array.isArray(it.variants) ? it.variants : [])
34
+ if (isObj(v) && typeof v.version === "string" && v.version)
35
+ return v.version;
36
+ return null;
37
+ }
38
+ /** The items of one media-picker / diff-review block, in its order. */
39
+ export function itemsOf(kind, rounds, params, state) {
40
+ const p = isObj(params) ? params : {};
41
+ const st = isObj(state) ? state : {};
42
+ const out = [];
43
+ for (const it of Array.isArray(p.items) ? p.items : []) {
44
+ if (!isObj(it) || typeof it.id !== "string")
45
+ continue;
46
+ // An informational item asks no decision: context, not a unit — it
47
+ // never counts and can never block a Send.
48
+ if (it.informational === true)
49
+ continue;
50
+ const rawItem = st[it.id];
51
+ const d = isObj(rawItem) ? rawItem : null;
52
+ const raw = d && typeof d.verdict === "string" ? d.verdict.trim().toLowerCase() : "";
53
+ // A verdict (and its note) from an earlier round is not this round's
54
+ // answer — the block shows the item blank, so nothing here counts it.
55
+ const stale = d !== null && rounds.isStale(d, itemRoundOf(kind, it));
56
+ const verdict = stale ? null : (ITEM_VERDICTS.get(raw) ?? null);
57
+ const item = {
58
+ id: it.id,
59
+ verdict,
60
+ raw: verdict ? raw : null,
61
+ explicit: verdict !== null,
62
+ retest: verdict === null && (teamReadySince(it) !== null || stale),
63
+ };
64
+ if (d && !stale) {
65
+ if (typeof d.note === "string" && d.note.trim())
66
+ item.note = d.note.trim().slice(0, MAX_NOTE);
67
+ if (Array.isArray(d.attachments) && d.attachments.length > 0)
68
+ item.attachments = d.attachments;
69
+ }
70
+ out.push(item);
71
+ }
72
+ return out;
73
+ }
74
+ /** An item's title for a jump card; the item id when absent. */
75
+ export function itemLabelOf(params, itemId) {
76
+ const p = isObj(params) ? params : {};
77
+ for (const raw of Array.isArray(p.items) ? p.items : [])
78
+ if (isObj(raw) && raw.id === itemId && typeof raw.title === "string")
79
+ return raw.title;
80
+ return itemId;
81
+ }
82
+ /**
83
+ * A media item's thumbnail: the round under review's first image (or a
84
+ * video's poster frame), else any image variant; undefined when none.
85
+ */
86
+ export function mediaThumbOf(params, itemId) {
87
+ const p = isObj(params) ? params : {};
88
+ for (const raw of Array.isArray(p.items) ? p.items : []) {
89
+ if (!isObj(raw) || raw.id !== itemId)
90
+ continue;
91
+ const variants = (Array.isArray(raw.variants) ? raw.variants : []).filter(isObj);
92
+ const currentVersion = typeof raw.currentVersion === "string" && raw.currentVersion
93
+ ? raw.currentVersion
94
+ : variants.find((v) => typeof v.version === "string" && v.version)
95
+ ?.version;
96
+ const inRound = (v) => currentVersion === undefined ||
97
+ typeof v.version !== "string" ||
98
+ v.version === currentVersion;
99
+ const img = variants.find((v) => inRound(v) && v.kind === "image" && typeof v.src === "string") ??
100
+ // No image in this round: a stale round's image still orients better
101
+ // than an empty tile.
102
+ variants.find((v) => v.kind === "image" && typeof v.src === "string");
103
+ const poster = variants.find((v) => inRound(v) && v.kind === "video" && typeof v.poster === "string");
104
+ const hit = img ?? poster;
105
+ if (!hit)
106
+ return undefined;
107
+ if (typeof hit.poster === "string" && hit.kind === "video")
108
+ return hit.poster;
109
+ return typeof hit.src === "string" ? hit.src : undefined;
110
+ }
111
+ return undefined;
112
+ }