dreamcontext 0.17.1 → 0.18.0

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 (181) hide show
  1. package/README.md +321 -565
  2. package/agents/sleep-product.md +21 -2
  3. package/agents/sleep-state.md +24 -0
  4. package/agents/sleep-tasks.md +16 -0
  5. package/dist/agents/sleep-product.md +21 -2
  6. package/dist/agents/sleep-state.md +24 -0
  7. package/dist/agents/sleep-tasks.md +16 -0
  8. package/dist/dashboard/announcements/dashboard-highlights-0-17-0-18.excalidraw.md +1531 -0
  9. package/dist/dashboard/announcements/goal-skill-v2.excalidraw.md +1490 -0
  10. package/dist/dashboard/announcements/task-manager.excalidraw.md +1217 -0
  11. package/dist/dashboard/announcements/visual-announcements.excalidraw.md +1373 -0
  12. package/dist/dashboard/announcements.json +38 -0
  13. package/dist/dashboard/assets/{BrainCanvas3D-DyF_3yYD.js → BrainCanvas3D-D9G3dOLf.js} +1 -1
  14. package/dist/dashboard/assets/{_baseUniq-Do8VMBA0.js → _baseUniq-Cyshlz4Y.js} +1 -1
  15. package/dist/dashboard/assets/{ar-SA-G6X2FPQ2-DmbXDb2R.js → ar-SA-G6X2FPQ2-qj-2Cbgo.js} +1 -1
  16. package/dist/dashboard/assets/{arc-CwtUBQfU.js → arc-0OnX5WDs.js} +1 -1
  17. package/dist/dashboard/assets/{architectureDiagram-Q4EWVU46-O9bOog_f.js → architectureDiagram-Q4EWVU46-Cx3c0nUi.js} +1 -1
  18. package/dist/dashboard/assets/{az-AZ-76LH7QW2-c9Zk10vG.js → az-AZ-76LH7QW2-CBuoh6M3.js} +1 -1
  19. package/dist/dashboard/assets/{bg-BG-XCXSNQG7-uMTg84lX.js → bg-BG-XCXSNQG7-X5QYyZWx.js} +1 -1
  20. package/dist/dashboard/assets/{blockDiagram-DXYQGD6D-BTENXCSO.js → blockDiagram-DXYQGD6D-BwESAg1j.js} +1 -1
  21. package/dist/dashboard/assets/{bn-BD-2XOGV67Q-DUFKHcua.js → bn-BD-2XOGV67Q-DlodQLNR.js} +1 -1
  22. package/dist/dashboard/assets/{c4Diagram-AHTNJAMY-DMXPWRiQ.js → c4Diagram-AHTNJAMY-CopL0lbP.js} +1 -1
  23. package/dist/dashboard/assets/{ca-ES-6MX7JW3Y-BuYX1q89.js → ca-ES-6MX7JW3Y-DfqF6Yhy.js} +1 -1
  24. package/dist/dashboard/assets/channel-BLisRbYb.js +1 -0
  25. package/dist/dashboard/assets/{chunk-4BX2VUAB-DgefvnWS.js → chunk-4BX2VUAB-CRwifLK3.js} +1 -1
  26. package/dist/dashboard/assets/{chunk-4TB4RGXK-BnFO5G1t.js → chunk-4TB4RGXK-uxJYY_64.js} +1 -1
  27. package/dist/dashboard/assets/{chunk-55IACEB6-COm2TYEM.js → chunk-55IACEB6-DhYIp7Oc.js} +1 -1
  28. package/dist/dashboard/assets/{chunk-EDXVE4YY-CtsGCwZp.js → chunk-EDXVE4YY-CCxfcirt.js} +1 -1
  29. package/dist/dashboard/assets/{chunk-FMBD7UC4-cfSvx35j.js → chunk-FMBD7UC4-D7OC8QFC.js} +1 -1
  30. package/dist/dashboard/assets/{chunk-OYMX7WX6-DAhDKteL.js → chunk-OYMX7WX6-CwHgaUQo.js} +1 -1
  31. package/dist/dashboard/assets/{chunk-QZHKN3VN-B5h4qML3.js → chunk-QZHKN3VN-7Idpf6YJ.js} +1 -1
  32. package/dist/dashboard/assets/{chunk-YZCP3GAM-CGp_NiRg.js → chunk-YZCP3GAM-D4Lh0fgr.js} +1 -1
  33. package/dist/dashboard/assets/classDiagram-6PBFFD2Q-CgqeQiHi.js +1 -0
  34. package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-CgqeQiHi.js +1 -0
  35. package/dist/dashboard/assets/clone-CQXNMCV5.js +1 -0
  36. package/dist/dashboard/assets/{cose-bilkent-S5V4N54A-Dk7CMFA4.js → cose-bilkent-S5V4N54A-BYcMtJTF.js} +1 -1
  37. package/dist/dashboard/assets/{cs-CZ-2BRQDIVT-zfUTiS_d.js → cs-CZ-2BRQDIVT-rnrpKdSJ.js} +1 -1
  38. package/dist/dashboard/assets/{da-DK-5WZEPLOC-CU14f8NA.js → da-DK-5WZEPLOC-D5OGAvk7.js} +1 -1
  39. package/dist/dashboard/assets/{dagre-KV5264BT-BiMVxC4Q.js → dagre-KV5264BT-V0f1_b4R.js} +1 -1
  40. package/dist/dashboard/assets/{de-DE-XR44H4JA-CDOHAJGo.js → de-DE-XR44H4JA-CDyb8YfL.js} +1 -1
  41. package/dist/dashboard/assets/{diagram-5BDNPKRD-DfJEsVTA.js → diagram-5BDNPKRD-BmpizoaS.js} +1 -1
  42. package/dist/dashboard/assets/{diagram-G4DWMVQ6-CztLHIcY.js → diagram-G4DWMVQ6-ClQg-LTu.js} +1 -1
  43. package/dist/dashboard/assets/{diagram-MMDJMWI5-DqoQLlWK.js → diagram-MMDJMWI5-BkXcDMIV.js} +1 -1
  44. package/dist/dashboard/assets/{diagram-TYMM5635-DvyvQBCc.js → diagram-TYMM5635-BbFiiCM2.js} +1 -1
  45. package/dist/dashboard/assets/{el-GR-BZB4AONW-ChUphbcv.js → el-GR-BZB4AONW-Y1YnKZL0.js} +1 -1
  46. package/dist/dashboard/assets/{erDiagram-SMLLAGMA-D_uSs2_2.js → erDiagram-SMLLAGMA-Dzdv7_l5.js} +1 -1
  47. package/dist/dashboard/assets/{es-ES-U4NZUMDT-BNalBEhb.js → es-ES-U4NZUMDT-D0T536hs.js} +1 -1
  48. package/dist/dashboard/assets/{eu-ES-A7QVB2H4-BMiQyA2y.js → eu-ES-A7QVB2H4-Bfrya0et.js} +1 -1
  49. package/dist/dashboard/assets/{fa-IR-HGAKTJCU-Bd6ZnTSo.js → fa-IR-HGAKTJCU-pYE_hG-L.js} +1 -1
  50. package/dist/dashboard/assets/{fi-FI-Z5N7JZ37-BWAfJadC.js → fi-FI-Z5N7JZ37-D3C-fNyk.js} +1 -1
  51. package/dist/dashboard/assets/{flowDiagram-DWJPFMVM-Bmsp6kyU.js → flowDiagram-DWJPFMVM-CFhyjwKY.js} +1 -1
  52. package/dist/dashboard/assets/{fr-FR-RHASNOE6-CspbYzOW.js → fr-FR-RHASNOE6-CgwnG5JW.js} +1 -1
  53. package/dist/dashboard/assets/{ganttDiagram-T4ZO3ILL-wEdysXo_.js → ganttDiagram-T4ZO3ILL-BRKRiSzf.js} +1 -1
  54. package/dist/dashboard/assets/{gitGraphDiagram-UUTBAWPF-BEiGpPBj.js → gitGraphDiagram-UUTBAWPF-CTqCK49d.js} +1 -1
  55. package/dist/dashboard/assets/{gl-ES-HMX3MZ6V-CV76vsWr.js → gl-ES-HMX3MZ6V-DjJTqTm3.js} +1 -1
  56. package/dist/dashboard/assets/{graph-CZFIeNHZ.js → graph-D429kSbY.js} +1 -1
  57. package/dist/dashboard/assets/{he-IL-6SHJWFNN-Y1VoXJ_H.js → he-IL-6SHJWFNN-C84FlH6r.js} +1 -1
  58. package/dist/dashboard/assets/{hi-IN-IWLTKZ5I-Crh5S49o.js → hi-IN-IWLTKZ5I-Blhkfum2.js} +1 -1
  59. package/dist/dashboard/assets/{hu-HU-A5ZG7DT2-D4I3gCbp.js → hu-HU-A5ZG7DT2-DLC1JfN-.js} +1 -1
  60. package/dist/dashboard/assets/{id-ID-SAP4L64H-Chvzkuvi.js → id-ID-SAP4L64H-C3pB_IWB.js} +1 -1
  61. package/dist/dashboard/assets/image-FgR93jdt.js +1 -0
  62. package/dist/dashboard/assets/index-2m8ueOUn.js +1 -0
  63. package/dist/dashboard/assets/index-CWwjuihq.css +32 -0
  64. package/dist/dashboard/assets/{index-C7Bo5AQH.js → index-CbOXCDms.js} +1 -1
  65. package/dist/dashboard/assets/index-SJ64pf-4.js +551 -0
  66. package/dist/dashboard/assets/{infoDiagram-42DDH7IO-BzfuDHl4.js → infoDiagram-42DDH7IO-CIzDShDw.js} +1 -1
  67. package/dist/dashboard/assets/{ishikawaDiagram-UXIWVN3A-BVoNJ9VS.js → ishikawaDiagram-UXIWVN3A-CuasYzdX.js} +1 -1
  68. package/dist/dashboard/assets/{it-IT-JPQ66NNP-BH_gN5es.js → it-IT-JPQ66NNP-DGGEoNPu.js} +1 -1
  69. package/dist/dashboard/assets/{ja-JP-DBVTYXUO-DU_WmLVa.js → ja-JP-DBVTYXUO-CEqP9aps.js} +1 -1
  70. package/dist/dashboard/assets/{journeyDiagram-VCZTEJTY-DbwICa_a.js → journeyDiagram-VCZTEJTY-DHiq2OTQ.js} +1 -1
  71. package/dist/dashboard/assets/{kaa-6HZHGXH3-KYU9qowM.js → kaa-6HZHGXH3-GOFmTAXq.js} +1 -1
  72. package/dist/dashboard/assets/{kab-KAB-ZGHBKWFO-bjhyiUq5.js → kab-KAB-ZGHBKWFO-B865Wwzu.js} +1 -1
  73. package/dist/dashboard/assets/{kanban-definition-6JOO6SKY-D9XucLs4.js → kanban-definition-6JOO6SKY-BhlTKFbL.js} +1 -1
  74. package/dist/dashboard/assets/{kk-KZ-P5N5QNE5-CYTx_dAZ.js → kk-KZ-P5N5QNE5-CNfuE6b6.js} +1 -1
  75. package/dist/dashboard/assets/{km-KH-HSX4SM5Z-DqsuY4Lf.js → km-KH-HSX4SM5Z-CsmgRWxu.js} +1 -1
  76. package/dist/dashboard/assets/{ko-KR-MTYHY66A-BZe7NywF.js → ko-KR-MTYHY66A-B_02RgEz.js} +1 -1
  77. package/dist/dashboard/assets/{ku-TR-6OUDTVRD-F5d6HFWt.js → ku-TR-6OUDTVRD-JfQA5u05.js} +1 -1
  78. package/dist/dashboard/assets/{layout-CotqU0S9.js → layout-DvrQSvOI.js} +1 -1
  79. package/dist/dashboard/assets/{linear-Dqv87Scf.js → linear-6r0qttLs.js} +1 -1
  80. package/dist/dashboard/assets/{lt-LT-XHIRWOB4-BCCuaPbS.js → lt-LT-XHIRWOB4-DWFUXEdf.js} +1 -1
  81. package/dist/dashboard/assets/{lv-LV-5QDEKY6T-NLGi-vJ6.js → lv-LV-5QDEKY6T-C5yAd0n_.js} +1 -1
  82. package/dist/dashboard/assets/{min-BbJdnc3h.js → min-D_EP9dBf.js} +1 -1
  83. package/dist/dashboard/assets/{mindmap-definition-QFDTVHPH-Dk4QySu7.js → mindmap-definition-QFDTVHPH-Col3ao9B.js} +1 -1
  84. package/dist/dashboard/assets/{mr-IN-CRQNXWMA-BnGDIgVN.js → mr-IN-CRQNXWMA-Dwjz0Xdm.js} +1 -1
  85. package/dist/dashboard/assets/{my-MM-5M5IBNSE-CspgYE9_.js → my-MM-5M5IBNSE-EXJDNyjA.js} +1 -1
  86. package/dist/dashboard/assets/{nb-NO-T6EIAALU-BeRPw3hu.js → nb-NO-T6EIAALU-BWm2bgLd.js} +1 -1
  87. package/dist/dashboard/assets/{nl-NL-IS3SIHDZ-C-G-bljM.js → nl-NL-IS3SIHDZ-f8nP-yp-.js} +1 -1
  88. package/dist/dashboard/assets/{nn-NO-6E72VCQL-Ct7VZY8D.js → nn-NO-6E72VCQL-B0c_gCNR.js} +1 -1
  89. package/dist/dashboard/assets/{oc-FR-POXYY2M6-Cv5-C4Qq.js → oc-FR-POXYY2M6-C40PSwAh.js} +1 -1
  90. package/dist/dashboard/assets/{pa-IN-N4M65BXN-DAgkIThL.js → pa-IN-N4M65BXN-DDcgXFkB.js} +1 -1
  91. package/dist/dashboard/assets/{percentages-BXMCSKIN-vidRYabK.js → percentages-BXMCSKIN-MHBQP8nI.js} +7 -7
  92. package/dist/dashboard/assets/{pica-Cy-Xl-mi.js → pica-C5CFA09W.js} +1 -1
  93. package/dist/dashboard/assets/{pieDiagram-DEJITSTG-BnP4-zwd.js → pieDiagram-DEJITSTG-DFHmB0fL.js} +1 -1
  94. package/dist/dashboard/assets/{pl-PL-T2D74RX3-BuJQ3HD-.js → pl-PL-T2D74RX3-yFTBt0Xu.js} +1 -1
  95. package/dist/dashboard/assets/{pt-BR-5N22H2LF-BbAqsvD6.js → pt-BR-5N22H2LF-ilAW3Fdv.js} +1 -1
  96. package/dist/dashboard/assets/{pt-PT-UZXXM6DQ-DzFJedU3.js → pt-PT-UZXXM6DQ-CXHFN95K.js} +1 -1
  97. package/dist/dashboard/assets/{quadrantDiagram-34T5L4WZ-BXN88cHy.js → quadrantDiagram-34T5L4WZ-CR732_oP.js} +1 -1
  98. package/dist/dashboard/assets/{requirementDiagram-MS252O5E-D34hL42a.js → requirementDiagram-MS252O5E-mKoVG52Z.js} +1 -1
  99. package/dist/dashboard/assets/{ro-RO-JPDTUUEW-BV2-sOiv.js → ro-RO-JPDTUUEW-BKlWpyF4.js} +1 -1
  100. package/dist/dashboard/assets/{ru-RU-B4JR7IUQ-BCL4N9Ej.js → ru-RU-B4JR7IUQ-BGoxlhJm.js} +1 -1
  101. package/dist/dashboard/assets/{sankeyDiagram-XADWPNL6-0mFeok-3.js → sankeyDiagram-XADWPNL6-DXipts7M.js} +1 -1
  102. package/dist/dashboard/assets/{sequenceDiagram-FGHM5R23-C-5nW5j6.js → sequenceDiagram-FGHM5R23-DFQhU8q7.js} +1 -1
  103. package/dist/dashboard/assets/{si-LK-N5RQ5JYF-CNhaw4jx.js → si-LK-N5RQ5JYF-yDbJWFTH.js} +1 -1
  104. package/dist/dashboard/assets/{sk-SK-C5VTKIMK-mHCphOXu.js → sk-SK-C5VTKIMK-DglyhbNB.js} +1 -1
  105. package/dist/dashboard/assets/{sl-SI-NN7IZMDC--lzgHthQ.js → sl-SI-NN7IZMDC-DPfq0s4I.js} +1 -1
  106. package/dist/dashboard/assets/{stateDiagram-FHFEXIEX-C50iDFqe.js → stateDiagram-FHFEXIEX-Dm50jiEh.js} +1 -1
  107. package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-CoSm11sD.js +1 -0
  108. package/dist/dashboard/assets/{subset-shared.chunk-DNsjL37s.js → subset-shared.chunk-OHz6M6ct.js} +1 -1
  109. package/dist/dashboard/assets/{subset-worker.chunk-BaV3r-bl.js → subset-worker.chunk-BzwrOBtO.js} +1 -1
  110. package/dist/dashboard/assets/{sv-SE-XGPEYMSR-ckw8jfGN.js → sv-SE-XGPEYMSR-BLHNwr4k.js} +1 -1
  111. package/dist/dashboard/assets/{ta-IN-2NMHFXQM-CSeNmVhS.js → ta-IN-2NMHFXQM-Dw-6hUpN.js} +1 -1
  112. package/dist/dashboard/assets/{th-TH-HPSO5L25-Cvn1sg_3.js → th-TH-HPSO5L25-DPkcM_k9.js} +1 -1
  113. package/dist/dashboard/assets/{timeline-definition-GMOUNBTQ-DRYfuqHT.js → timeline-definition-GMOUNBTQ-BKFNQ_7e.js} +1 -1
  114. package/dist/dashboard/assets/{tr-TR-DEFEU3FU-CyhY22TB.js → tr-TR-DEFEU3FU-rBVsY5RS.js} +1 -1
  115. package/dist/dashboard/assets/{uk-UA-QMV73CPH-BvvAEO6U.js → uk-UA-QMV73CPH-CV6_yTZs.js} +1 -1
  116. package/dist/dashboard/assets/{vennDiagram-DHZGUBPP-Crril7Cj.js → vennDiagram-DHZGUBPP-BZb_r5tY.js} +1 -1
  117. package/dist/dashboard/assets/{vi-VN-M7AON7JQ-c7AwSUZ8.js → vi-VN-M7AON7JQ-CJMgSsLz.js} +1 -1
  118. package/dist/dashboard/assets/{wardley-RL74JXVD-Da5AhSWU.js → wardley-RL74JXVD-DgqjmWyN.js} +1 -1
  119. package/dist/dashboard/assets/{wardleyDiagram-NUSXRM2D-C4GeB_Df.js → wardleyDiagram-NUSXRM2D-BDpniS9m.js} +1 -1
  120. package/dist/dashboard/assets/webviewWindow-Bwv2421L.js +1 -0
  121. package/dist/dashboard/assets/window-BObcHN8e.js +1 -0
  122. package/dist/dashboard/assets/{xychartDiagram-5P7HB3ND-BPCFKXqR.js → xychartDiagram-5P7HB3ND-CCyPcx2A.js} +1 -1
  123. package/dist/dashboard/assets/{zh-CN-LNUGB5OW-BvNiZwKU.js → zh-CN-LNUGB5OW-9LJLF_AC.js} +1 -1
  124. package/dist/dashboard/assets/{zh-HK-E62DVLB3-CCFtoCWb.js → zh-HK-E62DVLB3-IefAiePp.js} +1 -1
  125. package/dist/dashboard/assets/{zh-TW-RAJ6MFWO-bYBr-stu.js → zh-TW-RAJ6MFWO-CHeY9rNZ.js} +1 -1
  126. package/dist/dashboard/index.html +2 -2
  127. package/dist/index.js +4099 -1538
  128. package/dist/skill-packs/agents/goal-implementer.md +42 -1
  129. package/dist/skill-packs/agents/goal-plan-reviewer.md +7 -0
  130. package/dist/skill-packs/agents/goal-planner.md +34 -0
  131. package/dist/skill-packs/catalog.json +31 -21
  132. package/dist/skill-packs/excalidraw/SKILL.md +244 -13
  133. package/dist/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
  134. package/dist/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
  135. package/dist/skill-packs/excalidraw/examples/atomic_check.js +99 -0
  136. package/dist/skill-packs/excalidraw/examples/chart.spec.json +122 -0
  137. package/dist/skill-packs/excalidraw/examples/chart_board.js +173 -0
  138. package/dist/skill-packs/excalidraw/examples/hello.spec.json +79 -9
  139. package/dist/skill-packs/excalidraw/examples/visual_board.js +82 -0
  140. package/dist/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
  141. package/dist/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
  142. package/dist/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
  143. package/dist/skill-packs/excalidraw/scripts/lib/style.js +369 -35
  144. package/dist/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
  145. package/dist/skill-packs/goal-skill/SKILL.md +369 -100
  146. package/dist/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
  147. package/dist/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
  148. package/package.json +2 -1
  149. package/skill/references/integrations.md +54 -0
  150. package/skill/references/knowledge-and-recall.md +3 -1
  151. package/skill/references/sleep.md +4 -4
  152. package/skill-packs/agents/goal-implementer.md +42 -1
  153. package/skill-packs/agents/goal-plan-reviewer.md +7 -0
  154. package/skill-packs/agents/goal-planner.md +34 -0
  155. package/skill-packs/catalog.json +31 -21
  156. package/skill-packs/excalidraw/SKILL.md +244 -13
  157. package/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
  158. package/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
  159. package/skill-packs/excalidraw/examples/atomic_check.js +99 -0
  160. package/skill-packs/excalidraw/examples/chart.spec.json +122 -0
  161. package/skill-packs/excalidraw/examples/chart_board.js +173 -0
  162. package/skill-packs/excalidraw/examples/hello.spec.json +79 -9
  163. package/skill-packs/excalidraw/examples/visual_board.js +82 -0
  164. package/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
  165. package/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
  166. package/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
  167. package/skill-packs/excalidraw/scripts/lib/style.js +369 -35
  168. package/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
  169. package/skill-packs/goal-skill/SKILL.md +369 -100
  170. package/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
  171. package/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
  172. package/skill-task-manager/SKILL.md +93 -0
  173. package/dist/dashboard/assets/channel-2YfRjala.js +0 -1
  174. package/dist/dashboard/assets/classDiagram-6PBFFD2Q-DB_UnN1h.js +0 -1
  175. package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-DB_UnN1h.js +0 -1
  176. package/dist/dashboard/assets/clone-DaDvB3N6.js +0 -1
  177. package/dist/dashboard/assets/index-BIfrbaFS.js +0 -516
  178. package/dist/dashboard/assets/index-BdZ3tppu.css +0 -32
  179. package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-eW9-s0Sm.js +0 -1
  180. package/dist/dashboard/assets/webviewWindow-BbG9Cr7f.js +0 -1
  181. package/dist/dashboard/assets/window-DmNpeJAL.js +0 -1
