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.
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/README.md +225 -2
- package/bin/cli.js +811 -0
- package/bin/commands/check.js +298 -0
- package/bin/commands/create.js +169 -0
- package/bin/commands/doctor.js +392 -0
- package/bin/commands/import.js +557 -0
- package/bin/commands/next.js +125 -0
- package/bin/commands/review.js +207 -0
- package/bin/commands/status.js +114 -0
- package/bin/commands/studio.js +497 -0
- package/bin/commands/trail.js +211 -0
- package/bin/commands/web.js +350 -0
- package/bin/course-reader.js +611 -0
- package/bin/node-version.js +60 -0
- package/bin/platforms/_shared.js +211 -0
- package/bin/platforms/claude.js +104 -0
- package/bin/platforms/cursor.js +49 -0
- package/bin/platforms/gemini.js +65 -0
- package/bin/platforms/opencode.js +95 -0
- package/bin/registry-helpers.js +504 -0
- package/bin/static-server.js +153 -0
- package/bin/studio-store.js +94 -0
- package/bin/sync-shared.sh +5 -0
- package/package.json +91 -4
- package/plugin.json +139 -0
- package/shared/SKILL.md +568 -0
- package/shared/agents/coach.md +225 -0
- package/shared/agents/navigator.md +215 -0
- package/shared/agents/professor.md +372 -0
- package/shared/agents/researcher.md +181 -0
- package/shared/agents/spotter.md +129 -0
- package/shared/commands/professor/archive.md +103 -0
- package/shared/commands/professor/capstone-review.md +79 -0
- package/shared/commands/professor/capstone.md +34 -0
- package/shared/commands/professor/discuss.md +18 -0
- package/shared/commands/professor/done.md +44 -0
- package/shared/commands/professor/export.md +69 -0
- package/shared/commands/professor/hint.md +108 -0
- package/shared/commands/professor/navigator.md +67 -0
- package/shared/commands/professor/new-topic.md +73 -0
- package/shared/commands/professor/next.md +174 -0
- package/shared/commands/professor/note.md +63 -0
- package/shared/commands/professor/onboard.md +95 -0
- package/shared/commands/professor/progress.md +46 -0
- package/shared/commands/professor/publish.md +93 -0
- package/shared/commands/professor/quiz.md +27 -0
- package/shared/commands/professor/recall.md +74 -0
- package/shared/commands/professor/review.md +43 -0
- package/shared/commands/professor/schedule.md +75 -0
- package/shared/commands/professor/skill-export.md +87 -0
- package/shared/commands/professor/spotter.md +76 -0
- package/shared/commands/professor/stuck.md +27 -0
- package/shared/commands/professor/switch.md +50 -0
- package/shared/commands/professor/syllabus.md +9 -0
- package/shared/commands/professor/template-export.md +91 -0
- package/shared/commands/professor/template-import.md +111 -0
- package/shared/commands/professor/worktrees.md +58 -0
- package/shared/hooks/pre-compact.js +232 -0
- package/studio/README.md +313 -0
- package/studio/dist/assets/BetaReportPanel-8NrDLiPm.js +1 -0
- package/studio/dist/assets/Capstone-Cv9kH6LY.js +6 -0
- package/studio/dist/assets/CodeWorkspace-BwCEINWo.js +2 -0
- package/studio/dist/assets/Course-BJ_hafUP.js +1 -0
- package/studio/dist/assets/Editor-CV0mNhlz.js +53 -0
- package/studio/dist/assets/Home-C1ry89ak.js +1 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/studio/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/studio/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/studio/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/studio/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/studio/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/studio/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/studio/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/studio/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/studio/dist/assets/Lesson-2e9TD9yL.js +2 -0
- package/studio/dist/assets/NewCourse-BZeOg2xn.js +952 -0
- package/studio/dist/assets/Progress-CRc_xEWC.js +1 -0
- package/studio/dist/assets/Review-6QEbHEfk.js +1 -0
- package/studio/dist/assets/Settings-NFnqVQui.js +1 -0
- package/studio/dist/assets/TutorPanel-DXQ6Z3BY.js +123 -0
- package/studio/dist/assets/abnfDiagram-WEYOIA3C-qeSjKlGM.js +1 -0
- package/studio/dist/assets/anthropic-D7NiQCUH.js +15 -0
- package/studio/dist/assets/arc-Y1ARJVZ4.js +1 -0
- package/studio/dist/assets/architecture-WOLXFQ4H-COG-m35l.js +1 -0
- package/studio/dist/assets/architectureDiagram-ZHVYKT53-VO7uKoeD.js +36 -0
- package/studio/dist/assets/array-BifhSqXX.js +1 -0
- package/studio/dist/assets/blockDiagram-RJAE7QA3-BGJF_qiL.js +140 -0
- package/studio/dist/assets/bridge-BTh0AeNX.js +1 -0
- package/studio/dist/assets/c4Diagram-XDPPPE54-C-S10RgU.js +38 -0
- package/studio/dist/assets/capstone-Amn6Hztq.js +135 -0
- package/studio/dist/assets/card-store-BrbrtX9w.js +1 -0
- package/studio/dist/assets/channel-SJiUQu_N.js +1 -0
- package/studio/dist/assets/chunk-2E7WHVMD-fv-iqTyC.js +88 -0
- package/studio/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/studio/dist/assets/chunk-4EMNKR2T-CY9IZrgv.js +1 -0
- package/studio/dist/assets/chunk-55XCU76K-DyTZz7v_.js +1 -0
- package/studio/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/studio/dist/assets/chunk-7S6XSTFO-BNMzTLRW.js +2 -0
- package/studio/dist/assets/chunk-AQDTSKZB-CEYANohH.js +72 -0
- package/studio/dist/assets/chunk-DSNLSHJZ-CDk5EXjH.js +129 -0
- package/studio/dist/assets/chunk-DUW6YSOI-Dgx8z5s3.js +1 -0
- package/studio/dist/assets/chunk-HAGFOTED-hNAi1zh_.js +62 -0
- package/studio/dist/assets/chunk-HNE4Q36P-COJnSYoZ.js +1 -0
- package/studio/dist/assets/chunk-J5ZVWO5B-oDCd-gWO.js +1 -0
- package/studio/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/studio/dist/assets/chunk-NGNAAXSQ-C2Wy9Xmz.js +136 -0
- package/studio/dist/assets/chunk-NQOKRVIF-BU6KVxRr.js +213 -0
- package/studio/dist/assets/chunk-UHW7PFBP-DYvxL9JG.js +1 -0
- package/studio/dist/assets/chunk-VQ5W62QI-CuRfk65j.js +1 -0
- package/studio/dist/assets/chunk-WQQL4CDK-KaG7x-Mb.js +10 -0
- package/studio/dist/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
- package/studio/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/studio/dist/assets/chunk-YZO67HFG-DbuqIZmk.js +1 -0
- package/studio/dist/assets/chunk-ZAUX2LTX-bk4ivUmc.js +1 -0
- package/studio/dist/assets/chunk-ZCHMQSOR-B_LElfwh.js +2 -0
- package/studio/dist/assets/classDiagram-v2-4MYWR3UA-DtCJ6H_X.js +217 -0
- package/studio/dist/assets/cose-bilkent-JH36ORCC-DMBAZFnj.js +1 -0
- package/studio/dist/assets/course-export-DpaOTT3a.js +1 -0
- package/studio/dist/assets/cynefin-EF2NZ3EQ-D9btHJvU.js +1 -0
- package/studio/dist/assets/cynefinDiagram-WF24UDRP-CsFJuv27.js +62 -0
- package/studio/dist/assets/cytoscape.esm-CHBj4VkZ.js +321 -0
- package/studio/dist/assets/dagre-NK4GIZVX-Cg1Duec7.js +4 -0
- package/studio/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/studio/dist/assets/diagram-CBK3MWTC-_KU2m5gd.js +24 -0
- package/studio/dist/assets/diagram-I5XIIV2D-5RaBrwUA.js +24 -0
- package/studio/dist/assets/diagram-ODIL2UCW-C0VKxoD3.js +30 -0
- package/studio/dist/assets/diagram-PFBEJUXE-BaHcZEP2.js +3 -0
- package/studio/dist/assets/diagram-RJA3I4VV-cwSAZ90p.js +41 -0
- package/studio/dist/assets/diagram-TYUNFSNR-BJImxCEN.js +200 -0
- package/studio/dist/assets/dist-CumnGVmJ.js +1 -0
- package/studio/dist/assets/ebnfDiagram-V4P3TTPT-OQQLRXcm.js +1 -0
- package/studio/dist/assets/elk-QONTBCTQ-BJGhQHxQ.js +27 -0
- package/studio/dist/assets/erDiagram-KYQIGQAA-D91EKSep.js +99 -0
- package/studio/dist/assets/error-BTdmRKoB.js +1 -0
- package/studio/dist/assets/errors-BZa_CNHL.js +1 -0
- package/studio/dist/assets/eventmodeling-K75KTNOO-DgdTRgnw.js +1 -0
- package/studio/dist/assets/fetch-local-DG3O3hmQ.js +4 -0
- package/studio/dist/assets/flowDiagram-IUGLLWBU-CVnJYMum.js +1 -0
- package/studio/dist/assets/ganttDiagram-R7GPNTJY-CSYLPWEI.js +292 -0
- package/studio/dist/assets/gitGraph-VSP46ZUC-D6GWaV9y.js +1 -0
- package/studio/dist/assets/gitGraphDiagram-PDYL7XHY-CaV31bvT.js +106 -0
- package/studio/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/studio/dist/assets/index-CVenTTOY.css +2 -0
- package/studio/dist/assets/index-DioXTZVc.js +313 -0
- package/studio/dist/assets/info-OHQRW6UA-BN-x8GoN.js +1 -0
- package/studio/dist/assets/infoDiagram-MVDRKAX5-CIose_cy.js +2 -0
- package/studio/dist/assets/init-C-OQMol4.js +1 -0
- package/studio/dist/assets/ishikawaDiagram-HPEHV2GE-DCihSNBw.js +70 -0
- package/studio/dist/assets/journeyDiagram-BOSHW7O7-Bz3o3RIX.js +139 -0
- package/studio/dist/assets/js.worker-AD7DjksO.js +4 -0
- package/studio/dist/assets/kanban-definition-FNSA4HC5-BUyqvlFE.js +89 -0
- package/studio/dist/assets/katex-B_JcIwqj.js +257 -0
- package/studio/dist/assets/katex-BgsA2lO1.css +1 -0
- package/studio/dist/assets/line-B08T8KlK.js +1 -0
- package/studio/dist/assets/linear-C9iwaMOi.js +1 -0
- package/studio/dist/assets/mermaid-parser.core-BYwreSUy.js +7 -0
- package/studio/dist/assets/mermaid.core-DTO1oDmq.js +44 -0
- package/studio/dist/assets/mindmap-definition-6OY57UG3-BGLwKheY.js +98 -0
- package/studio/dist/assets/node.browser-BISniYn8.js +1 -0
- package/studio/dist/assets/node.browser-HQ5SKBOf.js +1 -0
- package/studio/dist/assets/ollama-De5w84tG.js +1 -0
- package/studio/dist/assets/openai-compatible-Joy1oK3L.js +1 -0
- package/studio/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/studio/dist/assets/packet-JDAUHWVQ-CDIMjHS_.js +1 -0
- package/studio/dist/assets/path-fybaL0A-.js +1 -0
- package/studio/dist/assets/pegDiagram-ECIUJNBX-DWXm4mwS.js +1 -0
- package/studio/dist/assets/pie-XZMESJXO-CY3QQiKZ.js +1 -0
- package/studio/dist/assets/pieDiagram-GNMPY74T-M1aCnI7W.js +39 -0
- package/studio/dist/assets/python.worker-CzTrFdd9.js +24 -0
- package/studio/dist/assets/quadrantDiagram-D57K4MC3-BFFtO7U5.js +7 -0
- package/studio/dist/assets/radar-ABXABTNO-DT0W4_Xn.js +1 -0
- package/studio/dist/assets/railroad-I3PHUGI6-_VCpgZR5.js +1 -0
- package/studio/dist/assets/railroad-abnf-LNEFI6M7-DkSpQ5YY.js +1 -0
- package/studio/dist/assets/railroad-ebnf-DWYD2UWJ-BqWILdGb.js +1 -0
- package/studio/dist/assets/railroad-peg-JR7BVNR7-BB0bQL-Z.js +1 -0
- package/studio/dist/assets/railroadDiagram-Y72NRBL7-CjLKjIdJ.js +1 -0
- package/studio/dist/assets/react-B8SKf8RM.js +1 -0
- package/studio/dist/assets/rehype-katex-C-sKddsH.js +259 -0
- package/studio/dist/assets/remark-math-C-ox67hE.js +5 -0
- package/studio/dist/assets/requirementDiagram-EQYSW5RA-CnwzZqR8.js +84 -0
- package/studio/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
- package/studio/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/studio/dist/assets/sankeyDiagram-SY24JHO5-CPDugTUl.js +40 -0
- package/studio/dist/assets/schemas-BOc2YsOP.js +55 -0
- package/studio/dist/assets/sequenceDiagram-BLEHNHQU-1dLfeU9Y.js +169 -0
- package/studio/dist/assets/settings-DhwuyNPU.js +1 -0
- package/studio/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/studio/dist/assets/spec-nAnsSMkw.js +1 -0
- package/studio/dist/assets/sql-wasm-C1D_RJuy.wasm +0 -0
- package/studio/dist/assets/sql.worker-D7pBHOrq.js +4 -0
- package/studio/dist/assets/src-BWF7jMDv.js +1 -0
- package/studio/dist/assets/stateDiagram-v2-A6IESKWZ-DR7CmEjj.js +291 -0
- package/studio/dist/assets/swimlanes-B3ENRKFQ-BAWB9omQ.js +1 -0
- package/studio/dist/assets/swimlanesDiagram-AYQRV4AK-qeuHru16.js +8 -0
- package/studio/dist/assets/timeline-definition-JXYKGLGB-DNkj8z77.js +120 -0
- package/studio/dist/assets/treeView-D4JQ5SDB-BA-ouhMb.js +1 -0
- package/studio/dist/assets/treemap-SAJKECNS-BrrGxsAb.js +1 -0
- package/studio/dist/assets/useStream-rdsR6A7P.js +1 -0
- package/studio/dist/assets/usecaseDiagram-WPOLBNNX-BB3D0Dt_.js +328 -0
- package/studio/dist/assets/vennDiagram-Q3Y3IDCW-BrtqtKTE.js +34 -0
- package/studio/dist/assets/wardley-7MLQ67FV-BKkemDql.js +1 -0
- package/studio/dist/assets/wardleyDiagram-IVAIDKWA-DFnI3P5z.js +78 -0
- package/studio/dist/assets/xychartDiagram-4URNHDU4-Da0DZG_q.js +7 -0
- package/studio/dist/index.html +20 -0
- package/studio/dist/preview.html +22 -0
- package/studio/migrations/0001_init.sql +188 -0
- package/studio/migrations/0002_external_ids.sql +20 -0
- package/studio/migrations/0003_course_language.sql +12 -0
- package/studio/migrations/0004_capstone.sql +51 -0
- package/studio/migrations/0005_beta_loop.sql +73 -0
- package/studio/migrations/0006_interactive_exercises.sql +49 -0
- package/studio/src/lib/activity.js +92 -0
- package/studio/src/lib/ai/anthropic.js +109 -0
- package/studio/src/lib/ai/bridge.js +126 -0
- package/studio/src/lib/ai/catalog.js +160 -0
- package/studio/src/lib/ai/config.js +70 -0
- package/studio/src/lib/ai/errors.js +95 -0
- package/studio/src/lib/ai/fetch-local.js +104 -0
- package/studio/src/lib/ai/index.js +194 -0
- package/studio/src/lib/ai/ollama.js +136 -0
- package/studio/src/lib/ai/openai-compatible.js +101 -0
- package/studio/src/lib/ai/provider-utils.js +107 -0
- package/studio/src/lib/ai/sse.js +99 -0
- package/studio/src/lib/ai/status.js +92 -0
- package/studio/src/lib/ai/useStream.js +109 -0
- package/studio/src/lib/beta/metrics.js +147 -0
- package/studio/src/lib/beta/report.js +134 -0
- package/studio/src/lib/capstone-evidence.js +758 -0
- package/studio/src/lib/capstone.js +663 -0
- package/studio/src/lib/chat-store.js +128 -0
- package/studio/src/lib/course-export.js +38 -0
- package/studio/src/lib/course.js +187 -0
- package/studio/src/lib/exercises/journal-entry.js +259 -0
- package/studio/src/lib/exercises/placement.js +31 -0
- package/studio/src/lib/exercises/registry.js +19 -0
- package/studio/src/lib/exercises/spec.js +129 -0
- package/studio/src/lib/i18n/detect.js +162 -0
- package/studio/src/lib/i18n/en/app.json +23 -0
- package/studio/src/lib/i18n/en/betaReport.json +55 -0
- package/studio/src/lib/i18n/en/capstone.json +57 -0
- package/studio/src/lib/i18n/en/common.json +25 -0
- package/studio/src/lib/i18n/en/course.json +27 -0
- package/studio/src/lib/i18n/en/data.json +35 -0
- package/studio/src/lib/i18n/en/durability.json +5 -0
- package/studio/src/lib/i18n/en/errors.json +20 -0
- package/studio/src/lib/i18n/en/exercises.json +55 -0
- package/studio/src/lib/i18n/en/externalEvidence.json +76 -0
- package/studio/src/lib/i18n/en/home.json +18 -0
- package/studio/src/lib/i18n/en/lesson.json +33 -0
- package/studio/src/lib/i18n/en/nav.json +8 -0
- package/studio/src/lib/i18n/en/newCourse.json +51 -0
- package/studio/src/lib/i18n/en/onboarding.json +35 -0
- package/studio/src/lib/i18n/en/playwrightTemplate.json +20 -0
- package/studio/src/lib/i18n/en/practice.json +79 -0
- package/studio/src/lib/i18n/en/provider.json +27 -0
- package/studio/src/lib/i18n/en/providerMeta.json +26 -0
- package/studio/src/lib/i18n/en/reflection.json +17 -0
- package/studio/src/lib/i18n/en/revision.json +52 -0
- package/studio/src/lib/i18n/en/runnerNotice.json +14 -0
- package/studio/src/lib/i18n/en/settings.json +12 -0
- package/studio/src/lib/i18n/en/storage.json +49 -0
- package/studio/src/lib/i18n/en/tutor.json +39 -0
- package/studio/src/lib/i18n/en/workspace.json +50 -0
- package/studio/src/lib/i18n/index.js +144 -0
- package/studio/src/lib/i18n/languages.js +93 -0
- package/studio/src/lib/i18n/vi/app.json +23 -0
- package/studio/src/lib/i18n/vi/betaReport.json +55 -0
- package/studio/src/lib/i18n/vi/capstone.json +57 -0
- package/studio/src/lib/i18n/vi/common.json +25 -0
- package/studio/src/lib/i18n/vi/course.json +27 -0
- package/studio/src/lib/i18n/vi/data.json +35 -0
- package/studio/src/lib/i18n/vi/durability.json +5 -0
- package/studio/src/lib/i18n/vi/errors.json +20 -0
- package/studio/src/lib/i18n/vi/exercises.json +55 -0
- package/studio/src/lib/i18n/vi/externalEvidence.json +76 -0
- package/studio/src/lib/i18n/vi/home.json +18 -0
- package/studio/src/lib/i18n/vi/lesson.json +33 -0
- package/studio/src/lib/i18n/vi/nav.json +8 -0
- package/studio/src/lib/i18n/vi/newCourse.json +51 -0
- package/studio/src/lib/i18n/vi/onboarding.json +35 -0
- package/studio/src/lib/i18n/vi/playwrightTemplate.json +20 -0
- package/studio/src/lib/i18n/vi/practice.json +79 -0
- package/studio/src/lib/i18n/vi/provider.json +27 -0
- package/studio/src/lib/i18n/vi/providerMeta.json +26 -0
- package/studio/src/lib/i18n/vi/reflection.json +17 -0
- package/studio/src/lib/i18n/vi/revision.json +52 -0
- package/studio/src/lib/i18n/vi/runnerNotice.json +14 -0
- package/studio/src/lib/i18n/vi/settings.json +12 -0
- package/studio/src/lib/i18n/vi/storage.json +49 -0
- package/studio/src/lib/i18n/vi/tutor.json +39 -0
- package/studio/src/lib/i18n/vi/workspace.json +50 -0
- package/studio/src/lib/ids.js +10 -0
- package/studio/src/lib/json.js +26 -0
- package/studio/src/lib/markdown.js +74 -0
- package/studio/src/lib/prompts/base.js +139 -0
- package/studio/src/lib/prompts/capstone.js +777 -0
- package/studio/src/lib/prompts/course.js +150 -0
- package/studio/src/lib/prompts/lesson.js +275 -0
- package/studio/src/lib/prompts/practice.js +511 -0
- package/studio/src/lib/prompts/tutor.js +509 -0
- package/studio/src/lib/repo/blocked-adapter.js +69 -0
- package/studio/src/lib/repo/events.js +239 -0
- package/studio/src/lib/repo/idb-adapter.js +226 -0
- package/studio/src/lib/repo/index.js +1315 -0
- package/studio/src/lib/repo/memory-adapter.js +115 -0
- package/studio/src/lib/repo/remote-adapter.js +158 -0
- package/studio/src/lib/repo/schema.js +143 -0
- package/studio/src/lib/repo/select-adapter.js +345 -0
- package/studio/src/lib/router.js +71 -0
- package/studio/src/lib/runner/browser.js +244 -0
- package/studio/src/lib/runner/checks.js +248 -0
- package/studio/src/lib/runner/current-code.js +98 -0
- package/studio/src/lib/runner/esm-cjs.js +109 -0
- package/studio/src/lib/runner/hash.js +34 -0
- package/studio/src/lib/runner/history.js +48 -0
- package/studio/src/lib/runner/index.js +168 -0
- package/studio/src/lib/runner/languages.js +258 -0
- package/studio/src/lib/runner/local.js +139 -0
- package/studio/src/lib/runner/path.js +104 -0
- package/studio/src/lib/runner/skeleton.js +315 -0
- package/studio/src/lib/runner/strip-types.js +134 -0
- package/studio/src/lib/settings.js +399 -0
- package/studio/src/lib/srs.js +39 -0
- package/templates/agent/settings.json +6 -0
- package/templates/claude/plugin.json +139 -0
- package/templates/claude/settings.json +15 -0
- package/templates/cursor/professor.mdc +4 -0
- package/templates/cursor/settings.json +18 -0
- package/templates/gemini/GEMINI.md +28 -0
- package/templates/gemini/settings.json +5 -0
- package/templates/opencode/opencode.json +3 -0
- package/templates/researcher/README.md +61 -0
- package/templates/researcher/settings.json +18 -0
- package/web/client/components.json +15 -0
- package/web/client/index.html +12 -0
- package/web/client/package-lock.json +8732 -0
- package/web/client/package.json +39 -0
- package/web/client/postcss.config.js +6 -0
- package/web/client/src/App.jsx +140 -0
- package/web/client/src/api/client.js +357 -0
- package/web/client/src/components/ChatPanel.jsx +417 -0
- package/web/client/src/components/CommandPills.jsx +50 -0
- package/web/client/src/components/DeprecationBanner.jsx +21 -0
- package/web/client/src/components/ExerciseEditor.jsx +164 -0
- package/web/client/src/components/Header.jsx +119 -0
- package/web/client/src/components/LecturePanel.jsx +342 -0
- package/web/client/src/components/MobileTabSwitcher.jsx +51 -0
- package/web/client/src/components/SplitPane.jsx +42 -0
- package/web/client/src/components/ui/alert.jsx +38 -0
- package/web/client/src/components/ui/badge.jsx +24 -0
- package/web/client/src/components/ui/button.jsx +23 -0
- package/web/client/src/components/ui/select.jsx +74 -0
- package/web/client/src/hooks/useSSE.js +152 -0
- package/web/client/src/hooks/useWebSocket.js +98 -0
- package/web/client/src/index.css +23 -0
- package/web/client/src/lib/utils.js +6 -0
- package/web/client/src/main.jsx +12 -0
- package/web/client/src/utils/markdown.js +50 -0
- package/web/client/tailwind.config.js +8 -0
- package/web/client/vite.config.js +21 -0
- package/web/dist/assets/index-C1W0eXIQ.js +294 -0
- package/web/dist/assets/index-C3OUx70T.css +1 -0
- package/web/dist/index.html +13 -0
- package/web/lecture-archive.js +172 -0
- package/web/legacy-server.js +1277 -0
- package/web/package-lock.json +855 -0
- package/web/package.json +10 -0
- package/web/security.js +1016 -0
- package/web/server.js +390 -0
- package/web/studio-api/agents.js +580 -0
- package/web/studio-api/bridge.js +199 -0
- package/web/studio-api/db.js +66 -0
- package/web/studio-api/guards.js +201 -0
- package/web/studio-api/health.js +538 -0
- package/web/studio-api.js +214 -0
- package/web/studio-db.js +380 -0
- package/web/studio-runner.js +568 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure event vocabulary and row builders (D-01/D-02/D-03/D-05).
|
|
3
|
+
*
|
|
4
|
+
* No adapter, repo or I/O lives here: the repository validates through these helpers and the
|
|
5
|
+
* adapter only ever sees plain columns. Keeping the enum and the meta rules pure makes the
|
|
6
|
+
* frozen Wave 2 contract testable without storage.
|
|
7
|
+
*/
|
|
8
|
+
import { dayKey } from '../course.js';
|
|
9
|
+
|
|
10
|
+
/** Closed meaningful-event enum, in the frozen D-01 order. */
|
|
11
|
+
export const EVENT_KINDS = Object.freeze([
|
|
12
|
+
'quiz_completed',
|
|
13
|
+
'recall_completed',
|
|
14
|
+
'answer_sent_for_review',
|
|
15
|
+
'workspace_run',
|
|
16
|
+
'exercise_completed',
|
|
17
|
+
'capstone_checkpoint',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
/** Grouping surfaces (D-03). `import` is reserved for non-meaningful import bookkeeping. */
|
|
21
|
+
export const EVENT_SURFACES = Object.freeze(['quiz', 'recall', 'tutor', 'workspace', 'capstone', 'import']);
|
|
22
|
+
|
|
23
|
+
/** The one legal surface per meaningful kind. */
|
|
24
|
+
export const KIND_SURFACE = Object.freeze({
|
|
25
|
+
quiz_completed: 'quiz',
|
|
26
|
+
recall_completed: 'recall',
|
|
27
|
+
answer_sent_for_review: 'tutor',
|
|
28
|
+
workspace_run: 'workspace',
|
|
29
|
+
exercise_completed: 'workspace',
|
|
30
|
+
capstone_checkpoint: 'capstone',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/** Closed allowlist of tables a revision may reference (D-06, T-05-08). */
|
|
34
|
+
export const REVISION_FEEDBACK_TABLES = Object.freeze([
|
|
35
|
+
'chat_messages',
|
|
36
|
+
'run_results',
|
|
37
|
+
'capstone_submissions',
|
|
38
|
+
'quiz_attempts',
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
/** Frozen revision link kinds (D-06). */
|
|
42
|
+
export const REVISION_LINK_KINDS = Object.freeze(['explicit', 'inferred']);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} value
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
export function isRevisionLinkKind(value) {
|
|
49
|
+
return REVISION_LINK_KINDS.includes(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Throw before any adapter call when a caller-supplied table name is outside the allowlist.
|
|
54
|
+
* @param {string} table
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
export function validateRevisionTable(table) {
|
|
58
|
+
if (!REVISION_FEEDBACK_TABLES.includes(table)) {
|
|
59
|
+
throw new Error(`Unknown revision table: ${table}`);
|
|
60
|
+
}
|
|
61
|
+
return table;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the exact `revision_links` row columns (D-06).
|
|
66
|
+
* @param {{ surface: string, courseId?: string|null, lessonId?: string|null, attemptTable?: string|null, attemptId?: string|null, feedbackTable: string, feedbackId: string, revisedTable: string, revisedId: string, linkKind: string }} input
|
|
67
|
+
* @returns {object}
|
|
68
|
+
*/
|
|
69
|
+
export function buildRevisionLink({
|
|
70
|
+
surface,
|
|
71
|
+
courseId = null,
|
|
72
|
+
lessonId = null,
|
|
73
|
+
attemptTable = null,
|
|
74
|
+
attemptId = null,
|
|
75
|
+
feedbackTable,
|
|
76
|
+
feedbackId,
|
|
77
|
+
revisedTable,
|
|
78
|
+
revisedId,
|
|
79
|
+
linkKind,
|
|
80
|
+
} = {}) {
|
|
81
|
+
if (!EVENT_SURFACES.includes(surface)) throw new Error(`Unknown revision surface: ${surface}`);
|
|
82
|
+
if (!isRevisionLinkKind(linkKind)) throw new Error(`Unknown revision link kind: ${linkKind}`);
|
|
83
|
+
validateRevisionTable(feedbackTable);
|
|
84
|
+
validateRevisionTable(revisedTable);
|
|
85
|
+
if (attemptTable != null) validateRevisionTable(attemptTable);
|
|
86
|
+
return {
|
|
87
|
+
surface,
|
|
88
|
+
course_id: courseId ?? null,
|
|
89
|
+
lesson_id: lessonId ?? null,
|
|
90
|
+
attempt_table: attemptTable ?? null,
|
|
91
|
+
attempt_id: attemptId ?? null,
|
|
92
|
+
feedback_table: feedbackTable,
|
|
93
|
+
feedback_id: feedbackId,
|
|
94
|
+
revised_table: revisedTable,
|
|
95
|
+
revised_id: revisedId,
|
|
96
|
+
link_kind: linkKind,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} kind
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
export function validateEventKind(kind) {
|
|
105
|
+
if (!EVENT_KINDS.includes(kind)) throw new Error(`Unknown event kind: ${kind}`);
|
|
106
|
+
return kind;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @param {string} kind
|
|
111
|
+
* @param {string} surface
|
|
112
|
+
* @returns {string}
|
|
113
|
+
*/
|
|
114
|
+
export function validateEventSurface(kind, surface) {
|
|
115
|
+
validateEventKind(kind);
|
|
116
|
+
if (!EVENT_SURFACES.includes(surface)) throw new Error(`Unknown event surface: ${surface}`);
|
|
117
|
+
if (surface !== KIND_SURFACE[kind]) {
|
|
118
|
+
throw new Error(`Event kind "${kind}" must use surface "${KIND_SURFACE[kind]}"`);
|
|
119
|
+
}
|
|
120
|
+
return surface;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Unwrap the inner checks array. `CodeWorkspace` always sends `{ checks, snapshot }`, while CLI
|
|
125
|
+
* imports and legacy rows may send a bare array (P1).
|
|
126
|
+
* @param {any} checks
|
|
127
|
+
* @returns {any[]}
|
|
128
|
+
*/
|
|
129
|
+
export function checksList(checks) {
|
|
130
|
+
if (Array.isArray(checks)) return checks;
|
|
131
|
+
if (checks && Array.isArray(checks.checks)) return checks.checks;
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const NUMBER = Object.freeze({ type: 'number' });
|
|
136
|
+
|
|
137
|
+
function enumSpec(values) {
|
|
138
|
+
return Object.freeze({ type: 'enum', values: Object.freeze(values) });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Allowed meta keys and value shapes per kind (D-05). */
|
|
142
|
+
export const META_WHITELIST = Object.freeze({
|
|
143
|
+
quiz_completed: Object.freeze({ total: NUMBER, score: NUMBER, answered: NUMBER }),
|
|
144
|
+
recall_completed: Object.freeze({ grade: Object.freeze({ type: 'number', min: 0, max: 3 }) }),
|
|
145
|
+
answer_sent_for_review: Object.freeze({ mode: enumSpec(['check_answer', 'review']) }),
|
|
146
|
+
workspace_run: Object.freeze({
|
|
147
|
+
runner: enumSpec(['browser', 'local']),
|
|
148
|
+
checks_total: NUMBER,
|
|
149
|
+
exit_code: NUMBER,
|
|
150
|
+
}),
|
|
151
|
+
exercise_completed: Object.freeze({
|
|
152
|
+
runner: enumSpec(['browser', 'local']),
|
|
153
|
+
checks_total: NUMBER,
|
|
154
|
+
checks_passed: NUMBER,
|
|
155
|
+
}),
|
|
156
|
+
capstone_checkpoint: Object.freeze({
|
|
157
|
+
source: enumSpec(['in_app', 'external']),
|
|
158
|
+
attempt: NUMBER,
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const META_MAX_LENGTH = 512;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Drop null/undefined keys so optional scores and exit codes never reach validation.
|
|
166
|
+
* @param {Record<string, any>|null|undefined} meta
|
|
167
|
+
* @returns {Record<string, any>}
|
|
168
|
+
*/
|
|
169
|
+
export function compactMeta(meta = {}) {
|
|
170
|
+
const out = {};
|
|
171
|
+
for (const [key, value] of Object.entries(meta || {})) {
|
|
172
|
+
if (value !== null && value !== undefined) out[key] = value;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Reject any meta that could carry learner content: unknown keys, non-finite numbers, arrays,
|
|
179
|
+
* free strings, booleans and anything over the 512-character serialized cap (D-05/P8).
|
|
180
|
+
* @param {string} kind
|
|
181
|
+
* @param {Record<string, any>} meta already compacted by `compactMeta`
|
|
182
|
+
* @returns {Record<string, any>}
|
|
183
|
+
*/
|
|
184
|
+
export function validateMeta(kind, meta = {}) {
|
|
185
|
+
validateEventKind(kind);
|
|
186
|
+
const whitelist = META_WHITELIST[kind];
|
|
187
|
+
const serialized = JSON.stringify(meta ?? {});
|
|
188
|
+
if (serialized.length > META_MAX_LENGTH) {
|
|
189
|
+
throw new Error(`Event meta exceeds ${META_MAX_LENGTH} characters`);
|
|
190
|
+
}
|
|
191
|
+
for (const [key, value] of Object.entries(meta || {})) {
|
|
192
|
+
if (!Object.prototype.hasOwnProperty.call(whitelist, key)) {
|
|
193
|
+
throw new Error(`Unknown meta key "${key}" for event kind "${kind}"`);
|
|
194
|
+
}
|
|
195
|
+
const spec = whitelist[key];
|
|
196
|
+
if (Array.isArray(value)) throw new Error(`Meta "${key}" must not be an array`);
|
|
197
|
+
if (spec.type === 'number') {
|
|
198
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
199
|
+
throw new Error(`Meta "${key}" must be a finite number`);
|
|
200
|
+
}
|
|
201
|
+
if (spec.min !== undefined && value < spec.min) throw new Error(`Meta "${key}" is below ${spec.min}`);
|
|
202
|
+
if (spec.max !== undefined && value > spec.max) throw new Error(`Meta "${key}" is above ${spec.max}`);
|
|
203
|
+
} else if (spec.type === 'enum') {
|
|
204
|
+
if (typeof value !== 'string' || !spec.values.includes(value)) {
|
|
205
|
+
throw new Error(`Meta "${key}" must be one of: ${spec.values.join(', ')}`);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
throw new Error(`Meta "${key}" has an unsupported type`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return meta;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {{ kind: string, surface: string, courseId?: string|null, lessonId?: string|null, refId?: string|null, meta?: object, now?: Date|string }} input
|
|
216
|
+
* @returns {{ day: string, kind: string, surface: string, course_id: string|null, lesson_id: string|null, ref_id: string|null, meta_json: string }}
|
|
217
|
+
*/
|
|
218
|
+
export function buildEventRow({
|
|
219
|
+
kind,
|
|
220
|
+
surface,
|
|
221
|
+
courseId = null,
|
|
222
|
+
lessonId = null,
|
|
223
|
+
refId = null,
|
|
224
|
+
meta = {},
|
|
225
|
+
now = new Date(),
|
|
226
|
+
} = {}) {
|
|
227
|
+
validateEventSurface(kind, surface);
|
|
228
|
+
const compact = compactMeta(meta);
|
|
229
|
+
validateMeta(kind, compact);
|
|
230
|
+
return {
|
|
231
|
+
day: dayKey(now),
|
|
232
|
+
kind,
|
|
233
|
+
surface,
|
|
234
|
+
course_id: courseId ?? null,
|
|
235
|
+
lesson_id: lessonId ?? null,
|
|
236
|
+
ref_id: refId ?? null,
|
|
237
|
+
meta_json: JSON.stringify(compact),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB StorageAdapter.
|
|
3
|
+
*
|
|
4
|
+
* One object store per table in schema.js, one index per indexed field. All filtering is done
|
|
5
|
+
* in JS after a getAll so the semantics are byte-for-byte identical to the memory and (future)
|
|
6
|
+
* remote adapters — the indexes exist for the SQLite/D1 side and for future query optimisation.
|
|
7
|
+
*
|
|
8
|
+
* When IndexedDB is unavailable (private mode, locked-down webview, vitest without
|
|
9
|
+
* fake-indexeddb) every method transparently delegates to the in-memory adapter and
|
|
10
|
+
* `info().kind` reports 'memory'.
|
|
11
|
+
*/
|
|
12
|
+
import { TABLE_NAMES, SCHEMA_VERSION, tableSpec, assertIndexedField } from './schema.js';
|
|
13
|
+
import { createMemoryAdapter, validateDump, APP_ID } from './memory-adapter.js';
|
|
14
|
+
|
|
15
|
+
const DB_NAME = 'professor-studio';
|
|
16
|
+
/** The IndexedDB database name (DELETE it to remove the browser copy — STORE-03). */
|
|
17
|
+
export const IDB_DB_NAME = DB_NAME;
|
|
18
|
+
// v1 = the seed db.js (courses/cards/meta); v2 = the normalized schema; v3 adds the CLI-import
|
|
19
|
+
// `external_id` indexes; v4 adds the capstone tables (Phase 33); v5 adds the beta-loop tables
|
|
20
|
+
// `learning_events`, `revision_links` and `beta_profile` (Phase 5); v6 adds the interactive-exercise
|
|
21
|
+
// tables `exercises` and `exercise_attempts`. Bumping the version is what makes `onupgradeneeded`
|
|
22
|
+
// run for browsers that already have an older database — otherwise the new stores/indexes would
|
|
23
|
+
// never be created and `exportAll`/`list` would throw `NotFoundError`.
|
|
24
|
+
export const DB_VERSION = 6;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Bring an open (upgrading) database in line with schema.js: create missing stores AND add any
|
|
28
|
+
* missing index to stores that already exist. Runs inside the `onupgradeneeded` versionchange
|
|
29
|
+
* transaction, so it must handle both fresh databases and upgrades from any older version.
|
|
30
|
+
*/
|
|
31
|
+
function createStores(db, tx) {
|
|
32
|
+
for (const table of TABLE_NAMES) {
|
|
33
|
+
const spec = tableSpec(table);
|
|
34
|
+
let os;
|
|
35
|
+
if (!db.objectStoreNames.contains(table)) {
|
|
36
|
+
os = db.createObjectStore(table, { keyPath: spec.id });
|
|
37
|
+
} else {
|
|
38
|
+
// Existing store: keep it and its rows, only backfill indexes added since it was created.
|
|
39
|
+
os = tx.objectStore(table);
|
|
40
|
+
}
|
|
41
|
+
for (const field of spec.indexes) {
|
|
42
|
+
if (!os.indexNames.contains(field)) os.createIndex(field, field, { unique: false });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (db.objectStoreNames.contains('meta')) db.deleteObjectStore('meta'); // legacy seed store
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function openDb(dbName) {
|
|
49
|
+
if (typeof indexedDB === 'undefined') return Promise.resolve(null);
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
let req;
|
|
52
|
+
try {
|
|
53
|
+
req = indexedDB.open(dbName, DB_VERSION);
|
|
54
|
+
} catch {
|
|
55
|
+
resolve(null);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
req.onupgradeneeded = () => {
|
|
59
|
+
try {
|
|
60
|
+
createStores(req.result, req.transaction);
|
|
61
|
+
} catch {
|
|
62
|
+
// migration failure aborts the open; onerror resolves null and we fall back to memory
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
req.onsuccess = () => {
|
|
66
|
+
const db = req.result;
|
|
67
|
+
db.onversionchange = () => db.close();
|
|
68
|
+
resolve(db);
|
|
69
|
+
};
|
|
70
|
+
req.onerror = () => resolve(null);
|
|
71
|
+
req.onblocked = () => resolve(null);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function requestToPromise(req) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
req.onsuccess = () => resolve(req.result);
|
|
78
|
+
req.onerror = () => reject(req.error);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function txDone(tx) {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
tx.oncomplete = () => resolve();
|
|
85
|
+
tx.onerror = () => reject(tx.error);
|
|
86
|
+
tx.onabort = () => reject(tx.error);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {{ dbName?: string }} [opts]
|
|
92
|
+
* @returns {import('./remote-adapter.js').StorageAdapter & { label: string }}
|
|
93
|
+
*/
|
|
94
|
+
export function createIdbAdapter({ dbName = DB_NAME } = {}) {
|
|
95
|
+
const fallback = createMemoryAdapter();
|
|
96
|
+
let persistent = false;
|
|
97
|
+
let persistRequested = false;
|
|
98
|
+
|
|
99
|
+
const ready = openDb(dbName).then(async (db) => {
|
|
100
|
+
if (!db) return null;
|
|
101
|
+
try {
|
|
102
|
+
const storage = globalThis.navigator?.storage;
|
|
103
|
+
if (storage && !persistRequested) {
|
|
104
|
+
persistRequested = true;
|
|
105
|
+
persistent = (await storage.persist?.()) ?? (await storage.persisted?.()) ?? false;
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
persistent = false;
|
|
109
|
+
}
|
|
110
|
+
return db;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
async function getAll(table) {
|
|
114
|
+
const db = await ready;
|
|
115
|
+
if (!db) return fallback.list(table, { includeDeleted: true });
|
|
116
|
+
const tx = db.transaction(table, 'readonly');
|
|
117
|
+
const rows = (await requestToPromise(tx.objectStore(table).getAll())) || [];
|
|
118
|
+
await txDone(tx);
|
|
119
|
+
return rows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
label: 'indexeddb',
|
|
124
|
+
async get(table, id) {
|
|
125
|
+
const db = await ready;
|
|
126
|
+
if (!db) return fallback.get(table, id);
|
|
127
|
+
const tx = db.transaction(table, 'readonly');
|
|
128
|
+
const row = await requestToPromise(tx.objectStore(table).get(id));
|
|
129
|
+
await txDone(tx);
|
|
130
|
+
return row ?? null;
|
|
131
|
+
},
|
|
132
|
+
async put(table, row) {
|
|
133
|
+
const db = await ready;
|
|
134
|
+
if (!db) return fallback.put(table, row);
|
|
135
|
+
if (row?.[tableSpec(table).id] === undefined) throw new Error(`Row for "${table}" is missing its id`);
|
|
136
|
+
const tx = db.transaction(table, 'readwrite');
|
|
137
|
+
tx.objectStore(table).put(row);
|
|
138
|
+
await txDone(tx);
|
|
139
|
+
return { ...row };
|
|
140
|
+
},
|
|
141
|
+
async putMany(table, rows) {
|
|
142
|
+
const db = await ready;
|
|
143
|
+
if (!db) return fallback.putMany(table, rows);
|
|
144
|
+
const tx = db.transaction(table, 'readwrite');
|
|
145
|
+
const os = tx.objectStore(table);
|
|
146
|
+
for (const row of rows || []) os.put(row);
|
|
147
|
+
await txDone(tx);
|
|
148
|
+
return (rows || []).map((r) => ({ ...r }));
|
|
149
|
+
},
|
|
150
|
+
async list(table, opts = {}) {
|
|
151
|
+
if (opts.where) for (const f of Object.keys(opts.where)) assertIndexedField(table, f);
|
|
152
|
+
const rows = await getAll(table);
|
|
153
|
+
return filterRows(rows, opts);
|
|
154
|
+
},
|
|
155
|
+
async softDelete(table, id) {
|
|
156
|
+
const db = await ready;
|
|
157
|
+
if (!db) return fallback.softDelete(table, id);
|
|
158
|
+
const tx = db.transaction(table, 'readwrite');
|
|
159
|
+
const os = tx.objectStore(table);
|
|
160
|
+
const row = await requestToPromise(os.get(id));
|
|
161
|
+
if (!row) {
|
|
162
|
+
await txDone(tx);
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const next = { ...row, deleted_at: new Date().toISOString() };
|
|
166
|
+
os.put(next);
|
|
167
|
+
await txDone(tx);
|
|
168
|
+
return next;
|
|
169
|
+
},
|
|
170
|
+
async exportAll() {
|
|
171
|
+
const tables = {};
|
|
172
|
+
for (const table of TABLE_NAMES) tables[table] = await getAll(table);
|
|
173
|
+
return {
|
|
174
|
+
app: APP_ID,
|
|
175
|
+
version: 1,
|
|
176
|
+
schemaVersion: SCHEMA_VERSION,
|
|
177
|
+
exportedAt: new Date().toISOString(),
|
|
178
|
+
tables,
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
async importAll(dump) {
|
|
182
|
+
validateDump(dump);
|
|
183
|
+
const db = await ready;
|
|
184
|
+
if (!db) return fallback.importAll(dump);
|
|
185
|
+
let imported = 0;
|
|
186
|
+
for (const table of TABLE_NAMES) {
|
|
187
|
+
const rows = dump.tables[table] || [];
|
|
188
|
+
if (!rows.length) continue;
|
|
189
|
+
const tx = db.transaction(table, 'readwrite');
|
|
190
|
+
const os = tx.objectStore(table);
|
|
191
|
+
for (const row of rows) os.put(row);
|
|
192
|
+
await txDone(tx);
|
|
193
|
+
imported += rows.length;
|
|
194
|
+
}
|
|
195
|
+
return { imported };
|
|
196
|
+
},
|
|
197
|
+
async info() {
|
|
198
|
+
const db = await ready;
|
|
199
|
+
return db ? { kind: 'indexeddb', persistent } : { kind: 'memory', persistent: false };
|
|
200
|
+
},
|
|
201
|
+
async clearAll() {
|
|
202
|
+
const db = await ready;
|
|
203
|
+
if (!db) return fallback.clearAll();
|
|
204
|
+
for (const table of TABLE_NAMES) {
|
|
205
|
+
const tx = db.transaction(table, 'readwrite');
|
|
206
|
+
tx.objectStore(table).clear();
|
|
207
|
+
await txDone(tx);
|
|
208
|
+
}
|
|
209
|
+
return { cleared: true };
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Shared list filtering — identical across adapters. */
|
|
215
|
+
export function filterRows(rows, { where, includeDeleted = false, orderBy, limit } = {}) {
|
|
216
|
+
let out = rows || [];
|
|
217
|
+
if (where) for (const key of Object.keys(where)) out = out.filter((r) => r[key] === where[key]);
|
|
218
|
+
if (!includeDeleted) out = out.filter((r) => !r.deleted_at);
|
|
219
|
+
if (orderBy) {
|
|
220
|
+
const field = typeof orderBy === 'string' ? orderBy : orderBy.field;
|
|
221
|
+
const dir = typeof orderBy === 'string' ? 1 : orderBy.dir === 'desc' ? -1 : 1;
|
|
222
|
+
out = [...out].sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1) * dir));
|
|
223
|
+
}
|
|
224
|
+
if (typeof limit === 'number') out = out.slice(0, limit);
|
|
225
|
+
return out.map((r) => ({ ...r }));
|
|
226
|
+
}
|