feat(student): wire roster identity and student session

New Platform Shell entry point (frontend/src/platform/) alongside the
existing, completely unmodified Lab UI (App.jsx and everything under
frontend/src/components/). main.jsx now splits on the current URL path —
/lab/<token>/ renders the existing <App/> exactly as before; anything else
renders the new <PlatformShell/>. Both live in the same built bundle (it's
served both by the persistent identity instance and by every ephemeral Lab
container), split purely at runtime.

- EntryScreen.jsx — cohort dropdown -> name dropdown (existing
  GET /api/roster/cohorts, GET /api/roster?cohort=) -> POST /api/login. No
  password. Visibly labeled pilot identity, not production auth, per the
  governing requirement that this be stated explicitly, not just true in the
  architecture docs.
- api.js — student_id lives in localStorage only, sent as X-Student-Id on
  every authenticated request. Every authenticated call throws if no student
  is stored rather than silently going out anonymously — this is what
  "prevent accidental anonymous progress writes" means in practice, not just
  a comment.
- PlatformShell.jsx — entry screen if no stored session, dashboard otherwise.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-18 11:16:24 -05:00
co-authored by Claude Sonnet 5
parent ce6abfe6c7
commit 5e4f5518fd
4 changed files with 143 additions and 1 deletions
+14
View File
@@ -0,0 +1,14 @@
import { useState } from 'react'
import { getStoredStudent } from './api'
import EntryScreen from './EntryScreen'
import StudentDashboard from './StudentDashboard'
// Top-level Platform Shell: entry screen if no student session, dashboard otherwise. This is
// everything OUTSIDE the /lab/<token>/ Lab experience (which stays the existing, unmodified
// <App/> — see main.jsx).
export default function PlatformShell() {
const [student, setStudent] = useState(getStoredStudent)
if (!student) return <EntryScreen onLogin={setStudent} />
return <StudentDashboard student={student} onLogout={() => setStudent(null)} />
}