kitfly 0.2.0 → 0.2.1
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 +12 -0
- package/VERSION +1 -1
- package/dist/_raw/content/reference/slides-authoring-guidelines.md +129 -0
- package/dist/_raw/content/reference.md +1 -0
- package/dist/content/deployment/preflight.html +4 -4
- package/dist/content/deployment/recipes/aws-s3.html +4 -4
- package/dist/content/deployment/recipes/cloudflare-pages.html +4 -4
- package/dist/content/deployment/recipes/cloudflare-r2.html +4 -4
- package/dist/content/deployment/recipes/fly-io.html +4 -4
- package/dist/content/deployment/recipes/github-pages.html +4 -4
- package/dist/content/deployment/recipes/netlify.html +4 -4
- package/dist/content/deployment/recipes/vercel.html +4 -4
- package/dist/content/deployment/secrets-and-env-vars.html +4 -4
- package/dist/content/deployment.html +4 -4
- package/dist/content/guide/approaches.html +4 -4
- package/dist/content/guide/features.html +4 -4
- package/dist/content/guide/getting-started.html +4 -4
- package/dist/content/guide/kitfly-overview.html +4 -4
- package/dist/content/reference/configuration.html +4 -4
- package/dist/content/reference/design-catalog.html +4 -4
- package/dist/content/reference/environment-variables.html +4 -4
- package/dist/content/reference/glossary.html +4 -4
- package/dist/content/reference/key-concepts.html +4 -4
- package/dist/content/reference/plugins.html +4 -4
- package/dist/content/reference/slides-authoring-guidelines.html +418 -0
- package/dist/content/reference/structure.html +4 -4
- package/dist/content/reference.html +5 -4
- package/dist/content/templates/crucible.html +4 -4
- package/dist/content/templates/handbook.html +4 -4
- package/dist/content/templates/minimal.html +4 -4
- package/dist/content/templates/overview.html +4 -4
- package/dist/content/templates/pipeline.html +4 -4
- package/dist/content/templates/productbook.html +4 -4
- package/dist/content/templates/runbook.html +4 -4
- package/dist/content/templates/servicebook.html +4 -4
- package/dist/content-index.json +11 -2
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +4 -4
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +4 -4
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +4 -4
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +4 -4
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +4 -4
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +4 -4
- package/dist/docs/decisions/DDR-0002-theme-system.html +4 -4
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +4 -4
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +4 -4
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +4 -4
- package/dist/docs/userguide/cli/build.html +4 -4
- package/dist/docs/userguide/cli/bundle.html +4 -4
- package/dist/docs/userguide/cli/dev.html +4 -4
- package/dist/docs/userguide/cli/init.html +4 -4
- package/dist/docs/userguide/cli/servers.html +4 -4
- package/dist/docs/userguide/cli/stop.html +4 -4
- package/dist/docs/userguide/cli/update.html +4 -4
- package/dist/docs/userguide/cli/version.html +4 -4
- package/dist/docs/userguide/cli.html +4 -4
- package/dist/docs/userguide/sharing.html +4 -4
- package/dist/index.html +4 -4
- package/dist/llms.txt +3 -3
- package/dist/provenance.json +4 -4
- package/dist/schemas/plugin-registry.schema.html +4 -4
- package/dist/schemas/plugin-schemas-notes.html +4 -4
- package/dist/schemas/plugin.schema.html +4 -4
- package/dist/schemas/plugins.schema.html +4 -4
- package/dist/schemas/v0/common.schema.html +4 -4
- package/dist/schemas/v0/plugin-registry.schema.html +4 -4
- package/dist/schemas/v0/plugin.schema.html +4 -4
- package/dist/schemas/v0/plugins.schema.html +4 -4
- package/dist/schemas/v0/site.schema.html +4 -4
- package/dist/schemas/v0/theme.schema.html +4 -4
- package/dist/schemas.html +4 -4
- package/package.json +1 -1
- package/plugins-dist/slides-visuals.css +166 -0
- package/plugins-dist/slides-visuals.js +124 -33
- package/registry/plugins.yaml +5 -5
- package/scripts/build.ts +4 -1
- package/scripts/bundle.ts +4 -1
- package/scripts/dev.ts +100 -12
- package/src/__tests__/build.test.ts +65 -3
- package/src/__tests__/dev-plugin-errors.test.ts +20 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-branching-no-source.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching-no-split.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase-down.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
- package/src/__tests__/shared.test.ts +23 -0
- package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +33 -0
- package/src/shared.ts +36 -0
package/scripts/dev.ts
CHANGED
|
@@ -18,7 +18,13 @@ import { readdir, readFile, stat } from "node:fs/promises";
|
|
|
18
18
|
import { basename, extname, join, resolve } from "node:path";
|
|
19
19
|
import { marked, Renderer } from "marked";
|
|
20
20
|
import { ENGINE_ASSETS_DIR, ENGINE_SITE_DIR } from "../src/engine.ts";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
loadPluginInjections,
|
|
23
|
+
PluginConfigError,
|
|
24
|
+
PluginIntegrityError,
|
|
25
|
+
PluginNetworkError,
|
|
26
|
+
PluginPolicyError,
|
|
27
|
+
} from "../src/plugin-loader.ts";
|
|
22
28
|
import {
|
|
23
29
|
buildBreadcrumbsSimple,
|
|
24
30
|
buildFooter,
|
|
@@ -37,6 +43,7 @@ import {
|
|
|
37
43
|
envString,
|
|
38
44
|
// Formatting
|
|
39
45
|
escapeHtml,
|
|
46
|
+
filterUnknownSlidesVisualsTypeDiagnostics,
|
|
40
47
|
// Provenance
|
|
41
48
|
generateProvenance,
|
|
42
49
|
// YAML/Config parsing
|
|
@@ -75,6 +82,60 @@ let daemonLog: {
|
|
|
75
82
|
error: (msg: string) => void;
|
|
76
83
|
} | null = null;
|
|
77
84
|
|
|
85
|
+
function isPluginLoaderError(error: unknown): error is Error {
|
|
86
|
+
return (
|
|
87
|
+
error instanceof PluginConfigError ||
|
|
88
|
+
error instanceof PluginIntegrityError ||
|
|
89
|
+
error instanceof PluginPolicyError ||
|
|
90
|
+
error instanceof PluginNetworkError
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pluginVersionMismatchHint(message: string): string {
|
|
95
|
+
const m = message.match(/^Plugin ([a-z0-9-]+) version mismatch: ([^ ]+) != ([^ ]+)$/i);
|
|
96
|
+
if (!m) return "";
|
|
97
|
+
const pluginId = m[1];
|
|
98
|
+
const expected = m[3];
|
|
99
|
+
return `Update <code>kitfly.plugins.yaml</code> to <code>${pluginId}@${expected}</code>, then refresh.`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function buildDevPluginErrorHtml(message: string): string {
|
|
103
|
+
const hint = pluginVersionMismatchHint(message);
|
|
104
|
+
const safeMessage = escapeHtml(message);
|
|
105
|
+
const hintBlock = hint
|
|
106
|
+
? `<p>${hint}</p>`
|
|
107
|
+
: "<p>Check <code>kitfly.plugins.yaml</code> and <code>registry/plugins.yaml</code>, then refresh.</p>";
|
|
108
|
+
return `<!doctype html>
|
|
109
|
+
<html lang="en">
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="utf-8">
|
|
112
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
113
|
+
<title>Plugin Configuration Error</title>
|
|
114
|
+
<style>
|
|
115
|
+
body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 0; background: #0b1020; color: #e8ecf3; }
|
|
116
|
+
main { max-width: 820px; margin: 8vh auto; padding: 1.25rem; }
|
|
117
|
+
.card { background: #131a2e; border: 1px solid #2a3557; border-radius: 12px; padding: 1rem 1.1rem; }
|
|
118
|
+
h1 { margin: 0 0 0.75rem; font-size: 1.25rem; }
|
|
119
|
+
p, li { line-height: 1.5; }
|
|
120
|
+
code { background: #0e1528; padding: 0.08rem 0.3rem; border-radius: 6px; border: 1px solid #2a3557; }
|
|
121
|
+
pre { margin: 0.8rem 0 0; padding: 0.75rem; background: #0e1528; border: 1px solid #2a3557; border-radius: 8px; overflow: auto; }
|
|
122
|
+
.muted { color: #b5bfd2; font-size: 0.92rem; }
|
|
123
|
+
</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<main>
|
|
127
|
+
<div class="card">
|
|
128
|
+
<h1>Plugin setup error</h1>
|
|
129
|
+
<p>Kitfly could not load one or more plugins for dev preview.</p>
|
|
130
|
+
${hintBlock}
|
|
131
|
+
<pre><code>${safeMessage}</code></pre>
|
|
132
|
+
<p class="muted">After updating config, refresh this page. No dev server restart required.</p>
|
|
133
|
+
</div>
|
|
134
|
+
</main>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
78
139
|
/** Log info — uses structured logger in daemon mode, console.log otherwise */
|
|
79
140
|
function logInfo(msg: string): void {
|
|
80
141
|
if (daemonLog) daemonLog.info(msg);
|
|
@@ -367,7 +428,9 @@ async function renderSlidesPage(
|
|
|
367
428
|
let inner = "";
|
|
368
429
|
if (slide.kind === "markdown") {
|
|
369
430
|
if (validateFences) {
|
|
370
|
-
const diagnostics =
|
|
431
|
+
const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
|
|
432
|
+
validateSlidesVisualsFences(slide.body),
|
|
433
|
+
);
|
|
371
434
|
if (diagnostics.length) {
|
|
372
435
|
const msg = diagnostics
|
|
373
436
|
.slice(0, 12)
|
|
@@ -854,19 +917,44 @@ async function main() {
|
|
|
854
917
|
return new Response("Not found", { status: 404 });
|
|
855
918
|
}
|
|
856
919
|
|
|
857
|
-
// Wrap with request logging
|
|
858
|
-
const fetch =
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
920
|
+
// Wrap with request logging + friendly plugin errors.
|
|
921
|
+
const fetch = async (req: Request) => {
|
|
922
|
+
const start = performance.now();
|
|
923
|
+
const url = new URL(req.url);
|
|
924
|
+
try {
|
|
925
|
+
const response = await handleRequest(req);
|
|
926
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
862
927
|
const duration = (performance.now() - start).toFixed(0);
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
928
|
+
daemonLog.info(`${req.method} ${url.pathname} ${response.status} ${duration}ms`);
|
|
929
|
+
}
|
|
930
|
+
return response;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const duration = (performance.now() - start).toFixed(0);
|
|
933
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
934
|
+
if (isPluginLoaderError(error)) {
|
|
935
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
936
|
+
daemonLog.warn(
|
|
937
|
+
`${req.method} ${url.pathname} 500 ${duration}ms plugin error: ${message}`,
|
|
938
|
+
);
|
|
939
|
+
} else if (!daemonLog) {
|
|
940
|
+
logWarn(`Plugin error: ${message}`);
|
|
866
941
|
}
|
|
867
|
-
return
|
|
942
|
+
return new Response(buildDevPluginErrorHtml(message), {
|
|
943
|
+
status: 500,
|
|
944
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
945
|
+
});
|
|
868
946
|
}
|
|
869
|
-
|
|
947
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
948
|
+
daemonLog.error(`${req.method} ${url.pathname} 500 ${duration}ms ${message}`);
|
|
949
|
+
} else if (!daemonLog) {
|
|
950
|
+
console.error(error);
|
|
951
|
+
}
|
|
952
|
+
return new Response("Internal server error", {
|
|
953
|
+
status: 500,
|
|
954
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
};
|
|
870
958
|
|
|
871
959
|
// Create server
|
|
872
960
|
Bun.serve({
|
|
@@ -365,7 +365,7 @@ plugins:
|
|
|
365
365
|
slides-visuals:
|
|
366
366
|
name: "Slides Visuals"
|
|
367
367
|
description: "Test visuals"
|
|
368
|
-
version: "0.2.
|
|
368
|
+
version: "0.2.1"
|
|
369
369
|
contract: "1"
|
|
370
370
|
kitfly: ">=0.2.0 <1.0.0"
|
|
371
371
|
license: MIT
|
|
@@ -383,15 +383,77 @@ plugins:
|
|
|
383
383
|
|
|
384
384
|
await writeFile(
|
|
385
385
|
join(siteDir, "kitfly.plugins.yaml"),
|
|
386
|
-
"plugins:\n - slides-visuals@0.2.
|
|
386
|
+
"plugins:\n - slides-visuals@0.2.1\n",
|
|
387
387
|
"utf-8",
|
|
388
388
|
);
|
|
389
389
|
|
|
390
390
|
await build({ folder: siteDir, out: outDir });
|
|
391
391
|
|
|
392
392
|
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
393
|
-
expect(html).toContain('data-kitfly-plugin="slides-visuals@0.2.
|
|
393
|
+
expect(html).toContain('data-kitfly-plugin="slides-visuals@0.2.1"');
|
|
394
394
|
expect(html).toContain(css);
|
|
395
395
|
expect(html).toContain(js);
|
|
396
396
|
});
|
|
397
|
+
|
|
398
|
+
it("ignores unknown slides-visuals block types while enforcing known contracts", async () => {
|
|
399
|
+
const siteDir = await makeTempDir();
|
|
400
|
+
const outDir = "out";
|
|
401
|
+
await writeSiteYaml(siteDir, { mode: "slides" });
|
|
402
|
+
await writeMd(
|
|
403
|
+
siteDir,
|
|
404
|
+
"docs/deck.md",
|
|
405
|
+
`# Title
|
|
406
|
+
|
|
407
|
+
:::future-thing
|
|
408
|
+
note: this should pass through
|
|
409
|
+
:::
|
|
410
|
+
|
|
411
|
+
:::kpi
|
|
412
|
+
label: Uptime
|
|
413
|
+
value: 99.95%
|
|
414
|
+
:::
|
|
415
|
+
`,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const js = "console.log('slides visuals');";
|
|
419
|
+
const css = ".kitfly-visual{border:1px solid red;}";
|
|
420
|
+
await mkdir(join(siteDir, "plugins-dist"), { recursive: true });
|
|
421
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.js"), js, "utf-8");
|
|
422
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.css"), css, "utf-8");
|
|
423
|
+
await mkdir(join(siteDir, "registry"), { recursive: true });
|
|
424
|
+
await writeFile(
|
|
425
|
+
join(siteDir, "registry", "plugins.yaml"),
|
|
426
|
+
`version: 1
|
|
427
|
+
updated: "2026-02-15"
|
|
428
|
+
baseUrl: ""
|
|
429
|
+
plugins:
|
|
430
|
+
slides-visuals:
|
|
431
|
+
name: "Slides Visuals"
|
|
432
|
+
description: "Test visuals"
|
|
433
|
+
version: "0.2.1"
|
|
434
|
+
contract: "1"
|
|
435
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
436
|
+
license: MIT
|
|
437
|
+
verified: true
|
|
438
|
+
modes: ["slides"]
|
|
439
|
+
assets:
|
|
440
|
+
js: "plugins-dist/slides-visuals.js"
|
|
441
|
+
css: "plugins-dist/slides-visuals.css"
|
|
442
|
+
assetSha256:
|
|
443
|
+
js: "sha256:${sha256Hex(js)}"
|
|
444
|
+
css: "sha256:${sha256Hex(css)}"
|
|
445
|
+
`,
|
|
446
|
+
"utf-8",
|
|
447
|
+
);
|
|
448
|
+
await writeFile(
|
|
449
|
+
join(siteDir, "kitfly.plugins.yaml"),
|
|
450
|
+
"plugins:\n - slides-visuals@0.2.1\n",
|
|
451
|
+
"utf-8",
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
await expect(build({ folder: siteDir, out: outDir })).resolves.toBeUndefined();
|
|
455
|
+
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
456
|
+
expect(html).toContain('data-kitfly-plugin="slides-visuals@0.2.1"');
|
|
457
|
+
expect(html).toContain("future-thing");
|
|
458
|
+
});
|
|
397
459
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildDevPluginErrorHtml } from "../../scripts/dev.ts";
|
|
3
|
+
|
|
4
|
+
describe("dev plugin error page", () => {
|
|
5
|
+
it("shows actionable update hint for plugin version mismatch", () => {
|
|
6
|
+
const html = buildDevPluginErrorHtml("Plugin slides-visuals version mismatch: 0.2.0 != 0.2.1");
|
|
7
|
+
expect(html).toContain("Plugin setup error");
|
|
8
|
+
expect(html).toContain("kitfly.plugins.yaml");
|
|
9
|
+
expect(html).toContain("slides-visuals@0.2.1");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("escapes error text and falls back to generic guidance", () => {
|
|
13
|
+
const html = buildDevPluginErrorHtml('Invalid plugin ref: bad"@1.0.0 <x>');
|
|
14
|
+
expect(html).toContain(
|
|
15
|
+
"Check <code>kitfly.plugins.yaml</code> and <code>registry/plugins.yaml</code>",
|
|
16
|
+
);
|
|
17
|
+
expect(html).toContain("bad"@1.0.0 <x>");
|
|
18
|
+
expect(html).not.toContain('bad"@1.0.0 <x>');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
envString,
|
|
25
25
|
escapeHtml,
|
|
26
26
|
exists,
|
|
27
|
+
filterUnknownSlidesVisualsTypeDiagnostics,
|
|
27
28
|
formatDate,
|
|
28
29
|
generateProvenance,
|
|
29
30
|
getGitInfo,
|
|
@@ -42,6 +43,7 @@ import {
|
|
|
42
43
|
stripQuotes,
|
|
43
44
|
toUrlPath,
|
|
44
45
|
validatePath,
|
|
46
|
+
validateSlidesVisualsFences,
|
|
45
47
|
} from "../shared.ts";
|
|
46
48
|
|
|
47
49
|
describe("slugify", () => {
|
|
@@ -155,6 +157,27 @@ Still one slide`;
|
|
|
155
157
|
});
|
|
156
158
|
});
|
|
157
159
|
|
|
160
|
+
describe("slides-visuals diagnostics filtering", () => {
|
|
161
|
+
it("drops unknown-type diagnostics while preserving schema violations", () => {
|
|
162
|
+
const markdown = `:::future-thing
|
|
163
|
+
foo: bar
|
|
164
|
+
:::
|
|
165
|
+
|
|
166
|
+
:::kpi
|
|
167
|
+
label: Missing value
|
|
168
|
+
:::`;
|
|
169
|
+
const diagnostics = validateSlidesVisualsFences(markdown);
|
|
170
|
+
const filtered = filterUnknownSlidesVisualsTypeDiagnostics(diagnostics);
|
|
171
|
+
expect(
|
|
172
|
+
diagnostics.some((d) => d.message.startsWith("Unknown slides-visuals block type:")),
|
|
173
|
+
).toBe(true);
|
|
174
|
+
expect(filtered.some((d) => d.message.startsWith("Unknown slides-visuals block type:"))).toBe(
|
|
175
|
+
false,
|
|
176
|
+
);
|
|
177
|
+
expect(filtered.some((d) => d.message.includes("Missing required key: value"))).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
158
181
|
describe("segmentSlides", () => {
|
|
159
182
|
it("uses frontmatter title and class when present", () => {
|
|
160
183
|
const input = `---
|
|
@@ -112,3 +112,36 @@ test("slides-visuals: rowCells parses JSON array strings", async () => {
|
|
|
112
112
|
const hooks = await loadHooks();
|
|
113
113
|
expect(hooks.rowCells('["A", "B", "C"]')).toEqual(["A", "B", "C"]);
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
test("slides-visuals: absorbed scalar marker preserves preceding item (flow-converging)", async () => {
|
|
117
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
118
|
+
|
|
119
|
+
const out = parseBodyNodesWithFirstLines(
|
|
120
|
+
["sources:"],
|
|
121
|
+
[],
|
|
122
|
+
new FakeElement("UL", "", [
|
|
123
|
+
new FakeElement("LI", "Frontend Logs\ntarget: Dashboard"),
|
|
124
|
+
new FakeElement("LI", "API Logs"),
|
|
125
|
+
]),
|
|
126
|
+
"flow-converging",
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(out.target).toBe("Dashboard");
|
|
130
|
+
expect(out.sources).toEqual(["Frontend Logs", "API Logs"]);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("slides-visuals: parses object list items for timeline events", async () => {
|
|
134
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
135
|
+
|
|
136
|
+
const out = parseBodyNodesWithFirstLines(
|
|
137
|
+
["events:"],
|
|
138
|
+
[],
|
|
139
|
+
new FakeElement("UL", "", [
|
|
140
|
+
new FakeElement("LI", "label: Kickoff\ndate: Jan 2026"),
|
|
141
|
+
new FakeElement("LI", "label: Alpha"),
|
|
142
|
+
]),
|
|
143
|
+
"timeline-horizontal",
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(out.events).toEqual([{ label: "Kickoff", date: "Jan 2026" }, { label: "Alpha" }]);
|
|
147
|
+
});
|
package/src/shared.ts
CHANGED
|
@@ -506,6 +506,11 @@ const SLIDES_VISUALS_TYPES = new Set([
|
|
|
506
506
|
"layer-cake",
|
|
507
507
|
"pyramid",
|
|
508
508
|
"funnel",
|
|
509
|
+
"timeline-horizontal",
|
|
510
|
+
"timeline-vertical",
|
|
511
|
+
"flow-branching",
|
|
512
|
+
"flow-converging",
|
|
513
|
+
"staircase",
|
|
509
514
|
]);
|
|
510
515
|
|
|
511
516
|
const SLIDES_VISUALS_RULES: Record<
|
|
@@ -564,6 +569,31 @@ const SLIDES_VISUALS_RULES: Record<
|
|
|
564
569
|
scalars: [],
|
|
565
570
|
lists: { stages: { kind: "strings" } },
|
|
566
571
|
},
|
|
572
|
+
"timeline-horizontal": {
|
|
573
|
+
required: ["events"],
|
|
574
|
+
scalars: [],
|
|
575
|
+
lists: { events: { kind: "objects", fields: ["label"], optional: ["date"] } },
|
|
576
|
+
},
|
|
577
|
+
"timeline-vertical": {
|
|
578
|
+
required: ["events"],
|
|
579
|
+
scalars: [],
|
|
580
|
+
lists: { events: { kind: "objects", fields: ["label"], optional: ["date"] } },
|
|
581
|
+
},
|
|
582
|
+
"flow-branching": {
|
|
583
|
+
required: ["source", "branches"],
|
|
584
|
+
scalars: ["source", "split"],
|
|
585
|
+
lists: { branches: { kind: "strings" } },
|
|
586
|
+
},
|
|
587
|
+
"flow-converging": {
|
|
588
|
+
required: ["sources", "target"],
|
|
589
|
+
scalars: ["target", "merge"],
|
|
590
|
+
lists: { sources: { kind: "strings" } },
|
|
591
|
+
},
|
|
592
|
+
staircase: {
|
|
593
|
+
required: ["steps"],
|
|
594
|
+
scalars: ["direction"],
|
|
595
|
+
lists: { steps: { kind: "strings" } },
|
|
596
|
+
},
|
|
567
597
|
};
|
|
568
598
|
|
|
569
599
|
/**
|
|
@@ -754,6 +784,12 @@ export function validateSlidesVisualsFences(markdown: string): SlidesVisualsFenc
|
|
|
754
784
|
return diagnostics;
|
|
755
785
|
}
|
|
756
786
|
|
|
787
|
+
export function filterUnknownSlidesVisualsTypeDiagnostics(
|
|
788
|
+
diagnostics: SlidesVisualsFenceDiagnostic[],
|
|
789
|
+
): SlidesVisualsFenceDiagnostic[] {
|
|
790
|
+
return diagnostics.filter((d) => !d.message.startsWith("Unknown slides-visuals block type:"));
|
|
791
|
+
}
|
|
792
|
+
|
|
757
793
|
/**
|
|
758
794
|
* Split markdown content into slide chunks using explicit delimiter.
|
|
759
795
|
* Delimiter lines inside fenced code blocks are ignored.
|