@cfbender/cesium 0.3.6 → 0.5.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/CHANGELOG.md +53 -0
- package/README.md +28 -14
- package/assets/styleguide.html +149 -0
- package/package.json +1 -1
- package/src/cli/commands/serve.ts +3 -0
- package/src/index.ts +4 -1
- package/src/prompt/field-reference.ts +94 -0
- package/src/prompt/system-fragment.md +56 -65
- package/src/render/blocks/catalog.ts +39 -0
- package/src/render/blocks/escape.ts +27 -0
- package/src/render/blocks/index.ts +6 -0
- package/src/render/blocks/markdown.ts +217 -0
- package/src/render/blocks/render.ts +96 -0
- package/src/render/blocks/renderers/callout.ts +38 -0
- package/src/render/blocks/renderers/code.ts +44 -0
- package/src/render/blocks/renderers/compare-table.ts +56 -0
- package/src/render/blocks/renderers/diagram.ts +48 -0
- package/src/render/blocks/renderers/divider.ts +31 -0
- package/src/render/blocks/renderers/hero.ts +66 -0
- package/src/render/blocks/renderers/kv.ts +45 -0
- package/src/render/blocks/renderers/list.ts +51 -0
- package/src/render/blocks/renderers/pill-row.ts +45 -0
- package/src/render/blocks/renderers/prose.ts +29 -0
- package/src/render/blocks/renderers/raw-html.ts +32 -0
- package/src/render/blocks/renderers/risk-table.ts +76 -0
- package/src/render/blocks/renderers/section.ts +95 -0
- package/src/render/blocks/renderers/timeline.ts +58 -0
- package/src/render/blocks/renderers/tldr.ts +30 -0
- package/src/render/blocks/types.ts +127 -0
- package/src/render/blocks/validate-block.ts +202 -0
- package/src/render/critique.ts +410 -10
- package/src/render/fallback.ts +18 -0
- package/src/render/theme.ts +235 -0
- package/src/render/validate.ts +282 -17
- package/src/render/wrap.ts +7 -7
- package/src/server/lifecycle.ts +7 -1
- package/src/storage/assets.ts +66 -0
- package/src/storage/index-cache.ts +1 -0
- package/src/storage/index-gen.ts +13 -14
- package/src/tools/ask.ts +5 -3
- package/src/tools/critique.ts +41 -6
- package/src/tools/publish.ts +39 -12
- package/src/tools/styleguide.ts +109 -9
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// List block renderer.
|
|
2
|
+
// src/render/blocks/renderers/list.ts
|
|
3
|
+
|
|
4
|
+
import type { ListBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { renderMarkdown } from "../markdown.ts";
|
|
8
|
+
|
|
9
|
+
export function renderList(block: ListBlock, _ctx: RenderCtx): string {
|
|
10
|
+
const style = block.style ?? "bullet";
|
|
11
|
+
|
|
12
|
+
const items = block.items
|
|
13
|
+
.map((item) => {
|
|
14
|
+
const content = renderMarkdown(item).replace(/^<p>|<\/p>$/g, "");
|
|
15
|
+
return ` <li>${content}</li>`;
|
|
16
|
+
})
|
|
17
|
+
.join("\n");
|
|
18
|
+
|
|
19
|
+
if (style === "number") {
|
|
20
|
+
return `<ol>\n${items}\n</ol>`;
|
|
21
|
+
} else if (style === "check") {
|
|
22
|
+
const checkItems = block.items
|
|
23
|
+
.map((item) => {
|
|
24
|
+
const content = renderMarkdown(item).replace(/^<p>|<\/p>$/g, "");
|
|
25
|
+
return ` <li class="check">${content}</li>`;
|
|
26
|
+
})
|
|
27
|
+
.join("\n");
|
|
28
|
+
return `<ul class="check-list">\n${checkItems}\n</ul>`;
|
|
29
|
+
} else {
|
|
30
|
+
return `<ul>\n${items}\n</ul>`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const meta: BlockMeta = {
|
|
35
|
+
type: "list",
|
|
36
|
+
description: "Bullet, numbered, or checklist. Items are markdown strings.",
|
|
37
|
+
schema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
type: { const: "list" },
|
|
41
|
+
style: { type: "string", enum: ["bullet", "number", "check"] },
|
|
42
|
+
items: { type: "array", items: { type: "string" } },
|
|
43
|
+
},
|
|
44
|
+
required: ["type", "items"],
|
|
45
|
+
},
|
|
46
|
+
example: {
|
|
47
|
+
type: "list",
|
|
48
|
+
style: "bullet",
|
|
49
|
+
items: ["First item with **bold**", "Second item", "Third item"],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// PillRow block renderer.
|
|
2
|
+
// src/render/blocks/renderers/pill-row.ts
|
|
3
|
+
|
|
4
|
+
import type { PillRowBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { escapeHtml } from "../escape.ts";
|
|
8
|
+
|
|
9
|
+
export function renderPillRow(block: PillRowBlock, _ctx: RenderCtx): string {
|
|
10
|
+
const pills = block.items
|
|
11
|
+
.map((item) => ` <span class="${item.kind}">${escapeHtml(item.text)}</span>`)
|
|
12
|
+
.join("\n");
|
|
13
|
+
return `<div class="pill-row">\n${pills}\n</div>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const meta: BlockMeta = {
|
|
17
|
+
type: "pill_row",
|
|
18
|
+
description: "Horizontal row of pill or tag chips. Each item has a kind (pill or tag) and text.",
|
|
19
|
+
schema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
type: { const: "pill_row" },
|
|
23
|
+
items: {
|
|
24
|
+
type: "array",
|
|
25
|
+
items: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
kind: { type: "string", enum: ["pill", "tag"] },
|
|
29
|
+
text: { type: "string" },
|
|
30
|
+
},
|
|
31
|
+
required: ["kind", "text"],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
required: ["type", "items"],
|
|
36
|
+
},
|
|
37
|
+
example: {
|
|
38
|
+
type: "pill_row",
|
|
39
|
+
items: [
|
|
40
|
+
{ kind: "pill", text: "TypeScript" },
|
|
41
|
+
{ kind: "pill", text: "Bun" },
|
|
42
|
+
{ kind: "tag", text: "phase-2" },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Prose block renderer.
|
|
2
|
+
// src/render/blocks/renderers/prose.ts
|
|
3
|
+
|
|
4
|
+
import type { ProseBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { renderMarkdown } from "../markdown.ts";
|
|
8
|
+
|
|
9
|
+
export function renderProse(block: ProseBlock, _ctx: RenderCtx): string {
|
|
10
|
+
return renderMarkdown(block.markdown);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const meta: BlockMeta = {
|
|
14
|
+
type: "prose",
|
|
15
|
+
description: "Free-form markdown text block. Renders paragraphs, lists, emphasis, links.",
|
|
16
|
+
schema: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
type: { const: "prose" },
|
|
20
|
+
markdown: { type: "string" },
|
|
21
|
+
},
|
|
22
|
+
required: ["type", "markdown"],
|
|
23
|
+
},
|
|
24
|
+
example: {
|
|
25
|
+
type: "prose",
|
|
26
|
+
markdown:
|
|
27
|
+
"This is a paragraph with **bold** and *italic* text.\n\n- Item one\n- Item two",
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// RawHtml block renderer — escape-hatch for fully custom HTML payloads.
|
|
2
|
+
// src/render/blocks/renderers/raw-html.ts
|
|
3
|
+
|
|
4
|
+
import type { RawHtmlBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { scrub } from "../../scrub.ts";
|
|
8
|
+
|
|
9
|
+
export function renderRawHtml(block: RawHtmlBlock, _ctx: RenderCtx): string {
|
|
10
|
+
const scrubResult = scrub(block.html);
|
|
11
|
+
return scrubResult.html;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const meta: BlockMeta = {
|
|
15
|
+
type: "raw_html",
|
|
16
|
+
description:
|
|
17
|
+
"Fully custom HTML payload. Scrubbed of external resources. Use when no structured block fits. Include a purpose string for audit trail.",
|
|
18
|
+
schema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
type: { const: "raw_html" },
|
|
22
|
+
html: { type: "string" },
|
|
23
|
+
purpose: { type: "string" },
|
|
24
|
+
},
|
|
25
|
+
required: ["type", "html"],
|
|
26
|
+
},
|
|
27
|
+
example: {
|
|
28
|
+
type: "raw_html",
|
|
29
|
+
html: '<div class="card" style="display:grid;grid-template-columns:1fr 1fr;gap:16px;"><div><h3>Option A</h3><p>Fast but brittle.</p></div><div><h3>Option B</h3><p>Slower but robust.</p></div></div>',
|
|
30
|
+
purpose: "Two-column card layout not expressible as compare_table",
|
|
31
|
+
},
|
|
32
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// RiskTable block renderer.
|
|
2
|
+
// src/render/blocks/renderers/risk-table.ts
|
|
3
|
+
|
|
4
|
+
import type { RiskTableBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { escapeHtml } from "../escape.ts";
|
|
8
|
+
|
|
9
|
+
export function renderRiskTable(block: RiskTableBlock, _ctx: RenderCtx): string {
|
|
10
|
+
const headerRow =
|
|
11
|
+
" <thead>\n <tr>\n" +
|
|
12
|
+
" <th>Risk</th>\n <th>Likelihood</th>\n <th>Impact</th>\n <th>Mitigation</th>\n" +
|
|
13
|
+
" </tr>\n </thead>";
|
|
14
|
+
|
|
15
|
+
const bodyRows = block.rows
|
|
16
|
+
.map((row) => {
|
|
17
|
+
return (
|
|
18
|
+
` <tr>\n` +
|
|
19
|
+
` <td>${escapeHtml(row.risk)}</td>\n` +
|
|
20
|
+
` <td class="risk-${escapeHtml(row.likelihood)}">${escapeHtml(row.likelihood)}</td>\n` +
|
|
21
|
+
` <td class="risk-${escapeHtml(row.impact)}">${escapeHtml(row.impact)}</td>\n` +
|
|
22
|
+
` <td>${escapeHtml(row.mitigation)}</td>\n` +
|
|
23
|
+
` </tr>`
|
|
24
|
+
);
|
|
25
|
+
})
|
|
26
|
+
.join("\n");
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
`<table class="risk-table">\n` +
|
|
30
|
+
`${headerRow}\n` +
|
|
31
|
+
` <tbody>\n${bodyRows}\n </tbody>\n` +
|
|
32
|
+
`</table>`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const meta: BlockMeta = {
|
|
37
|
+
type: "risk_table",
|
|
38
|
+
description: "Risk register grid with likelihood/impact/mitigation columns.",
|
|
39
|
+
schema: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
type: { const: "risk_table" },
|
|
43
|
+
rows: {
|
|
44
|
+
type: "array",
|
|
45
|
+
items: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
risk: { type: "string" },
|
|
49
|
+
likelihood: { type: "string", enum: ["low", "medium", "high"] },
|
|
50
|
+
impact: { type: "string", enum: ["low", "medium", "high"] },
|
|
51
|
+
mitigation: { type: "string" },
|
|
52
|
+
},
|
|
53
|
+
required: ["risk", "likelihood", "impact", "mitigation"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ["type", "rows"],
|
|
58
|
+
},
|
|
59
|
+
example: {
|
|
60
|
+
type: "risk_table",
|
|
61
|
+
rows: [
|
|
62
|
+
{
|
|
63
|
+
risk: "Agent ignores blocks mode",
|
|
64
|
+
likelihood: "high",
|
|
65
|
+
impact: "high",
|
|
66
|
+
mitigation: "Prompt steering + critique bonus + styleguide.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
risk: "Markdown subset too narrow",
|
|
70
|
+
likelihood: "low",
|
|
71
|
+
impact: "medium",
|
|
72
|
+
mitigation: "Audit first 50 artifacts; expand if needed.",
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Section block renderer.
|
|
2
|
+
// src/render/blocks/renderers/section.ts
|
|
3
|
+
|
|
4
|
+
import type { SectionBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { renderBlock } from "../render.ts";
|
|
8
|
+
import { escapeHtml } from "../escape.ts";
|
|
9
|
+
|
|
10
|
+
export function renderSection(block: SectionBlock, ctx: RenderCtx): string {
|
|
11
|
+
// Determine section number: explicit or auto-increment
|
|
12
|
+
let num: string;
|
|
13
|
+
if (block.num !== undefined && block.num !== "") {
|
|
14
|
+
num = block.num;
|
|
15
|
+
} else {
|
|
16
|
+
num = String(ctx.sectionCounter.value).padStart(2, "0");
|
|
17
|
+
ctx.sectionCounter.value += 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const parts: string[] = [];
|
|
21
|
+
|
|
22
|
+
if (block.eyebrow !== undefined && block.eyebrow !== "") {
|
|
23
|
+
parts.push(` <div class="eyebrow">${escapeHtml(block.eyebrow)}</div>`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
parts.push(
|
|
27
|
+
` <h2 class="h-section"><span class="section-num">${escapeHtml(num)}</span> ${escapeHtml(block.title)}</h2>`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Render children with incremented depth.
|
|
31
|
+
// Non-section children are grouped into <div class="card"> wrappers for visual polish.
|
|
32
|
+
// Nested section children are emitted at top level (they carry their own card structure).
|
|
33
|
+
const childCtx: RenderCtx = {
|
|
34
|
+
sectionCounter: ctx.sectionCounter,
|
|
35
|
+
depth: ctx.depth + 1,
|
|
36
|
+
path: `${ctx.path}.children`,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
let buffer: string[] = [];
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < block.children.length; i++) {
|
|
42
|
+
const child = block.children[i];
|
|
43
|
+
if (child === undefined) continue;
|
|
44
|
+
const childBlockCtx: RenderCtx = {
|
|
45
|
+
...childCtx,
|
|
46
|
+
path: `${ctx.path}.children[${i}]`,
|
|
47
|
+
};
|
|
48
|
+
const rendered = renderBlock(child, childBlockCtx);
|
|
49
|
+
if (child.type === "section") {
|
|
50
|
+
// Flush buffered non-section children into a card first
|
|
51
|
+
if (buffer.length > 0) {
|
|
52
|
+
parts.push(` <div class="card">\n${buffer.join("\n")}\n </div>`);
|
|
53
|
+
buffer = [];
|
|
54
|
+
}
|
|
55
|
+
parts.push(` ${rendered}`);
|
|
56
|
+
} else {
|
|
57
|
+
buffer.push(` ${rendered}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Flush any remaining non-section children
|
|
62
|
+
if (buffer.length > 0) {
|
|
63
|
+
parts.push(` <div class="card">\n${buffer.join("\n")}\n </div>`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `<section>\n${parts.join("\n")}\n</section>`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const meta: BlockMeta = {
|
|
70
|
+
type: "section",
|
|
71
|
+
description:
|
|
72
|
+
"Numbered section with title and child blocks. Only block type with children. Nesting depth ≤ 3.",
|
|
73
|
+
schema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
type: { const: "section" },
|
|
77
|
+
title: { type: "string" },
|
|
78
|
+
num: { type: "string" },
|
|
79
|
+
eyebrow: { type: "string" },
|
|
80
|
+
children: { type: "array", items: { type: "object" } },
|
|
81
|
+
},
|
|
82
|
+
required: ["type", "title", "children"],
|
|
83
|
+
},
|
|
84
|
+
example: {
|
|
85
|
+
type: "section",
|
|
86
|
+
title: "Goals",
|
|
87
|
+
eyebrow: "Why we're here",
|
|
88
|
+
children: [
|
|
89
|
+
{
|
|
90
|
+
type: "prose",
|
|
91
|
+
markdown: "Reduce output tokens by moving structural HTML into the server.",
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Timeline block renderer.
|
|
2
|
+
// src/render/blocks/renderers/timeline.ts
|
|
3
|
+
|
|
4
|
+
import type { TimelineBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { escapeHtml } from "../escape.ts";
|
|
8
|
+
|
|
9
|
+
export function renderTimeline(block: TimelineBlock, _ctx: RenderCtx): string {
|
|
10
|
+
const items = block.items
|
|
11
|
+
.map((item) => {
|
|
12
|
+
const dateHtml =
|
|
13
|
+
item.date !== undefined && item.date !== ""
|
|
14
|
+
? ` <span class="timeline-date">${escapeHtml(item.date)}</span>`
|
|
15
|
+
: "";
|
|
16
|
+
return (
|
|
17
|
+
` <li class="timeline-item">\n` +
|
|
18
|
+
` <span class="timeline-label">${escapeHtml(item.label)}${dateHtml}</span>\n` +
|
|
19
|
+
` <span class="timeline-text">${escapeHtml(item.text)}</span>\n` +
|
|
20
|
+
` </li>`
|
|
21
|
+
);
|
|
22
|
+
})
|
|
23
|
+
.join("\n");
|
|
24
|
+
|
|
25
|
+
return `<ul class="timeline">\n${items}\n</ul>`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const meta: BlockMeta = {
|
|
29
|
+
type: "timeline",
|
|
30
|
+
description: "Milestone list with dot connectors. Each item has a label, text, and optional date.",
|
|
31
|
+
schema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
type: { const: "timeline" },
|
|
35
|
+
items: {
|
|
36
|
+
type: "array",
|
|
37
|
+
items: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
label: { type: "string" },
|
|
41
|
+
text: { type: "string" },
|
|
42
|
+
date: { type: "string" },
|
|
43
|
+
},
|
|
44
|
+
required: ["label", "text"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
required: ["type", "items"],
|
|
49
|
+
},
|
|
50
|
+
example: {
|
|
51
|
+
type: "timeline",
|
|
52
|
+
items: [
|
|
53
|
+
{ label: "Phase 1", text: "CSS extraction and theme serving", date: "2026-05-10" },
|
|
54
|
+
{ label: "Phase 2", text: "Block plumbing and renderers", date: "2026-05-12" },
|
|
55
|
+
{ label: "Phase 3", text: "Tooling flip — prompt and styleguide update" },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Tldr block renderer.
|
|
2
|
+
// src/render/blocks/renderers/tldr.ts
|
|
3
|
+
|
|
4
|
+
import type { TldrBlock } from "../types.ts";
|
|
5
|
+
import type { BlockMeta } from "../types.ts";
|
|
6
|
+
import type { RenderCtx } from "../render.ts";
|
|
7
|
+
import { renderMarkdown } from "../markdown.ts";
|
|
8
|
+
|
|
9
|
+
export function renderTldr(block: TldrBlock, _ctx: RenderCtx): string {
|
|
10
|
+
return `<aside class="tldr">\n${renderMarkdown(block.markdown)}\n</aside>`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const meta: BlockMeta = {
|
|
14
|
+
type: "tldr",
|
|
15
|
+
description:
|
|
16
|
+
"Clay-bordered summary box. Use at most one per document, near the top. Content is markdown.",
|
|
17
|
+
schema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
type: { const: "tldr" },
|
|
21
|
+
markdown: { type: "string" },
|
|
22
|
+
},
|
|
23
|
+
required: ["type", "markdown"],
|
|
24
|
+
},
|
|
25
|
+
example: {
|
|
26
|
+
type: "tldr",
|
|
27
|
+
markdown:
|
|
28
|
+
"**Summary:** This document covers the block-mode refactor for `cesium_publish`. Three phases: plumbing, tooling flip, and cleanup.",
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Block discriminated union — the closed type system for cesium_publish structured input.
|
|
2
|
+
// src/render/blocks/types.ts
|
|
3
|
+
|
|
4
|
+
export type Block =
|
|
5
|
+
| HeroBlock
|
|
6
|
+
| TldrBlock
|
|
7
|
+
| SectionBlock
|
|
8
|
+
| ProseBlock
|
|
9
|
+
| ListBlock
|
|
10
|
+
| CalloutBlock
|
|
11
|
+
| CodeBlock
|
|
12
|
+
| TimelineBlock
|
|
13
|
+
| CompareTableBlock
|
|
14
|
+
| RiskTableBlock
|
|
15
|
+
| KvBlock
|
|
16
|
+
| PillRowBlock
|
|
17
|
+
| DividerBlock
|
|
18
|
+
| DiagramBlock
|
|
19
|
+
| RawHtmlBlock;
|
|
20
|
+
|
|
21
|
+
export type HeroBlock = {
|
|
22
|
+
type: "hero";
|
|
23
|
+
eyebrow?: string;
|
|
24
|
+
title: string;
|
|
25
|
+
subtitle?: string;
|
|
26
|
+
meta?: Array<{ k: string; v: string }>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TldrBlock = {
|
|
30
|
+
type: "tldr";
|
|
31
|
+
// markdown subset; at most one tldr per document
|
|
32
|
+
markdown: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type SectionBlock = {
|
|
36
|
+
type: "section";
|
|
37
|
+
title: string;
|
|
38
|
+
num?: string; // omitted = auto-numbered sequentially
|
|
39
|
+
eyebrow?: string;
|
|
40
|
+
children: Block[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ProseBlock = {
|
|
44
|
+
type: "prose";
|
|
45
|
+
markdown: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ListBlock = {
|
|
49
|
+
type: "list";
|
|
50
|
+
style?: "bullet" | "number" | "check";
|
|
51
|
+
items: string[]; // each item is markdown (subset)
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type CalloutBlock = {
|
|
55
|
+
type: "callout";
|
|
56
|
+
variant: "note" | "warn" | "risk";
|
|
57
|
+
title?: string;
|
|
58
|
+
markdown: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type CodeBlock = {
|
|
62
|
+
type: "code";
|
|
63
|
+
lang: string; // required; "text" if unknown
|
|
64
|
+
code: string;
|
|
65
|
+
filename?: string;
|
|
66
|
+
caption?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type TimelineBlock = {
|
|
70
|
+
type: "timeline";
|
|
71
|
+
items: Array<{ label: string; text: string; date?: string }>;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type CompareTableBlock = {
|
|
75
|
+
type: "compare_table";
|
|
76
|
+
headers: string[];
|
|
77
|
+
rows: string[][]; // cells are markdown (subset)
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type RiskTableBlock = {
|
|
81
|
+
type: "risk_table";
|
|
82
|
+
rows: Array<{
|
|
83
|
+
risk: string;
|
|
84
|
+
likelihood: "low" | "medium" | "high";
|
|
85
|
+
impact: "low" | "medium" | "high";
|
|
86
|
+
mitigation: string;
|
|
87
|
+
}>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type KvBlock = {
|
|
91
|
+
type: "kv";
|
|
92
|
+
rows: Array<{ k: string; v: string }>;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type PillRowBlock = {
|
|
96
|
+
type: "pill_row";
|
|
97
|
+
items: Array<{ kind: "pill" | "tag"; text: string }>;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type DividerBlock = {
|
|
101
|
+
type: "divider";
|
|
102
|
+
label?: string;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type DiagramBlock = {
|
|
106
|
+
type: "diagram";
|
|
107
|
+
caption?: string;
|
|
108
|
+
// exactly one of svg or html
|
|
109
|
+
svg?: string;
|
|
110
|
+
html?: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export type RawHtmlBlock = {
|
|
114
|
+
type: "raw_html";
|
|
115
|
+
html: string;
|
|
116
|
+
purpose?: string; // brief reason; surfaced in critique findings
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// ─── BlockMeta ───────────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
export type BlockMeta = {
|
|
122
|
+
type: Block["type"];
|
|
123
|
+
description: string;
|
|
124
|
+
schema: object; // JSON Schema fragment for the block
|
|
125
|
+
example: Block; // canonical example matching schema
|
|
126
|
+
renderedExample?: string; // optional pre-rendered HTML for docs
|
|
127
|
+
};
|