@plebeiantech/lat.md 0.12.3-fork.13 → 0.12.3-fork.14

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 (274) hide show
  1. package/README.md +24 -0
  2. package/dist/src/cli/check-context.d.ts +56 -0
  3. package/dist/src/cli/check-context.js +215 -0
  4. package/dist/src/cli/check-coverage.js +9 -5
  5. package/dist/src/cli/check-frontmatter.js +1 -1
  6. package/dist/src/cli/check-mode.js +1 -1
  7. package/dist/src/cli/check-status.js +1 -1
  8. package/dist/src/cli/check.d.ts +19 -12
  9. package/dist/src/cli/check.js +259 -206
  10. package/dist/src/cli/context.js +1 -1
  11. package/dist/src/cli/expand.d.ts +1 -2
  12. package/dist/src/cli/expand.js +52 -5
  13. package/dist/src/cli/external.d.ts +11 -0
  14. package/dist/src/cli/external.js +185 -0
  15. package/dist/src/cli/fork-instructions.d.ts +1 -0
  16. package/dist/src/cli/fork-instructions.js +3 -2
  17. package/dist/src/cli/hook.d.ts +26 -0
  18. package/dist/src/cli/hook.js +112 -40
  19. package/dist/src/cli/index.js +55 -3
  20. package/dist/src/cli/init.d.ts +1 -0
  21. package/dist/src/cli/init.js +40 -2
  22. package/dist/src/cli/locate.js +4 -3
  23. package/dist/src/cli/refs.d.ts +2 -1
  24. package/dist/src/cli/refs.js +88 -43
  25. package/dist/src/cli/reindex.js +2 -1
  26. package/dist/src/cli/search.d.ts +12 -3
  27. package/dist/src/cli/search.js +45 -49
  28. package/dist/src/cli/section.d.ts +3 -1
  29. package/dist/src/cli/section.js +114 -38
  30. package/dist/src/code-refs.d.ts +17 -5
  31. package/dist/src/code-refs.js +246 -65
  32. package/dist/src/context.d.ts +3 -0
  33. package/dist/src/document-formats.d.ts +7 -0
  34. package/dist/src/document-formats.js +39 -0
  35. package/dist/src/extensions/alert-marker.d.ts +25 -0
  36. package/dist/src/extensions/alert-marker.js +55 -0
  37. package/dist/src/external-documents.d.ts +56 -0
  38. package/dist/src/external-documents.js +404 -0
  39. package/dist/src/external-sources.d.ts +136 -0
  40. package/dist/src/external-sources.js +1098 -0
  41. package/dist/src/fork/antigravity-hook.d.ts +47 -0
  42. package/dist/src/fork/antigravity-hook.js +424 -0
  43. package/dist/src/fork/antigravity-init.d.ts +17 -0
  44. package/dist/src/fork/antigravity-init.js +124 -0
  45. package/dist/src/fork/frontmatter-fields.d.ts +36 -0
  46. package/dist/src/fork/frontmatter-fields.js +23 -0
  47. package/dist/src/format.d.ts +5 -2
  48. package/dist/src/format.js +8 -3
  49. package/dist/src/graph-export.js +1 -1
  50. package/dist/src/lattice-model.d.ts +77 -0
  51. package/dist/src/lattice-model.js +464 -0
  52. package/dist/src/lattice.d.ts +9 -104
  53. package/dist/src/lattice.js +69 -516
  54. package/dist/src/markdown-analysis-cache.d.ts +20 -0
  55. package/dist/src/markdown-analysis-cache.js +139 -0
  56. package/dist/src/markdown-analysis-worker.d.ts +16 -0
  57. package/dist/src/markdown-analysis-worker.js +33 -0
  58. package/dist/src/markdown-analysis.d.ts +50 -0
  59. package/dist/src/markdown-analysis.js +139 -0
  60. package/dist/src/markdown-analyzer-loader.d.ts +4 -0
  61. package/dist/src/markdown-analyzer-loader.js +13 -0
  62. package/dist/src/markdown-validation.d.ts +25 -0
  63. package/dist/src/markdown-validation.js +104 -0
  64. package/dist/src/mcp/server.js +27 -11
  65. package/dist/src/parser-cache.d.ts +17 -0
  66. package/dist/src/parser-cache.js +65 -0
  67. package/dist/src/parser-import.d.ts +8 -0
  68. package/dist/src/parser-import.js +1 -0
  69. package/dist/src/parser.js +80 -5
  70. package/dist/src/path.d.ts +2 -0
  71. package/dist/src/path.js +4 -0
  72. package/dist/src/profiler.d.ts +16 -0
  73. package/dist/src/profiler.js +81 -0
  74. package/dist/src/project-analysis.d.ts +59 -0
  75. package/dist/src/project-analysis.js +233 -0
  76. package/dist/src/project-discovery.d.ts +6 -0
  77. package/dist/src/project-discovery.js +30 -0
  78. package/dist/src/repository-path.d.ts +11 -0
  79. package/dist/src/repository-path.js +52 -0
  80. package/dist/src/search/db.d.ts +1 -1
  81. package/dist/src/search/db.js +2 -2
  82. package/dist/src/search/embedder.d.ts +2 -1
  83. package/dist/src/search/embedder.js +6 -5
  84. package/dist/src/search/index.d.ts +2 -1
  85. package/dist/src/search/index.js +10 -8
  86. package/dist/src/search/query.d.ts +20 -0
  87. package/dist/src/search/query.js +67 -0
  88. package/dist/src/search/search.d.ts +3 -1
  89. package/dist/src/search/search.js +5 -2
  90. package/dist/src/source-formats.d.ts +7 -0
  91. package/dist/src/source-formats.js +26 -0
  92. package/dist/src/source-parser.d.ts +44 -4
  93. package/dist/src/source-parser.js +494 -57
  94. package/dist/src/view/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  95. package/dist/src/view/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  96. package/dist/src/view/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  97. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  98. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  99. package/dist/src/view/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  100. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  101. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  102. package/dist/src/view/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  103. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  104. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  105. package/dist/src/view/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  106. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  107. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  108. package/dist/src/view/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  109. package/dist/src/view/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  110. package/dist/src/view/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  111. package/dist/src/view/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  112. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  113. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  114. package/dist/src/view/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  115. package/dist/src/view/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  116. package/dist/src/view/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  117. package/dist/src/view/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  118. package/dist/src/view/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  119. package/dist/src/view/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  120. package/dist/src/view/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  121. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  122. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  123. package/dist/src/view/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  124. package/dist/src/view/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  125. package/dist/src/view/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  126. package/dist/src/view/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  127. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  128. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  129. package/dist/src/view/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  130. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  131. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  132. package/dist/src/view/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  133. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  134. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  135. package/dist/src/view/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  136. package/dist/src/view/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  137. package/dist/src/view/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  138. package/dist/src/view/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  139. package/dist/src/view/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  140. package/dist/src/view/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  141. package/dist/src/view/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  142. package/dist/src/view/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  143. package/dist/src/view/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  144. package/dist/src/view/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  145. package/dist/src/view/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  146. package/dist/src/view/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  147. package/dist/src/view/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  148. package/dist/src/view/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  149. package/dist/src/view/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  150. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  151. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  152. package/dist/src/view/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  153. package/dist/src/view/client/assets/MarkdownEditor-B7quqPb8.js +28 -0
  154. package/dist/src/view/client/assets/OrbitControls-DwcpQNja.js +1 -0
  155. package/dist/src/view/client/assets/STLLoader-CLWTKt5i.js +1 -0
  156. package/dist/src/view/client/assets/abnfDiagram-VCTEODGH-V7-T6qsl.js +1 -0
  157. package/dist/src/view/client/assets/arc-BnwR8Bu7.js +1 -0
  158. package/dist/src/view/client/assets/architectureDiagram-5GKGNRK7-u97vDna9.js +36 -0
  159. package/dist/src/view/client/assets/blockDiagram-I7D4REHJ-DETl8e_H.js +129 -0
  160. package/dist/src/view/client/assets/c4Diagram-7LVT6UL2-DJRAT1Ve.js +38 -0
  161. package/dist/src/view/client/assets/channel-BwQbnWCN.js +1 -0
  162. package/dist/src/view/client/assets/chunk-2Q5K7J3B-C0TmOC1Q.js +1 -0
  163. package/dist/src/view/client/assets/chunk-5VM5RSS4-dfBOrYE6.js +15 -0
  164. package/dist/src/view/client/assets/chunk-F27PBJKO-DAnks99K.js +1 -0
  165. package/dist/src/view/client/assets/chunk-IMKFNOWR-CPjB07if.js +231 -0
  166. package/dist/src/view/client/assets/chunk-JWPE2WC7-VvU_gZI6.js +1 -0
  167. package/dist/src/view/client/assets/chunk-POPQ4Y6H-DDUbVF55.js +1 -0
  168. package/dist/src/view/client/assets/chunk-SVP7TREG-BHROkr6D.js +88 -0
  169. package/dist/src/view/client/assets/chunk-TICWLB2K-95PzGIOg.js +206 -0
  170. package/dist/src/view/client/assets/chunk-XXDRQBXY-MdmuLhIs.js +1 -0
  171. package/dist/src/view/client/assets/classDiagram-ZZMXUADV-HvQKkIS-.js +1 -0
  172. package/dist/src/view/client/assets/classDiagram-v2-VYDZK3BY-HvQKkIS-.js +1 -0
  173. package/dist/src/view/client/assets/cose-bilkent-JH36ORCC-CauUf_rN.js +1 -0
  174. package/dist/src/view/client/assets/cynefin-OW5HDTMX-CeA8IKWo.js +166 -0
  175. package/dist/src/view/client/assets/cynefinDiagram-5FMLGOSQ-L1rtcT8l.js +62 -0
  176. package/dist/src/view/client/assets/cytoscape.esm-DzSz-X2X.js +321 -0
  177. package/dist/src/view/client/assets/dagre-GXQ25YYZ-DLQwTa3H.js +4 -0
  178. package/dist/src/view/client/assets/defaultLocale-DX6XiGOO.js +1 -0
  179. package/dist/src/view/client/assets/diagram-S7CK7UJ4-kbKdIyXG.js +30 -0
  180. package/dist/src/view/client/assets/diagram-UQ7AKVKN-kmzp92RC.js +41 -0
  181. package/dist/src/view/client/assets/diagram-VSXAHHWV-EXJTU8qq.js +3 -0
  182. package/dist/src/view/client/assets/diagram-VX7I27RA-DAiIyMCG.js +24 -0
  183. package/dist/src/view/client/assets/diagram-Z3DM3KII-G9wz50ZV.js +24 -0
  184. package/dist/src/view/client/assets/ebnfDiagram-PWID7BFC-D2tVRCNA.js +1 -0
  185. package/dist/src/view/client/assets/erDiagram-RLTQ6QDP-C925J5A3.js +99 -0
  186. package/dist/src/view/client/assets/flowDiagram-HODETNUW-DzoCa-Nq.js +168 -0
  187. package/dist/src/view/client/assets/ganttDiagram-EL5Y4UJY-D2ebbg4p.js +292 -0
  188. package/dist/src/view/client/assets/gitGraphDiagram-WWUBYQGX-B4QratYy.js +106 -0
  189. package/dist/src/view/client/assets/index-BnENo9bV.js +1 -0
  190. package/dist/src/view/client/assets/index-D0ajr_Ja.css +1 -0
  191. package/dist/src/view/client/assets/index-KZcnpXJH.js +321 -0
  192. package/dist/src/view/client/assets/infoDiagram-27XIBGKW-DL4mtHkt.js +2 -0
  193. package/dist/src/view/client/assets/init-Gi6I4Gst.js +1 -0
  194. package/dist/src/view/client/assets/ishikawaDiagram-5VMMS53U-CW4mto2d.js +70 -0
  195. package/dist/src/view/client/assets/journeyDiagram-3NMN7TZE-8R4eacFW.js +139 -0
  196. package/dist/src/view/client/assets/kanban-definition-UXKFOSKX-Cy9ioXEJ.js +89 -0
  197. package/dist/src/view/client/assets/katex-HP8lGamR.js +257 -0
  198. package/dist/src/view/client/assets/layout-C0MFuhth.js +1 -0
  199. package/dist/src/view/client/assets/linear-Dv3N4dQR.js +1 -0
  200. package/dist/src/view/client/assets/logo-small-RR_KBOAT.svg +149 -0
  201. package/dist/src/view/client/assets/maplibre-gl-DnqZgfUS.js +800 -0
  202. package/dist/src/view/client/assets/mermaid.core-bds9A7e2.js +310 -0
  203. package/dist/src/view/client/assets/mindmap-definition-YA3MSWOX-DjmGW355.js +96 -0
  204. package/dist/src/view/client/assets/ordinal-Cboi1Yqb.js +1 -0
  205. package/dist/src/view/client/assets/pegDiagram-XKGWAZYB-BWsZp0ab.js +1 -0
  206. package/dist/src/view/client/assets/pieDiagram-E7YTZNPT-CCH2gGR-.js +39 -0
  207. package/dist/src/view/client/assets/quadrantDiagram-AXDQQJYC-CjocPJFz.js +7 -0
  208. package/dist/src/view/client/assets/railroadDiagram-O6MQD6OU-BjLeb2KF.js +1 -0
  209. package/dist/src/view/client/assets/requirementDiagram-BXWQKSXE-Ct6AoXRr.js +84 -0
  210. package/dist/src/view/client/assets/sankeyDiagram-P5KCCOFB-CIpgGN_w.js +40 -0
  211. package/dist/src/view/client/assets/sequenceDiagram-WJ2MYXX4-DrmLFj6w.js +162 -0
  212. package/dist/src/view/client/assets/sizeCapture-INFHLROL-BdFMe2Lr.js +1 -0
  213. package/dist/src/view/client/assets/stateDiagram-D77RDMKH-Zd9F1goW.js +1 -0
  214. package/dist/src/view/client/assets/stateDiagram-v2-MP3YSRHH-ByIKCftl.js +1 -0
  215. package/dist/src/view/client/assets/swimlanes-42K2YHIH-a7VFwi00.js +1 -0
  216. package/dist/src/view/client/assets/swimlanesDiagram-VR7AAH4N-CzxaUHB-.js +8 -0
  217. package/dist/src/view/client/assets/three.module-PDSP0dbZ.js +4108 -0
  218. package/dist/src/view/client/assets/timeline-definition-24CTP7MA-BExPu6X5.js +120 -0
  219. package/dist/src/view/client/assets/vennDiagram-4TSXK5OY-PC1MU3si.js +34 -0
  220. package/dist/src/view/client/assets/wardleyDiagram-VM6X3IG4-DNWqU3fa.js +78 -0
  221. package/dist/src/view/client/assets/xychartDiagram-S5SC5T6Z-E1uA28_D.js +7 -0
  222. package/dist/src/view/client/index.html +2 -2
  223. package/dist/src/view/client-shell.d.ts +2 -0
  224. package/dist/src/view/client-shell.js +5 -0
  225. package/dist/src/view/diagnostics.d.ts +3 -2
  226. package/dist/src/view/diagnostics.js +49 -58
  227. package/dist/src/view/document-edit.d.ts +8 -0
  228. package/dist/src/view/document-edit.js +25 -0
  229. package/dist/src/view/document-route.d.ts +12 -0
  230. package/dist/src/view/document-route.js +93 -0
  231. package/dist/src/view/document-tree.d.ts +18 -0
  232. package/dist/src/view/document-tree.js +139 -0
  233. package/dist/src/view/external-document-tree.d.ts +3 -0
  234. package/dist/src/view/external-document-tree.js +674 -0
  235. package/dist/src/view/git-diff.js +187 -0
  236. package/dist/src/view/git.js +1 -1
  237. package/dist/src/view/graph.d.ts +3 -2
  238. package/dist/src/view/graph.js +94 -7
  239. package/dist/src/view/highlight.d.ts +10 -2
  240. package/dist/src/view/highlight.js +4540 -1360
  241. package/dist/src/view/markdown.d.ts +3 -2
  242. package/dist/src/view/markdown.js +214 -53
  243. package/dist/src/view/protocol.d.ts +59 -6
  244. package/dist/src/view/references.d.ts +10 -25
  245. package/dist/src/view/references.js +117 -107
  246. package/dist/src/view/repository.d.ts +13 -3
  247. package/dist/src/view/repository.js +234 -15
  248. package/dist/src/view/search.d.ts +1 -1
  249. package/dist/src/view/search.js +3 -7
  250. package/dist/src/view/server.d.ts +11 -2
  251. package/dist/src/view/server.js +212 -70
  252. package/dist/src/view/source-target.js +5 -8
  253. package/dist/src/view/static-build.d.ts +1 -0
  254. package/dist/src/view/static-build.js +245 -42
  255. package/dist/src/view/static-protocol.d.ts +19 -2
  256. package/dist/src/view/store.d.ts +16 -3
  257. package/dist/src/view/store.js +228 -36
  258. package/dist/src/view/table-of-contents.d.ts +2 -2
  259. package/dist/src/view/table-of-contents.js +2 -22
  260. package/dist/src/walk.d.ts +5 -3
  261. package/dist/src/walk.js +102 -8
  262. package/package.json +46 -8
  263. package/plugins/lat-md/README.md +61 -0
  264. package/plugins/lat-md/hooks.json +38 -0
  265. package/plugins/lat-md/plugin.json +9 -0
  266. package/plugins/lat-md/rules/AGENTS.md +118 -0
  267. package/plugins/lat-md/skills/lat-md/SKILL.md +178 -0
  268. package/plugins/lat-md/skills/lat-md-conventions/SKILL.md +182 -0
  269. package/templates/AGENTS.md +15 -3
  270. package/templates/cursor-rules.md +12 -3
  271. package/templates/skill/SKILL.md +22 -1
  272. package/dist/src/view/client/assets/index-C3iaZyYT.css +0 -1
  273. package/dist/src/view/client/assets/index-KWi67IMz.js +0 -334
  274. package/dist/src/view/client/assets/logo-CNF8zs47.svg +0 -182
