세션 평가·라이브코치·교수자 분석 라운드 마감 + 문서 정리 + 코드품질 리팩터

- 누적 작업트리 커밋: 회기 평가 복구·durable 저장, 라이브 코치 이력/근거, 교수자 학생분석, 음성 비언어 메타, PII 마스킹, 운영 티켓/헬스 등
- 문서: 완료 기록 docs/archive/ 냉동 보관, docs/ 단일 인덱스(docs/README.md)+통합 TODO(docs/TODO.md)로 정리
- 리팩터(행위 보존): Stage enum SSOT(taxonomy 소유·state_machine re-export), store recent/masked_turns 중복 제거, speaker_ko_label 단일 헬퍼, _list_sessions N+1 제거(state/turns 배치 + 턴평가 하이드레이션 배치)
- 검증: 백엔드 pytest 352 passed, _list_sessions E2E chromium-single-run 2 passed
This commit is contained in:
Yun Chan 2026-07-02 02:50:36 +09:00
parent 7c41c3ce79
commit 778e8526d4
108 changed files with 6457 additions and 455 deletions

View file

@ -17,7 +17,7 @@
import type { ReactNode } from "react";
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
import { AuthProvider, canAccessRole, useAuth, roleHomePath, type Role } from "./lib/auth";
import { AuthProvider, canAccessRole, useAuth, roleHomePath, type AuthUser, type Role } from "./lib/auth";
import Login from "./pages/Login";
import Onboarding from "./pages/Onboarding";
@ -74,6 +74,17 @@ function RequireAuth({
return <>{children}</>;
}
function isAdminWorkspacePath(path: string) {
return path === "/admin" || path.startsWith("/admin/");
}
function approvedHomePath(user: AuthUser) {
if (user.onboardingCompletedAt == null) {
return canAccessRole(user, "admin") ? "/admin" : "/onboarding";
}
return roleHomePath(user.role);
}
function OnboardingGate({ children }: { children: ReactNode }) {
const { user, loading } = useAuth();
const location = useLocation();
@ -82,6 +93,9 @@ function OnboardingGate({ children }: { children: ReactNode }) {
if (loading) return <BootScreen />;
if (path === "/pending") return <>{children}</>;
if (user && user.onboardingCompletedAt == null && path !== "/onboarding") {
if (isAdminWorkspacePath(path) && canAccessRole(user, "admin")) {
return <>{children}</>;
}
return <Navigate to="/onboarding" replace state={{ from: path }} />;
}
if (user && user.onboardingCompletedAt != null && (path === "/login" || path === "/onboarding")) {
@ -100,12 +114,7 @@ function PendingApprovalGate({ children }: { children: ReactNode }) {
return <Navigate to="/pending" replace state={{ from: path }} />;
}
if (user && user.accountStatus === "approved" && path === "/pending") {
return (
<Navigate
to={user.onboardingCompletedAt == null ? "/onboarding" : roleHomePath(user.role)}
replace
/>
);
return <Navigate to={approvedHomePath(user)} replace />;
}
return <>{children}</>;
}
@ -118,7 +127,7 @@ function RootRedirect() {
return <Navigate to="/pending" replace />;
}
if (user && user.onboardingCompletedAt == null) {
return <Navigate to="/onboarding" replace />;
return <Navigate to={approvedHomePath(user)} replace />;
}
return <Navigate to={user ? roleHomePath(user.role) : "/login"} replace />;
}