mulmoterminal 4.7.6 → 4.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +11 -3
  2. package/common/chromeFromColor.ts +33 -2
  3. package/common/collectionNotifyTarget.ts +12 -2
  4. package/common/collectionPortability.ts +84 -0
  5. package/common/pathSegments.ts +23 -0
  6. package/common/toolGroups.ts +14 -0
  7. package/dist/assets/{abnfDiagram-N423BO3Z-D0pzqjdS-mU_JC_gy.js → abnfDiagram-N423BO3Z-D0pzqjdS-CTSEh1uL.js} +1 -1
  8. package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-DYURJf3a.js +1 -0
  9. package/dist/assets/{architectureDiagram-T3A2C74G-CX-Yoe5G-BYABfiyL.js → architectureDiagram-T3A2C74G-CX-Yoe5G-D1TCsmEW.js} +1 -1
  10. package/dist/assets/{blockDiagram-VBNYF7ZC-B1eQZQ68-BzYgPGSd.js → blockDiagram-VBNYF7ZC-B1eQZQ68-0ZOTzgbN.js} +1 -1
  11. package/dist/assets/{c4Diagram-5PPSVZJV-SBrN9MrU-DeipaoWs.js → c4Diagram-5PPSVZJV-SBrN9MrU-Dj-EgTgq.js} +1 -1
  12. package/dist/assets/channel-DKMa7Sn5-BaH4NZYv.js +1 -0
  13. package/dist/assets/{chunk-2GRJ4B5K-DdkAx9o--B_kTCl3i.js → chunk-2GRJ4B5K-DdkAx9o--DjDCETVM.js} +1 -1
  14. package/dist/assets/{chunk-3NCLNEKW-CBztX243-DYiY7RPy.js → chunk-3NCLNEKW-CBztX243-DK2bYr2d.js} +1 -1
  15. package/dist/assets/{chunk-4I5QYGJK-CmBJaHzs-apWHpIvV.js → chunk-4I5QYGJK-CmBJaHzs-BdhY8OlN.js} +1 -1
  16. package/dist/assets/{chunk-5RXB4S5H-iaipMW_h-CdfjA_Tg.js → chunk-5RXB4S5H-iaipMW_h-Dc9HCjZr.js} +1 -1
  17. package/dist/assets/{chunk-6Q2QTUOP-Cx8nz5_t-CnRVqq3q.js → chunk-6Q2QTUOP-Cx8nz5_t-BmPIYTTz.js} +1 -1
  18. package/dist/assets/{chunk-7Z6QIM7H-CZs6l8Md-p53y8Cce.js → chunk-7Z6QIM7H-CZs6l8Md-DEA_98nb.js} +1 -1
  19. package/dist/assets/{chunk-GF5L2VYU-9OEKHEe4-ClU0Eq5H.js → chunk-GF5L2VYU-9OEKHEe4-DQnl6g7D.js} +1 -1
  20. package/dist/assets/{chunk-I66GZJ75-DSPCVCny-DUUEhGIc.js → chunk-I66GZJ75-DSPCVCny-DxpV96_X.js} +1 -1
  21. package/dist/assets/{chunk-J7OUQ5F2-CQoryswr-Bd7yatzt.js → chunk-J7OUQ5F2-CQoryswr-BthKg0zB.js} +2 -2
  22. package/dist/assets/{chunk-JQJVKLGR-6F2ui09L-C00hw8rt.js → chunk-JQJVKLGR-6F2ui09L-QjpkYHh7.js} +1 -1
  23. package/dist/assets/{chunk-KBJHAD2P-DXXCSVOS-DikyccfA.js → chunk-KBJHAD2P-DXXCSVOS-CEssdQjr.js} +1 -1
  24. package/dist/assets/{chunk-NSK5VX7P-DQgMj0Kn-DW0V7wZF.js → chunk-NSK5VX7P-DQgMj0Kn-CdfyTnLD.js} +1 -1
  25. package/dist/assets/{chunk-QR6OTTB3-BQl8P7pD-BsjqkGSs.js → chunk-QR6OTTB3-BQl8P7pD-DnkT63LY.js} +1 -1
  26. package/dist/assets/{chunk-UBXNYLIW-BbdYEvTI-BXrjgAx6.js → chunk-UBXNYLIW-BbdYEvTI-V5OTlgR8.js} +1 -1
  27. package/dist/assets/{chunk-W5SLKNZC-DSJLk1SI-C2WU94h8.js → chunk-W5SLKNZC-DSJLk1SI-B3X0CacL.js} +1 -1
  28. package/dist/assets/{chunk-WRU74C26-DOE_TBL8-BM1_oxuE.js → chunk-WRU74C26-DOE_TBL8-Ktu8fy3t.js} +1 -1
  29. package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-BW-s5osU.js +1 -0
  30. package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-BW-s5osU.js +1 -0
  31. package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-AwH2T8qv.js +1 -0
  32. package/dist/assets/{cynefinDiagram-MW4NZA55-DuBeESb6-CtkoviKh.js → cynefinDiagram-MW4NZA55-DuBeESb6-D1mkKqHb.js} +1 -1
  33. package/dist/assets/{dagre-VZM6K2ZE-W5cB3U70-RcQsLHRw.js → dagre-VZM6K2ZE-W5cB3U70-CmGRjn5N.js} +1 -1
  34. package/dist/assets/{diagram-7IWD3JNH-Fk1aWqfX-Ck8_3vru.js → diagram-7IWD3JNH-Fk1aWqfX-5ZttPYaC.js} +1 -1
  35. package/dist/assets/{diagram-B4RE2ZJO-q-8xygfU-DmLhXIkJ.js → diagram-B4RE2ZJO-q-8xygfU-Jaes9Saf.js} +1 -1
  36. package/dist/assets/{diagram-LBJQPF4R-1SKCcOis-JwGjKf0R.js → diagram-LBJQPF4R-1SKCcOis-DOnKMUhx.js} +1 -1
  37. package/dist/assets/{diagram-Q27KOJAE-CtW4StAQ-CjFTvm0p.js → diagram-Q27KOJAE-CtW4StAQ-CK--yh3Q.js} +1 -1
  38. package/dist/assets/{diagram-UB23O5K3-r5fK-tbF-BdXHIyiX.js → diagram-UB23O5K3-r5fK-tbF-CDEwYerq.js} +1 -1
  39. package/dist/assets/{dist-C8a-yjqr.js → dist-BVPKfO9h.js} +1 -1
  40. package/dist/assets/{dist-DLyXHsFo.js → dist-CSWHG2cH.js} +1 -1
  41. package/dist/assets/{dist-D7CcWwOu.js → dist-Cf1PlkcF.js} +1 -1
  42. package/dist/assets/{dist-CDrvAasC.js → dist-Di66sOMV.js} +1 -1
  43. package/dist/assets/{ebnfDiagram-BXEA7PRR-BZoIqTyx-CiH17BLa.js → ebnfDiagram-BXEA7PRR-BZoIqTyx-Bd0ceZ6T.js} +1 -1
  44. package/dist/assets/{erDiagram-JOGREHBK-CM8pI2JI-BzQ3AZBS.js → erDiagram-JOGREHBK-CM8pI2JI-B5EFrDqh.js} +1 -1
  45. package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-_-vzwCaU.js +1 -0
  46. package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-CTSDZ3kq.js +1 -0
  47. package/dist/assets/{ganttDiagram-PKOTCBZU-DYTkxDIh-9KsdZhhO.js → ganttDiagram-PKOTCBZU-DYTkxDIh-CdH3rbwy.js} +1 -1
  48. package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-DJFY2Jhk.js +1 -0
  49. package/dist/assets/{gitGraphDiagram-DS77QQ5N-DafidN4D-BENdpVYO.js → gitGraphDiagram-DS77QQ5N-DafidN4D-dgab4OC4.js} +1 -1
  50. package/dist/assets/{index-O3HcsBk0.css → index-ClKZRsCv.css} +1 -1
  51. package/dist/assets/index-jaLr_Prz.js +623 -0
  52. package/dist/assets/info-DKCQHKI2-DPUCBYWS-3rgS_gc8.js +1 -0
  53. package/dist/assets/{infoDiagram-6WML65LV-DxSxsg1o-DohszP_m.js → infoDiagram-6WML65LV-DxSxsg1o-D4YGxu1g.js} +1 -1
  54. package/dist/assets/{ishikawaDiagram-WSZJBQD7-kAIMMFB5-C2ghY9nw.js → ishikawaDiagram-WSZJBQD7-kAIMMFB5-wsr5xc5H.js} +1 -1
  55. package/dist/assets/{journeyDiagram-NVQOT4AX-ClybI8XI-Dd5mVngg.js → journeyDiagram-NVQOT4AX-ClybI8XI-DPUOB6uo.js} +1 -1
  56. package/dist/assets/{kanban-definition-27J2QSJJ-DDYHyW1q-p5r5fZtE.js → kanban-definition-27J2QSJJ-DDYHyW1q-CZgSIoc2.js} +1 -1
  57. package/dist/assets/{lib-Jhts0zYK.js → lib-C4gx-pAc.js} +1 -1
  58. package/dist/assets/{line-CtRAsVKI-CxBaSxDR.js → line-CtRAsVKI-BhCm0031.js} +1 -1
  59. package/dist/assets/{marp-CrOOtG2e.js → marp-CeXrcVjr.js} +1 -1
  60. package/dist/assets/{mermaid-parser.core-C1baW7NG-CxIKaNt7.js → mermaid-parser.core-C1baW7NG-m5_U2vyD.js} +2 -2
  61. package/dist/assets/{mermaid.core-CGR3z-ay-BeIQeUd9.js → mermaid.core-CGR3z-ay-DvwBCqCs.js} +3 -3
  62. package/dist/assets/{mindmap-definition-FAOFIHXS-DU-wbYKK-DSiJQU50.js → mindmap-definition-FAOFIHXS-DU-wbYKK-kWq_dek4.js} +1 -1
  63. package/dist/assets/packet-7NZHBO7P-CC7His0F-BdxXKgeT.js +1 -0
  64. package/dist/assets/{pegDiagram-VL7TDLO6-CZuV1UUk-Ciz03uoq.js → pegDiagram-VL7TDLO6-CZuV1UUk-4WS0bS_y.js} +1 -1
  65. package/dist/assets/pie-RZYD4A2V-CCrcq2sL-CP-ev46m.js +1 -0
  66. package/dist/assets/{pieDiagram-7S7Q4E2Y-B6cd1CWd-CPmXRIzT.js → pieDiagram-7S7Q4E2Y-B6cd1CWd-D6qNs4Iu.js} +1 -1
  67. package/dist/assets/{quadrantDiagram-CIZ2JOQS-BvGHVjRA-qpv2zwpv.js → quadrantDiagram-CIZ2JOQS-BvGHVjRA-CXWlp-3-.js} +1 -1
  68. package/dist/assets/radar-I7S5WNFK-C5TUrpHf-DWVX-MsP.js +1 -0
  69. package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-DKlMYwIS.js +1 -0
  70. package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-CoCu9q79.js +1 -0
  71. package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-xwwvt-Nh.js +1 -0
  72. package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-DKXIsmZD.js +1 -0
  73. package/dist/assets/{railroadDiagram-AXF67PYL-DDtBla5s-DswUZ1l7.js → railroadDiagram-AXF67PYL-DDtBla5s-C5dmRUSB.js} +1 -1
  74. package/dist/assets/{requirementDiagram-LRYGKXZP-DEh2MJlW-DSsLsY6R.js → requirementDiagram-LRYGKXZP-DEh2MJlW-Dx5xtPm0.js} +1 -1
  75. package/dist/assets/{sankeyDiagram-W5VNT64P-CHkaBx7U-c9ucgH40.js → sankeyDiagram-W5VNT64P-CHkaBx7U-DfX5prrR.js} +1 -1
  76. package/dist/assets/{sequenceDiagram-SI44F4Z6-D8tHDhWP-GbjcMRwM.js → sequenceDiagram-SI44F4Z6-D8tHDhWP-BWGOYuS_.js} +1 -1
  77. package/dist/assets/{stateDiagram-OKZ733FA-KNjKsfsT-oJnFM5PH.js → stateDiagram-OKZ733FA-KNjKsfsT-Dxoi_j8b.js} +1 -1
  78. package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-RHLLKJ5Z.js +1 -0
  79. package/dist/assets/{swimlanes-SLNWSIFB-D_yJNxBK-ClZU9R9x.js → swimlanes-SLNWSIFB-D_yJNxBK-BuG8H1q8.js} +1 -1
  80. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-7gQf7HNa.js +8 -0
  81. package/dist/assets/{timeline-definition-Z64GVDOM-BcEcbSq1-B4Bzc1Aq.js → timeline-definition-Z64GVDOM-BcEcbSq1-D4hjgInK.js} +1 -1
  82. package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-D-gX-9lw.js +1 -0
  83. package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-C90mQNBy.js +1 -0
  84. package/dist/assets/{vennDiagram-T6HMQDX7-CxS3fcVg-D5bZGr-c.js → vennDiagram-T6HMQDX7-CxS3fcVg-DkIZ05uL.js} +1 -1
  85. package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-DdIlimET.js +1 -0
  86. package/dist/assets/{wardleyDiagram-T6FBY63Y-D_XCiR14-etNVBxbw.js → wardleyDiagram-T6FBY63Y-D_XCiR14-DiKk_43E.js} +1 -1
  87. package/dist/assets/{xychartDiagram-ELKLHX3M-BU4yBDeE-C58nd7P0.js → xychartDiagram-ELKLHX3M-BU4yBDeE-4brN3p2i.js} +1 -1
  88. package/dist/index.html +2 -2
  89. package/package.json +4 -4
  90. package/server/backends/collectionNotifierAdapter.ts +38 -7
  91. package/server/backends/collectionSelfContainment.ts +288 -0
  92. package/server/backends/collectionWatchers.ts +174 -15
  93. package/server/backends/collections.ts +13 -2
  94. package/server/backends/feed-worker-options.ts +22 -0
  95. package/server/backends/feeds.ts +21 -12
  96. package/server/backends/files.ts +43 -7
  97. package/server/backends/remoteHost/commandScope.ts +45 -0
  98. package/server/backends/remoteHost/getCollection.spec.ts +14 -1
  99. package/server/backends/remoteHost/handlers/getCollection.ts +11 -6
  100. package/server/backends/remoteHost/handlers/getFeed.ts +9 -4
  101. package/server/backends/remoteHost/handlers/getRemoteView.ts +3 -2
  102. package/server/backends/remoteHost/handlers/getRemoteViewItems.ts +3 -2
  103. package/server/backends/remoteHost/handlers/index.ts +4 -0
  104. package/server/backends/remoteHost/handlers/listCollectionProjects.ts +111 -0
  105. package/server/backends/remoteHost/handlers/listCollections.ts +4 -4
  106. package/server/backends/remoteHost/handlers/listFeeds.ts +7 -4
  107. package/server/backends/remoteHost/handlers/listSkills.ts +6 -3
  108. package/server/backends/remoteHost/handlers/mutateRemoteView.ts +3 -2
  109. package/server/backends/remoteView.ts +5 -1
  110. package/server/backends/scheduled-run.ts +86 -0
  111. package/server/backends/scheduler-adapter.ts +39 -0
  112. package/server/backends/scheduler-state-seed.ts +87 -0
  113. package/server/backends/scheduler.ts +98 -26
  114. package/server/backends/system-tasks.ts +38 -5
  115. package/server/backends/worklog.ts +20 -10
  116. package/server/backends/workspaceSetup.ts +8 -1
  117. package/server/config/app-config.ts +1 -1
  118. package/server/config/config-lock.ts +207 -0
  119. package/server/config/config-routes.ts +161 -3
  120. package/server/index.ts +30 -11
  121. package/server/infra/collection-tool.ts +23 -2
  122. package/server/infra/collectionToolPortability.ts +63 -0
  123. package/server/infra/project-root.ts +71 -8
  124. package/server/routes/app-routes.ts +14 -3
  125. package/server/routes/plugin-routes.ts +72 -22
  126. package/server/routes/stampCardScope.ts +84 -0
  127. package/server/routes/tool-routes.ts +16 -2
  128. package/server/session/background-chat.ts +9 -0
  129. package/server/session/scheduled-chat.ts +8 -2
  130. package/dist/assets/architecture-TIHT7OUA-Cqhmai3f-Cv_ndCVi.js +0 -1
  131. package/dist/assets/channel-DKMa7Sn5-BaAfUVFI.js +0 -1
  132. package/dist/assets/classDiagram-JCYQIIEL-C1ePmEG1-C9EzE_DN.js +0 -1
  133. package/dist/assets/classDiagram-v2-OCEON4UE-C_O3VNBf-C9EzE_DN.js +0 -1
  134. package/dist/assets/cynefin-VYW2F7L2-DvSvuXnG-DYdPNWBf.js +0 -1
  135. package/dist/assets/eventmodeling-45OFAUF4-jdctDg0T-CaXk3edG.js +0 -1
  136. package/dist/assets/flowDiagram-UKHOOZJN-CR3dcoRl-BruZOSTL.js +0 -1
  137. package/dist/assets/gitGraph-TEB2WS4Q-C4cKwl5j-2aSGczK5.js +0 -1
  138. package/dist/assets/index-CG2zWFsc.js +0 -623
  139. package/dist/assets/info-DKCQHKI2-DPUCBYWS-DfgqsrhV.js +0 -1
  140. package/dist/assets/packet-7NZHBO7P-CC7His0F-3jvv5KyU.js +0 -1
  141. package/dist/assets/pie-RZYD4A2V-CCrcq2sL-B1d6EDGA.js +0 -1
  142. package/dist/assets/radar-I7S5WNFK-C5TUrpHf-BNT9q7Hl.js +0 -1
  143. package/dist/assets/railroad-3IZDKUUU-CGuuFEd8-DvKf9W63.js +0 -1
  144. package/dist/assets/railroad-abnf-AHOZXSZD-DuoCSFZY-B6303QUX.js +0 -1
  145. package/dist/assets/railroad-ebnf-EBAXGLYW-ig-DG9e_-eWx1p0cd.js +0 -1
  146. package/dist/assets/railroad-peg-LSFZ7HO6-BkBgpyvu-gjuyjp8y.js +0 -1
  147. package/dist/assets/stateDiagram-v2-UEYNNEHI-B88w7Ous-CJTxIq2F.js +0 -1
  148. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DNcZzqTp-a-R1A8Fy.js +0 -8
  149. package/dist/assets/treeView-QDETBFTQ-CqfT0I1z-CheA8f5U.js +0 -1
  150. package/dist/assets/treemap-6X3UGDF4-CiJkiGWo-93EKyl15.js +0 -1
  151. package/dist/assets/wardley-OPB4EBWU-D1tuVHAV-CHpba1o8.js +0 -1
