@launchsecure/launch-kit 0.0.25 → 0.0.27

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 (132) hide show
  1. package/README.md +50 -0
  2. package/dist/beacon/beacon.mjs +1016 -0
  3. package/dist/beacon/beacon.mjs.map +1 -0
  4. package/dist/beacon/beacon.umd.js +87 -0
  5. package/dist/beacon/beacon.umd.js.map +1 -0
  6. package/dist/beacon/index-DAIDnjfR.mjs +513 -0
  7. package/dist/beacon/index-DAIDnjfR.mjs.map +1 -0
  8. package/dist/beacon/types/capture/element.d.ts +3 -0
  9. package/dist/beacon/types/capture/element.d.ts.map +1 -0
  10. package/dist/beacon/types/capture/framework.d.ts +3 -0
  11. package/dist/beacon/types/capture/framework.d.ts.map +1 -0
  12. package/dist/beacon/types/capture/metadata.d.ts +3 -0
  13. package/dist/beacon/types/capture/metadata.d.ts.map +1 -0
  14. package/dist/beacon/types/capture/overlay.d.ts +7 -0
  15. package/dist/beacon/types/capture/overlay.d.ts.map +1 -0
  16. package/dist/beacon/types/capture/picker.d.ts +12 -0
  17. package/dist/beacon/types/capture/picker.d.ts.map +1 -0
  18. package/dist/beacon/types/capture/screenshot.d.ts +7 -0
  19. package/dist/beacon/types/capture/screenshot.d.ts.map +1 -0
  20. package/dist/beacon/types/capture/selector.d.ts +2 -0
  21. package/dist/beacon/types/capture/selector.d.ts.map +1 -0
  22. package/dist/beacon/types/element.d.ts +50 -0
  23. package/dist/beacon/types/element.d.ts.map +1 -0
  24. package/dist/beacon/types/index.d.ts +4 -0
  25. package/dist/beacon/types/index.d.ts.map +1 -0
  26. package/dist/beacon/types/transport/submit.d.ts +3 -0
  27. package/dist/beacon/types/transport/submit.d.ts.map +1 -0
  28. package/dist/beacon/types/types.d.ts +88 -0
  29. package/dist/beacon/types/types.d.ts.map +1 -0
  30. package/dist/beacon/types/ui/button.d.ts +2 -0
  31. package/dist/beacon/types/ui/button.d.ts.map +1 -0
  32. package/dist/beacon/types/ui/drawer.d.ts +31 -0
  33. package/dist/beacon/types/ui/drawer.d.ts.map +1 -0
  34. package/dist/beacon/types/ui/icons.d.ts +9 -0
  35. package/dist/beacon/types/ui/icons.d.ts.map +1 -0
  36. package/dist/beacon/types/ui/pick-mode-overlay.d.ts +25 -0
  37. package/dist/beacon/types/ui/pick-mode-overlay.d.ts.map +1 -0
  38. package/dist/beacon/types/ui/pin-popover.d.ts +14 -0
  39. package/dist/beacon/types/ui/pin-popover.d.ts.map +1 -0
  40. package/dist/chart-client/assets/index-CJ4mgRRF.css +1 -0
  41. package/dist/chart-client/assets/{index-C8ANseEa.js → index-Ccy-DpI-.js} +82 -73
  42. package/dist/chart-client/index.html +2 -2
  43. package/dist/client/assets/index-DI5qSR_w.css +32 -0
  44. package/dist/client/assets/index-Dp0_okva.js +294 -0
  45. package/dist/client/index.html +2 -2
  46. package/dist/council-client/assets/index-C_-vAM9L.css +1 -0
  47. package/dist/council-client/assets/{index-Dc41S-R2.js → index-Dt4zWKSj.js} +14 -14
  48. package/dist/council-client/index.html +2 -2
  49. package/dist/deck-client/assets/{_baseUniq-2gclQXo7.js → _baseUniq-W2JQDmje.js} +1 -1
  50. package/dist/deck-client/assets/{arc-DcMY5Wm0.js → arc-DIBWAId9.js} +1 -1
  51. package/dist/deck-client/assets/{architectureDiagram-Q4EWVU46-B8iirmmJ.js → architectureDiagram-Q4EWVU46-CAIRMvJK.js} +1 -1
  52. package/dist/deck-client/assets/{blockDiagram-DXYQGD6D-B4JBLjmJ.js → blockDiagram-DXYQGD6D-BeNaNiOi.js} +1 -1
  53. package/dist/deck-client/assets/{c4Diagram-AHTNJAMY-CojrJAk8.js → c4Diagram-AHTNJAMY-B9Ozi62h.js} +1 -1
  54. package/dist/deck-client/assets/channel-CRdozqbp.js +1 -0
  55. package/dist/deck-client/assets/{chunk-4BX2VUAB-Bmb_BMDo.js → chunk-4BX2VUAB-D7AZ47dt.js} +1 -1
  56. package/dist/deck-client/assets/{chunk-4TB4RGXK-CumBy8qe.js → chunk-4TB4RGXK-DnVnNPcI.js} +1 -1
  57. package/dist/deck-client/assets/{chunk-55IACEB6-Ka8Hb1wD.js → chunk-55IACEB6-UKYs-YNd.js} +1 -1
  58. package/dist/deck-client/assets/{chunk-EDXVE4YY-B3sIPiQo.js → chunk-EDXVE4YY-D43b-SKn.js} +1 -1
  59. package/dist/deck-client/assets/{chunk-FMBD7UC4-C1tYkaqu.js → chunk-FMBD7UC4-QzBAoyyW.js} +1 -1
  60. package/dist/deck-client/assets/{chunk-OYMX7WX6-D7Wacbky.js → chunk-OYMX7WX6-Cjif4r6W.js} +1 -1
  61. package/dist/deck-client/assets/{chunk-QZHKN3VN-ChXI0vO3.js → chunk-QZHKN3VN-CqLDirEI.js} +1 -1
  62. package/dist/deck-client/assets/{chunk-YZCP3GAM-BXhiqf8u.js → chunk-YZCP3GAM-_FQvmMs4.js} +1 -1
  63. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-lIZMp57W.js +1 -0
  64. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-lIZMp57W.js +1 -0
  65. package/dist/deck-client/assets/clone-BtWeSTyJ.js +1 -0
  66. package/dist/deck-client/assets/{cose-bilkent-S5V4N54A-Bqp3p68D.js → cose-bilkent-S5V4N54A-rfrocesE.js} +1 -1
  67. package/dist/deck-client/assets/{dagre-KV5264BT-BS-rtyhZ.js → dagre-KV5264BT-Bv_7DJat.js} +1 -1
  68. package/dist/deck-client/assets/{diagram-5BDNPKRD-BIrj9YGI.js → diagram-5BDNPKRD-4F1414G5.js} +1 -1
  69. package/dist/deck-client/assets/{diagram-G4DWMVQ6-noHWPIg4.js → diagram-G4DWMVQ6-C4-Pszqm.js} +1 -1
  70. package/dist/deck-client/assets/{diagram-MMDJMWI5-C2qHxvqV.js → diagram-MMDJMWI5-B647TIx9.js} +1 -1
  71. package/dist/deck-client/assets/{diagram-TYMM5635-BytnGQr-.js → diagram-TYMM5635-BFAqpezd.js} +1 -1
  72. package/dist/deck-client/assets/{erDiagram-SMLLAGMA-BfK5m2YQ.js → erDiagram-SMLLAGMA-BfBfrJOC.js} +1 -1
  73. package/dist/deck-client/assets/{flowDiagram-DWJPFMVM-Cq925G1Z.js → flowDiagram-DWJPFMVM-DX9YAYes.js} +1 -1
  74. package/dist/deck-client/assets/{ganttDiagram-T4ZO3ILL-DhhHPAmj.js → ganttDiagram-T4ZO3ILL-DCuiy7wF.js} +1 -1
  75. package/dist/deck-client/assets/{gitGraphDiagram-UUTBAWPF-B3Lc0h9q.js → gitGraphDiagram-UUTBAWPF-CGp1IXUh.js} +1 -1
  76. package/dist/deck-client/assets/{graph-RTawgVWm.js → graph-B7g8aoxv.js} +1 -1
  77. package/dist/deck-client/assets/{index-BfIfJXmS.js → index-Dg1r-WSN.js} +68 -68
  78. package/dist/deck-client/assets/index-DsIZ3LqL.css +1 -0
  79. package/dist/deck-client/assets/{infoDiagram-42DDH7IO-BlR584kX.js → infoDiagram-42DDH7IO-L3fahMkF.js} +1 -1
  80. package/dist/deck-client/assets/{ishikawaDiagram-UXIWVN3A-DygKoNGY.js → ishikawaDiagram-UXIWVN3A-aS_EjWBZ.js} +1 -1
  81. package/dist/deck-client/assets/{journeyDiagram-VCZTEJTY-BnaiYp9N.js → journeyDiagram-VCZTEJTY-djTSQZF9.js} +1 -1
  82. package/dist/deck-client/assets/{kanban-definition-6JOO6SKY-BQBUBzJC.js → kanban-definition-6JOO6SKY-CcTHo4CM.js} +1 -1
  83. package/dist/deck-client/assets/{layout-DeZ8HI1T.js → layout-mEJiadb7.js} +1 -1
  84. package/dist/deck-client/assets/{linear-C6roLi_9.js → linear-XgTKqyRu.js} +1 -1
  85. package/dist/deck-client/assets/{min-CbUksbuI.js → min-Ct9jZdpd.js} +1 -1
  86. package/dist/deck-client/assets/{mindmap-definition-QFDTVHPH-iNxV62yN.js → mindmap-definition-QFDTVHPH-BaFxCGNU.js} +1 -1
  87. package/dist/deck-client/assets/{pieDiagram-DEJITSTG-DHVA0jaG.js → pieDiagram-DEJITSTG-CIbYYjtw.js} +1 -1
  88. package/dist/deck-client/assets/{quadrantDiagram-34T5L4WZ-DBeKKLUQ.js → quadrantDiagram-34T5L4WZ-D9EtCOvh.js} +1 -1
  89. package/dist/deck-client/assets/{requirementDiagram-MS252O5E-CBwITx7p.js → requirementDiagram-MS252O5E-xeni9eVG.js} +1 -1
  90. package/dist/deck-client/assets/{sankeyDiagram-XADWPNL6-BtE-1YTU.js → sankeyDiagram-XADWPNL6-LYeknz9h.js} +1 -1
  91. package/dist/deck-client/assets/{sequenceDiagram-FGHM5R23-DN96yPP2.js → sequenceDiagram-FGHM5R23-RDbsKFZf.js} +1 -1
  92. package/dist/deck-client/assets/{stateDiagram-FHFEXIEX-VUkKC2uJ.js → stateDiagram-FHFEXIEX-BH1Zjglk.js} +1 -1
  93. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-BrV78NDR.js +1 -0
  94. package/dist/deck-client/assets/{timeline-definition-GMOUNBTQ-oUeZhRns.js → timeline-definition-GMOUNBTQ-IFXxKptt.js} +1 -1
  95. package/dist/deck-client/assets/{vennDiagram-DHZGUBPP-D87fK90n.js → vennDiagram-DHZGUBPP-D-sLkQs9.js} +1 -1
  96. package/dist/deck-client/assets/wardley-RL74JXVD-C010F8l4.js +162 -0
  97. package/dist/deck-client/assets/{wardleyDiagram-NUSXRM2D-Ca_i0QRA.js → wardleyDiagram-NUSXRM2D-BTjjuDU3.js} +1 -1
  98. package/dist/deck-client/assets/{xychartDiagram-5P7HB3ND-CUOJVIvq.js → xychartDiagram-5P7HB3ND-AYbv92n-.js} +1 -1
  99. package/dist/deck-client/index.html +2 -2
  100. package/dist/server/chart-serve.js +4524 -3564
  101. package/dist/server/cli.js +27351 -5398
  102. package/dist/server/council-entry.js +17 -5
  103. package/dist/server/council-serve.js +8 -3
  104. package/dist/server/deck-mcp-entry.js +354 -13
  105. package/dist/server/deck-serve.js +298 -7
  106. package/dist/server/graph/queries/classify.scm +8 -0
  107. package/dist/server/graph/queries/exports.scm +7 -0
  108. package/dist/server/graph-mcp-entry.js +5943 -4361
  109. package/dist/server/init-entry.js +609 -0
  110. package/dist/server/orbit-entry.js +2272 -0
  111. package/dist/server/{server/chart-serve.js → parse-worker-entry.js} +1900 -1822
  112. package/dist/server/recall-entry.js +1450 -0
  113. package/package.json +40 -8
  114. package/scaffolds/migrate-safety/.github/workflows/backup-on-migration.yml +72 -0
  115. package/scaffolds/migrate-safety/docs/migrations-runbook.md +172 -0
  116. package/scaffolds/migrate-safety/scripts/migrate-with-backup.sh +294 -0
  117. package/dist/chart-client/assets/index--120d9P9.css +0 -1
  118. package/dist/client/assets/index-Bf8zdL3x.css +0 -32
  119. package/dist/client/assets/index-Ds9UP_cj.js +0 -291
  120. package/dist/council-client/assets/index-CofZh7pS.css +0 -1
  121. package/dist/deck-client/assets/channel-ERh5jKXV.js +0 -1
  122. package/dist/deck-client/assets/classDiagram-6PBFFD2Q-CMi1Gaev.js +0 -1
  123. package/dist/deck-client/assets/classDiagram-v2-HSJHXN6E-CMi1Gaev.js +0 -1
  124. package/dist/deck-client/assets/clone-DfWhlD4X.js +0 -1
  125. package/dist/deck-client/assets/index-765AIQ9z.css +0 -1
  126. package/dist/deck-client/assets/stateDiagram-v2-QKLJ7IA2-CA0IjulK.js +0 -1
  127. package/dist/deck-client/assets/wardley-RL74JXVD-DYbYcpDp.js +0 -162
  128. package/dist/server/deck-server/deck-mcp-entry.js +0 -1789
  129. package/dist/server/deck-server/deck-serve.js +0 -1275
  130. package/dist/server/server/cli.js +0 -13360
  131. package/dist/server/server/fb-wizard.js +0 -136
  132. package/dist/server/server/graph-mcp-entry.js +0 -6776
