@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,500 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* HistoryScrubber (origin mos-p9m3g, living artifacts phase 4) — "what did
|
|
4
|
+
* this page look like yesterday at 9am?"
|
|
5
|
+
*
|
|
6
|
+
* A collapsed "History" control on a blocks page. On mount it loads the
|
|
7
|
+
* timeline through the host's HistoryService (layout versions + data
|
|
8
|
+
* snapshots) and renders NOTHING until there is something to scrub: a page
|
|
9
|
+
* with one version and no data trail (or a failed load) shows no control.
|
|
10
|
+
* Opened, a slider walks the timeline stop by stop, ending at "Now". At any
|
|
11
|
+
* past stop it shows:
|
|
12
|
+
*
|
|
13
|
+
* - which layout version and which data snapshot were live then;
|
|
14
|
+
* - the diff of that data against now — KPI deltas for stats-shaped
|
|
15
|
+
* values, added / removed / changed rows for tables, changed leaves for
|
|
16
|
+
* anything else (history.ts diffData);
|
|
17
|
+
* - the page itself as it was, rendered read-only by BlockRenderer from
|
|
18
|
+
* the past spec + past data.
|
|
19
|
+
*
|
|
20
|
+
* "Compare with" swaps "now" for a second stop: diff A → B.
|
|
21
|
+
*
|
|
22
|
+
* Self-contained by design: the view mounts it with the artifact id, the
|
|
23
|
+
* live data it is already polling and the host's HistoryService; no
|
|
24
|
+
* service → it is not mounted.
|
|
25
|
+
*/
|
|
26
|
+
import BlockRenderer from "./BlockRenderer.svelte"
|
|
27
|
+
import {
|
|
28
|
+
buildTimeline,
|
|
29
|
+
diffData,
|
|
30
|
+
formatDelta,
|
|
31
|
+
HISTORY_RETENTION,
|
|
32
|
+
type DataDiff,
|
|
33
|
+
type HistorySnapshot,
|
|
34
|
+
type HistoryVersion,
|
|
35
|
+
type TimelinePoint,
|
|
36
|
+
} from "./history.js"
|
|
37
|
+
import type { HistoryPastState, HistoryService } from "./types.js"
|
|
38
|
+
|
|
39
|
+
interface Props {
|
|
40
|
+
artifactId: string
|
|
41
|
+
/** The live data the host is showing now (its poller's blob). */
|
|
42
|
+
currentData: Record<string, unknown>
|
|
43
|
+
/** The layout version live now. */
|
|
44
|
+
currentVersion: number
|
|
45
|
+
/** The host's history reads (HostAdapter.history). */
|
|
46
|
+
service: HistoryService
|
|
47
|
+
/** Start expanded once there is a trail (tests, deep links). */
|
|
48
|
+
open?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let {
|
|
52
|
+
artifactId,
|
|
53
|
+
currentData,
|
|
54
|
+
currentVersion,
|
|
55
|
+
service,
|
|
56
|
+
open = $bindable(false),
|
|
57
|
+
}: Props = $props()
|
|
58
|
+
|
|
59
|
+
type PastState = HistoryPastState
|
|
60
|
+
|
|
61
|
+
/** A stop on the slider: a timeline point, or the synthetic "Now" end. */
|
|
62
|
+
type Stop = TimelinePoint | { kind: "now"; at: null; ms: number }
|
|
63
|
+
|
|
64
|
+
let loadState = $state<"idle" | "loading" | "ready" | "error">("idle")
|
|
65
|
+
let versions: HistoryVersion[] = $state([])
|
|
66
|
+
let snapshots: HistorySnapshot[] = $state([])
|
|
67
|
+
let trailStartAt: string | null = $state(null)
|
|
68
|
+
|
|
69
|
+
const stops: Stop[] = $derived([
|
|
70
|
+
...buildTimeline(versions, snapshots),
|
|
71
|
+
{ kind: "now", at: null, ms: Infinity },
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
/** Selected stop (A), and the compare-with stop (B) in compare mode. */
|
|
75
|
+
let index = $state(0)
|
|
76
|
+
let compare = $state(false)
|
|
77
|
+
let compareIndex = $state(0)
|
|
78
|
+
|
|
79
|
+
const stateCache = new Map<string, PastState>()
|
|
80
|
+
let selected = $state<PastState | null>(null)
|
|
81
|
+
let compared = $state<PastState | null>(null)
|
|
82
|
+
let stateError = $state("")
|
|
83
|
+
let fetching = $state(false)
|
|
84
|
+
|
|
85
|
+
const dateFmt = new Intl.DateTimeFormat(undefined, {
|
|
86
|
+
dateStyle: "medium",
|
|
87
|
+
timeStyle: "short",
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
function fmt(iso: string | null): string {
|
|
91
|
+
if (!iso) return "now"
|
|
92
|
+
const d = new Date(iso)
|
|
93
|
+
return Number.isNaN(d.getTime()) ? iso : dateFmt.format(d)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function stopLabel(s: Stop): string {
|
|
97
|
+
if (s.kind === "now") return "Now (live)"
|
|
98
|
+
if (s.kind === "version") return `${fmt(s.at)} · layout v${s.version}`
|
|
99
|
+
return `${fmt(s.at)} · data update`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function loadTimeline() {
|
|
103
|
+
loadState = "loading"
|
|
104
|
+
try {
|
|
105
|
+
const body = (await service.timeline({ artifactId })) as {
|
|
106
|
+
versions?: HistoryVersion[]
|
|
107
|
+
snapshots?: HistorySnapshot[]
|
|
108
|
+
trailStart?: string | null
|
|
109
|
+
}
|
|
110
|
+
versions = Array.isArray(body.versions) ? body.versions : []
|
|
111
|
+
snapshots = Array.isArray(body.snapshots) ? body.snapshots : []
|
|
112
|
+
trailStartAt = body.trailStart ?? null
|
|
113
|
+
index = stops.length - 1
|
|
114
|
+
compareIndex = stops.length - 1
|
|
115
|
+
loadState = "ready"
|
|
116
|
+
} catch {
|
|
117
|
+
// History is an optional affordance: a page whose trail cannot be read
|
|
118
|
+
// simply shows no control, never an error the viewer cannot act on.
|
|
119
|
+
loadState = "error"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
$effect(() => {
|
|
124
|
+
if (loadState === "idle") void loadTimeline()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
/** Something to scrub: more than the single layout version + "Now". */
|
|
128
|
+
const hasTrail = $derived(loadState === "ready" && stops.length > 2)
|
|
129
|
+
|
|
130
|
+
async function fetchState(s: Stop): Promise<PastState | null> {
|
|
131
|
+
if (s.kind === "now") return null
|
|
132
|
+
const hit = stateCache.get(s.at)
|
|
133
|
+
if (hit) return hit
|
|
134
|
+
const body = await service.at({ artifactId, at: s.at })
|
|
135
|
+
stateCache.set(s.at, body)
|
|
136
|
+
return body
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Resolve the selected (and compared) stops. Debounced so dragging the
|
|
140
|
+
// slider asks for the stop it lands on, not every stop it crosses; a
|
|
141
|
+
// stale response never overwrites a newer selection.
|
|
142
|
+
let ticket = 0
|
|
143
|
+
$effect(() => {
|
|
144
|
+
if (loadState !== "ready") return
|
|
145
|
+
const a = stops[index]
|
|
146
|
+
const b = compare ? stops[compareIndex] : undefined
|
|
147
|
+
const mine = ++ticket
|
|
148
|
+
const run = async () => {
|
|
149
|
+
fetching = true
|
|
150
|
+
stateError = ""
|
|
151
|
+
try {
|
|
152
|
+
const [sa, sb] = await Promise.all([
|
|
153
|
+
a ? fetchState(a) : null,
|
|
154
|
+
b ? fetchState(b) : null,
|
|
155
|
+
])
|
|
156
|
+
if (mine !== ticket) return
|
|
157
|
+
selected = sa
|
|
158
|
+
compared = sb
|
|
159
|
+
} catch {
|
|
160
|
+
if (mine === ticket) stateError = "Could not load that point in time."
|
|
161
|
+
} finally {
|
|
162
|
+
if (mine === ticket) fetching = false
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const t = setTimeout(run, 150)
|
|
166
|
+
return () => clearTimeout(t)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const atNow = $derived(stops[index]?.kind === "now")
|
|
170
|
+
const bAtNow = $derived(!compare || stops[compareIndex]?.kind === "now")
|
|
171
|
+
|
|
172
|
+
/** A's data and B's data (B = now unless comparing). */
|
|
173
|
+
const fromData = $derived(atNow ? currentData : (selected?.data ?? null))
|
|
174
|
+
const toData = $derived(bAtNow ? currentData : (compared?.data ?? null))
|
|
175
|
+
const fromVersion = $derived(atNow ? currentVersion : selected?.version)
|
|
176
|
+
const toVersion = $derived(bAtNow ? currentVersion : compared?.version)
|
|
177
|
+
|
|
178
|
+
const diff: DataDiff | null = $derived(
|
|
179
|
+
fromData && toData && !(atNow && bAtNow)
|
|
180
|
+
? diffData(fromData, toData)
|
|
181
|
+
: null,
|
|
182
|
+
)
|
|
183
|
+
const changed = $derived(
|
|
184
|
+
diff ? diff.keys.filter((k) => k.status !== "same") : [],
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
function step(delta: number) {
|
|
188
|
+
index = Math.min(stops.length - 1, Math.max(0, index + delta))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Change is shown by sign, never by color: whether "up" is good is the
|
|
192
|
+
// metric's business (a stats card's goodDirection), not the diff's.
|
|
193
|
+
|
|
194
|
+
function num(v: number | null): string {
|
|
195
|
+
return v === null
|
|
196
|
+
? "-"
|
|
197
|
+
: v.toLocaleString(undefined, { maximumFractionDigits: 2 })
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function leafText(v: unknown): string {
|
|
201
|
+
if (v === undefined) return "-"
|
|
202
|
+
const s = typeof v === "string" ? v : JSON.stringify(v)
|
|
203
|
+
return s.length > 60 ? `${s.slice(0, 59)}…` : s
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const toLabel = $derived(
|
|
207
|
+
bAtNow ? "now" : fmt(stops[compareIndex]?.at ?? null),
|
|
208
|
+
)
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
{#if hasTrail}
|
|
212
|
+
<!-- mos-8fvt9: collapsed, one quiet line (no card, no comment pill);
|
|
213
|
+
opened, the scrubber's card. -->
|
|
214
|
+
<section
|
|
215
|
+
class={open
|
|
216
|
+
? "border-base-content/10 bg-base-100 rounded-box mb-4 border"
|
|
217
|
+
: "mb-2"}
|
|
218
|
+
aria-label="Page history"
|
|
219
|
+
data-history-scrubber
|
|
220
|
+
>
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
class={open
|
|
224
|
+
? "btn btn-ghost btn-sm rounded-field w-full justify-between"
|
|
225
|
+
: "btn btn-ghost btn-xs rounded-field text-base-content/60 -ml-2 gap-1.5 font-normal"}
|
|
226
|
+
aria-expanded={open}
|
|
227
|
+
onclick={() => (open = !open)}
|
|
228
|
+
>
|
|
229
|
+
{#if open}
|
|
230
|
+
<span>History</span>
|
|
231
|
+
<span class="text-base-content/60 text-xs font-normal">Hide</span>
|
|
232
|
+
{:else}
|
|
233
|
+
<!-- History glyph (lucide "history" path), inline: no icon dependency. -->
|
|
234
|
+
<svg
|
|
235
|
+
width="14"
|
|
236
|
+
height="14"
|
|
237
|
+
viewBox="0 0 24 24"
|
|
238
|
+
fill="none"
|
|
239
|
+
stroke="currentColor"
|
|
240
|
+
stroke-width="2"
|
|
241
|
+
stroke-linecap="round"
|
|
242
|
+
stroke-linejoin="round"
|
|
243
|
+
aria-hidden="true"
|
|
244
|
+
><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" /><path
|
|
245
|
+
d="M3 3v5h5"
|
|
246
|
+
/><path d="M12 7v5l4 2" /></svg
|
|
247
|
+
>
|
|
248
|
+
<span>History</span>
|
|
249
|
+
<span class="text-base-content/40"
|
|
250
|
+
>· see this page at an earlier time</span
|
|
251
|
+
>
|
|
252
|
+
{/if}
|
|
253
|
+
</button>
|
|
254
|
+
|
|
255
|
+
{#if open}
|
|
256
|
+
<div class="border-base-content/10 space-y-4 border-t p-3 sm:p-4">
|
|
257
|
+
<div class="space-y-2">
|
|
258
|
+
<label
|
|
259
|
+
class="block text-sm font-medium"
|
|
260
|
+
for="history-at-{artifactId}"
|
|
261
|
+
>
|
|
262
|
+
{compare ? "Compare from" : "Show the page as it was at"}
|
|
263
|
+
</label>
|
|
264
|
+
<input
|
|
265
|
+
id="history-at-{artifactId}"
|
|
266
|
+
type="range"
|
|
267
|
+
class="range range-sm range-primary w-full"
|
|
268
|
+
min="0"
|
|
269
|
+
max={stops.length - 1}
|
|
270
|
+
step="1"
|
|
271
|
+
bind:value={index}
|
|
272
|
+
aria-valuetext={stopLabel(stops[index])}
|
|
273
|
+
/>
|
|
274
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
class="btn btn-ghost btn-xs rounded-field"
|
|
278
|
+
disabled={index === 0}
|
|
279
|
+
onclick={() => step(-1)}
|
|
280
|
+
aria-label="Earlier point">← Earlier</button
|
|
281
|
+
>
|
|
282
|
+
<button
|
|
283
|
+
type="button"
|
|
284
|
+
class="btn btn-ghost btn-xs rounded-field"
|
|
285
|
+
disabled={index === stops.length - 1}
|
|
286
|
+
onclick={() => step(1)}
|
|
287
|
+
aria-label="Later point">Later →</button
|
|
288
|
+
>
|
|
289
|
+
<button
|
|
290
|
+
type="button"
|
|
291
|
+
class="btn btn-ghost btn-xs rounded-field"
|
|
292
|
+
disabled={atNow}
|
|
293
|
+
onclick={() => (index = stops.length - 1)}>Back to now</button
|
|
294
|
+
>
|
|
295
|
+
<label class="label ml-auto cursor-pointer gap-2 text-sm">
|
|
296
|
+
<input
|
|
297
|
+
type="checkbox"
|
|
298
|
+
class="toggle toggle-sm"
|
|
299
|
+
bind:checked={compare}
|
|
300
|
+
/>
|
|
301
|
+
Compare with…
|
|
302
|
+
</label>
|
|
303
|
+
</div>
|
|
304
|
+
<p class="text-sm" aria-live="polite" data-history-selected>
|
|
305
|
+
<span class="font-medium">{stopLabel(stops[index])}</span>
|
|
306
|
+
<span class="text-base-content/60">
|
|
307
|
+
· {index + 1} of {stops.length}</span
|
|
308
|
+
>
|
|
309
|
+
</p>
|
|
310
|
+
|
|
311
|
+
{#if compare}
|
|
312
|
+
<label
|
|
313
|
+
class="block pt-2 text-sm font-medium"
|
|
314
|
+
for="history-to-{artifactId}">Compare to</label
|
|
315
|
+
>
|
|
316
|
+
<input
|
|
317
|
+
id="history-to-{artifactId}"
|
|
318
|
+
type="range"
|
|
319
|
+
class="range range-sm range-secondary w-full"
|
|
320
|
+
min="0"
|
|
321
|
+
max={stops.length - 1}
|
|
322
|
+
step="1"
|
|
323
|
+
bind:value={compareIndex}
|
|
324
|
+
aria-valuetext={stopLabel(stops[compareIndex])}
|
|
325
|
+
/>
|
|
326
|
+
<p class="text-sm" data-history-compared>
|
|
327
|
+
<span class="font-medium">{stopLabel(stops[compareIndex])}</span>
|
|
328
|
+
</p>
|
|
329
|
+
{/if}
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
{#if trailStartAt}
|
|
333
|
+
<p class="text-base-content/60 text-xs">
|
|
334
|
+
Data history starts {fmt(trailStartAt)}. Up to {HISTORY_RETENTION.maxSnapshots}
|
|
335
|
+
updates from the last {HISTORY_RETENTION.maxAgeDays} days are kept.
|
|
336
|
+
</p>
|
|
337
|
+
{/if}
|
|
338
|
+
|
|
339
|
+
{#if stateError}
|
|
340
|
+
<div
|
|
341
|
+
role="alert"
|
|
342
|
+
class="border-error/30 bg-error/10 text-error-content rounded-box border p-3 text-sm"
|
|
343
|
+
>
|
|
344
|
+
{stateError}
|
|
345
|
+
</div>
|
|
346
|
+
{:else if atNow && bAtNow}
|
|
347
|
+
<p class="text-base-content/70 text-sm">
|
|
348
|
+
You are looking at the live page. Move the slider back to see an
|
|
349
|
+
earlier state and what changed since.
|
|
350
|
+
</p>
|
|
351
|
+
{:else if fetching && !diff}
|
|
352
|
+
<p class="text-base-content/70 text-sm" role="status">Loading…</p>
|
|
353
|
+
{:else if diff}
|
|
354
|
+
<div class="space-y-3" data-history-diff>
|
|
355
|
+
<h3 class="text-sm font-semibold">
|
|
356
|
+
What changed, {atNow ? "now" : fmt(stops[index]?.at ?? null)} → {toLabel}
|
|
357
|
+
</h3>
|
|
358
|
+
{#if fromVersion !== undefined && toVersion !== undefined && fromVersion !== toVersion}
|
|
359
|
+
<p class="text-sm">
|
|
360
|
+
Layout changed: v{fromVersion} → v{toVersion}
|
|
361
|
+
</p>
|
|
362
|
+
{/if}
|
|
363
|
+
{#if selected?.beforeTrail && !atNow}
|
|
364
|
+
<p
|
|
365
|
+
class="bg-warning/10 text-warning-content rounded-box p-2 text-xs"
|
|
366
|
+
>
|
|
367
|
+
This point is older than the retained data history; the page
|
|
368
|
+
shows its built-in numbers, which may differ from what was on
|
|
369
|
+
screen then.
|
|
370
|
+
</p>
|
|
371
|
+
{/if}
|
|
372
|
+
{#if changed.length === 0}
|
|
373
|
+
<p class="text-base-content/70 text-sm">No data changed.</p>
|
|
374
|
+
{/if}
|
|
375
|
+
{#each changed as k (k.key)}
|
|
376
|
+
<div class="border-base-content/10 rounded-box border p-2">
|
|
377
|
+
<p class="text-sm font-medium">
|
|
378
|
+
<code class="text-xs">{k.key}</code>
|
|
379
|
+
{#if k.status === "added"}
|
|
380
|
+
<span class="badge badge-success badge-sm ml-1">added</span>
|
|
381
|
+
{:else if k.status === "removed"}
|
|
382
|
+
<span class="badge badge-error badge-sm ml-1">removed</span>
|
|
383
|
+
{/if}
|
|
384
|
+
</p>
|
|
385
|
+
{#if k.status === "changed" && k.shape === "kpi"}
|
|
386
|
+
<div class="overflow-x-auto">
|
|
387
|
+
<table class="table table-xs mt-1">
|
|
388
|
+
<thead>
|
|
389
|
+
<tr>
|
|
390
|
+
<th scope="col">Metric</th>
|
|
391
|
+
<th scope="col" class="text-right">Then</th>
|
|
392
|
+
<th scope="col" class="text-right">Now</th>
|
|
393
|
+
<th scope="col" class="text-right">Change</th>
|
|
394
|
+
</tr>
|
|
395
|
+
</thead>
|
|
396
|
+
<tbody>
|
|
397
|
+
{#each k.kpis as kpi (kpi.label)}
|
|
398
|
+
<tr>
|
|
399
|
+
<th scope="row" class="font-normal">{kpi.label}</th>
|
|
400
|
+
<td class="text-right tabular-nums"
|
|
401
|
+
>{num(kpi.then)}</td
|
|
402
|
+
>
|
|
403
|
+
<td class="text-right tabular-nums"
|
|
404
|
+
>{num(kpi.now)}</td
|
|
405
|
+
>
|
|
406
|
+
<td class="text-right font-medium tabular-nums">
|
|
407
|
+
{kpi.delta === null
|
|
408
|
+
? "-"
|
|
409
|
+
: formatDelta(kpi.delta, kpi.pct)}
|
|
410
|
+
</td>
|
|
411
|
+
</tr>
|
|
412
|
+
{/each}
|
|
413
|
+
</tbody>
|
|
414
|
+
</table>
|
|
415
|
+
</div>
|
|
416
|
+
{:else if k.status === "changed" && k.shape === "table"}
|
|
417
|
+
<p class="text-sm">
|
|
418
|
+
<span class="text-success">{k.added.length} added</span>
|
|
419
|
+
· <span class="text-error">{k.removed.length} removed</span>
|
|
420
|
+
· <span>{k.changed.length} changed</span>
|
|
421
|
+
{#if k.truncated}
|
|
422
|
+
<span class="text-base-content/60">
|
|
423
|
+
(+{k.truncated} more)</span
|
|
424
|
+
>
|
|
425
|
+
{/if}
|
|
426
|
+
</p>
|
|
427
|
+
<ul class="mt-1 space-y-0.5 text-xs">
|
|
428
|
+
{#each k.added as r (`a-${r.key}`)}
|
|
429
|
+
<li><span class="text-success">+</span> {r.key}</li>
|
|
430
|
+
{/each}
|
|
431
|
+
{#each k.removed as r (`r-${r.key}`)}
|
|
432
|
+
<li><span class="text-error">−</span> {r.key}</li>
|
|
433
|
+
{/each}
|
|
434
|
+
{#each k.changed as r (`c-${r.key}`)}
|
|
435
|
+
<li>
|
|
436
|
+
<span class="text-base-content/60">~</span>
|
|
437
|
+
{r.key}:
|
|
438
|
+
<span class="text-base-content/70"
|
|
439
|
+
>{r.fields.join(", ")}</span
|
|
440
|
+
>
|
|
441
|
+
</li>
|
|
442
|
+
{/each}
|
|
443
|
+
</ul>
|
|
444
|
+
{:else if k.status === "changed" && k.shape === "value"}
|
|
445
|
+
<ul class="mt-1 space-y-0.5 text-xs">
|
|
446
|
+
{#each k.leaves as l (l.path)}
|
|
447
|
+
<li class="break-words">
|
|
448
|
+
<code>{l.path}</code>:
|
|
449
|
+
{leafText(l.then)} → {leafText(l.now)}
|
|
450
|
+
{#if l.delta !== null}
|
|
451
|
+
<span class="font-medium"
|
|
452
|
+
>({formatDelta(l.delta)})</span
|
|
453
|
+
>
|
|
454
|
+
{/if}
|
|
455
|
+
</li>
|
|
456
|
+
{/each}
|
|
457
|
+
{#if k.truncated}
|
|
458
|
+
<li class="text-base-content/60">+{k.truncated} more</li>
|
|
459
|
+
{/if}
|
|
460
|
+
</ul>
|
|
461
|
+
{/if}
|
|
462
|
+
</div>
|
|
463
|
+
{/each}
|
|
464
|
+
</div>
|
|
465
|
+
{/if}
|
|
466
|
+
|
|
467
|
+
{#if !compare && !atNow && selected}
|
|
468
|
+
<div
|
|
469
|
+
class="border-base-content/10 rounded-box border"
|
|
470
|
+
data-history-past-page
|
|
471
|
+
>
|
|
472
|
+
<p
|
|
473
|
+
class="bg-base-200 text-base-content/80 rounded-t-box border-base-content/10 border-b px-3 py-2 text-xs"
|
|
474
|
+
>
|
|
475
|
+
The page as it was {fmt(selected.at)}: layout v{selected.version}{selected.dataAt
|
|
476
|
+
? `, data from ${fmt(selected.dataAt)}`
|
|
477
|
+
: ""}. Read-only.
|
|
478
|
+
</p>
|
|
479
|
+
<div class="p-3">
|
|
480
|
+
{#if selected.spec}
|
|
481
|
+
<BlockRenderer
|
|
482
|
+
spec={selected.spec}
|
|
483
|
+
{artifactId}
|
|
484
|
+
version={selected.version}
|
|
485
|
+
data={selected.data}
|
|
486
|
+
readonly
|
|
487
|
+
/>
|
|
488
|
+
{:else}
|
|
489
|
+
<p class="text-base-content/70 text-sm">
|
|
490
|
+
Version {selected.version} is not a blocks page, so it cannot be
|
|
491
|
+
shown here.
|
|
492
|
+
</p>
|
|
493
|
+
{/if}
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
{/if}
|
|
497
|
+
</div>
|
|
498
|
+
{/if}
|
|
499
|
+
</section>
|
|
500
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HistoryService } from "./types.js";
|
|
2
|
+
interface Props {
|
|
3
|
+
artifactId: string;
|
|
4
|
+
/** The live data the host is showing now (its poller's blob). */
|
|
5
|
+
currentData: Record<string, unknown>;
|
|
6
|
+
/** The layout version live now. */
|
|
7
|
+
currentVersion: number;
|
|
8
|
+
/** The host's history reads (HostAdapter.history). */
|
|
9
|
+
service: HistoryService;
|
|
10
|
+
/** Start expanded once there is a trail (tests, deep links). */
|
|
11
|
+
open?: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const HistoryScrubber: import("svelte").Component<Props, {}, "open">;
|
|
14
|
+
type HistoryScrubber = ReturnType<typeof HistoryScrubber>;
|
|
15
|
+
export default HistoryScrubber;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Markdown (jatui-vsuzi) — the ONE rich-prose surface of artifact blocks:
|
|
4
|
+
* source → the shared sanitized renderer (blocks/markdown/render.ts) →
|
|
5
|
+
* {@html}, with the prose styles carried HERE rather than borrowed from a
|
|
6
|
+
* host's typography plugin. A host without `@tailwindcss/typography` (the
|
|
7
|
+
* jat IDE) rendered MOS markdown as flat text with invisible links
|
|
8
|
+
* (PARAMS-SPEC §4); these scoped styles make headings, lists, tables,
|
|
9
|
+
* code and — above all — links visible everywhere.
|
|
10
|
+
*
|
|
11
|
+
* Token-only theming (DaisyUI `--color-*` → jatui radius tokens), light +
|
|
12
|
+
* dark by construction, phone-safe (tables scroll inside the block, long
|
|
13
|
+
* words wrap). Paragraphs and list items cap near 70ch.
|
|
14
|
+
*
|
|
15
|
+
* The {@html} here is the contract's sanctioned path: the HTML has passed
|
|
16
|
+
* marked + DOMPurify with the blocks URL policy.
|
|
17
|
+
*/
|
|
18
|
+
import type { Snippet } from "svelte"
|
|
19
|
+
import {
|
|
20
|
+
renderBlockMarkdown,
|
|
21
|
+
renderBlockMarkdownInline,
|
|
22
|
+
} from "./blocks/markdown/render.js"
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
source: string | null | undefined
|
|
26
|
+
/** Inline only: no paragraphs/headings (labels, option details). */
|
|
27
|
+
inline?: boolean
|
|
28
|
+
/** Smaller body text (card sections, comment threads). */
|
|
29
|
+
size?: "base" | "sm"
|
|
30
|
+
class?: string
|
|
31
|
+
/** Rendered before the prose (SectionTools floats here). */
|
|
32
|
+
children?: Snippet
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
source,
|
|
37
|
+
inline = false,
|
|
38
|
+
size = "base",
|
|
39
|
+
class: className = "",
|
|
40
|
+
children,
|
|
41
|
+
}: Props = $props()
|
|
42
|
+
|
|
43
|
+
const html = $derived(
|
|
44
|
+
inline ? renderBlockMarkdownInline(source) : renderBlockMarkdown(source),
|
|
45
|
+
)
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
{#if inline}
|
|
49
|
+
<span class="ab-md ab-md-inline {className}">{@html html}</span>
|
|
50
|
+
{:else}
|
|
51
|
+
<div class="ab-md flow-root text-base-content {className}" class:ab-md-sm={size === "sm"}>
|
|
52
|
+
{@render children?.()}
|
|
53
|
+
{@html html}
|
|
54
|
+
</div>
|
|
55
|
+
{/if}
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.ab-md {
|
|
59
|
+
min-width: 0;
|
|
60
|
+
max-width: 100%;
|
|
61
|
+
font-size: 1rem;
|
|
62
|
+
line-height: 1.65;
|
|
63
|
+
overflow-wrap: anywhere;
|
|
64
|
+
}
|
|
65
|
+
.ab-md-sm {
|
|
66
|
+
font-size: 0.875rem;
|
|
67
|
+
line-height: 1.6;
|
|
68
|
+
}
|
|
69
|
+
.ab-md :global(:where(p, ul, ol, blockquote, pre, hr, .md-table)) {
|
|
70
|
+
margin-block: 0.75em;
|
|
71
|
+
}
|
|
72
|
+
.ab-md :global(:where(p, li)) {
|
|
73
|
+
max-width: 70ch;
|
|
74
|
+
}
|
|
75
|
+
.ab-md > :global(:first-child),
|
|
76
|
+
.ab-md :global(:where(li) > :first-child) {
|
|
77
|
+
margin-top: 0;
|
|
78
|
+
}
|
|
79
|
+
.ab-md > :global(:last-child) {
|
|
80
|
+
margin-bottom: 0;
|
|
81
|
+
}
|
|
82
|
+
.ab-md :global(:where(h1, h2, h3, h4, h5, h6)) {
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
letter-spacing: -0.01em;
|
|
85
|
+
line-height: 1.3;
|
|
86
|
+
margin-block: 1.4em 0.5em;
|
|
87
|
+
}
|
|
88
|
+
.ab-md :global(h1) {
|
|
89
|
+
font-size: 1.5em;
|
|
90
|
+
}
|
|
91
|
+
.ab-md :global(h2) {
|
|
92
|
+
font-size: 1.25em;
|
|
93
|
+
}
|
|
94
|
+
.ab-md :global(h3) {
|
|
95
|
+
font-size: 1.1em;
|
|
96
|
+
}
|
|
97
|
+
.ab-md :global(:where(h4, h5, h6)) {
|
|
98
|
+
font-size: 1em;
|
|
99
|
+
}
|
|
100
|
+
.ab-md :global(:where(strong, b)) {
|
|
101
|
+
font-weight: 600;
|
|
102
|
+
}
|
|
103
|
+
/* Links are VISIBLE: primary hue + underline, never inherited body text. */
|
|
104
|
+
.ab-md :global(a[href]) {
|
|
105
|
+
color: var(--color-primary);
|
|
106
|
+
text-decoration: underline;
|
|
107
|
+
text-decoration-thickness: from-font;
|
|
108
|
+
text-underline-offset: 0.15em;
|
|
109
|
+
}
|
|
110
|
+
.ab-md :global(a[href]:hover) {
|
|
111
|
+
text-decoration-thickness: 2px;
|
|
112
|
+
}
|
|
113
|
+
.ab-md :global(a[href]:focus-visible) {
|
|
114
|
+
outline: 2px solid var(--color-primary);
|
|
115
|
+
outline-offset: 2px;
|
|
116
|
+
border-radius: var(--jatui-radius-field, 0.25rem);
|
|
117
|
+
}
|
|
118
|
+
.ab-md :global(ul) {
|
|
119
|
+
list-style: disc;
|
|
120
|
+
padding-left: 1.4em;
|
|
121
|
+
}
|
|
122
|
+
.ab-md :global(ol) {
|
|
123
|
+
list-style: decimal;
|
|
124
|
+
padding-left: 1.6em;
|
|
125
|
+
}
|
|
126
|
+
.ab-md :global(li) {
|
|
127
|
+
margin-block: 0.25em;
|
|
128
|
+
}
|
|
129
|
+
.ab-md :global(li::marker) {
|
|
130
|
+
color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
|
|
131
|
+
}
|
|
132
|
+
.ab-md :global(blockquote) {
|
|
133
|
+
border-left: 3px solid
|
|
134
|
+
color-mix(in oklab, var(--color-base-content) 20%, transparent);
|
|
135
|
+
padding-left: 0.9em;
|
|
136
|
+
color: color-mix(in oklab, var(--color-base-content) 75%, transparent);
|
|
137
|
+
}
|
|
138
|
+
.ab-md :global(hr) {
|
|
139
|
+
border: 0;
|
|
140
|
+
border-top: 1px solid
|
|
141
|
+
color-mix(in oklab, var(--color-base-content) 15%, transparent);
|
|
142
|
+
}
|
|
143
|
+
.ab-md :global(:not(pre) > code) {
|
|
144
|
+
font-size: 0.875em;
|
|
145
|
+
padding: 0.1em 0.4em;
|
|
146
|
+
border-radius: var(--jatui-radius-field, 0.25rem);
|
|
147
|
+
background: color-mix(in oklab, var(--color-base-content) 10%, transparent);
|
|
148
|
+
}
|
|
149
|
+
.ab-md :global(pre) {
|
|
150
|
+
overflow-x: auto;
|
|
151
|
+
padding: 0.8em 1em;
|
|
152
|
+
border-radius: var(--jatui-radius-box, 0.5rem);
|
|
153
|
+
background: color-mix(in oklab, var(--color-base-content) 8%, transparent);
|
|
154
|
+
font-size: 0.875em;
|
|
155
|
+
line-height: 1.5;
|
|
156
|
+
overflow-wrap: normal;
|
|
157
|
+
}
|
|
158
|
+
.ab-md :global(.md-table) {
|
|
159
|
+
overflow-x: auto;
|
|
160
|
+
}
|
|
161
|
+
.ab-md :global(table) {
|
|
162
|
+
border-collapse: collapse;
|
|
163
|
+
font-size: 0.875em;
|
|
164
|
+
min-width: 100%;
|
|
165
|
+
}
|
|
166
|
+
.ab-md :global(:where(th, td)) {
|
|
167
|
+
padding: 0.4em 0.75em;
|
|
168
|
+
border-bottom: 1px solid
|
|
169
|
+
color-mix(in oklab, var(--color-base-content) 12%, transparent);
|
|
170
|
+
text-align: left;
|
|
171
|
+
font-variant-numeric: tabular-nums;
|
|
172
|
+
overflow-wrap: normal;
|
|
173
|
+
}
|
|
174
|
+
.ab-md :global(th) {
|
|
175
|
+
font-weight: 600;
|
|
176
|
+
}
|
|
177
|
+
.ab-md :global(img) {
|
|
178
|
+
max-width: 100%;
|
|
179
|
+
height: auto;
|
|
180
|
+
border-radius: var(--jatui-radius-box, 0.5rem);
|
|
181
|
+
}
|
|
182
|
+
.ab-md-inline :global(p) {
|
|
183
|
+
display: inline;
|
|
184
|
+
margin: 0;
|
|
185
|
+
}
|
|
186
|
+
</style>
|