docs: WP-A learner package plan

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 03:15:48 +01:00
co-authored by Claude Opus 5.5
parent 3ca05fcfbc
commit 0b117159cd
3 changed files with 269 additions and 1 deletions
+125
View File
@@ -57,6 +57,13 @@
"packages/feature-learner": {
"name": "@stc/feature-learner",
"version": "0.0.0",
"dependencies": {
"@better-auth/sso": "^1.7.14",
"better-auth": "^1.7.14",
"dompurify": "^3.4.17",
"marked": "^18.1.2",
"pdfjs-dist": "^6.4.299",
},
},
"packages/feature-studio": {
"name": "@stc/feature-studio",
@@ -68,14 +75,68 @@
},
},
"packages": {
"@authenio/xml-encryption": ["@authenio/[email protected]", "", { "dependencies": { "@xmldom/xmldom": "^0.8.13", "escape-html": "^1.0.3" } }, "sha512-/jv5qO935fH4Ma+fxh5rJLRYZvUM57fd/I74IiSoR8cFm+sgjszXFEEn1PkqZhhBD4MVkH2YT+/UDY+pnW0S0Q=="],
"@better-auth/core": ["@better-auth/[email protected]", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/utils": "^0.4.2 || ^0.5.0", "@better-fetch/fetch": "1.3.3", "@opentelemetry/api": "^1.9.0", "better-call": "1.4.4", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-ByhdrGVwp/el+hoTYKSVOHIH23/lQj9TQ1txdGcWOg22tnfr3hJ6ukzySooeYj/R36XsQgBhkJqIp8mKsNeErA=="],
"@better-auth/drizzle-adapter": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0" }, "optionalPeers": ["drizzle-orm"] }, "sha512-5RC8Ec5Ur15FWsS4As9/VrlRbB3+9EfShxIpA5A1FcD6IgPxFvzQNkAm2+FYx9LUSqx47NPf9hBTG2GUCMP9Ig=="],
"@better-auth/kysely-adapter": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-4wkqj8a8JVpWDYLlgzfbk7eOaWCFkL6HHQd6CLamFKaC8vNsXew1x4v5dE5O6dGr8j4g5qMISS+BAvOuIvBevg=="],
"@better-auth/memory-adapter": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0" } }, "sha512-Ojv9BcmFyHuTHha9ZM/NUW1ut9J84wLngta+07Es0Ejn8kekqP5aHBpZd5Z8AvADs5iJUOWvQGFmGlPWAKJgBg=="],
"@better-auth/mongo-adapter": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-6IA0RSPZFMdRwwnLZ9oWnDIiD0y0o0azTHUfa01BNU2AFJgMDkeiASUV3bKcJBskzQoyQrff74eFYTsTSeeVUQ=="],
"@better-auth/prisma-adapter": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-mF6t5xqeZNWzUe0ErmrSdzQmtslJscmu9X8Mp5QmROlpnnAzvSc70RILu8lHms8CePt7N34+KRH1QvYjGFrVGQ=="],
"@better-auth/sso": ["@better-auth/[email protected]", "", { "dependencies": { "@xmldom/xmldom": "^0.9.12", "fast-xml-parser": "^5.8.0", "jose": "^6.2.3", "samlify": "^2.13.1", "tldts": "^7.4.3", "zod": "^4.5.4" }, "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "@better-fetch/fetch": "1.3.3", "better-auth": "^1.7.14", "better-call": "1.4.4" } }, "sha512-sfUzpm6GO5l5MSUA17qRO/E4INthqF+KRmymtj4Hhm+Xsx6CoRgr0BVIDfAic3tSqVhObcVYiiiZ38dXXhkCCw=="],
"@better-auth/telemetry": ["@better-auth/[email protected]", "", { "peerDependencies": { "@better-auth/core": "^1.7.14", "@better-auth/utils": "^0.4.2 || ^0.5.0", "@better-fetch/fetch": "1.3.3" } }, "sha512-PtKFzsH7gje/WJuUPDjckOoNFazsq8NMt3AdiXIPzHl7tZULcNGTKAX8K7+yn0rG6cO/4mtMgpOjhtilElOnmg=="],
"@better-auth/utils": ["@better-auth/[email protected]", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-WETqw5vQ52uQ/xwWR4E8rTgT6fDEr/wnS+otDNkBCa/69281FCF32OKDdQUcahMCnjsvqXBNiEvAQHFB0EVRRg=="],
"@better-fetch/fetch": ["@better-fetch/[email protected]", "", {}, "sha512-U6SlDt/GUWtK2JAxS/NhdoYTEim+EL1G5byz2TUj83KjLZBUpYzb5SmX0aXaCnbGkAjFKnV/o/0+cyXMbEcqqg=="],
"@fontsource/ibm-plex-mono": ["@fontsource/[email protected]", "", {}, "sha512-eTgnZjZEGk1QtD3ZstF+Vclo2HLAni8YMy34/DxllwZvyz1lR/1RF/xTiAquOBO7MvqBx8D2Ig2WCPMVfdZu7Q=="],
"@fontsource/ibm-plex-sans": ["@fontsource/[email protected]", "", {}, "sha512-CbE4CbbEEZJX860XyUiRpsksXIQR8Rp2XDva2VO53NJox9tVNtusrysd2x5YkUEY3ErQ66W1IiiQL8/wihhw5w=="],
"@fontsource/literata": ["@fontsource/[email protected]", "", {}, "sha512-9JGMT5+YfBK49thoMq9ASaMRJErHT4V4QuMhAEwKHRLreOsIMP2xP6ZRLhPJ0e6YlET+kjWTJuN7LAxt9/gWGQ=="],
"@napi-rs/canvas": ["@napi-rs/[email protected]", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "1.0.13", "@napi-rs/canvas-darwin-arm64": "1.0.13", "@napi-rs/canvas-darwin-x64": "1.0.13", "@napi-rs/canvas-linux-arm-gnueabihf": "1.0.13", "@napi-rs/canvas-linux-arm64-gnu": "1.0.13", "@napi-rs/canvas-linux-arm64-musl": "1.0.13", "@napi-rs/canvas-linux-riscv64-gnu": "1.0.13", "@napi-rs/canvas-linux-x64-gnu": "1.0.13", "@napi-rs/canvas-linux-x64-musl": "1.0.13", "@napi-rs/canvas-win32-arm64-msvc": "1.0.13", "@napi-rs/canvas-win32-x64-msvc": "1.0.13" } }, "sha512-b87ugZpQ5ViwqVBkjervxpwa0GmJPsKHLBNoZN6GpjWeHzTbDUGryriq+2DYW2XbB+xCqSIINQysC0/I87nPbw=="],
"@napi-rs/canvas-android-arm64": ["@napi-rs/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-trn7AaDzQ+yeeA0ezhkC7cB1ZDPtp4UYk0EvB5F8qtYL9jEVWtVrU30WOpI52DszoTxaTsCgf5PP4ZVaOlOIHQ=="],
"@napi-rs/canvas-darwin-arm64": ["@napi-rs/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6Y2djEetL8copxfNK6hg7oig/xqTwOchv19hBOm+zBNdKc/02Z3kjZN5wrLzv/l5O04/bAQS6I/AlQpwBlXgPg=="],
"@napi-rs/canvas-darwin-x64": ["@napi-rs/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-OZoPwwu4TMMg8GTCFBrg8UFExwQTU/55i3uTC/izDat6PHE+ro9dimyQoOboFf91+NY7Pnn70ScR8f8I8+eZXw=="],
"@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-dZSYaKROuswU2eUWG5aBKndNVe68elo1YX93xtRax7q658tj8rYE+HwduovWJgs6qwv1r1/CLR60nDeBbyjvyg=="],
"@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-W6FzYE8ADHI93In1vi80KBL6btDtAMuj50Ch5ptkWS8xuntyzbR4h5JDUmXoakxZn2yMInksudTEqVSWoFqFGg=="],
"@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-+6fQ+rR49hkAa4voh2stLIwQ+4t+gmoseM8Bc53/DlMXb73IKymDPWK9A/NtWeqTW4ijWppYuyuxS2hPI8tzNA=="],
"@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-17XEMO3t9RW/TtHtSFf2AkaSWuCWdzbPeidz7zrOFawHxoLSJCnpTI1hN5aUvrZNnkNNmt/vZVJCh0ryUaik3A=="],
"@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-lc48IwknVHBsNzXn9Xs5OkUsRX38Ml3Z1aOybQ51mMEzZckm8+4A0qROlyYHEoBPzr1kL3IU8ZUAqDb7wtdX8w=="],
"@napi-rs/canvas-linux-x64-musl": ["@napi-rs/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-bGY1r26S2bs8UNFJcPWcCSNdCFbKep5PATsvtgtC9A5u6SRgr2/jJE/RlhHL5m9ZpixNDhVkLY/5h16Kx5V9YA=="],
"@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-2vRPBZoaIvJrUkDhZTE2Hn0YrnpJRCWVM3EJACoBKjuWI6WgPxGtUoRMsxRviCsyks8bKTSFdyw14CellnG2qA=="],
"@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-DUlW3lZPz63kYsf8mxgyyZuevhlH0T3/BGP9l+NazhB0F1+gL6D4hqZdnhZpw24DlWmTpATDcyYSZlJOi+DdfQ=="],
"@noble/ciphers": ["@noble/[email protected]", "", {}, "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw=="],
"@noble/hashes": ["@noble/[email protected]", "", {}, "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA=="],
"@nodable/entities": ["@nodable/[email protected]", "", {}, "sha512-LsS/DjHr+uDM647Gru/cA8+J3a3HfhttwCKLyuoyN7yXTFCxKBtSgMQBvrW9yNPa2/zDRUNuGPaH5QUFoa4arQ=="],
"@remix-run/router": ["@remix-run/[email protected]", "", {}, "sha512-q7j5geK7xs3UJSdm9/iytUNclBnLmYx1EnSeCFXHPeutdqgIMeFeHtUZgS3EhlKxdBEAu8OwtJCwmLrEzpSs7Q=="],
"@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@stc/contracts": ["@stc/contracts@workspace:packages/contracts"],
"@stc/core": ["@stc/core@workspace:packages/core"],
@@ -104,10 +165,46 @@
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
"@types/trusted-types": ["@types/[email protected]", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@xmldom/is-dom-node": ["@xmldom/[email protected]", "", {}, "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q=="],
"@xmldom/xmldom": ["@xmldom/[email protected]", "", {}, "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A=="],
"anynum": ["[email protected]", "", {}, "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A=="],
"better-auth": ["[email protected]", "", { "dependencies": { "@better-auth/core": "1.7.14", "@better-auth/drizzle-adapter": "1.7.14", "@better-auth/kysely-adapter": "1.7.14", "@better-auth/memory-adapter": "1.7.14", "@better-auth/mongo-adapter": "1.7.14", "@better-auth/prisma-adapter": "1.7.14", "@better-auth/telemetry": "1.7.14", "@better-auth/utils": "0.5.1", "@better-fetch/fetch": "1.3.3", "@noble/ciphers": "^2.2.0", "@noble/hashes": "^2.2.0", "better-call": "1.4.4", "defu": "^6.1.4", "jose": "^6.2.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.3.0", "zod": "^4.5.4" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0 || ^3.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "drizzle-kit": ">=0.31.4 || >=1.0.0-beta.1", "drizzle-orm": "^0.45.2 || >=1.0.0-rc.1 <2.0.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-QxDCI0pOCCHDYmruisKhKyrvOJ6Icv9xg0RA6ZUysfKFXENYwhNNpwUlkE/NmViH2nDMgkKcymmLr55pnpR9vw=="],
"better-call": ["[email protected]", "", { "dependencies": { "@better-auth/utils": "^0.5.0", "@better-fetch/fetch": "^1.3.1", "rou3": "^0.9.1", "set-cookie-parser": "^3.1.2" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-Gq4XdGFy4dmUxFEAX2nj0ycFcFbD9ZWh8YROouZoyXeF1M11TpYbplk0pDByTFF8Wz+MYa6W9052c9m7dD+mfw=="],
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-yjrJh5oGX7mMPKGd19YXyIjmEuj4G4zNC30bs7PM9Az7NQ3ndF3j6hbeRdvJTRQEhJ/FYQwW0a2moweEi2hMxQ=="],
"csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"defu": ["[email protected]", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
"dompurify": ["[email protected]", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w=="],
"escape-html": ["[email protected]", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"fast-xml-builder": ["[email protected]", "", { "dependencies": { "path-expression-matcher": "^1.6.2", "xml-naming": "^0.3.0" } }, "sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug=="],
"fast-xml-parser": ["[email protected]", "", { "dependencies": { "@nodable/entities": "^3.0.1", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", "strnum": "^2.4.2", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-R9iDuNrQYeQut46cn2r2wHKn4HYzVDvDm5J1wW+koZewykv0yuO2HChTYeZtrULyHIDM9cj9TUXloCsueCQUog=="],
"is-unsafe": ["[email protected]", "", {}, "sha512-HgbIHPBH0KHHCcjLfGsCvhtPTVxjaAZlXjwdz7/GQC40SjSe4sfQsar8J5VFo8JOSbarkpV0OLG95bbaNd9aAQ=="],
"jose": ["[email protected]", "", {}, "sha512-3JasLuki1NRLQvY4wCoC37KZk4qxoBx2k2aFqLxT84IezqW0bkJKnNtxnZj/Fu1xsQivoc/lytxaRHJoGLjJ1A=="],
"kysely": ["[email protected]", "", {}, "sha512-fmIq+7kv4WbFm2tcfznRzikqB4cuNpgZ40PSU2fAJ2/OO5XhojqnTPrmgrCjYSRJVwBln03XcRXJGnw9H099IQ=="],
"marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-DfOnvLAQ6WYeWxWO1u4RyFOeqyHup6dks5PPqnoFiooU7ipRvidO/2j8plhAHIiZg574E9ZlzSjUiPMLAzFkgg=="],
"nanostores": ["[email protected]", "", {}, "sha512-FixtE239Gl6Gz0ZGCK0WzvNwObNCIwVT+L7zQcf1QgcA6AzBxblzqe9OT88qiucNkhsjSOqiogSaF5jVbJiqSA=="],
"path-expression-matcher": ["[email protected]", "", {}, "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ=="],
"pdfjs-dist": ["[email protected]", "", { "optionalDependencies": { "@napi-rs/canvas": "^1.0.10" } }, "sha512-AVl138zALtfaAPvADulE0PZThbYzCBS79nL4pOSL/6Sm/4AH5A21BD9VHt97OlCuzJuCpmeZtAtkinisF4Vb1g=="],
"react": ["[email protected]", "", {}, "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw=="],
"react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.1" } }, "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ=="],
@@ -116,14 +213,42 @@
"react-router-dom": ["[email protected]", "", { "dependencies": { "@remix-run/router": "1.23.4", "react-router": "6.30.6" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-0RHKZz7wwffvkU+2MFVT2NnjK44ssLEV+m0CAJaS2Ksmorrwj7WxH00jO0SOCW26/tINUnJHToXblDs33I38YQ=="],
"rou3": ["[email protected]", "", {}, "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ=="],
"samlify": ["[email protected]", "", { "dependencies": { "@authenio/xml-encryption": "^6.0.2", "@xmldom/xmldom": "^0.9.12", "xml": "^1.0.1", "xml-crypto": "^6.4.0", "xml-escape": "^1.1.0", "xpath": "^0.0.35" } }, "sha512-peZitMO2cAnf0pY45H0yw0Drqmby7U91yRovH3AH8TdwYCNRnmidVORXIjcK+21cBk3Mjl8EVz8Nvuw/v20rAw=="],
"scheduler": ["[email protected]", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
"set-cookie-parser": ["[email protected]", "", {}, "sha512-Npd1Pt+4zb3BrSZDPd2qFwRpS6b1EUEasRrO8xS0zMJi0fApXXK5YuzP0minyJnxwnFHoVoJwA55q0A0cu63gQ=="],
"strnum": ["[email protected]", "", { "dependencies": { "anynum": "^1.0.1" } }, "sha512-UM4nTLWzQiDBseVxUGxN0wJzIk0UU89Qi7pKXz9qISo+lhviJikTuikBsK/oFRlpvYHvSdz0TVoLyQ+sEpYwxA=="],
"tldts": ["[email protected]", "", { "dependencies": { "tldts-core": "^7.4.19" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-48dH6aSpQnpbzDUc4vIQZ1VjB8tkK+e8c1yx05/SZK+rkssgjEOlxDd/yU8/a4vGYQ9vRgJr9dwMq9TJ0b3X2Q=="],
"tldts-core": ["[email protected]", "", {}, "sha512-C630TWXZCZmLq0F2bW4QqwnaaxCQ/svpAqvJwnC4JvgEM5ohhBnGsEzTLSOXcN+xjz+kEaHS5uoWIw2klnqd0A=="],
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["[email protected]", "", {}, "sha512-EXE4ydL3S76dFb27UFjNVZHTyv/TLdATj/KUe29ptdcg6qXL48J2tZ3126E5kICj5jn781OR+XlVH1N6Mz8jbA=="],
"xml": ["[email protected]", "", {}, "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="],
"xml-crypto": ["[email protected]", "", { "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.15", "xpath": "^0.0.33" } }, "sha512-d21ModhBO/+QWWAmOjyDeQJuHNmsR/HtOxyBchiTbKfGNKuHYwQNd+SlLhwyyrwoAGLp4Qc6BQ7nAtnM47dHSQ=="],
"xml-escape": ["[email protected]", "", {}, "sha512-B/T4sDK8Z6aUh/qNr7mjKAwwncIljFuUP+DO/D5hloYFj+90O88z8Wf7oSucZTHxBAsC1/CTP4rtx/x1Uf72Mg=="],
"xml-naming": ["[email protected]", "", {}, "sha512-ghig2TBE/H11aOVgmahA3MhimvkBr6JIYknH/Dhdk10nXwdbIqBJsbfMxpvFPG8bAw77gN29aQWvKpmVoPlvPQ=="],
"xpath": ["[email protected]", "", {}, "sha512-8O+m2N04SwLpGw/1Grz2HD8ejnWDV7M1dMPgKKuOjhYh5W9H4pel/+06kInohIdpS2FeSbL4i8ID8c4tpxwq9Q=="],
"zod": ["[email protected]", "", {}, "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg=="],
"@authenio/xml-encryption/@xmldom/xmldom": ["@xmldom/[email protected]", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
"@types/node/undici-types": ["[email protected]", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
"xml-crypto/@xmldom/xmldom": ["@xmldom/[email protected]", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
"xml-crypto/xpath": ["[email protected]", "", {}, "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA=="],
}
}
@@ -0,0 +1,136 @@
# WP-A Learner App Implementation Plan
> **For agentic workers:** executed inline by the WP-A agent (no subagents), task by task, TDD, one commit per task. Steps use checkbox (`- [ ]`) syntax.
**Goal:** Build `packages/feature-learner`: sign-in (Better Auth), roles/groups admin API, the library, the lesson reader for every lesson type, notes, progress, gating, and the in-browser JS/TS exercise runner. It must render `sample`, `pure-snake` and `systemic-design`.
**Architecture:**
- Server side (`src/server/*`): `learnerFeature(ctx): Feature & { auth: AuthService }` returns the JSON API routes and the `AuthService`. It runs its own migrations (`migrations/0001_learner_auth.sql`) lazily on first use until Phase 2 wires `migrationsDirs` into `migrate()`.
- Auth: Better Auth with a **thin custom adapter over `Bun.SQL`** (`src/server/auth/adapter.ts`). One adapter serves both dialects because `@stc/db` already gives a `Bun.SQL` handle for SQLite and Postgres.
- Web side (`src/web/*`): React routes `/`, `/c/:courseId`, `/c/:courseId/:lessonId`, `/c/:courseId/notes`, `/account`, `/account/admin`. Styles are plain CSS loaded as text and injected once, so the same module graph is safe to import on the server.
- `LessonView` is a presentational lesson renderer that takes a `LessonPayload`. The studio uses it for preview.
- The browser runner (`src/web/runner/*`) runs learner code and tests as ES modules from blob URLs inside a module Web Worker, with a hard timeout (the worker is terminated). TS is transpiled by the server's `Bun.Transpiler` (`POST /api/learner/transpile`).
- Decks render client-side with pdfjs-dist. The worker script is served from `/api/learner/pdf.worker.mjs`.
**Tech Stack:** Bun 1.3, TypeScript, React 19, React Router 6, better-auth 1.7 (+ `@better-auth/sso` for SAML/OIDC SSO, `generic-oauth` for a configured OIDC provider), marked + DOMPurify, pdfjs-dist.
**Spec:** sections 4.1–4.4, 6, 7, 12, 13. Visual reference: `docs/design/mockups/platform-v1.html` minus its `[` / `]` / `/` / Ctrl+Enter shortcuts.
## Global Constraints
Inherited from `docs/superpowers/plans/2026-10-30-stcourses.md` (Bun only, plain CSS, no course content in the DB, both dialects, AGPL, tokens, keyboard rules, commit trailer). Package-specific:
- Owned paths: `packages/feature-learner/**`, `docs/superpowers/plans/2026-10-30-learner.md`, `docs/contract-requests/learner.md`. Shared touches: one line in `apps/web/src/features.ts`, the feature wiring in `apps/server/src/main.ts`.
- Frozen: `packages/contracts`, `packages/course-format`, `packages/db` (src + 0001), `packages/core`, `packages/ui`.
- No new colours: course accent (manifest) and the tokens only. Literata only for lesson titles and reading text.
## Review Focus
1. **Auth adapter correctness on both dialects** (where operators, AND/OR grouping, dates and booleans as text/int). Test: Task 2 runs the adapter suite under `forEachDb`.
2. **Owner bootstrap is race-safe**: exactly one owner even with concurrent sign-ups (unique key claim). Test: Task 3.
3. **Visibility**: lesson, outline, code and zip routes all apply `canView` and return 404 for hidden courses; gating returns `locked` without content. Test: Task 5/7.
4. **Zip download** handles spaces and non-ASCII names (UTF-8 flag) and never follows symlinks out of the root. Test: Task 7.
5. **Runner isolation**: learner code runs only inside the worker; an infinite loop is killed by the timeout. Test: Task 8.
---
### Task 1: Package scaffold, migrations runner, test helper
**Files:** `packages/feature-learner/{package.json,src/index.ts,src/server/migrate.ts,migrations/0001_learner_auth.sql,test/helpers.ts,test/migrate.test.ts}`
**Interfaces (Produces):**
- `migrationsDirs: string[]` (absolute path of `packages/feature-learner/migrations`).
- `runMigrations(db: Db, dirs?: string[]): Promise<string[]>` — same bookkeeping table and semantics as `@stc/db` `migrate()`.
- Tables: `auth_user`, `auth_session`, `auth_account`, `auth_verification`, `auth_sso_provider` (Better Auth, snake_case columns, ISO text dates, integer booleans), `learner_meta (key PK, value)`, `learner_group_claims (user_id, group_id)`.
- test helper `forEachDb(name, fn)` = `@stc/db`'s helper plus the learner migrations.
- [ ] Step 1: failing test: after `runMigrations` the tables exist on both dialects and a second run applies nothing.
- [ ] Step 2: implement; Step 3: green on SQLite and Postgres; commit.
### Task 2: Better Auth adapter over Bun.SQL
**Files:** `src/server/auth/adapter.ts`, `test/adapter.test.ts`
**Interfaces:** `bunSqlAdapter(db: Db)` → Better Auth `DBAdapterInstance`. Model `x` → table `auth_<snake(x)>`, field `camelCase` → `snake_case`. `supportsDates/Booleans/JSON: false`, no transactions (SQLite has one connection; a nested query in a transaction would deadlock).
- [ ] Failing tests (both dialects): create/findOne/findMany (eq, ne, in, not_in, contains, starts_with, ends_with, lt/gt, insensitive mode, OR grouping, sort, limit, offset), update, updateMany, delete, deleteMany, count, consumeOne, incrementOne.
- [ ] Implement, green, commit.
### Task 3: AuthService, owner bootstrap, claim groups, feature wiring
**Files:** `src/server/auth/index.ts`, `src/server/auth/groups.ts`, `src/server/feature.ts`, `test/auth.test.ts`, `apps/server/src/main.ts` (wiring), `docs/contract-requests/learner.md`
**Interfaces:**
- `createAuth(ctx, opts?)` → `{ handler(req), service: AuthService, providers(): ProviderInfo[] }`.
- Email/password always on; social providers (GitHub, Google, Microsoft, GitLab, Discord) from `STC_AUTH_<NAME>_ID/SECRET`; one generic OIDC provider from `STC_AUTH_OIDC_*`; SAML and per-domain OIDC through `@better-auth/sso` (owner-only registration).
- First user to sign up becomes `owner` (claimed via `INSERT` into `learner_meta` key `owner`, PK conflict loses); others get `learner`.
- `syncClaimGroups(db, userId, names)` creates groups by name and replaces the user's claim-sourced memberships; manual memberships are untouched.
- `learnerFeature(ctx)` returns `Feature & { auth }`; `startServer` accepts feature factories `(ctx) => Feature` and installs a feature's `auth` (contract request filed; two-line change in `main.ts`).
- [ ] Failing tests: sign-up → session cookie → `userFromRequest` returns roles `[owner]`; second user `[learner]`; concurrent first sign-ups give one owner; sign-out clears; claim sync keeps manual memberships.
- [ ] Implement, green, commit.
### Task 4: Admin API (roles, groups, course ACL, library curation)
**Files:** `src/server/admin.ts`, `test/admin.test.ts`
- Routes (owner only, 403 otherwise): `GET /api/admin/users`, `PUT /api/admin/users/:id/roles`, `GET|POST /api/admin/groups`, `DELETE /api/admin/groups/:id`, `PUT /api/admin/groups/:id/members`, `GET|PUT /api/admin/courses/:id/acl`, `GET|PUT /api/admin/library` (featured + collections).
- The last owner cannot drop their own owner role.
- [ ] Tests first; implement; commit.
### Task 5: Library, outline and lesson API
**Files:** `src/server/catalog.ts`, `src/server/lesson.ts`, `src/server/gating.ts`, `test/catalog.test.ts`, `test/lesson.test.ts`
- `GET /api/library` → `{ featured, continue[], newest[], collections[], courses[] }` filtered by `canView`. Each course: id, title, pitch, authors, coverUrl, accent, formats (`text|video|slides|exercises`), lessonCount, minutes (video duration, text estimated at 220 wpm, exercises 15 min), collections, progress, status.
- `GET /api/library/search?q=` → courses and lessons matching title/pitch/lesson titles.
- `GET /api/courses/:id` → outline, tracks, gating, `locked` per module, progress, diagnostics (errors only for owner/authors).
- `GET /api/courses/:id/lessons/:lessonId` → `LessonPayload` (markdown, hints, decks, video, transcript, code, attachments, browser exercise, local command, prev/next) or `{ locked: true }`.
- Gating: module *i* locked when any lesson of an earlier module is not done; owners and course authors bypass.
- [ ] Tests against the sample fixture plus temp courses; implement; commit.
### Task 6: Progress, notes, exercise attempts API
**Files:** `src/server/userdata.ts`, `test/userdata.test.ts`
- `PUT /api/courses/:id/progress/:lessonId` `{state, positionSeconds?}`; notes CRUD (`GET/POST /api/courses/:id/notes`, `PATCH/DELETE /api/notes/:id`); attempts `GET/PUT /api/courses/:id/lessons/:lessonId/attempt?track=`. A passing attempt marks the lesson done. Anonymous → 401. Hidden course → 404.
- [ ] Tests on both dialects (acceptance: notes persist on SQLite and Postgres); implement; commit.
### Task 7: Code files and zip download
**Files:** `src/server/zip.ts`, `src/server/code.ts`, `test/zip.test.ts`, `test/code.test.ts`
- `GET /api/courses/:id/code/:moduleId` → `{ dir, command, files: {path,size}[] }` (skips `.git`, `node_modules`, symlinks escaping root; caps 2000 files).
- `GET /api/courses/:id/code/:moduleId/zip` → `application/zip`, `Content-Disposition` via `contentDisposition("<dir name>.zip")`.
- `zip(entries)`: store/deflate, CRC32, UTF-8 name flag.
- [ ] Tests: zip round-trip via `unzip -l`, non-ASCII names, symlink escape skipped; commit.
### Task 8: Browser exercise runner
**Files:** `src/web/runner/{assert.ts,worker-source.ts,runner.ts}`, `src/server/transpile.ts`, `test/runner.test.ts`
- `createBrowserRunner(language, { transpile })`: `ExerciseRunner`. Worker disables `fetch`, `XMLHttpRequest`, `WebSocket`, `importScripts` before importing code. Results carry expected/actual for failed asserts.
- [ ] Tests (Bun supports module workers and blob URLs): starter fails, solution passes, a syntax error is reported, `while(true)` times out, TS is transpiled.
- [ ] commit.
### Task 9: Markdown pipeline
**Files:** `src/web/markdown.ts`, `test/markdown.test.ts`
- `renderLessonHtml(md, { courseId, baseFile, tracks, lessonFiles })` → sanitized HTML: heading ids, code blocks tagged with their track (`data-t`), bold track labels tagged, `<details>` turned into folds; consecutive hint folds become a tier group (`data-tier`, `data-tiers`), relative images/links rewritten to `/api/media/...` or lesson routes.
- [ ] Tests with Pure Snake-style input; commit.
### Task 10: Web shell and library
**Files:** `src/web/{routes.tsx,api.ts,styles.css,shell/*,library/*}`
- Header extras (course switcher, search, Reading/Work, account) portalled into the Phase 0 header; course outline sidebar (mobile overlay, Escape closes); library: featured hero, continue row, New row, collection rows of covers, format/length/in-progress filters, search.
- [ ] Component smoke tests with `react-dom/server` render; screenshot check later; commit.
### Task 11: Lesson views and `LessonView`
**Files:** `src/web/lesson/*`
- Text (track switcher, Reading/Work, folds, hint tiers), video (range-served `<video>`, resume, done near end, timestamp notes), deck viewer (pdfjs, thumbs, arrows, fullscreen, per-slide note), code files (tree, tabs, copy, zip), exercise page (editor, run, reset, per-test results, hints, local command; local-only: command + "Mark done" + optional pasted evidence), notes list, floating action bar.
- [ ] Render tests for `LessonView` with each lesson type; commit.
### Task 12: Account and admin pages
**Files:** `src/web/account/*`
- Sign in / create account (email+password), configured provider buttons, SSO by email, sign out; owner admin page for users/roles, groups, featured course and collections.
- [ ] Render tests; commit.
### Task 13: Wiring, verification, report
- [ ] Append `learnerWeb` to `apps/web/src/features.ts`; `learnerFeature` in `main.ts`.
- [ ] `bun test`, `bun run typecheck`, Postgres run; dev server on 3461 with `STC_COURSE_DIRS=./fixtures/courses:./overlays`; headless Chrome screenshots of library, text, video, deck, code, exercise, account for all three courses; fix what looks wrong.
- [ ] Write `/home/user/STCourses/.superpowers/sdd/2026-10-30-stcourses/wp-learner-report.md`.
+8 -1
View File
@@ -3,5 +3,12 @@
"version": "0.0.0",
"private": true,
"type": "module",
"license": "AGPL-3.0-only"
"license": "AGPL-3.0-only",
"dependencies": {
"@better-auth/sso": "^1.7.14",
"better-auth": "^1.7.14",
"dompurify": "^3.4.17",
"marked": "^18.1.2",
"pdfjs-dist": "^6.4.299"
}
}