kitfly 0.2.1 → 0.2.4
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 +79 -0
- package/README.md +38 -21
- package/VERSION +1 -1
- package/dist/_raw/content/guide/branding.md +146 -0
- package/dist/_raw/content/guide/data-driven-content.md +204 -0
- package/dist/_raw/content/reference/configuration.md +145 -7
- package/dist/_raw/content/reference/environment-variables.md +26 -1
- package/dist/_raw/content/reference/gantt-widget.md +468 -0
- package/dist/_raw/content/reference/glossary.md +25 -1
- package/dist/_raw/content/reference/key-concepts.md +30 -2
- package/dist/_raw/content/reference/plugins.md +170 -1
- package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
- package/dist/content/deployment/preflight.html +11 -8
- package/dist/content/deployment/recipes/aws-s3.html +11 -8
- package/dist/content/deployment/recipes/cloudflare-pages.html +11 -8
- package/dist/content/deployment/recipes/cloudflare-r2.html +11 -8
- package/dist/content/deployment/recipes/fly-io.html +11 -8
- package/dist/content/deployment/recipes/github-pages.html +11 -8
- package/dist/content/deployment/recipes/netlify.html +11 -8
- package/dist/content/deployment/recipes/vercel.html +11 -8
- package/dist/content/deployment/secrets-and-env-vars.html +11 -8
- package/dist/content/deployment.html +11 -8
- package/dist/content/guide/approaches.html +11 -8
- package/dist/content/guide/branding.html +509 -0
- package/dist/content/guide/data-driven-content.html +542 -0
- package/dist/content/guide/features.html +11 -8
- package/dist/content/guide/getting-started.html +11 -8
- package/dist/content/guide/kitfly-overview.html +11 -8
- package/dist/content/reference/configuration.html +136 -11
- package/dist/content/reference/design-catalog.html +11 -8
- package/dist/content/reference/environment-variables.html +51 -10
- package/dist/content/reference/gantt-widget.html +899 -0
- package/dist/content/reference/glossary.html +25 -10
- package/dist/content/reference/key-concepts.html +34 -11
- package/dist/content/reference/plugins.html +261 -10
- package/dist/content/reference/slides-authoring-guidelines.html +11 -8
- package/dist/content/reference/structure.html +11 -8
- package/dist/content/reference.html +11 -8
- package/dist/content/templates/crucible.html +11 -8
- package/dist/content/templates/handbook.html +11 -8
- package/dist/content/templates/minimal.html +11 -8
- package/dist/content/templates/overview.html +11 -8
- package/dist/content/templates/pipeline.html +11 -8
- package/dist/content/templates/productbook.html +11 -8
- package/dist/content/templates/runbook.html +11 -8
- package/dist/content/templates/servicebook.html +11 -8
- package/dist/content-index.json +37 -2
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +11 -8
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +11 -8
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +11 -8
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +11 -8
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +11 -8
- package/dist/docs/decisions/ADR-0006-data-driven-content.html +751 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +11 -8
- package/dist/docs/decisions/DDR-0002-theme-system.html +11 -8
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +11 -8
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +11 -8
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +11 -8
- package/dist/docs/userguide/cli/build.html +11 -8
- package/dist/docs/userguide/cli/bundle.html +11 -8
- package/dist/docs/userguide/cli/dev.html +11 -8
- package/dist/docs/userguide/cli/init.html +11 -8
- package/dist/docs/userguide/cli/servers.html +11 -8
- package/dist/docs/userguide/cli/stop.html +11 -8
- package/dist/docs/userguide/cli/update.html +11 -8
- package/dist/docs/userguide/cli/version.html +11 -8
- package/dist/docs/userguide/cli.html +11 -8
- package/dist/docs/userguide/sharing.html +11 -8
- package/dist/index.html +11 -8
- package/dist/llms.txt +3 -3
- package/dist/provenance.json +4 -5
- package/dist/reports/license-inventory.csv +199 -0
- package/dist/schemas/plugin-registry.schema.html +11 -8
- package/dist/schemas/plugin-schemas-notes.html +11 -8
- package/dist/schemas/plugin.schema.html +11 -8
- package/dist/schemas/plugins.schema.html +11 -8
- package/dist/schemas/v0/common.schema.html +15 -12
- package/dist/schemas/v0/plugin-registry.schema.html +14 -11
- package/dist/schemas/v0/plugin.schema.html +14 -11
- package/dist/schemas/v0/plugins.schema.html +14 -11
- package/dist/schemas/v0/site.schema.html +68 -9
- package/dist/schemas/v0/theme.schema.html +22 -19
- package/dist/schemas.html +11 -8
- package/dist/styles.css +39 -4
- package/package.json +1 -1
- package/plugins-dist/latex-runtime.js +140 -0
- package/plugins-dist/latex.js +178 -0
- package/plugins-dist/planning-visuals.css +261 -0
- package/plugins-dist/planning-visuals.js +669 -0
- package/plugins-dist/slides-charts-lite-runtime.js +179 -0
- package/plugins-dist/slides-charts-lite.js +198 -0
- package/registry/plugins.yaml +40 -1
- package/schemas/v0/site.schema.json +56 -0
- package/scripts/build-all.ts +5 -0
- package/scripts/build.ts +264 -80
- package/scripts/bundle.ts +188 -17
- package/scripts/dev.ts +294 -171
- package/scripts/embed-docs.ts +119 -0
- package/src/__tests__/brief.test.ts +151 -0
- package/src/__tests__/build.test.ts +293 -1
- package/src/__tests__/bundle.test.ts +195 -0
- package/src/__tests__/docs.test.ts +117 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/bad-month-format.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/marker-format-mismatch.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/milestone-format-mismatch.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/missing-tracks.md +5 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/track-reversed.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-basic.md +15 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-no-milestones.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/month-basic.md +16 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/no-milestones.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/week-basic.md +20 -0
- package/src/__tests__/init.test.ts +51 -2
- package/src/__tests__/latex-runtime.bun.test.ts +35 -0
- package/src/__tests__/planning-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/planning-visuals-runtime-regressions.bun.test.ts +68 -0
- package/src/__tests__/planning-visuals-runtime.bun.test.ts +192 -0
- package/src/__tests__/shared.test.ts +719 -1
- package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
- package/src/cli.ts +124 -22
- package/src/commands/docs.ts +71 -0
- package/src/commands/init.ts +1 -1
- package/src/generated/embedded-docs.ts +2384 -0
- package/src/server-registry.ts +50 -10
- package/src/shared.ts +1174 -43
- package/src/site/styles.css +39 -4
- package/src/site/template.html +5 -2
- package/src/templates/brief.ts +486 -0
- package/src/templates/deck.ts +59 -0
- package/src/templates/driver.ts +46 -13
- package/src/templates/handbook.ts +32 -0
- package/src/templates/runbook.ts +32 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
type PlanningVisualsRegressionHooks = {
|
|
4
|
+
parseGanttNodesWithFirstLines: (
|
|
5
|
+
firstLines: string[],
|
|
6
|
+
between: any[],
|
|
7
|
+
endNode: any,
|
|
8
|
+
) => Record<string, unknown>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
class FakeElement {
|
|
12
|
+
tagName: string;
|
|
13
|
+
textContent: string;
|
|
14
|
+
#children: FakeElement[];
|
|
15
|
+
|
|
16
|
+
constructor(tagName: string, textContent = "", children: FakeElement[] = []) {
|
|
17
|
+
this.tagName = tagName;
|
|
18
|
+
this.textContent = textContent;
|
|
19
|
+
this.#children = children;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
querySelectorAll(selector: string): FakeElement[] {
|
|
23
|
+
if (selector === ":scope > li") return this.#children;
|
|
24
|
+
if (selector === ":scope > p") return [];
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function loadHooks(): Promise<PlanningVisualsRegressionHooks> {
|
|
30
|
+
// @ts-expect-error JS plugin registers test hooks on globalThis in non-DOM environments.
|
|
31
|
+
await import("../../plugins-dist/planning-visuals.js");
|
|
32
|
+
const hooks = (globalThis as any).__kitflyPlanningVisualsTest as
|
|
33
|
+
| PlanningVisualsRegressionHooks
|
|
34
|
+
| undefined;
|
|
35
|
+
if (!hooks) throw new Error("planning-visuals test hooks not found on globalThis");
|
|
36
|
+
return hooks;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test("planning-visuals: fragmented list-key switch preserves markers and milestones", async () => {
|
|
40
|
+
const { parseGanttNodesWithFirstLines } = await loadHooks();
|
|
41
|
+
const data = parseGanttNodesWithFirstLines(
|
|
42
|
+
['time-unit: "month"', 'time-start: "2026-03"', 'time-end: "2026-09"', "markers:"],
|
|
43
|
+
[],
|
|
44
|
+
new FakeElement("UL", "", [
|
|
45
|
+
new FakeElement("LI", 'label: "P66 Conf (May 26)"\ndate: "2026-05"\ntracks:'),
|
|
46
|
+
new FakeElement(
|
|
47
|
+
"LI",
|
|
48
|
+
'label: "Wave 1"\ndepth: 1\nstart: "2026-03"\nend: "2026-04"\nmilestones:',
|
|
49
|
+
),
|
|
50
|
+
new FakeElement("LI", 'label: "Auto Go-Live May 19"\ndate: "2026-05"\ntracks:'),
|
|
51
|
+
new FakeElement("LI", 'label: "Wave 2"\ndepth: 1\nstart: "2026-05"\nend: "2026-06"'),
|
|
52
|
+
]),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const markers = data.markers as Array<Record<string, string>>;
|
|
56
|
+
const tracks = data.tracks as Array<Record<string, string>>;
|
|
57
|
+
const milestones = data.milestones as Array<Record<string, string>>;
|
|
58
|
+
|
|
59
|
+
expect(markers).toHaveLength(1);
|
|
60
|
+
expect(markers[0].label).toBe("P66 Conf (May 26)");
|
|
61
|
+
|
|
62
|
+
expect(milestones).toHaveLength(1);
|
|
63
|
+
expect(milestones[0].label).toBe("Auto Go-Live May 19");
|
|
64
|
+
|
|
65
|
+
expect(tracks).toHaveLength(2);
|
|
66
|
+
expect(tracks[0].label).toBe("Wave 1");
|
|
67
|
+
expect(tracks[1].label).toBe("Wave 2");
|
|
68
|
+
});
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
type PlanningVisualsHooks = {
|
|
4
|
+
parseFence: (text: string) => { type: string; data: Record<string, unknown> } | null;
|
|
5
|
+
buildAxisCellLabel: (
|
|
6
|
+
unit: string,
|
|
7
|
+
info: { year: number; week?: number; month?: number; label: string },
|
|
8
|
+
prev: { year: number; week?: number; month?: number; label: string } | null,
|
|
9
|
+
index: number,
|
|
10
|
+
totalUnits: number,
|
|
11
|
+
) => string;
|
|
12
|
+
weekLabelStepForUnits: (totalUnits: number) => number;
|
|
13
|
+
weekAxisContextLabel: (
|
|
14
|
+
startInfo: { year: number; week: number; label: string },
|
|
15
|
+
endInfo: { year: number; week: number; label: string },
|
|
16
|
+
) => string;
|
|
17
|
+
parseListItemText: (rawText: string) => {
|
|
18
|
+
item: Record<string, string>;
|
|
19
|
+
switchListKey: string | null;
|
|
20
|
+
};
|
|
21
|
+
assignMarkerLabelLanes: (markers: Array<Record<string, any>>) => {
|
|
22
|
+
markers: Array<Record<string, any>>;
|
|
23
|
+
laneCount: number;
|
|
24
|
+
};
|
|
25
|
+
parseMarkerPosition: (value: string, unit: string) => number | null;
|
|
26
|
+
parseUnitOrdinal: (value: string, unit: string) => number | null;
|
|
27
|
+
weekLabelFromOrdinal: (ordinal: number) => { year: number; week: number; label: string };
|
|
28
|
+
monthLabelFromOrdinal: (ordinal: number) => { year: number; month: number; label: string };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
async function loadHooks(): Promise<PlanningVisualsHooks> {
|
|
32
|
+
// @ts-expect-error JS plugin registers test hooks on globalThis in non-DOM environments.
|
|
33
|
+
await import("../../plugins-dist/planning-visuals.js");
|
|
34
|
+
const hooks = (globalThis as any).__kitflyPlanningVisualsTest as PlanningVisualsHooks | undefined;
|
|
35
|
+
if (!hooks) throw new Error("planning-visuals test hooks not found on globalThis");
|
|
36
|
+
return hooks;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test("planning-visuals: week roundtrip keeps same label", async () => {
|
|
40
|
+
const hooks = await loadHooks();
|
|
41
|
+
const ordinal = hooks.parseUnitOrdinal("2026-W14", "week");
|
|
42
|
+
expect(ordinal).not.toBeNull();
|
|
43
|
+
const label = hooks.weekLabelFromOrdinal(ordinal as number);
|
|
44
|
+
expect(label.label).toBe("W14");
|
|
45
|
+
expect(label.year).toBe(2026);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("planning-visuals: week boundary label resolves correct year", async () => {
|
|
49
|
+
const hooks = await loadHooks();
|
|
50
|
+
const ordinal = hooks.parseUnitOrdinal("2027-W01", "week");
|
|
51
|
+
expect(ordinal).not.toBeNull();
|
|
52
|
+
const label = hooks.weekLabelFromOrdinal(ordinal as number);
|
|
53
|
+
expect(label.label).toBe("W01");
|
|
54
|
+
expect(label.year).toBe(2027);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("planning-visuals: compact week axis uses sparse full week labels", async () => {
|
|
58
|
+
const hooks = await loadHooks();
|
|
59
|
+
expect(hooks.weekLabelStepForUnits(28)).toBe(4);
|
|
60
|
+
const first = hooks.weekLabelFromOrdinal(hooks.parseUnitOrdinal("2026-W10", "week") as number);
|
|
61
|
+
const interior = hooks.weekLabelFromOrdinal(
|
|
62
|
+
(hooks.parseUnitOrdinal("2026-W10", "week") as number) + 4,
|
|
63
|
+
);
|
|
64
|
+
const firstText = hooks.buildAxisCellLabel("week", first, null, 0, 28);
|
|
65
|
+
const interiorText = hooks.buildAxisCellLabel("week", interior, first, 4, 28);
|
|
66
|
+
expect(firstText.startsWith("W10")).toBe(true);
|
|
67
|
+
expect(interiorText).toBe("W14");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("planning-visuals: week axis context label is explicit for audience clarity", async () => {
|
|
71
|
+
const hooks = await loadHooks();
|
|
72
|
+
const start = hooks.weekLabelFromOrdinal(hooks.parseUnitOrdinal("2026-W10", "week") as number);
|
|
73
|
+
const end = hooks.weekLabelFromOrdinal(hooks.parseUnitOrdinal("2026-W36", "week") as number);
|
|
74
|
+
const context = hooks.weekAxisContextLabel(start, end);
|
|
75
|
+
expect(context).toBe("ISO Weeks W10-W36 (2026)");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("planning-visuals: parseFence preserves interleaved row order from repeated lists", async () => {
|
|
79
|
+
const hooks = await loadHooks();
|
|
80
|
+
const parsed = hooks.parseFence(
|
|
81
|
+
`:::gantt
|
|
82
|
+
+time-unit: week
|
|
83
|
+
+time-start: 2026-W14
|
|
84
|
+
+time-end: 2026-W20
|
|
85
|
+
+tracks:
|
|
86
|
+
+ - label: Wave 1
|
|
87
|
+
+ depth: 1
|
|
88
|
+
+ start: 2026-W14
|
|
89
|
+
+ end: 2026-W16
|
|
90
|
+
+milestones:
|
|
91
|
+
+ - label: Go/No-Go
|
|
92
|
+
+ date: 2026-W15
|
|
93
|
+
+tracks:
|
|
94
|
+
+ - label: Wave 2
|
|
95
|
+
+ depth: 1
|
|
96
|
+
+ start: 2026-W17
|
|
97
|
+
+ end: 2026-W20
|
|
98
|
+
+:::`.replace(/^\+/gm, ""),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(parsed?.type).toBe("gantt");
|
|
102
|
+
const order = (parsed?.data.__rowOrder as Array<{ kind: string; index: number }>) || [];
|
|
103
|
+
expect(order).toEqual([
|
|
104
|
+
{ kind: "track", index: 0 },
|
|
105
|
+
{ kind: "milestone", index: 0 },
|
|
106
|
+
{ kind: "track", index: 1 },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("planning-visuals: parseFence parses markers list", async () => {
|
|
111
|
+
const hooks = await loadHooks();
|
|
112
|
+
const parsed = hooks.parseFence(
|
|
113
|
+
`:::gantt
|
|
114
|
+
+time-unit: week
|
|
115
|
+
+time-start: 2026-W14
|
|
116
|
+
+time-end: 2026-W30
|
|
117
|
+
+markers:
|
|
118
|
+
+ - label: Go/No-Go
|
|
119
|
+
+ date: 2026-W20
|
|
120
|
+
+ - label: Phase Gate
|
|
121
|
+
+ date: 2026-W28
|
|
122
|
+
+tracks:
|
|
123
|
+
+ - label: Phase 1
|
|
124
|
+
+ depth: 1
|
|
125
|
+
+ start: 2026-W14
|
|
126
|
+
+ end: 2026-W24
|
|
127
|
+
+:::`.replace(/^\+/gm, ""),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
expect(parsed?.type).toBe("gantt");
|
|
131
|
+
const markers = parsed?.data.markers as Array<Record<string, string>>;
|
|
132
|
+
expect(markers).toHaveLength(2);
|
|
133
|
+
expect(markers[0].label).toBe("Go/No-Go");
|
|
134
|
+
expect(markers[0].date).toBe("2026-W20");
|
|
135
|
+
expect(markers[1].label).toBe("Phase Gate");
|
|
136
|
+
expect(markers[1].date).toBe("2026-W28");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("planning-visuals: parseFence keeps optional marker color", async () => {
|
|
140
|
+
const hooks = await loadHooks();
|
|
141
|
+
const parsed = hooks.parseFence(
|
|
142
|
+
`:::gantt
|
|
143
|
+
time-unit: month
|
|
144
|
+
time-start: 2026-03
|
|
145
|
+
time-end: 2026-10
|
|
146
|
+
markers:
|
|
147
|
+
- label: Go/No-Go
|
|
148
|
+
date: 2026-05-26
|
|
149
|
+
color: #38bdf8
|
|
150
|
+
tracks:
|
|
151
|
+
- label: Phase 1
|
|
152
|
+
depth: 1
|
|
153
|
+
start: 2026-03
|
|
154
|
+
end: 2026-06
|
|
155
|
+
:::`.replace(/^\+/gm, ""),
|
|
156
|
+
);
|
|
157
|
+
const markers = parsed?.data.markers as Array<Record<string, string>>;
|
|
158
|
+
expect(markers).toHaveLength(1);
|
|
159
|
+
expect(markers[0].color).toBe("#38bdf8");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("planning-visuals: marker parser supports day precision in month mode", async () => {
|
|
163
|
+
const hooks = await loadHooks();
|
|
164
|
+
const monthCenter = hooks.parseMarkerPosition("2026-05", "month");
|
|
165
|
+
const monthDay = hooks.parseMarkerPosition("2026-05-26", "month");
|
|
166
|
+
expect(monthCenter).not.toBeNull();
|
|
167
|
+
expect(monthDay).not.toBeNull();
|
|
168
|
+
expect(monthDay as number).toBeGreaterThan(monthCenter as number);
|
|
169
|
+
expect(hooks.parseMarkerPosition("2026-02-30", "month")).toBeNull();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("planning-visuals: list item parser detects in-item list key switch", async () => {
|
|
173
|
+
const hooks = await loadHooks();
|
|
174
|
+
const parsed = hooks.parseListItemText(
|
|
175
|
+
`label: "P66 Conf (May 26)"
|
|
176
|
+
date: "2026-05"
|
|
177
|
+
tracks:`,
|
|
178
|
+
);
|
|
179
|
+
expect(parsed.item.label).toBe("P66 Conf (May 26)");
|
|
180
|
+
expect(parsed.item.date).toBe("2026-05");
|
|
181
|
+
expect(parsed.switchListKey).toBe("tracks");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("planning-visuals: marker layout assigns separate lanes for nearby labels", async () => {
|
|
185
|
+
const hooks = await loadHooks();
|
|
186
|
+
const layout = hooks.assignMarkerLabelLanes([
|
|
187
|
+
{ label: "Conference", left: 40, color: "" },
|
|
188
|
+
{ label: "Go-Live", left: 42, color: "" },
|
|
189
|
+
]);
|
|
190
|
+
expect(layout.laneCount).toBeGreaterThan(1);
|
|
191
|
+
expect(layout.markers[0].__lane).not.toBe(layout.markers[1].__lane);
|
|
192
|
+
});
|