import { useEffect, useMemo, useRef, useState } from 'react' import { Link, NavLink, Route, Routes, useParams } from 'react-router' import { Terminal } from '@xterm/xterm' import { FitAddon } from '@xterm/addon-fit' import { marked } from 'marked' import DOMPurify from 'dompurify' type Check = { taskId?: string; name: string; type: string; passed: boolean; message: string } type Task = { id: string; title: string; description: string; hints: string[]; checks: unknown[] } type Lab = { id: string; title: string; summary: string; difficulty: string; estimatedMinutes: number; prerequisites: string[]; tasks: Task[]; lesson?: string } type Score = { stars: number; correctness: number; speed: number; cleanliness: number; durationSeconds: number; failedValidations: number; hintsRevealed: number } type TaskProgress = { taskId: string; failedValidations: number; ghostHints: number } type Validation = { status: string; passed: number; checks: Check[]; taskProgress?: TaskProgress[]; score?: Score; ghostHintEvery?: number } type Progress = { labId: string; status: string; attempts: number; updatedAt: string; score?: Score } type Session = { running: boolean; container?: string } type UnlockGate = { completedFromModule?: string; count?: number } type PathModule = { id: string; title: string; summary?: string; labs: string[]; comingSoon?: string[]; source?: string; unlock?: UnlockGate } type PathPhase = { id: string; title: string; summary?: string; modules: PathModule[] } type LearningPath = { id: string; title: string; summary: string; source?: string; phases: PathPhase[] } async function request(path: string, init?: RequestInit): Promise { const response = await fetch(path, init) if (!response.ok) { const body = await response.json().catch(() => ({ error: response.statusText })) throw new Error(body.error) } return response.status === 204 ? (undefined as T) : response.json() } function starsLabel(n = 0) { return '★'.repeat(Math.max(0, Math.min(3, n))) + '☆'.repeat(Math.max(0, 3 - Math.max(0, Math.min(3, n)))) } function formatMinutes(total: number) { if (total <= 0) return '0 min' if (total < 60) return `${total} min` const hours = Math.floor(total / 60) const mins = total % 60 return mins ? `${hours}h ${mins}m` : `${hours}h` } function parseTipHint(hint: string): { code?: string; text: string } { const match = hint.match(/Tip codes?\s*:?\s*([A-Z][A-Z0-9_]*)\s*[:.—-]\s*(.+)/i) || hint.match(/Tip codes?\s*:?\s*([A-Z][A-Z0-9_]*)/i) if (!match) return { text: hint } const rest = (match[2] || hint.slice(match[0].length)).trim().replace(/^[:.—-]\s*/, '') return { code: match[1].toUpperCase(), text: rest || hint } } function tipCodesFor(result: Validation, lab?: Lab) { const codes = new Set() const failedTaskIds = new Set(result.checks.filter(c => !c.passed && c.taskId).map(c => c.taskId as string)) for (const task of lab?.tasks || []) { if (!failedTaskIds.has(task.id)) continue for (const hint of task.hints || []) { const parsed = parseTipHint(hint) if (parsed.code) codes.add(parsed.code) } } return [...codes] } function tipGlossaryFor(lab?: Lab) { const byCode = new Map() for (const task of lab?.tasks || []) { for (const hint of task.hints || []) { const parsed = parseTipHint(hint) if (parsed.code && !byCode.has(parsed.code)) byCode.set(parsed.code, parsed.text) } } const fromLesson = lab?.lesson?.matchAll(/`([A-Z][A-Z0-9_]{2,})`\s*[—–-]\s*([^\n]+)/g) || [] for (const match of fromLesson) { if (!byCode.has(match[1])) byCode.set(match[1], match[2].trim()) } return [...byCode.entries()].map(([code, text]) => ({ code, text })) } function useLearningPathOrder() { const [pathLabs, setPathLabs] = useState([]) const [path, setPath] = useState() useEffect(() => { request('/api/paths').then(paths => { const first = paths[0] setPath(first) setPathLabs(first?.phases.flatMap(phase => phase.modules.flatMap(module => module.labs || [])) || []) }).catch(() => { setPath(undefined); setPathLabs([]) }) }, []) const nextLabId = (labId: string) => { const index = pathLabs.indexOf(labId) if (index < 0 || index >= pathLabs.length - 1) return undefined return pathLabs[index + 1] } return { path, pathLabs, nextLabId } } function useContinueLab( statusFor: (id: string) => string | undefined, isLocked: (lab?: Lab) => boolean, labMap: Record, ) { const { path, pathLabs } = useLearningPathOrder() const continueLabId = path && pathLabs.find(labId => { if (statusFor(labId) === 'completed') return false const module = path.phases.flatMap(phase => phase.modules).find(item => item.labs.includes(labId)) return !!module && moduleUnlocked(module, path, statusFor) && !isLocked(labMap[labId]) }) return { continueLabId, continueLab: continueLabId ? labMap[continueLabId] : undefined, } } function useLabsAndProgress() { const [labs, setLabs] = useState([]) const [progress, setProgress] = useState([]) const [error, setError] = useState('') useEffect(() => { Promise.all([request('/api/labs'), request('/api/progress')]) .then(([labsData, progressData]) => { setLabs(labsData); setProgress(progressData) }) .catch(e => setError(e.message)) }, []) const labMap = useMemo(() => Object.fromEntries(labs.map(l => [l.id, l])), [labs]) const statusFor = (labId: string) => progress.find(p => p.labId === labId)?.status const scoreFor = (labId: string) => progress.find(p => p.labId === labId)?.score const missingPrereqs = (lab?: Lab) => (lab?.prerequisites || []).filter(id => statusFor(id) !== 'completed') const isLocked = (lab?: Lab) => missingPrereqs(lab).length > 0 return { labs, progress, error, labMap, statusFor, scoreFor, missingPrereqs, isLocked } } function Catalog() { const { labs, error, statusFor, scoreFor, isLocked, missingPrereqs, labMap } = useLabsAndProgress() const { continueLabId, continueLab } = useContinueLab(statusFor, isLocked, labMap) return <>

LOCAL-FIRST PLATFORM ENGINEERING

Learn by fixing real systems.

Short lessons. Isolated environments. Deterministic validation. Follow the DevOps Engineer Path or browse all labs.

{continueLabId &&

Continue → {continueLab?.title || continueLabId}

}
{error &&

{error}

}
{labs.map((lab, index) => { const locked = isLocked(lab) const body = <>
{String(index + 1).padStart(2, '0')}{locked ? 'locked' : lab.difficulty}

{lab.title}

{lab.summary}

{locked &&

Locked — complete: {missingPrereqs(lab).join(', ')}

} {!locked && lab.prerequisites?.length > 0 &&

Requires: {lab.prerequisites.join(', ')}

}
{lab.estimatedMinutes} min {statusFor(lab.id) === 'completed' && ✓ {scoreFor(lab.id) ? starsLabel(scoreFor(lab.id)?.stars) : 'completed'}} {locked ? 'Complete prereqs' : 'Open lab →'}
return locked ?
{body}
: {body} })}
} function moduleUnlocked(module: PathModule, path: LearningPath, statusFor: (id: string) => string | undefined) { if (!module.unlock?.count || !module.unlock.completedFromModule) return true const source = path.phases.flatMap(phase => phase.modules).find(item => item.id === module.unlock?.completedFromModule) if (!source) return true const done = source.labs.filter(labId => statusFor(labId) === 'completed').length return done >= module.unlock.count } function LearningPathView() { const { labMap, statusFor, scoreFor, error, isLocked, missingPrereqs } = useLabsAndProgress() const [path, setPath] = useState() const [loadError, setLoadError] = useState('') const { continueLabId, continueLab } = useContinueLab(statusFor, isLocked, labMap) useEffect(() => { request('/api/paths').then(paths => setPath(paths[0])).catch(e => setLoadError(e.message)) }, []) const pathLabs = useMemo(() => path?.phases.flatMap(phase => phase.modules.flatMap(module => module.labs || [])) || [], [path]) const completedCount = pathLabs.filter(labId => statusFor(labId) === 'completed').length const remainingMinutes = pathLabs .filter(labId => statusFor(labId) !== 'completed') .reduce((sum, labId) => sum + (labMap[labId]?.estimatedMinutes || 0), 0) const pathComplete = completedCount > 0 && completedCount === pathLabs.length if (loadError) return

{loadError}

if (!path) return

Loading learning path…

return <>

DEVOPS ENGINEER PATH

{path.title}

{path.summary}

Progress: {completedCount}/{pathLabs.length} labs completed · ~{formatMinutes(remainingMinutes)} remaining

{continueLabId &&

Continue → {continueLab?.title || continueLabId}

} {!continueLabId && pathComplete &&

Path complete — review stars on the dashboard.

} {path.source &&

{path.source}

}
{error &&

{error}

} {path.phases.map(phase => { const phaseLabs = phase.modules.flatMap(module => module.labs || []) const phaseDone = phaseLabs.filter(labId => statusFor(labId) === 'completed').length const phaseRemaining = phaseLabs .filter(labId => statusFor(labId) !== 'completed') .reduce((sum, labId) => sum + (labMap[labId]?.estimatedMinutes || 0), 0) return

{phase.title}

{phaseDone}/{phaseLabs.length} done · ~{formatMinutes(phaseRemaining)} left
{phase.summary &&

{phase.summary}

} {phase.modules.map(module => { const unlocked = moduleUnlocked(module, path, statusFor) return

{module.title}

{module.source && {module.source}}
{module.summary &&

{module.summary}

} {!unlocked && module.unlock &&

Sandbox locked — complete {module.unlock.count} labs in {module.unlock.completedFromModule} first.

}
    {module.labs.map(labId => { const lab = labMap[labId] const done = statusFor(labId) === 'completed' const locked = !unlocked || isLocked(lab) return
  • {locked ? {lab?.title || labId} ({missingPrereqs(lab).length ? `needs ${missingPrereqs(lab).join(', ')}` : 'locked'}) : {lab?.title || labId}} {lab?.estimatedMinutes || '?'} min {done && `✓ ${starsLabel(scoreFor(labId)?.stars || 0)}`}
  • })} {module.comingSoon?.map(item =>
  • {item}coming soon
  • )}
})}
})} } function BrowserTerminal({ labId, active }: { labId: string; active: boolean }) { const host = useRef(null) useEffect(() => { if (!active || !host.current) return const terminal = new Terminal({ cursorBlink: true, fontSize: 14, theme: { background: '#070b14', foreground: '#dce7ff', cursor: '#57e3c1' } }) const fit = new FitAddon() terminal.loadAddon(fit); terminal.open(host.current); fit.fit() terminal.writeln('\x1b[36mConnecting to isolated lab…\x1b[0m') let socket: WebSocket | undefined let retry: number | undefined const sendResize = () => { if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({ type: 'resize', rows: terminal.rows, cols: terminal.cols })) } const connect = () => { const protocol = location.protocol === 'https:' ? 'wss' : 'ws' socket = new WebSocket(`${protocol}://${location.host}/api/labs/${labId}/terminal`) socket.binaryType = 'arraybuffer' socket.onopen = () => { terminal.writeln('\x1b[32mConnected. Type commands below.\x1b[0m'); sendResize() } socket.onmessage = event => { const data = typeof event.data === 'string' ? event.data : new TextDecoder().decode(event.data as ArrayBuffer); terminal.write(data) } socket.onclose = () => { terminal.writeln('\r\n\x1b[33mDisconnected. Reconnecting…\x1b[0m'); retry = window.setTimeout(connect, 1500) } } connect() const input = terminal.onData(data => socket?.readyState === WebSocket.OPEN && socket.send(data)) const resize = () => { fit.fit(); sendResize() } window.addEventListener('resize', resize) return () => { input.dispose(); if (retry) clearTimeout(retry); socket?.close(); terminal.dispose(); window.removeEventListener('resize', resize) } }, [active, labId]) return
} function Lesson() { const { id = '' } = useParams() const { missingPrereqs, isLocked, labMap } = useLabsAndProgress() const { nextLabId } = useLearningPathOrder() const [lab, setLab] = useState() const [started, setStarted] = useState(false) const [busy, setBusy] = useState(false) const [result, setResult] = useState() const [error, setError] = useState('') const [manualHints, setManualHints] = useState>({}) const [ghostHints, setGhostHints] = useState>({}) const [glossaryOpen, setGlossaryOpen] = useState(false) const [focusTip, setFocusTip] = useState() const glossaryRef = useRef(null) useEffect(() => { setResult(undefined) setManualHints({}) setGhostHints({}) setGlossaryOpen(false) setFocusTip(undefined) request(`/api/labs/${id}`).then(setLab).catch(e => setError(e.message)) request(`/api/labs/${id}/status`).then(s => setStarted(s.running)).catch(() => setStarted(false)) request<{ taskProgress?: TaskProgress[] }>(`/api/progress/${id}`).then(detail => { const next: Record = {} for (const tp of detail.taskProgress || []) next[tp.taskId] = tp.ghostHints setGhostHints(next) }).catch(() => undefined) }, [id]) useEffect(() => { if (!glossaryOpen || !focusTip || !glossaryRef.current) return const target = glossaryRef.current.querySelector(`[data-tip="${focusTip}"]`) if (target instanceof HTMLElement) target.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) }, [glossaryOpen, focusTip]) const locked = lab ? isLocked(lab) : false const glossary = useMemo(() => tipGlossaryFor(lab), [lab]) const nextId = nextLabId(id) const nextLab = nextId ? labMap[nextId] : undefined const openTip = (code: string) => { setFocusTip(code) setGlossaryOpen(true) } const act = async (action: 'start' | 'reset' | 'validate' | 'stop') => { setBusy(true); setError('') try { if (action === 'stop') { await request(`/api/labs/${id}/stop`, { method: 'POST' }); setStarted(false); setResult(undefined) } else { const value = await request(`/api/labs/${id}/${action}`, { method: 'POST' }) setStarted(true) if (action === 'validate') { const validation = value as Validation setResult(validation) const next: Record = {} for (const tp of validation.taskProgress || []) next[tp.taskId] = tp.ghostHints setGhostHints(next) } if (action === 'reset') setResult(undefined) } } catch (e) { setError((e as Error).message) } finally { setBusy(false) } } const revealedFor = (task: Task) => Math.max(manualHints[task.id] || 0, ghostHints[task.id] || 0) const lessonHTML = useMemo(() => ({ __html: DOMPurify.sanitize(marked.parse(lab?.lesson || '') as string) }), [lab?.lesson]) const failedTipCodes = result && result.status !== 'passed' ? tipCodesFor(result, lab) : [] if (!lab) return

