@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,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline block — params schema, validation, date scale and layout
|
|
3
|
+
* (mos-d70v4).
|
|
4
|
+
*
|
|
5
|
+
* Pure functions only (no DOM): dates parse to UTC-midnight epoch ms (a
|
|
6
|
+
* date-only ISO string parses identically in every timezone), the domain,
|
|
7
|
+
* ticks, lane rows and dependency elbows are computed here, and colors
|
|
8
|
+
* NEVER appear — the renderer maps tones to theme-token classes.
|
|
9
|
+
*
|
|
10
|
+
* Untrusted-input rules (CONTRACT.md): text params are length-capped
|
|
11
|
+
* strings, dates are ISO-validated (impossible calendar dates are refused
|
|
12
|
+
* at publish), arrays are capped (50 milestones / 80 bars / 120 deps).
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { toneSchema, isoDateSchema } from "../../params.js";
|
|
16
|
+
import { MAX_MILESTONES, MAX_BARS, MAX_DEPS, MAX_LANES, BAR_TEXT_MAX, ID_MAX, LANE_MAX, } from "./constants.js";
|
|
17
|
+
// ── Schemas ───────────────────────────────────────────────────────────────
|
|
18
|
+
const timelineIdSchema = z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.max(ID_MAX)
|
|
22
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/);
|
|
23
|
+
const milestoneSchema = z.object({
|
|
24
|
+
id: timelineIdSchema.optional(),
|
|
25
|
+
label: z.string().min(1).max(BAR_TEXT_MAX),
|
|
26
|
+
date: isoDateSchema,
|
|
27
|
+
tone: toneSchema.optional(),
|
|
28
|
+
lane: z.string().min(1).max(LANE_MAX).optional(),
|
|
29
|
+
});
|
|
30
|
+
const barSchema = z.object({
|
|
31
|
+
id: timelineIdSchema,
|
|
32
|
+
label: z.string().min(1).max(BAR_TEXT_MAX),
|
|
33
|
+
start: isoDateSchema,
|
|
34
|
+
end: isoDateSchema,
|
|
35
|
+
lane: z.string().min(1).max(LANE_MAX).optional(),
|
|
36
|
+
tone: toneSchema.optional(),
|
|
37
|
+
/** Completion percentage 0-100 (int) — renders a darker fill portion. */
|
|
38
|
+
progress: z.number().int().min(0).max(100).optional(),
|
|
39
|
+
});
|
|
40
|
+
const dependencySchema = z.object({
|
|
41
|
+
from: timelineIdSchema,
|
|
42
|
+
to: timelineIdSchema,
|
|
43
|
+
});
|
|
44
|
+
export const timelineParamsSchema = z
|
|
45
|
+
.object({
|
|
46
|
+
title: z.string().max(200).optional(),
|
|
47
|
+
subtitle: z.string().max(300).optional(),
|
|
48
|
+
/** Axis granularity: day / week / month ticks. */
|
|
49
|
+
zoom: z.enum(["day", "week", "month"]).default("month"),
|
|
50
|
+
/** Dashed "today" line at the current date (when it falls in range). */
|
|
51
|
+
showToday: z.boolean().default(true),
|
|
52
|
+
milestones: z.array(milestoneSchema).max(MAX_MILESTONES).default([]),
|
|
53
|
+
bars: z.array(barSchema).max(MAX_BARS).default([]),
|
|
54
|
+
dependencies: z.array(dependencySchema).max(MAX_DEPS).default([]),
|
|
55
|
+
/** Explicit lane order; lanes discovered from bars/milestones append. */
|
|
56
|
+
lanes: z.array(z.string().min(1).max(LANE_MAX)).max(MAX_LANES).optional(),
|
|
57
|
+
})
|
|
58
|
+
.superRefine((p, ctx) => {
|
|
59
|
+
// bar ids unique; end >= start
|
|
60
|
+
const barIds = new Set();
|
|
61
|
+
for (const [i, b] of p.bars.entries()) {
|
|
62
|
+
if (barIds.has(b.id)) {
|
|
63
|
+
ctx.addIssue({
|
|
64
|
+
code: "custom",
|
|
65
|
+
message: `Duplicate bar id "${b.id}" (bar ${i})`,
|
|
66
|
+
path: ["bars", i, "id"],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
barIds.add(b.id);
|
|
70
|
+
if (Date.parse(b.start) > Date.parse(b.end)) {
|
|
71
|
+
ctx.addIssue({
|
|
72
|
+
code: "custom",
|
|
73
|
+
message: `Bar "${b.id}" ends before it starts`,
|
|
74
|
+
path: ["bars", i, "end"],
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// milestone ids unique (when given)
|
|
79
|
+
const msIds = new Set();
|
|
80
|
+
for (const [i, m] of p.milestones.entries()) {
|
|
81
|
+
if (!m.id)
|
|
82
|
+
continue;
|
|
83
|
+
if (msIds.has(m.id)) {
|
|
84
|
+
ctx.addIssue({
|
|
85
|
+
code: "custom",
|
|
86
|
+
message: `Duplicate milestone id "${m.id}" (milestone ${i})`,
|
|
87
|
+
path: ["milestones", i, "id"],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
msIds.add(m.id);
|
|
91
|
+
}
|
|
92
|
+
// dependencies reference bars, no self-deps, no duplicates
|
|
93
|
+
p.dependencies.forEach((d, i) => {
|
|
94
|
+
for (const key of ["from", "to"]) {
|
|
95
|
+
if (!barIds.has(d[key])) {
|
|
96
|
+
ctx.addIssue({
|
|
97
|
+
code: "custom",
|
|
98
|
+
message: `Dependency ${i} ${key} "${d[key]}" has no bar`,
|
|
99
|
+
path: ["dependencies", i, key],
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (d.from === d.to) {
|
|
104
|
+
ctx.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
message: `Dependency ${i} is a self-dependency`,
|
|
107
|
+
path: ["dependencies", i],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (let j = 0; j < i; j++) {
|
|
111
|
+
const o = p.dependencies[j];
|
|
112
|
+
if (o.from === d.from && o.to === d.to) {
|
|
113
|
+
ctx.addIssue({
|
|
114
|
+
code: "custom",
|
|
115
|
+
message: `Dependency ${i} duplicates dependency ${j}`,
|
|
116
|
+
path: ["dependencies", i],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ── Date scale (UTC-day anchored, deterministic) ──────────────────────────
|
|
123
|
+
export const DAY_MS = 86_400_000;
|
|
124
|
+
/** ISO date(-time) → epoch ms. Date-only strings are UTC midnight. */
|
|
125
|
+
export function parseDay(v) {
|
|
126
|
+
const ms = Date.parse(v);
|
|
127
|
+
return Number.isFinite(ms) ? ms : Number.NaN;
|
|
128
|
+
}
|
|
129
|
+
function startOfUtcDay(ms) {
|
|
130
|
+
return Math.floor(ms / DAY_MS) * DAY_MS;
|
|
131
|
+
}
|
|
132
|
+
export function addDays(ms, days) {
|
|
133
|
+
return ms + days * DAY_MS;
|
|
134
|
+
}
|
|
135
|
+
/** UTC Monday 00:00 of the week containing ms. */
|
|
136
|
+
function startOfUtcWeek(ms) {
|
|
137
|
+
const d = new Date(ms);
|
|
138
|
+
const day = (d.getUTCDay() + 6) % 7; // Mon=0 … Sun=6
|
|
139
|
+
return startOfUtcDay(ms) - day * DAY_MS;
|
|
140
|
+
}
|
|
141
|
+
/** UTC 1st of the month 00:00 containing ms. */
|
|
142
|
+
function startOfUtcMonth(ms) {
|
|
143
|
+
const d = new Date(ms);
|
|
144
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
|
|
145
|
+
}
|
|
146
|
+
/** Tick steps (in units) that keep the tick count honest. */
|
|
147
|
+
const STEP_LADDER = [1, 2, 3, 6, 12];
|
|
148
|
+
/** Calendar-aware ticks for the domain at the chosen zoom, snapped to real
|
|
149
|
+
* day/week/month boundaries (never "Mar 34"). Returns ≤ ~48 ticks; the step
|
|
150
|
+
* widens when the span is too long for the zoom. */
|
|
151
|
+
export function timeTicks(d0, d1, zoom) {
|
|
152
|
+
if (!Number.isFinite(d0) || !Number.isFinite(d1) || d1 <= d0)
|
|
153
|
+
return [];
|
|
154
|
+
const spanDays = (d1 - d0) / DAY_MS;
|
|
155
|
+
const out = [];
|
|
156
|
+
if (zoom === "day") {
|
|
157
|
+
const stepDays = STEP_LADDER.concat([14]).find((s) => spanDays / s <= 46) ?? 14;
|
|
158
|
+
let t = startOfUtcDay(d0);
|
|
159
|
+
while (t <= d1) {
|
|
160
|
+
if (t >= d0)
|
|
161
|
+
out.push({
|
|
162
|
+
ms: t,
|
|
163
|
+
label: new Intl.DateTimeFormat("en-US", {
|
|
164
|
+
month: "short",
|
|
165
|
+
day: "numeric",
|
|
166
|
+
}).format(t),
|
|
167
|
+
});
|
|
168
|
+
t = addDays(t, stepDays);
|
|
169
|
+
}
|
|
170
|
+
return out;
|
|
171
|
+
}
|
|
172
|
+
if (zoom === "week") {
|
|
173
|
+
const stepWeeks = STEP_LADDER.find((s) => spanDays / (7 * s) <= 46) ?? 12;
|
|
174
|
+
let t = startOfUtcWeek(d0);
|
|
175
|
+
while (t <= d1) {
|
|
176
|
+
if (t >= d0)
|
|
177
|
+
out.push({
|
|
178
|
+
ms: t,
|
|
179
|
+
label: new Intl.DateTimeFormat("en-US", {
|
|
180
|
+
month: "short",
|
|
181
|
+
day: "numeric",
|
|
182
|
+
}).format(t),
|
|
183
|
+
});
|
|
184
|
+
t = addDays(t, 7 * stepWeeks);
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
const stepMonths = STEP_LADDER.find((s) => spanDays / (30.4 * s) <= 46) ?? 12;
|
|
189
|
+
let t = startOfUtcMonth(d0);
|
|
190
|
+
while (t <= d1 && out.length < 60) {
|
|
191
|
+
if (t >= d0) {
|
|
192
|
+
const d = new Date(t);
|
|
193
|
+
const jan = d.getUTCMonth() === 0;
|
|
194
|
+
out.push({
|
|
195
|
+
ms: t,
|
|
196
|
+
label: jan
|
|
197
|
+
? new Intl.DateTimeFormat("en-US", {
|
|
198
|
+
month: "short",
|
|
199
|
+
year: "numeric",
|
|
200
|
+
}).format(t)
|
|
201
|
+
: new Intl.DateTimeFormat("en-US", { month: "short" }).format(t),
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const d = new Date(t);
|
|
205
|
+
t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + stepMonths, 1);
|
|
206
|
+
}
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
/** Domain pad per zoom (days each side) so marks aren't glued to the edge. */
|
|
210
|
+
function padDays(zoom) {
|
|
211
|
+
return zoom === "day" ? 1 : zoom === "week" ? 3 : 10;
|
|
212
|
+
}
|
|
213
|
+
/** Date domain over milestones + bars, padded per zoom. */
|
|
214
|
+
function timeDomain(p) {
|
|
215
|
+
let min = Number.POSITIVE_INFINITY;
|
|
216
|
+
let max = Number.NEGATIVE_INFINITY;
|
|
217
|
+
for (const m of p.milestones) {
|
|
218
|
+
const t = parseDay(m.date);
|
|
219
|
+
if (Number.isFinite(t)) {
|
|
220
|
+
min = Math.min(min, t);
|
|
221
|
+
max = Math.max(max, t);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const b of p.bars) {
|
|
225
|
+
const s = parseDay(b.start);
|
|
226
|
+
const e = parseDay(b.end);
|
|
227
|
+
if (Number.isFinite(s))
|
|
228
|
+
min = Math.min(min, s);
|
|
229
|
+
if (Number.isFinite(e))
|
|
230
|
+
max = Math.max(max, e);
|
|
231
|
+
}
|
|
232
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
233
|
+
// no data: a one-week window around today keeps the axis honest
|
|
234
|
+
const today = startOfUtcDay(Date.now());
|
|
235
|
+
return { d0: addDays(today, -2), d1: addDays(today, 5) };
|
|
236
|
+
}
|
|
237
|
+
min = startOfUtcDay(min);
|
|
238
|
+
max = startOfUtcDay(max);
|
|
239
|
+
if (min === max)
|
|
240
|
+
max = addDays(max, 1);
|
|
241
|
+
const pad = padDays(p.zoom);
|
|
242
|
+
return { d0: addDays(min, -pad), d1: addDays(max, pad) };
|
|
243
|
+
}
|
|
244
|
+
// ── Lanes ─────────────────────────────────────────────────────────────────
|
|
245
|
+
const MILESTONE_LANE = "__milestones";
|
|
246
|
+
/** Lane order: explicit `lanes` first (only those with items), then lanes
|
|
247
|
+
* discovered from bars/milestones in first-seen order. Lane-less milestones
|
|
248
|
+
* land on the dedicated top lane; lane-less bars on a trailing "—"-labeled
|
|
249
|
+
* lane. Returns lane ids with display labels. */
|
|
250
|
+
function laneOrder(p) {
|
|
251
|
+
const out = [];
|
|
252
|
+
const seen = new Set();
|
|
253
|
+
const push = (id, label) => {
|
|
254
|
+
if (!seen.has(id)) {
|
|
255
|
+
seen.add(id);
|
|
256
|
+
out.push({ id, label });
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const msLaneless = p.milestones.some((m) => !m.lane);
|
|
260
|
+
if (msLaneless)
|
|
261
|
+
push(MILESTONE_LANE, "Milestones");
|
|
262
|
+
for (const l of p.lanes ?? [])
|
|
263
|
+
push(l, l);
|
|
264
|
+
for (const m of p.milestones)
|
|
265
|
+
if (m.lane)
|
|
266
|
+
push(m.lane, m.lane);
|
|
267
|
+
for (const b of p.bars)
|
|
268
|
+
if (b.lane)
|
|
269
|
+
push(b.lane, b.lane);
|
|
270
|
+
if (p.bars.some((b) => !b.lane))
|
|
271
|
+
push("", "Tasks");
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
// ── Layout ────────────────────────────────────────────────────────────────
|
|
275
|
+
const TICK_H = 22;
|
|
276
|
+
const ROW_PAD = 9;
|
|
277
|
+
const BAR_H = 16;
|
|
278
|
+
const SUB_ROW_H = BAR_H + 7;
|
|
279
|
+
/** Full layout for the renderer. `nowMs` (client-only) adds the today line
|
|
280
|
+
* when it falls inside the domain; SSR passes null and the line is skipped
|
|
281
|
+
* — no hydration mismatch. */
|
|
282
|
+
export function layoutTimeline(p, opts = {
|
|
283
|
+
plotW: 700,
|
|
284
|
+
laneW: 120,
|
|
285
|
+
nowMs: null,
|
|
286
|
+
}) {
|
|
287
|
+
const { d0, d1 } = timeDomain(p);
|
|
288
|
+
const span = Math.max(1, d1 - d0);
|
|
289
|
+
const xFor = (ms) => opts.laneW + ((ms - d0) / span) * Math.max(40, opts.plotW);
|
|
290
|
+
const rows = [];
|
|
291
|
+
let y = TICK_H + ROW_PAD;
|
|
292
|
+
const laneDefs = laneOrder(p);
|
|
293
|
+
for (const lane of laneDefs) {
|
|
294
|
+
const bars = p.bars
|
|
295
|
+
.filter((b) => (b.lane ?? "") === lane.id)
|
|
296
|
+
.sort((a, b) => Date.parse(a.start) - Date.parse(b.start));
|
|
297
|
+
const milestones = p.milestones.filter((m) => (m.lane ?? "") === lane.id || (!m.lane && lane.id === MILESTONE_LANE));
|
|
298
|
+
// greedy sub-rows so overlapping bars in one lane stack
|
|
299
|
+
const subEnds = [];
|
|
300
|
+
const placed = bars.map((b) => {
|
|
301
|
+
const s = parseDay(b.start);
|
|
302
|
+
const e = parseDay(b.end);
|
|
303
|
+
let sub = subEnds.findIndex((end) => end <= s);
|
|
304
|
+
if (sub === -1) {
|
|
305
|
+
subEnds.push(e);
|
|
306
|
+
sub = subEnds.length - 1;
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
subEnds[sub] = e;
|
|
310
|
+
return { bar: b, sub, x: xFor(s), w: Math.max(2, xFor(e) - xFor(s)) };
|
|
311
|
+
});
|
|
312
|
+
const subCount = Math.max(subEnds.length, milestones.length ? 1 : 0, 1);
|
|
313
|
+
const height = subCount * SUB_ROW_H + ROW_PAD;
|
|
314
|
+
const rowY = y;
|
|
315
|
+
y += height;
|
|
316
|
+
rows.push({
|
|
317
|
+
id: lane.id,
|
|
318
|
+
label: lane.label,
|
|
319
|
+
y: rowY,
|
|
320
|
+
height,
|
|
321
|
+
bars: placed.map((pl) => ({
|
|
322
|
+
id: pl.bar.id,
|
|
323
|
+
label: pl.bar.label,
|
|
324
|
+
tone: pl.bar.tone,
|
|
325
|
+
progress: pl.bar.progress,
|
|
326
|
+
start: pl.bar.start,
|
|
327
|
+
end: pl.bar.end,
|
|
328
|
+
x: pl.x,
|
|
329
|
+
w: pl.w,
|
|
330
|
+
y: rowY + ROW_PAD / 2 + pl.sub * SUB_ROW_H + (SUB_ROW_H - BAR_H) / 2,
|
|
331
|
+
xStartMs: parseDay(pl.bar.start),
|
|
332
|
+
xEndMs: parseDay(pl.bar.end),
|
|
333
|
+
})),
|
|
334
|
+
milestones: milestones.map((m) => ({
|
|
335
|
+
id: m.id ?? `ms-${m.label}`,
|
|
336
|
+
label: m.label,
|
|
337
|
+
tone: m.tone,
|
|
338
|
+
date: m.date,
|
|
339
|
+
x: xFor(parseDay(m.date)),
|
|
340
|
+
y: rowY + height / 2,
|
|
341
|
+
})),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
const height = y + ROW_PAD;
|
|
345
|
+
const today = opts.nowMs != null &&
|
|
346
|
+
Number.isFinite(opts.nowMs) &&
|
|
347
|
+
opts.nowMs >= d0 &&
|
|
348
|
+
opts.nowMs <= d1
|
|
349
|
+
? { x: xFor(startOfUtcDay(opts.nowMs)) }
|
|
350
|
+
: null;
|
|
351
|
+
return {
|
|
352
|
+
d0,
|
|
353
|
+
d1,
|
|
354
|
+
ticks: timeTicks(d0, d1, p.zoom).map((t) => ({
|
|
355
|
+
...t,
|
|
356
|
+
x: xFor(t.ms),
|
|
357
|
+
})),
|
|
358
|
+
rows,
|
|
359
|
+
today,
|
|
360
|
+
width: opts.laneW + Math.max(40, opts.plotW) + 12,
|
|
361
|
+
height,
|
|
362
|
+
laneW: opts.laneW,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/** Dependency elbow path: from the END of bar A to the START of bar B,
|
|
366
|
+
* routed through the row gap. Points (not a path string) so the renderer
|
|
367
|
+
* draws + head-triangles it like every other polyline here. */
|
|
368
|
+
export function dependencyPoints(from, to) {
|
|
369
|
+
const dir = to.xStart >= from.xEnd ? 1 : -1;
|
|
370
|
+
const gap = 8 * dir;
|
|
371
|
+
if (Math.abs(to.y - from.y) < 2)
|
|
372
|
+
return [
|
|
373
|
+
{ x: from.xEnd, y: from.y },
|
|
374
|
+
{ x: to.xStart, y: to.y },
|
|
375
|
+
];
|
|
376
|
+
return [
|
|
377
|
+
{ x: from.xEnd, y: from.y },
|
|
378
|
+
{ x: from.xEnd + gap, y: from.y },
|
|
379
|
+
{ x: from.xEnd + gap, y: to.y },
|
|
380
|
+
{ x: to.xStart, y: to.y },
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
const dayFmt = new Intl.DateTimeFormat("en-US", { dateStyle: "medium" });
|
|
384
|
+
/** Flat rows for the accessible data fallback (sr-only table). */
|
|
385
|
+
export function timelineRows(p) {
|
|
386
|
+
const rows = [];
|
|
387
|
+
for (const m of p.milestones)
|
|
388
|
+
rows.push({
|
|
389
|
+
kind: "milestone",
|
|
390
|
+
label: m.label,
|
|
391
|
+
detail: `${dayFmt.format(Date.parse(m.date))}${m.lane ? ` · ${m.lane}` : ""}`,
|
|
392
|
+
});
|
|
393
|
+
for (const b of p.bars)
|
|
394
|
+
rows.push({
|
|
395
|
+
kind: "bar",
|
|
396
|
+
label: b.label,
|
|
397
|
+
detail: `${b.start} → ${b.end}${b.lane ? ` · ${b.lane}` : ""}${b.progress != null ? ` · ${b.progress}%` : ""}`,
|
|
398
|
+
});
|
|
399
|
+
for (const d of p.dependencies) {
|
|
400
|
+
const f = p.bars.find((b) => b.id === d.from)?.label ?? d.from;
|
|
401
|
+
const t = p.bars.find((b) => b.id === d.to)?.label ?? d.to;
|
|
402
|
+
rows.push({ kind: "bar", label: `${f} → ${t}`, detail: "dependency" });
|
|
403
|
+
}
|
|
404
|
+
return rows;
|
|
405
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline block thumbnail composer (mos-qkpva).
|
|
3
|
+
*
|
|
4
|
+
* The same layoutTimeline geometry the component draws (./schema.ts, pure and
|
|
5
|
+
* unit-tested) as a simplified silhouette: tick header, lane-labeled bar rows
|
|
6
|
+
* (tone fills at the component's opacities), progress overlays and milestone
|
|
7
|
+
* diamonds. The today line is client-only in the component (nowMs=null on
|
|
8
|
+
* SSR); the thumb passes null too, so a thumbnail never lies about "today".
|
|
9
|
+
*
|
|
10
|
+
* A timeline can be very tall (24 lanes); the thumb keeps rows only while
|
|
11
|
+
* they fit a ~2:1 box and clips the viewBox to what it drew — a tall timeline
|
|
12
|
+
* shows its first rows, not a dust-scale whole.
|
|
13
|
+
*/
|
|
14
|
+
import { timelineParamsSchema, layoutTimeline, } from "./schema.js";
|
|
15
|
+
import { LANE_W, BAR_H, MS_R } from "./constants.js";
|
|
16
|
+
import { r1, thumbText, thumbTruncate, toneColor, } from "../../thumb-kit.js";
|
|
17
|
+
const PLOT_W = 480;
|
|
18
|
+
const MAX_ROWS_HEIGHT = 300;
|
|
19
|
+
/** The components' static tone maps, resolved to concrete color+opacity. */
|
|
20
|
+
function barFill(palette, tone) {
|
|
21
|
+
switch (tone) {
|
|
22
|
+
case "success":
|
|
23
|
+
case "warning":
|
|
24
|
+
return { fill: toneColor(palette, tone), opacity: 0.3 };
|
|
25
|
+
case "neutral":
|
|
26
|
+
return { fill: palette.ink, opacity: 0.15 };
|
|
27
|
+
case undefined:
|
|
28
|
+
return { fill: palette.primary, opacity: 0.25 };
|
|
29
|
+
default:
|
|
30
|
+
return { fill: toneColor(palette, tone), opacity: 0.25 };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function barStroke(palette, tone) {
|
|
34
|
+
switch (tone) {
|
|
35
|
+
case "warning":
|
|
36
|
+
return { stroke: palette.warning, opacity: 0.8 };
|
|
37
|
+
case "neutral":
|
|
38
|
+
return { stroke: palette.ink, opacity: 0.35 };
|
|
39
|
+
case undefined:
|
|
40
|
+
return { stroke: palette.primary, opacity: 0.7 };
|
|
41
|
+
default:
|
|
42
|
+
return { stroke: toneColor(palette, tone), opacity: 0.7 };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function progressFill(palette, tone) {
|
|
46
|
+
switch (tone) {
|
|
47
|
+
case "neutral":
|
|
48
|
+
return { fill: palette.ink, opacity: 0.4 };
|
|
49
|
+
case undefined:
|
|
50
|
+
return { fill: palette.primary, opacity: 0.6 };
|
|
51
|
+
default:
|
|
52
|
+
return { fill: toneColor(palette, tone), opacity: 0.6 };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function drawTimelineThumb(raw, palette) {
|
|
56
|
+
const parsed = timelineParamsSchema.safeParse(raw);
|
|
57
|
+
if (!parsed.success)
|
|
58
|
+
return null;
|
|
59
|
+
const params = parsed.data;
|
|
60
|
+
if (params.bars.length === 0 && params.milestones.length === 0)
|
|
61
|
+
return null;
|
|
62
|
+
const layout = layoutTimeline(params, {
|
|
63
|
+
plotW: PLOT_W,
|
|
64
|
+
laneW: LANE_W,
|
|
65
|
+
nowMs: null,
|
|
66
|
+
});
|
|
67
|
+
// Keep rows until they no longer fit the ~2:1 silhouette box; the drawn
|
|
68
|
+
// height becomes the viewBox height.
|
|
69
|
+
const rows = [];
|
|
70
|
+
let drawnH = layout.rows.length ? layout.rows[0].y : 0;
|
|
71
|
+
for (const row of layout.rows) {
|
|
72
|
+
rows.push(row);
|
|
73
|
+
drawnH = row.y + row.height;
|
|
74
|
+
if (drawnH > MAX_ROWS_HEIGHT && rows.length > 1) {
|
|
75
|
+
rows.pop();
|
|
76
|
+
drawnH = (rows.at(-1)?.y ?? 0) + (rows.at(-1)?.height ?? 0);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (rows.length === 0)
|
|
81
|
+
return null;
|
|
82
|
+
const W = Math.max(240, layout.width);
|
|
83
|
+
const H = Math.max(60, drawnH + 6);
|
|
84
|
+
const out = [];
|
|
85
|
+
// tick header (position + faint line only — labels are dust at cell size)
|
|
86
|
+
for (const t of layout.ticks) {
|
|
87
|
+
out.push(`<line x1="${r1(t.x)}" x2="${r1(t.x)}" y1="0" y2="${r1(H)}" stroke="${palette.ink}" stroke-opacity="0.08" stroke-width="1"/>`);
|
|
88
|
+
}
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
// lane label
|
|
91
|
+
if (row.label)
|
|
92
|
+
out.push(thumbText(8, row.y + row.height / 2 + 3, thumbTruncate(row.label, 14), {
|
|
93
|
+
size: 10,
|
|
94
|
+
fill: palette.ink,
|
|
95
|
+
opacity: 0.55,
|
|
96
|
+
}));
|
|
97
|
+
// row hairline separator (alternate, like the component)
|
|
98
|
+
out.push(`<line x1="${r1(LANE_W)}" x2="${r1(W)}" y1="${r1(row.y + row.height)}" y2="${r1(row.y + row.height)}" stroke="${palette.ink}" stroke-opacity="0.08" stroke-width="1"/>`);
|
|
99
|
+
for (const b of row.bars) {
|
|
100
|
+
const f = barFill(palette, b.tone);
|
|
101
|
+
const s = barStroke(palette, b.tone);
|
|
102
|
+
out.push(`<rect x="${r1(b.x)}" y="${r1(b.y)}" width="${r1(Math.max(2, b.w))}" height="${BAR_H}" rx="4" fill="${f.fill}" fill-opacity="${f.opacity}" stroke="${s.stroke}" stroke-opacity="${s.opacity}" stroke-width="1"/>`);
|
|
103
|
+
if (b.progress != null) {
|
|
104
|
+
const pf = progressFill(palette, b.tone);
|
|
105
|
+
out.push(`<rect x="${r1(b.x)}" y="${r1(b.y)}" width="${r1(Math.max(2, (b.w * b.progress) / 100))}" height="${BAR_H}" rx="4" fill="${pf.fill}" fill-opacity="${pf.opacity}"/>`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
for (const m of row.milestones) {
|
|
109
|
+
const tone = m.tone ?? "accent";
|
|
110
|
+
const color = toneColor(palette, tone === "neutral" ? "accent" : tone);
|
|
111
|
+
const cx = r1(m.x);
|
|
112
|
+
const cy = r1(m.y);
|
|
113
|
+
out.push(`<polygon points="${cx},${cy - MS_R} ${cx + MS_R},${cy} ${cx},${cy + MS_R} ${cx - MS_R},${cy}" fill="${color}"/>`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return `<svg viewBox="0 0 ${r1(W)} ${r1(H)}">${out.join("")}</svg>`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline block — shared types. schema.ts owns the zod schema and infers
|
|
3
|
+
* TimelineParams from it; this file carries the layout vocabulary so
|
|
4
|
+
* schema.ts and TimelineBlock.svelte agree without a cycle.
|
|
5
|
+
*/
|
|
6
|
+
import type { z } from "zod";
|
|
7
|
+
import type { ToneTokenT } from "./constants.js";
|
|
8
|
+
import type { timelineParamsSchema } from "./schema.js";
|
|
9
|
+
export type ToneToken = ToneTokenT;
|
|
10
|
+
export type TimelineParams = z.infer<typeof timelineParamsSchema>;
|
|
11
|
+
export interface Point {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}
|
|
15
|
+
export interface Tick {
|
|
16
|
+
ms: number;
|
|
17
|
+
label: string;
|
|
18
|
+
/** x pixel position (layout fills this in). */
|
|
19
|
+
x: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TimelineBar {
|
|
22
|
+
id: string;
|
|
23
|
+
label: string;
|
|
24
|
+
tone?: ToneToken;
|
|
25
|
+
progress?: number;
|
|
26
|
+
start: string;
|
|
27
|
+
end: string;
|
|
28
|
+
/** Pixel geometry (layout fills these in). */
|
|
29
|
+
x: number;
|
|
30
|
+
w: number;
|
|
31
|
+
y: number;
|
|
32
|
+
/** Parsed epoch ms of start/end (for dependency routing). */
|
|
33
|
+
xStartMs: number;
|
|
34
|
+
xEndMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface TimelineMilestone {
|
|
37
|
+
id: string;
|
|
38
|
+
label: string;
|
|
39
|
+
tone?: ToneToken;
|
|
40
|
+
date: string;
|
|
41
|
+
/** Pixel center of the diamond (layout fills these in). */
|
|
42
|
+
x: number;
|
|
43
|
+
y: number;
|
|
44
|
+
}
|
|
45
|
+
export interface LaneRow {
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
y: number;
|
|
49
|
+
height: number;
|
|
50
|
+
bars: TimelineBar[];
|
|
51
|
+
milestones: TimelineMilestone[];
|
|
52
|
+
}
|
|
53
|
+
export interface TimelineLayout {
|
|
54
|
+
/** UTC-day-anchored domain (epoch ms). */
|
|
55
|
+
d0: number;
|
|
56
|
+
d1: number;
|
|
57
|
+
ticks: Tick[];
|
|
58
|
+
rows: LaneRow[];
|
|
59
|
+
/** Today line when it falls inside the domain (client-only). */
|
|
60
|
+
today: {
|
|
61
|
+
x: number;
|
|
62
|
+
} | null;
|
|
63
|
+
width: number;
|
|
64
|
+
height: number;
|
|
65
|
+
laneW: number;
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|