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