ronin-skillforce 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +225 -2
  4. package/bin/cli.js +811 -0
  5. package/bin/commands/check.js +298 -0
  6. package/bin/commands/create.js +169 -0
  7. package/bin/commands/doctor.js +392 -0
  8. package/bin/commands/import.js +557 -0
  9. package/bin/commands/next.js +125 -0
  10. package/bin/commands/review.js +207 -0
  11. package/bin/commands/status.js +114 -0
  12. package/bin/commands/studio.js +497 -0
  13. package/bin/commands/trail.js +211 -0
  14. package/bin/commands/web.js +350 -0
  15. package/bin/course-reader.js +611 -0
  16. package/bin/node-version.js +60 -0
  17. package/bin/platforms/_shared.js +211 -0
  18. package/bin/platforms/claude.js +104 -0
  19. package/bin/platforms/cursor.js +49 -0
  20. package/bin/platforms/gemini.js +65 -0
  21. package/bin/platforms/opencode.js +95 -0
  22. package/bin/registry-helpers.js +504 -0
  23. package/bin/static-server.js +153 -0
  24. package/bin/studio-store.js +94 -0
  25. package/bin/sync-shared.sh +5 -0
  26. package/package.json +91 -4
  27. package/plugin.json +139 -0
  28. package/shared/SKILL.md +568 -0
  29. package/shared/agents/coach.md +225 -0
  30. package/shared/agents/navigator.md +215 -0
  31. package/shared/agents/professor.md +372 -0
  32. package/shared/agents/researcher.md +181 -0
  33. package/shared/agents/spotter.md +129 -0
  34. package/shared/commands/professor/archive.md +103 -0
  35. package/shared/commands/professor/capstone-review.md +79 -0
  36. package/shared/commands/professor/capstone.md +34 -0
  37. package/shared/commands/professor/discuss.md +18 -0
  38. package/shared/commands/professor/done.md +44 -0
  39. package/shared/commands/professor/export.md +69 -0
  40. package/shared/commands/professor/hint.md +108 -0
  41. package/shared/commands/professor/navigator.md +67 -0
  42. package/shared/commands/professor/new-topic.md +73 -0
  43. package/shared/commands/professor/next.md +174 -0
  44. package/shared/commands/professor/note.md +63 -0
  45. package/shared/commands/professor/onboard.md +95 -0
  46. package/shared/commands/professor/progress.md +46 -0
  47. package/shared/commands/professor/publish.md +93 -0
  48. package/shared/commands/professor/quiz.md +27 -0
  49. package/shared/commands/professor/recall.md +74 -0
  50. package/shared/commands/professor/review.md +43 -0
  51. package/shared/commands/professor/schedule.md +75 -0
  52. package/shared/commands/professor/skill-export.md +87 -0
  53. package/shared/commands/professor/spotter.md +76 -0
  54. package/shared/commands/professor/stuck.md +27 -0
  55. package/shared/commands/professor/switch.md +50 -0
  56. package/shared/commands/professor/syllabus.md +9 -0
  57. package/shared/commands/professor/template-export.md +91 -0
  58. package/shared/commands/professor/template-import.md +111 -0
  59. package/shared/commands/professor/worktrees.md +58 -0
  60. package/shared/hooks/pre-compact.js +232 -0
  61. package/studio/README.md +313 -0
  62. package/studio/dist/assets/BetaReportPanel-8NrDLiPm.js +1 -0
  63. package/studio/dist/assets/Capstone-Cv9kH6LY.js +6 -0
  64. package/studio/dist/assets/CodeWorkspace-BwCEINWo.js +2 -0
  65. package/studio/dist/assets/Course-BJ_hafUP.js +1 -0
  66. package/studio/dist/assets/Editor-CV0mNhlz.js +53 -0
  67. package/studio/dist/assets/Home-C1ry89ak.js +1 -0
  68. package/studio/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  69. package/studio/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  70. package/studio/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  71. package/studio/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  72. package/studio/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  73. package/studio/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  74. package/studio/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  75. package/studio/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  76. package/studio/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  77. package/studio/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  78. package/studio/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  79. package/studio/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  80. package/studio/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  81. package/studio/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  82. package/studio/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  83. package/studio/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  84. package/studio/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  85. package/studio/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  86. package/studio/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  87. package/studio/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  88. package/studio/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  89. package/studio/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  90. package/studio/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  91. package/studio/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  92. package/studio/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  93. package/studio/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  94. package/studio/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  95. package/studio/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  96. package/studio/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  97. package/studio/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  98. package/studio/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  99. package/studio/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  100. package/studio/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  101. package/studio/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  102. package/studio/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  103. package/studio/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  104. package/studio/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  105. package/studio/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  106. package/studio/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  107. package/studio/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  108. package/studio/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  109. package/studio/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  110. package/studio/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  111. package/studio/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  112. package/studio/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  113. package/studio/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  114. package/studio/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  115. package/studio/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  116. package/studio/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  117. package/studio/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  118. package/studio/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  119. package/studio/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  120. package/studio/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  121. package/studio/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  122. package/studio/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  123. package/studio/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  124. package/studio/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  125. package/studio/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  126. package/studio/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  127. package/studio/dist/assets/Lesson-2e9TD9yL.js +2 -0
  128. package/studio/dist/assets/NewCourse-BZeOg2xn.js +952 -0
  129. package/studio/dist/assets/Progress-CRc_xEWC.js +1 -0
  130. package/studio/dist/assets/Review-6QEbHEfk.js +1 -0
  131. package/studio/dist/assets/Settings-NFnqVQui.js +1 -0
  132. package/studio/dist/assets/TutorPanel-DXQ6Z3BY.js +123 -0
  133. package/studio/dist/assets/abnfDiagram-WEYOIA3C-qeSjKlGM.js +1 -0
  134. package/studio/dist/assets/anthropic-D7NiQCUH.js +15 -0
  135. package/studio/dist/assets/arc-Y1ARJVZ4.js +1 -0
  136. package/studio/dist/assets/architecture-WOLXFQ4H-COG-m35l.js +1 -0
  137. package/studio/dist/assets/architectureDiagram-ZHVYKT53-VO7uKoeD.js +36 -0
  138. package/studio/dist/assets/array-BifhSqXX.js +1 -0
  139. package/studio/dist/assets/blockDiagram-RJAE7QA3-BGJF_qiL.js +140 -0
  140. package/studio/dist/assets/bridge-BTh0AeNX.js +1 -0
  141. package/studio/dist/assets/c4Diagram-XDPPPE54-C-S10RgU.js +38 -0
  142. package/studio/dist/assets/capstone-Amn6Hztq.js +135 -0
  143. package/studio/dist/assets/card-store-BrbrtX9w.js +1 -0
  144. package/studio/dist/assets/channel-SJiUQu_N.js +1 -0
  145. package/studio/dist/assets/chunk-2E7WHVMD-fv-iqTyC.js +88 -0
  146. package/studio/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  147. package/studio/dist/assets/chunk-4EMNKR2T-CY9IZrgv.js +1 -0
  148. package/studio/dist/assets/chunk-55XCU76K-DyTZz7v_.js +1 -0
  149. package/studio/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  150. package/studio/dist/assets/chunk-7S6XSTFO-BNMzTLRW.js +2 -0
  151. package/studio/dist/assets/chunk-AQDTSKZB-CEYANohH.js +72 -0
  152. package/studio/dist/assets/chunk-DSNLSHJZ-CDk5EXjH.js +129 -0
  153. package/studio/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
  154. package/studio/dist/assets/chunk-HAGFOTED-hNAi1zh_.js +62 -0
  155. package/studio/dist/assets/chunk-HNE4Q36P-COJnSYoZ.js +1 -0
  156. package/studio/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
  157. package/studio/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  158. package/studio/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
  159. package/studio/dist/assets/chunk-NQOKRVIF-BU6KVxRr.js +213 -0
  160. package/studio/dist/assets/chunk-UHW7PFBP-DYvxL9JG.js +1 -0
  161. package/studio/dist/assets/chunk-VQ5W62QI-CuRfk65j.js +1 -0
  162. package/studio/dist/assets/chunk-WQQL4CDK-KaG7x-Mb.js +10 -0
  163. package/studio/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
  164. package/studio/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  165. package/studio/dist/assets/chunk-YZO67HFG-DbuqIZmk.js +1 -0
  166. package/studio/dist/assets/chunk-ZAUX2LTX-bk4ivUmc.js +1 -0
  167. package/studio/dist/assets/chunk-ZCHMQSOR-B_LElfwh.js +2 -0
  168. package/studio/dist/assets/classDiagram-v2-4MYWR3UA-DtCJ6H_X.js +217 -0
  169. package/studio/dist/assets/cose-bilkent-JH36ORCC-DMBAZFnj.js +1 -0
  170. package/studio/dist/assets/course-export-DpaOTT3a.js +1 -0
  171. package/studio/dist/assets/cynefin-EF2NZ3EQ-D9btHJvU.js +1 -0
  172. package/studio/dist/assets/cynefinDiagram-WF24UDRP-CsFJuv27.js +62 -0
  173. package/studio/dist/assets/cytoscape.esm-CHBj4VkZ.js +321 -0
  174. package/studio/dist/assets/dagre-NK4GIZVX-Cg1Duec7.js +4 -0
  175. package/studio/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  176. package/studio/dist/assets/diagram-CBK3MWTC-_KU2m5gd.js +24 -0
  177. package/studio/dist/assets/diagram-I5XIIV2D-5RaBrwUA.js +24 -0
  178. package/studio/dist/assets/diagram-ODIL2UCW-C0VKxoD3.js +30 -0
  179. package/studio/dist/assets/diagram-PFBEJUXE-BaHcZEP2.js +3 -0
  180. package/studio/dist/assets/diagram-RJA3I4VV-cwSAZ90p.js +41 -0
  181. package/studio/dist/assets/diagram-TYUNFSNR-BJImxCEN.js +200 -0
  182. package/studio/dist/assets/dist-CumnGVmJ.js +1 -0
  183. package/studio/dist/assets/ebnfDiagram-V4P3TTPT-OQQLRXcm.js +1 -0
  184. package/studio/dist/assets/elk-QONTBCTQ-BJGhQHxQ.js +27 -0
  185. package/studio/dist/assets/erDiagram-KYQIGQAA-D91EKSep.js +99 -0
  186. package/studio/dist/assets/error-BTdmRKoB.js +1 -0
  187. package/studio/dist/assets/errors-BZa_CNHL.js +1 -0
  188. package/studio/dist/assets/eventmodeling-K75KTNOO-DgdTRgnw.js +1 -0
  189. package/studio/dist/assets/fetch-local-DG3O3hmQ.js +4 -0
  190. package/studio/dist/assets/flowDiagram-IUGLLWBU-CVnJYMum.js +1 -0
  191. package/studio/dist/assets/ganttDiagram-R7GPNTJY-CSYLPWEI.js +292 -0
  192. package/studio/dist/assets/gitGraph-VSP46ZUC-D6GWaV9y.js +1 -0
  193. package/studio/dist/assets/gitGraphDiagram-PDYL7XHY-CaV31bvT.js +106 -0
  194. package/studio/dist/assets/graphlib-DS17s2tU.js +1 -0
  195. package/studio/dist/assets/index-CVenTTOY.css +2 -0
  196. package/studio/dist/assets/index-DioXTZVc.js +313 -0
  197. package/studio/dist/assets/info-OHQRW6UA-BN-x8GoN.js +1 -0
  198. package/studio/dist/assets/infoDiagram-MVDRKAX5-CIose_cy.js +2 -0
  199. package/studio/dist/assets/init-C-OQMol4.js +1 -0
  200. package/studio/dist/assets/ishikawaDiagram-HPEHV2GE-DCihSNBw.js +70 -0
  201. package/studio/dist/assets/journeyDiagram-BOSHW7O7-Bz3o3RIX.js +139 -0
  202. package/studio/dist/assets/js.worker-AD7DjksO.js +4 -0
  203. package/studio/dist/assets/kanban-definition-FNSA4HC5-BUyqvlFE.js +89 -0
  204. package/studio/dist/assets/katex-B_JcIwqj.js +257 -0
  205. package/studio/dist/assets/katex-BgsA2lO1.css +1 -0
  206. package/studio/dist/assets/line-B08T8KlK.js +1 -0
  207. package/studio/dist/assets/linear-C9iwaMOi.js +1 -0
  208. package/studio/dist/assets/mermaid-parser.core-BYwreSUy.js +7 -0
  209. package/studio/dist/assets/mermaid.core-DTO1oDmq.js +44 -0
  210. package/studio/dist/assets/mindmap-definition-6OY57UG3-BGLwKheY.js +98 -0
  211. package/studio/dist/assets/node.browser-BISniYn8.js +1 -0
  212. package/studio/dist/assets/node.browser-HQ5SKBOf.js +1 -0
  213. package/studio/dist/assets/ollama-De5w84tG.js +1 -0
  214. package/studio/dist/assets/openai-compatible-Joy1oK3L.js +1 -0
  215. package/studio/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  216. package/studio/dist/assets/packet-JDAUHWVQ-CDIMjHS_.js +1 -0
  217. package/studio/dist/assets/path-fybaL0A-.js +1 -0
  218. package/studio/dist/assets/pegDiagram-ECIUJNBX-DWXm4mwS.js +1 -0
  219. package/studio/dist/assets/pie-XZMESJXO-CY3QQiKZ.js +1 -0
  220. package/studio/dist/assets/pieDiagram-GNMPY74T-M1aCnI7W.js +39 -0
  221. package/studio/dist/assets/python.worker-CzTrFdd9.js +24 -0
  222. package/studio/dist/assets/quadrantDiagram-D57K4MC3-BFFtO7U5.js +7 -0
  223. package/studio/dist/assets/radar-ABXABTNO-DT0W4_Xn.js +1 -0
  224. package/studio/dist/assets/railroad-I3PHUGI6-_VCpgZR5.js +1 -0
  225. package/studio/dist/assets/railroad-abnf-LNEFI6M7-DkSpQ5YY.js +1 -0
  226. package/studio/dist/assets/railroad-ebnf-DWYD2UWJ-BqWILdGb.js +1 -0
  227. package/studio/dist/assets/railroad-peg-JR7BVNR7-BB0bQL-Z.js +1 -0
  228. package/studio/dist/assets/railroadDiagram-Y72NRBL7-CjLKjIdJ.js +1 -0
  229. package/studio/dist/assets/react-B8SKf8RM.js +1 -0
  230. package/studio/dist/assets/rehype-katex-C-sKddsH.js +259 -0
  231. package/studio/dist/assets/remark-math-C-ox67hE.js +5 -0
  232. package/studio/dist/assets/requirementDiagram-EQYSW5RA-CnwzZqR8.js +84 -0
  233. package/studio/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  234. package/studio/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  235. package/studio/dist/assets/sankeyDiagram-SY24JHO5-CPDugTUl.js +40 -0
  236. package/studio/dist/assets/schemas-BOc2YsOP.js +55 -0
  237. package/studio/dist/assets/sequenceDiagram-BLEHNHQU-1dLfeU9Y.js +169 -0
  238. package/studio/dist/assets/settings-DhwuyNPU.js +1 -0
  239. package/studio/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  240. package/studio/dist/assets/spec-nAnsSMkw.js +1 -0
  241. package/studio/dist/assets/sql-wasm-C1D_RJuy.wasm +0 -0
  242. package/studio/dist/assets/sql.worker-D7pBHOrq.js +4 -0
  243. package/studio/dist/assets/src-BWF7jMDv.js +1 -0
  244. package/studio/dist/assets/stateDiagram-v2-A6IESKWZ-DR7CmEjj.js +291 -0
  245. package/studio/dist/assets/swimlanes-B3ENRKFQ-BAWB9omQ.js +1 -0
  246. package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-qeuHru16.js +8 -0
  247. package/studio/dist/assets/timeline-definition-JXYKGLGB-DNkj8z77.js +120 -0
  248. package/studio/dist/assets/treeView-D4JQ5SDB-BA-ouhMb.js +1 -0
  249. package/studio/dist/assets/treemap-SAJKECNS-BrrGxsAb.js +1 -0
  250. package/studio/dist/assets/useStream-rdsR6A7P.js +1 -0
  251. package/studio/dist/assets/usecaseDiagram-WPOLBNNX-BB3D0Dt_.js +328 -0
  252. package/studio/dist/assets/vennDiagram-Q3Y3IDCW-BrtqtKTE.js +34 -0
  253. package/studio/dist/assets/wardley-7MLQ67FV-BKkemDql.js +1 -0
  254. package/studio/dist/assets/wardleyDiagram-IVAIDKWA-DFnI3P5z.js +78 -0
  255. package/studio/dist/assets/xychartDiagram-4URNHDU4-Da0DZG_q.js +7 -0
  256. package/studio/dist/index.html +20 -0
  257. package/studio/dist/preview.html +22 -0
  258. package/studio/migrations/0001_init.sql +188 -0
  259. package/studio/migrations/0002_external_ids.sql +20 -0
  260. package/studio/migrations/0003_course_language.sql +12 -0
  261. package/studio/migrations/0004_capstone.sql +51 -0
  262. package/studio/migrations/0005_beta_loop.sql +73 -0
  263. package/studio/migrations/0006_interactive_exercises.sql +49 -0
  264. package/studio/src/lib/activity.js +92 -0
  265. package/studio/src/lib/ai/anthropic.js +109 -0
  266. package/studio/src/lib/ai/bridge.js +126 -0
  267. package/studio/src/lib/ai/catalog.js +160 -0
  268. package/studio/src/lib/ai/config.js +70 -0
  269. package/studio/src/lib/ai/errors.js +95 -0
  270. package/studio/src/lib/ai/fetch-local.js +104 -0
  271. package/studio/src/lib/ai/index.js +194 -0
  272. package/studio/src/lib/ai/ollama.js +136 -0
  273. package/studio/src/lib/ai/openai-compatible.js +101 -0
  274. package/studio/src/lib/ai/provider-utils.js +107 -0
  275. package/studio/src/lib/ai/sse.js +99 -0
  276. package/studio/src/lib/ai/status.js +92 -0
  277. package/studio/src/lib/ai/useStream.js +109 -0
  278. package/studio/src/lib/beta/metrics.js +147 -0
  279. package/studio/src/lib/beta/report.js +134 -0
  280. package/studio/src/lib/capstone-evidence.js +758 -0
  281. package/studio/src/lib/capstone.js +663 -0
  282. package/studio/src/lib/chat-store.js +128 -0
  283. package/studio/src/lib/course-export.js +38 -0
  284. package/studio/src/lib/course.js +187 -0
  285. package/studio/src/lib/exercises/journal-entry.js +259 -0
  286. package/studio/src/lib/exercises/placement.js +31 -0
  287. package/studio/src/lib/exercises/registry.js +19 -0
  288. package/studio/src/lib/exercises/spec.js +129 -0
  289. package/studio/src/lib/i18n/detect.js +162 -0
  290. package/studio/src/lib/i18n/en/app.json +23 -0
  291. package/studio/src/lib/i18n/en/betaReport.json +55 -0
  292. package/studio/src/lib/i18n/en/capstone.json +57 -0
  293. package/studio/src/lib/i18n/en/common.json +25 -0
  294. package/studio/src/lib/i18n/en/course.json +27 -0
  295. package/studio/src/lib/i18n/en/data.json +35 -0
  296. package/studio/src/lib/i18n/en/durability.json +5 -0
  297. package/studio/src/lib/i18n/en/errors.json +20 -0
  298. package/studio/src/lib/i18n/en/exercises.json +55 -0
  299. package/studio/src/lib/i18n/en/externalEvidence.json +76 -0
  300. package/studio/src/lib/i18n/en/home.json +18 -0
  301. package/studio/src/lib/i18n/en/lesson.json +33 -0
  302. package/studio/src/lib/i18n/en/nav.json +8 -0
  303. package/studio/src/lib/i18n/en/newCourse.json +51 -0
  304. package/studio/src/lib/i18n/en/onboarding.json +35 -0
  305. package/studio/src/lib/i18n/en/playwrightTemplate.json +20 -0
  306. package/studio/src/lib/i18n/en/practice.json +79 -0
  307. package/studio/src/lib/i18n/en/provider.json +27 -0
  308. package/studio/src/lib/i18n/en/providerMeta.json +26 -0
  309. package/studio/src/lib/i18n/en/reflection.json +17 -0
  310. package/studio/src/lib/i18n/en/revision.json +52 -0
  311. package/studio/src/lib/i18n/en/runnerNotice.json +14 -0
  312. package/studio/src/lib/i18n/en/settings.json +12 -0
  313. package/studio/src/lib/i18n/en/storage.json +49 -0
  314. package/studio/src/lib/i18n/en/tutor.json +39 -0
  315. package/studio/src/lib/i18n/en/workspace.json +50 -0
  316. package/studio/src/lib/i18n/index.js +144 -0
  317. package/studio/src/lib/i18n/languages.js +93 -0
  318. package/studio/src/lib/i18n/vi/app.json +23 -0
  319. package/studio/src/lib/i18n/vi/betaReport.json +55 -0
  320. package/studio/src/lib/i18n/vi/capstone.json +57 -0
  321. package/studio/src/lib/i18n/vi/common.json +25 -0
  322. package/studio/src/lib/i18n/vi/course.json +27 -0
  323. package/studio/src/lib/i18n/vi/data.json +35 -0
  324. package/studio/src/lib/i18n/vi/durability.json +5 -0
  325. package/studio/src/lib/i18n/vi/errors.json +20 -0
  326. package/studio/src/lib/i18n/vi/exercises.json +55 -0
  327. package/studio/src/lib/i18n/vi/externalEvidence.json +76 -0
  328. package/studio/src/lib/i18n/vi/home.json +18 -0
  329. package/studio/src/lib/i18n/vi/lesson.json +33 -0
  330. package/studio/src/lib/i18n/vi/nav.json +8 -0
  331. package/studio/src/lib/i18n/vi/newCourse.json +51 -0
  332. package/studio/src/lib/i18n/vi/onboarding.json +35 -0
  333. package/studio/src/lib/i18n/vi/playwrightTemplate.json +20 -0
  334. package/studio/src/lib/i18n/vi/practice.json +79 -0
  335. package/studio/src/lib/i18n/vi/provider.json +27 -0
  336. package/studio/src/lib/i18n/vi/providerMeta.json +26 -0
  337. package/studio/src/lib/i18n/vi/reflection.json +17 -0
  338. package/studio/src/lib/i18n/vi/revision.json +52 -0
  339. package/studio/src/lib/i18n/vi/runnerNotice.json +14 -0
  340. package/studio/src/lib/i18n/vi/settings.json +12 -0
  341. package/studio/src/lib/i18n/vi/storage.json +49 -0
  342. package/studio/src/lib/i18n/vi/tutor.json +39 -0
  343. package/studio/src/lib/i18n/vi/workspace.json +50 -0
  344. package/studio/src/lib/ids.js +10 -0
  345. package/studio/src/lib/json.js +26 -0
  346. package/studio/src/lib/markdown.js +74 -0
  347. package/studio/src/lib/prompts/base.js +139 -0
  348. package/studio/src/lib/prompts/capstone.js +777 -0
  349. package/studio/src/lib/prompts/course.js +150 -0
  350. package/studio/src/lib/prompts/lesson.js +275 -0
  351. package/studio/src/lib/prompts/practice.js +511 -0
  352. package/studio/src/lib/prompts/tutor.js +509 -0
  353. package/studio/src/lib/repo/blocked-adapter.js +69 -0
  354. package/studio/src/lib/repo/events.js +239 -0
  355. package/studio/src/lib/repo/idb-adapter.js +226 -0
  356. package/studio/src/lib/repo/index.js +1315 -0
  357. package/studio/src/lib/repo/memory-adapter.js +115 -0
  358. package/studio/src/lib/repo/remote-adapter.js +158 -0
  359. package/studio/src/lib/repo/schema.js +143 -0
  360. package/studio/src/lib/repo/select-adapter.js +345 -0
  361. package/studio/src/lib/router.js +71 -0
  362. package/studio/src/lib/runner/browser.js +244 -0
  363. package/studio/src/lib/runner/checks.js +248 -0
  364. package/studio/src/lib/runner/current-code.js +98 -0
  365. package/studio/src/lib/runner/esm-cjs.js +109 -0
  366. package/studio/src/lib/runner/hash.js +34 -0
  367. package/studio/src/lib/runner/history.js +48 -0
  368. package/studio/src/lib/runner/index.js +168 -0
  369. package/studio/src/lib/runner/languages.js +258 -0
  370. package/studio/src/lib/runner/local.js +139 -0
  371. package/studio/src/lib/runner/path.js +104 -0
  372. package/studio/src/lib/runner/skeleton.js +315 -0
  373. package/studio/src/lib/runner/strip-types.js +134 -0
  374. package/studio/src/lib/settings.js +399 -0
  375. package/studio/src/lib/srs.js +39 -0
  376. package/templates/agent/settings.json +6 -0
  377. package/templates/claude/plugin.json +139 -0
  378. package/templates/claude/settings.json +15 -0
  379. package/templates/cursor/professor.mdc +4 -0
  380. package/templates/cursor/settings.json +18 -0
  381. package/templates/gemini/GEMINI.md +28 -0
  382. package/templates/gemini/settings.json +5 -0
  383. package/templates/opencode/opencode.json +3 -0
  384. package/templates/researcher/README.md +61 -0
  385. package/templates/researcher/settings.json +18 -0
  386. package/web/client/components.json +15 -0
  387. package/web/client/index.html +12 -0
  388. package/web/client/package-lock.json +8732 -0
  389. package/web/client/package.json +39 -0
  390. package/web/client/postcss.config.js +6 -0
  391. package/web/client/src/App.jsx +140 -0
  392. package/web/client/src/api/client.js +357 -0
  393. package/web/client/src/components/ChatPanel.jsx +417 -0
  394. package/web/client/src/components/CommandPills.jsx +50 -0
  395. package/web/client/src/components/DeprecationBanner.jsx +21 -0
  396. package/web/client/src/components/ExerciseEditor.jsx +164 -0
  397. package/web/client/src/components/Header.jsx +119 -0
  398. package/web/client/src/components/LecturePanel.jsx +342 -0
  399. package/web/client/src/components/MobileTabSwitcher.jsx +51 -0
  400. package/web/client/src/components/SplitPane.jsx +42 -0
  401. package/web/client/src/components/ui/alert.jsx +38 -0
  402. package/web/client/src/components/ui/badge.jsx +24 -0
  403. package/web/client/src/components/ui/button.jsx +23 -0
  404. package/web/client/src/components/ui/select.jsx +74 -0
  405. package/web/client/src/hooks/useSSE.js +152 -0
  406. package/web/client/src/hooks/useWebSocket.js +98 -0
  407. package/web/client/src/index.css +23 -0
  408. package/web/client/src/lib/utils.js +6 -0
  409. package/web/client/src/main.jsx +12 -0
  410. package/web/client/src/utils/markdown.js +50 -0
  411. package/web/client/tailwind.config.js +8 -0
  412. package/web/client/vite.config.js +21 -0
  413. package/web/dist/assets/index-C1W0eXIQ.js +294 -0
  414. package/web/dist/assets/index-C3OUx70T.css +1 -0
  415. package/web/dist/index.html +13 -0
  416. package/web/lecture-archive.js +172 -0
  417. package/web/legacy-server.js +1277 -0
  418. package/web/package-lock.json +855 -0
  419. package/web/package.json +10 -0
  420. package/web/security.js +1016 -0
  421. package/web/server.js +390 -0
  422. package/web/studio-api/agents.js +580 -0
  423. package/web/studio-api/bridge.js +199 -0
  424. package/web/studio-api/db.js +66 -0
  425. package/web/studio-api/guards.js +201 -0
  426. package/web/studio-api/health.js +538 -0
  427. package/web/studio-api.js +214 -0
  428. package/web/studio-db.js +380 -0
  429. package/web/studio-runner.js +568 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Header.jsx - Course selector dropdown + progress indicator
