Initial commit: Learning Lab Express

Includes fixes found while reviewing the app:
- Results: open each result by a type+agent+id key, so a video and a
  material draft with the same id no longer open each other's popup
- AuthGate: clear the query cache during render, before pages build their
  queries, and remount pages on organization switch (pages no longer stay
  on skeletons for up to a minute after sign-in or refresh)
- Instruction: keep only failed rules after a partial approve, so retrying
  does not create the successful schedules twice; drop the monthly note
  once count or frequency is edited
- Parser/schedules: never schedule a count below one
- Audio: ignore the end event of a cancelled utterance when changing speed
- Downloads: revoke the object URL later so Safari/Firefox can read it
- Results: refetch SCORM content when any part's revision changes; show a
  failed video as failed instead of "still rendering"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Iqbal
2026-10-23 10:42:12 +07:00
commit f219b26f71
41 changed files with 8172 additions and 0 deletions

34
vite.config.ts Normal file
View File

@@ -0,0 +1,34 @@
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react'
// Clerk publishable keys are public. Default to the engine's own key so Express
// and learning-lab share one Clerk instance (same users, organizations, credits).
function engineClerkKey(): string {
try {
const env = readFileSync(fileURLToPath(new URL('../learning-lab/backend/.env', import.meta.url)), 'utf8')
return env.match(/^VITE_CLERK_PUBLISHABLE_KEY=["']?([^"'\s]+)/m)?.[1] ?? ''
} catch {
return ''
}
}
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const engineUrl = env.ENGINE_URL || 'http://localhost:8000'
const clerkKey = env.VITE_CLERK_PUBLISHABLE_KEY || engineClerkKey()
// Learning Studio (learning-lab/frontend), where a result opens in Compose for full editing.
const studioUrl = env.VITE_STUDIO_URL || 'http://localhost:5173'
return {
plugins: [react()],
define: {
'import.meta.env.VITE_CLERK_PUBLISHABLE_KEY': JSON.stringify(clerkKey),
'import.meta.env.VITE_STUDIO_URL': JSON.stringify(studioUrl),
},
// Same-origin /api keeps the engine free of CORS setup, as in production behind one domain.
server: { port: 5174, proxy: { '/api': { target: engineUrl, changeOrigin: true } } },
preview: { port: 5174, proxy: { '/api': { target: engineUrl, changeOrigin: true } } },
}
})