kitfly 0.1.2 → 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.
Files changed (209) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +63 -16
  3. package/VERSION +1 -1
  4. package/dist/_raw/content/deployment/preflight.md +134 -0
  5. package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
  6. package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
  7. package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
  8. package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
  9. package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
  10. package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
  11. package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
  12. package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
  13. package/dist/_raw/content/deployment.md +128 -0
  14. package/dist/_raw/content/guide/approaches.md +182 -0
  15. package/dist/_raw/content/guide/features.md +121 -0
  16. package/dist/_raw/content/guide/getting-started.md +112 -0
  17. package/dist/_raw/content/guide/kitfly-overview.md +209 -0
  18. package/dist/_raw/content/reference/configuration.md +259 -0
  19. package/dist/_raw/content/reference/design-catalog.md +167 -0
  20. package/dist/_raw/content/reference/environment-variables.md +66 -0
  21. package/dist/_raw/content/reference/glossary.md +92 -0
  22. package/dist/_raw/content/reference/key-concepts.md +118 -0
  23. package/dist/_raw/content/reference/plugins.md +220 -0
  24. package/dist/_raw/content/reference/slides-authoring-guidelines.md +129 -0
  25. package/dist/_raw/content/reference/structure.md +166 -0
  26. package/dist/_raw/content/reference.md +20 -0
  27. package/dist/_raw/content/templates/crucible.md +192 -0
  28. package/dist/_raw/content/templates/handbook.md +83 -0
  29. package/dist/_raw/content/templates/minimal.md +138 -0
  30. package/dist/_raw/content/templates/overview.md +187 -0
  31. package/dist/_raw/content/templates/pipeline.md +151 -0
  32. package/dist/_raw/content/templates/productbook.md +187 -0
  33. package/dist/_raw/content/templates/runbook.md +193 -0
  34. package/dist/_raw/content/templates/servicebook.md +163 -0
  35. package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
  36. package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
  37. package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
  38. package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
  39. package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
  40. package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
  41. package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
  42. package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
  43. package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
  44. package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
  45. package/dist/_raw/docs/userguide/cli/build.md +85 -0
  46. package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
  47. package/dist/_raw/docs/userguide/cli/dev.md +92 -0
  48. package/dist/_raw/docs/userguide/cli/init.md +116 -0
  49. package/dist/_raw/docs/userguide/cli/servers.md +69 -0
  50. package/dist/_raw/docs/userguide/cli/stop.md +76 -0
  51. package/dist/_raw/docs/userguide/cli/update.md +78 -0
  52. package/dist/_raw/docs/userguide/cli/version.md +65 -0
  53. package/dist/_raw/docs/userguide/cli.md +34 -0
  54. package/dist/_raw/docs/userguide/sharing.md +94 -0
  55. package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
  56. package/dist/_raw/schemas.md +42 -0
  57. package/dist/assets/brand/kitfly-favicon-32.png +0 -0
  58. package/dist/assets/brand/kitfly-icon-64.png +0 -0
  59. package/dist/assets/brand/kitfly-logo-128.png +0 -0
  60. package/dist/assets/brand/kitfly-logo-512.png +0 -0
  61. package/dist/assets/brand/kitfly-logo.svg +12132 -0
  62. package/dist/assets/brand/kitfly-neon-128.png +0 -0
  63. package/dist/assets/brand/kitfly-neon-192.png +0 -0
  64. package/dist/assets/brand/kitfly-neon-256.png +0 -0
  65. package/dist/assets/brand/kitfly-neon.png +0 -0
  66. package/dist/assets/brand/palette.md +75 -0
  67. package/dist/content/deployment/index.html +11 -0
  68. package/dist/content/deployment/preflight.html +418 -0
  69. package/dist/content/deployment/recipes/aws-s3.html +421 -0
  70. package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
  71. package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
  72. package/dist/content/deployment/recipes/fly-io.html +356 -0
  73. package/dist/content/deployment/recipes/github-pages.html +414 -0
  74. package/dist/content/deployment/recipes/index.html +11 -0
  75. package/dist/content/deployment/recipes/netlify.html +394 -0
  76. package/dist/content/deployment/recipes/vercel.html +382 -0
  77. package/dist/content/deployment/secrets-and-env-vars.html +380 -0
  78. package/dist/content/deployment.html +426 -0
  79. package/dist/content/guide/approaches.html +501 -0
  80. package/dist/content/guide/features.html +436 -0
  81. package/dist/content/guide/getting-started.html +403 -0
  82. package/dist/content/guide/index.html +11 -0
  83. package/dist/content/guide/kitfly-overview.html +544 -0
  84. package/dist/content/index.html +11 -0
  85. package/dist/content/reference/configuration.html +580 -0
  86. package/dist/content/reference/design-catalog.html +449 -0
  87. package/dist/content/reference/environment-variables.html +367 -0
  88. package/dist/content/reference/glossary.html +368 -0
  89. package/dist/content/reference/index.html +11 -0
  90. package/dist/content/reference/key-concepts.html +399 -0
  91. package/dist/content/reference/plugins.html +491 -0
  92. package/dist/content/reference/slides-authoring-guidelines.html +418 -0
  93. package/dist/content/reference/structure.html +463 -0
  94. package/dist/content/reference.html +335 -0
  95. package/dist/content/templates/crucible.html +546 -0
  96. package/dist/content/templates/handbook.html +405 -0
  97. package/dist/content/templates/index.html +11 -0
  98. package/dist/content/templates/minimal.html +447 -0
  99. package/dist/content/templates/overview.html +558 -0
  100. package/dist/content/templates/pipeline.html +494 -0
  101. package/dist/content/templates/productbook.html +540 -0
  102. package/dist/content/templates/runbook.html +543 -0
  103. package/dist/content/templates/servicebook.html +523 -0
  104. package/dist/content-index.json +549 -0
  105. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
  106. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
  107. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
  108. package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
  109. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
  110. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
  111. package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
  112. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
  113. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
  114. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
  115. package/dist/docs/decisions/index.html +11 -0
  116. package/dist/docs/userguide/cli/build.html +408 -0
  117. package/dist/docs/userguide/cli/bundle.html +419 -0
  118. package/dist/docs/userguide/cli/dev.html +428 -0
  119. package/dist/docs/userguide/cli/index.html +11 -0
  120. package/dist/docs/userguide/cli/init.html +436 -0
  121. package/dist/docs/userguide/cli/servers.html +393 -0
  122. package/dist/docs/userguide/cli/stop.html +408 -0
  123. package/dist/docs/userguide/cli/update.html +406 -0
  124. package/dist/docs/userguide/cli/version.html +406 -0
  125. package/dist/docs/userguide/cli.html +386 -0
  126. package/dist/docs/userguide/index.html +11 -0
  127. package/dist/docs/userguide/sharing.html +465 -0
  128. package/dist/index.html +387 -0
  129. package/dist/llms.txt +18 -0
  130. package/dist/provenance.json +7 -0
  131. package/dist/schemas/index.html +11 -0
  132. package/dist/schemas/plugin-registry.schema.html +327 -0
  133. package/dist/schemas/plugin-schemas-notes.html +364 -0
  134. package/dist/schemas/plugin.schema.html +327 -0
  135. package/dist/schemas/plugins.schema.html +327 -0
  136. package/dist/schemas/v0/common.schema.html +386 -0
  137. package/dist/schemas/v0/index.html +11 -0
  138. package/dist/schemas/v0/plugin-registry.schema.html +547 -0
  139. package/dist/schemas/v0/plugin.schema.html +497 -0
  140. package/dist/schemas/v0/plugins.schema.html +406 -0
  141. package/dist/schemas/v0/site.schema.html +541 -0
  142. package/dist/schemas/v0/theme.schema.html +615 -0
  143. package/dist/schemas.html +351 -0
  144. package/dist/styles.css +1262 -0
  145. package/package.json +4 -2
  146. package/plugins-dist/callouts.css +32 -0
  147. package/plugins-dist/callouts.js +46 -0
  148. package/plugins-dist/slides-visuals.css +390 -0
  149. package/plugins-dist/slides-visuals.js +689 -0
  150. package/registry/plugins.yaml +35 -0
  151. package/schemas/README.md +10 -0
  152. package/schemas/plugin-registry.schema.json +5 -0
  153. package/schemas/plugin-schemas-notes.md +71 -0
  154. package/schemas/plugin.schema.json +5 -0
  155. package/schemas/plugins.schema.json +5 -0
  156. package/schemas/v0/common.schema.json +64 -0
  157. package/schemas/v0/plugin-registry.schema.json +225 -0
  158. package/schemas/v0/plugin.schema.json +175 -0
  159. package/schemas/v0/plugins.schema.json +84 -0
  160. package/schemas/v0/site.schema.json +56 -9
  161. package/schemas/v0/theme.schema.json +105 -22
  162. package/scripts/build.ts +158 -3
  163. package/scripts/bundle.ts +261 -95
  164. package/scripts/dev.ts +301 -11
  165. package/src/__tests__/build.test.ts +220 -1
  166. package/src/__tests__/bundle.test.ts +31 -0
  167. package/src/__tests__/cli.test.ts +14 -3
  168. package/src/__tests__/dev-plugin-errors.test.ts +20 -0
  169. package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
  170. package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
  171. package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
  172. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-branching-no-source.md +5 -0
  173. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
  174. package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
  175. package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
  176. package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
  177. package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -0
  178. package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
  179. package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
  180. package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
  181. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching-no-split.md +7 -0
  182. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
  183. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
  184. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -0
  185. package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
  186. package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
  187. package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
  188. package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
  189. package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
  190. package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
  191. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase-down.md +7 -0
  192. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
  193. package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
  194. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
  195. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
  196. package/src/__tests__/init.test.ts +35 -0
  197. package/src/__tests__/plugin-loader.test.ts +221 -0
  198. package/src/__tests__/shared.test.ts +451 -0
  199. package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
  200. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +147 -0
  201. package/src/__tests__/styles.test.ts +35 -0
  202. package/src/cli.ts +9 -4
  203. package/src/plugin-loader.ts +245 -0
  204. package/src/shared.ts +650 -7
  205. package/src/site/styles.css +331 -0
  206. package/src/site/template.html +66 -5
  207. package/src/templates/deck.ts +186 -0
  208. package/src/templates/driver.ts +11 -1
  209. package/src/templates/minimal.ts +1 -0
