@cfbender/cesium 0.5.0 → 0.5.2
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 +86 -1
- package/README.md +8 -8
- package/package.json +19 -18
- package/src/cli/commands/serve.ts +16 -4
- package/src/index.ts +4 -1
- package/src/prompt/field-reference.ts +2 -2
- package/src/prompt/system-fragment.md +46 -16
- package/src/render/blocks/catalog.ts +2 -0
- package/src/render/blocks/diff/myers.ts +221 -0
- package/src/render/blocks/diff/parse-unified.ts +101 -0
- package/src/render/blocks/highlight.ts +185 -0
- package/src/render/blocks/markdown.ts +28 -7
- package/src/render/blocks/render.ts +16 -5
- package/src/render/blocks/renderers/code.ts +5 -5
- package/src/render/blocks/renderers/compare-table.ts +3 -4
- package/src/render/blocks/renderers/diagram.ts +2 -5
- package/src/render/blocks/renderers/diff.ts +378 -0
- package/src/render/blocks/renderers/prose.ts +1 -2
- package/src/render/blocks/renderers/section.ts +4 -2
- package/src/render/blocks/renderers/timeline.ts +2 -1
- package/src/render/blocks/themes/claret-dark.ts +201 -0
- package/src/render/blocks/themes/claret-light.ts +222 -0
- package/src/render/blocks/types.ts +13 -1
- package/src/render/blocks/validate-block.ts +19 -9
- package/src/render/theme.ts +131 -0
- package/src/render/validate.ts +53 -9
- package/src/server/lifecycle.ts +188 -3
- package/src/storage/index-gen.ts +2 -3
- package/src/tools/ask.ts +2 -2
- package/src/tools/publish.ts +6 -6
- package/src/tools/styleguide.ts +25 -20
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
// Diff block renderer — split-view before/after code diff with bezier SVG connectors.
|
|
2
|
+
// src/render/blocks/renderers/diff.ts
|
|
3
|
+
|
|
4
|
+
import type { DiffBlock, BlockMeta } from "../types.ts";
|
|
5
|
+
import type { RenderCtx } from "../render.ts";
|
|
6
|
+
import { escapeHtml, escapeAttr } from "../escape.ts";
|
|
7
|
+
import { highlightCode } from "../highlight.ts";
|
|
8
|
+
import { parseUnifiedDiff } from "../diff/parse-unified.ts";
|
|
9
|
+
import type { DiffEntry, DiffLine } from "../diff/parse-unified.ts";
|
|
10
|
+
import { diffLines } from "../diff/myers.ts";
|
|
11
|
+
|
|
12
|
+
const LINE_H = 22; // px — must match .diff-line height in CSS
|
|
13
|
+
const SVG_W = 60; // px — connector column width
|
|
14
|
+
|
|
15
|
+
// ─── Change region ────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
type RegionKind = "add" | "remove" | "change";
|
|
18
|
+
|
|
19
|
+
interface ChangeRegion {
|
|
20
|
+
kind: RegionKind;
|
|
21
|
+
leftStart: number; // 0-based index into leftLines
|
|
22
|
+
leftEnd: number;
|
|
23
|
+
rightStart: number; // 0-based index into rightLines
|
|
24
|
+
rightEnd: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── SVG path generation ──────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function svgPath(region: ChangeRegion): string {
|
|
30
|
+
const W = SVG_W;
|
|
31
|
+
const H = LINE_H;
|
|
32
|
+
|
|
33
|
+
const { kind, leftStart, leftEnd, rightStart, rightEnd } = region;
|
|
34
|
+
|
|
35
|
+
if (kind === "change") {
|
|
36
|
+
const y0l = leftStart * H;
|
|
37
|
+
const y1l = leftEnd * H;
|
|
38
|
+
const y0r = rightStart * H;
|
|
39
|
+
const y1r = rightEnd * H;
|
|
40
|
+
return (
|
|
41
|
+
`M 0 ${y0l} ` +
|
|
42
|
+
`C ${W / 2} ${y0l}, ${W / 2} ${y0r}, ${W} ${y0r} ` +
|
|
43
|
+
`L ${W} ${y1r} ` +
|
|
44
|
+
`C ${W / 2} ${y1r}, ${W / 2} ${y1l}, 0 ${y1l} ` +
|
|
45
|
+
`Z`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (kind === "add") {
|
|
50
|
+
// anchorY is on the left side (leftStart === leftEnd for pure add)
|
|
51
|
+
const anchorY = leftStart * H;
|
|
52
|
+
const y0r = rightStart * H;
|
|
53
|
+
const y1r = rightEnd * H;
|
|
54
|
+
return (
|
|
55
|
+
`M 0 ${anchorY} ` +
|
|
56
|
+
`C ${W / 2} ${anchorY}, ${W / 2} ${y0r}, ${W} ${y0r} ` +
|
|
57
|
+
`L ${W} ${y1r} ` +
|
|
58
|
+
`C ${W / 2} ${y1r}, ${W / 2} ${anchorY}, 0 ${anchorY} ` +
|
|
59
|
+
`Z`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// kind === "remove"
|
|
64
|
+
// anchorY is on the right side (rightStart === rightEnd for pure remove)
|
|
65
|
+
const anchorY = rightStart * H;
|
|
66
|
+
const y0l = leftStart * H;
|
|
67
|
+
const y1l = leftEnd * H;
|
|
68
|
+
return (
|
|
69
|
+
`M 0 ${y0l} ` +
|
|
70
|
+
`C ${W / 2} ${y0l}, ${W / 2} ${anchorY}, ${W} ${anchorY} ` +
|
|
71
|
+
`L ${W} ${anchorY} ` +
|
|
72
|
+
`C ${W / 2} ${anchorY}, ${W / 2} ${y1l}, 0 ${y1l} ` +
|
|
73
|
+
`Z`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Region detection ─────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
function detectRegions(entries: DiffEntry[]): ChangeRegion[] {
|
|
80
|
+
const regions: ChangeRegion[] = [];
|
|
81
|
+
|
|
82
|
+
// Walk entries tracking left/right indices
|
|
83
|
+
let leftIdx = 0;
|
|
84
|
+
let rightIdx = 0;
|
|
85
|
+
|
|
86
|
+
// Region accumulation
|
|
87
|
+
let inRegion = false;
|
|
88
|
+
let regionLeftStart = 0;
|
|
89
|
+
let regionRightStart = 0;
|
|
90
|
+
let regionLeftEnd = 0;
|
|
91
|
+
let regionRightEnd = 0;
|
|
92
|
+
let hasRemoves = false;
|
|
93
|
+
let hasAdds = false;
|
|
94
|
+
|
|
95
|
+
function flushRegion() {
|
|
96
|
+
if (!inRegion) return;
|
|
97
|
+
const kind: RegionKind = hasAdds && hasRemoves ? "change" : hasAdds ? "add" : "remove";
|
|
98
|
+
regions.push({
|
|
99
|
+
kind,
|
|
100
|
+
leftStart: regionLeftStart,
|
|
101
|
+
leftEnd: regionLeftEnd,
|
|
102
|
+
rightStart: regionRightStart,
|
|
103
|
+
rightEnd: regionRightEnd,
|
|
104
|
+
});
|
|
105
|
+
inRegion = false;
|
|
106
|
+
hasAdds = false;
|
|
107
|
+
hasRemoves = false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (entry.kind === "hunk-sep") {
|
|
112
|
+
flushRegion();
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { kind } = entry;
|
|
117
|
+
|
|
118
|
+
if (kind === "context") {
|
|
119
|
+
flushRegion();
|
|
120
|
+
leftIdx++;
|
|
121
|
+
rightIdx++;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (kind === "remove") {
|
|
126
|
+
if (!inRegion) {
|
|
127
|
+
inRegion = true;
|
|
128
|
+
regionLeftStart = leftIdx;
|
|
129
|
+
regionRightStart = rightIdx;
|
|
130
|
+
hasRemoves = false;
|
|
131
|
+
hasAdds = false;
|
|
132
|
+
}
|
|
133
|
+
hasRemoves = true;
|
|
134
|
+
leftIdx++;
|
|
135
|
+
regionLeftEnd = leftIdx;
|
|
136
|
+
// For pure remove regions, rightEnd tracks the anchor
|
|
137
|
+
if (!hasAdds) {
|
|
138
|
+
regionRightEnd = rightIdx;
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (kind === "add") {
|
|
144
|
+
if (!inRegion) {
|
|
145
|
+
inRegion = true;
|
|
146
|
+
regionLeftStart = leftIdx;
|
|
147
|
+
regionLeftEnd = leftIdx; // anchor for pure-add
|
|
148
|
+
regionRightStart = rightIdx;
|
|
149
|
+
hasRemoves = false;
|
|
150
|
+
hasAdds = false;
|
|
151
|
+
}
|
|
152
|
+
hasAdds = true;
|
|
153
|
+
rightIdx++;
|
|
154
|
+
regionRightEnd = rightIdx;
|
|
155
|
+
// For pure add, update leftEnd to track anchor
|
|
156
|
+
if (!hasRemoves) {
|
|
157
|
+
regionLeftEnd = leftIdx;
|
|
158
|
+
}
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
flushRegion();
|
|
164
|
+
return regions;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ─── Highlight helpers ────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Highlight a multi-line text and return an array of highlighted line HTML strings.
|
|
171
|
+
* Each element corresponds to one source line.
|
|
172
|
+
*/
|
|
173
|
+
async function highlightLines(text: string, lang: string, ctx: RenderCtx): Promise<string[]> {
|
|
174
|
+
if (text === "") return [];
|
|
175
|
+
const highlighted = await highlightCode(text, lang, ctx.highlightTheme);
|
|
176
|
+
return highlighted.split("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── Main renderer ────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
export async function renderDiff(block: DiffBlock, ctx: RenderCtx): Promise<string> {
|
|
182
|
+
const lang = block.lang ?? "text";
|
|
183
|
+
|
|
184
|
+
// ── 1. Resolve DiffEntry[] ─────────────────────────────────────────────────
|
|
185
|
+
let entries: DiffEntry[];
|
|
186
|
+
|
|
187
|
+
if (block.patch !== undefined) {
|
|
188
|
+
const parsed = parseUnifiedDiff(block.patch);
|
|
189
|
+
if (parsed === null) {
|
|
190
|
+
// Fallback: plaintext panel
|
|
191
|
+
const escaped = escapeHtml(block.patch);
|
|
192
|
+
const filename =
|
|
193
|
+
block.filename !== undefined
|
|
194
|
+
? `<span class="diff-filename">${escapeHtml(block.filename)}</span>`
|
|
195
|
+
: "";
|
|
196
|
+
const header = filename !== "" ? `<header class="diff-header">${filename}</header>\n` : "";
|
|
197
|
+
return (
|
|
198
|
+
`<figure class="diff-block fallback" data-lang="${escapeAttr(lang)}">\n` +
|
|
199
|
+
header +
|
|
200
|
+
` <pre><code>${escaped}</code></pre>\n` +
|
|
201
|
+
`</figure>`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
entries = parsed;
|
|
205
|
+
} else {
|
|
206
|
+
const before = block.before ?? "";
|
|
207
|
+
const after = block.after ?? "";
|
|
208
|
+
entries = diffLines(before, after);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── 2. Build left/right line lists & recompose text for shiki ─────────────
|
|
212
|
+
const leftEntries: DiffLine[] = [];
|
|
213
|
+
const rightEntries: DiffLine[] = [];
|
|
214
|
+
|
|
215
|
+
for (const e of entries) {
|
|
216
|
+
if (e.kind === "hunk-sep") continue;
|
|
217
|
+
if (e.kind === "context" || e.kind === "remove") {
|
|
218
|
+
leftEntries.push(e);
|
|
219
|
+
}
|
|
220
|
+
if (e.kind === "context" || e.kind === "add") {
|
|
221
|
+
rightEntries.push(e);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const beforeText = leftEntries.map((e) => e.text).join("\n");
|
|
226
|
+
const afterText = rightEntries.map((e) => e.text).join("\n");
|
|
227
|
+
|
|
228
|
+
const [leftHighlighted, rightHighlighted] = await Promise.all([
|
|
229
|
+
highlightLines(beforeText, lang, ctx),
|
|
230
|
+
highlightLines(afterText, lang, ctx),
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
// ── 3. Compute stats ───────────────────────────────────────────────────────
|
|
234
|
+
let addCount = 0;
|
|
235
|
+
let removeCount = 0;
|
|
236
|
+
for (const e of entries) {
|
|
237
|
+
if (e.kind === "add") addCount++;
|
|
238
|
+
if (e.kind === "remove") removeCount++;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── 4. Detect change regions (for SVG connectors) ─────────────────────────
|
|
242
|
+
const regions = detectRegions(entries);
|
|
243
|
+
|
|
244
|
+
// ── 5. Compute SVG height ──────────────────────────────────────────────────
|
|
245
|
+
// Each side renders context+remove (left) or context+add (right) lines,
|
|
246
|
+
// plus one row per hunk-sep
|
|
247
|
+
let leftLineCount = 0;
|
|
248
|
+
let rightLineCount = 0;
|
|
249
|
+
for (const e of entries) {
|
|
250
|
+
if (e.kind === "hunk-sep") {
|
|
251
|
+
leftLineCount++;
|
|
252
|
+
rightLineCount++;
|
|
253
|
+
} else if (e.kind === "context") {
|
|
254
|
+
leftLineCount++;
|
|
255
|
+
rightLineCount++;
|
|
256
|
+
} else if (e.kind === "remove") {
|
|
257
|
+
leftLineCount++;
|
|
258
|
+
} else if (e.kind === "add") {
|
|
259
|
+
rightLineCount++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const svgH = Math.max(leftLineCount, rightLineCount) * LINE_H;
|
|
263
|
+
|
|
264
|
+
// ── 6. Render left side ────────────────────────────────────────────────────
|
|
265
|
+
let leftHighIdx = 0;
|
|
266
|
+
let rightHighIdx = 0;
|
|
267
|
+
const leftRows: string[] = [];
|
|
268
|
+
const rightRows: string[] = [];
|
|
269
|
+
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (entry.kind === "hunk-sep") {
|
|
272
|
+
const sepLabel = `… @ ${entry.newStart}`;
|
|
273
|
+
const sepHtml = `<li class="diff-line hunk-sep"><span class="num"></span><span class="content">${escapeHtml(sepLabel)}</span></li>`;
|
|
274
|
+
leftRows.push(sepHtml);
|
|
275
|
+
rightRows.push(sepHtml);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const { kind } = entry;
|
|
280
|
+
|
|
281
|
+
if (kind === "context" || kind === "remove") {
|
|
282
|
+
const hl =
|
|
283
|
+
leftHighlighted[leftHighIdx] ?? `<span class="line">${escapeHtml(entry.text)}</span>`;
|
|
284
|
+
leftHighIdx++;
|
|
285
|
+
const lineNum = entry.beforeLineNum !== null ? String(entry.beforeLineNum) : "";
|
|
286
|
+
leftRows.push(
|
|
287
|
+
`<li class="diff-line ${kind}"><span class="num">${escapeHtml(lineNum)}</span><span class="content">${hl}</span></li>`,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (kind === "context" || kind === "add") {
|
|
292
|
+
const hl =
|
|
293
|
+
rightHighlighted[rightHighIdx] ?? `<span class="line">${escapeHtml(entry.text)}</span>`;
|
|
294
|
+
rightHighIdx++;
|
|
295
|
+
const lineNum = entry.afterLineNum !== null ? String(entry.afterLineNum) : "";
|
|
296
|
+
rightRows.push(
|
|
297
|
+
`<li class="diff-line ${kind}"><span class="num">${escapeHtml(lineNum)}</span><span class="content">${hl}</span></li>`,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── 7. Render SVG connector paths ─────────────────────────────────────────
|
|
303
|
+
const svgPaths = regions.map((region) => {
|
|
304
|
+
const d = svgPath(region);
|
|
305
|
+
return ` <path class="diff-conn ${region.kind}" d="${d}"/>`;
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const svgEl =
|
|
309
|
+
` <div class="diff-connector" style="--lineH: ${LINE_H}px;">\n` +
|
|
310
|
+
` <svg viewBox="0 0 ${SVG_W} ${svgH}" preserveAspectRatio="none" aria-hidden="true" style="height: ${svgH}px">\n` +
|
|
311
|
+
svgPaths.join("\n") +
|
|
312
|
+
(svgPaths.length > 0 ? "\n" : "") +
|
|
313
|
+
` </svg>\n` +
|
|
314
|
+
` </div>`;
|
|
315
|
+
|
|
316
|
+
// ── 8. Header ──────────────────────────────────────────────────────────────
|
|
317
|
+
const filenameHtml =
|
|
318
|
+
block.filename !== undefined
|
|
319
|
+
? `<span class="diff-filename">${escapeHtml(block.filename)}</span>`
|
|
320
|
+
: `<span class="diff-filename"></span>`;
|
|
321
|
+
const statHtml = `<span class="diff-stat"><span class="add">+${addCount}</span> <span class="rem">-${removeCount}</span></span>`;
|
|
322
|
+
const headerHtml = `<header class="diff-header">${filenameHtml}${statHtml}</header>`;
|
|
323
|
+
|
|
324
|
+
// ── 9. Caption ─────────────────────────────────────────────────────────────
|
|
325
|
+
const captionHtml =
|
|
326
|
+
block.caption !== undefined ? `\n <figcaption>${escapeHtml(block.caption)}</figcaption>` : "";
|
|
327
|
+
|
|
328
|
+
// ── 10. Assemble ───────────────────────────────────────────────────────────
|
|
329
|
+
const leftOl =
|
|
330
|
+
` <ol class="diff-side before">\n` +
|
|
331
|
+
leftRows.map((r) => ` ${r}`).join("\n") +
|
|
332
|
+
(leftRows.length > 0 ? "\n" : "") +
|
|
333
|
+
` </ol>`;
|
|
334
|
+
|
|
335
|
+
const rightOl =
|
|
336
|
+
` <ol class="diff-side after">\n` +
|
|
337
|
+
rightRows.map((r) => ` ${r}`).join("\n") +
|
|
338
|
+
(rightRows.length > 0 ? "\n" : "") +
|
|
339
|
+
` </ol>`;
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
`<figure class="diff-block" data-lang="${escapeAttr(lang)}">\n` +
|
|
343
|
+
` ${headerHtml}\n` +
|
|
344
|
+
` <div class="diff-grid">\n` +
|
|
345
|
+
`${leftOl}\n` +
|
|
346
|
+
`${svgEl}\n` +
|
|
347
|
+
`${rightOl}\n` +
|
|
348
|
+
` </div>${captionHtml}\n` +
|
|
349
|
+
`</figure>`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export const meta: BlockMeta = {
|
|
354
|
+
type: "diff",
|
|
355
|
+
description:
|
|
356
|
+
"Side-by-side before/after code diff with curved bezier connectors. Use for showing what changed or proposing a change. Provide either a unified diff `patch`, or both `before` and `after` strings.",
|
|
357
|
+
schema: {
|
|
358
|
+
type: "object",
|
|
359
|
+
properties: {
|
|
360
|
+
type: { const: "diff" },
|
|
361
|
+
patch: { type: "string" },
|
|
362
|
+
before: { type: "string" },
|
|
363
|
+
after: { type: "string" },
|
|
364
|
+
lang: { type: "string" },
|
|
365
|
+
filename: { type: "string" },
|
|
366
|
+
caption: { type: "string" },
|
|
367
|
+
},
|
|
368
|
+
required: ["type"],
|
|
369
|
+
},
|
|
370
|
+
example: {
|
|
371
|
+
type: "diff",
|
|
372
|
+
filename: "src/auth.ts",
|
|
373
|
+
lang: "typescript",
|
|
374
|
+
before: "function login(user, pass) {\n return db.find(user, pass);\n}",
|
|
375
|
+
after:
|
|
376
|
+
"async function login(user, pass) {\n const u = await db.find(user);\n if (!u) return null;\n return verify(u, pass) ? u : null;\n}",
|
|
377
|
+
},
|
|
378
|
+
};
|
|
@@ -23,7 +23,6 @@ export const meta: BlockMeta = {
|
|
|
23
23
|
},
|
|
24
24
|
example: {
|
|
25
25
|
type: "prose",
|
|
26
|
-
markdown:
|
|
27
|
-
"This is a paragraph with **bold** and *italic* text.\n\n- Item one\n- Item two",
|
|
26
|
+
markdown: "This is a paragraph with **bold** and *italic* text.\n\n- Item one\n- Item two",
|
|
28
27
|
},
|
|
29
28
|
};
|
|
@@ -7,7 +7,7 @@ import type { RenderCtx } from "../render.ts";
|
|
|
7
7
|
import { renderBlock } from "../render.ts";
|
|
8
8
|
import { escapeHtml } from "../escape.ts";
|
|
9
9
|
|
|
10
|
-
export function renderSection(block: SectionBlock, ctx: RenderCtx): string {
|
|
10
|
+
export async function renderSection(block: SectionBlock, ctx: RenderCtx): Promise<string> {
|
|
11
11
|
// Determine section number: explicit or auto-increment
|
|
12
12
|
let num: string;
|
|
13
13
|
if (block.num !== undefined && block.num !== "") {
|
|
@@ -34,6 +34,7 @@ export function renderSection(block: SectionBlock, ctx: RenderCtx): string {
|
|
|
34
34
|
sectionCounter: ctx.sectionCounter,
|
|
35
35
|
depth: ctx.depth + 1,
|
|
36
36
|
path: `${ctx.path}.children`,
|
|
37
|
+
highlightTheme: ctx.highlightTheme,
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
let buffer: string[] = [];
|
|
@@ -45,7 +46,8 @@ export function renderSection(block: SectionBlock, ctx: RenderCtx): string {
|
|
|
45
46
|
...childCtx,
|
|
46
47
|
path: `${ctx.path}.children[${i}]`,
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
+
// eslint-disable-next-line no-await-in-loop -- sequential render required; card buffer tracks contiguous non-section children
|
|
50
|
+
const rendered = await renderBlock(child, childBlockCtx);
|
|
49
51
|
if (child.type === "section") {
|
|
50
52
|
// Flush buffered non-section children into a card first
|
|
51
53
|
if (buffer.length > 0) {
|
|
@@ -27,7 +27,8 @@ export function renderTimeline(block: TimelineBlock, _ctx: RenderCtx): string {
|
|
|
27
27
|
|
|
28
28
|
export const meta: BlockMeta = {
|
|
29
29
|
type: "timeline",
|
|
30
|
-
description:
|
|
30
|
+
description:
|
|
31
|
+
"Milestone list with dot connectors. Each item has a label, text, and optional date.",
|
|
31
32
|
schema: {
|
|
32
33
|
type: "object",
|
|
33
34
|
properties: {
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Claret-dark shiki theme — converted from ports/bat/ClaretDark.tmTheme.
|
|
2
|
+
// src/render/blocks/themes/claret-dark.ts
|
|
3
|
+
//
|
|
4
|
+
// Source of truth: /claret.nvim/ports/bat/ClaretDark.tmTheme
|
|
5
|
+
// Every scope rule in the tmTheme is preserved verbatim; comma-separated
|
|
6
|
+
// scope strings are split into the string[] form shiki prefers.
|
|
7
|
+
//
|
|
8
|
+
// Global tokens (from tmTheme global settings):
|
|
9
|
+
// bg #180810 — editor background
|
|
10
|
+
// fg #DDD3C7 — default foreground
|
|
11
|
+
// selection #2B1F22
|
|
12
|
+
// gutter fg #71685E
|
|
13
|
+
//
|
|
14
|
+
// Derived palette reference (claret.nvim/lua/claret/palette.lua — dark):
|
|
15
|
+
// rose_1 #C75B7A keyword / statement / accent
|
|
16
|
+
// rose_2 #B04A68 property / data keys
|
|
17
|
+
// gold_1 #D4A76A function / number / constant / decorator
|
|
18
|
+
// sage_1 #8FA86E string
|
|
19
|
+
// slate_1 #8995A8 type / class / tag / escape / link
|
|
20
|
+
// slate_2 #6E7A90 tag attribute
|
|
21
|
+
// text #DDD3C7 variable / default fg
|
|
22
|
+
// text_2 #BDB3A7 operator (syntax.lua maps Operator → text_2; tmTheme uses #9E9288)
|
|
23
|
+
// NOTE: the tmTheme uses #9E9288 (text_3) for operator/punctuation — preserved.
|
|
24
|
+
// text_4 #71685E comment (fg)
|
|
25
|
+
// terra_1 #C44536 invalid / diff deleted
|
|
26
|
+
|
|
27
|
+
import type { ThemeRegistration } from "shiki";
|
|
28
|
+
|
|
29
|
+
export const claretDark: ThemeRegistration = {
|
|
30
|
+
name: "claret-dark",
|
|
31
|
+
type: "dark",
|
|
32
|
+
fg: "#DDD3C7",
|
|
33
|
+
bg: "#180810",
|
|
34
|
+
colors: {
|
|
35
|
+
"editor.foreground": "#DDD3C7",
|
|
36
|
+
"editor.background": "#180810",
|
|
37
|
+
"editor.selectionBackground": "#2B1F22",
|
|
38
|
+
"editor.lineHighlightBackground": "#2B1F22",
|
|
39
|
+
"editorLineNumber.foreground": "#71685E",
|
|
40
|
+
},
|
|
41
|
+
tokenColors: [
|
|
42
|
+
// Comment — #71685E italic
|
|
43
|
+
{
|
|
44
|
+
name: "Comment",
|
|
45
|
+
scope: ["comment", "punctuation.definition.comment"],
|
|
46
|
+
settings: { foreground: "#71685E", fontStyle: "italic" },
|
|
47
|
+
},
|
|
48
|
+
// Keyword — #C75B7A
|
|
49
|
+
{
|
|
50
|
+
name: "Keyword",
|
|
51
|
+
scope: ["keyword", "storage.type", "storage.modifier"],
|
|
52
|
+
settings: { foreground: "#C75B7A" },
|
|
53
|
+
},
|
|
54
|
+
// Function — #D4A76A
|
|
55
|
+
{
|
|
56
|
+
name: "Function",
|
|
57
|
+
scope: ["entity.name.function", "support.function"],
|
|
58
|
+
settings: { foreground: "#D4A76A" },
|
|
59
|
+
},
|
|
60
|
+
// String — #8FA86E
|
|
61
|
+
{
|
|
62
|
+
name: "String",
|
|
63
|
+
scope: ["string", "punctuation.definition.string"],
|
|
64
|
+
settings: { foreground: "#8FA86E" },
|
|
65
|
+
},
|
|
66
|
+
// Number — #D4A76A
|
|
67
|
+
{
|
|
68
|
+
name: "Number",
|
|
69
|
+
scope: ["constant.numeric"],
|
|
70
|
+
settings: { foreground: "#D4A76A" },
|
|
71
|
+
},
|
|
72
|
+
// Constant — #D4A76A
|
|
73
|
+
{
|
|
74
|
+
name: "Constant",
|
|
75
|
+
scope: ["constant", "constant.language", "variable.language"],
|
|
76
|
+
settings: { foreground: "#D4A76A" },
|
|
77
|
+
},
|
|
78
|
+
// Type — #8995A8
|
|
79
|
+
{
|
|
80
|
+
name: "Type",
|
|
81
|
+
scope: ["entity.name.type", "entity.name.class", "support.type", "support.class"],
|
|
82
|
+
settings: { foreground: "#8995A8" },
|
|
83
|
+
},
|
|
84
|
+
// Variable — #DDD3C7
|
|
85
|
+
{
|
|
86
|
+
name: "Variable",
|
|
87
|
+
scope: ["variable", "variable.parameter"],
|
|
88
|
+
settings: { foreground: "#DDD3C7" },
|
|
89
|
+
},
|
|
90
|
+
// Parameter — #DDD3C7 italic (overrides Variable for parameters)
|
|
91
|
+
{
|
|
92
|
+
name: "Parameter",
|
|
93
|
+
scope: ["variable.parameter"],
|
|
94
|
+
settings: { foreground: "#DDD3C7", fontStyle: "italic" },
|
|
95
|
+
},
|
|
96
|
+
// Property — #B04A68
|
|
97
|
+
{
|
|
98
|
+
name: "Property",
|
|
99
|
+
scope: ["variable.other.property", "variable.other.member"],
|
|
100
|
+
settings: { foreground: "#B04A68" },
|
|
101
|
+
},
|
|
102
|
+
// JSON/YAML/TOML Keys — #B04A68
|
|
103
|
+
{
|
|
104
|
+
name: "JSON/YAML/TOML Keys",
|
|
105
|
+
scope: [
|
|
106
|
+
"meta.mapping.key string",
|
|
107
|
+
"support.type.property-name.json",
|
|
108
|
+
"punctuation.support.type.property-name.json",
|
|
109
|
+
"support.type.property-name.toml",
|
|
110
|
+
"punctuation.support.type.property-name.toml",
|
|
111
|
+
"entity.name.tag.yaml",
|
|
112
|
+
"support.type.property-name.yaml",
|
|
113
|
+
],
|
|
114
|
+
settings: { foreground: "#B04A68" },
|
|
115
|
+
},
|
|
116
|
+
// Operator — #9E9288
|
|
117
|
+
{
|
|
118
|
+
name: "Operator",
|
|
119
|
+
scope: ["keyword.operator"],
|
|
120
|
+
settings: { foreground: "#9E9288" },
|
|
121
|
+
},
|
|
122
|
+
// Punctuation — #9E9288
|
|
123
|
+
{
|
|
124
|
+
name: "Punctuation",
|
|
125
|
+
scope: ["punctuation"],
|
|
126
|
+
settings: { foreground: "#9E9288" },
|
|
127
|
+
},
|
|
128
|
+
// Decorator — #D4A76A italic
|
|
129
|
+
{
|
|
130
|
+
name: "Decorator",
|
|
131
|
+
scope: ["meta.decorator", "punctuation.decorator"],
|
|
132
|
+
settings: { foreground: "#D4A76A", fontStyle: "italic" },
|
|
133
|
+
},
|
|
134
|
+
// Tag — #8995A8
|
|
135
|
+
{
|
|
136
|
+
name: "Tag",
|
|
137
|
+
scope: ["entity.name.tag"],
|
|
138
|
+
settings: { foreground: "#8995A8" },
|
|
139
|
+
},
|
|
140
|
+
// Tag Attribute — #6E7A90
|
|
141
|
+
{
|
|
142
|
+
name: "Tag Attribute",
|
|
143
|
+
scope: ["entity.other.attribute-name"],
|
|
144
|
+
settings: { foreground: "#6E7A90" },
|
|
145
|
+
},
|
|
146
|
+
// Invalid — #C44536
|
|
147
|
+
{
|
|
148
|
+
name: "Invalid",
|
|
149
|
+
scope: ["invalid", "invalid.illegal"],
|
|
150
|
+
settings: { foreground: "#C44536" },
|
|
151
|
+
},
|
|
152
|
+
// Escape — #8995A8
|
|
153
|
+
{
|
|
154
|
+
name: "Escape",
|
|
155
|
+
scope: ["constant.character.escape"],
|
|
156
|
+
settings: { foreground: "#8995A8" },
|
|
157
|
+
},
|
|
158
|
+
// Markup Heading — #C75B7A bold
|
|
159
|
+
{
|
|
160
|
+
name: "Markup Heading",
|
|
161
|
+
scope: ["markup.heading"],
|
|
162
|
+
settings: { foreground: "#C75B7A", fontStyle: "bold" },
|
|
163
|
+
},
|
|
164
|
+
// Markup Bold — bold (no color override)
|
|
165
|
+
{
|
|
166
|
+
name: "Markup Bold",
|
|
167
|
+
scope: ["markup.bold"],
|
|
168
|
+
settings: { fontStyle: "bold" },
|
|
169
|
+
},
|
|
170
|
+
// Markup Italic — italic (no color override)
|
|
171
|
+
{
|
|
172
|
+
name: "Markup Italic",
|
|
173
|
+
scope: ["markup.italic"],
|
|
174
|
+
settings: { fontStyle: "italic" },
|
|
175
|
+
},
|
|
176
|
+
// Markup Link — #8995A8
|
|
177
|
+
{
|
|
178
|
+
name: "Markup Link",
|
|
179
|
+
scope: ["markup.underline.link", "string.other.link"],
|
|
180
|
+
settings: { foreground: "#8995A8" },
|
|
181
|
+
},
|
|
182
|
+
// Diff Added — #8FA86E
|
|
183
|
+
{
|
|
184
|
+
name: "Diff Added",
|
|
185
|
+
scope: ["markup.inserted"],
|
|
186
|
+
settings: { foreground: "#8FA86E" },
|
|
187
|
+
},
|
|
188
|
+
// Diff Deleted — #C44536
|
|
189
|
+
{
|
|
190
|
+
name: "Diff Deleted",
|
|
191
|
+
scope: ["markup.deleted"],
|
|
192
|
+
settings: { foreground: "#C44536" },
|
|
193
|
+
},
|
|
194
|
+
// Diff Changed — #D4A76A
|
|
195
|
+
{
|
|
196
|
+
name: "Diff Changed",
|
|
197
|
+
scope: ["markup.changed"],
|
|
198
|
+
settings: { foreground: "#D4A76A" },
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|