@@ -0,0 +1,161 @@
1
+ // wireframe_board.js — the device + product-UI kit, on one board.
2
+ // Run: node examples/wireframe_board.js → examples/Wireframe Kit.excalidraw.md
3
+ //
4
+ // Doubles as the kit's smoke test. Note how each screen is composed: device() hands back `.inner`
5
+ // (the safe region inside the bezel, below the island, above the home indicator) and everything is
6
+ // flowed into it with stack()/row(). No screen coordinates are ever typed by hand.
7
+ const path = require('path');
8
+ const { buildExcalidraw } = require(path.resolve(__dirname, '../scripts/build_excalidraw.js'));
9
+ const { sectionTitle, prose, stack, row, READ_W } = require(path.resolve(__dirname, '../scripts/lib/style.js'));
10
+ const {
11
+ device, appBar, tabBar, icon, iconButton, iconNames,
12
+ listRow, toggle, segmented, slider, searchField,
13
+ button, input, textRows, imagePlaceholder, avatar, chip, navbar, windowFrame,
14
+ } = require(path.resolve(__dirname, '../scripts/lib/wireframe.js'));
15
+
16
+ const els = [];
17
+ const P = (...a) => els.push(...a.flat());
18
+
19
+ // flow the header — a hardcoded y let the wrapped title land on the prose (the audit caught it)
20
+ P(stack({
21
+ x: 60, y: 40, gap: 12, items: [
22
+ (x, y) => sectionTitle({ x, y, text: 'Wireframe kit — cihaz kalıpları + UI kontrolleri', fontSize: 40, maxWidth: 1400 }),
23
+ (x, y) => prose({
24
+ x, y, width: READ_W, fontSize: 15,
25
+ text: 'Cihazlar gerçek oranlarda (iPhone 393×852, iPad 834×1194, Mac 1440×900). device() güvenli alanı .inner olarak döner — çocuklar oraya akar, chrome’a çarpamaz.',
26
+ }),
27
+ ],
28
+ }));
29
+
30
+ // ── iPhone: a feed screen ─────────────────────────────────────────────────
31
+ const ph = device({ kind: 'iphone', x: 60, y: 180, label: 'iPhone — feed' });
32
+ P(ph);
33
+ P(stack({
34
+ x: ph.inner.x, y: ph.inner.y, gap: 10, items: [
35
+ (x, y) => appBar({ x, y, w: ph.inner.w, title: 'Discover', back: false, actions: ['bell', 'more-v'] }),
36
+ (x, y) => searchField({ x, y, w: ph.inner.w, placeholder: 'Ara…' }),
37
+ (x, y) => segmented({ x, y, w: ph.inner.w, items: ['Hepsi', 'Takip', 'Yeni'], active: 0 }),
38
+ (x, y) => imagePlaceholder({ x, y, w: ph.inner.w, h: 130, label: 'kapak' }),
39
+ (x, y) => textRows({ x, y, w: ph.inner.w, rows: 3 }),
40
+ (x, y) => row({
41
+ x, y, gap: 8, valign: 'middle', items: [
42
+ (x, y) => iconButton({ x, y, size: 32, icon: 'heart', variant: 'ghost', color: 'red' }),
43
+ (x, y) => iconButton({ x, y, size: 32, icon: 'share', variant: 'ghost' }),
44
+ (x, y) => iconButton({ x, y, size: 32, icon: 'bookmark', variant: 'ghost' }),
45
+ (x, y) => button({ x, y, w: 110, h: 32, text: 'Takip et', fontSize: 13 }),
46
+ ],
47
+ }),
48
+ ],
49
+ }));
50
+ // tab bar pinned to the bottom of the safe area
51
+ P(tabBar({
52
+ x: ph.inner.x, y: ph.inner.y + ph.inner.h - 52, w: ph.inner.w, active: 0,
53
+ items: [{ icon: 'home', label: 'Akış' }, { icon: 'search', label: 'Ara' }, { icon: 'heart', label: 'Beğeni' }, { icon: 'user', label: 'Profil' }],
54
+ }));
55
+
56
+ // ── iPhone: a settings screen ─────────────────────────────────────────────
57
+ const ph2 = device({ kind: 'iphone', x: 520, y: 180, label: 'iPhone — ayarlar' });
58
+ P(ph2);
59
+ P(stack({
60
+ x: ph2.inner.x, y: ph2.inner.y, gap: 0, items: [
61
+ (x, y) => appBar({ x, y, w: ph2.inner.w, title: 'Ayarlar', back: true }),
62
+ (x, y) => listRow({ x, y, w: ph2.inner.w, leading: 'avatar', title: 'Alex Doe', subtitle: 'Account, iCloud' }),
63
+ (x, y) => listRow({ x, y, w: ph2.inner.w, leading: 'bell', title: 'Bildirimler', trailing: 'toggle' }),
64
+ (x, y) => listRow({ x, y, w: ph2.inner.w, leading: 'lock', title: 'Gizlilik', trailing: 'chevron-right' }),
65
+ (x, y) => listRow({ x, y, w: ph2.inner.w, leading: 'settings', title: 'Genel', trailing: 'chevron-right' }),
66
+ (x, y) => listRow({ x, y, w: ph2.inner.w, leading: 'mail', title: 'Posta', trailing: '3', divider: false }),
67
+ ],
68
+ }));
69
+
70
+ // ── iPad: a split view ────────────────────────────────────────────────────
71
+ const ip = device({ kind: 'ipad', x: 980, y: 180, label: 'iPad — split view' });
72
+ P(ip);
73
+ const sideW = Math.round(ip.inner.w * 0.34);
74
+ P(stack({
75
+ x: ip.inner.x, y: ip.inner.y, gap: 0, items: [
76
+ (x, y) => listRow({ x, y, w: sideW, leading: 'home', title: 'Gelen kutusu', trailing: '12' }),
77
+ (x, y) => listRow({ x, y, w: sideW, leading: 'star', title: 'Yıldızlı' }),
78
+ (x, y) => listRow({ x, y, w: sideW, leading: 'bookmark', title: 'Kaydedilen' }),
79
+ (x, y) => listRow({ x, y, w: sideW, leading: 'trash', title: 'Çöp', divider: false }),
80
+ ],
81
+ }));
82
+ P(stack({
83
+ x: ip.inner.x + sideW + 16, y: ip.inner.y, gap: 12, items: [
84
+ (x, y) => appBar({ x, y, w: ip.inner.w - sideW - 16, title: 'Mesaj', back: false, actions: ['edit', 'trash'], border: false }),
85
+ (x, y) => imagePlaceholder({ x, y, w: ip.inner.w - sideW - 16, h: 150 }),
86
+ (x, y) => textRows({ x, y, w: ip.inner.w - sideW - 16, rows: 5 }),
87
+ ],
88
+ }));
89
+
90
+ // ── Mac: a desktop app ────────────────────────────────────────────────────
91
+ // device({content}) is the safe pattern: widths derive from the `inner` you're handed, and anything
92
+ // escaping the glass is reported. Hardcoding these columns overflowed the bezel by 26px.
93
+ P(device({
94
+ kind: 'mac', x: 60, y: 1140, label: 'dreamcontext',
95
+ content: (inner) => {
96
+ const SIDE = Math.round(inner.w * 0.27), RIGHT = Math.round(inner.w * 0.25);
97
+ const MID = inner.w - SIDE - RIGHT - 32;
98
+ return stack({
99
+ x: inner.x, y: inner.y, gap: 12, items: [
100
+ (x, y) => navbar({ x, y, w: inner.w, brand: 'dreamcontext', items: ['Tasks', 'Knowledge', 'Roadmap'], cta: 'Sleep' }),
101
+ (x, y) => row({
102
+ x, y, gap: 16, valign: 'top', items: [
103
+ (x, y) => stack({
104
+ x, y, gap: 0, items: [
105
+ (x, y) => listRow({ x, y, w: SIDE, leading: 'home', title: 'Board' }),
106
+ (x, y) => listRow({ x, y, w: SIDE, leading: 'calendar', title: 'Roadmap' }),
107
+ (x, y) => listRow({ x, y, w: SIDE, leading: 'image', title: 'Diagrams', divider: false }),
108
+ ],
109
+ }),
110
+ (x, y) => stack({
111
+ x, y, gap: 12, items: [
112
+ (x, y) => searchField({ x, y, w: MID, placeholder: 'recall…' }),
113
+ (x, y) => textRows({ x, y, w: MID, rows: 4 }),
114
+ (x, y) => row({ x, y, gap: 10, items: [(x, y) => chip({ x, y, text: 'topic:excalidraw', color: 'blue' }), (x, y) => chip({ x, y, text: 'enhancement', color: 'green' })] }),
115
+ ],
116
+ }),
117
+ (x, y) => stack({
118
+ x, y, gap: 14, items: [
119
+ (x, y) => input({ x, y, w: RIGHT, label: 'Slug', placeholder: 'chart-kit' }),
120
+ (x, y) => slider({ x, y, w: RIGHT, value: 0.65 }),
121
+ (x, y) => row({ x, y, gap: 10, valign: 'middle', items: [(x, y) => toggle({ x, y, on: true }), (x, y) => toggle({ x, y, on: false })] }),
122
+ (x, y) => button({ x, y, w: RIGHT, text: 'Kaydet' }),
123
+ (x, y) => button({ x, y, w: RIGHT, text: 'Vazgeç', variant: 'outline', color: 'gray' }),
124
+ ],
125
+ }),
126
+ ],
127
+ }),
128
+ ],
129
+ });
130
+ },
131
+ }));
132
+
133
+ // ── icon set ──────────────────────────────────────────────────────────────
134
+ const ICON_Y = 1900;
135
+ P(sectionTitle({ x: 60, y: ICON_Y, text: `icon() — ${iconNames().length} glif`, fontSize: 26 }));
136
+ const names = iconNames();
137
+ const PER = 11, CELL = 92;
138
+ names.forEach((n, i) => {
139
+ const cx = 60 + (i % PER) * CELL, cy = ICON_Y + 50 + Math.floor(i / PER) * 76;
140
+ P(icon({ name: n, x: cx + 18, y: cy, size: 26, color: '#1e1e1e' }));
141
+ P({ type: 'text', x: cx - 6, y: cy + 34, text: n, fontSize: 9, color: '#868e96', width: CELL - 6, align: 'center', fontFamily: 5 });
142
+ });
143
+
144
+ // ── iconButton variants ───────────────────────────────────────────────────
145
+ const IB_Y = ICON_Y + 50 + Math.ceil(names.length / PER) * 76 + 30;
146
+ P(sectionTitle({ x: 60, y: IB_Y, text: 'iconButton() — shape × variant', fontSize: 26 }));
147
+ P(row({
148
+ x: 60, y: IB_Y + 46, gap: 18, valign: 'middle', items: [
149
+ (x, y) => iconButton({ x, y, icon: 'plus', shape: 'circle', variant: 'solid', color: 'blue' }),
150
+ (x, y) => iconButton({ x, y, icon: 'heart', shape: 'circle', variant: 'solid', color: 'red' }),
151
+ (x, y) => iconButton({ x, y, icon: 'check', shape: 'circle', variant: 'solid', color: 'green' }),
152
+ (x, y) => iconButton({ x, y, icon: 'search', shape: 'square', variant: 'outline' }),
153
+ (x, y) => iconButton({ x, y, icon: 'settings', shape: 'rounded', variant: 'outline' }),
154
+ (x, y) => iconButton({ x, y, icon: 'more-v', variant: 'ghost' }),
155
+ (x, y) => iconButton({ x, y, icon: 'share', variant: 'ghost' }),
156
+ (x, y) => iconButton({ x, y, icon: 'trash', shape: 'circle', variant: 'outline', color: 'red' }),
157
+ ],
158
+ }));
159
+
160
+ const res = buildExcalidraw({ out: path.resolve(__dirname, 'Wireframe Kit.excalidraw.md'), elements: els });
161
+ console.log(`elements=${res.elements} texts=${res.texts} overlaps=${res.overlaps} buriedText=${res.buriedText} longLines=${res.longLines}`);
@@ -21,11 +21,79 @@
21
21
  // "elements": [ <ElementSpec>, ... ]