@@ -648,6 +648,12 @@ describe("buildBundleSidebarHeader", () => {
648
648
  expect(source).toContain(`\${themeCSS}`);
649
649
  });
650
650
 
651
+ it("bundle script keeps docs-mode smooth anchor scrolling", async () => {
652
+ const source = await readFile(`${process.cwd()}/scripts/bundle.ts`, "utf-8");
653
+ expect(source).toContain("if (!shell) {");
654
+ expect(source).toContain("scrollIntoView({ behavior: 'smooth', block: 'start' });");
655
+ });
656
+
651
657
  it("shows version label with v prefix when version is provided", () => {
652
658
  const config: SiteConfig = {
653
659
  docroot: ".",
@@ -684,6 +690,31 @@ describe("buildBundleSidebarHeader", () => {
684
690
  expect(html).toContain('alt="My Company"');
685
691
  });
686
692
 
693
+ it("includes initial fallback metadata and onerror handler", () => {
694
+ const config: SiteConfig = {
695
+ docroot: ".",
696
+ title: "Test",
697
+ brand: { name: "Acme", url: "/" },
698
+ sections: [],
699
+ };
700
+
701
+ const html = buildBundleSidebarHeader(config, "1.0", "logo.png");
702
+ expect(html).toContain('data-initial="A"');
703
+ expect(html).toContain("classList.add('logo-fallback')");
704
+ });
705
+
706
+ it("escapes initial fallback character in data attribute", () => {
707
+ const config: SiteConfig = {
708
+ docroot: ".",
709
+ title: "Test",
710
+ brand: { name: '"quoted', url: "/" },
711
+ sections: [],
712
+ };
713
+
714
+ const html = buildBundleSidebarHeader(config, "1.0", "logo.png");
715
+ expect(html).toContain('data-initial="""');
716
+ });
717
+
687
718
  it("links brand to brand URL", () => {
688
719
  const config: SiteConfig = {
689
720
  docroot: ".",
@@ -54,7 +54,7 @@ kitfly v${version} - Turn your writing into a website
54
54
  Usage:
55
55
  kitfly dev [folder] Start dev server with hot reload
56
56
  kitfly build [folder] Build static site to dist/
57
- kitfly bundle [folder] Build single-file HTML bundle
57
+ kitfly bundle [folder] Build single-file HTML bundle to bundles/
58
58
  kitfly init [name] Create new project from template
59
59
  kitfly servers List running dev servers
60
60
  kitfly stop <port|all> Stop dev server(s)
@@ -68,11 +68,15 @@ Dev options:
68
68
  --json Output JSON (implies --daemon)
69
69
  --no-open Don't open browser
70
70
 
71
- Build/bundle options:
71
+ Build options:
72
72
  --out <dir> Output directory [env: KITFLY_BUILD_OUT] (default: dist)
73
- --name <file> Bundle filename (default: bundle.html)
74
73
  --no-raw Don't include raw markdown
75
74
 
75
+ Bundle options:
76
+ --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] (default: bundles)
77
+ --name <file> Bundle filename (default: bundle.html)
78
+ --no-raw Don't include raw markdown [env: KITFLY_BUNDLE_RAW]
79
+
76
80
  Stop options:
77
81
  --force Skip graceful shutdown, kill immediately
78
82
 
@@ -84,6 +88,7 @@ Examples:
84
88
  kitfly stop 4000
85
89
  kitfly stop all
86
90
  kitfly build ./docs --out ./public
91
+ kitfly bundle ./docs --out ./bundles --name docs.html
87
92
  kitfly init my-handbook
88
93
 
89
94
  Documentation: https://kitfly.app
@@ -647,6 +652,12 @@ describe("command argument defaults", () => {
647
652
  const name = (flags.name as string) || "bundle.html";
648
653
  expect(name).toBe("bundle.html");
649
654
  });
655
+
656
+ it("bundle defaults out to bundles", () => {
657
+ const { flags } = routeCommand(["bundle"]);
658
+ const out = (flags.out as string) || "bundles";
659
+ expect(out).toBe("bundles");
660
+ });
650
661
  });
651
662
 
652
663
  describe("daemon mode detection", () => {
@@ -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&quot;@1.0.0 &lt;x&gt;");
18
+ expect(html).not.toContain('bad"@1.0.0 <x>');
19
+ });
20
+ });
@@ -0,0 +1,5 @@
1
+ :::stat-grid
2
+ metrics:
3
+ - label: Users
4
+ value: 1,234
5
+ :::
@@ -0,0 +1,5 @@
1
+ :::kpi
2
+ label: A
3
+
4
+ value: B
5
+ :::
@@ -0,0 +1,9 @@
1
+ :::compare
2
+ left-title: "Build"
3
+ right-title: "Buy"
4
+ left:
5
+ - label: "Control"
6
+ value: "High"
7
+ right:
8
+ - "Fast to ship"
9
+ :::
@@ -0,0 +1,5 @@
1
+ :::flow-branching
2
+ branches:
3
+ - "API Handler"
4
+ - "Static Files"
5
+ :::
@@ -0,0 +1,6 @@
1
+ :::flow-converging
2
+ sources:
3
+ - "Frontend Logs"
4
+ - "API Logs"
5
+ merge: "Aggregator"
6
+ :::
@@ -0,0 +1,4 @@
1
+ :::kpi
2
+ label: A
3
+ value: B
4
+ :::
@@ -0,0 +1,5 @@
1
+ :::stat-grid
2
+ metrics:
3
+ - label: "Users"
4
+ - label: "Revenue"
5
+ :::
@@ -0,0 +1,3 @@
1
+ :::mystery
2
+ key: value
3
+ :::
@@ -0,0 +1,10 @@
1
+ :::compare
2
+ left-title: "Pros"
3
+ right-title: "Cons"
4
+ left:
5
+ - "Fast"
6
+ - "Simple"
7
+ right:
8
+ - "Limited"
9
+ - "Opinionated"
10
+ :::
@@ -0,0 +1,14 @@
1
+ :::comparison-table
2
+ headers:
3
+ - Feature
4
+ - Us
5
+ - Competitor A
6
+ - Competitor B
7
+ rows:
8
+ - ["Real-time sync", "Yes", "Yes", "No"]
9
+ - ["Offline mode", "Yes", "No", "Yes"]
10
+ - ["Plugin system", "Yes (v0.2.0)", "No", "Limited"]
11
+ - ["Self-hosted option", "Yes", "No", "No"]
12
+ - ["AI generation", "Yes", "Beta", "No"]
13
+ - ["Price (team/mo)", "$49", "$79", "$39"]
14
+ :::
@@ -0,0 +1,7 @@
1
+ :::flow-branching
2
+ source: "Incoming Request"
3
+ branches:
4
+ - "API Handler"
5
+ - "Static Files"
6
+ - "WebSocket"
7
+ :::
@@ -0,0 +1,8 @@
1
+ :::flow-branching
2
+ source: "Incoming Request"
3
+ split: "Route"
4
+ branches:
5
+ - "API Handler"
6
+ - "Static Files"
7
+ - "WebSocket"
8
+ :::
@@ -0,0 +1,7 @@
1
+ :::flow-converging
2
+ sources:
3
+ - "Frontend Logs"
4
+ - "API Logs"
5
+ - "DB Logs"
6
+ target: "Dashboard"
7
+ :::
@@ -0,0 +1,8 @@
1
+ :::flow-converging
2
+ sources:
3
+ - "Frontend Logs"
4
+ - "API Logs"
5
+ - "DB Logs"
6
+ merge: "Aggregator"
7
+ target: "Dashboard"
8
+ :::
@@ -0,0 +1,7 @@
1
+ :::funnel
2
+ stages:
3
+ - "Leads (1,200)"
4
+ - "Qualified (420)"
5
+ - "Proposals (120)"
6
+ - "Closed (47)"
7
+ :::
@@ -0,0 +1,5 @@
1
+ :::kpi
2
+ label: "Deals Closed"
3
+ value: "$8.1M"
4
+ trend: "+12%"
5
+ :::
@@ -0,0 +1,6 @@
1
+ :::layer-cake
2
+ layers:
3
+ - "Product"
4
+ - "Platform"
5
+ - "Infrastructure"
6
+ :::
@@ -0,0 +1,6 @@
1
+ :::pyramid
2
+ levels:
3
+ - "Vision"
4
+ - "Strategy"
5
+ - "Execution"
6
+ :::
@@ -0,0 +1,8 @@
1
+ :::quadrant-grid
2
+ axis-x: "Effort"
3
+ axis-y: "Impact"
4
+ tl: "Quick Wins"
5
+ tr: "Major Projects"
6
+ bl: "Fill-ins"
7
+ br: "Thankless Tasks"
8
+ :::
@@ -0,0 +1,13 @@
1
+ :::scorecard
2
+ metrics:
3
+ - label: "MRR"
4
+ value: "$240k"
5
+ trend: "+6%"
6
+ - label: "Churn"
7
+ value: "1.2%"
8
+ trend: "-0.3%"
9
+ - label: "NPS"
10
+ value: "48"
11
+ - label: "DAU"
12
+ value: "12,400"
13
+ :::
@@ -0,0 +1,7 @@
1
+ :::staircase
2
+ direction: down
3
+ steps:
4
+ - "Optimized"
5
+ - "Managed"
6
+ - "Defined"
7
+ :::
@@ -0,0 +1,8 @@
1
+ :::staircase
2
+ steps:
3
+ - "Ad Hoc"
4
+ - "Repeatable"
5
+ - "Defined"
6
+ - "Managed"
7
+ - "Optimized"
8
+ :::
@@ -0,0 +1,8 @@
1
+ :::stat-grid
2
+ metrics:
3
+ - label: Users
4
+ value: 1,234
5
+ - label: Uptime
6
+ value: 99.95%
7
+ trend: +0.3%
8
+ :::
@@ -0,0 +1,9 @@
1
+ :::timeline-horizontal
2
+ events:
3
+ - label: "Kickoff"
4
+ date: "Jan 2026"
5
+ - label: "Alpha"
6
+ date: "Mar 2026"
7
+ - label: "GA Release"
8
+ date: "Jun 2026"
9
+ :::
@@ -0,0 +1,10 @@
1
+ :::timeline-vertical
2
+ events:
3
+ - label: "Research Phase"
4
+ date: "Q1"
5
+ - label: "Prototype"
6
+ - label: "User Testing"
7
+ date: "Q3"
8
+ - label: "Launch"
9
+ date: "Q4"
10
+ :::
@@ -51,6 +51,7 @@ describe("template registry", () => {
51
51
  const ids = templates.map((t) => t.id);
52
52
 
53
53
  expect(ids).toContain("minimal");
54
+ expect(ids).toContain("deck");
54
55
  expect(ids).toContain("handbook");
55
56
  expect(templates.length).toBeGreaterThanOrEqual(2);
56
57
  });
@@ -257,6 +258,40 @@ describe("handbook template", () => {
257
258
  });
258
259
  });