@@ -31,9 +31,18 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  ));
32
32
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
33
 
34
+ // src/server/launch-kit-paths.ts
35
+ var LAUNCHSECURE_DIR;
36
+ var init_launch_kit_paths = __esm({
37
+ "src/server/launch-kit-paths.ts"() {
38
+ "use strict";
39
+ LAUNCHSECURE_DIR = ".launchsecure";
40
+ }
41
+ });
42
+
34
43
  // src/server/council-lockfile.ts
35
44
  function lockDir(projectRoot) {
36
- return projectRoot ? (0, import_node_path.join)(projectRoot, ".launchsecure") : (0, import_node_path.join)((0, import_node_os.homedir)(), ".launchsecure");
45
+ return projectRoot ? (0, import_node_path.join)(projectRoot, LAUNCHSECURE_DIR) : (0, import_node_path.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR);
37
46
  }
38
47
  function lockPath(projectRoot) {
39
48
  return (0, import_node_path.join)(lockDir(projectRoot), "launch-council.lock");
@@ -111,6 +120,7 @@ var init_council_lockfile = __esm({
111
120
  import_node_fs = require("node:fs");
112
121
  import_node_os = require("node:os");
113
122
  import_node_path = require("node:path");
123
+ init_launch_kit_paths();
114
124
  }
115
125
  });