22
22
  // }
23
23
  // ElementSpec.type: text | image | rectangle | ellipse | diamond | line | arrow | frame
24
+ // ...plus any COMPOSITE type (chart / house-style widget / layout) — see COMPOSITES below. A
25
+ // composite element is expanded into primitives before the build, so the CLI can draw a whole
26
+ // lineChart or funnel declaratively without writing a generator script.
24
27
  const fs = require('fs');
25
28
  const path = require('path');
26
29
  const crypto = require('crypto');
27
30
  const { generateNKeysBetween } = require('./lib/fractional-indexing.js');
28
31
  const { imageSize } = require('./lib/imagesize.js');
32
+ const STYLE = require('./lib/style.js');
33
+ const CHARTS = require('./lib/charts.js');
34
+ const WF = require('./lib/wireframe.js');
35
+
36
+ // ---------- composite element types (declarative surface) ----------
37
+ // Every entry maps a spec `type` to a builder that returns an ElementSpec[]. The expander runs BEFORE
38
+ // the primitive loop and is recursive, so composites may emit composites. This is what makes the JSON
39
+ // surface as capable as the JS API — the same builders back both.
40
+ const COMPOSITES = {
41
+ // charts
42
+ lineChart: CHARTS.lineChart, barChart: CHARTS.barChart, barCompare: CHARTS.barCompare,
43
+ stackedBar: CHARTS.stackedBar, gantt: CHARTS.gantt, quadrant: CHARTS.quadrant,
44
+ donut: CHARTS.donut, pie: (o) => CHARTS.donut(Object.assign({ inner: 0 }, o)),
45
+ sparkline: CHARTS.sparkline, heatmap: CHARTS.heatmap, table: CHARTS.table,
46
+ timeline: CHARTS.timeline, kpi: CHARTS.kpi, callout: CHARTS.callout,
47
+ // house style
48
+ funnel: STYLE.funnel, card: STYLE.card, node: STYLE.node, column: STYLE.column, hub: STYLE.hub,
49
+ connector: STYLE.connector, annotate: STYLE.annotate, chip: STYLE.chip, divider: STYLE.divider,
50
+ prose: STYLE.prose, bullets: STYLE.bullets, sectionTitle: STYLE.sectionTitle,
51
+ // wireframe kit
52
+ windowFrame: STYLE.windowFrame, navbar: STYLE.navbar, button: STYLE.button, input: STYLE.input,
53
+ textRows: STYLE.textRows, imagePlaceholder: STYLE.imagePlaceholder, avatar: STYLE.avatar,
54
+ // device + product-UI kit
55
+ device: WF.device, appBar: WF.appBar, tabBar: WF.tabBar, icon: WF.icon, iconButton: WF.iconButton,
56
+ listRow: WF.listRow, toggle: WF.toggle, segmented: WF.segmented, slider: WF.slider,
57
+ searchField: WF.searchField, roundRect: WF.roundRect,
58
+ };
59
+ // Layout composites take `items` (child specs) rather than scalar props, so they expand differently.
60
+ const LAYOUTS = { stack: STYLE.stack, row: STYLE.row };
61
+
62
+ function expandElements(list) {
63
+ const out = [];
64
+ for (const e of (list || [])) {
65
+ if (!e || typeof e !== 'object' || Array.isArray(e)) { if (e) out.push(e); continue; }
66
+ if (LAYOUTS[e.type]) {
67
+ // each item is itself a spec (or array of specs) → expand first, then let stack/row measure it
68
+ const items = (e.items || []).map((it) => expandElements(Array.isArray(it) ? it : [it]));
69
+ out.push(...expandElements(LAYOUTS[e.type](Object.assign({}, e, { items }))));
70
+ } else if (COMPOSITES[e.type]) {
71
+ out.push(...expandElements(COMPOSITES[e.type](e)));
72
+ } else {
73
+ out.push(e);
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+
79
+ // ---------- frontmatter YAML ----------
80
+ // Minimal, dependency-free emitters. Only what frontmatter needs: a safe scalar and a folded block
81
+ // for long prose (a description is often a paragraph, and an unquoted one with `:` breaks the parse).
82
+ function yamlScalar(v) {
83
+ const s = String(v);
84
+ return /^[\w][\w .\-/()]*$/.test(s) ? s : JSON.stringify(s);
85
+ }
86
+ function yamlFolded(key, text, width = 96) {
87
+ // `>-` folds newlines to spaces on read, so wrap freely; indent 2 marks the block.
88
+ const words = String(text).replace(/\s+/g, ' ').trim().split(' ');
89
+ const lines = [];
90
+ let cur = '';
91
+ for (const w of words) {
92
+ if (cur && (cur + ' ' + w).length > width) { lines.push(cur); cur = w; } else cur = cur ? cur + ' ' + w : w;
93
+ }
94
+ if (cur) lines.push(cur);
95
+ return [`${key}: >-`, ...lines.map((l) => ' ' + l)];
96
+ }
29
97
 
30
98
  // ---------- deterministic PRNG (stable seeds => clean git diffs) ----------
31
99
  function mulberry32(seed) {
@@ -71,28 +139,52 @@ function isWideGlyph(code) {
71
139
  function glyphW(ch, fontSize) { return fontSize * (isWideGlyph(ch.codePointAt(0)) ? 1.05 : 0.58); }
72
140
  function measureText(s, fontSize) { let w = 0; for (const ch of String(s)) w += glyphW(ch, fontSize); return w; }
73
141
 
74
- // When a fixedWidth is given the text element is treated as WRAPPING (autoResize off downstream),
75
- // so height must reflect word-wrapped line count otherwise long captions render as one wide line
76
- // and overlap their neighbours. Honors explicit \n and greedily wraps each logical line to width.
77
- function sizeText(text, fontSize, fixedWidth) {
78
- const rawLines = String(text).split('\n');
79
- if (fixedWidth != null) {
80
- let totalLines = 0;
81
- for (const ln of rawLines) {
82
- const words = ln.split(/\s+/).filter(Boolean);
83
- if (!words.length) { totalLines += 1; continue; }
84
- let cur = '', lc = 1;
85
- for (const w of words) {
86
- if (!cur) { cur = w; continue; }
87
- if (measureText(cur + ' ' + w, fontSize) > fixedWidth) { lc++; cur = w; }
88
- else cur += ' ' + w;
142
+ // Greedy word-wrap to a pixel width. Honors existing \n and HARD-breaks any single word wider than
143
+ // the box (e.g. a long URL). Returns the text WITH newlines baked in mirrors wrapToWidth() in
144
+ // scripts/lib/style.js. Baking is what actually fixes over-wide text: a width-set text element with no
145
+ // hard newlines is re-flowed by the renderer into ONE line wider than its box (captions then overlap
146
+ // their neighbours). Baking the breaks makes every wrapped label render at the intended measure.
147
+ // A logical line's LEADING INDENT is preserved and re-applied to every line it wraps into — that is
148
+ // what keeps a hanging indent (bullets(), indented notes) intact through the bake. Without it the
149
+ // wrap would collapse the author's indentation and continuation lines would snap to the left margin.
150
+ function wrapText(text, fontSize, width) {
151
+ const out = [];
152
+ for (const logical of String(text).split('\n')) {
153
+ const indent = (logical.match(/^[ \t]*/) || [''])[0];
154
+ const avail = Math.max(1, width - measureText(indent, fontSize));
155
+ const push = (s) => out.push(indent + s);
156
+ const words = logical.slice(indent.length).split(/\s+/).filter(Boolean);
157
+ if (!words.length) { out.push(''); continue; }
158
+ let cur = '';
159
+ for (let word of words) {
160
+ while (measureText(word, fontSize) > avail) {
161
+ let acc = '';
162
+ for (const ch of word) { if (acc && measureText(acc + ch, fontSize) > avail) break; acc += ch; }
163
+ if (cur) { push(cur); cur = ''; }
164
+ push(acc); word = word.slice(acc.length); if (!word) break;
89
165
  }
90
- totalLines += lc;
166
+ if (!word) continue;
167
+ const cand = cur ? cur + ' ' + word : word;
168
+ if (cur && measureText(cand, fontSize) > avail) { push(cur); cur = word; }
169
+ else cur = cand;
91
170
  }
92
- return { width: fixedWidth, height: Math.max(Math.round(fontSize * 1.25), Math.round(totalLines * fontSize * 1.25)) };
171
+ if (cur) push(cur);
172
+ }
173
+ return out.join('\n');
174
+ }
175
+
176
+ // When a fixedWidth is given the text WRAPS to it (autoResize off) and the wrapped text is baked with
177
+ // newlines, so it renders at the intended measure and its height reflects the real line count.
178
+ // Returns { width, height, text } — `text` carries the baked newlines when a width was given.
179
+ function sizeText(text, fontSize, fixedWidth) {
180
+ if (fixedWidth != null) {
181
+ const wrapped = wrapText(text, fontSize, fixedWidth);
182
+ const lc = wrapped.split('\n').length;
183
+ return { width: fixedWidth, height: Math.max(Math.round(fontSize * 1.25), Math.round(lc * fontSize * 1.25)), text: wrapped };
93
184
  }
185
+ const rawLines = String(text).split('\n');
94
186
  const maxW = rawLines.reduce((m, l) => Math.max(m, measureText(l, fontSize)), 0);
95
- return { width: Math.max(10, Math.round(maxW)), height: Math.round(rawLines.length * fontSize * 1.25) };
187
+ return { width: Math.max(10, Math.round(maxW)), height: Math.round(rawLines.length * fontSize * 1.25), text: String(text) };
96
188
  }
97
189
 
98
190
  // ---------- element factory ----------
@@ -123,10 +215,159 @@ function baseEl(rng, type, x, y, w, h, overrides) {
123
215
  }, overrides || {});
124
216
  }
125
217
 
218
+ // ---------- overlap auditor ----------
219
+ // Overlapping opaque boxes are the #1 thing that makes a generated board look broken. After the scene
220
+ // is built we scan for it and warn — so a hand-authored spec that miscomputed a coordinate gets caught
221
+ // at generate time instead of in the reader's face. We flag SIBLING COLLISIONS: two comparable filled
222
+ // boxes (rect / ellipse / diamond, or images) that partially overlap or fully stack. We do NOT flag
223
+ // CONTAINMENT — a small box nested inside a much larger one (a button inside a window, a dot inside a
224
+ // title bar, content inside a frame) is the normal parent/child relationship. Grouped composites (a
225
+ // card's own label, a window's own chrome) and connectors / transparent shapes are skipped too.
226
+ // Disable with `spec.audit === false`.
227
+ function auditOverlaps(elements) {
228
+ const label = (e) => {
229
+ if (e.type === 'text') return JSON.stringify(String(e.text).split('\n')[0].slice(0, 24));
230
+ return `${e.type}@(${Math.round(e.x)},${Math.round(e.y)})`;
231
+ };
232
+ const grp = (e) => (e.groupIds && e.groupIds[0]) || null;
233
+ const isOpaqueBox = (e) => (
234
+ ((e.type === 'rectangle' || e.type === 'ellipse' || e.type === 'diamond') && e.backgroundColor && e.backgroundColor !== 'transparent') ||
235
+ e.type === 'image'
236
+ );
237
+ const area = (e) => Math.max(0, e.width) * Math.max(0, e.height);
238
+ const overlap = (a, b) => {
239
+ const ix = Math.max(0, Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x));
240
+ const iy = Math.max(0, Math.min(a.y + a.height, b.y + b.height) - Math.max(a.y, b.y));
241
+ return ix * iy;
242
+ };
243
+ const boxes = elements.filter(isOpaqueBox);
244
+ const hits = [];
245
+ for (let i = 0; i < boxes.length; i++) {
246
+ for (let j = i + 1; j < boxes.length; j++) {
247
+ const a = boxes[i], b = boxes[j];
248
+ if (grp(a) && grp(a) === grp(b)) continue; // same composite ⇒ intentional
249
+ const ov = overlap(a, b); if (ov <= 0) continue;
250
+ const minA = Math.min(area(a), area(b)), maxA = Math.max(area(a), area(b));
251
+ const cover = ov / Math.max(1, minA); // fraction of the SMALLER box that is covered
252
+ const sizeRatio = minA / Math.max(1, maxA); // ~1 = comparable boxes, ~0 = tiny-in-huge
253
+ if (cover > 0.85 && sizeRatio < 0.5) continue; // small box nested in a big container ⇒ intentional
254
+ if (cover > 0.12) hits.push({ a, b, frac: cover }); // sibling boxes covering >12% of the smaller ⇒ collision
255
+ }
256
+ }
257
+ if (hits.length) {
258
+ const sample = hits.slice(0, 5).map((h) => ` ${label(h.a)} ↔ ${label(h.b)} (${Math.round(h.frac * 100)}%)`).join('\n');
259
+ try {
260
+ console.warn(`[excalidraw] ${hits.length} overlap(s) detected — boards read cleaner when boxes don't stack.\n${sample}${hits.length > 5 ? '\n …' : ''}\n Fix: lay out with stack()/row() (flow layout) or move the boxes apart.`);
261
+ } catch (e) {}
262
+ }
263
+ return hits.length;
264
+ }
265
+
266
+ // A text element buried under a foreign opaque box. The box-vs-box audit above cannot see this: a
267
+ // section title swallowed by a card is text-vs-box, and it ships looking like `overlaps: 0`.
268
+ //
269
+ // Z-ORDER is the discriminator, and it is what keeps this check honest. Excalidraw paints in array
270
+ // order, so a box only BURIES text that was pushed BEFORE it. Text pushed after a box is drawn on top
271
+ // — that's just a label on a shape (the `hello.spec.json` pattern), which is fine and must not warn.
272
+ // Text inside its OWN composite (same group) never flags either.
273
+ function auditTextCollisions(elements) {
274
+ const zOf = new Map();
275
+ elements.forEach((e, i) => zOf.set(e, i));
276
+ const isOpaqueBox = (e) => (
277
+ ((e.type === 'rectangle' || e.type === 'ellipse' || e.type === 'diamond') && e.backgroundColor && e.backgroundColor !== 'transparent') ||
278
+ e.type === 'image'
279
+ );
280
+ const grp = (e) => (e.groupIds && e.groupIds[0]) || null;
281
+ const texts = elements.filter((e) => e.type === 'text' && e.width > 0 && e.height > 0);
282
+ const boxes = elements.filter(isOpaqueBox);
283
+ const hits = [];
284
+ for (const t of texts) {
285
+ // Check EACH LINE, not the element as a whole: a 2-line title with its second line swallowed only
286
+ // scores ~18% element-wide and would slip through, yet that line is just as unreadable. A line is
287
+ // also only as wide as its glyphs — measuring the full `width` box would over-report centered text.
288
+ const lines = String(t.text).split('\n');
289
+ const lh = (t.fontSize || 20) * 1.25;
290
+ let worst = null;
291
+ for (let i = 0; i < lines.length; i++) {
292
+ const lw = measureText(lines[i], t.fontSize || 20);
293
+ if (lw <= 0) continue;
294
+ const align = t.textAlign || 'left';
295
+ const lx = align === 'center' ? t.x + (t.width - lw) / 2 : align === 'right' ? t.x + t.width - lw : t.x;
296
+ const ly = t.y + i * lh;
297
+ const lArea = Math.max(1, lw * lh);
298
+ for (const b of boxes) {
299
+ if (grp(t) && grp(t) === grp(b)) continue; // its own composite's chrome
300
+ if (zOf.get(b) < zOf.get(t)) continue; // box painted first ⇒ text sits ON it (a label)
301
+ const ix = Math.max(0, Math.min(lx + lw, b.x + b.width) - Math.max(lx, b.x));
302
+ const iy = Math.max(0, Math.min(ly + lh, b.y + b.height) - Math.max(ly, b.y));
303
+ const frac = (ix * iy) / lArea;
304
+ if (frac > 0.3 && (!worst || frac > worst.frac)) worst = { t, b, frac, line: lines[i] };
305
+ }
306
+ // Text-on-text. Two texts have no reason to share pixels — this is always a defect, and z-order
307
+ // can't save it (both are painted, so they just tangle). A wrapped title landing on the subtitle
308
+ // beneath it is the classic case, and neither the box audit nor the measure audit can see it.
309
+ for (const u of texts) {
310
+ if (u === t) continue;
311
+ if (grp(t) && grp(t) === grp(u)) continue;
312
+ if (zOf.get(u) < zOf.get(t)) continue; // report the pair once, from the lower element
313
+ const ulines = String(u.text).split('\n');
314
+ const ulh = (u.fontSize || 20) * 1.25;
315
+ for (let k = 0; k < ulines.length; k++) {
316
+ const uw = measureText(ulines[k], u.fontSize || 20);
317
+ if (uw <= 0) continue;
318
+ const ua = u.textAlign || 'left';
319
+ const ux = ua === 'center' ? u.x + (u.width - uw) / 2 : ua === 'right' ? u.x + u.width - uw : u.x;
320
+ const uy = u.y + k * ulh;
321
+ const ix = Math.max(0, Math.min(lx + lw, ux + uw) - Math.max(lx, ux));
322
+ const iy = Math.max(0, Math.min(ly + lh, uy + ulh) - Math.max(ly, uy));
323
+ const frac = (ix * iy) / lArea;
324
+ if (frac > 0.3 && (!worst || frac > worst.frac)) worst = { t, b: u, frac, line: lines[i], vsText: true };
325
+ }
326
+ }
327
+ }
328
+ if (worst) hits.push(worst);
329
+ }
330
+ if (hits.length) {
331
+ const sample = hits.slice(0, 5).map((h) => {
332
+ const onto = h.vsText ? `text ${JSON.stringify(String(h.b.text).split('\n')[0].slice(0, 22))}` : `${h.b.type}@(${Math.round(h.b.x)},${Math.round(h.b.y)})`;
333
+ return ` ${JSON.stringify(String(h.line).slice(0, 30))} collides with ${onto} (${Math.round(h.frac * 100)}%)`;
334
+ }).join('\n');
335
+ try {
336
+ console.warn(`[excalidraw] ${hits.length} text element(s) collide — the label is unreadable.\n${sample}${hits.length > 5 ? '\n …' : ''}\n Fix: flow the region with stack()/row(); a wrapped title landing on the text below is the usual cause.`);
337
+ } catch (e) {}
338
+ }
339
+ return hits.length;
340
+ }
341
+
342
+ // Body copy that runs past the reading measure. This is the skill's headline rule and it was, until
343
+ // now, unenforced — a board could ship 1400px lines reporting a clean audit. Display type (>= 28px)
344
+ // is scanned, not read, so it is exempt; a 10% tolerance keeps borderline headings quiet.
345
+ function auditMeasure(elements, limit) {
346
+ const cap = (limit || STYLE.READ_W) * 1.1;
347
+ const hits = [];
348
+ for (const e of elements) {
349
+ if (e.type !== 'text' || !e.text || e.fontSize >= 28) continue;
350
+ let longest = 0;
351
+ for (const line of String(e.text).split('\n')) longest = Math.max(longest, STYLE.measureText(line, e.fontSize));
352
+ if (longest > cap) hits.push({ e, longest });
353
+ }
354
+ if (hits.length) {
355
+ hits.sort((a, b) => b.longest - a.longest);
356
+ const sample = hits.slice(0, 5).map((h) => ` ${Math.round(h.longest)}px ${JSON.stringify(String(h.e.text).split('\n')[0].slice(0, 36))}`).join('\n');
357
+ try {
358
+ console.warn(`[excalidraw] ${hits.length} text block(s) exceed the reading measure (${Math.round(cap)}px) — long lines are the #1 readability killer.\n${sample}${hits.length > 5 ? '\n …' : ''}\n Fix: use prose()/bullets()/callout() (they cap at READ_W), or set an explicit \`width\` on the text element.`);
359
+ } catch (e) {}
360
+ }
361
+ return hits.length;
362
+ }
363
+
126
364
  // ---------- main build ----------
127
365
  function buildExcalidraw(spec) {
128
366
  const outPath = spec.out;
129
367
  if (!outPath) throw new Error('spec.out (output .excalidraw.md path) is required');
368
+ // Expand composite types (charts, house-style widgets, stack/row) down to primitives first, so the
369
+ // rest of the build only ever sees text/image/rectangle/line/… — one code path for JSON and JS.
370
+ spec = Object.assign({}, spec, { elements: expandElements(spec.elements) });
130
371
  const boardDir = path.dirname(path.resolve(outPath));
131
372
  const vaultRoot = spec.vaultRoot || findVaultRoot(boardDir) || boardDir;
132
373
  const attachDir = spec.attachDir || 'Attachments';
@@ -201,12 +442,12 @@ function buildExcalidraw(spec) {
201
442
  const type = e.type;
202
443
  if (type === 'text') {
203
444
  const fontSize = e.fontSize || 20;
204
- const { width, height } = sizeText(e.text, fontSize, e.width);
445
+ const { width, height, text: baked } = sizeText(e.text, fontSize, e.width);
205
446
  const el = baseEl(rng, 'text', e.x || 0, e.y || 0, width, height, {
206
447
  strokeColor: e.color || '#1e1e1e',
207
- text: String(e.text),
208
- rawText: String(e.text),
209
- originalText: String(e.text),
448
+ text: baked,
449
+ rawText: baked,
450
+ originalText: baked,
210
451
  fontSize,
211
452
  fontFamily: e.fontFamily || 5,
212
453
  textAlign: e.align || 'left',
@@ -238,7 +479,7 @@ function buildExcalidraw(spec) {
238
479
  strokeColor: e.strokeColor || '#1e1e1e',
239
480
  backgroundColor: e.backgroundColor || 'transparent',
240
481
  fillStyle: e.fillStyle || 'solid',
241
- strokeWidth: e.strokeWidth || 2,
482
+ strokeWidth: e.strokeWidth != null ? e.strokeWidth : 2,
242
483
  strokeStyle: e.strokeStyle || 'solid',
243
484
  roundness: e.roundness ? { type: 3 } : null,
244
485
  });
@@ -249,10 +490,16 @@ function buildExcalidraw(spec) {
249
490
  const minX = Math.min(...xs); const minY = Math.min(...ys);
250
491
  const norm = pts.map((p) => [p[0] - minX, p[1] - minY]);
251
492
  const w = Math.max(...xs) - minX; const h = Math.max(...ys) - minY;
493
+ // A `line` may be a FILLED polygon: pass backgroundColor + fillStyle and close the path
494
+ // (first point repeated as last) and Excalidraw fills it — this is how funnel trapezoids and
495
+ // custom regions are drawn. Arrows never fill. roundness softens polygon corners when asked.
252
496
  const el = baseEl(rng, type, (e.x || 0) + minX, (e.y || 0) + minY, w, h, {
253
497
  strokeColor: e.strokeColor || '#1e1e1e',
254
- strokeWidth: e.strokeWidth || 2,
498
+ backgroundColor: type === 'line' ? (e.backgroundColor || 'transparent') : 'transparent',
499
+ fillStyle: e.fillStyle || 'solid',
500
+ strokeWidth: e.strokeWidth != null ? e.strokeWidth : 2,
255
501
  strokeStyle: e.strokeStyle || 'solid',
502
+ roundness: e.roundness ? { type: 2 } : null,
256
503
  points: norm,
257
504
  lastCommittedPoint: null,
258
505
  startBinding: null,
@@ -280,6 +527,15 @@ function buildExcalidraw(spec) {
280
527
  const keys = generateNKeysBetween(null, null, elements.length);
281
528
  elements.forEach((el, i) => { el.index = keys[i]; });
282
529
 
530
+ // Catch the three ways a board ships broken (opt out with spec.audit === false):
531
+ // overlaps — two opaque boxes stacked
532
+ // buriedText — a label swallowed by a foreign box (box-vs-box audit is blind to this)
533
+ // longLines — body copy past the reading measure
534
+ const doAudit = spec.audit !== false;
535
+ const overlaps = doAudit ? auditOverlaps(elements) : 0;
536
+ const buriedText = doAudit ? auditTextCollisions(elements) : 0;
537
+ const longLines = doAudit ? auditMeasure(elements, spec.measure) : 0;
538
+
283
539
  const scene = {
284
540
  type: 'excalidraw',
285
541
  version: 2,
@@ -291,7 +547,16 @@ function buildExcalidraw(spec) {
291
547
 
292
548
  // ---------- assemble markdown ----------
293
549
  const L = [];
294
- L.push('---', '', 'excalidraw-plugin: parsed', 'tags: [excalidraw]', '', '---');
550
+ // Frontmatter. dreamcontext REQUIRES `name` + `description` to index/recall a board — memory reads
551
+ // frontmatter + `## Text Elements`, never the scene JSON — so emit them when given rather than
552
+ // making every generator hand-patch the file afterwards.
553
+ L.push('---', '');
554
+ if (spec.name) L.push(`name: ${yamlScalar(spec.name)}`);
555
+ if (spec.description) L.push(...yamlFolded('description', spec.description));
556
+ const tags = spec.tags && spec.tags.length ? spec.tags : ['excalidraw'];
557
+ L.push(`tags: [${tags.join(', ')}]`);
558
+ for (const [k, v] of Object.entries(spec.frontmatter || {})) L.push(`${k}: ${yamlScalar(v)}`);
559
+ L.push('excalidraw-plugin: parsed', '', '---');
295
560
  L.push("==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: 'Decompress current Excalidraw file'. For more info check in plugin settings under 'Saving'");
296
561
  L.push('', '', '# Excalidraw Data', '');
297
562
  L.push('## Text Elements');
@@ -309,7 +574,7 @@ function buildExcalidraw(spec) {
309
574
  fs.mkdirSync(boardDir, { recursive: true });
310
575
  fs.writeFileSync(outPath, md, 'utf8');
311
576
 
312
- return { outPath, elements: elements.length, images: embedded.size, texts: textEntries.length, vaultRoot };
577
+ return { outPath, elements: elements.length, images: embedded.size, texts: textEntries.length, overlaps, buriedText, longLines, vaultRoot };
313
578
  }
314
579
 
315
580
  // ---------- layout helpers (JS API) ----------
@@ -392,5 +657,5 @@ if (require.main === module) {
392
657
  const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
393
658
  if (out) spec.out = out;
394
659
  const res = buildExcalidraw(spec);
395
- console.log(`OK ${res.outPath}\n elements=${res.elements} images=${res.images} texts=${res.texts}\n vaultRoot=${res.vaultRoot}`);
660
+ console.log(`OK ${res.outPath}\n elements=${res.elements} images=${res.images} texts=${res.texts} overlaps=${res.overlaps} buriedText=${res.buriedText} longLines=${res.longLines}\n vaultRoot=${res.vaultRoot}`);
396
661
  }