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.
- package/README.md +321 -565
- package/agents/sleep-product.md +21 -2
- package/agents/sleep-state.md +24 -0
- package/agents/sleep-tasks.md +16 -0
- package/dist/agents/sleep-product.md +21 -2
- package/dist/agents/sleep-state.md +24 -0
- package/dist/agents/sleep-tasks.md +16 -0
- package/dist/dashboard/announcements/dashboard-highlights-0-17-0-18.excalidraw.md +1531 -0
- package/dist/dashboard/announcements/goal-skill-v2.excalidraw.md +1490 -0
- package/dist/dashboard/announcements/task-manager.excalidraw.md +1217 -0
- package/dist/dashboard/announcements/visual-announcements.excalidraw.md +1373 -0
- package/dist/dashboard/announcements.json +38 -0
- package/dist/dashboard/assets/{BrainCanvas3D-DyF_3yYD.js → BrainCanvas3D-D9G3dOLf.js} +1 -1
- package/dist/dashboard/assets/{_baseUniq-Do8VMBA0.js → _baseUniq-Cyshlz4Y.js} +1 -1
- package/dist/dashboard/assets/{ar-SA-G6X2FPQ2-DmbXDb2R.js → ar-SA-G6X2FPQ2-qj-2Cbgo.js} +1 -1
- package/dist/dashboard/assets/{arc-CwtUBQfU.js → arc-0OnX5WDs.js} +1 -1
- package/dist/dashboard/assets/{architectureDiagram-Q4EWVU46-O9bOog_f.js → architectureDiagram-Q4EWVU46-Cx3c0nUi.js} +1 -1
- package/dist/dashboard/assets/{az-AZ-76LH7QW2-c9Zk10vG.js → az-AZ-76LH7QW2-CBuoh6M3.js} +1 -1
- package/dist/dashboard/assets/{bg-BG-XCXSNQG7-uMTg84lX.js → bg-BG-XCXSNQG7-X5QYyZWx.js} +1 -1
- package/dist/dashboard/assets/{blockDiagram-DXYQGD6D-BTENXCSO.js → blockDiagram-DXYQGD6D-BwESAg1j.js} +1 -1
- package/dist/dashboard/assets/{bn-BD-2XOGV67Q-DUFKHcua.js → bn-BD-2XOGV67Q-DlodQLNR.js} +1 -1
- package/dist/dashboard/assets/{c4Diagram-AHTNJAMY-DMXPWRiQ.js → c4Diagram-AHTNJAMY-CopL0lbP.js} +1 -1
- package/dist/dashboard/assets/{ca-ES-6MX7JW3Y-BuYX1q89.js → ca-ES-6MX7JW3Y-DfqF6Yhy.js} +1 -1
- package/dist/dashboard/assets/channel-BLisRbYb.js +1 -0
- package/dist/dashboard/assets/{chunk-4BX2VUAB-DgefvnWS.js → chunk-4BX2VUAB-CRwifLK3.js} +1 -1
- package/dist/dashboard/assets/{chunk-4TB4RGXK-BnFO5G1t.js → chunk-4TB4RGXK-uxJYY_64.js} +1 -1
- package/dist/dashboard/assets/{chunk-55IACEB6-COm2TYEM.js → chunk-55IACEB6-DhYIp7Oc.js} +1 -1
- package/dist/dashboard/assets/{chunk-EDXVE4YY-CtsGCwZp.js → chunk-EDXVE4YY-CCxfcirt.js} +1 -1
- package/dist/dashboard/assets/{chunk-FMBD7UC4-cfSvx35j.js → chunk-FMBD7UC4-D7OC8QFC.js} +1 -1
- package/dist/dashboard/assets/{chunk-OYMX7WX6-DAhDKteL.js → chunk-OYMX7WX6-CwHgaUQo.js} +1 -1
- package/dist/dashboard/assets/{chunk-QZHKN3VN-B5h4qML3.js → chunk-QZHKN3VN-7Idpf6YJ.js} +1 -1
- package/dist/dashboard/assets/{chunk-YZCP3GAM-CGp_NiRg.js → chunk-YZCP3GAM-D4Lh0fgr.js} +1 -1
- package/dist/dashboard/assets/classDiagram-6PBFFD2Q-CgqeQiHi.js +1 -0
- package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-CgqeQiHi.js +1 -0
- package/dist/dashboard/assets/clone-CQXNMCV5.js +1 -0
- package/dist/dashboard/assets/{cose-bilkent-S5V4N54A-Dk7CMFA4.js → cose-bilkent-S5V4N54A-BYcMtJTF.js} +1 -1
- package/dist/dashboard/assets/{cs-CZ-2BRQDIVT-zfUTiS_d.js → cs-CZ-2BRQDIVT-rnrpKdSJ.js} +1 -1
- package/dist/dashboard/assets/{da-DK-5WZEPLOC-CU14f8NA.js → da-DK-5WZEPLOC-D5OGAvk7.js} +1 -1
- package/dist/dashboard/assets/{dagre-KV5264BT-BiMVxC4Q.js → dagre-KV5264BT-V0f1_b4R.js} +1 -1
- package/dist/dashboard/assets/{de-DE-XR44H4JA-CDOHAJGo.js → de-DE-XR44H4JA-CDyb8YfL.js} +1 -1
- package/dist/dashboard/assets/{diagram-5BDNPKRD-DfJEsVTA.js → diagram-5BDNPKRD-BmpizoaS.js} +1 -1
- package/dist/dashboard/assets/{diagram-G4DWMVQ6-CztLHIcY.js → diagram-G4DWMVQ6-ClQg-LTu.js} +1 -1
- package/dist/dashboard/assets/{diagram-MMDJMWI5-DqoQLlWK.js → diagram-MMDJMWI5-BkXcDMIV.js} +1 -1
- package/dist/dashboard/assets/{diagram-TYMM5635-DvyvQBCc.js → diagram-TYMM5635-BbFiiCM2.js} +1 -1
- package/dist/dashboard/assets/{el-GR-BZB4AONW-ChUphbcv.js → el-GR-BZB4AONW-Y1YnKZL0.js} +1 -1
- package/dist/dashboard/assets/{erDiagram-SMLLAGMA-D_uSs2_2.js → erDiagram-SMLLAGMA-Dzdv7_l5.js} +1 -1
- package/dist/dashboard/assets/{es-ES-U4NZUMDT-BNalBEhb.js → es-ES-U4NZUMDT-D0T536hs.js} +1 -1
- package/dist/dashboard/assets/{eu-ES-A7QVB2H4-BMiQyA2y.js → eu-ES-A7QVB2H4-Bfrya0et.js} +1 -1
- package/dist/dashboard/assets/{fa-IR-HGAKTJCU-Bd6ZnTSo.js → fa-IR-HGAKTJCU-pYE_hG-L.js} +1 -1
- package/dist/dashboard/assets/{fi-FI-Z5N7JZ37-BWAfJadC.js → fi-FI-Z5N7JZ37-D3C-fNyk.js} +1 -1
- package/dist/dashboard/assets/{flowDiagram-DWJPFMVM-Bmsp6kyU.js → flowDiagram-DWJPFMVM-CFhyjwKY.js} +1 -1
- package/dist/dashboard/assets/{fr-FR-RHASNOE6-CspbYzOW.js → fr-FR-RHASNOE6-CgwnG5JW.js} +1 -1
- package/dist/dashboard/assets/{ganttDiagram-T4ZO3ILL-wEdysXo_.js → ganttDiagram-T4ZO3ILL-BRKRiSzf.js} +1 -1
- package/dist/dashboard/assets/{gitGraphDiagram-UUTBAWPF-BEiGpPBj.js → gitGraphDiagram-UUTBAWPF-CTqCK49d.js} +1 -1
- package/dist/dashboard/assets/{gl-ES-HMX3MZ6V-CV76vsWr.js → gl-ES-HMX3MZ6V-DjJTqTm3.js} +1 -1
- package/dist/dashboard/assets/{graph-CZFIeNHZ.js → graph-D429kSbY.js} +1 -1
- package/dist/dashboard/assets/{he-IL-6SHJWFNN-Y1VoXJ_H.js → he-IL-6SHJWFNN-C84FlH6r.js} +1 -1
- package/dist/dashboard/assets/{hi-IN-IWLTKZ5I-Crh5S49o.js → hi-IN-IWLTKZ5I-Blhkfum2.js} +1 -1
- package/dist/dashboard/assets/{hu-HU-A5ZG7DT2-D4I3gCbp.js → hu-HU-A5ZG7DT2-DLC1JfN-.js} +1 -1
- package/dist/dashboard/assets/{id-ID-SAP4L64H-Chvzkuvi.js → id-ID-SAP4L64H-C3pB_IWB.js} +1 -1
- package/dist/dashboard/assets/image-FgR93jdt.js +1 -0
- package/dist/dashboard/assets/index-2m8ueOUn.js +1 -0
- package/dist/dashboard/assets/index-CWwjuihq.css +32 -0
- package/dist/dashboard/assets/{index-C7Bo5AQH.js → index-CbOXCDms.js} +1 -1
- package/dist/dashboard/assets/index-SJ64pf-4.js +551 -0
- package/dist/dashboard/assets/{infoDiagram-42DDH7IO-BzfuDHl4.js → infoDiagram-42DDH7IO-CIzDShDw.js} +1 -1
- package/dist/dashboard/assets/{ishikawaDiagram-UXIWVN3A-BVoNJ9VS.js → ishikawaDiagram-UXIWVN3A-CuasYzdX.js} +1 -1
- package/dist/dashboard/assets/{it-IT-JPQ66NNP-BH_gN5es.js → it-IT-JPQ66NNP-DGGEoNPu.js} +1 -1
- package/dist/dashboard/assets/{ja-JP-DBVTYXUO-DU_WmLVa.js → ja-JP-DBVTYXUO-CEqP9aps.js} +1 -1
- package/dist/dashboard/assets/{journeyDiagram-VCZTEJTY-DbwICa_a.js → journeyDiagram-VCZTEJTY-DHiq2OTQ.js} +1 -1
- package/dist/dashboard/assets/{kaa-6HZHGXH3-KYU9qowM.js → kaa-6HZHGXH3-GOFmTAXq.js} +1 -1
- package/dist/dashboard/assets/{kab-KAB-ZGHBKWFO-bjhyiUq5.js → kab-KAB-ZGHBKWFO-B865Wwzu.js} +1 -1
- package/dist/dashboard/assets/{kanban-definition-6JOO6SKY-D9XucLs4.js → kanban-definition-6JOO6SKY-BhlTKFbL.js} +1 -1
- package/dist/dashboard/assets/{kk-KZ-P5N5QNE5-CYTx_dAZ.js → kk-KZ-P5N5QNE5-CNfuE6b6.js} +1 -1
- package/dist/dashboard/assets/{km-KH-HSX4SM5Z-DqsuY4Lf.js → km-KH-HSX4SM5Z-CsmgRWxu.js} +1 -1
- package/dist/dashboard/assets/{ko-KR-MTYHY66A-BZe7NywF.js → ko-KR-MTYHY66A-B_02RgEz.js} +1 -1
- package/dist/dashboard/assets/{ku-TR-6OUDTVRD-F5d6HFWt.js → ku-TR-6OUDTVRD-JfQA5u05.js} +1 -1
- package/dist/dashboard/assets/{layout-CotqU0S9.js → layout-DvrQSvOI.js} +1 -1
- package/dist/dashboard/assets/{linear-Dqv87Scf.js → linear-6r0qttLs.js} +1 -1
- package/dist/dashboard/assets/{lt-LT-XHIRWOB4-BCCuaPbS.js → lt-LT-XHIRWOB4-DWFUXEdf.js} +1 -1
- package/dist/dashboard/assets/{lv-LV-5QDEKY6T-NLGi-vJ6.js → lv-LV-5QDEKY6T-C5yAd0n_.js} +1 -1
- package/dist/dashboard/assets/{min-BbJdnc3h.js → min-D_EP9dBf.js} +1 -1
- package/dist/dashboard/assets/{mindmap-definition-QFDTVHPH-Dk4QySu7.js → mindmap-definition-QFDTVHPH-Col3ao9B.js} +1 -1
- package/dist/dashboard/assets/{mr-IN-CRQNXWMA-BnGDIgVN.js → mr-IN-CRQNXWMA-Dwjz0Xdm.js} +1 -1
- package/dist/dashboard/assets/{my-MM-5M5IBNSE-CspgYE9_.js → my-MM-5M5IBNSE-EXJDNyjA.js} +1 -1
- package/dist/dashboard/assets/{nb-NO-T6EIAALU-BeRPw3hu.js → nb-NO-T6EIAALU-BWm2bgLd.js} +1 -1
- package/dist/dashboard/assets/{nl-NL-IS3SIHDZ-C-G-bljM.js → nl-NL-IS3SIHDZ-f8nP-yp-.js} +1 -1
- package/dist/dashboard/assets/{nn-NO-6E72VCQL-Ct7VZY8D.js → nn-NO-6E72VCQL-B0c_gCNR.js} +1 -1
- package/dist/dashboard/assets/{oc-FR-POXYY2M6-Cv5-C4Qq.js → oc-FR-POXYY2M6-C40PSwAh.js} +1 -1
- package/dist/dashboard/assets/{pa-IN-N4M65BXN-DAgkIThL.js → pa-IN-N4M65BXN-DDcgXFkB.js} +1 -1
- package/dist/dashboard/assets/{percentages-BXMCSKIN-vidRYabK.js → percentages-BXMCSKIN-MHBQP8nI.js} +7 -7
- package/dist/dashboard/assets/{pica-Cy-Xl-mi.js → pica-C5CFA09W.js} +1 -1
- package/dist/dashboard/assets/{pieDiagram-DEJITSTG-BnP4-zwd.js → pieDiagram-DEJITSTG-DFHmB0fL.js} +1 -1
- package/dist/dashboard/assets/{pl-PL-T2D74RX3-BuJQ3HD-.js → pl-PL-T2D74RX3-yFTBt0Xu.js} +1 -1
- package/dist/dashboard/assets/{pt-BR-5N22H2LF-BbAqsvD6.js → pt-BR-5N22H2LF-ilAW3Fdv.js} +1 -1
- package/dist/dashboard/assets/{pt-PT-UZXXM6DQ-DzFJedU3.js → pt-PT-UZXXM6DQ-CXHFN95K.js} +1 -1
- package/dist/dashboard/assets/{quadrantDiagram-34T5L4WZ-BXN88cHy.js → quadrantDiagram-34T5L4WZ-CR732_oP.js} +1 -1
- package/dist/dashboard/assets/{requirementDiagram-MS252O5E-D34hL42a.js → requirementDiagram-MS252O5E-mKoVG52Z.js} +1 -1
- package/dist/dashboard/assets/{ro-RO-JPDTUUEW-BV2-sOiv.js → ro-RO-JPDTUUEW-BKlWpyF4.js} +1 -1
- package/dist/dashboard/assets/{ru-RU-B4JR7IUQ-BCL4N9Ej.js → ru-RU-B4JR7IUQ-BGoxlhJm.js} +1 -1
- package/dist/dashboard/assets/{sankeyDiagram-XADWPNL6-0mFeok-3.js → sankeyDiagram-XADWPNL6-DXipts7M.js} +1 -1
- package/dist/dashboard/assets/{sequenceDiagram-FGHM5R23-C-5nW5j6.js → sequenceDiagram-FGHM5R23-DFQhU8q7.js} +1 -1
- package/dist/dashboard/assets/{si-LK-N5RQ5JYF-CNhaw4jx.js → si-LK-N5RQ5JYF-yDbJWFTH.js} +1 -1
- package/dist/dashboard/assets/{sk-SK-C5VTKIMK-mHCphOXu.js → sk-SK-C5VTKIMK-DglyhbNB.js} +1 -1
- package/dist/dashboard/assets/{sl-SI-NN7IZMDC--lzgHthQ.js → sl-SI-NN7IZMDC-DPfq0s4I.js} +1 -1
- package/dist/dashboard/assets/{stateDiagram-FHFEXIEX-C50iDFqe.js → stateDiagram-FHFEXIEX-Dm50jiEh.js} +1 -1
- package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-CoSm11sD.js +1 -0
- package/dist/dashboard/assets/{subset-shared.chunk-DNsjL37s.js → subset-shared.chunk-OHz6M6ct.js} +1 -1
- package/dist/dashboard/assets/{subset-worker.chunk-BaV3r-bl.js → subset-worker.chunk-BzwrOBtO.js} +1 -1
- package/dist/dashboard/assets/{sv-SE-XGPEYMSR-ckw8jfGN.js → sv-SE-XGPEYMSR-BLHNwr4k.js} +1 -1
- package/dist/dashboard/assets/{ta-IN-2NMHFXQM-CSeNmVhS.js → ta-IN-2NMHFXQM-Dw-6hUpN.js} +1 -1
- package/dist/dashboard/assets/{th-TH-HPSO5L25-Cvn1sg_3.js → th-TH-HPSO5L25-DPkcM_k9.js} +1 -1
- package/dist/dashboard/assets/{timeline-definition-GMOUNBTQ-DRYfuqHT.js → timeline-definition-GMOUNBTQ-BKFNQ_7e.js} +1 -1
- package/dist/dashboard/assets/{tr-TR-DEFEU3FU-CyhY22TB.js → tr-TR-DEFEU3FU-rBVsY5RS.js} +1 -1
- package/dist/dashboard/assets/{uk-UA-QMV73CPH-BvvAEO6U.js → uk-UA-QMV73CPH-CV6_yTZs.js} +1 -1
- package/dist/dashboard/assets/{vennDiagram-DHZGUBPP-Crril7Cj.js → vennDiagram-DHZGUBPP-BZb_r5tY.js} +1 -1
- package/dist/dashboard/assets/{vi-VN-M7AON7JQ-c7AwSUZ8.js → vi-VN-M7AON7JQ-CJMgSsLz.js} +1 -1
- package/dist/dashboard/assets/{wardley-RL74JXVD-Da5AhSWU.js → wardley-RL74JXVD-DgqjmWyN.js} +1 -1
- package/dist/dashboard/assets/{wardleyDiagram-NUSXRM2D-C4GeB_Df.js → wardleyDiagram-NUSXRM2D-BDpniS9m.js} +1 -1
- package/dist/dashboard/assets/webviewWindow-Bwv2421L.js +1 -0
- package/dist/dashboard/assets/window-BObcHN8e.js +1 -0
- package/dist/dashboard/assets/{xychartDiagram-5P7HB3ND-BPCFKXqR.js → xychartDiagram-5P7HB3ND-CCyPcx2A.js} +1 -1
- package/dist/dashboard/assets/{zh-CN-LNUGB5OW-BvNiZwKU.js → zh-CN-LNUGB5OW-9LJLF_AC.js} +1 -1
- package/dist/dashboard/assets/{zh-HK-E62DVLB3-CCFtoCWb.js → zh-HK-E62DVLB3-IefAiePp.js} +1 -1
- package/dist/dashboard/assets/{zh-TW-RAJ6MFWO-bYBr-stu.js → zh-TW-RAJ6MFWO-CHeY9rNZ.js} +1 -1
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +4099 -1538
- package/dist/skill-packs/agents/goal-implementer.md +42 -1
- package/dist/skill-packs/agents/goal-plan-reviewer.md +7 -0
- package/dist/skill-packs/agents/goal-planner.md +34 -0
- package/dist/skill-packs/catalog.json +31 -21
- package/dist/skill-packs/excalidraw/SKILL.md +244 -13
- package/dist/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
- package/dist/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
- package/dist/skill-packs/excalidraw/examples/atomic_check.js +99 -0
- package/dist/skill-packs/excalidraw/examples/chart.spec.json +122 -0
- package/dist/skill-packs/excalidraw/examples/chart_board.js +173 -0
- package/dist/skill-packs/excalidraw/examples/hello.spec.json +79 -9
- package/dist/skill-packs/excalidraw/examples/visual_board.js +82 -0
- package/dist/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
- package/dist/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
- package/dist/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
- package/dist/skill-packs/excalidraw/scripts/lib/style.js +369 -35
- package/dist/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
- package/dist/skill-packs/goal-skill/SKILL.md +369 -100
- package/dist/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
- package/dist/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
- package/package.json +2 -1
- package/skill/references/integrations.md +54 -0
- package/skill/references/knowledge-and-recall.md +3 -1
- package/skill/references/sleep.md +4 -4
- package/skill-packs/agents/goal-implementer.md +42 -1
- package/skill-packs/agents/goal-plan-reviewer.md +7 -0
- package/skill-packs/agents/goal-planner.md +34 -0
- package/skill-packs/catalog.json +31 -21
- package/skill-packs/excalidraw/SKILL.md +244 -13
- package/skill-packs/excalidraw/examples/Chart Kit.excalidraw.md +15740 -0
- package/skill-packs/excalidraw/examples/Wireframe Kit.excalidraw.md +16860 -0
- package/skill-packs/excalidraw/examples/atomic_check.js +99 -0
- package/skill-packs/excalidraw/examples/chart.spec.json +122 -0
- package/skill-packs/excalidraw/examples/chart_board.js +173 -0
- package/skill-packs/excalidraw/examples/hello.spec.json +79 -9
- package/skill-packs/excalidraw/examples/visual_board.js +82 -0
- package/skill-packs/excalidraw/examples/wireframe_board.js +161 -0
- package/skill-packs/excalidraw/scripts/build_excalidraw.js +292 -27
- package/skill-packs/excalidraw/scripts/lib/charts.js +771 -0
- package/skill-packs/excalidraw/scripts/lib/style.js +369 -35
- package/skill-packs/excalidraw/scripts/lib/wireframe.js +406 -0
- package/skill-packs/goal-skill/SKILL.md +369 -100
- package/skill-packs/goal-skill/assets/goal-skill-demo.cjs +51 -0
- package/skill-packs/goal-skill/assets/goal-skill-viewer.cjs +181 -0
- package/skill-task-manager/SKILL.md +93 -0
- package/dist/dashboard/assets/channel-2YfRjala.js +0 -1
- package/dist/dashboard/assets/classDiagram-6PBFFD2Q-DB_UnN1h.js +0 -1
- package/dist/dashboard/assets/classDiagram-v2-HSJHXN6E-DB_UnN1h.js +0 -1
- package/dist/dashboard/assets/clone-DaDvB3N6.js +0 -1
- package/dist/dashboard/assets/index-BIfrbaFS.js +0 -516
- package/dist/dashboard/assets/index-BdZ3tppu.css +0 -32
- package/dist/dashboard/assets/stateDiagram-v2-QKLJ7IA2-eW9-s0Sm.js +0 -1
- package/dist/dashboard/assets/webviewWindow-BbG9Cr7f.js +0 -1
- 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
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
208
|
-
rawText:
|
|
209
|
-
originalText:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|