116
126
 
@@ -446,7 +456,7 @@ Be specific with search terms \u2014 use actual names that would appear in code
446
456
 
447
457
  // src/server/graph-reader.ts
448
458
  function readGraphLayer(projectRoot, layer) {
449
- const filePath = (0, import_node_path4.join)(projectRoot, GRAPH_DIR, `${layer}.json`);
459
+ const filePath = (0, import_node_path4.join)(projectRoot, LAUNCHSECURE_DIR, GRAPHS_SUBDIR, `${layer}.json`);
450
460
  if (!(0, import_node_fs4.existsSync)(filePath)) return null;
451
461
  try {
452
462
  return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
@@ -503,13 +513,14 @@ function searchGraph(projectRoot, searchTerms, layers = ["ui", "api", "db"]) {
503
513
  layers: usedLayers
504
514
  };
505
515
  }
506
- var import_node_fs4, import_node_path4, GRAPH_DIR;
516
+ var import_node_fs4, import_node_path4, GRAPHS_SUBDIR;
507
517
  var init_graph_reader = __esm({
508
518
  "src/server/graph-reader.ts"() {
509
519
  "use strict";
510
520
  import_node_fs4 = require("node:fs");
511
521
  import_node_path4 = require("node:path");
512
- GRAPH_DIR = ".launchsecure/graphs";
522
+ init_launch_kit_paths();
523
+ GRAPHS_SUBDIR = "graphs";
513
524
  }
514
525
  });
515
526
 
@@ -1186,7 +1197,7 @@ async function handleTool(name, args) {
1186
1197
  }));
1187
1198
  }