package/README.md CHANGED
@@ -692,6 +692,14 @@ and not built*. The window is **since the last run** (tracked in
692
692
  work. It reads and reconciles progress against `vision.md` / `milestones.md` (creating
693
693
  empty ones if absent) so a long-running goal isn't forgotten.
694
694
 
695
+ A run due while the server was **off** is not lost: built-in scheduled tasks record their
696
+ runs in `config/scheduler/state.json` and **catch up at startup**, each by its own missed-run
697
+ policy. The worklog's is `run-once` — a single run covering everything since the last, so
698
+ several missed windows don't become several batches summarising the same period. Whether
699
+ they ran, and when they run next, is in `GET /api/scheduler/tasks`; the history is in
700
+ `GET /api/scheduler/logs` (`?taskId=&since=&limit=`, newest first) and on disk under
701
+ `data/scheduler/logs/`.
702
+
695
703
  Output lands in the wiki: one **weekly page** per ISO week
696
704
  (`data/wiki/pages/dev-log-YYYY-www.md` — filenames are lowercase, or the wiki can't open
697
705
  them), each tagged `worklog`. To browse them, open the **作業ログ 一覧** hub page
@@ -778,7 +786,7 @@ Settings → Directory settings names both files and lists which keys the local
778
786
  | `icon` | An **image** marking this directory — shown in the cell header, the cockpit roster, the filmstrip thumbnails, the launcher's directory chips, and the phone's terminal list and terminal screen. Either a path **relative to this directory** (an absolute path, or a `../` that escapes it, is rejected), an `http(s)://` URL, or a `data:image/…` URI. PNG / JPEG / **GIF (animated plays)** / WebP / AVIF / SVG / ICO / BMP. Not to be confused with a header **button's** `icon`, which is a Material Symbols name. **Omit it and the repository's own favicon is used** (`public/favicon.svg`, `apple-touch-icon.png`, a web manifest — see `autoDirIcon`); `false` means no icon here and stops that search. |
779
787
  | `badgeColor` | Badge background color (`#rrggbb`); text auto-contrasts. |
780
788
  | `headerColor` | Header **background** color (`#rrggbb`) — the grid cell's header row and the terminal's own header row (grid row 2). While a terminal is working/blocked the status tint still shows; the custom color applies when idle. |
781
- | `headerTextColor` | Header **text** color (`#rrggbb`) — the dir path, title, and prompt. |
789
+ | `headerTextColor` | Header **text** color (`#rrggbb`) — everything written on the header: the dir path, title and prompt, plus the model/context badge, the token counts and any custom chip. **Omit it and a readable colour is derived from `headerColor`**, while that colour is what shows (a working/blocked cell paints its own header tint, so its text returns to the theme's). |
782
790
  | `cellColor` | Cell **body background** color (`#rrggbb`) — the frame around the terminal. |
783
791
  | `cellBorderColor` | Cell **border** color (`#rrggbb`). The status frame (working/blocked) still overrides it while active. |
784
792
  | `dotColor` | **Idle** status-dot color (`#rrggbb`). The working/waiting colors are unchanged so the activity signal stays intact. |
@@ -1130,9 +1138,9 @@ absent from the answer — which is how a caller learns work cannot start on it.
1130
1138
  Each grid cell's header shows two badges for its session, refreshed when a turn finishes
1131
1139
  (from `GET /api/session/:id`):
1132
1140
 
1133
- ![A live Claude cell — the header shows the model·context and token badges this section describes](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-cell-live.png)
1141
+ ![A live Claude cell's header, close up — the model·context and token badges this section describes](https://raw.githubusercontent.com/receptron/mulmoterminal/main/docs/guide/images/grid-cell-live.png)
1134
1142
 
1135
- *Both badges, live on a real Claude session: **`Opus · ctx 5%`** (model family + how full its context window is) and **`⇡427k ⇣1.8k`** (cumulative input / output tokens for the session). They sit in the header's first row alongside the **status dot**, directory, and **git chip** (`⎇ main ●2`), with what the agent is doing to the right; the icon buttons and the **timeline** (🕘) of tool calls are on the second row.*
1143
+ *Both badges, live on a real Claude session: **`Sonnet · ctx 9%`** (model family + how full its context window is) and **`⇡1.8M ⇣6.9k`** (cumulative input / output tokens for the session). They sit in the header's first row alongside the **status dot** and the **git chip** (`⎇ main ●2`), with what the agent is doing to the right; the working directory, the icon buttons and the **timeline** (🕘) of tool calls are on the second row.*
1136
1144
 
1137
1145
  - **Context badge** — e.g. `Opus · ctx 35%`: the model family plus how full its context
1138
1146
  window is (the *last* turn's input + cache tokens ÷ the model's window — **1M** for
@@ -15,7 +15,7 @@
15
15
  // Snapping every role to absolute values drifts by ΔE 20 at the dark end of the hue wheel; making
16
16
  // every role a relative offset drifts by ΔE 15 at the pale end. Both were tried; this is the mix
17
17
  // that fits. Anyone retuning these numbers should re-measure rather than reason about them.
18
- import { readableTextColor } from "./contrast.js";
18
+ import { contrastRatio, readableTextColor, relativeLuminance } from "./contrast.js";
19
19
 
20
20
  export interface ChromeColors {
21
21
  badgeColor: string;
@@ -36,6 +36,37 @@ const BUTTON = { s: 0.67, l: 0.89 };
36
36
 
37
37
  const clamp01 = (n: number): number => Math.min(1, Math.max(0, n));
38
38
 
39
+ // The inks a header background can be written in. Black is softened to a near-black so a pale
40
+ // header does not read as a printed page; white is left pure, since anything softer loses contrast
41
+ // on the dark end where it is chosen.
42
+ const DARK_INK = "#1b2430";
43
+ const PURE_DARK_INK = "#000000";
44
+ const LIGHT_INK = "#ffffff";
45
+
46
+ // WCAG AA for normal text. These chips run to 10px, so the large-text 3:1 bar does not apply.
47
+ const AA_CONTRAST = 4.5;
48
+ const DARK_INK_LUMINANCE = relativeLuminance(0x1b, 0x24, 0x30);
49
+
50
+ // The softening is a PREFERENCE, and it costs contrast: on a mid-tone background that cost is what
51
+ // drops the text below AA — `#e8341c` measures 3.68:1 against the softened ink and 4.94:1 against
52
+ // pure black (Codex on #1592, which is right and was worth measuring). So it is applied only while
53
+ // it is free. Falling back always works: at the worst background — the luminance where black and
54
+ // white are equally readable — the pure ink still reaches 4.58:1.
55
+ function inkFor(rgb: [number, number, number]): string {
56
+ if (readableTextColor(rgb[0], rgb[1], rgb[2]) === "#fff") return LIGHT_INK;
57
+ const background = relativeLuminance(rgb[0], rgb[1], rgb[2]);
58
+ return contrastRatio(background, DARK_INK_LUMINANCE) >= AA_CONTRAST ? DARK_INK : PURE_DARK_INK;
59
+ }
60
+
61
+ /** Which ink a header background wants, or null when it isn't a colour we can read. Exported so a
62
+ * directory that declares only `headerColor` derives the SAME text colour this file gives a
63
+ * repo.json brand colour (src/components/cellHeaderStyle.ts) — two answers to one question is how
64
+ * one directory ends up written in two different colours. */
65
+ export function headerTextColorFor(background: string): string | null {
66
+ const rgb = parseHex(background);
67
+ return rgb ? inkFor(rgb) : null;
68
+ }
69
+
39
70
  /** The seven colours for a brand colour, or null when it isn't a colour we can read. */
40
71
  export function chromeFromColor(primary: string, background?: string | null): ChromeColors | null {
41
72
  const rgb = parseHex(primary);
@@ -50,7 +81,7 @@ export function chromeFromColor(primary: string, background?: string | null): Ch
50
81
  headerColor: normalizeHex(primary),
51
82
  // Never taken from the file — see the spec's "text colour is never declared". Deriving it is
52
83
  // also what makes two conforming tools agree.
53
- headerTextColor: readableTextColor(rgb[0], rgb[1], rgb[2]) === "#000" ? "#1b2430" : "#ffffff",
84
+ headerTextColor: inkFor(rgb),
54
85
  // `background` is the surface the brand colour sits on, which is exactly what a cell body is.
55
86
  // Gated on it PARSING, not on it being present: an unusable value here used to win the branch,
56
87
  // survive as a non-colour, and then be dropped downstream — leaving no surface at all, neither
@@ -19,6 +19,12 @@ export interface CollectionNotifyTarget {
19
19
  slug: string;
20
20
  /** Absent on a collection-level bell — those can't accent a specific record. */
21
21
  itemId?: string | undefined;
22
+ /** The record's project, as the opaque id the API takes. ABSENT MEANS THE WORKSPACE — both
23
+ * because that is what a workspace bell has always written and because it is the only thing
24
+ * MulmoClaude, which has one root, can write. A slug is unique within a root and nowhere
25
+ * else, so a reader that ignores this accents the same-named record of whichever project is
26
+ * on screen. */
27
+ project?: string | undefined;
22
28
  }
23
29
 
24
30
  /** Narrow a notification's opaque `pluginData` to its collection navigate target,
@@ -31,7 +37,11 @@ export function collectionNotifyTargetOf(pluginData: unknown): CollectionNotifyT
31
37
  if (!isRecord(action)) return null;
32
38
  const { target } = action;
33
39
  if (!isRecord(target)) return null;
34
- const { view, slug, itemId } = target;
40
+ const { view, slug, itemId, project } = target;
35
41
  if (view !== COLLECTION_NOTIFY_VIEW || typeof slug !== "string" || slug.length === 0) return null;
36
- return { slug, itemId: typeof itemId === "string" && itemId.length > 0 ? itemId : undefined };
42
+ return {
43
+ slug,
44
+ itemId: typeof itemId === "string" && itemId.length > 0 ? itemId : undefined,
45
+ project: typeof project === "string" && project.length > 0 ? project : undefined,
46
+ };
37
47
  }
@@ -0,0 +1,84 @@
1
+ // "Would this collection survive a clone?" — the wire shape, shared because BOTH sides decide
2
+ // from it: the server produces the report (server/backends/collectionSelfContainment.ts) and the
3
+ // Collections pane renders it, colouring by severity and blocking on `portable`.
4
+ //
5
+ // In `common/` rather than mirrored, for the reason the layout rule gives: a second copy of a
6
+ // union is how a writer and a reader drift while both keep compiling. The RULES stay server-side
7
+ // — they read the filesystem and shell out to git — and only their result crosses.
8
+ import { isRecord } from "./isRecord.js";
9
+ import { isUnknownArray } from "./isUnknownArray.js";
10
+
11
+ /** Why a collection would not survive a clone. Stable strings — a client branches on these, and
12
+ * the prose is free to be rewritten without breaking it. */
13
+ export type SelfContainmentCode =
14
+ /** The skill lives in `~/.claude/skills`, i.e. on this machine and not in the repo. */
15
+ | "user-scope"
16
+ /** Records are one SQLite file: git cannot merge it. */
17
+ | "sqlite-store"
18
+ /** Records are rows of a CSV read through DuckDB — a runtime the clone must also have. */
19
+ | "csv-runtime"
20
+ /** Something the records need is git-ignored: the schema travels, they do not. */
21
+ | "data-ignored"
22
+ /** No `primaryKey`, so ids are 4 random bytes and two machines can mint the same one. */
23
+ | "no-primary-key"
24
+ /** The project is not a git repo, so there is nothing to clone and most checks do not apply. */
25
+ | "not-a-repo";
26
+
27
+ /** `blocker` stops the clone working; `warning` costs something on the other machine; `info` is
28
+ * context. Only a blocker makes `portable` false. */
29
+ export type SelfContainmentSeverity = "blocker" | "warning" | "info";
30
+
31
+ export interface SelfContainmentFinding {
32
+ /** One of `SelfContainmentCode` in practice, typed as a plain string on the WIRE on purpose: a
33
+ * newer server may report a code this build has never heard of, and narrowing here would drop
34
+ * a real finding behind a version skew. Nothing branches on it — the pane renders `message`
35
+ * and colours by `severity` — so widening costs nothing. A spec pins that everything the
36
+ * server produces IS in the union. */
37
+ code: string;
38
+ severity: SelfContainmentSeverity;
39
+ /** What breaks on the other machine, in the terms the person will see it. */
40
+ message: string;
41
+ }
42
+
43
+ export interface SelfContainmentReport {
44
+ slug: string;
45
+ /** True when nothing BLOCKS the clone. Warnings can still be present. */
46
+ portable: boolean;
47
+ findings: SelfContainmentFinding[];
48
+ }
49
+
50
+ /** The severity IS narrowed, unlike the code: it decides how the row is rendered AND whether the
51
+ * finding is a blocker, so an unrecognised one cannot be shown honestly. A predicate rather than
52
+ * a list membership test, so the narrowing is the type system's rather than an assertion's. */
53
+ export function isSelfContainmentSeverity(value: unknown): value is SelfContainmentSeverity {
54
+ return value === "blocker" || value === "warning" || value === "info";
55
+ }
56
+
57
+ function asFinding(value: unknown): SelfContainmentFinding | null {
58
+ if (!isRecord(value)) return null;
59
+ const { code, severity, message } = value;
60
+ if (typeof code !== "string" || typeof message !== "string") return null;
61
+ if (!isSelfContainmentSeverity(severity)) return null;
62
+ return { code, severity, message };
63
+ }
64
+
65
+ /** Narrow a fetched body to the report, or null when it is not one. The body arrives as
66
+ * `unknown` — a truncated response, an error page, an older server — so every level is checked
67
+ * rather than trusted. */
68
+ export function asSelfContainmentReport(body: unknown): SelfContainmentReport | null {
69
+ if (!isRecord(body)) return null;
70
+ const { slug, portable, findings } = body;
71
+ if (typeof slug !== "string" || typeof portable !== "boolean" || !isUnknownArray(findings)) return null;
72
+ const parsed: SelfContainmentFinding[] = [];
73
+ for (const entry of findings) {
74
+ const finding = asFinding(entry);
75
+ // ONE UNREADABLE FINDING REJECTS THE WHOLE REPORT. Skipping it would be the worse failure:
76
+ // if the dropped finding was the only blocker, the caller is handed `findings: []` and tells
77
+ // the user "nothing to fix — it travels" about a collection that does not. A report we cannot
78
+ // fully read is not a clean bill of health, and "could not run the check" is the honest
79
+ // answer — visible and actionable, where a false all-clear is neither.
80
+ if (!finding) return null;
81
+ parsed.push(finding);
82
+ }
83
+ return { slug, portable, findings: parsed };
84
+ }
@@ -0,0 +1,23 @@
1
+ // Splitting a filesystem path into its parts, on EITHER separator.
2
+ //
3
+ // `path.sep` is not the answer here: these paths come from a config file and from a saved
4
+ // directory list, so a Windows path can be read on a mac (a synced config, a bug report, a test
5
+ // fixture) and a POSIX one on Windows. Code that splits on "/" alone does not fail loudly on
6
+ // `C:\Users\me\project` — it returns the WHOLE STRING as one segment, which is how a "label"
7
+ // becomes an absolute path.
8
+ //
9
+ // That is a privacy contract in one place: the phone's project listing sends a label and never a
10
+ // path, precisely so a command or an artifact cannot publish the user's home directory. A
11
+ // one-separator split turns that guarantee off on Windows without changing a line of the code
12
+ // that states it.
13
+
14
+ /** The non-empty parts of a path, splitting on `/` and `\` alike. A drive letter (`C:`) stays a
15
+ * segment of its own — it is not a directory name, and nothing here wants it as one. */
16
+ export function pathSegments(value: string): string[] {
17
+ return value.split(/[/\\]+/).filter((segment) => segment.length > 0);
18
+ }
19
+
20
+ /** The last part of a path — its directory or file name — or the input when it has none. */
21
+ export function lastSegment(value: string): string {
22
+ return pathSegments(value).at(-1) ?? value;
23
+ }
@@ -31,6 +31,20 @@ export const isToolGroup = (value: unknown): value is ToolGroup => TOOL_GROUPS.s
31
31
  // front of the spend (same reasoning as presentDocument below).
32
32
  export const CANVAS_TOOL_GROUPS: readonly ToolGroup[] = ["render", "media"];
33
33
 
34
+ // The groups the COLLECTIONS pane is made of. `data` is the group manageCollection and
35
+ // presentCollection belong to, and the pane is a window onto exactly that store — a directory
36
+ // that never registered the group has no collection tools, so the pane is a door onto a room the
37
+ // agent beside it cannot enter.
38
+ //
39
+ // Named here rather than written as `"data"` at the call site for the same reason
40
+ // CANVAS_TOOL_GROUPS is: a rename must not leave a literal pointing at a group that is gone.
41
+ export const COLLECTIONS_TOOL_GROUPS: readonly ToolGroup[] = ["data"];
42
+
43
+ /** Does this session/directory have the collection tools? Same shape as `hasCanvasGroup`, and
44
+ * validated the same way — an unknown group name from a newer server must not count. */
45
+ export const hasCollectionsGroup = (groups: unknown): boolean =>
46
+ Array.isArray(groups) && groups.some((group) => isToolGroup(group) && COLLECTIONS_TOOL_GROUPS.includes(group));
47
+
34
48
  // Does a session/directory reach the Canvas at all? Asked of the group list the server reports,
35
49
  // whose members arrive as plain strings — validated rather than cast, since an unknown name from
36
50
  // a newer server must not count as a canvas group.
@@ -1 +1 @@
1
- import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-CxIKaNt7.js";import"./chunk-I66GZJ75-DSPCVCny-DUUEhGIc.js";import"./chunk-3NCLNEKW-CBztX243-DYiY7RPy.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-Cx8nz5_t-CnRVqq3q.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
1
+ import{n as e}from"./chunk-Y2CYZVJY-Bdt8pFDJ-DsF7k-Jl.js";import{f as t}from"./src-CvsWqFln-Tc7ZSrRz.js";import{_ as n,g as r}from"./mermaid-parser.core-C1baW7NG-m5_U2vyD.js";import"./chunk-I66GZJ75-DSPCVCny-DxpV96_X.js";import"./chunk-3NCLNEKW-CBztX243-DK2bYr2d.js";import{n as i,r as a,t as o}from"./chunk-6Q2QTUOP-Cx8nz5_t-BmPIYTTz.js";import{t as s}from"./chunk-JWPE2WC7-BQ3zXr2k-DSd3Ct95.js";var c=r().RailroadAbnf.parser.LangiumParser,l=e(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=e(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=e(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=e(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=e(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=e(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=e(e=>{s(e,a),e.title&&a.setTitle(e.title),e.rules.map(e=>a.addRule(m(e)))},`populateDb`),g={parser:{parse:e(e=>{a.clear(),t.debug(`[ABNF Parser] Starting Langium parse`);let r=c.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new n(r);let i=r.value;t.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),t.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:a}},db:a,renderer:o,styles:i};export{g as diagram};
@@ -0,0 +1 @@
1
+ import{f as e}from"./mermaid-parser.core-C1baW7NG-m5_U2vyD.js";export{e as createArchitectureServices};