@joewinke/jatui 0.36.3 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/dist/artifact-blocks/AttachmentChip.svelte +66 -0
  2. package/dist/artifact-blocks/AttachmentChip.svelte.d.ts +7 -0
  3. package/dist/artifact-blocks/BlockComments.svelte +309 -0
  4. package/dist/artifact-blocks/BlockComments.svelte.d.ts +33 -0
  5. package/dist/artifact-blocks/BlockRenderer.svelte +674 -0
  6. package/dist/artifact-blocks/BlockRenderer.svelte.d.ts +55 -0
  7. package/dist/artifact-blocks/BlockVerdict.svelte +151 -0
  8. package/dist/artifact-blocks/BlockVerdict.svelte.d.ts +13 -0
  9. package/dist/artifact-blocks/BlocksArtifactView.svelte +1616 -0
  10. package/dist/artifact-blocks/BlocksArtifactView.svelte.d.ts +113 -0
  11. package/dist/artifact-blocks/CONTRACT.md +225 -0
  12. package/dist/artifact-blocks/CommentPill.svelte +49 -0
  13. package/dist/artifact-blocks/CommentPill.svelte.d.ts +16 -0
  14. package/dist/artifact-blocks/FoldedRounds.svelte +95 -0
  15. package/dist/artifact-blocks/FoldedRounds.svelte.d.ts +9 -0
  16. package/dist/artifact-blocks/Glyph.svelte +54 -0
  17. package/dist/artifact-blocks/Glyph.svelte.d.ts +10 -0
  18. package/dist/artifact-blocks/HistoryScrubber.svelte +500 -0
  19. package/dist/artifact-blocks/HistoryScrubber.svelte.d.ts +15 -0
  20. package/dist/artifact-blocks/Markdown.svelte +186 -0
  21. package/dist/artifact-blocks/Markdown.svelte.d.ts +30 -0
  22. package/dist/artifact-blocks/NoteComposer.svelte +31 -0
  23. package/dist/artifact-blocks/NoteComposer.svelte.d.ts +8 -0
  24. package/dist/artifact-blocks/PlainComposer.svelte +200 -0
  25. package/dist/artifact-blocks/PlainComposer.svelte.d.ts +15 -0
  26. package/dist/artifact-blocks/SectionTools.svelte +102 -0
  27. package/dist/artifact-blocks/SectionTools.svelte.d.ts +19 -0
  28. package/dist/artifact-blocks/adapter.d.ts +13 -0
  29. package/dist/artifact-blocks/adapter.js +31 -0
  30. package/dist/artifact-blocks/autoSend.d.ts +49 -0
  31. package/dist/artifact-blocks/autoSend.js +103 -0
  32. package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte +557 -0
  33. package/dist/artifact-blocks/blocks/actions/ActionsBlock.svelte.d.ts +48 -0
  34. package/dist/artifact-blocks/blocks/actions/block.d.ts +8 -0
  35. package/dist/artifact-blocks/blocks/actions/block.js +74 -0
  36. package/dist/artifact-blocks/blocks/actions/requests.d.ts +62 -0
  37. package/dist/artifact-blocks/blocks/actions/requests.js +26 -0
  38. package/dist/artifact-blocks/blocks/actions/schema.d.ts +168 -0
  39. package/dist/artifact-blocks/blocks/actions/schema.js +253 -0
  40. package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte +779 -0
  41. package/dist/artifact-blocks/blocks/callout/CalloutBlock.svelte.d.ts +40 -0
  42. package/dist/artifact-blocks/blocks/callout/block.d.ts +7 -0
  43. package/dist/artifact-blocks/blocks/callout/block.js +39 -0
  44. package/dist/artifact-blocks/blocks/callout/schema.d.ts +72 -0
  45. package/dist/artifact-blocks/blocks/callout/schema.js +115 -0
  46. package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte +875 -0
  47. package/dist/artifact-blocks/blocks/chart/ChartBlock.svelte.d.ts +59 -0
  48. package/dist/artifact-blocks/blocks/chart/block.d.ts +9 -0
  49. package/dist/artifact-blocks/blocks/chart/block.js +70 -0
  50. package/dist/artifact-blocks/blocks/chart/schema.d.ts +171 -0
  51. package/dist/artifact-blocks/blocks/chart/schema.js +498 -0
  52. package/dist/artifact-blocks/blocks/chart/thumb.d.ts +2 -0
  53. package/dist/artifact-blocks/blocks/chart/thumb.js +201 -0
  54. package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte +1048 -0
  55. package/dist/artifact-blocks/blocks/checklist/ChecklistBlock.svelte.d.ts +16 -0
  56. package/dist/artifact-blocks/blocks/checklist/Icon.svelte +105 -0
  57. package/dist/artifact-blocks/blocks/checklist/Icon.svelte.d.ts +17 -0
  58. package/dist/artifact-blocks/blocks/checklist/block.d.ts +10 -0
  59. package/dist/artifact-blocks/blocks/checklist/block.js +63 -0
  60. package/dist/artifact-blocks/blocks/checklist/itemReview.d.ts +16 -0
  61. package/dist/artifact-blocks/blocks/checklist/itemReview.js +63 -0
  62. package/dist/artifact-blocks/blocks/checklist/labels.d.ts +62 -0
  63. package/dist/artifact-blocks/blocks/checklist/labels.js +96 -0
  64. package/dist/artifact-blocks/blocks/checklist/retest.d.ts +43 -0
  65. package/dist/artifact-blocks/blocks/checklist/retest.js +68 -0
  66. package/dist/artifact-blocks/blocks/checklist/schema.d.ts +84 -0
  67. package/dist/artifact-blocks/blocks/checklist/schema.js +91 -0
  68. package/dist/artifact-blocks/blocks/code/CodeBlock.svelte +128 -0
  69. package/dist/artifact-blocks/blocks/code/CodeBlock.svelte.d.ts +12 -0
  70. package/dist/artifact-blocks/blocks/code/block.d.ts +8 -0
  71. package/dist/artifact-blocks/blocks/code/block.js +31 -0
  72. package/dist/artifact-blocks/blocks/code/highlight.d.ts +34 -0
  73. package/dist/artifact-blocks/blocks/code/highlight.js +280 -0
  74. package/dist/artifact-blocks/blocks/code/schema.d.ts +32 -0
  75. package/dist/artifact-blocks/blocks/code/schema.js +39 -0
  76. package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte +290 -0
  77. package/dist/artifact-blocks/blocks/collage/CollageBlock.svelte.d.ts +29 -0
  78. package/dist/artifact-blocks/blocks/collage/block.d.ts +9 -0
  79. package/dist/artifact-blocks/blocks/collage/block.js +29 -0
  80. package/dist/artifact-blocks/blocks/collage/schema.d.ts +52 -0
  81. package/dist/artifact-blocks/blocks/collage/schema.js +86 -0
  82. package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte +484 -0
  83. package/dist/artifact-blocks/blocks/compare/CompareBlock.svelte.d.ts +25 -0
  84. package/dist/artifact-blocks/blocks/compare/block.d.ts +9 -0
  85. package/dist/artifact-blocks/blocks/compare/block.js +32 -0
  86. package/dist/artifact-blocks/blocks/compare/schema.d.ts +74 -0
  87. package/dist/artifact-blocks/blocks/compare/schema.js +148 -0
  88. package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte +392 -0
  89. package/dist/artifact-blocks/blocks/diagram/DiagramBlock.svelte.d.ts +30 -0
  90. package/dist/artifact-blocks/blocks/diagram/block.d.ts +9 -0
  91. package/dist/artifact-blocks/blocks/diagram/block.js +50 -0
  92. package/dist/artifact-blocks/blocks/diagram/constants.d.ts +48 -0
  93. package/dist/artifact-blocks/blocks/diagram/constants.js +53 -0
  94. package/dist/artifact-blocks/blocks/diagram/schema.d.ts +161 -0
  95. package/dist/artifact-blocks/blocks/diagram/schema.js +621 -0
  96. package/dist/artifact-blocks/blocks/diagram/thumb.d.ts +2 -0
  97. package/dist/artifact-blocks/blocks/diagram/thumb.js +157 -0
  98. package/dist/artifact-blocks/blocks/diagram/types.d.ts +74 -0
  99. package/dist/artifact-blocks/blocks/diagram/types.js +1 -0
  100. package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte +81 -0
  101. package/dist/artifact-blocks/blocks/diff/DiffBlock.svelte.d.ts +10 -0
  102. package/dist/artifact-blocks/blocks/diff/block.d.ts +7 -0
  103. package/dist/artifact-blocks/blocks/diff/block.js +36 -0
  104. package/dist/artifact-blocks/blocks/diff/schema.d.ts +18 -0
  105. package/dist/artifact-blocks/blocks/diff/schema.js +35 -0
  106. package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte +1055 -0
  107. package/dist/artifact-blocks/blocks/diff-review/DiffReviewBlock.svelte.d.ts +5 -0
  108. package/dist/artifact-blocks/blocks/diff-review/block.d.ts +15 -0
  109. package/dist/artifact-blocks/blocks/diff-review/block.js +64 -0
  110. package/dist/artifact-blocks/blocks/diff-review/decisions.d.ts +57 -0
  111. package/dist/artifact-blocks/blocks/diff-review/decisions.js +80 -0
  112. package/dist/artifact-blocks/blocks/diff-review/schema.d.ts +13 -0
  113. package/dist/artifact-blocks/blocks/diff-review/schema.js +84 -0
  114. package/dist/artifact-blocks/blocks/diff-review/types.d.ts +35 -0
  115. package/dist/artifact-blocks/blocks/diff-review/types.js +1 -0
  116. package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte +145 -0
  117. package/dist/artifact-blocks/blocks/explainer/ExplainerBlock.svelte.d.ts +5 -0
  118. package/dist/artifact-blocks/blocks/explainer/block.d.ts +8 -0
  119. package/dist/artifact-blocks/blocks/explainer/block.js +21 -0
  120. package/dist/artifact-blocks/blocks/explainer/schema.d.ts +26 -0
  121. package/dist/artifact-blocks/blocks/explainer/schema.js +69 -0
  122. package/dist/artifact-blocks/blocks/group-content.d.ts +10 -0
  123. package/dist/artifact-blocks/blocks/group-content.js +16 -0
  124. package/dist/artifact-blocks/blocks/group-data.d.ts +10 -0
  125. package/dist/artifact-blocks/blocks/group-data.js +7 -0
  126. package/dist/artifact-blocks/blocks/group-review.d.ts +10 -0
  127. package/dist/artifact-blocks/blocks/group-review.js +12 -0
  128. package/dist/artifact-blocks/blocks/group-visual.d.ts +17 -0
  129. package/dist/artifact-blocks/blocks/group-visual.js +14 -0
  130. package/dist/artifact-blocks/blocks/map/MapBlock.svelte +413 -0
  131. package/dist/artifact-blocks/blocks/map/MapBlock.svelte.d.ts +53 -0
  132. package/dist/artifact-blocks/blocks/map/block.d.ts +9 -0
  133. package/dist/artifact-blocks/blocks/map/block.js +62 -0
  134. package/dist/artifact-blocks/blocks/map/schema.d.ts +123 -0
  135. package/dist/artifact-blocks/blocks/map/schema.js +113 -0
  136. package/dist/artifact-blocks/blocks/map/thumb.d.ts +2 -0
  137. package/dist/artifact-blocks/blocks/map/thumb.js +69 -0
  138. package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte +33 -0
  139. package/dist/artifact-blocks/blocks/markdown/MarkdownBlock.svelte.d.ts +15 -0
  140. package/dist/artifact-blocks/blocks/markdown/block.d.ts +7 -0
  141. package/dist/artifact-blocks/blocks/markdown/block.js +28 -0
  142. package/dist/artifact-blocks/blocks/markdown/render.d.ts +17 -0
  143. package/dist/artifact-blocks/blocks/markdown/render.js +115 -0
  144. package/dist/artifact-blocks/blocks/markdown/schema.d.ts +10 -0
  145. package/dist/artifact-blocks/blocks/markdown/schema.js +23 -0
  146. package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte +2112 -0
  147. package/dist/artifact-blocks/blocks/media-picker/MediaPickerBlock.svelte.d.ts +5 -0
  148. package/dist/artifact-blocks/blocks/media-picker/block.d.ts +24 -0
  149. package/dist/artifact-blocks/blocks/media-picker/block.js +137 -0
  150. package/dist/artifact-blocks/blocks/media-picker/choices.d.ts +28 -0
  151. package/dist/artifact-blocks/blocks/media-picker/choices.js +35 -0
  152. package/dist/artifact-blocks/blocks/media-picker/decisions.d.ts +51 -0
  153. package/dist/artifact-blocks/blocks/media-picker/decisions.js +75 -0
  154. package/dist/artifact-blocks/blocks/media-picker/schema.d.ts +7 -0
  155. package/dist/artifact-blocks/blocks/media-picker/schema.js +199 -0
  156. package/dist/artifact-blocks/blocks/media-picker/types.d.ts +92 -0
  157. package/dist/artifact-blocks/blocks/media-picker/types.js +1 -0
  158. package/dist/artifact-blocks/blocks/media-picker/versions.d.ts +51 -0
  159. package/dist/artifact-blocks/blocks/media-picker/versions.js +118 -0
  160. package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte +213 -0
  161. package/dist/artifact-blocks/blocks/mockup/MockupBlock.svelte.d.ts +5 -0
  162. package/dist/artifact-blocks/blocks/mockup/block.d.ts +26 -0
  163. package/dist/artifact-blocks/blocks/mockup/block.js +52 -0
  164. package/dist/artifact-blocks/blocks/mockup/doc.d.ts +24 -0
  165. package/dist/artifact-blocks/blocks/mockup/doc.js +44 -0
  166. package/dist/artifact-blocks/blocks/mockup/sandbox.d.ts +21 -0
  167. package/dist/artifact-blocks/blocks/mockup/sandbox.js +27 -0
  168. package/dist/artifact-blocks/blocks/mockup/schema.d.ts +7 -0
  169. package/dist/artifact-blocks/blocks/mockup/schema.js +90 -0
  170. package/dist/artifact-blocks/blocks/mockup/types.d.ts +36 -0
  171. package/dist/artifact-blocks/blocks/mockup/types.js +17 -0
  172. package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte +359 -0
  173. package/dist/artifact-blocks/blocks/program/ProgramBlock.svelte.d.ts +32 -0
  174. package/dist/artifact-blocks/blocks/program/block.d.ts +35 -0
  175. package/dist/artifact-blocks/blocks/program/block.js +68 -0
  176. package/dist/artifact-blocks/blocks/program/schema.d.ts +132 -0
  177. package/dist/artifact-blocks/blocks/program/schema.js +268 -0
  178. package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte +315 -0
  179. package/dist/artifact-blocks/blocks/stats/StatsBlock.svelte.d.ts +24 -0
  180. package/dist/artifact-blocks/blocks/stats/block.d.ts +7 -0
  181. package/dist/artifact-blocks/blocks/stats/block.js +49 -0
  182. package/dist/artifact-blocks/blocks/stats/schema.d.ts +82 -0
  183. package/dist/artifact-blocks/blocks/stats/schema.js +43 -0
  184. package/dist/artifact-blocks/blocks/stats/thumb.d.ts +2 -0
  185. package/dist/artifact-blocks/blocks/stats/thumb.js +102 -0
  186. package/dist/artifact-blocks/blocks/table/TableBlock.svelte +490 -0
  187. package/dist/artifact-blocks/blocks/table/TableBlock.svelte.d.ts +28 -0
  188. package/dist/artifact-blocks/blocks/table/block.d.ts +9 -0
  189. package/dist/artifact-blocks/blocks/table/block.js +100 -0
  190. package/dist/artifact-blocks/blocks/table/schema.d.ts +143 -0
  191. package/dist/artifact-blocks/blocks/table/schema.js +278 -0
  192. package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte +347 -0
  193. package/dist/artifact-blocks/blocks/timeline/TimelineBlock.svelte.d.ts +42 -0
  194. package/dist/artifact-blocks/blocks/timeline/block.d.ts +9 -0
  195. package/dist/artifact-blocks/blocks/timeline/block.js +96 -0
  196. package/dist/artifact-blocks/blocks/timeline/constants.d.ts +26 -0
  197. package/dist/artifact-blocks/blocks/timeline/constants.js +27 -0
  198. package/dist/artifact-blocks/blocks/timeline/schema.d.ts +102 -0
  199. package/dist/artifact-blocks/blocks/timeline/schema.js +405 -0
  200. package/dist/artifact-blocks/blocks/timeline/thumb.d.ts +2 -0
  201. package/dist/artifact-blocks/blocks/timeline/thumb.js +117 -0
  202. package/dist/artifact-blocks/blocks/timeline/types.d.ts +66 -0
  203. package/dist/artifact-blocks/blocks/timeline/types.js +1 -0
  204. package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte +169 -0
  205. package/dist/artifact-blocks/blocks/transcript/TranscriptBlock.svelte.d.ts +26 -0
  206. package/dist/artifact-blocks/blocks/transcript/block.d.ts +9 -0
  207. package/dist/artifact-blocks/blocks/transcript/block.js +86 -0
  208. package/dist/artifact-blocks/blocks/transcript/schema.d.ts +114 -0
  209. package/dist/artifact-blocks/blocks/transcript/schema.js +126 -0
  210. package/dist/artifact-blocks/builtins.d.ts +40 -0
  211. package/dist/artifact-blocks/builtins.js +22 -0
  212. package/dist/artifact-blocks/contractBlocks.d.ts +92 -0
  213. package/dist/artifact-blocks/contractBlocks.js +279 -0
  214. package/dist/artifact-blocks/history.d.ts +140 -0
  215. package/dist/artifact-blocks/history.js +363 -0
  216. package/dist/artifact-blocks/index.d.ts +40 -0
  217. package/dist/artifact-blocks/index.js +46 -0
  218. package/dist/artifact-blocks/listen/context.d.ts +30 -0
  219. package/dist/artifact-blocks/listen/context.js +30 -0
  220. package/dist/artifact-blocks/liveData.d.ts +78 -0
  221. package/dist/artifact-blocks/liveData.js +137 -0
  222. package/dist/artifact-blocks/liveDataPoller.d.ts +41 -0
  223. package/dist/artifact-blocks/liveDataPoller.js +140 -0
  224. package/dist/artifact-blocks/openNav.d.ts +46 -0
  225. package/dist/artifact-blocks/openNav.js +106 -0
  226. package/dist/artifact-blocks/pageReviewKeys.d.ts +17 -0
  227. package/dist/artifact-blocks/pageReviewKeys.js +115 -0
  228. package/dist/artifact-blocks/params.d.ts +98 -0
  229. package/dist/artifact-blocks/params.js +159 -0
  230. package/dist/artifact-blocks/registry.d.ts +27 -0
  231. package/dist/artifact-blocks/registry.js +83 -0
  232. package/dist/artifact-blocks/review/BlockIcon.svelte +99 -0
  233. package/dist/artifact-blocks/review/BlockIcon.svelte.d.ts +17 -0
  234. package/dist/artifact-blocks/review/BulkRejectForm.svelte +94 -0
  235. package/dist/artifact-blocks/review/BulkRejectForm.svelte.d.ts +11 -0
  236. package/dist/artifact-blocks/review/BulkUndoBar.svelte +40 -0
  237. package/dist/artifact-blocks/review/BulkUndoBar.svelte.d.ts +9 -0
  238. package/dist/artifact-blocks/review/ItemNote.svelte +193 -0
  239. package/dist/artifact-blocks/review/ItemNote.svelte.d.ts +33 -0
  240. package/dist/artifact-blocks/review/NoteToggle.svelte +52 -0
  241. package/dist/artifact-blocks/review/NoteToggle.svelte.d.ts +12 -0
  242. package/dist/artifact-blocks/review/TeamChip.svelte +71 -0
  243. package/dist/artifact-blocks/review/TeamChip.svelte.d.ts +8 -0
  244. package/dist/artifact-blocks/review/attachments.d.ts +10 -0
  245. package/dist/artifact-blocks/review/attachments.js +37 -0
  246. package/dist/artifact-blocks/review/bulk.d.ts +52 -0
  247. package/dist/artifact-blocks/review/bulk.js +61 -0
  248. package/dist/artifact-blocks/review/itemReview.d.ts +33 -0
  249. package/dist/artifact-blocks/review/itemReview.js +112 -0
  250. package/dist/artifact-blocks/review/roundLines.d.ts +14 -0
  251. package/dist/artifact-blocks/review/roundLines.js +20 -0
  252. package/dist/artifact-blocks/review/rounds.d.ts +86 -0
  253. package/dist/artifact-blocks/review/rounds.js +302 -0
  254. package/dist/artifact-blocks/review/saver.d.ts +23 -0
  255. package/dist/artifact-blocks/review/saver.js +48 -0
  256. package/dist/artifact-blocks/review/team.d.ts +31 -0
  257. package/dist/artifact-blocks/review/team.js +42 -0
  258. package/dist/artifact-blocks/review/visualOrder.d.ts +29 -0
  259. package/dist/artifact-blocks/review/visualOrder.js +66 -0
  260. package/dist/artifact-blocks/review.d.ts +180 -0
  261. package/dist/artifact-blocks/review.js +716 -0
  262. package/dist/artifact-blocks/thumb-kit.d.ts +80 -0
  263. package/dist/artifact-blocks/thumb-kit.js +149 -0
  264. package/dist/artifact-blocks/types.d.ts +513 -0
  265. package/dist/artifact-blocks/types.js +1 -0
  266. package/dist/components/Button.svelte +8 -0
  267. package/dist/components/TextInput.svelte +39 -6
  268. package/dist/components/TextInput.svelte.d.ts +15 -7
  269. package/dist/components/basic/Checkbox.svelte +52 -14
  270. package/dist/components/basic/Checkbox.svelte.d.ts +11 -1
  271. package/package.json +11 -2
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Params schema for the checklist block (the living UAT / test checklist).
3
+ * Imported by block.ts (def), ChecklistBlock.svelte (props typing) and the
4
+ * tests — keeps them cycle-free.
5
+ */
6
+ import { z } from "zod";
7
+ export const CHECKLIST_STATUSES = [
8
+ "pass",
9
+ "fail",
10
+ "blocked",
11
+ "untested",
12
+ ];
13
+ /**
14
+ * The TEAM status vocabulary (teamMode only, mos-mssz0): the viewer's four
15
+ * verdicts plus two agent-side progress states a viewer never sets —
16
+ * `in_progress` (the team is working the row) and `ready` (fixed, ready for
17
+ * the viewer to retest). A `ready` row is what makes a page "need you" on
18
+ * /admin/artifacts without any jat task link.
19
+ */
20
+ const CHECKLIST_TEAM_STATUSES = [
21
+ ...CHECKLIST_STATUSES,
22
+ "ready",
23
+ "in_progress",
24
+ ];
25
+ /**
26
+ * URL policy for test links: https:// or root-relative only.
27
+ * LOCAL COPY of the shared `urlSchema` the blocks contract points at
28
+ * (src/lib/artifact-blocks/params.ts, owned by the core blocks task
29
+ * mos-bqeoa — not landed when this block shipped). When params.ts lands,
30
+ * switch this field to the shared schema; the policy is identical. Kept
31
+ * module-private so the dead-exports gate stays clean (phase-1 discipline).
32
+ */
33
+ const urlSchema = z
34
+ .string()
35
+ .max(2048)
36
+ .refine((s) => /^(https:\/\/|\/(?!\/))/.test(s.trim()), "URL must be https:// or root-relative (/…) — no javascript:, data:, http:// or protocol-relative URLs");
37
+ const rowSchema = z.object({
38
+ /** Stable id the per-viewer status/note state keys on — keep it stable
39
+ * across republishes or the viewer's verdicts lose their row. */
40
+ id: z
41
+ .string()
42
+ .regex(/^[a-z0-9][a-z0-9_-]{0,63}$/, "row id must be lowercase a-z0-9_- (max 64)"),
43
+ title: z.string().min(1, "row title is required").max(200),
44
+ /** The test URL — where this row is verified (page or the thing itself). */
45
+ url: urlSchema.optional(),
46
+ /** Who owns / should verify this row, e.g. an agent or person (max 80). */
47
+ owner: z.string().max(80).optional(),
48
+ /** Authored note — what to test, what landed, how it's measured. */
49
+ note: z.string().max(2000).optional(),
50
+ /**
51
+ * Authored status: the row's starting status ("untested" when omitted), and
52
+ * in teamMode the SHARED/team status column (update it by republishing).
53
+ * `ready` / `in_progress` are team-only and need teamMode.
54
+ */
55
+ status: z.enum(CHECKLIST_TEAM_STATUSES).optional(),
56
+ /**
57
+ * teamMode: when the team last marked this row `ready` (ISO timestamp). A
58
+ * viewer verdict set before it is stale — the row awaits their retest
59
+ * again. Omitted = any viewer verdict other than untested answers it.
60
+ */
61
+ retestSince: z.iso.datetime({ offset: true }).optional(),
62
+ });
63
+ const ROWS_MAX = 200;
64
+ export const checklistSchema = z
65
+ .object({
66
+ /** Optional heading above the rows. */
67
+ title: z.string().max(120).optional(),
68
+ rows: z
69
+ .array(rowSchema)
70
+ .min(1, "at least one row")
71
+ .max(ROWS_MAX, `at most ${ROWS_MAX} rows`),
72
+ /**
73
+ * teamMode — the authored status in params is the TEAM status (what the
74
+ * agents/republish recorded); the viewer's own status renders beside it as
75
+ * a private "yours" column. Without it the authored status is just the
76
+ * starting seed for the viewer's single status column.
77
+ */
78
+ teamMode: z.boolean().optional(),
79
+ })
80
+ .superRefine((v, ctx) => {
81
+ if (v.teamMode)
82
+ return;
83
+ v.rows.forEach((row, i) => {
84
+ if (row.status === "ready" || row.status === "in_progress")
85
+ ctx.addIssue({
86
+ code: "custom",
87
+ path: ["rows", i, "status"],
88
+ message: `status "${row.status}" is a team status — set teamMode: true`,
89
+ });
90
+ });
91
+ });
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ /**
3
+ * code block — highlighted code with filename, line-number gutter,
4
+ * highlighted lines and a copy button.
5
+ *
6
+ * The highlighter (./highlight) is a pure, dependency-free tokenizer that
7
+ * returns STRUCTURED tokens; every token renders as a plain <span> so
8
+ * Svelte escapes the text — never {@html} (contract: params are untrusted).
9
+ * Token colors are DaisyUI semantic classes via a static map.
10
+ *
11
+ * The copy button uses jatui's copyText (clipboard API with an
12
+ * execCommand fallback for plain-HTTP origins) and confirms with a short
13
+ * "Copied" state. Read-only safe: no per-viewer state is saved, so the
14
+ * block renders identically on the public share route.
15
+ */
16
+ import Icon from "../checklist/Icon.svelte"
17
+ import { copyText } from "../../../utils/clipboard.js"
18
+ import type { BlockProps } from "../../types.js"
19
+ import {
20
+ highlightLines,
21
+ languageFromFilename,
22
+ resolveLanguage,
23
+ TOKEN_CLASS,
24
+ type Language,
25
+ } from "./highlight.js"
26
+ import { type CodeParams } from "./schema.js"
27
+
28
+ let { params }: BlockProps<CodeParams> = $props()
29
+
30
+ const lang = $derived<Language>(
31
+ resolveLanguage(params?.language) === "plain" && params?.filename
32
+ ? languageFromFilename(params.filename)
33
+ : resolveLanguage(params?.language),
34
+ )
35
+
36
+ const code = $derived(params?.code ?? "")
37
+ const lines = $derived(highlightLines(code, lang))
38
+ const gutterWidth = $derived(String(lines.length).length)
39
+ const highlighted = $derived(new Set(params?.highlightLines ?? []))
40
+
41
+ /** One line row's classes — both branches are static literals (Tailwind
42
+ * extracts literals; dynamic class names never work). */
43
+ function rowClass(n: number): string {
44
+ return highlighted.has(n)
45
+ ? "flex px-2 bg-primary/8 border-l-2 border-l-primary"
46
+ : "flex px-2"
47
+ }
48
+
49
+ let copied = $state(false)
50
+ let copyTimer: ReturnType<typeof setTimeout> | undefined
51
+
52
+ async function onCopy() {
53
+ const ok = await copyText(code)
54
+ if (!ok) return
55
+ copied = true
56
+ if (copyTimer) clearTimeout(copyTimer)
57
+ copyTimer = setTimeout(() => (copied = false), 2000)
58
+ }
59
+ </script>
60
+
61
+ <figure class="artifact-code w-full">
62
+ <div
63
+ class="overflow-hidden rounded-box border border-base-content/10 bg-base-200/40"
64
+ >
65
+ <!-- header: filename / language chip + copy -->
66
+ <div
67
+ class="flex items-center gap-2 border-b border-base-content/10 bg-base-100/60 px-3 py-1.5"
68
+ >
69
+ {#if params?.filename}
70
+ <span
71
+ class="truncate rounded-field bg-base-content/5 px-2 py-0.5 font-mono text-xs font-medium text-base-content/80"
72
+ >
73
+ {params.filename}
74
+ </span>
75
+ {/if}
76
+ <span
77
+ class="ml-auto shrink-0 text-2xs uppercase tracking-wide text-base-content/45"
78
+ >
79
+ {lang === "plain" ? "text" : lang}
80
+ </span>
81
+ {#if params?.copy !== false}
82
+ <button
83
+ type="button"
84
+ class="btn btn-ghost btn-xs rounded-field shrink-0 gap-1 text-base-content/70"
85
+ onclick={onCopy}
86
+ aria-label="Copy code to clipboard"
87
+ >
88
+ {#if copied}
89
+ <Icon name="check" size={13} aria-hidden="true" />
90
+ <span aria-live="polite">Copied</span>
91
+ {:else}
92
+ <Icon name="copy" size={13} aria-hidden="true" />
93
+ <span>Copy</span>
94
+ {/if}
95
+ </button>
96
+ {/if}
97
+ </div>
98
+
99
+ <!-- code viewport -->
100
+ <div
101
+ class="overflow-auto font-mono text-xs leading-relaxed"
102
+ style:max-height="{params?.maxHeightPx ?? 420}px"
103
+ >
104
+ <pre class="m-0 p-0"><code
105
+ class="block min-w-max px-1 py-2 text-base-content"
106
+ >{#each lines as lineTokens, i (i)}
107
+ <div class={rowClass(i + 1)}>
108
+ {#if params?.lineNumbers !== false}
109
+ <span
110
+ class="select-none pr-3 text-right text-base-content/35 tabular-nums"
111
+ style:min-width="{gutterWidth}ch"
112
+ aria-hidden="true">
113
+ {i + 1}
114
+ </span>
115
+ {/if}
116
+ <!-- The token spans ARE the text (escaped by Svelte, read once
117
+ by screen readers, selectable and copyable as usual). -->
118
+ <span class="whitespace-pre"
119
+ >{#each lineTokens as t, k (k)}<span class={TOKEN_CLASS[t.type]}
120
+ >{t.text}</span
121
+ >{/each}</span
122
+ >
123
+ </div>
124
+ {/each}</code
125
+ ></pre>
126
+ </div>
127
+ </div>
128
+ </figure>
@@ -0,0 +1,12 @@
1
+ import type { BlockProps } from "../../types.js";
2
+ declare const CodeBlock: import("svelte").Component<BlockProps<{
3
+ code: string;
4
+ lineNumbers: boolean;
5
+ copy: boolean;
6
+ maxHeightPx: number;
7
+ filename?: string | undefined;
8
+ language?: "html" | "plain" | "typescript" | "javascript" | "json" | "bash" | "yaml" | "css" | "python" | "sql" | "diff" | undefined;
9
+ highlightLines?: number[] | undefined;
10
+ }>, {}, "">;
11
+ type CodeBlock = ReturnType<typeof CodeBlock>;
12
+ export default CodeBlock;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * code block — highlighted code with filename, line numbers, highlighted
3
+ * lines and a copy button.
4
+ */
5
+ import type { ArtifactBlockDef } from "../../types.js";
6
+ import { type CodeParams } from "./schema.js";
7
+ declare const def: ArtifactBlockDef<CodeParams>;
8
+ export default def;
@@ -0,0 +1,31 @@
1
+ import CodeBlock from "./CodeBlock.svelte";
2
+ import { codeSchema } from "./schema.js";
3
+ const EXAMPLE_CODE = `import { describe, expect, it } from "vitest"
4
+ import { mount } from "svelte"
5
+ import CodeBlock from "./CodeBlock.svelte"
6
+
7
+ describe("code block", () => {
8
+ it("renders every line", async () => {
9
+ const lines = highlightLines("const n = 42", "typescript")
10
+ expect(lines).toHaveLength(1)
11
+ expect(lines[0][0].type).toBe("keyword")
12
+ })
13
+ })`;
14
+ const EXAMPLE = {
15
+ filename: "CodeBlock.client.test.ts",
16
+ language: "typescript",
17
+ code: EXAMPLE_CODE,
18
+ lineNumbers: true,
19
+ highlightLines: [6],
20
+ copy: true,
21
+ maxHeightPx: 420,
22
+ };
23
+ const def = {
24
+ type: "code",
25
+ title: "Code",
26
+ description: "Highlighted code with a filename label, a line-number gutter, spotlighted lines and a copy button. Params: code (required, up to 50,000 chars — rendered escaped as text, never evaluated), filename (also picks the language from the extension), language (typescript|javascript|json|bash|yaml|html|css|python|sql|diff|plain — inferred from the filename when omitted, unknown values fall back to plain), lineNumbers (default true), highlightLines (up to 50 one-based line numbers to spotlight), copy (default true), maxHeightPx (scroll viewport, default 420). The highlighter is a small token map, not a language server — good for review snippets and config, not for whole compilable programs. Tip: one logical snippet per block; put the takeaway in a markdown block before it and point highlightLines at the lines that matter.",
27
+ schema: codeSchema,
28
+ component: CodeBlock,
29
+ example: EXAMPLE,
30
+ };
31
+ export default def;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Code block — a small, dependency-free, token-only syntax highlighter.
3
+ *
4
+ * Why hand-rolled: the repo has no highlighter dependency (no shiki/prism),
5
+ * and block params are UNTRUSTED input (CONTRACT.md) — the only sanctioned
6
+ * {@html} path is the markdown block's sanitized renderer. So this tokenizer
7
+ * returns STRUCTURED tokens ({ text, type }) and CodeBlock.svelte renders
8
+ * each as a plain <span> — Svelte escapes everything, nothing is ever
9
+ * interpreted. Colors are DaisyUI semantic classes only (token-only rule);
10
+ * the mapping is a static table (no dynamic class names).
11
+ *
12
+ * It is deliberately modest: a handful of languages with regex rules and a
13
+ * keyword set each, plus a plain fallback. The goal is readable code on a
14
+ * review page — not a language server.
15
+ */
16
+ export type TokenType = "plain" | "comment" | "string" | "number" | "keyword" | "literal" | "tag" | "attr" | "heading" | "add" | "del" | "punct";
17
+ /** Static class map — Tailwind extracts these literals; never build one. */
18
+ export declare const TOKEN_CLASS: Record<TokenType, string>;
19
+ /** Curated set. `plain` is the fallback everything falls back to. */
20
+ export declare const LANGUAGES: readonly ["typescript", "javascript", "json", "bash", "yaml", "html", "css", "python", "sql", "diff", "plain"];
21
+ export type Language = (typeof LANGUAGES)[number];
22
+ /** Filename → language (used when params omit an explicit language). */
23
+ export declare function languageFromFilename(filename: string | undefined): Language;
24
+ /** A param value (`"ts"`, `"Typescript"`, `"text/plain"`) → a known language. */
25
+ export declare function resolveLanguage(value: string | undefined): Language;
26
+ export interface Token {
27
+ text: string;
28
+ type: TokenType;
29
+ }
30
+ /**
31
+ * Tokenize `code` into lines of tokens. Never throws: on any internal error
32
+ * it degrades to plain text — a broken highlight must never break the page.
33
+ */
34
+ export declare function highlightLines(code: string, language: Language): Token[][];
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Code block — a small, dependency-free, token-only syntax highlighter.
3
+ *
4
+ * Why hand-rolled: the repo has no highlighter dependency (no shiki/prism),
5
+ * and block params are UNTRUSTED input (CONTRACT.md) — the only sanctioned
6
+ * {@html} path is the markdown block's sanitized renderer. So this tokenizer
7
+ * returns STRUCTURED tokens ({ text, type }) and CodeBlock.svelte renders
8
+ * each as a plain <span> — Svelte escapes everything, nothing is ever
9
+ * interpreted. Colors are DaisyUI semantic classes only (token-only rule);
10
+ * the mapping is a static table (no dynamic class names).
11
+ *
12
+ * It is deliberately modest: a handful of languages with regex rules and a
13
+ * keyword set each, plus a plain fallback. The goal is readable code on a
14
+ * review page — not a language server.
15
+ */
16
+ /** Static class map — Tailwind extracts these literals; never build one. */
17
+ export const TOKEN_CLASS = {
18
+ plain: "",
19
+ comment: "text-base-content/45 italic",
20
+ string: "text-success",
21
+ number: "text-info",
22
+ keyword: "text-primary font-medium",
23
+ literal: "text-secondary",
24
+ tag: "text-error",
25
+ attr: "text-accent",
26
+ heading: "text-primary font-semibold",
27
+ add: "text-success",
28
+ del: "text-error",
29
+ punct: "text-base-content/60",
30
+ };
31
+ // ── Languages ───────────────────────────────────────────────────────────────
32
+ /** Curated set. `plain` is the fallback everything falls back to. */
33
+ export const LANGUAGES = [
34
+ "typescript",
35
+ "javascript",
36
+ "json",
37
+ "bash",
38
+ "yaml",
39
+ "html",
40
+ "css",
41
+ "python",
42
+ "sql",
43
+ "diff",
44
+ "plain",
45
+ ];
46
+ const ALIASES = {
47
+ ts: "typescript",
48
+ tsx: "typescript",
49
+ js: "javascript",
50
+ jsx: "javascript",
51
+ mjs: "javascript",
52
+ cjs: "javascript",
53
+ json: "json",
54
+ jsonc: "json",
55
+ sh: "bash",
56
+ shell: "bash",
57
+ zsh: "bash",
58
+ console: "bash",
59
+ yml: "yaml",
60
+ yaml: "yaml",
61
+ html: "html",
62
+ xml: "html",
63
+ svelte: "html",
64
+ vue: "html",
65
+ css: "css",
66
+ scss: "css",
67
+ py: "python",
68
+ python: "python",
69
+ sql: "sql",
70
+ diff: "diff",
71
+ patch: "diff",
72
+ text: "plain",
73
+ txt: "plain",
74
+ md: "plain",
75
+ };
76
+ /** Filename → language (used when params omit an explicit language). */
77
+ export function languageFromFilename(filename) {
78
+ if (!filename)
79
+ return "plain";
80
+ const m = /\.([a-z0-9]+)$/i.exec(filename.trim());
81
+ if (!m)
82
+ return "plain";
83
+ return ALIASES[m[1].toLowerCase()] ?? "plain";
84
+ }
85
+ /** A param value (`"ts"`, `"Typescript"`, `"text/plain"`) → a known language. */
86
+ export function resolveLanguage(value) {
87
+ if (!value)
88
+ return "plain";
89
+ const v = value.trim().toLowerCase();
90
+ if (LANGUAGES.includes(v))
91
+ return v;
92
+ if (ALIASES[v])
93
+ return ALIASES[v];
94
+ const base = v.split(/[/+.-]/)[0];
95
+ if (base && LANGUAGES.includes(base))
96
+ return base;
97
+ if (base && ALIASES[base])
98
+ return ALIASES[base];
99
+ return "plain";
100
+ }
101
+ const KEYWORDS = {
102
+ typescript: new Set("abstract any as asserts async await boolean break case catch class const continue debugger declare default delete do else enum export extends false finally for from function get if implements import in infer instanceof interface is keyof let namespace never new null number of object operator override package private protected public readonly return satisfies set static string super switch symbol this throw true try type typeof undefined union unique unknown var void while with yield".split(" ")),
103
+ javascript: new Set("async await break case catch class const continue debugger default delete do else export extends false finally for from function if import in instanceof let new null of return static super switch this throw true try typeof undefined var void while with yield".split(" ")),
104
+ json: new Set(["true", "false", "null"]),
105
+ bash: new Set("if then else elif fi for while until do done case esac function in select time coproc return exit break continue local readonly declare typeset export unset shift eval trap source alias set".split(" ")),
106
+ python: new Set("and as assert async await break class continue def del elif else except False finally for from global if import in is lambda None nonlocal not or pass raise return True try while with yield match case".split(" ")),
107
+ sql: new Set("SELECT FROM WHERE INSERT INTO VALUES UPDATE SET DELETE CREATE TABLE ALTER DROP JOIN LEFT RIGHT INNER OUTER ON GROUP BY ORDER HAVING LIMIT OFFSET AS AND OR NOT NULL PRIMARY KEY FOREIGN REFERENCES UNIQUE INDEX VIEW UNION ALL DISTINCT COUNT SUM AVG MIN MAX CASE WHEN THEN ELSE END".split(" ")),
108
+ };
109
+ function wordRules(lang) {
110
+ const kws = KEYWORDS[lang];
111
+ return kws
112
+ ? [
113
+ {
114
+ type: "keyword",
115
+ re: new RegExp(`\\b(?:${[...kws]
116
+ .map((k) => k.replace(/[$]/g, "\\$"))
117
+ .sort((a, b) => b.length - a.length)
118
+ .join("|")})\\b`, "y"),
119
+ },
120
+ ]
121
+ : [];
122
+ }
123
+ const NUMBER_RE = /\b\d[\d_]*(?:\.\d+)?(?:[eE][+-]?\d+)?\b/y;
124
+ const LINE_COMMENT_RE = /(?:\/\/|#(?!\[)|--)[^\n]*/y;
125
+ const BLOCK_COMMENT_RE = /\/\*[\s\S]*?(?:\*\/|$)/y;
126
+ const TEMPLATE_RE = /`(?:\\[\s\S]|[^`\\])*`?/y;
127
+ const SQ_RE = /'(?:\\[\s\S]|[^'\\\n])*'?/y;
128
+ const DQ_RE = /"(?:\\[\s\S]|[^"\\\n])*"?/y;
129
+ function stringRules() {
130
+ return [
131
+ { type: "string", re: TEMPLATE_RE },
132
+ { type: "string", re: DQ_RE },
133
+ { type: "string", re: SQ_RE },
134
+ ];
135
+ }
136
+ /** Per-language rule lists, tried in order at each scan position. */
137
+ function rulesFor(lang) {
138
+ switch (lang) {
139
+ case "typescript":
140
+ case "javascript":
141
+ return [
142
+ { type: "comment", re: BLOCK_COMMENT_RE },
143
+ { type: "comment", re: /\/\/[^\n]*/y },
144
+ ...stringRules(),
145
+ { type: "number", re: NUMBER_RE },
146
+ ...wordRules(lang),
147
+ ];
148
+ case "json":
149
+ return [
150
+ { type: "comment", re: /\/\/[^\n]*/y },
151
+ ...stringRules(),
152
+ { type: "number", re: NUMBER_RE },
153
+ ...wordRules(lang),
154
+ ];
155
+ case "bash":
156
+ return [
157
+ { type: "comment", re: /#[^\n]*/y },
158
+ ...stringRules(),
159
+ { type: "number", re: /\$\{[^}]*\}|\$[A-Za-z_][A-Za-z0-9_]*|\b\d+\b/y },
160
+ ...wordRules(lang),
161
+ ];
162
+ case "yaml":
163
+ return [
164
+ { type: "comment", re: /#[^\n]*/y },
165
+ ...stringRules(),
166
+ { type: "heading", re: /^[ \t]*(?:- )?[A-Za-z_][\w.-]*(?=:)/y },
167
+ { type: "number", re: /\b\d[\d_]*(?:\.\d+)?\b/y },
168
+ {
169
+ type: "literal",
170
+ re: /\b(?:true|false|null|yes|no|on|off|True|False|None)\b/y,
171
+ },
172
+ ];
173
+ case "html":
174
+ return [
175
+ { type: "comment", re: /<!--[\s\S]*?(?:-->|$)/y },
176
+ { type: "tag", re: /<\/?[A-Za-z][\w:-]*|\/?>/y },
177
+ { type: "attr", re: /[A-Za-z_:][\w:.-]*(?==)/y },
178
+ ...stringRules(),
179
+ ];
180
+ case "css":
181
+ return [
182
+ { type: "comment", re: /\/\*[\s\S]*?(?:\*\/|$)/y },
183
+ ...stringRules(),
184
+ { type: "keyword", re: /@[\w-]+/y },
185
+ {
186
+ type: "attr",
187
+ re: /[A-Za-z-]+(?=\s*:)/y,
188
+ },
189
+ {
190
+ type: "number",
191
+ re: /#[0-9a-fA-F]{3,8}\b|\b\d+(?:\.\d+)?(?:%|px|rem|em|vh|vw|s|ms|deg|fr)?\b/y,
192
+ },
193
+ { type: "tag", re: /[.#][\w-]+/y },
194
+ ];
195
+ case "python":
196
+ return [
197
+ { type: "comment", re: /#[^\n]*/y },
198
+ ...stringRules(),
199
+ { type: "number", re: NUMBER_RE },
200
+ ...wordRules(lang),
201
+ ];
202
+ case "sql":
203
+ return [
204
+ { type: "comment", re: LINE_COMMENT_RE },
205
+ ...stringRules(),
206
+ { type: "number", re: NUMBER_RE },
207
+ ...wordRules(lang),
208
+ ];
209
+ case "diff":
210
+ return [
211
+ { type: "heading", re: /^(?:@@[^\n]*@@|diff [^\n]*|index [^\n]*)$/my },
212
+ { type: "del", re: /^-.*$/my },
213
+ { type: "add", re: /^\+.*$/my },
214
+ { type: "heading", re: /^(?:---|\+\+\+) [^\n]*/my },
215
+ ];
216
+ case "plain":
217
+ default:
218
+ return [];
219
+ }
220
+ }
221
+ /**
222
+ * Tokenize `code` into lines of tokens. Never throws: on any internal error
223
+ * it degrades to plain text — a broken highlight must never break the page.
224
+ */
225
+ export function highlightLines(code, language) {
226
+ const lines = code.split("\n");
227
+ const rules = rulesFor(language);
228
+ const out = [];
229
+ try {
230
+ if (rules.length === 0) {
231
+ for (const line of lines)
232
+ out.push([{ text: line, type: "plain" }]);
233
+ return out;
234
+ }
235
+ for (const line of lines) {
236
+ const tokens = [];
237
+ let i = 0;
238
+ let buffer = "";
239
+ const flush = () => {
240
+ if (buffer) {
241
+ tokens.push({ text: buffer, type: "plain" });
242
+ buffer = "";
243
+ }
244
+ };
245
+ while (i < line.length) {
246
+ let matched = false;
247
+ for (const rule of rules) {
248
+ rule.re.lastIndex = i;
249
+ const m = rule.re.exec(line);
250
+ if (m && m.index === i && m[0].length > 0) {
251
+ // Multiline-flagged rules (diff/yaml) match whole lines; when
252
+ // they were applied mid-line we still consume to end of line.
253
+ let text = m[0];
254
+ if (text.includes("\n"))
255
+ text = text.replace(/\n[\s\S]*$/, "");
256
+ if (text.length === 0)
257
+ continue;
258
+ flush();
259
+ tokens.push({ text, type: rule.type });
260
+ i += text.length;
261
+ matched = true;
262
+ break;
263
+ }
264
+ }
265
+ if (!matched) {
266
+ buffer += line[i];
267
+ i += 1;
268
+ }
269
+ }
270
+ flush();
271
+ if (tokens.length === 0)
272
+ tokens.push({ text: "", type: "plain" });
273
+ out.push(tokens);
274
+ }
275
+ return out;
276
+ }
277
+ catch {
278
+ return lines.map((l) => [{ text: l, type: "plain" }]);
279
+ }
280
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Params schema for the code block (highlighted code with filename, line
3
+ * numbers, highlighted lines and a copy button). Imported by block.ts (def),
4
+ * CodeBlock.svelte (props typing) and the tests — keeps them cycle-free.
5
+ *
6
+ * Untrusted input rules (CONTRACT.md): `code` is length-capped and rendered
7
+ * as TEXT in <span>s — never {@html} (the highlighter returns structured
8
+ * tokens, Svelte escapes every character).
9
+ */
10
+ import { z } from "zod";
11
+ export declare const codeSchema: z.ZodObject<{
12
+ code: z.ZodString;
13
+ filename: z.ZodOptional<z.ZodString>;
14
+ language: z.ZodOptional<z.ZodEnum<{
15
+ html: "html";
16
+ plain: "plain";
17
+ typescript: "typescript";
18
+ javascript: "javascript";
19
+ json: "json";
20
+ bash: "bash";
21
+ yaml: "yaml";
22
+ css: "css";
23
+ python: "python";
24
+ sql: "sql";
25
+ diff: "diff";
26
+ }>>;
27
+ lineNumbers: z.ZodDefault<z.ZodBoolean>;
28
+ highlightLines: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
29
+ copy: z.ZodDefault<z.ZodBoolean>;
30
+ maxHeightPx: z.ZodDefault<z.ZodNumber>;
31
+ }, z.core.$strip>;
32
+ export type CodeParams = z.infer<typeof codeSchema>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Params schema for the code block (highlighted code with filename, line
3
+ * numbers, highlighted lines and a copy button). Imported by block.ts (def),
4
+ * CodeBlock.svelte (props typing) and the tests — keeps them cycle-free.
5
+ *
6
+ * Untrusted input rules (CONTRACT.md): `code` is length-capped and rendered
7
+ * as TEXT in <span>s — never {@html} (the highlighter returns structured
8
+ * tokens, Svelte escapes every character).
9
+ */
10
+ import { z } from "zod";
11
+ import { LANGUAGES } from "./highlight.js";
12
+ /** 50k chars ≈ ~1.6k lines, inside the ~256KB per-block params budget. */
13
+ const CODE_MAX_CHARS = 50_000;
14
+ const MAX_HIGHLIGHT_LINES = 50;
15
+ export const codeSchema = z.object({
16
+ /** The code itself. Rendered verbatim, escaped. */
17
+ code: z
18
+ .string()
19
+ .min(1, "code is required")
20
+ .max(CODE_MAX_CHARS, `keep code under ${CODE_MAX_CHARS} characters`),
21
+ /** Optional file label in the header (also picks the language). */
22
+ filename: z.string().max(200).optional(),
23
+ /**
24
+ * Language for the highlighter. When omitted it is inferred from the
25
+ * filename's extension; unknown values fall back to plain text.
26
+ */
27
+ language: z.enum(LANGUAGES).optional(),
28
+ /** Show the line-number gutter (default true). */
29
+ lineNumbers: z.boolean().default(true),
30
+ /** 1-based line numbers to spotlight (max 50). Out-of-range is ignored. */
31
+ highlightLines: z
32
+ .array(z.number().int().min(1).max(1_000_000))
33
+ .max(MAX_HIGHLIGHT_LINES)
34
+ .optional(),
35
+ /** Offer the copy button (default true). */
36
+ copy: z.boolean().default(true),
37
+ /** Scroll viewport height in px (default 420, max 2000). */
38
+ maxHeightPx: z.number().int().min(120).max(2000).default(420),
39
+ });