@@ -1,5 +1,6 @@
1
1
  import { dirname, relative, resolve } from 'node:path';
2
- import { toPosix } from '../walk.js';
2
+ import { toPosix } from '../path.js';
3
+ import { documentUrl } from './document-route.js';
3
4
  const VIEW_SEARCH_LIMIT = 10;
4
5
  const defaultDependencies = {
5
6
  async runIndex(latDir) {
@@ -11,11 +12,6 @@ const defaultDependencies = {
11
12
  return runSearch(latDir, query, limit, undefined, options);
12
13
  },
13
14
  };
14
- function documentUrl(path, headingId) {
15
- const encodedPath = path.split('/').map(encodeURIComponent).join('/');
16
- const fragment = headingId ? `#${encodeURIComponent(headingId)}` : '';
17
- return `/docs/${encodedPath}${fragment}`;
18
- }
19
15
  function viewSearchResult(latDir, match) {
20
16
  const section = match.section;
21
17
  const projectRoot = dirname(latDir);
@@ -27,7 +23,7 @@ function viewSearchResult(latDir, match) {
27
23
  path,
28
24
  breadcrumbs: [...fileBreadcrumbs, ...section.id.split('#').slice(1)],
29
25
  description: section.firstParagraph,
30
- url: documentUrl(path, section.githubSlug),
26
+ url: documentUrl(path, section.githubSlug ?? ''),
31
27
  score: match.score ?? 0,
32
28
  };
33
29
  }
@@ -1,5 +1,6 @@
1
1
  import { type Server } from 'node:http';
2
- import type { CmdContext } from '../context.js';
2
+ import { type LatServerApp } from '@lat.md/server';
3
+ import { type CmdContext } from '../context.js';
3
4
  import { type ViewSearch } from './search.js';
4
5
  import { type ViewStore } from './store.js';
5
6
  export declare const DEFAULT_VIEW_PORT = 4242;
@@ -9,6 +10,11 @@ export type ViewServer = {
9
10
  url: string;
10
11
  close: () => Promise<void>;
11
12
  };
13
+ export type ViewApp = {
14
+ app: LatServerApp;
15
+ store: ViewStore;
16
+ close: () => Promise<void>;
17
+ };
12
18
  export type ViewServerOptions = {
13
19
  clientDir?: string;
14
20
  git?: boolean;
@@ -18,6 +24,9 @@ export type ViewServerOptions = {
18
24
  port?: number;
19
25
  search?: ViewSearch;
20
26
  watch?: boolean;
27
+ externalCa?: string | Buffer;
21
28
  };
22
- /** Start the read-only loopback server used by `lat ui`. */
29
+ /** Create the portable Express application used by live Lat UI servers. */
30
+ export declare function createViewApp(ctx: CmdContext, options?: ViewServerOptions): Promise<ViewApp>;
31
+ /** Start the loopback server used by `lat ui`. */
23
32
  export declare function startViewServer(ctx: CmdContext, options?: ViewServerOptions): Promise<ViewServer>;
@@ -1,22 +1,22 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { createServer } from 'node:http';
2
+ import { randomUUID } from 'node:crypto';
3
3
  import { extname, join, relative, resolve, sep } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
+ import { createLatServerApp, listenLatServer, } from '@lat.md/server';
6
+ import { plainStyler } from '../context.js';
7
+ import { sectionCommand } from '../cli/section.js';
5
8
  import { DEFAULT_VIEW_LOGO_TEXT, } from './protocol.js';
6
- import { ViewDocumentNotFoundError, ViewSourceNotFoundError, } from './repository.js';
9
+ import { ViewDocumentConflictError } from './document-edit.js';
10
+ import { ViewExternalNotFoundError, ViewDocumentNotFoundError, ViewSourceNotFoundError, } from './repository.js';
7
11
  import { createViewSearch } from './search.js';
8
12
  import { createViewStore } from './store.js';
13
+ import { rewriteClientAssetUrls } from './client-shell.js';
14
+ import { documentResourcePath, documentUrl, rawDocumentPath, } from './document-route.js';
9
15
  const DEFAULT_HOST = '127.0.0.1';
10
16
  export const DEFAULT_VIEW_PORT = 4242;
17
+ const MAX_DOCUMENT_EDIT_BODY_BYTES = 16 * 1024 * 1024;
18
+ const MAX_DOCUMENT_CONTENT_LENGTH = 8 * 1024 * 1024;
11
19
  const defaultClientDir = fileURLToPath(new URL('./client/', import.meta.url));
12
- function documentUrl(path) {
13
- return `/docs/${path.split('/').map(encodeURIComponent).join('/')}`;
14
- }
15
- function setSecurityHeaders(res) {
16
- res.setHeader('Content-Security-Policy', "default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'");
17
- res.setHeader('Referrer-Policy', 'no-referrer');
18
- res.setHeader('X-Content-Type-Options', 'nosniff');
19
- }
20
20
  function send(res, status, contentType, body, headOnly = false) {
21
21
  res.statusCode = status;
22
22
  res.setHeader('Content-Type', contentType);
@@ -27,6 +27,24 @@ function sendJson(res, status, value, headOnly) {
27
27
  res.setHeader('Cache-Control', 'no-store');
28
28
  send(res, status, 'application/json; charset=utf-8', JSON.stringify(value), headOnly);
29
29
  }
30
+ async function readJsonBody(req) {
31
+ const chunks = [];
32
+ let size = 0;
33
+ for await (const value of req) {
34
+ const chunk = Buffer.isBuffer(value) ? value : Buffer.from(value);
35
+ size += chunk.byteLength;
36
+ if (size > MAX_DOCUMENT_EDIT_BODY_BYTES) {
37
+ throw new RangeError('Document edit is too large');
38
+ }
39
+ chunks.push(chunk);
40
+ }
41
+ try {
42
+ return JSON.parse(Buffer.concat(chunks).toString('utf8'));
43
+ }
44
+ catch {
45
+ throw new SyntaxError('Document edit must be valid JSON');
46
+ }
47
+ }
30
48
  function contentType(path) {
31
49
  switch (extname(path)) {
32
50
  case '.css':
@@ -37,8 +55,17 @@ function contentType(path) {
37
55
  return 'text/javascript; charset=utf-8';
38
56
  case '.json':
39
57
  return 'application/json; charset=utf-8';
58
+ case '.gif':
59
+ return 'image/gif';
60
+ case '.jpeg':
61
+ case '.jpg':
62
+ return 'image/jpeg';
63
+ case '.png':
64
+ return 'image/png';
40
65
  case '.svg':
41
66
  return 'image/svg+xml';
67
+ case '.webp':
68
+ return 'image/webp';
42
69
  case '.woff2':
43
70
  return 'font/woff2';
44
71
  default:
@@ -71,23 +98,20 @@ async function sendClientFile(res, path, headOnly, immutable = false) {
71
98
  send(res, 404, 'text/plain; charset=utf-8', 'Not found', headOnly);
72
99
  }
73
100
  }
74
- function listen(server, host, port) {
75
- return new Promise((resolveListen, reject) => {
76
- const onError = (error) => {
77
- server.off('listening', onListening);
78
- reject(error);
79
- };
80
- const onListening = () => {
81
- server.off('error', onError);
82
- resolveListen();
83
- };
84
- server.once('error', onError);
85
- server.once('listening', onListening);
86
- server.listen(port, host);
87
- });
101
+ async function sendClientShell(res, path, headOnly) {
102
+ try {
103
+ const body = rewriteClientAssetUrls(await readFile(path, 'utf8'), '/');
104
+ res.setHeader('Cache-Control', 'no-cache');
105
+ send(res, 200, 'text/html; charset=utf-8', body, headOnly);
106
+ }
107
+ catch (error) {
108
+ if (error.code !== 'ENOENT')
109
+ throw error;
110
+ send(res, 404, 'text/plain; charset=utf-8', 'Not found', headOnly);
111
+ }
88
112
  }
89
- /** Start the read-only loopback server used by `lat ui`. */
90
- export async function startViewServer(ctx, options = {}) {
113
+ /** Create the portable Express application used by live Lat UI servers. */
114
+ export async function createViewApp(ctx, options = {}) {
91
115
  const host = options.host ?? DEFAULT_HOST;
92
116
  const clientDir = options.clientDir ?? defaultClientDir;
93
117
  const logoText = options.logoText ?? DEFAULT_VIEW_LOGO_TEXT;
@@ -95,27 +119,34 @@ export async function startViewServer(ctx, options = {}) {
95
119
  git: options.git,
96
120
  gitPollMs: options.gitPollMs,
97
121
  watch: options.watch,
122
+ externalCa: options.externalCa,
98
123
  });
99
124
  const search = options.search ??
100
125
  createViewSearch(ctx.latDir, undefined, () => store.markdownGeneration);
101
126
  const eventClients = new Set();
127
+ const instanceId = randomUUID();
102
128
  const broadcastChange = (change) => {
103
- const message = `event: change\ndata: ${JSON.stringify(change)}\n\n`;
129
+ const message = `event: change\ndata: ${JSON.stringify({ ...change, instanceId })}\n\n`;
104
130
  for (const client of eventClients)
105
131
  client.write(message);
106
132
  };
107
133
  const unsubscribeStore = store.subscribe(broadcastChange);
108
- const server = createServer((req, res) => {
134
+ const heartbeat = setInterval(() => {
135
+ for (const client of eventClients)
136
+ client.write(': heartbeat\n\n');
137
+ }, 15_000);
138
+ heartbeat.unref();
139
+ const handleRequest = (req, res) => {
109
140
  void (async () => {
110
- setSecurityHeaders(res);
111
141
  const method = req.method ?? 'GET';
112
142
  const headOnly = method === 'HEAD';
113
- if (method !== 'GET' && !headOnly) {
143
+ const url = new URL(req.url ?? '/', `http://${host}`);
144
+ const documentEdit = method === 'PATCH' && url.pathname === '/api/document';
145
+ if (method !== 'GET' && !headOnly && !documentEdit) {
114
146
  res.setHeader('Allow', 'GET, HEAD');
115
147
  send(res, 405, 'text/plain; charset=utf-8', 'Method not allowed');
116
148
  return;
117
149
  }
118
- const url = new URL(req.url ?? '/', `http://${host}`);
119
150
  if (url.pathname === '/') {
120
151
  const entry = store.getIndex().entry;
121
152
  if (!entry) {
@@ -146,12 +177,52 @@ export async function startViewServer(ctx, options = {}) {
146
177
  return;
147
178
  }
148
179
  eventClients.add(res);
149
- res.write(`event: ready\ndata: ${JSON.stringify({ generation: store.snapshot.generation, markdownGeneration: store.markdownGeneration })}\n\n`);
180
+ res.write(`retry: 1000\nevent: ready\ndata: ${JSON.stringify({ instanceId, generation: store.snapshot.generation, markdownGeneration: store.markdownGeneration })}\n\n`);
150
181
  req.once('close', () => eventClients.delete(res));
151
182
  return;
152
183
  }
153
184
  if (url.pathname === '/api/document') {
154
185
  const path = url.searchParams.get('path') ?? '';
186
+ if (documentEdit) {
187
+ let body;
188
+ try {
189
+ body = await readJsonBody(req);
190
+ }
191
+ catch (error) {
192
+ sendJson(res, error instanceof RangeError ? 413 : 400, { error: error.message }, false);
193
+ return;
194
+ }
195
+ if (!body ||
196
+ typeof body !== 'object' ||
197
+ typeof body.baseContent !==
198
+ 'string' ||
199
+ typeof body.content !==
200
+ 'string') {
201
+ sendJson(res, 400, {
202
+ error: 'Document edit content is required',
203
+ }, false);
204
+ return;
205
+ }
206
+ const edit = body;
207
+ if (edit.baseContent.length > MAX_DOCUMENT_CONTENT_LENGTH ||
208
+ edit.content.length > MAX_DOCUMENT_CONTENT_LENGTH) {
209
+ sendJson(res, 413, { error: 'Document edit is too large' }, false);
210
+ return;
211
+ }
212
+ try {
213
+ sendJson(res, 200, await store.editDocument(path, edit.baseContent, edit.content), false);
214
+ }
215
+ catch (error) {
216
+ if (error instanceof ViewDocumentConflictError) {
217
+ sendJson(res, 409, { error: error.message }, false);
218
+ return;
219
+ }
220
+ if (!(error instanceof ViewDocumentNotFoundError))
221
+ throw error;
222
+ sendJson(res, 404, { error: error.message }, false);
223
+ }
224
+ return;
225
+ }
155
226
  try {
156
227
  sendJson(res, 200, await store.getDocument(path), headOnly);
157
228
  }
@@ -162,6 +233,18 @@ export async function startViewServer(ctx, options = {}) {
162
233
  }
163
234
  return;
164
235
  }
236
+ if (url.pathname === '/api/document-source') {
237
+ const path = url.searchParams.get('path') ?? '';
238
+ try {
239
+ sendJson(res, 200, await store.getDocumentSource(path), headOnly);
240
+ }
241
+ catch (error) {
242
+ if (!(error instanceof ViewDocumentNotFoundError))
243
+ throw error;
244
+ sendJson(res, 404, { error: error.message }, headOnly);
245
+ }
246
+ return;
247
+ }
165
248
  if (url.pathname === '/api/search') {
166
249
  const query = (url.searchParams.get('query') ?? '').trim();
167
250
  if (query.length > 500) {
@@ -171,6 +254,30 @@ export async function startViewServer(ctx, options = {}) {
171
254
  sendJson(res, 200, await search(query), headOnly);
172
255
  return;
173
256
  }
257
+ if (url.pathname === '/api/section') {
258
+ const query = (url.searchParams.get('query') ?? '').trim();
259
+ if (!query) {
260
+ sendJson(res, 400, { error: 'Section ID is required' }, headOnly);
261
+ return;
262
+ }
263
+ if (query.length > 1_000) {
264
+ sendJson(res, 400, { error: 'Section ID is too long' }, headOnly);
265
+ return;
266
+ }
267
+ const result = await sectionCommand({
268
+ latDir: ctx.latDir,
269
+ projectRoot: ctx.projectRoot,
270
+ styler: plainStyler,
271
+ mode: 'cli',
272
+ }, query);
273
+ const tree = await store.renderSectionOutput(result.output, query);
274
+ sendJson(res, 200, {
275
+ output: result.output,
276
+ tree,
277
+ isError: result.isError === true,
278
+ }, headOnly);
279
+ return;
280
+ }
174
281
  if (url.pathname === '/api/source') {
175
282
  const path = url.searchParams.get('path') ?? '';
176
283
  const symbol = url.searchParams.get('symbol') ?? '';
@@ -193,6 +300,18 @@ export async function startViewServer(ctx, options = {}) {
193
300
  }
194
301
  return;
195
302
  }
303
+ if (url.pathname === '/api/external') {
304
+ const target = url.searchParams.get('target') ?? '';
305
+ try {
306
+ sendJson(res, 200, await store.getExternal(target), headOnly);
307
+ }
308
+ catch (error) {
309
+ if (!(error instanceof ViewExternalNotFoundError))
310
+ throw error;
311
+ sendJson(res, 404, { error: error.message }, headOnly);
312
+ }
313
+ return;
314
+ }
196
315
  if (url.pathname.startsWith('/assets/')) {
197
316
  const path = clientPath(clientDir, url.pathname);
198
317
  if (!path) {
@@ -202,11 +321,40 @@ export async function startViewServer(ctx, options = {}) {
202
321
  await sendClientFile(res, path, headOnly, true);
203
322
  return;
204
323
  }
324
+ const rawMarkdownPath = rawDocumentPath(url.pathname);
325
+ if (rawMarkdownPath) {
326
+ try {
327
+ const source = await store.getDocumentSource(rawMarkdownPath);
328
+ res.setHeader('Cache-Control', 'no-cache');
329
+ send(res, 200, 'text/markdown; charset=utf-8', source.content, headOnly);
330
+ }
331
+ catch (error) {
332
+ if (!(error instanceof ViewDocumentNotFoundError))
333
+ throw error;
334
+ send(res, 404, 'text/plain; charset=utf-8', error.message, headOnly);
335
+ }
336
+ return;
337
+ }
338
+ const resourcePath = documentResourcePath(url.pathname);
339
+ if (resourcePath) {
340
+ try {
341
+ const body = await store.getDocumentResource(resourcePath);
342
+ res.setHeader('Cache-Control', 'no-cache');
343
+ send(res, 200, contentType(resourcePath), body, headOnly);
344
+ }
345
+ catch (error) {
346
+ if (!(error instanceof ViewDocumentNotFoundError))
347
+ throw error;
348
+ send(res, 404, 'text/plain; charset=utf-8', error.message, headOnly);
349
+ }
350
+ return;
351
+ }
205
352
  if (url.pathname === '/search' ||
206
353
  url.pathname === '/graph' ||
207
354
  url.pathname.startsWith('/docs/') ||
208
- url.pathname.startsWith('/code/')) {
209
- await sendClientFile(res, join(clientDir, 'index.html'), headOnly);
355
+ url.pathname.startsWith('/code/') ||
356
+ url.pathname.startsWith('/external/')) {
357
+ await sendClientShell(res, join(clientDir, 'index.html'), headOnly);
210
358
  return;
211
359
  }
212
360
  send(res, 404, 'text/plain; charset=utf-8', 'Not found', headOnly);
@@ -217,50 +365,44 @@ export async function startViewServer(ctx, options = {}) {
217
365
  }
218
366
  sendJson(res, 500, { error: error.message }, false);
219
367
  });
220
- });
221
- try {
222
- const requestedPort = options.port;
223
- let port = requestedPort ?? DEFAULT_VIEW_PORT;
224
- while (true) {
225
- try {
226
- await listen(server, host, port);
227
- break;
228
- }
229
- catch (error) {
230
- if (requestedPort !== undefined ||
231
- error.code !== 'EADDRINUSE' ||
232
- port === 65_535) {
233
- throw error;
234
- }
235
- port++;
236
- }
237
- }
238
- }
239
- catch (error) {
240
- unsubscribeStore();
241
- await store.close();
242
- throw error;
243
- }
244
- const address = server.address();
245
- if (!address || typeof address === 'string') {
246
- await new Promise((resolveClose) => server.close(() => resolveClose()));
247
- unsubscribeStore();
248
- await store.close();
249
- throw new Error('Could not determine lat ui server address');
250
- }
368
+ };
369
+ const app = createLatServerApp({ handle: handleRequest });
251
370
  return {
252
- server,
371
+ app,
253
372
  store,
254
- url: `http://${host}:${address.port}/`,
255
373
  close: async () => {
374
+ clearInterval(heartbeat);
256
375
  unsubscribeStore();
257
376
  for (const client of eventClients)
258
377
  client.end();
259
378
  eventClients.clear();
260
379
  await store.close();
261
- await new Promise((resolveClose, reject) => {
262
- server.close((error) => (error ? reject(error) : resolveClose()));
263
- });
380
+ },
381
+ };
382
+ }
383
+ /** Start the loopback server used by `lat ui`. */
384
+ export async function startViewServer(ctx, options = {}) {
385
+ const host = options.host ?? DEFAULT_HOST;
386
+ const view = await createViewApp(ctx, options);
387
+ let running;
388
+ try {
389
+ const requestedPort = options.port;
390
+ running = await listenLatServer(view.app, {
391
+ host,
392
+ port: requestedPort ?? DEFAULT_VIEW_PORT,
393
+ findAvailablePort: requestedPort === undefined,
394
+ });
395
+ }
396
+ catch (error) {
397
+ await view.close();
398
+ throw error;
399
+ }
400
+ return {
401
+ server: running.server,
402
+ store: view.store,
403
+ url: running.url,
404
+ close: async () => {
405
+ await Promise.all([running.close(), view.close()]);
264
406
  },
265
407
  };
266
408
  }
@@ -1,16 +1,13 @@
1
- import { extname, posix } from 'node:path';
2
- import { SOURCE_EXTENSIONS } from '../source-parser.js';
1
+ import { extname } from 'node:path';
2
+ import { normalizeRepositoryPath } from '../repository-path.js';
3
+ import { isSourceFileExtension } from '../source-formats.js';
3
4
  /** Normalize a supported source wiki-link target for view indexes and routes. */
4
5
  export function viewSourceTarget(target) {
5
6
  const hash = target.indexOf('#');
6
7
  const authoredPath = hash === -1 ? target : target.slice(0, hash);
7
- if (!authoredPath ||
8
- authoredPath.includes('\\') ||
9
- posix.isAbsolute(authoredPath) ||
10
- !SOURCE_EXTENSIONS.has(extname(authoredPath))) {
8
+ const path = normalizeRepositoryPath(authoredPath);
9
+ if (!path || !isSourceFileExtension(extname(path)))
11
10
  return null;
12
- }
13
- const path = posix.normalize(authoredPath).replace(/^\.\//, '');
14
11
  const symbol = hash === -1 ? '' : target.slice(hash + 1);
15
12
  const fileKey = path.toLowerCase();
16
13
  return {
@@ -3,6 +3,7 @@ export type StaticViewBuildOptions = {
3
3
  basePath?: string;
4
4
  clientDir?: string;
5
5
  logoText?: string;
6
+ externalCa?: string | Buffer;
6
7
  };
7
8
  export type StaticViewBuildResult = {
8
9
  documents: number;