259
260
 
261
+ // ---------------------------------------------------------------------------
262
+ // Deck Template (slides mode)
263
+ // ---------------------------------------------------------------------------
264
+
265
+ describe("deck template", () => {
266
+ const projectName = "test-deck";
267
+
268
+ it("creates slides-focused starter files", async () => {
269
+ await runTemplate({
270
+ name: projectName,
271
+ template: "deck",
272
+ git: false,
273
+ });
274
+
275
+ expect(generatedExists(projectName, "site.yaml")).toBe(true);
276
+ expect(generatedExists(projectName, "content/slides/briefing.md")).toBe(true);
277
+ expect(generatedExists(projectName, "CUSTOMIZING.md")).toBe(true);
278
+ });
279
+
280
+ it("configures site.yaml for slides mode", async () => {
281
+ await runTemplate({
282
+ name: projectName,
283
+ template: "deck",
284
+ git: false,
285
+ });
286
+
287
+ const siteYaml = await readGenerated(projectName, "site.yaml");
288
+ expect(siteYaml).toContain("mode: slides");
289
+ expect(siteYaml).toContain('aspect: "16/9"');
290
+ expect(siteYaml).toContain('name: "Slides"');
291
+ expect(siteYaml).toContain('path: "content/slides"');
292
+ });
293
+ });
294
+
260
295
  // ---------------------------------------------------------------------------