1188
1199
  try {
1189
- const logDir = (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".launchsecure");
1200
+ const logDir = (0, import_node_path6.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR);
1190
1201
  (0, import_node_fs6.mkdirSync)(logDir, { recursive: true });
1191
1202
  const logPath = (0, import_node_path6.join)(logDir, "launch-council.log");
1192
1203
  const out = (0, import_node_fs6.openSync)(logPath, "a");
@@ -1326,6 +1337,7 @@ var init_council_mcp = __esm({
1326
1337
  import_node_child_process3 = require("node:child_process");
1327
1338
  import_node_fs6 = require("node:fs");
1328
1339
  import_node_os2 = require("node:os");
1340
+ init_launch_kit_paths();
1329
1341
  init_council_lockfile();
1330
1342
  init_council_config();
1331
1343
  SERVER_INFO = {
@@ -152,8 +152,13 @@ var import_node_child_process = require("node:child_process");
152
152
  var import_node_fs = require("node:fs");
153
153
  var import_node_os = require("node:os");
154
154
  var import_node_path = require("node:path");
155
+
156
+ // src/server/launch-kit-paths.ts
157
+ var LAUNCHSECURE_DIR = ".launchsecure";
158
+
159
+ // src/server/council-lockfile.ts
155
160
  function lockDir(projectRoot) {
156
- return projectRoot ? (0, import_node_path.join)(projectRoot, ".launchsecure") : (0, import_node_path.join)((0, import_node_os.homedir)(), ".launchsecure");
161
+ return projectRoot ? (0, import_node_path.join)(projectRoot, LAUNCHSECURE_DIR) : (0, import_node_path.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR);
157
162
  }
158
163
  function lockPath(projectRoot) {
159
164
  return (0, import_node_path.join)(lockDir(projectRoot), "launch-council.lock");
@@ -531,9 +536,9 @@ async function extractIntent(adapter, discussionContent) {
531
536
  // src/server/graph-reader.ts
532
537
  var import_node_fs4 = require("node:fs");
533
538
  var import_node_path4 = require("node:path");
534
- var GRAPH_DIR = ".launchsecure/graphs";
539
+ var GRAPHS_SUBDIR = "graphs";
535
540
  function readGraphLayer(projectRoot, layer) {
536
- const filePath = (0, import_node_path4.join)(projectRoot, GRAPH_DIR, `${layer}.json`);
541
+ const filePath = (0, import_node_path4.join)(projectRoot, LAUNCHSECURE_DIR, GRAPHS_SUBDIR, `${layer}.json`);
537
542
  if (!(0, import_node_fs4.existsSync)(filePath)) return null;
538
543
  try {
539
544
  return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
@@ -30,12 +30,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
+ // src/server/launch-kit-paths.ts
34
+ var LAUNCHSECURE_DIR;
35
+ var init_launch_kit_paths = __esm({
36
+ "src/server/launch-kit-paths.ts"() {
37
+ "use strict";
38
+ LAUNCHSECURE_DIR = ".launchsecure";
39
+ }
40
+ });
41
+
33
42
  // src/server/deck-lockfile.ts
34
43
  function lockDir(projectRoot) {
35
44
  if (projectRoot) {
36
- return (0, import_node_path.join)(projectRoot, ".launchsecure");
45
+ return (0, import_node_path.join)(projectRoot, LAUNCHSECURE_DIR);
37
46
  }
38
- return (0, import_node_path.join)((0, import_node_os.homedir)(), ".launchsecure");
47
+ return (0, import_node_path.join)((0, import_node_os.homedir)(), LAUNCHSECURE_DIR);
39
48
  }
40
49
  function lockPath(projectRoot) {
41
50
  return (0, import_node_path.join)(lockDir(projectRoot), "launch-deck.lock");
@@ -113,6 +122,7 @@ var init_deck_lockfile = __esm({
113
122
  import_node_fs = require("node:fs");
114
123
  import_node_os = require("node:os");
115
124
  import_node_path = require("node:path");
125
+ init_launch_kit_paths();
116
126
  }
117
127
  });
118
128
 
@@ -892,6 +902,277 @@ var init_contract_generator = __esm({
892
902
  }
893
903
  });
894
904
 
905
+ // src/server/rich-html-render.ts
906
+ function renderRichHtml(opts) {
907
+ const theme = opts.theme ?? "light";
908
+ const title = escapeHtml(opts.title ?? "LaunchDeck");
909
+ if (opts.framework === "wired") {
910
+ return renderWired(opts.content, theme, title);
911
+ }
912
+ if (opts.framework === "reveal") {
913
+ return renderReveal(opts.content, theme, title);
914
+ }
915
+ return opts.content;
916
+ }
917
+ function themeResolverScript(initial) {
918
+ return `<script>
919
+ (function () {
920
+ function applyTheme(t) {
921
+ if (t !== 'light' && t !== 'dark') return;
922
+ var prev = document.documentElement.dataset.theme;
923
+ document.documentElement.dataset.theme = t;
924
+ if (prev !== t) {
925
+ document.dispatchEvent(new CustomEvent('deck-theme', { detail: t }));
926
+ }
927
+ }
928
+ // 1) initial preference: URL > prefers-color-scheme (standalone) > baked-in default
929
+ var url = new URL(window.location.href);
930
+ var fromUrl = url.searchParams.get('theme');
931
+ if (fromUrl === 'light' || fromUrl === 'dark') {
932
+ applyTheme(fromUrl);
933
+ } else if (window.parent === window) {
934
+ var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
935
+ applyTheme(prefersDark ? 'dark' : 'light');
936
+ } else {
937
+ applyTheme(${JSON.stringify(initial)});
938
+ }
939
+ // 2) live updates from parent deck
940
+ window.addEventListener('message', function (e) {
941
+ if (e.data && e.data.type === 'deck-theme') applyTheme(e.data.theme);
942
+ });
943
+ // 3) tell parent we are ready so it can push the current theme back
944
+ if (window.parent !== window) {
945
+ window.parent.postMessage({ type: 'deck-iframe-ready' }, '*');
946
+ }
947
+ })();
948
+ </script>`;
949
+ }
950
+ function renderWired(body, initialTheme, title) {
951
+ const LIGHT = { bg: "#ffffff", fg: "#18181b", accent: "#7c3aed", cardBg: "#ffffff" };
952
+ const DARK = { bg: "#0e0e10", fg: "#e4e4e7", accent: "#a78bfa", cardBg: "#1a1a1d" };
953
+ const wiredVarsBlock = (vars) => `
954
+ --wired-card-background-fill: ${vars.cardBg};
955
+ --wired-button-color: ${vars.fg};
956
+ --wired-combo-popup-bg: ${vars.cardBg};
957
+ --wired-progress-color: ${vars.accent};
958
+ --wired-progress-label-background: ${vars.cardBg};
959
+ --wired-progress-label-color: ${vars.fg};
960
+ `;
961
+ return `<!DOCTYPE html>
962
+ <html lang="en" data-theme="${initialTheme}">
963
+ <head>
964
+ <meta charset="utf-8">
965
+ <meta name="viewport" content="width=device-width, initial-scale=1">
966
+ <title>${title}</title>
967
+ <script type="module" src="${WIRED_CDN}"></script>
968
+ <link href="https://fonts.googleapis.com/css2?family=Gloria+Hallelujah&family=DM+Sans:wght@400;600;700&display=swap" rel="stylesheet">
969
+ <style>
970
+ *, *::before, *::after { box-sizing: border-box; }
971
+ html, body {
972
+ margin: 0;
973
+ min-height: 100%;
974
+ font-family: 'Gloria Hallelujah', 'DM Sans', system-ui, sans-serif;
975
+ background: var(--page-bg);
976
+ color: var(--page-fg);
977
+ transition: background 0.15s, color 0.15s;
978
+ }
979
+ body { padding: 24px; }
980
+ h1, h2, h3, h4 { font-family: 'Gloria Hallelujah', cursive; margin: 0.5em 0; color: var(--page-fg); }
981
+ a { color: var(--page-accent); }
982
+ .row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin: 8px 0; }
983
+ .col { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
984
+ .card { padding: 16px; }
985
+
986
+ /* Light theme */
987
+ :root[data-theme="light"] {
988
+ --page-bg: ${LIGHT.bg};
989
+ --page-fg: ${LIGHT.fg};
990
+ --page-accent: ${LIGHT.accent};
991
+ ${wiredVarsBlock(LIGHT)}
992
+ }
993
+
994
+ /* Dark theme */
995
+ :root[data-theme="dark"] {
996
+ --page-bg: ${DARK.bg};
997
+ --page-fg: ${DARK.fg};
998
+ --page-accent: ${DARK.accent};
999
+ ${wiredVarsBlock(DARK)}
1000
+ }
1001
+
1002
+ /* Hosts: text color inherits via component "color: inherit" rules.
1003
+ Transparent background lets the page bg show through where the
1004
+ rough.js rectangle leaves space. */
1005
+ wired-button, wired-card, wired-checkbox, wired-radio, wired-toggle,
1006
+ wired-input, wired-textarea, wired-search-input, wired-combo, wired-item,
1007
+ wired-listbox, wired-link, wired-tabs, wired-tab, wired-fab,
1008
+ wired-icon-button, wired-divider, wired-progress, wired-slider,
1009
+ wired-calendar, wired-dialog {
1010
+ color: var(--page-fg);
1011
+ background: transparent;
1012
+ }
1013
+ </style>
1014
+ </head>
1015
+ <body>
1016
+ ${body}
1017
+ ${themeResolverScript(initialTheme)}
1018
+ <script type="module">
1019
+ // wired-input / wired-textarea / wired-search-input render their native
1020
+ // <input>/<textarea> INSIDE shadow DOM. Outer CSS cannot reach those, and
1021
+ // the native control has appearance:auto which the UA repaints over any
1022
+ // "background: transparent" rule. We must set inline styles directly on
1023
+ // the inner element with !important.
1024
+ //
1025
+ // We register one applier per theme. Re-runs when theme changes or new
1026
+ // nodes are added so the page reacts live to the deck sun/moon toggle.
1027
+ const FG = { light: ${JSON.stringify(LIGHT.fg)}, dark: ${JSON.stringify(DARK.fg)} };
1028
+ const TAGS = ['wired-input', 'wired-textarea', 'wired-search-input'];
1029
+ const DARK_PROPS = [
1030
+ 'appearance', '-webkit-appearance',
1031
+ 'background', 'background-color',
1032
+ 'color', '-webkit-text-fill-color', 'caret-color',
1033
+ 'border', 'outline',
1034
+ ];
1035
+
1036
+ function applyDark(node) {
1037
+ const s = node.style;
1038
+ s.setProperty('appearance', 'none', 'important');
1039
+ s.setProperty('-webkit-appearance', 'none', 'important');
1040
+ s.setProperty('background', 'transparent', 'important');
1041
+ s.setProperty('background-color', 'transparent', 'important');
1042
+ s.setProperty('color', FG.dark, 'important');
1043
+ s.setProperty('-webkit-text-fill-color', FG.dark, 'important');
1044
+ s.setProperty('caret-color', FG.dark, 'important');
1045
+ s.setProperty('border', 'none', 'important');
1046
+ s.setProperty('outline', 'none', 'important');
1047
+ }
1048
+
1049
+ function applyLight(node) {
1050
+ DARK_PROPS.forEach((p) => node.style.removeProperty(p));
1051
+ }
1052
+
1053
+ function placeholderStyle(host, theme) {
1054
+ const existing = host.shadowRoot.querySelector('style[data-deck-theme]');
1055
+ if (existing) existing.remove();
1056
+ if (theme !== 'dark') return;
1057
+ const style = document.createElement('style');
1058
+ style.dataset.deckTheme = '1';
1059
+ style.textContent =
1060
+ '::placeholder{color:' + FG.dark + '99 !important;' +
1061
+ '-webkit-text-fill-color:' + FG.dark + '99 !important;}';
1062
+ host.shadowRoot.appendChild(style);
1063
+ }
1064
+
1065
+ function sweepOnce() {
1066
+ const theme = document.documentElement.dataset.theme;
1067
+ for (const tag of TAGS) {
1068
+ for (const host of document.querySelectorAll(tag)) {
1069
+ if (!host.shadowRoot) continue;
1070
+ const inner = host.shadowRoot.querySelector('input, textarea');
1071
+ if (!inner) continue;
1072
+ if (theme === 'dark') applyDark(inner);
1073
+ else applyLight(inner);
1074
+ placeholderStyle(host, theme);
1075
+ }
1076
+ }
1077
+ }
1078
+
1079
+ // Initial + late-upgrade catch (CDN script loads async).
1080
+ sweepOnce();
1081
+ let elapsed = 0;
1082
+ function tick() {
1083
+ sweepOnce();
1084
+ elapsed += 100;
1085
+ if (elapsed < 3000) setTimeout(tick, 100);
1086
+ }
1087
+ setTimeout(tick, 100);
1088
+
1089
+ // Re-sweep on theme change.
1090
+ document.addEventListener('deck-theme', sweepOnce);
1091
+ // Re-sweep when new nodes are added.
1092
+ new MutationObserver(sweepOnce).observe(document.body, { subtree: true, childList: true });
1093
+
1094
+ // whenDefined fires when each tag is registered \u2014 allSettled-style, never hangs.
1095
+ TAGS.forEach((tag) => {
1096
+ customElements.whenDefined(tag).then(sweepOnce).catch(() => {});
1097
+ });
1098
+ </script>
1099
+ </body>
1100
+ </html>`;
1101
+ }
1102
+ function renderReveal(content, initialTheme, title) {
1103
+ const slides = content.includes("<section") ? content : content.split(/\n\s*---\s*\n/).map((md) => `<section data-markdown><textarea data-template>
1104
+ ${md.trim()}
1105
+ </textarea></section>`).join("\n");
1106
+ return `<!DOCTYPE html>
1107
+ <html lang="en" data-theme="${initialTheme}">
1108
+ <head>
1109
+ <meta charset="utf-8">
1110
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1111
+ <title>${title}</title>
1112
+ <link rel="stylesheet" href="${REVEAL_CSS}">
1113
+ <link id="reveal-theme-light" rel="stylesheet" href="${REVEAL_THEME("light")}" ${initialTheme === "dark" ? "disabled" : ""}>
1114
+ <link id="reveal-theme-dark" rel="stylesheet" href="${REVEAL_THEME("dark")}" ${initialTheme === "dark" ? "" : "disabled"}>
1115
+ <style>
1116
+ html, body { margin: 0; height: 100%; }
1117
+ .reveal .slides { text-align: left; }
1118
+ .reveal .slides section { padding: 0 32px; }
1119
+ </style>
1120
+ </head>
1121
+ <body>
1122
+ <div class="reveal">
1123
+ <div class="slides">
1124
+ ${slides}
1125
+ </div>
1126
+ </div>
1127
+ <script src="${REVEAL_JS}"></script>
1128
+ <script src="${REVEAL_MD}"></script>
1129
+ <script src="${REVEAL_HL}"></script>
1130
+ <script src="${REVEAL_NOTES}"></script>
1131
+ <script>
1132
+ Reveal.initialize({
1133
+ hash: true,
1134
+ slideNumber: 'c/t',
1135
+ controls: true,
1136
+ progress: true,
1137
+ transition: 'slide',
1138
+ plugins: [RevealMarkdown, RevealHighlight, RevealNotes],
1139
+ });
1140
+ // React to theme changes: flip which stylesheet is enabled.
1141
+ function syncRevealTheme() {
1142
+ var dark = document.documentElement.dataset.theme === 'dark';
1143
+ var lightLink = document.getElementById('reveal-theme-light');
1144
+ var darkLink = document.getElementById('reveal-theme-dark');
1145
+ if (lightLink) lightLink.disabled = dark;
1146
+ if (darkLink) darkLink.disabled = !dark;
1147
+ }
1148
+ document.addEventListener('deck-theme', syncRevealTheme);
1149
+ syncRevealTheme();
1150
+ </script>
1151
+ ${themeResolverScript(initialTheme)}
1152
+ </body>
1153
+ </html>`;
1154
+ }
1155
+ function escapeHtml(s) {
1156
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1157
+ }
1158
+ function slugify(label) {
1159
+ return label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "block";
1160
+ }
1161
+ var WIRED_CDN, REVEAL_VER, REVEAL_CSS, REVEAL_THEME, REVEAL_JS, REVEAL_MD, REVEAL_HL, REVEAL_NOTES;
1162
+ var init_rich_html_render = __esm({
1163
+ "src/server/rich-html-render.ts"() {
1164
+ "use strict";
1165
+ WIRED_CDN = "https://unpkg.com/wired-elements?module";
1166
+ REVEAL_VER = "5.1.0";
1167
+ REVEAL_CSS = `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/dist/reveal.css`;
1168
+ REVEAL_THEME = (t) => `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/dist/theme/${t === "dark" ? "black" : "white"}.css`;
1169
+ REVEAL_JS = `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/dist/reveal.js`;
1170
+ REVEAL_MD = `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/plugin/markdown/markdown.js`;
1171
+ REVEAL_HL = `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/plugin/highlight/highlight.js`;
1172
+ REVEAL_NOTES = `https://cdn.jsdelivr.net/npm/reveal.js@${REVEAL_VER}/plugin/notes/notes.js`;
1173
+ }
1174
+ });
1175
+
895
1176
  // src/server/deck-serve.ts
896
1177
  var deck_serve_exports = {};
897
1178
  __export(deck_serve_exports, {
@@ -1051,7 +1332,7 @@ async function startDeckServer(opts = {}) {
1051
1332
  const baseUrl = `/deck-files/${sessionEncoded}`;
1052
1333
  const html = generateBlastRadiusHtml(block.manifest, baseUrl);
1053
1334
  const report = generateBlastRadiusReport(block.manifest);
1054
- const dir = import_node_path3.default.join(cwd, ".launchsecure", "deck-files", body.session);
1335
+ const dir = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1055
1336
  import_node_fs3.default.mkdirSync(dir, { recursive: true });
1056
1337
  import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "blast-radius.html"), html);
1057
1338
  import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, "blast-radius-report.md"), report);
@@ -1067,6 +1348,29 @@ async function startDeckServer(opts = {}) {
1067
1348
  console.error("Failed to generate blast radius HTML:", err);
1068
1349
  }
1069
1350
  }
1351
+ if (block.type === "rich-html" && block.content) {
1352
+ try {
1353
+ const html = renderRichHtml({
1354
+ framework: block.framework ?? "raw",
1355
+ content: block.content,
1356
+ theme: block.theme,
1357
+ title: block.label
1358
+ });
1359
+ const sessionEncoded = encodeURIComponent(body.session);
1360
+ const baseUrl = `/deck-files/${sessionEncoded}`;
1361
+ const filename = `${slugify(block.label)}.html`;
1362
+ const dir = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", body.session);
1363
+ import_node_fs3.default.mkdirSync(dir, { recursive: true });
1364
+ import_node_fs3.default.writeFileSync(import_node_path3.default.join(dir, filename), html);
1365
+ block.type = "iframe";
1366
+ block.src = `${baseUrl}/${filename}`;
1367
+ delete block.content;
1368
+ delete block.framework;
1369
+ delete block.theme;
1370
+ } catch (err) {
1371
+ console.error("Failed to generate rich-html:", err);
1372
+ }
1373
+ }
1070
1374
  }
1071
1375
  broadcastToClients({
1072
1376
  type: "session",
@@ -1103,7 +1407,7 @@ async function startDeckServer(opts = {}) {
1103
1407
  resolveFeedback(session, { comment: "", closed: true });
1104
1408
  }
1105
1409
  try {
1106
- const deckFilesBase = import_node_path3.default.join(cwd, ".launchsecure", "deck-files");
1410
+ const deckFilesBase = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1107
1411
  if (session === "all") {
1108
1412
  if (import_node_fs3.default.existsSync(deckFilesBase)) {
1109
1413
  import_node_fs3.default.rmSync(deckFilesBase, { recursive: true, force: true });
@@ -1131,7 +1435,7 @@ async function startDeckServer(opts = {}) {
1131
1435
  res.end("Forbidden");
1132
1436
  return;
1133
1437
  }
1134
- const filePath = import_node_path3.default.join(cwd, ".launchsecure", "deck-files", relative);
1438
+ const filePath = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1135
1439
  if (serveStatic(res, filePath)) return;
1136
1440
  res.writeHead(404);
1137
1441
  res.end("Not found");
@@ -1144,7 +1448,7 @@ async function startDeckServer(opts = {}) {
1144
1448
  res.end("Forbidden");
1145
1449
  return;
1146
1450
  }
1147
- const filePath = import_node_path3.default.join(cwd, ".launchsecure", "deck-files", relative);
1451
+ const filePath = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files", relative);
1148
1452
  if (!import_node_fs3.default.existsSync(filePath) || !import_node_fs3.default.statSync(filePath).isFile()) {
1149
1453
  res.writeHead(404);
1150
1454
  res.end("Not found");
@@ -1229,7 +1533,7 @@ async function startDeckServer(opts = {}) {
1229
1533
  pendingFeedback.delete(id);
1230
1534
  }
1231
1535
  try {
1232
- const deckFilesBase = import_node_path3.default.join(cwd, ".launchsecure", "deck-files");
1536
+ const deckFilesBase = import_node_path3.default.join(cwd, LAUNCHSECURE_DIR, "deck-files");
1233
1537
  if (import_node_fs3.default.existsSync(deckFilesBase)) {
1234
1538
  import_node_fs3.default.rmSync(deckFilesBase, { recursive: true, force: true });
1235
1539
  }
@@ -1276,10 +1580,12 @@ var init_deck_serve = __esm({
1276
1580
  import_node_fs3 = __toESM(require("node:fs"));
1277
1581
  import_node_path3 = __toESM(require("node:path"));
1278
1582
  import_ws = require("ws");
1583
+ init_launch_kit_paths();
1279
1584
  init_deck_lockfile();
1280
1585
  init_deck_config();
1281
1586
  init_blast_radius_render();
1282
1587
  init_contract_generator();
1588
+ init_rich_html_render();
1283
1589
  DEFAULT_PORT = 52829;
1284
1590
  MAX_PORT_SCAN = 3;
1285
1591
  MIME_TYPES = {
@@ -1365,7 +1671,7 @@ async function handleTool(name, args) {
1365
1671
  const baseUrl = `/deck-files/${sessionEncoded}`;
1366
1672
  const html = generateBlastRadiusHtml(block.manifest, baseUrl);
1367
1673
  const report = generateBlastRadiusReport(block.manifest);
1368
- const dir = (0, import_node_path4.join)(projectRoot, ".launchsecure", "deck-files", session);
1674
+ const dir = (0, import_node_path4.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
1369
1675
  import_node_fs4.default.mkdirSync(dir, { recursive: true });
1370
1676
  import_node_fs4.default.writeFileSync((0, import_node_path4.join)(dir, "blast-radius.html"), html);
1371
1677
  import_node_fs4.default.writeFileSync((0, import_node_path4.join)(dir, "blast-radius-report.md"), report);
@@ -1381,6 +1687,29 @@ async function handleTool(name, args) {
1381
1687
  return text(JSON.stringify({ error: `Failed to generate blast radius HTML: ${err}` }));
1382
1688
  }
1383
1689
  }
1690
+ if (block.type === "rich-html" && block.content) {
1691
+ try {
1692
+ const html = renderRichHtml({
1693
+ framework: block.framework ?? "raw",
1694
+ content: block.content,
1695
+ theme: block.theme,
1696
+ title: block.label
1697
+ });
1698
+ const sessionEncoded = encodeURIComponent(session);
1699
+ const baseUrl = `/deck-files/${sessionEncoded}`;
1700
+ const filename = `${slugify(block.label)}.html`;
1701
+ const dir = (0, import_node_path4.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
1702
+ import_node_fs4.default.mkdirSync(dir, { recursive: true });
1703
+ import_node_fs4.default.writeFileSync((0, import_node_path4.join)(dir, filename), html);
1704
+ block.type = "iframe";
1705
+ block.src = `${baseUrl}/${filename}`;
1706
+ delete block.content;
1707
+ delete block.framework;
1708
+ delete block.theme;
1709
+ } catch (err) {
1710
+ return text(JSON.stringify({ error: `Failed to generate rich-html: ${err}` }));
1711
+ }
1712
+ }
1384
1713
  }
1385
1714
  try {
1386
1715
  await httpPost(`${lock.url}/api/deck`, { session, mode, blocks, prompt });
@@ -1464,7 +1793,7 @@ async function handleTool(name, args) {
1464
1793
  }));
1465
1794
  }
1466
1795
  try {
1467
- const logDir = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".launchsecure");
1796
+ const logDir = (0, import_node_path4.join)((0, import_node_os2.homedir)(), LAUNCHSECURE_DIR);
1468
1797
  (0, import_node_fs5.mkdirSync)(logDir, { recursive: true });
1469
1798
  const logPath = (0, import_node_path4.join)(logDir, "launch-deck.log");
1470
1799
  const out = (0, import_node_fs5.openSync)(logPath, "a");
@@ -1524,7 +1853,7 @@ async function handleTool(name, args) {
1524
1853
  }
1525
1854
  case "generate_contract": {
1526
1855
  const session = args.session;
1527
- const dir = (0, import_node_path4.join)(projectRoot, ".launchsecure", "deck-files", session);
1856
+ const dir = (0, import_node_path4.join)(projectRoot, LAUNCHSECURE_DIR, "deck-files", session);
1528
1857
  const manifestPath = (0, import_node_path4.join)(dir, "blast-radius-manifest.json");
1529
1858
  if (!import_node_fs4.default.existsSync(manifestPath)) {
1530
1859
  return text(JSON.stringify({
@@ -1639,10 +1968,12 @@ var init_deck_mcp = __esm({
1639
1968
  import_node_child_process2 = require("node:child_process");
1640
1969
  import_node_fs5 = require("node:fs");
1641
1970
  import_node_os2 = require("node:os");
1971
+ init_launch_kit_paths();
1642
1972
  init_deck_lockfile();
1643
1973
  init_deck_config();
1644
1974
  init_blast_radius_render();
1645
1975
  init_contract_generator();
1976
+ init_rich_html_render();
1646
1977
  SERVER_INFO = {
1647
1978
  name: "launch-deck",
1648
1979
  version: "0.0.3"
@@ -1650,7 +1981,7 @@ var init_deck_mcp = __esm({
1650
1981
  TOOLS = [
1651
1982
  {
1652
1983
  name: "deck",
1653
- description: 'Push visual blocks (HTML, Mermaid diagrams, options, markdown) to the LaunchDeck browser.\n\nEach call creates a named session tab in the browser. Tabs accumulate \u2014 previous sessions are preserved.\n\nModes:\n- "show": Display content and return immediately. Terminal confirms delivery. No user feedback.\n- "feedback": Display content with an input bar for the user to respond. Returns immediately with session info. Call `await_feedback` with the same session ID to block until the user responds.\n\nBlock types:\n- html: Raw HTML/CSS (rendered in a sandboxed iframe)\n- mermaid: Mermaid diagram DSL (flowchart, sequence, ERD, etc.)\n- markdown: Rendered markdown text\n- options: Selection cards \u2014 user picks one or more\n- blast-radius: Interactive D3 radial graph for blast radius analysis. Requires `manifest` field.\n\nAfter calling this tool, the user should check the LaunchDeck browser tab to view content.',
1984
+ description: 'Push visual blocks (HTML, Mermaid diagrams, options, markdown) to the LaunchDeck browser.\n\nEach call creates a named session tab in the browser. Tabs accumulate \u2014 previous sessions are preserved.\n\nModes:\n- "show": Display content and return immediately. Terminal confirms delivery. No user feedback.\n- "feedback": Display content with an input bar for the user to respond. Returns immediately with session info. Call `await_feedback` with the same session ID to block until the user responds.\n\nBlock types:\n- html: Raw HTML/CSS (sandboxed iframe \u2014 NO scripts). Use for static markup only.\n- mermaid: Mermaid diagram DSL (flowchart, sequence, ERD, etc.)\n- markdown: Rendered markdown text\n- options: Selection cards \u2014 user picks one or more\n- blast-radius: Interactive D3 radial graph for blast radius analysis. Requires `manifest` field.\n- rich-html: Self-contained HTML page with FULL JS support (events, animations, CDN scripts). Specify `framework` and `content`:\n \u2022 framework: "wired" \u2014 wireframe UI via wired-elements (https://wiredjs.com). `content` is body HTML using <wired-button>, <wired-input>, <wired-card>, <wired-checkbox>, <wired-combo>, <wired-slider>, <wired-tabs>/<wired-tab>, etc. Hand-drawn aesthetic. Loads CDN automatically.\n \u2022 framework: "reveal" \u2014 slide presentation via reveal.js (https://revealjs.com). `content` is markdown slides joined by `\\n---\\n` (recommended) OR raw <section>\u2026</section> HTML. Arrow keys navigate. Supports code highlighting and speaker notes. Loads CDN automatically.\n \u2022 framework: "raw" \u2014 `content` is a complete <!DOCTYPE html> document (caller controls everything).\n Optional `theme: "light" | "dark"` sets the INITIAL preference. The deck\'s sun/moon toggle overrides live \u2014 content reacts in place. Usually omit it; only set when the AI has a specific reason (e.g. mockup that only makes sense in one mode). Standalone downloads fall back to OS prefers-color-scheme.\n\nAfter calling this tool, the user should check the LaunchDeck browser tab to view content.',
1654
1985
  inputSchema: {
1655
1986
  type: "object",
1656
1987
  properties: {
@@ -1669,9 +2000,19 @@ var init_deck_mcp = __esm({
1669
2000
  items: {
1670
2001
  type: "object",
1671
2002
  properties: {
1672
- type: { type: "string", enum: ["html", "mermaid", "options", "markdown", "blast-radius"] },
2003
+ type: { type: "string", enum: ["html", "mermaid", "options", "markdown", "blast-radius", "rich-html"] },
1673
2004
  label: { type: "string", description: "Label for this block (shown as sub-tab if multiple blocks)" },
1674
- content: { type: "string", description: "Content string (HTML, Mermaid DSL, Markdown)" },
2005
+ content: { type: "string", description: "Content string (HTML, Mermaid DSL, Markdown, or rich-html body)" },
2006
+ framework: {
2007
+ type: "string",
2008
+ enum: ["wired", "reveal", "raw"],
2009
+ description: "For rich-html only: which framework to wrap content with."
2010
+ },
2011
+ theme: {
2012
+ type: "string",
2013
+ enum: ["light", "dark"],
2014
+ description: "For rich-html only: theme for the generated page."
2015
+ },
1675
2016
  manifest: {
1676
2017
  type: "object",
1677
2018
  description: 'For blast-radius type: BlastRadiusManifest object. Generates an interactive D3 radial graph.\n\nRequired fields:\n- mode: "structural" (file-level, rings = hops) or "feature" (rings = modify/create/ripple)\n- title: string \u2014 shown in header (e.g. "lib/permissions/types.ts" or "Channels for Comms")\n- layers: array of { id, name, icon, color } \u2014 defines the project layers (e.g. db/api/ui). icon = Lucide icon name, color = hex fill color\n- rings: array of { id, name, color } \u2014 defines concentric rings. For structural: [{id:"hop1",name:"Direct",color:"#f97316"},{id:"hop2",name:"Indirect",color:"#eab308"}]. For feature: [{id:"modify",name:"Modify",color:"#f97316"},{id:"create",name:"Create",color:"#22c55e"},{id:"ripple",name:"Ripple",color:"#eab308"}]\n- center: { name, description } \u2014 the center node label and tooltip\n- nodes: array of { id, name, layer, ring, path?, reason?, type? } \u2014 layer refs layers[].id, ring refs rings[].id\n- edges: array of { source, target } \u2014 source/target are node ids, use "center" for edges from the center node\n\nOptional: subtitle (shown above title)\n\nExample layers: [{id:"db",name:"Database",icon:"database",color:"#172554"},{id:"api",name:"API",icon:"server",color:"#1e3a5f"},{id:"ui",name:"UI",icon:"layout-dashboard",color:"#0c4a6e"}]'