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

35
index.html Normal file
View File

@@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="description" content="Learning Lab Express by Bawana: Express Mode for learning-lab." />
<title>Learning Lab Express</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400..800&display=swap" />
<!-- Loading screen styles live here, not in theme.css, so they paint before the app bundle loads. -->
<style>
body{margin:0;background:#f5f8ff}
.boot{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;font-family:'Plus Jakarta Sans',system-ui,-apple-system,"Segoe UI",sans-serif;color:#101828;background:radial-gradient(600px 400px at 50% 38%,rgba(46,144,250,.18),rgba(46,144,250,0) 70%),#f5f8ff;animation:boot-in .3s ease-out}
.boot-mark{position:relative;display:grid;place-items:center;width:64px;height:64px;border-radius:20px;color:#fff;background:linear-gradient(140deg,#53b1fd,#2e90fa 45%,#1570ef);box-shadow:inset 0 1px 0 rgba(255,255,255,.3),0 14px 30px -10px rgba(21,112,239,.7)}
.boot-mark svg{width:32px;height:32px}
.boot-mark::before,.boot-mark::after{content:"";position:absolute;inset:-2px;border-radius:22px;border:2px solid rgba(46,144,250,.45);animation:boot-ring 1.8s ease-out infinite}
.boot-mark::after{animation-delay:.9s}
.boot-name{font-size:17px;font-weight:600;letter-spacing:-.02em}
.boot-name b{font-weight:800;color:#1570ef}
.boot-bar{position:relative;width:160px;height:4px;border-radius:4px;background:#d1e9ff;overflow:hidden}
.boot-bar i{position:absolute;inset:0 auto 0 0;width:40%;border-radius:4px;background:linear-gradient(90deg,#2e90fa,#1570ef);animation:boot-slide 1.1s ease-in-out infinite}
.boot-text{font-size:13px;color:#667085}
@keyframes boot-ring{from{transform:scale(1);opacity:1}to{transform:scale(1.55);opacity:0}}
@keyframes boot-slide{from{transform:translateX(-100%)}to{transform:translateX(250%)}}
@keyframes boot-in{from{opacity:0}}
@media (prefers-reduced-motion:reduce){.boot,.boot-mark::before,.boot-mark::after,.boot-bar i{animation:none}.boot-bar i{width:100%}}
</style>
</head>
<body>
<!-- Shown until React mounts; AuthGate renders the same screen while Clerk loads. -->
<div id="root"><div class="boot" role="status"><span class="boot-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5c3-1 6 0 9 2 3-2 6-3 9-2v14c-3-1-6 0-9 2-3-2-6-3-9-2z"/><path d="M12 7v14M6 9l3 1M15 10l3-1"/></svg></span><span class="boot-name">Learning Lab <b>Express</b></span><span class="boot-bar"><i></i></span></div></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>