3
+ */
4
+
5
+ import { useState, useEffect } from 'react';
6
+ import { fetchCourses } from '../api/client.js';
7
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select.jsx';
8
+
9
+ /**
10
+ * Header component with course selector and progress indicator
11
+ * @param {Object} props
12
+ * @param {Object|null} props.selectedCourse - Currently selected course
13
+ * @param {Function} props.onCourseSelect - Callback when course is selected
14
+ */
15
+ export default function Header({ selectedCourse, onCourseSelect }) {
16
+ const [courses, setCourses] = useState([]);
17
+ const [loading, setLoading] = useState(true);
18
+ const [error, setError] = useState(null);
19
+
20
+ useEffect(() => {
21
+ loadCourses();
22
+ }, []);
23
+
24
+ async function loadCourses() {
25
+ setLoading(true);
26
+ setError(null);
27
+ try {
28
+ const data = await fetchCourses();
29
+ setCourses(data || []);
30
+ } catch (err) {
31
+ console.error('Failed to load courses:', err);
32
+ setError('Failed to load courses');
33
+ setCourses([]);
34
+ } finally {
35
+ setLoading(false);
36
+ }
37
+ }
38
+
39
+ function handleCourseChange(value) {
40
+ const slug = value === '__none__' ? '' : value;
41
+ const course = slug ? courses.find((c) => c.slug === slug) : null;
42
+ onCourseSelect?.(course);
43
+ }
44
+
45
+ // Format last active timestamp
46
+ function formatLastActive(timestamp) {
47
+ if (!timestamp || timestamp === 'Unknown') return '';
48
+
49
+ try {
50
+ const date = new Date(timestamp);
51
+ if (isNaN(date.getTime())) return timestamp;
52
+
53
+ const now = new Date();
54
+ const diffMs = now - date;
55
+ const diffMins = Math.floor(diffMs / 60000);
56
+ const diffHours = Math.floor(diffMs / 3600000);
57
+ const diffDays = Math.floor(diffMs / 86400000);
58
+
59
+ if (diffMins < 1) return 'Just now';
60
+ if (diffMins < 60) return `${diffMins}m ago`;
61
+ if (diffHours < 24) return `${diffHours}h ago`;
62
+ if (diffDays < 7) return `${diffDays}d ago`;
63
+
64
+ return date.toLocaleDateString();
65
+ } catch {
66
+ return timestamp;
67
+ }
68
+ }
69
+
70
+ if (loading) {
71
+ return (
72
+ <header className="flex shrink-0 items-center border-b border-slate-800 bg-slate-900 px-4 py-3 md:px-5">
73
+ <div className="text-sm text-slate-400">Loading courses...</div>
74
+ </header>
75
+ );
76
+ }
77
+
78
+ return (
79
+ <header className="flex shrink-0 flex-wrap items-center gap-3 border-b border-slate-800 bg-slate-900 px-3 py-3 md:px-5">
80
+ <div className="flex items-center gap-2">
81
+ <span className="text-lg">📚</span>
82
+ <span className="text-lg font-semibold">Professor</span>
83
+ </div>
84
+
85
+ <div className="w-full md:w-auto">
86
+ <Select
87
+ value={selectedCourse?.slug || '__none__'}
88
+ onValueChange={handleCourseChange}
89
+ disabled={loading}
90
+ >
91
+ <SelectTrigger className="h-9 w-full min-w-[180px] md:w-[220px]">
92
+ <SelectValue placeholder="Select course..." />
93
+ </SelectTrigger>
94
+ <SelectContent>
95
+ <SelectItem value="__none__">Select course...</SelectItem>
96
+ {courses.map((course) => (
97
+ <SelectItem key={course.slug} value={course.slug}>
98
+ {course.name}
99
+ </SelectItem>
100
+ ))}
101
+ </SelectContent>
102
+ </Select>
103
+ </div>
104
+
105
+ {selectedCourse && (
106
+ <div className="ml-0 flex w-full items-center gap-3 text-sm md:ml-auto md:w-auto">
107
+ <span className="font-medium text-slate-100">{selectedCourse.name}</span>
108
+ {selectedCourse.lastActive && (
109
+ <span className="text-xs text-slate-400">
110
+ {formatLastActive(selectedCourse.lastActive)}
111
+ </span>
112
+ )}
113
+ </div>
114
+ )}
115
+
116
+ {error && <div className="text-xs text-red-400">{error}</div>}
117
+ </header>
118
+ );
119
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * LecturePanel.jsx - Markdown rendering with syntax highlighting
3
+ * Supports tabs: Lecture (LECTURE.md), Syllabus (COURSE.md), Exercises
4
+ */
5
+
6
+ import { useState, useEffect, useCallback } from 'react';
7
+ import ReactMarkdown from 'react-markdown';
8
+ import remarkGfm from 'remark-gfm';
9
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
10
+ import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
11
+ import { fetchCourseFile, fetchExercises, fetchExercise, saveExercise } from '../api/client.js';
12
+ import { Button } from './ui/button.jsx';
13
+ import ExerciseEditor from './ExerciseEditor.jsx';
14
+
15
+ const CODE_COMPONENTS = {
16
+ code({ node, inline, className, children, ...props }) {
17
+ const match = /language-(\w+)/.exec(className || '');
18
+ return !inline && match ? (
19
+ <SyntaxHighlighter
20
+ style={oneDark}
21
+ language={match[1]}
22
+ PreTag="div"
23
+ {...props}
24
+ >
25
+ {String(children).replace(/\n$/, '')}
26
+ </SyntaxHighlighter>
27
+ ) : (
28
+ <code className={className} {...props}>
29
+ {children}
30
+ </code>
31
+ );
32
+ },
33
+ };
34
+
35
+ /**
36
+ * LecturePanel component - displays markdown lecture content
37
+ * @param {Object} props
38
+ * @param {string|null} props.courseSlug - Current course slug
39
+ * @param {string} [props.content] - Optional pre-loaded content
40
+ * @param {Function} [props.onLectureUpdate] - Callback when lecture updates
41
+ */
42
+ export default function LecturePanel({ courseSlug, content: initialContent, onLectureUpdate }) {
43
+ const [activeTab, setActiveTab] = useState('lecture'); // 'lecture' | 'syllabus' | 'exercises'
44
+ const [lectureContent, setLectureContent] = useState(initialContent || '');
45
+ const [syllabusContent, setSyllabusContent] = useState('');
46
+ const [exercises, setExercises] = useState([]);
47
+ const [activeExercise, setActiveExercise] = useState(null);
48
+ const [exerciseContent, setExerciseContent] = useState('');
49
+ const [selectedExercise, setSelectedExercise] = useState(null);
50
+ const [loading, setLoading] = useState(false);
51
+ const [error, setError] = useState(null);
52
+ const [saving, setSaving] = useState(false);
53
+
54
+ const content = activeTab === 'lecture' ? lectureContent :
55
+ activeTab === 'syllabus' ? syllabusContent : '';
56
+
57
+ const loadFile = useCallback(async (tab) => {
58
+ if (!courseSlug) return;
59
+
60
+ const file = tab === 'lecture' ? 'LECTURE.md' : 'COURSE.md';
61
+ setLoading(true);
62
+ setError(null);
63
+
64
+ try {
65
+ const data = await fetchCourseFile(courseSlug, file);
66
+ const text = data?.content || '';
67
+ if (tab === 'lecture') {
68
+ setLectureContent(text);
69
+ } else {
70
+ setSyllabusContent(text);
71
+ }
72
+ } catch (err) {
73
+ console.error(`Failed to load ${file}:`, err);
74
+ setError(`Failed to load ${tab}`);
75
+ } finally {
76
+ setLoading(false);
77
+ }
78
+ }, [courseSlug]);
79
+
80
+ // Load exercises
81
+ const loadExercises = useCallback(async () => {
82
+ if (!courseSlug) return;
83
+
84
+ setLoading(true);
85
+ setError(null);
86
+
87
+ try {
88
+ const data = await fetchExercises(courseSlug);
89
+ setExercises(data.exercises || []);
90
+ setActiveExercise(data.activeExercise);
91
+
92
+ // Auto-select active exercise or first one
93
+ const toSelect = data.activeExercise || (data.exercises?.[0]?.filename);
94
+ if (toSelect) {
95
+ setSelectedExercise(toSelect);
96
+ const exerciseData = await fetchExercise(courseSlug, toSelect);
97
+ setExerciseContent(exerciseData.content);
98
+ }
99
+ } catch (err) {
100
+ console.error('Failed to load exercises:', err);
101
+ setError('Failed to load exercises');
102
+ } finally {
103
+ setLoading(false);
104
+ }
105
+ }, [courseSlug]);
106
+
107
+ // Handle exercise selection
108
+ const handleExerciseSelect = async (filename) => {
109
+ if (!courseSlug) return;
110
+
111
+ setSelectedExercise(filename);
112
+ try {
113
+ const data = await fetchExercise(courseSlug, filename);
114
+ setExerciseContent(data.content);
115
+ } catch (err) {
116
+ console.error('Failed to load exercise:', err);
117
+ }
118
+ };
119
+
120
+ // Handle exercise save
121
+ const handleExerciseSave = async () => {
122
+ if (!courseSlug || !selectedExercise) return;
123
+
124
+ setSaving(true);
125
+ try {
126
+ await saveExercise(courseSlug, selectedExercise, exerciseContent);
127
+ alert('Exercise saved!');
128
+ } catch (err) {
129
+ console.error('Failed to save exercise:', err);
130
+ alert('Failed to save exercise');
131
+ } finally {
132
+ setSaving(false);
133
+ }
134
+ };
135
+
136
+ // Load both files on course change
137
+ useEffect(() => {
138
+ if (!courseSlug) {
139
+ setLectureContent('');
140
+ setSyllabusContent('');
141
+ setExercises([]);
142
+ setSelectedExercise(null);
143
+ setExerciseContent('');
144
+ return;
145
+ }
146
+ loadFile('lecture');
147
+ loadFile('syllabus');
148
+ if (activeTab === 'exercises') {
149
+ loadExercises();
150
+ }
151
+ }, [courseSlug, loadFile, activeTab, loadExercises]);
152
+
153
+ // Load exercises when switching to exercises tab
154
+ useEffect(() => {
155
+ if (activeTab === 'exercises' && courseSlug && exercises.length === 0) {
156
+ loadExercises();
157
+ }
158
+ }, [activeTab, courseSlug, exercises.length, loadExercises]);
159
+
160
+ // Refresh via custom events (fired when server saves course files)
161
+ useEffect(() => {
162
+ const handleLecture = () => loadFile('lecture');
163
+ const handleSyllabus = () => loadFile('syllabus');
164
+ window.addEventListener('refresh-lecture', handleLecture);
165
+ window.addEventListener('refresh-syllabus', handleSyllabus);
166
+ return () => {
167
+ window.removeEventListener('refresh-lecture', handleLecture);
168
+ window.removeEventListener('refresh-syllabus', handleSyllabus);
169
+ };
170
+ }, [loadFile]);
171
+
172
+ // Notify parent of lecture content changes
173
+ useEffect(() => {
174
+ if (onLectureUpdate) {
175
+ onLectureUpdate(lectureContent);
176
+ }
177
+ }, [lectureContent, onLectureUpdate]);
178
+
179
+ if (!courseSlug) {
180
+ return (
181
+ <div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
182
+ <div className="flex flex-col items-center gap-2">
183
+ <span className="text-5xl">📖</span>
184
+ <p>Select a course to see the current lecture.</p>
185
+ </div>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ return (
191
+ <div className="flex h-full min-h-0 flex-col overflow-hidden">
192
+ <div className="flex shrink-0 gap-2 border-b border-slate-800 bg-slate-900/70 px-3 pt-2 md:px-4">
193
+ <Button
194
+ variant="ghost"
195
+ className={`h-9 rounded-b-none border-b-2 px-3 ${
196
+ activeTab === 'lecture'
197
+ ? 'border-indigo-400 text-indigo-300'
198
+ : 'border-transparent text-slate-400'
199
+ }`}
200
+ onClick={() => setActiveTab('lecture')}
201
+ >
202
+ 📝 Lecture
203
+ </Button>
204
+ <Button
205
+ variant="ghost"
206
+ className={`h-9 rounded-b-none border-b-2 px-3 ${
207
+ activeTab === 'syllabus'
208
+ ? 'border-indigo-400 text-indigo-300'
209
+ : 'border-transparent text-slate-400'
210
+ }`}
211
+ onClick={() => setActiveTab('syllabus')}
212
+ >
213
+ 📋 Syllabus
214
+ </Button>
215
+ <Button
216
+ variant="ghost"
217
+ className={`h-9 rounded-b-none border-b-2 px-3 ${
218
+ activeTab === 'exercises'
219
+ ? 'border-indigo-400 text-indigo-300'
220
+ : 'border-transparent text-slate-400'
221
+ }`}
222
+ onClick={() => setActiveTab('exercises')}
223
+ >
224
+ 💪 Exercises
225
+ {activeExercise && <span className="ml-1 text-[10px] text-emerald-500">●</span>}
226
+ </Button>
227
+ </div>
228
+
229
+ {loading ? (
230
+ <div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
231
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-700 border-t-indigo-400" />
232
+ <p>Loading...</p>
233
+ </div>
234
+ ) : error ? (
235
+ <div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
236
+ <p>{error}</p>
237
+ <Button onClick={() => loadFile(activeTab)}>Retry</Button>
238
+ </div>
239
+ ) : activeTab === 'exercises' ? (
240
+ <div className="flex min-h-0 h-full overflow-hidden">
241
+ {exercises.length === 0 ? (
242
+ <div className="flex h-full flex-1 flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
243
+ <div className="flex flex-col items-center gap-2">
244
+ <span className="text-5xl">💪</span>
245
+ <p>No exercises yet.</p>
246
+ <p className="text-sm">
247
+ Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:next</code> in chat to
248
+ create exercises.
249
+ </p>
250
+ </div>
251
+ </div>
252
+ ) : (
253
+ <>
254
+ <div className="hidden w-48 shrink-0 border-r border-slate-800 bg-slate-900/40 md:block">
255
+ <div className="p-3">
256
+ <h4 className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-400">
257
+ Exercises
258
+ </h4>
259
+ {exercises.map((ex) => (
260
+ <Button
261
+ key={ex.filename}
262
+ variant={selectedExercise === ex.filename ? 'secondary' : 'ghost'}
263
+ className="mb-1 h-auto w-full justify-start px-2 py-2 text-left text-xs"
264
+ onClick={() => handleExerciseSelect(ex.filename)}
265
+ >
266
+ {ex.filename.replace('.md', '')}
267
+ {activeExercise === ex.filename && (
268
+ <span className="ml-1 text-[10px] text-emerald-500">●</span>
269
+ )}
270
+ </Button>
271
+ ))}
272
+ </div>
273
+ </div>
274
+ <div className="flex min-h-0 flex-1 flex-col overflow-hidden">
275
+ <div className="flex items-center justify-between border-b border-slate-800 bg-slate-900/40 px-3 py-2 md:px-4">
276
+ <h4 className="text-sm font-medium">{selectedExercise}</h4>
277
+ <Button
278
+ size="sm"
279
+ onClick={handleExerciseSave}
280
+ disabled={saving}
281
+ >
282
+ {saving ? 'Saving...' : '💾 Save'}
283
+ </Button>
284
+ </div>
285
+ <div className="border-b border-slate-800 bg-slate-900/40 p-2 md:hidden">
286
+ <select
287
+ value={selectedExercise || ''}
288
+ onChange={(e) => handleExerciseSelect(e.target.value)}
289
+ className="h-9 w-full rounded-md border border-slate-700 bg-slate-950 px-2 text-sm text-slate-100"
290
+ >
291
+ {exercises.map((ex) => (
292
+ <option key={ex.filename} value={ex.filename}>
293
+ {ex.filename.replace('.md', '')}
294
+ </option>
295
+ ))}
296
+ </select>
297
+ </div>
298
+ <div className="min-h-0 flex-1 overflow-hidden">
299
+ <ExerciseEditor
300
+ value={exerciseContent}
301
+ onChange={setExerciseContent}
302
+ />
303
+ </div>
304
+ </div>
305
+ </>
306
+ )}
307
+ </div>
308
+ ) : !content ? (
309
+ <div className="flex h-full flex-col items-center justify-center gap-3 px-6 text-center text-slate-400">
310
+ <div className="flex flex-col items-center gap-2">
311
+ <span className="text-5xl">✨</span>
312
+ {activeTab === 'lecture' ? (
313
+ <>
314
+ <p>No lecture loaded yet.</p>
315
+ <p className="text-sm">
316
+ Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:next</code> in chat to
317
+ start your first section.
318
+ </p>
319
+ </>
320
+ ) : (
321
+ <>
322
+ <p>No syllabus found.</p>
323
+ <p className="text-sm">
324
+ Type <code className="rounded bg-slate-800 px-1.5 py-0.5 text-slate-200">professor:new-topic</code> in chat
325
+ to create a course.
326
+ </p>
327
+ </>
328
+ )}
329
+ </div>
330
+ </div>
331
+ ) : (
332
+ <div className="min-h-0 flex-1 overflow-y-auto px-4 py-5 md:px-6">
333
+ <div className="max-w-none text-sm leading-7 text-slate-200 [&_a]:text-indigo-300 [&_a:hover]:underline [&_blockquote]:my-4 [&_blockquote]:border-l-2 [&_blockquote]:border-indigo-400 [&_blockquote]:pl-4 [&_blockquote]:italic [&_code]:rounded [&_code]:bg-slate-800 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-xs [&_h1]:mt-6 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:mt-5 [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:mt-4 [&_h3]:text-lg [&_h3]:font-medium [&_hr]:my-6 [&_hr]:border-slate-700 [&_img]:my-3 [&_img]:max-w-full [&_img]:rounded-md [&_li]:my-1 [&_ol]:my-3 [&_ol]:pl-6 [&_p]:my-3 [&_pre]:my-4 [&_pre]:overflow-x-auto [&_pre]:rounded-md [&_pre]:border [&_pre]:border-slate-700 [&_pre]:bg-slate-900 [&_pre]:p-4 [&_table]:my-4 [&_table]:w-full [&_table]:border-collapse [&_td]:border [&_td]:border-slate-700 [&_td]:p-2 [&_th]:border [&_th]:border-slate-700 [&_th]:bg-slate-800 [&_th]:p-2 [&_th]:text-left [&_ul]:my-3 [&_ul]:pl-6">
334
+ <ReactMarkdown remarkPlugins={[remarkGfm]} components={CODE_COMPONENTS}>
335
+ {content}
336
+ </ReactMarkdown>
337
+ </div>
338
+ </div>
339
+ )}
340
+ </div>
341
+ );
342
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * MobileTabSwitcher.jsx - Mobile view toggle
3
+ */
4
+ import { Button } from './ui/button.jsx';
5
+
6
+ /**
7
+ * MobileTabSwitcher component - toggle between lecture and chat on mobile
8
+ * @param {Object} props
9
+ * @param {string} props.activeTab - Current active tab: 'lecture' | 'chat'
10
+ * @param {Function} props.onTabChange - Callback when tab changes
11
+ */
12
+ export default function MobileTabSwitcher({ activeTab = 'lecture', onTabChange }) {
13
+ function handleLectureClick() {
14
+ onTabChange?.('lecture');
15
+ }
16
+
17
+ function handleChatClick() {
18
+ onTabChange?.('chat');
19
+ }
20
+
21
+ return (
22
+ <div className="flex shrink-0 border-b border-slate-800 bg-slate-900 md:hidden">
23
+ <Button
24
+ variant="ghost"
25
+ className={`h-11 flex-1 justify-center rounded-none border-b-2 ${
26
+ activeTab === 'lecture'
27
+ ? 'border-indigo-400 text-indigo-300'
28
+ : 'border-transparent text-slate-400'
29
+ }`}
30
+ onClick={handleLectureClick}
31
+ aria-pressed={activeTab === 'lecture'}
32
+ >
33
+ <span>📖</span>
34
+ <span>Lecture</span>
35
+ </Button>
36
+ <Button
37
+ variant="ghost"
38
+ className={`h-11 flex-1 justify-center rounded-none border-b-2 ${
39
+ activeTab === 'chat'
40
+ ? 'border-indigo-400 text-indigo-300'
41
+ : 'border-transparent text-slate-400'
42
+ }`}
43
+ onClick={handleChatClick}
44
+ aria-pressed={activeTab === 'chat'}
45
+ >
46
+ <span>💬</span>
47
+ <span>Chat</span>
48
+ </Button>
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * SplitPane.jsx - 50/50 split layout with divider
3
+ */
4
+
5
+ /**
6
+ * SplitPane component - 50/50 split layout with mobile support
7
+ * @param {Object} props
8
+ * @param {React.ReactNode} props.lecturePanel - Content for lecture panel (left)
9
+ * @param {React.ReactNode} props.chatPanel - Content for chat panel (right)
10
+ * @param {string} [props.activeTab] - 'lecture' | 'chat' - which panel to show on mobile
11
+ * @param {string} [props.mobileTab] - Current mobile tab state (controlled)
12
+ */
13
+ export default function SplitPane({
14
+ lecturePanel,
15
+ chatPanel,
16
+ activeTab = 'both',
17
+ mobileTab = 'lecture'
18
+ }) {
19
+ // On mobile, show only the active tab
20
+ const showLecture = activeTab === 'both' || mobileTab === 'lecture';
21
+ const showChat = activeTab === 'both' || mobileTab === 'chat';
22
+
23
+ return (
24
+ <div className="flex h-full min-h-0 w-full flex-1 flex-col overflow-hidden md:flex-row">
25
+ {showLecture && (
26
+ <div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden md:w-1/2">
27
+ {lecturePanel}
28
+ </div>
29
+ )}
30
+
31
+ {activeTab === 'both' && showLecture && showChat && (
32
+ <div className="hidden w-px shrink-0 bg-border md:block" />
33
+ )}
34
+
35
+ {showChat && (
36
+ <div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden md:w-1/2">
37
+ {chatPanel}
38
+ </div>
39
+ )}
40
+ </div>
41
+ );
42
+ }
@@ -0,0 +1,38 @@
1
+ import { cn } from '../../lib/utils.js';
2
+
3
+ export function Alert({ className, children, variant = 'default', ...props }) {
4
+ const variants = {
5
+ default: 'border-slate-700 bg-slate-900 text-slate-200',
6
+ destructive: 'border-red-700/60 bg-red-900/30 text-red-200',
7
+ };
8
+
9
+ return (
10
+ <div
11
+ role="alert"
12
+ className={cn(
13
+ 'rounded-md border px-3 py-2 text-sm',
14
+ variants[variant] || variants.default,
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ {children}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ export function AlertTitle({ className, children, ...props }) {
25
+ return (
26
+ <h5 className={cn('mb-1 text-sm font-semibold', className)} {...props}>
27
+ {children}
28
+ </h5>
29
+ );
30
+ }
31
+
32
+ export function AlertDescription({ className, children, ...props }) {
33
+ return (
34
+ <div className={cn('text-xs leading-5', className)} {...props}>
35
+ {children}
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,24 @@
1
+ import { cn } from '../../lib/utils.js';
2
+
3
+ export function Badge({ className, children, variant = 'default', ...props }) {
4
+ const variants = {
5
+ default: 'border border-slate-600 bg-slate-800 text-slate-100',
6
+ secondary: 'border border-slate-700 bg-slate-900 text-slate-300',
7
+ warning: 'border border-amber-600/60 bg-amber-900/40 text-amber-200',
8
+ };
9
+
10
+ return (
11
+ <span
12
+ className={cn(
13
+ 'inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium',
14
+ variants[variant] || variants.default,
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ {children}
20
+ </span>
21
+ );
22
+ }
23
+
24
+ export default Badge;
@@ -0,0 +1,23 @@
1
+ import { cn } from '../../lib/utils.js';
2
+
3
+ export function Button({ className, variant = 'default', children, ...props }) {
4
+ const variants = {
5
+ default: 'bg-indigo-600 text-white hover:bg-indigo-700 disabled:opacity-40',
6
+ secondary: 'bg-slate-800 text-slate-100 border border-slate-700 hover:bg-slate-700',
7
+ };
8
+
9
+ return (
10
+ <button
11
+ className={cn(
12
+ 'inline-flex items-center justify-center rounded-md px-3 py-2 text-sm font-medium transition-colors',
13
+ variants[variant] || variants.default,
14
+ className
15
+ )}
16
+ {...props}
17
+ >
18
+ {children}
19
+ </button>
20
+ );
21
+ }
22
+
23
+ export default Button;