{error || 'Loading lab…'}

return
{started ? '● LAB RUNNING' : '○ LAB STOPPED'}
{!started && } {started && <>}
{error &&

{error}

}

Validation

{!result &&

Complete the objectives, then validate your environment. After {result?.ghostHintEvery || 2} failed validates on a task, a ghost hint appears.

} {result && <>

{result.passed}/{result.checks.length} checks passed

{failedTipCodes.length > 0 &&
{failedTipCodes.map(code => )}
} {result.checks.map((check, i) =>
{check.passed ? '✓' : '×'} {check.name}{check.message}
)} {result.status === 'passed' && result.score &&

Debrief

{starsLabel(result.score.stars)}

  • Correctness {result.score.correctness}/3
  • Speed {result.score.speed}/3 · {Math.round(result.score.durationSeconds / 60)} min
  • Cleanliness {result.score.cleanliness}/3 · {result.score.failedValidations} failed validate{result.score.failedValidations === 1 ? '' : 's'}, {result.score.hintsRevealed} ghost hint{result.score.hintsRevealed === 1 ? '' : 's'}

Progress saved — open the dashboard to review stars.

{nextId &&

Next lab → {nextLab?.title || nextId}

} {!nextId &&

Back to learning path →

}
} }
} function Dashboard() { const { labs, progress } = useLabsAndProgress() const titleFor = (labId: string) => labs.find(l => l.id === labId)?.title || labId const completed = progress.filter(p => p.status === 'completed').length const totalStars = progress.reduce((sum, p) => sum + (p.score?.stars || 0), 0) return

YOUR PROGRESS

Skills dashboard

{completed}labs completed
{totalStars}stars earned
{progress.map(p =>
{titleFor(p.labId)}{p.status.replace('_', ' ')} · {p.attempts} attempt{p.attempts === 1 ? '' : 's'}{p.score ? ` · ${starsLabel(p.score.stars)}` : ''}
)}
} export function App() { return
PlatformForge
} />} />} />} />
}