rdstudio 0.2.0__py3-none-any.whl

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 (251) hide show
  1. rdstudio/__init__.py +3 -0
  2. rdstudio/_node/rdstudio.mjs +59351 -0
  3. rdstudio/brief.py +46 -0
  4. rdstudio/build.py +218 -0
  5. rdstudio/classify.py +131 -0
  6. rdstudio/cli.py +369 -0
  7. rdstudio/config.py +115 -0
  8. rdstudio/gitlog.py +92 -0
  9. rdstudio/launcher.py +63 -0
  10. rdstudio/learner.py +187 -0
  11. rdstudio/mcp_server.py +313 -0
  12. rdstudio/okf.py +775 -0
  13. rdstudio/procedures.py +233 -0
  14. rdstudio/references.py +228 -0
  15. rdstudio/reports.py +75 -0
  16. rdstudio/scaffold.py +227 -0
  17. rdstudio/scopes.py +162 -0
  18. rdstudio/search.py +149 -0
  19. rdstudio/serve.py +214 -0
  20. rdstudio/store.py +161 -0
  21. rdstudio/templates/agents/critic.md +20 -0
  22. rdstudio/templates/agents/librarian.md +21 -0
  23. rdstudio/templates/agents/searcher.md +21 -0
  24. rdstudio/templates/agents_section.md +31 -0
  25. rdstudio/templates/claude_section.md +3 -0
  26. rdstudio/templates/knowledge/overview.md +20 -0
  27. rdstudio/templates/knowledge/tasks/bootstrap-knowledge-base.md +45 -0
  28. rdstudio/templates/skills/decision/SKILL.md +47 -0
  29. rdstudio/templates/skills/handoff/SKILL.md +18 -0
  30. rdstudio/templates/skills/ingest-ref/SKILL.md +32 -0
  31. rdstudio/templates/skills/lint-okf/SKILL.md +15 -0
  32. rdstudio/templates/skills/promote/SKILL.md +25 -0
  33. rdstudio/templates/skills/question/SKILL.md +33 -0
  34. rdstudio/templates/skills/record-okf/SKILL.md +61 -0
  35. rdstudio/templates/skills/report/SKILL.md +46 -0
  36. rdstudio/templates/skills/report/template.html +74 -0
  37. rdstudio/templates/skills/search-okf/SKILL.md +36 -0
  38. rdstudio/templates/skills/task/SKILL.md +51 -0
  39. rdstudio/web/app.js +140 -0
  40. rdstudio/web/fonts.css +48 -0
  41. rdstudio/web/icon-192.png +0 -0
  42. rdstudio/web/icon-512.png +0 -0
  43. rdstudio/web/index.html +109 -0
  44. rdstudio/web/js/data.js +125 -0
  45. rdstudio/web/js/diagrams.js +116 -0
  46. rdstudio/web/js/graph.js +396 -0
  47. rdstudio/web/js/knowledge.js +212 -0
  48. rdstudio/web/js/layout-worker.js +15 -0
  49. rdstudio/web/js/layout.js +226 -0
  50. rdstudio/web/js/learn.js +83 -0
  51. rdstudio/web/js/map.js +1293 -0
  52. rdstudio/web/js/markdown.js +173 -0
  53. rdstudio/web/js/pages.js +219 -0
  54. rdstudio/web/js/procedures.js +262 -0
  55. rdstudio/web/js/settings.js +95 -0
  56. rdstudio/web/js/util.js +99 -0
  57. rdstudio/web/report.css +35 -0
  58. rdstudio/web/report.js +104 -0
  59. rdstudio/web/style.css +566 -0
  60. rdstudio/web/sw.js +166 -0
  61. rdstudio/web/themes/cyber.css +139 -0
  62. rdstudio/web/themes/map.css +133 -0
  63. rdstudio/web/themes/notebook.css +134 -0
  64. rdstudio/web/themes/space.css +135 -0
  65. rdstudio/web/themes/studio.css +110 -0
  66. rdstudio/web/vendor/d3.min.js +2 -0
  67. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-italic.woff2 +0 -0
  68. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-ext-wght-normal.woff2 +0 -0
  69. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-italic.woff2 +0 -0
  70. rdstudio/web/vendor/fonts/atkinson-hyperlegible-next-latin-wght-normal.woff2 +0 -0
  71. rdstudio/web/vendor/fonts/caveat-latin-wght-normal.woff2 +0 -0
  72. rdstudio/web/vendor/fonts/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  73. rdstudio/web/vendor/fonts/ibm-plex-mono-latin-600-normal.woff2 +0 -0
  74. rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-italic.woff2 +0 -0
  75. rdstudio/web/vendor/fonts/ibm-plex-sans-latin-wght-normal.woff2 +0 -0
  76. rdstudio/web/vendor/fonts/inter-latin-opsz-italic.woff2 +0 -0
  77. rdstudio/web/vendor/fonts/inter-latin-opsz-normal.woff2 +0 -0
  78. rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-italic.woff2 +0 -0
  79. rdstudio/web/vendor/fonts/jetbrains-mono-latin-wght-normal.woff2 +0 -0
  80. rdstudio/web/vendor/fonts/literata-latin-ext-opsz-italic.woff2 +0 -0
  81. rdstudio/web/vendor/fonts/literata-latin-ext-opsz-normal.woff2 +0 -0
  82. rdstudio/web/vendor/fonts/literata-latin-opsz-italic.woff2 +0 -0
  83. rdstudio/web/vendor/fonts/literata-latin-opsz-normal.woff2 +0 -0
  84. rdstudio/web/vendor/fonts/source-sans-3-latin-wght-italic.woff2 +0 -0
  85. rdstudio/web/vendor/fonts/source-sans-3-latin-wght-normal.woff2 +0 -0
  86. rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-italic.woff2 +0 -0
  87. rdstudio/web/vendor/fonts/source-serif-4-latin-opsz-normal.woff2 +0 -0
  88. rdstudio/web/vendor/hljs/github-dark.min.css +10 -0
  89. rdstudio/web/vendor/hljs/github.min.css +10 -0
  90. rdstudio/web/vendor/hljs/highlight.min.js +1262 -0
  91. rdstudio/web/vendor/hljs/julia.min.js +18 -0
  92. rdstudio/web/vendor/hljs/latex.min.js +33 -0
  93. rdstudio/web/vendor/hljs/matlab.min.js +15 -0
  94. rdstudio/web/vendor/katex/auto-render.min.js +1 -0
  95. rdstudio/web/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  96. rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  97. rdstudio/web/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  98. rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  99. rdstudio/web/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  100. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  101. rdstudio/web/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  102. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  103. rdstudio/web/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  104. rdstudio/web/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  105. rdstudio/web/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  106. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  107. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  108. rdstudio/web/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  109. rdstudio/web/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  110. rdstudio/web/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  111. rdstudio/web/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  112. rdstudio/web/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  113. rdstudio/web/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  114. rdstudio/web/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  115. rdstudio/web/vendor/katex/katex.min.css +1 -0
  116. rdstudio/web/vendor/katex/katex.min.js +1 -0
  117. rdstudio/web/vendor/licenses/atkinson-hyperlegible-next-OFL.txt +93 -0
  118. rdstudio/web/vendor/licenses/caveat-OFL.txt +93 -0
  119. rdstudio/web/vendor/licenses/d3-7.9.0.txt +13 -0
  120. rdstudio/web/vendor/licenses/highlightjs-cdn-assets-11.12.0.txt +29 -0
  121. rdstudio/web/vendor/licenses/ibm-plex-mono-OFL.txt +93 -0
  122. rdstudio/web/vendor/licenses/ibm-plex-sans-OFL.txt +93 -0
  123. rdstudio/web/vendor/licenses/inter-OFL.txt +93 -0
  124. rdstudio/web/vendor/licenses/jetbrains-mono-OFL.txt +93 -0
  125. rdstudio/web/vendor/licenses/katex-0.18.7.txt +21 -0
  126. rdstudio/web/vendor/licenses/literata-OFL.txt +93 -0
  127. rdstudio/web/vendor/licenses/markdown-it-15.0.2.txt +22 -0
  128. rdstudio/web/vendor/licenses/markdown-it-footnote-4.0.0.txt +22 -0
  129. rdstudio/web/vendor/licenses/markdown-it-texmath-1.0.0.txt +21 -0
  130. rdstudio/web/vendor/licenses/mermaid-12.0.0.txt +21 -0
  131. rdstudio/web/vendor/licenses/source-sans-3-OFL.txt +93 -0
  132. rdstudio/web/vendor/licenses/source-serif-4-OFL.txt +93 -0
  133. rdstudio/web/vendor/licenses/vega-6.4.0.txt +27 -0
  134. rdstudio/web/vendor/licenses/vega-embed-7.2.0.txt +27 -0
  135. rdstudio/web/vendor/licenses/vega-lite-6.4.3.txt +27 -0
  136. rdstudio/web/vendor/markdown-it-footnote.min.js +2 -0
  137. rdstudio/web/vendor/markdown-it.min.js +14 -0
  138. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/abnfDiagram-DGLNOSUI.mjs +1 -0
  139. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architecture-7GRP2DOG-HF4J73XS.mjs +1 -0
  140. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/architectureDiagram-IU6H5EVY.mjs +36 -0
  141. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/blockDiagram-FSX6DT2V.mjs +140 -0
  142. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/c4Diagram-YNRVYILN.mjs +38 -0
  143. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-22SYA5HA.mjs +1 -0
  144. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-2FTYKNOQ.mjs +1 -0
  145. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-36VM3LGA.mjs +1 -0
  146. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3SEI7E7V.mjs +1 -0
  147. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-3T2MNPEU.mjs +1 -0
  148. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-44AU3KAP.mjs +174 -0
  149. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-4MZOTL6B.mjs +1 -0
  150. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-52IMQVNL.mjs +1 -0
  151. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-5NYYYZGL.mjs +1 -0
  152. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-BOZ75642.mjs +15 -0
  153. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-CVBX6EWK.mjs +1 -0
  154. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-D36F7FYG.mjs +14 -0
  155. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-DEGLBX6O.mjs +213 -0
  156. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-F6PIGZHG.mjs +1 -0
  157. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FE53TMRB.mjs +133 -0
  158. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-FNR2RGIF.mjs +1 -0
  159. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-GDOATIN3.mjs +32 -0
  160. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-H7BX327W.mjs +2 -0
  161. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-HI3EPXIC.mjs +2 -0
  162. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JCBDXJ5W.mjs +1 -0
  163. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-JGXDHUYS.mjs +1 -0
  164. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-L2B47AAZ.mjs +1 -0
  165. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LIEGGFIX.mjs +1 -0
  166. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-LLWYPJEZ.mjs +1 -0
  167. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MFYRHTYG.mjs +1 -0
  168. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MIE3LDYP.mjs +1 -0
  169. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MLQVGYRW.mjs +1 -0
  170. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-MNT3HHAC.mjs +1 -0
  171. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-NRRTYJAG.mjs +1 -0
  172. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-OCMJQKYE.mjs +10 -0
  173. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-PKJCTQEK.mjs +339 -0
  174. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QADHU5CF.mjs +2 -0
  175. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QVIEFLGF.mjs +1 -0
  176. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-QX7LGXZU.mjs +1 -0
  177. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-R2KH53MI.mjs +1 -0
  178. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-RXM7Z7NK.mjs +2 -0
  179. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-S4TE5DX6.mjs +1 -0
  180. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-SDIBIOXT.mjs +1 -0
  181. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TDFULVG6.mjs +1 -0
  182. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TECWHJ6D.mjs +62 -0
  183. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-TINYC7RT.mjs +70 -0
  184. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-V3KJR4XU.mjs +1 -0
  185. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-VTMXW4ES.mjs +1 -0
  186. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5NT6LW5.mjs +88 -0
  187. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-Y5O5OPKC.mjs +3 -0
  188. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-YZG4BXY6.mjs +1 -0
  189. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/chunk-ZZ52EIBP.mjs +1 -0
  190. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/classDiagram-v2-VV4INRHB.mjs +217 -0
  191. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cose-bilkent-MSIMRU2B.mjs +1 -0
  192. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefin-OW5HDTMX-KMPDV6GS.mjs +1 -0
  193. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/cynefinDiagram-KTGLXFXK.mjs +62 -0
  194. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/dagre-QJJCSHY4.mjs +4 -0
  195. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-2ZIM67RP.mjs +3 -0
  196. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-FYHH5O6R.mjs +24 -0
  197. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-LYLDACMS.mjs +200 -0
  198. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-TYT3HRBC.mjs +24 -0
  199. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-XVIN6YVD.mjs +30 -0
  200. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/diagram-ZO2NLLYC.mjs +41 -0
  201. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ebnfDiagram-TU3FGH35.mjs +1 -0
  202. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/elk-GHAXNSLK.mjs +27 -0
  203. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/erDiagram-XID3VV23.mjs +99 -0
  204. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/eventmodeling-NTZA5JFV-P5QJSWSK.mjs +1 -0
  205. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/flowDiagram-WPHQAKTU.mjs +1 -0
  206. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ganttDiagram-QACGSKG6.mjs +292 -0
  207. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraph-4MIJSDKK-V5NEQQYP.mjs +1 -0
  208. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/gitGraphDiagram-EB76O4HC.mjs +106 -0
  209. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/info-A6RAGUB7-IGBHH2L7.mjs +1 -0
  210. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/infoDiagram-343ZKIDN.mjs +2 -0
  211. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/ishikawaDiagram-UEEU5D75.mjs +70 -0
  212. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/journeyDiagram-4QTZOVLF.mjs +139 -0
  213. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/kanban-definition-VEJL4RG2.mjs +89 -0
  214. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/katex-QVRI4OIV.mjs +257 -0
  215. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/mindmap-definition-C5K5ZT7T.mjs +96 -0
  216. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/packet-AYTQ26CC-PJTKDECJ.mjs +1 -0
  217. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pegDiagram-FHIILVJB.mjs +1 -0
  218. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pie-WAS4IAKB-U3JFQXZN.mjs +1 -0
  219. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/pieDiagram-S76BU6V7.mjs +39 -0
  220. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/quadrantDiagram-HZBLWUZE.mjs +7 -0
  221. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/radar-RG4KPBEZ-7T2UW32D.mjs +1 -0
  222. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-74A4TZTK-DZY7ARDL.mjs +1 -0
  223. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-abnf-HS5TGJTU-LWS535ZK.mjs +1 -0
  224. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-ebnf-LZEXJU2U-EA3S4ZFM.mjs +1 -0
  225. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroad-peg-WCYAUIDC-DUKZONPX.mjs +1 -0
  226. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/railroadDiagram-R6RP3MQ2.mjs +1 -0
  227. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/requirementDiagram-SGBABQV6.mjs +84 -0
  228. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sankeyDiagram-IM6M5BOW.mjs +40 -0
  229. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sequenceDiagram-FGJOIFJ2.mjs +169 -0
  230. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/sizeCapture-ERV5D3W5.mjs +1 -0
  231. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/stateDiagram-v2-6KDGCZ5J.mjs +291 -0
  232. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanes-OJBUSBEF.mjs +1 -0
  233. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/swimlanesDiagram-LNXYTHDW.mjs +8 -0
  234. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/timeline-definition-7G6YQRTY.mjs +120 -0
  235. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treeView-Q6P3EWNA-AABPVERA.mjs +1 -0
  236. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/treemap-WGGIJYW6-F2JSY3V3.mjs +1 -0
  237. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/usecaseDiagram-6Y6TMLBO.mjs +383 -0
  238. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/vennDiagram-6RODYPCK.mjs +34 -0
  239. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardley-WFR3VGLG-EIT5NWVN.mjs +1 -0
  240. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/wardleyDiagram-RFZKLAPA.mjs +78 -0
  241. rdstudio/web/vendor/mermaid/chunks/mermaid.esm.min/xychartDiagram-TWWI2QJZ.mjs +7 -0
  242. rdstudio/web/vendor/mermaid/mermaid.esm.min.mjs +14 -0
  243. rdstudio/web/vendor/texmath.js +368 -0
  244. rdstudio/web/vendor/vega/vega-embed.min.js +7 -0
  245. rdstudio/web/vendor/vega/vega-lite.min.js +2 -0
  246. rdstudio/web/vendor/vega/vega.min.js +1 -0
  247. rdstudio-0.2.0.dist-info/METADATA +201 -0
  248. rdstudio-0.2.0.dist-info/RECORD +251 -0
  249. rdstudio-0.2.0.dist-info/WHEEL +4 -0
  250. rdstudio-0.2.0.dist-info/entry_points.txt +3 -0
  251. rdstudio-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,212 @@