261
296
  // Custom Branding
262
297
  // ---------------------------------------------------------------------------
@@ -0,0 +1,221 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { describe, expect, it } from "vitest";
7
+ import {
8
+ loadPluginInjections,
9
+ loadPluginRegistry,
10
+ PluginConfigError,
11
+ PluginIntegrityError,
12
+ } from "../plugin-loader.ts";
13
+
14
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
15
+
16
+ function sha256Hex(text: string): string {
17
+ return createHash("sha256").update(new TextEncoder().encode(text)).digest("hex");
18
+ }
19
+
20
+ describe("plugin loader", () => {
21
+ it("inlines local assets with checksum verification", async () => {
22
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
23
+
24
+ await mkdir(join(root, "registry"), { recursive: true });
25
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
26
+
27
+ const js = "console.log('hello plugin');";
28
+ const css = ".hello{color:red;}";
29
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
30
+ await writeFile(join(root, "plugins-dist", "hello.css"), css, "utf-8");
31
+
32
+ const registryYaml = `version: 1
33
+ updated: "2026-02-12"
34
+ baseUrl: ""
35
+ plugins:
36
+ hello:
37
+ name: "Hello"
38
+ description: "Test plugin"
39
+ version: "1.0.0"
40
+ contract: "1"
41
+ kitfly: ">=0.2.0 <1.0.0"
42
+ license: MIT
43
+ verified: true
44
+ assets:
45
+ js: "plugins-dist/hello.js"
46
+ css: "plugins-dist/hello.css"
47
+ assetSha256:
48
+ js: "sha256:${sha256Hex(js)}"
49
+ css: "sha256:${sha256Hex(css)}"
50
+ `;
51
+
52
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
53
+
54
+ const configYaml = `plugins:
55
+ - hello@1.0.0
56
+ `;
57
+ await writeFile(join(root, "kitfly.plugins.yaml"), configYaml, "utf-8");
58
+
59
+ const injections = await loadPluginInjections({ root });
60
+ expect(injections.head).toContain(css);
61
+ expect(injections.bodyEnd).toContain(js);
62
+ });
63
+
64
+ it("respects plugin mode allowlist (modes)", async () => {
65
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
66
+
67
+ await mkdir(join(root, "registry"), { recursive: true });
68
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
69
+
70
+ const js = "console.log('hello plugin');";
71
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
72
+
73
+ const registryYaml = `version: 1
74
+ updated: "2026-02-12"
75
+ baseUrl: ""
76
+ plugins:
77
+ hello:
78
+ name: "Hello"
79
+ description: "Test plugin"
80
+ version: "1.0.0"
81
+ contract: "1"
82
+ kitfly: ">=0.2.0 <1.0.0"
83
+ license: MIT
84
+ verified: true
85
+ modes: ["slides"]
86
+ assets:
87
+ js: "plugins-dist/hello.js"
88
+ assetSha256:
89
+ js: "sha256:${sha256Hex(js)}"
90
+ `;
91
+
92
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
93
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
94
+
95
+ const docs = await loadPluginInjections({ root, mode: "docs" });
96
+ expect(docs.head).toBe("");
97
+ expect(docs.bodyEnd).toBe("");
98
+
99
+ const slides = await loadPluginInjections({ root, mode: "slides" });
100
+ expect(slides.bodyEnd).toContain(js);
101
+ });
102
+
103
+ it("treats empty modes as blocked", async () => {
104
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
105
+
106
+ await mkdir(join(root, "registry"), { recursive: true });
107
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
108
+
109
+ const js = "console.log('hello plugin');";
110
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
111
+
112
+ const registryYaml = `version: 1
113
+ updated: "2026-02-12"
114
+ baseUrl: ""
115
+ plugins:
116
+ hello:
117
+ name: "Hello"
118
+ description: "Test plugin"
119
+ version: "1.0.0"
120
+ contract: "1"
121
+ kitfly: ">=0.2.0 <1.0.0"
122
+ license: MIT
123
+ verified: true
124
+ modes: []
125
+ assets:
126
+ js: "plugins-dist/hello.js"
127
+ assetSha256:
128
+ js: "sha256:${sha256Hex(js)}"
129
+ `;
130
+
131
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
132
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
133
+
134
+ const injections = await loadPluginInjections({ root, mode: "slides" });
135
+ expect(injections.head).toBe("");
136
+ expect(injections.bodyEnd).toBe("");
137
+ });
138
+
139
+ it("throws on checksum mismatch", async () => {
140
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
141
+
142
+ await mkdir(join(root, "registry"), { recursive: true });
143
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
144
+
145
+ const js = "console.log('hello plugin');";
146
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
147
+
148
+ const registryYaml = `version: 1
149
+ updated: "2026-02-12"
150
+ baseUrl: ""
151
+ plugins:
152
+ hello:
153
+ name: "Hello"
154
+ description: "Test plugin"
155
+ version: "1.0.0"
156
+ contract: "1"
157
+ kitfly: ">=0.2.0 <1.0.0"
158
+ license: MIT
159
+ verified: true
160
+ assets:
161
+ js: "plugins-dist/hello.js"
162
+ assetSha256:
163
+ js: "sha256:${"0".repeat(64)}"
164
+ `;
165
+
166
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
167
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
168
+
169
+ await expect(loadPluginInjections({ root })).rejects.toBeInstanceOf(PluginIntegrityError);
170
+ });
171
+
172
+ it("rejects invalid canonical refs (prevents attribute injection)", async () => {
173
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
174
+ await mkdir(join(root, "registry"), { recursive: true });
175
+
176
+ await writeFile(
177
+ join(root, "registry", "plugins.yaml"),
178
+ `version: 1
179
+ updated: "2026-02-12"
180
+ baseUrl: ""
181
+ plugins: {}
182
+ `,
183
+ "utf-8",
184
+ );
185
+
186
+ await writeFile(
187
+ join(root, "kitfly.plugins.yaml"),
188
+ `plugins:
189
+ - bad"@1.0.0
190
+ `,
191
+ "utf-8",
192
+ );
193
+
194
+ await expect(loadPluginInjections({ root })).rejects.toBeInstanceOf(PluginConfigError);
195
+ });
196
+ });
197
+
198
+ describe("registry consistency", () => {
199
+ it("all plugin dist checksums match registry/plugins.yaml", async () => {
200
+ const registryPath = join(REPO_ROOT, "registry", "plugins.yaml");
201
+ const registry = await loadPluginRegistry(registryPath);
202
+
203
+ for (const [id, plugin] of Object.entries(registry.plugins)) {
204
+ const { assets } = plugin;
205
+ for (const kind of ["js", "css"] as const) {
206
+ const relPath = assets[kind];
207
+ if (!relPath) continue;
208
+
209
+ const expectedRaw = assets.assetSha256[kind];
210
+ if (!expectedRaw) throw new Error(`${id}: missing assetSha256.${kind}`);
211
+
212
+ const expectedHex = expectedRaw.replace(/^sha256:/, "").toLowerCase();
213
+ const filePath = join(REPO_ROOT, relPath);
214
+ const content = await readFile(filePath);
215
+ const actualHex = createHash("sha256").update(content).digest("hex");
216
+
217
+ expect(actualHex, `${id} ${kind} (${relPath})`).toBe(expectedHex);
218
+ }
219
+ }
220
+ });
221
+ });