1
+ // Knowledge tab: directory tree, concept and directory pages, frontmatter panel.
2
+
3
+ import { store, body, learner, record } from "./data.js";
4
+ import { hasRequires, prerequisites, depthLabel } from "./learn.js";
5
+ import { render, wireAnchors } from "./markdown.js";
6
+ import { h, conceptHref, dirHref, trustState, trustBadge, timeEl, titleCase, fmtDateTime } from "./util.js";
7
+
8
+ const openDirs = new Set(JSON.parse(sessionStorage.getItem("rdstudio.openDirs") || "[]"));
9
+ let filterText = "";
10
+
11
+ function saveOpenDirs() {
12
+ try { sessionStorage.setItem("rdstudio.openDirs", JSON.stringify([...openDirs])); } catch { /* private mode */ }
13
+ }
14
+
15
+ function dirLabel(id) {
16
+ if (!id) return store.site.title || "Knowledge";
17
+ const name = id.split("/").pop();
18
+ return titleCase(name.replace(/[-_]/g, " "));
19
+ }
20
+
21
+ // ------------------------------------------------------------------ tree
22
+
23
+ function matches(c) {
24
+ if (!filterText) return true;
25
+ const q = filterText.toLowerCase();
26
+ return [c.title, c.description, c.type, ...(c.tags || [])].join(" ").toLowerCase().includes(q);
27
+ }
28
+
29
+ function dirHasMatch(id) {
30
+ const d = store.tree[id];
31
+ if (!d) return false;
32
+ return d.concepts.some((cid) => matches(store.concepts.get(cid))) || d.children.some(dirHasMatch);
33
+ }
34
+
35
+ function treeList(id, current) {
36
+ const d = store.tree[id];
37
+ const items = [];
38
+ for (const child of d.children) {
39
+ if (filterText && !dirHasMatch(child)) continue;
40
+ const open = filterText ? true : openDirs.has(child) || (current && (current + "/").startsWith(child + "/"));
41
+ const details = h("details", { open },
42
+ h("summary", { class: `dir${current === child ? " current" : ""}` },
43
+ h("svg:svg", { class: "twisty", width: 12, height: 12, viewBox: "0 0 12 12", "aria-hidden": "true" },
44
+ h("svg:path", { d: "M4 2l4 4-4 4", fill: "none", stroke: "currentColor", "stroke-width": 1.6 })),
45
+ h("a", { href: dirHref(child) }, dirLabel(child))),
46
+ treeList(child, current));
47
+ details.addEventListener("toggle", () => {
48
+ if (filterText) return;
49
+ details.open ? openDirs.add(child) : openDirs.delete(child);
50
+ saveOpenDirs();
51
+ });
52
+ items.push(h("li", {}, details));
53
+ }
54
+ for (const cid of d.concepts) {
55
+ const c = store.concepts.get(cid);
56
+ if (!c || !matches(c) || (cid === d.overview && !filterText)) continue;
57
+ items.push(h("li", { class: "item" },
58
+ h("a", { href: conceptHref(cid), "aria-current": current === "k:" + cid ? "page" : null, title: c.description || c.title },
59
+ h("span", { class: `dot ${trustState(c)}` }), h("span", {}, c.title))));
60
+ }
61
+ return h("ul", {}, items);
62
+ }
63
+
64
+ function renderTree(aside, current) {
65
+ const input = h("input", {
66
+ class: "filter", type: "search", placeholder: "Filter by title, type or tag", value: filterText,
67
+ "aria-label": "Filter knowledge",
68
+ });
69
+ const list = h("div");
70
+ const draw = () => list.replaceChildren(
71
+ h("a", { class: "root-link", href: "#/" }, dirLabel("")),
72
+ treeList("", current));
73
+ input.addEventListener("input", () => { filterText = input.value.trim(); draw(); });
74
+ aside.replaceChildren(input, list);
75
+ draw();
76
+ }
77
+
78
+ // ------------------------------------------------------------ frontmatter
79
+
80
+ const HIDDEN_KEYS = new Set(["title", "description"]);
81
+
82
+ function fmValue(value) {
83
+ if (value === null || value === undefined) return "";
84
+ if (Array.isArray(value)) {
85
+ if (value.every((v) => typeof v !== "object" || v === null)) {
86
+ return h("span", {}, value.map((v) => h("span", { class: "chip" }, String(v))));
87
+ }
88
+ return h("ul", {}, value.map((v) => h("li", {}, fmValue(v))));
89
+ }
90
+ if (typeof value === "object") {
91
+ // Actor events ({by, at}) read best as "who" over "when".
92
+ if ("by" in value && Object.keys(value).every((k) => k === "by" || k === "at")) {
93
+ return h("span", {}, String(value.by), value.at ? h("span", { class: "sub" }, fmtDateTime(value.at)) : "");
94
+ }
95
+ return h("ul", {}, Object.entries(value).map(([k, v]) => h("li", {}, h("span", { class: "sub" }, k), fmValue(v))));
96
+ }
97
+ const s = String(value);
98
+ if (/^https?:\/\//.test(s)) return h("a", { href: s, target: "_blank", rel: "noopener" }, s);
99
+ if (/^\d{4}-\d{2}-\d{2}T/.test(s)) return h("span", { title: s }, fmtDateTime(s));
100
+ return s;
101
+ }
102
+
103
+ function frontmatterTable(meta) {
104
+ const rows = Object.entries(meta).filter(([k]) => !HIDDEN_KEYS.has(k));
105
+ return h("table", { class: "fm" }, h("tbody", {}, rows.map(([k, v]) => h("tr", {}, h("th", { scope: "row" }, k), h("td", {}, fmValue(v))))));
106
+ }
107
+
108
+ function metaPanel(c) {
109
+ const narrow = matchMedia("(max-width: 760px)").matches;
110
+ const outline = c.headings.filter((x) => x.level <= 3);
111
+ const back = c.backlinks.map((id) => store.concepts.get(id)).filter(Boolean);
112
+ const out = c.links.filter((l) => l.kind === "concept" && !l.broken).map((l) => store.concepts.get(l.target)).filter(Boolean);
113
+ const uniq = (list) => [...new Map(list.map((x) => [x.id, x])).values()];
114
+ return h("aside", { class: "meta", "aria-label": "Details" },
115
+ h("div", { class: "meta-inner" },
116
+ h("h2", {}, "Trust"),
117
+ trustBadge(c),
118
+ c.verification_stale ? h("p", { class: "section-note" }, "Meaningfully edited after the last human review.") : "",
119
+ h("details", { class: "fm-wrap", open: !narrow },
120
+ h("summary", {}, "Frontmatter"),
121
+ h("h2", {}, "Frontmatter"),
122
+ frontmatterTable(c.meta)),
123
+ outline.length > 1 ? [h("h2", {}, "On this page"),
124
+ h("ul", { class: "outline" }, outline.map((x) => h("li", { class: `l${x.level}` },
125
+ h("a", { href: "javascript:void(0)", "data-anchor": "h-" + x.slug }, x.text))))] : "",
126
+ studyPath(c),
127
+ back.length ? [h("h2", {}, "Linked from"), linkList(uniq(back))] : "",
128
+ out.length ? [h("h2", {}, "Links to"), linkList(uniq(out))] : ""));
129
+ }
130
+
131
+ // What to read first: everything this note requires, in reading order.
132
+ function studyPath(c) {
133
+ if (!hasRequires()) return "";
134
+ const before = prerequisites(c.id);
135
+ if (!before.length) return [h("h2", {}, "Study path"), h("p", { class: "section-note" }, "Nothing is marked as required before this note.")];
136
+ const list = h("ol", { class: "linklist study" }, before.map((p) => h("li", {}, h("a", { href: conceptHref(p.id) }, p.title), depthLabel(p))));
137
+ return [
138
+ h("h2", {}, "Study path"),
139
+ h("p", { class: "section-note" }, `${before.length} ${before.length === 1 ? "note comes" : "notes come"} before this one. `,
140
+ h("a", { href: "#/path/" + encodeURIComponent(c.id) }, "Show on map")),
141
+ before.length > 8 ? h("details", {}, h("summary", {}, "In reading order"), list) : list,
142
+ ];
143
+ }
144
+
145
+ // Opening a note is recorded once per version per half hour, when the record is on.
146
+ function noteSeen(c) {
147
+ if (!learner.enabled) return;
148
+ const recent = learner.events.findLast((e) => e.event === "seen" && e.concept === c.id);
149
+ if (recent && recent.hash === c.hash && Date.now() - Date.parse(recent.at) < 30 * 60e3) return;
150
+ record({ event: "seen", concept: c.id });
151
+ }
152
+
153
+ function linkList(concepts) {
154
+ return h("ul", { class: "linklist" }, concepts.map((c) => h("li", {}, h("a", { href: conceptHref(c.id) }, c.title))));
155
+ }
156
+
157
+ // ------------------------------------------------------------------ pages
158
+
159
+ function layout(current, main, meta) {
160
+ const aside = h("aside", { class: "tree", "aria-label": "Contents" });
161
+ renderTree(aside, current);
162
+ const wrap = h("div", { class: `kn${meta ? "" : " no-meta"}` }, aside, main, meta || "");
163
+ wireAnchors(wrap);
164
+ return wrap;
165
+ }
166
+
167
+ export async function conceptView(id) {
168
+ const c = store.concepts.get(id);
169
+ if (!c) return missing(id);
170
+ const text = await body(id);
171
+ const article = h("article", { class: "doc" },
172
+ h("header", { class: "doc-head" },
173
+ h("p", { class: "doc-kind" },
174
+ h("span", {}, c.type || "Concept"),
175
+ c.status !== "stable" ? h("span", { class: "chip" }, c.status) : "",
176
+ c.generated_at ? h("span", {}, "Updated ", timeEl(c.generated_at)) : ""),
177
+ h("h1", {}, c.title),
178
+ c.description ? h("p", { class: "description" }, c.description) : ""),
179
+ h("div", { class: "prose", html: render(text, { dir: c.directory }) }));
180
+ document.title = `${c.title} · ${store.site.title}`;
181
+ noteSeen(c);
182
+ return layout("k:" + id, article, metaPanel(c));
183
+ }
184
+
185
+ export async function dirView(id) {
186
+ const d = store.tree[id];
187
+ if (!d) return missing(id + "/");
188
+ const parts = [];
189
+ const overview = d.overview ? store.concepts.get(d.overview) : null;
190
+ if (overview) {
191
+ const text = await body(overview.id);
192
+ parts.push(h("header", { class: "doc-head" },
193
+ h("p", { class: "doc-kind" }, h("a", { href: conceptHref(overview.id) }, "Overview"), overview.generated_at ? h("span", {}, "Updated ", timeEl(overview.generated_at)) : ""),
194
+ h("h1", {}, id ? dirLabel(id) : overview.title),
195
+ overview.description ? h("p", { class: "description" }, overview.description) : ""));
196
+ parts.push(h("div", { class: "prose" , html: render(text, { dir: overview.directory }) }));
197
+ parts.push(h("hr"));
198
+ } else {
199
+ parts.push(h("header", { class: "doc-head" }, h("p", { class: "doc-kind" }, "Directory"), h("h1", {}, dirLabel(id))));
200
+ }
201
+ // The generated OKF index, minus its frontmatter.
202
+ const index = d.index.replace(/^---\n[\s\S]*?\n---\n/, "");
203
+ parts.push(h("div", { class: "prose index", html: render(index, { dir: id }) }));
204
+ document.title = `${dirLabel(id)} · ${store.site.title}`;
205
+ return layout(id, h("article", { class: "doc" }, parts), null);
206
+ }
207
+
208
+ function missing(what) {
209
+ return layout(null, h("article", { class: "doc" },
210
+ h("header", { class: "doc-head" }, h("p", { class: "doc-kind" }, "Not found"), h("h1", {}, what)),
211
+ h("div", { class: "prose" }, h("p", {}, "Nothing is written here yet. The link may point at knowledge that has not been recorded, or the file was moved."))), null);
212
+ }
@@ -0,0 +1,15 @@
1
+ // Works out map layouts off the main thread (see layout.js).
2
+
3
+ import { layoutPositions } from "./layout.js";
4
+
5
+ const ready = import("../vendor/d3.min.js"); // sets the global d3 that layout.js uses
6
+
7
+ self.onmessage = async ({ data }) => {
8
+ try {
9
+ await ready;
10
+ const xyr = layoutPositions(data.model, data.o);
11
+ self.postMessage({ id: data.id, xyr }, [xyr.buffer]);
12
+ } catch (err) {
13
+ self.postMessage({ id: data.id, error: String(err?.message || err) });
14
+ }
15
+ };
@@ -0,0 +1,226 @@
1
+ // Map layout: where each folder and note sits, in layout units. Runs in a Web
2
+ // Worker (layout-worker.js) so the page stays responsive; the result is cached
3
+ // in this browser by what the map contains and the layout settings, so a
4
+ // reload places everything at once. Uses the global d3.
5
+
6
+ /* global d3 */
7
+
8
+ export const SIZE = 1000; // layout units
9
+ // Bump when the algorithm changes, so cached layouts are not reused.
10
+ const VERSION = 2;
11
+ // The settings that change positions (the rest only change drawing).
12
+ export const LAYOUT_KEYS = ["room", "spread", "outward", "spacing", "margin"];
13
+ const CACHE = "rdstudio.layout";
14
+
15
+ // What the layout needs from the map model, as plain data a worker can receive.
16
+ export function plainModel(model) {
17
+ const strip = (n) => (n.kind === "dir"
18
+ ? { kind: "dir", id: n.id, ref: n.ref, children: n.children.map(strip) }
19
+ : { kind: n.kind, id: n.id, ref: n.ref, weight: n.weight });
20
+ return { root: strip(model.root), edges: model.edges };
21
+ }
22
+
23
+ function hierarchy(root) {
24
+ return d3.hierarchy(root, (d) => (d.kind === "dir" ? d.children : null))
25
+ .sum((d) => (d.kind === "concept" ? d.weight : d.children.length ? 0 : 1));
26
+ }
27
+
28
+ // The quick starting arrangement (circle packing), with an index and colour
29
+ // groups: a usable map while the full layout is worked out.
30
+ export function start(modelRoot) {
31
+ const root = hierarchy(modelRoot);
32
+ d3.pack().size([SIZE, SIZE]).padding((d) => (d.depth === 0 ? 10 : 6))(root);
33
+ const byId = new Map();
34
+ root.each((n) => byId.set(n.data.id, n));
35
+ const groups = [...new Set((root.children || []).map((c) => c.data.ref))].sort();
36
+ root.each((n) => {
37
+ const top = n.ancestors().reverse()[1];
38
+ n.group = top ? (top.data.kind === "dir" ? groups.indexOf(top.data.ref) : -1) : -1;
39
+ });
40
+ return { root, byId };
41
+ }
42
+
43
+ // Positions as [x, y, r] per node, in the order root.each() visits them.
44
+ export function positions(root) {
45
+ const out = [];
46
+ root.each((n) => out.push(n.x, n.y, n.r));
47
+ return Float64Array.from(out);
48
+ }
49
+
50
+ export function applyPositions(root, xyr) {
51
+ let i = 0;
52
+ root.each((n) => { n.x = xyr[i++]; n.y = xyr[i++]; n.r = xyr[i++]; });
53
+ }
54
+
55
+ // The full layout, from a plain model. Returns positions.
56
+ export function layoutPositions(model, o) {
57
+ const { root, byId } = start(model.root);
58
+ arrange(root, byId, model.edges, o);
59
+ return positions(root);
60
+ }
61
+
62
+ // Top down: give each folder's contents room, then spread them out evenly
63
+ // inside its wall, keeping linked items near each other and drawing items
64
+ // towards the side where their links leave the folder.
65
+ function arrange(root, byId, edges, o) {
66
+ const leafEdges = edges.map(([a, b]) => [byId.get("c:" + a), byId.get("c:" + b)]).filter(([a, b]) => a && b);
67
+ // A link matters only to the folders that contain one of its ends, so each
68
+ // folder looks at those links alone (in their original order).
69
+ const linksIn = new Map();
70
+ for (const e of leafEdges) {
71
+ const folders = new Set([...e[0].ancestors().slice(1), ...e[1].ancestors().slice(1)]);
72
+ for (const f of folders) {
73
+ if (!linksIn.has(f)) linksIn.set(f, []);
74
+ linksIn.get(f).push(e);
75
+ }
76
+ }
77
+ const moveTree = (n, dx, dy) => n.each((d) => { d.x += dx; d.y += dy; });
78
+ const scaleTree = (n, k) => n.each((d) => { d.x = n.x + (d.x - n.x) * k; d.y = n.y + (d.y - n.y) * k; d.r *= k; });
79
+ const childOf = (folder, n) => n.ancestors().find((a) => a.parent === folder);
80
+
81
+ function place(folder) {
82
+ const kids = folder.children;
83
+ if (!kids) return;
84
+ const unit = folder.r / (SIZE / 2); // settings are given for the whole map; scale them to this folder
85
+ const inner = folder.depth ? folder.r - Math.min(o.margin * unit, folder.r * 0.25) : folder.r;
86
+ const gap = o.spacing * unit;
87
+ const area = kids.reduce((s, c) => s + c.r * c.r, 0) || 1;
88
+ const k = Math.min(1, Math.sqrt((o.room * inner * inner) / area));
89
+ for (const c of kids) {
90
+ scaleTree(c, k);
91
+ moveTree(c, folder.x + (c.x - folder.x) * (inner / folder.r) - c.x, folder.y + (c.y - folder.y) * (inner / folder.r) - c.y);
92
+ }
93
+ if (kids.length > 1) {
94
+ const nodes = kids.map((c) => ({ c, x: c.x - folder.x, y: c.y - folder.y, r: c.r, pull: [0, 0, 0] }));
95
+ const index = new Map(kids.map((c, i) => [c, i]));
96
+ const links = [];
97
+ for (const [u, v] of linksIn.get(folder) || []) {
98
+ const cu = childOf(folder, u), cv = childOf(folder, v);
99
+ if (cu && cv && cu !== cv) links.push({ source: index.get(cu), target: index.get(cv) });
100
+ // A link leaving the folder pulls its end towards that side.
101
+ for (const [inside, outside, other] of [[cu, cv, v], [cv, cu, u]]) {
102
+ if (!inside || outside) continue;
103
+ const dx = other.x - folder.x, dy = other.y - folder.y, d = Math.hypot(dx, dy) || 1;
104
+ const pull = nodes[index.get(inside)].pull;
105
+ pull[0] += dx / d; pull[1] += dy / d; pull[2] += 1;
106
+ }
107
+ }
108
+ for (const nd of nodes) {
109
+ if (nd.pull[2]) {
110
+ const d = Math.hypot(nd.pull[0], nd.pull[1]) || 1;
111
+ nd.tx = (nd.pull[0] / d) * inner * 0.7;
112
+ nd.ty = (nd.pull[1] / d) * inner * 0.7;
113
+ }
114
+ }
115
+ const sim = d3.forceSimulation(nodes).stop()
116
+ .force("collide", d3.forceCollide((d) => d.r + gap / 2).strength(1).iterations(2))
117
+ .force("charge", d3.forceManyBody().strength(-o.spread * inner * 0.15))
118
+ .force("link", d3.forceLink(links).distance((l) => l.source.r + l.target.r + gap * 1.5).strength(0.04))
119
+ .force("x", d3.forceX((d) => d.tx ?? 0).strength((d) => (d.tx !== undefined ? 0.04 * o.outward : 0.03)))
120
+ .force("y", d3.forceY((d) => d.ty ?? 0).strength((d) => (d.ty !== undefined ? 0.04 * o.outward : 0.03)));
121
+ const contain = () => {
122
+ for (const nd of nodes) { // stay inside the wall
123
+ const d = Math.hypot(nd.x, nd.y), max = Math.max(0, inner - nd.r - gap / 4);
124
+ if (d > max) { nd.x *= max / d; nd.y *= max / d; }
125
+ }
126
+ };
127
+ for (let i = 0; i < 240; i++) { sim.tick(); contain(); }
128
+ // Finish by separating anything still overlapping, without other forces.
129
+ for (let round = 0; round < 80; round++) {
130
+ let moved = false;
131
+ for (let i = 0; i < nodes.length; i++) {
132
+ for (let j = i + 1; j < nodes.length; j++) {
133
+ const a = nodes[i], b = nodes[j];
134
+ const dx = b.x - a.x, dy = b.y - a.y, d = Math.hypot(dx, dy) || 1e-6, want = a.r + b.r + gap;
135
+ if (d < want) {
136
+ const push = (want - d) / 2;
137
+ a.x -= (dx / d) * push; a.y -= (dy / d) * push;
138
+ b.x += (dx / d) * push; b.y += (dy / d) * push;
139
+ moved = true;
140
+ }
141
+ }
142
+ }
143
+ contain();
144
+ if (!moved) break;
145
+ }
146
+ for (const nd of nodes) moveTree(nd.c, folder.x + nd.x - nd.c.x, folder.y + nd.y - nd.c.y);
147
+ }
148
+ kids.forEach(place);
149
+ }
150
+ place(root);
151
+ }
152
+
153
+ // ------------------------------------------------------------ cache
154
+
155
+ // cyrb53: a fast 53-bit string hash, plenty to tell layouts apart.
156
+ function hash(str) {
157
+ let h1 = 0xdeadbeef, h2 = 0x41c6ce57;
158
+ for (let i = 0; i < str.length; i++) {
159
+ const ch = str.charCodeAt(i);
160
+ h1 = Math.imul(h1 ^ ch, 2654435761);
161
+ h2 = Math.imul(h2 ^ ch, 1597334677);
162
+ }
163
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
164
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
165
+ return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
166
+ }
167
+
168
+ export function layoutKey(plain, o) {
169
+ return hash(JSON.stringify([VERSION, LAYOUT_KEYS.map((k) => o[k]), plain]));
170
+ }
171
+
172
+ export function cached(key, count) {
173
+ try {
174
+ const hit = JSON.parse(localStorage.getItem(CACHE) || "null");
175
+ return hit?.key === key && hit.xyr.length === count * 3 ? Float64Array.from(hit.xyr) : null;
176
+ } catch {
177
+ return null;
178
+ }
179
+ }
180
+
181
+ export function remember(key, xyr) {
182
+ try {
183
+ localStorage.setItem(CACHE, JSON.stringify({ key, xyr: Array.from(xyr, (v) => Math.round(v * 1000) / 1000) }));
184
+ } catch { /* storage full or unavailable: lay out again next time */ }
185
+ }
186
+
187
+ // ------------------------------------------------------------ worker
188
+
189
+ let worker = null, workerFailed = false, nextId = 0;
190
+ const waiting = new Map();
191
+
192
+ function getWorker() {
193
+ if (worker || workerFailed) return worker;
194
+ try {
195
+ worker = new Worker(new URL("./layout-worker.js", import.meta.url), { type: "module" });
196
+ worker.onmessage = ({ data }) => {
197
+ const w = waiting.get(data.id);
198
+ waiting.delete(data.id);
199
+ if (!w) return;
200
+ if (data.error) w.reject(new Error(data.error));
201
+ else w.resolve(data.xyr);
202
+ };
203
+ worker.onerror = (event) => {
204
+ event.preventDefault?.();
205
+ workerFailed = true;
206
+ worker = null;
207
+ for (const w of waiting.values()) w.reject(new Error("layout worker failed"));
208
+ waiting.clear();
209
+ };
210
+ } catch {
211
+ workerFailed = true;
212
+ }
213
+ return worker;
214
+ }
215
+
216
+ // The full layout's positions, worked out off the main thread where possible.
217
+ export function computeLayout(plain, o) {
218
+ const settings = Object.fromEntries(LAYOUT_KEYS.map((k) => [k, o[k]]));
219
+ const w = getWorker();
220
+ if (!w) return Promise.resolve(layoutPositions(plain, settings));
221
+ const id = ++nextId;
222
+ return new Promise((resolve, reject) => {
223
+ waiting.set(id, { resolve, reject });
224
+ w.postMessage({ id, model: plain, o: settings });
225
+ }).catch(() => layoutPositions(plain, settings)); // no worker after all: do it here
226
+ }
@@ -0,0 +1,83 @@
1
+ // Learn tab: reading order from `requires` links, and the private learner record.
2
+
3
+ import { store, learner } from "./data.js";
4
+ import { h, conceptHref, titleCase } from "./util.js";
5
+
6
+ export function hasRequires() {
7
+ for (const c of store.concepts.values()) if (c.requires?.length) return true;
8
+ return false;
9
+ }
10
+
11
+ // Everything `id` requires, directly or through a chain, in reading order.
12
+ export function prerequisites(id) {
13
+ const seen = new Set();
14
+ const todo = [...(store.concepts.get(id)?.requires || [])];
15
+ while (todo.length) {
16
+ const v = todo.pop();
17
+ if (seen.has(v) || !store.concepts.has(v)) continue;
18
+ seen.add(v);
19
+ todo.push(...store.concepts.get(v).requires);
20
+ }
21
+ seen.delete(id);
22
+ return [...seen].map((x) => store.concepts.get(x)).sort((a, b) => a.order - b.order);
23
+ }
24
+
25
+ function folderLabel(dir) {
26
+ return dir ? dir.split("/").map((p) => titleCase(p.replace(/[-_]/g, " "))).join(" / ") : "Top level";
27
+ }
28
+
29
+ export function depthLabel(c) {
30
+ return h("span", { class: "depth", title: "The longest chain of prerequisites below this note" },
31
+ c.depth ? `level ${c.depth}` : "start");
32
+ }
33
+
34
+ function readingOrder() {
35
+ const notes = [...store.concepts.values()].filter((c) => c.type !== "Tour").sort((a, b) => a.order - b.order);
36
+ const list = h("ol", { class: "reading" });
37
+ let dir = null;
38
+ for (const c of notes) {
39
+ if (c.directory !== dir) {
40
+ dir = c.directory;
41
+ list.append(h("li", { class: "folder", "aria-hidden": "true" }, folderLabel(dir)));
42
+ }
43
+ list.append(h("li", { class: "step", value: c.order + 1 },
44
+ h("a", { href: conceptHref(c.id), title: c.description || c.title }, c.title),
45
+ depthLabel(c),
46
+ c.depth ? h("a", { class: "path-link", href: "#/path/" + encodeURIComponent(c.id), title: `Study path to ${c.title} on the map` }, "path") : ""));
47
+ }
48
+ return list;
49
+ }
50
+
51
+ function recordSection() {
52
+ if (store.site.static) {
53
+ return h("p", { class: "section-note" }, "This is an exported snapshot, so nothing you do here is recorded.");
54
+ }
55
+ if (!learner.enabled) {
56
+ return [
57
+ h("p", { class: "section-note" },
58
+ "Off. When it is on, rdstudio keeps a private record of what you study in this project, outside the repository, for the exercises and review to come. To turn it on, add this to ~/.config/rdstudio/config.toml and restart rdstudio serve:"),
59
+ h("pre", {}, "[learner]\nenabled = true"),
60
+ ];
61
+ }
62
+ return h("p", { class: "section-note" },
63
+ `On. ${learner.events.length} ${learner.events.length === 1 ? "event" : "events"}, stored privately in `,
64
+ h("code", {}, learner.dir), ". Only you see it; it is never part of the project or an export.");
65
+ }
66
+
67
+ export function learnView() {
68
+ document.title = `Learn · ${store.site.title}`;
69
+ const page = h("div", { class: "page" }, h("h1", {}, "Learn"));
70
+ page.append(h("p", { class: "lede" },
71
+ "A reading order for this knowledge base, from the links rated requires: each note comes after everything it needs."));
72
+ page.append(h("h2", { class: "section-h" }, "Reading order"));
73
+ if (hasRequires()) {
74
+ page.append(h("p", { class: "section-note" },
75
+ "Notes stay with their folder where they can. The level is the longest chain of prerequisites below a note; path shows that chain on the map."),
76
+ readingOrder());
77
+ } else {
78
+ page.append(h("p", { class: "empty" },
79
+ "No links are rated requires yet, so there is no order to give. Rate a link by giving it the title \"requires\", as in [Topology](/topology.md \"requires\")."));
80
+ }
81
+ page.append(h("h2", { class: "section-h" }, "Your learner record"), recordSection());
82
+ return page;
83
+ }