diff --git a/apps/examlense/AGENTS.md b/apps/examlense/AGENTS.md index 1af7eb5..74d7615 100644 --- a/apps/examlense/AGENTS.md +++ b/apps/examlense/AGENTS.md @@ -46,53 +46,56 @@ npm run preview # Preview production build locally React 18 + TypeScript SPA for exam authoring, AI-powered parsing/solving/grading, and results analysis. Built with Vite (SWC), Tailwind CSS, shadcn/ui (Radix primitives). -The backend is a single **Spring Boot service** (`backend/`, JDK 21) talking to a plain **PostgreSQL** database (Docker, host port 5433). It owns everything: CRUD + ownership checks, the `parse-exam-pdf` / `solve-*` / admin endpoints, file storage (local filesystem behind `StorageService`, served via HMAC-signed time-limited URLs), and realtime via **SSE** (`/api/exams/{id}/events`, `/api/exams/events`). Auth is a **static bearer token** (`app.auth.token`, env `API_AUTH_TOKEN`) plus a per-IP rate limiter — single-user for now; `owner_id`/`user_id`/`graded_by` and storage paths are stamped server-side from the authenticated principal. (Supabase — auth, Postgres+RLS, storage, realtime, edge functions — has been fully removed; see `docs/supabase-migration.md`.) `src/lib/api-client.ts` is the single typed transport and `src/lib/sse.ts` is the SSE client. AI calls use provider-pinned strategies for GWDG/OpenAI-compatible, OpenAI, Anthropic, and Gemini models; the client-side catalog mirror lives in `src/lib/llm-models.ts`. The solver model is chosen at exam creation and locked for the run. +The backend is a single **Spring Boot service** (`backend/`, JDK 21) talking to a plain **PostgreSQL** database (Docker, host port 5433). It owns everything: CRUD + ownership checks, the `parse-exam-pdf` / `solve-*` / admin endpoints, file storage (local filesystem behind `StorageService`, served via HMAC-signed time-limited URLs), and realtime via **SSE** (`/api/exams/{id}/events`, `/api/exams/events`). Auth is a **static bearer token** (`app.auth.token`, env `API_AUTH_TOKEN`) plus a per-IP rate limiter — single-user for now; `owner_id`/`user_id`/`graded_by` and storage paths are stamped server-side from the authenticated principal. (Supabase — auth, Postgres+RLS, storage, realtime, edge functions — has been fully removed; see `docs/supabase-migration.md`.) `src/lib/api/api-client.ts` is the single typed transport and `src/lib/api/sse.ts` is the SSE client. AI calls use provider-pinned strategies for GWDG/OpenAI-compatible, OpenAI, Anthropic, and Gemini models; the client-side catalog mirror lives in `src/lib/exam/llm-models.ts`. The solver model is chosen at exam creation and locked for the run. ### Data Layer -All server state flows through **React Query** hooks in `src/hooks/`. Each hook exports query keys for cache invalidation. No Redux or global state library — auth lives in `useAuth()`, UI state in component `useState`. +All server state flows through **React Query** hooks in `src/hooks/data/` (the `use-*` hooks below). Each hook exports query keys for cache invalidation. No Redux or global state library — UI state lives in component `useState`. There is no client-side auth hook; the backend is gated by a static bearer token (see `src/lib/api/api-client.ts`) and treats every request as the single seeded user. + +Hooks and lib are grouped by concern: `src/hooks/{data,ui}/` (server-state/realtime vs generic UI hooks) and `src/lib/{api,exam,grading,learning-goals,parsing,utils}/`. -- `use-auth.ts` — single-user passthrough: returns a fixed `userId` (`DefaultUser.ID`), `ready: true`, no-op `signOut` (no real auth yet) - `use-exam.ts` — `useExam(id)`, `useTasks(id)` queries - `use-sections.ts` — `useSections(id)`, `useSectionBlocks(id)`, `useSectionFigures(id)` - `use-task-answers.ts` — AI-generated answers per task - `use-task-grades.ts` — grades with `useUpsertTaskGrade` mutation - `use-exam-progress.ts` — evaluation progress via the exam SSE `progress` event -- `use-learning-goals.ts` — `useLearningGoals()` account-scoped goal catalog + `goalsByIds(all, ids)` resolver +- `use-learning-goals.ts` — `useExamLearningGoals(examId)` resolved goals via the backend LGH proxy, `useLghCourses()` course picker data, `goalsByIds(all, ids)` resolver -**Realtime** is SSE: `src/lib/sse.ts` (`subscribeExam`, `subscribeExamsList`) opens an `EventSource` (token via `?token=` query param) and invalidates React Query caches on each event. Domain types are hand-defined in `src/lib/exam-helpers.ts` / `src/lib/grading.ts` (the old auto-generated Supabase DB types are gone). +**Realtime** is SSE: `src/lib/api/sse.ts` (`subscribeExam`, `subscribeExamsList`) opens an `EventSource` (token via `?token=` query param) and invalidates React Query caches on each event. Domain types are hand-defined in `src/lib/exam/exam-helpers.ts` / `src/lib/grading/grading.ts` (the old auto-generated Supabase DB types are gone). ### Core Domain Types -Defined in `src/lib/exam-helpers.ts`: +Defined in `src/lib/exam/exam-helpers.ts`: - **Exam** — status lifecycle: `parsing → draft → ready → evaluating → grading → finished` (or `failed`) - **Task** — types: `single_choice`, `multiple_choice`, `text`; belongs to an exam and optionally a section - **Section** — groups tasks with context blocks and figure blocks, ordered by position - **BlockItem** — union type produced by `mergeSectionItems()` to interleave tasks, context blocks, and figure blocks by position for rendering -Grading logic in `src/lib/grading.ts`: `autoGradeChoiceTask()` scores MC questions; `effectiveScore()` merges auto/manual grades. +Grading logic in `src/lib/grading/grading.ts`: `autoGradeChoiceTask()` scores MC questions; `effectiveScore()` merges auto/manual grades. ### Learning Goals -Course learning objectives tied to exam content. Both `exams` and `tasks` carry a `learning_goal_ids` (`jsonb` array of numeric ids); store ids only and resolve text/Bloom/SOLO at render via `useLearningGoals()`. Three touch points: (1) assigned to the exam during creation in `StartExamDialog` — for PDF, parsing is fired eagerly and the ids are attached afterward; (2) mapped 1–3 per task via a select on `TaskCard` (limited to the exam's goals, provided through `ExamGoalsContext`); (3) rolled up per goal with Bloom/SOLO on the results "Learning Goals" tab (`LearningGoalsCard`). +Derived automatically per task by the external **LearningGoalHub** (LGH) service (`apps/learninggoalhub`, VPN-only; base URL via backend env `LGH_BASE_URL`). Flow: (1) at exam creation the user links an LGH course in `StartExamDialog` (`exams.lgh_course_id`, optional — skipping it disables goal insights); (2) when a section is confirmed, the backend (`app/lgh/TaskGoalGenerationService`, `lghExecutor` pool, CAS lock on `sections.goals_started_at`) posts the section's context blocks + tasks to LGH's `POST /api/courses/{id}/exam-tasks/learning-goals` and stores the returned goal ids on `tasks.learning_goal_ids` (jsonb, ids only); re-confirm deletes the old LGH goals first (LGH doesn't dedup), unconfirm clears + best-effort deletes; (3) goals render read-only in the grading view (`ReadOnlyTaskCard`) and roll up per goal on the results "Learning Goals" tab (`LearningGoalsCard`). -Goals currently come from a **mock** behind `src/lib/learning-goals-api.ts` — the single swap point to the real `learninggoalhub` endpoint. Types + mock data are in `src/lib/learning-goals.ts`. +Goal text/Bloom/SOLO is resolved at render through the backend proxy (`GET /api/exams/{id}/learning-goals`, `GET /api/lgh/courses` — `app/lgh/LghController`); when LGH is unreachable the UI degrades to "Goal #id" placeholders (metrics still work, ids live on our tasks). Frontend types in `src/lib/learning-goals/learning-goals.ts`; the SSE `tasks` event signals goal generation finished. ### Page Routes (React Router v6) -- `/` — landing page +- `/` — redirects to `/exams` - `/exams` — exam list/management -- `/exam/:id/edit` — drag-and-drop exam editor (dnd-kit for reordering) -- `/exam/:id/grade` — AI answer grading interface -- `/exam/:id/results` — results dashboard (Recharts) +- `/exams/:id/edit` — drag-and-drop exam editor (dnd-kit for reordering) +- `/exams/:id/grade` — AI answer grading interface +- `/exams/:id/results` — results dashboard (Recharts) +- `/admin` — internal review dashboard ### Component Organization +Page-centric colocation: **`src/components/` holds only shared, reusable code; every page owns its private components under `src/pages//components/`.** The rule is self-explaining — a component in `components/` is shared across views; one under `pages/X/` is private to page X. Reusing a page-private component elsewhere means deliberately promoting it into `components/shared/`. + - `src/components/ui/` — shadcn/ui primitives (do not edit manually; managed by shadcn CLI) -- `src/components/exam-edit/` — exam editor components including `grading/` subdirectory -- `src/components/exam-results/` — results dashboard cards -- `src/components/landing/` — marketing/landing page sections +- `src/components/shared/` — cross-page building blocks: `exam-content/` (section/block renderers shared by edit, grading, and results — incl. `read-only/` variants and `BlockHeader`), `chrome/` (sticky header/footer shell + `HelpDialog`/`IntroStepGuide`), and shared atoms (`ModelLogo`, `ThemeToggle`) +- `src/pages//.tsx` — the route entry; `src/pages//components/` — that page's private components. Pages: `exams/` (+ `start-exam/` creation wizard), `exam-edit/`, `exam-grading/` (`GradingView` + `GradeRoute`), `exam-results/`, `admin/` ### Styling @@ -100,7 +103,7 @@ HESTIA design system built on Tailwind with custom HSL color tokens, defined in ### Copy / strings -The app is **English-only** — there is no i18n layer. UI strings are hardcoded directly in components. Shared enum labels live in `src/lib/labels.ts` (task types, Bloom/SOLO levels, grade sources) and the guided-feedback questionnaire in `src/lib/feedback-questions.ts`; reuse these instead of re-inlining. Note: the per-exam **content** `language` field (`en`/`de`/`other`, set in `StartExamDialog`) is unrelated to UI copy — it only tells the backend solver which language to answer that exam's PDF in. +The app is **English-only** — there is no i18n layer. UI strings are hardcoded directly in components. Shared enum labels live in `src/lib/exam/labels.ts` (task types, Bloom/SOLO levels, grade sources) and the guided-feedback questionnaire in `src/lib/feedback-questions.ts`; reuse these instead of re-inlining. Note: the per-exam **content** `language` field (`en`/`de`/`other`, set in `StartExamDialog`) is unrelated to UI copy — it only tells the backend solver which language to answer that exam's PDF in. ## Common Prompt Phrases & File Pointers @@ -110,70 +113,68 @@ When the user uses any of these phrases, treat them as references to the listed ### Top-level views / "modes" -- **"Landing"**, **"Home"**, **"Hero"**, **"Landing Page"** → `src/pages/Index.tsx` and `src/components/landing/` -- **"Dashboard"**, **"Exam List"**, **"Your Exams"**, **"My Exams"**, **"Exams Page"** → `src/pages/Exams.tsx` -- **"Evaluate Page"**, **"Start Exam"**, **"Upload PDF"**, **"Create From Scratch"**, **"Parse Exam"** → `src/pages/Evaluate.tsx` (entry); PDF pipeline in `backend/src/main/java/app/parse/` (`ParseExamService`) -- **"Edit View"**, **"Edit Mode"**, **"Editor"**, **"Exam Editor"**, **"Authoring View"** → `src/pages/ExamEdit.tsx` + components under `src/components/exam-edit/` (excluding `grading/`) -- **"Grading View"**, **"Grading Mode"**, **"Grade Mode"**, **"Grade Page"** → `src/pages/GradingView.tsx` (routed via `src/pages/GradeRoute.tsx`) + `src/components/exam-edit/grading/` -- **"Final Overview"**, **"Final Screen"**, **"Scoring Overview"**, **"Results"**, **"Results Dashboard"**, **"Insights"** → `src/pages/ExamResults.tsx` + `src/components/exam-results/` -- **"Admin"**, **"Admin Dashboard"**, **"Feedback Page"** → `src/pages/AdminDashboard.tsx` (route `/admin`). A tabbed dashboard that aggregates internal review data: user feedback, parsing-quality survey responses, and parser performance metrics. Panels live in `src/components/admin/`. -- **"Evaluating View"**, **"Evaluating Screen"** (the "solving in progress" splash) → `src/components/exam-edit/EvaluatingView.tsx` -- **"Parsing View"**, **"Parsing Screen"** (the "reading your exam" splash) → `src/components/exam-edit/EditorLoadingView.tsx` -- **"Intro"**, **"Intro Slide"** (first-time editor intro) → `src/components/exam-edit/IntroSlide.tsx` +- **"Dashboard"**, **"Exam List"**, **"Your Exams"**, **"My Exams"**, **"Exams Page"** → `src/pages/exams/Exams.tsx` + `src/pages/exams/components/` +- **"Start Exam"**, **"Upload PDF"**, **"Create From Scratch"**, **"Parse Exam"** → `src/pages/exams/start-exam/StartExamDialog.tsx` (the creation wizard); PDF pipeline in `backend/src/main/java/app/parse/` (`ParseExamService`) +- **"Edit View"**, **"Edit Mode"**, **"Editor"**, **"Exam Editor"**, **"Authoring View"** → `src/pages/exam-edit/ExamEdit.tsx` + `src/pages/exam-edit/components/` (shared section/block renderers live in `src/components/shared/exam-content/` and shell chrome in `src/components/shared/chrome/`) +- **"Grading View"**, **"Grading Mode"**, **"Grade Mode"**, **"Grade Page"** → `src/pages/exam-grading/GradingView.tsx` (routed via `src/pages/exam-grading/GradeRoute.tsx`) + `src/pages/exam-grading/components/` and the shared read-only renderers in `src/components/shared/exam-content/read-only/` +- **"Final Overview"**, **"Final Screen"**, **"Scoring Overview"**, **"Results"**, **"Results Dashboard"**, **"Insights"** → `src/pages/exam-results/ExamResults.tsx` + `src/pages/exam-results/components/` +- **"Admin"**, **"Admin Dashboard"**, **"Feedback Page"** → `src/pages/admin/AdminDashboard.tsx` (route `/admin`). A tabbed dashboard that aggregates internal review data: user feedback, parsing-quality survey responses, and parser performance metrics. Panels live in `src/pages/admin/components/`. +- **"Evaluating View"**, **"Evaluating Screen"** (the "solving in progress" splash) → `src/pages/exam-edit/components/EvaluatingView.tsx` +- **"Parsing View"**, **"Parsing Screen"** (the "reading your exam" splash) → `src/components/shared/exam-content/EditorLoadingView.tsx` +- **"Intro"**, **"Intro Slide"** (first-time editor intro) → `src/pages/exam-edit/components/IntroSlide.tsx` ### Shared section / block UI (used by Edit and Grading) -- **"Section Layout"**, **"Section Card"** (legacy name — now flat) → `src/components/exam-edit/SectionLayout.tsx` -- **"Section Title"**, **"Section Name"** (editable) → `src/components/exam-edit/SectionTitleInput.tsx` -- **"Section Tabs"**, **"Tabs Bar"** (top tabs above the section) → `src/components/exam-edit/SectionTabs.tsx` -- **"Carousel"**, **"Slide"** (single-section container) → `src/components/exam-edit/SectionCarousel.tsx` -- **"Confirm Button"**, **"Confirm Section"**, **"Section Status Chip"** → `src/components/exam-edit/ConfirmSectionButton.tsx` -- **"Block Row"**, **"Task Row"**, **"Collapsed Row"** → `src/components/exam-edit/BlockRow.tsx` (uses `BlockHeader.tsx` for the row layout) -- **"Add Task"**, **"+ Add"**, **"Add Block"** (inline popover) → `src/components/exam-edit/AddTaskInline.tsx` -- **"Chrome Header"**, **"Top Bar"**, **"Sticky Header"** → `src/components/exam-edit/chrome/ChromeHeader.tsx` -- **"Chrome Footer"**, **"Bottom Bar"**, **"Status Bar"** → `src/components/exam-edit/chrome/ChromeFooter.tsx` -- **"Utility Cluster"**, **"Header Actions"** (right-side controls) → `src/components/exam-edit/chrome/ChromeUtilityCluster.tsx` -- **"Save Status"**, **"Saving Indicator"** → `src/components/SaveStatus.tsx` +- **"Section Layout"**, **"Section Card"** (legacy name — now flat) → `src/components/shared/exam-content/SectionLayout.tsx` +- **"Section Title"**, **"Section Name"** (editable) → `src/pages/exam-edit/components/SectionTitleInput.tsx` +- **"Section Tabs"**, **"Tabs Bar"** (top tabs above the section) → `src/pages/exam-edit/components/SectionTabs.tsx` +- **"Carousel"**, **"Slide"** (single-section container) → `src/components/shared/exam-content/SectionCarousel.tsx` +- **"Confirm Button"**, **"Confirm Section"**, **"Section Status Chip"** → `src/pages/exam-edit/components/ConfirmSectionButton.tsx` +- **"Block Row"**, **"Task Row"**, **"Collapsed Row"** → `src/pages/exam-edit/components/BlockRow.tsx` (uses `BlockHeader` from `src/components/shared/exam-content/` for the row layout) +- **"Add Task"**, **"+ Add"**, **"Add Block"** (inline popover) → `src/pages/exam-edit/components/AddTaskInline.tsx` +- **"Chrome Header"**, **"Top Bar"**, **"Sticky Header"** → `src/components/shared/chrome/ChromeHeader.tsx` +- **"Chrome Footer"**, **"Bottom Bar"**, **"Status Bar"** → `src/components/shared/chrome/ChromeFooter.tsx` +- **"Utility Cluster"**, **"Header Actions"** (right-side controls) → `src/components/shared/chrome/ChromeUtilityCluster.tsx` +- **"Save Status"**, **"Saving Indicator"** → `src/pages/exam-edit/components/SaveStatus.tsx` ### Edit-only components -- **"Task Card"** (expanded editor) → `src/components/exam-edit/TaskCard.tsx` -- **"Context Block"** (editable) → `src/components/exam-edit/ContextBlockCard.tsx` -- **"Figure Block"** (editable) → `src/components/exam-edit/FigureBlockCard.tsx` -- **"Item Collapse State"**, **"Expand/Collapse Memory"** → `src/hooks/use-item-collapse-state.ts` -- **"Section Confirmations"** (pre-solve trigger) → `src/hooks/use-section-confirmations.ts` -- **"Exam Goals Context"** (provides exam's goals to task cards) → `src/components/exam-edit/ExamGoalsContext.tsx` +- **"Task Card"** (expanded editor) → `src/pages/exam-edit/components/TaskCard.tsx` +- **"Context Block"** (editable) → `src/pages/exam-edit/components/ContextBlockCard.tsx` +- **"Figure Block"** (editable) → `src/pages/exam-edit/components/FigureBlockCard.tsx` +- **"Item Collapse State"**, **"Expand/Collapse Memory"** → `src/hooks/ui/use-item-collapse-state.ts` +- **"Section Confirmations"** (pre-solve + goal-generation trigger) → `src/hooks/data/use-section-confirmations.ts` ### Grading-only components -- **"Read-Only Question"**, **"Question Block"** (grading: the static, card-less question — prompt + learning goals + optional reference answer) → `src/components/exam-edit/grading/ReadOnlyQuestionBlock.tsx` -- **"Read-Only Task"**, **"Task Preview"** (the single question+panel card — now used by the **results** view `AllTasksList`; grading composes `ReadOnlyQuestionBlock` + `TaskGradingPanel` instead) → `src/components/exam-edit/grading/ReadOnlyTaskCard.tsx` -- **"Read-Only Context"** → `src/components/exam-edit/grading/ReadOnlyContextBlock.tsx` -- **"Read-Only Figure"** → `src/components/exam-edit/grading/ReadOnlyFigureBlock.tsx` -- **"Grading Panel"**, **"Score Panel"**, **"Manual Grade"** (grading: the gradable AI-answer + score, rendered as the `primary` card) → `src/components/exam-edit/grading/TaskGradingPanel.tsx` +- **"Read-Only Question"**, **"Question Block"** (grading: the static, card-less question — prompt + learning goals + optional reference answer) → `src/pages/exam-grading/components/ReadOnlyQuestionBlock.tsx` +- **"Read-Only Task"**, **"Task Preview"** (the single question+panel card — shared; used by the **results** view `AllTasksList`; grading composes `ReadOnlyQuestionBlock` + `TaskGradingPanel` instead) → `src/components/shared/exam-content/read-only/ReadOnlyTaskCard.tsx` +- **"Read-Only Context"** → `src/components/shared/exam-content/read-only/ReadOnlyContextBlock.tsx` +- **"Read-Only Figure"** → `src/components/shared/exam-content/read-only/ReadOnlyFigureBlock.tsx` +- **"Grading Panel"**, **"Score Panel"**, **"Manual Grade"** (grading: the gradable AI-answer + score, rendered as the `primary` card) → `src/pages/exam-grading/components/TaskGradingPanel.tsx` ### Results dashboard cards -- **"Overall Score"** → `src/components/exam-results/OverallScoreCard.tsx` -- **"By Question Type"** → `src/components/exam-results/ByQuestionTypeCard.tsx` -- **"Task Breakdown"**, **"Per-Task Table"** → `src/components/exam-results/TaskBreakdownTable.tsx` -- **"Task Score Chart"** → `src/components/exam-results/TaskScoreBarChart.tsx` -- **"Learning Goals"** → `src/components/exam-results/LearningGoalsCard.tsx` -- **"Figures Comparison"** → `src/components/exam-results/FiguresComparisonCard.tsx` +- **"Overall Score"** → `src/pages/exam-results/components/OverallScoreCard.tsx` +- **"By Question Type"** → `src/pages/exam-results/components/ByQuestionTypeCard.tsx` +- **"Task Breakdown"**, **"Per-Task Table"** → `src/pages/exam-results/components/TaskBreakdownTable.tsx` +- **"Task Score Chart"** → `src/pages/exam-results/components/TaskScoreBarChart.tsx` +- **"Learning Goals"** → `src/pages/exam-results/components/LearningGoalsCard.tsx` +- **"Figures Comparison"** → `src/pages/exam-results/components/FiguresComparisonCard.tsx` ### Domain / data -- **"Exam Type"**, **"Task Type"**, **"Section Type"**, **"Block Item"** → `src/lib/exam-helpers.ts` -- **"Auto Grade"**, **"Effective Score"**, **"Grading Logic"** → `src/lib/grading.ts` -- **"Solver Model"**, **"AI Model"**, **"LLM Models"** → `src/lib/llm-models.ts` -- **"Learning Goals"**, **"Goals API"**, **"Goal Mock"**, **"Bloom"**, **"SOLO"** → `src/lib/learning-goals.ts` (types + mock), `src/lib/learning-goals-api.ts` (swap point), `src/hooks/use-learning-goals.ts` -- **"Backend Client"**, **"Spring Boot API"**, **"API Client"** → `src/lib/api-client.ts` -- **"Realtime"**, **"SSE"**, **"Progress Channel"** → `src/lib/sse.ts`, `src/hooks/use-exam-progress.ts` +- **"Exam Type"**, **"Task Type"**, **"Section Type"**, **"Block Item"** → `src/lib/exam/exam-helpers.ts` +- **"Auto Grade"**, **"Effective Score"**, **"Grading Logic"** → `src/lib/grading/grading.ts` +- **"Solver Model"**, **"AI Model"**, **"LLM Models"** → `src/lib/exam/llm-models.ts` +- **"Learning Goals"**, **"Goals API"**, **"LGH"**, **"LearningGoalHub"**, **"Bloom"**, **"SOLO"** → `src/lib/learning-goals/learning-goals.ts` (types), `src/hooks/data/use-learning-goals.ts` (hooks); backend integration in `backend/src/main/java/app/lgh/` +- **"Backend Client"**, **"Spring Boot API"**, **"API Client"** → `src/lib/api/api-client.ts` +- **"Realtime"**, **"SSE"**, **"Progress Channel"** → `src/lib/api/sse.ts`, `src/hooks/data/use-exam-progress.ts` ### Styling / system - **"Design Tokens"**, **"HESTIA Tokens"**, **"Theme"** → `src/index.css`, `tailwind.config.ts` -- **"Strings"**, **"Copy"**, **"Labels"** → strings are inlined in components (English-only, no i18n); shared enum labels in `src/lib/labels.ts`, feedback questions in `src/lib/feedback-questions.ts` +- **"Strings"**, **"Copy"**, **"Labels"** → strings are inlined in components (English-only, no i18n); shared enum labels in `src/lib/exam/labels.ts`, feedback questions in `src/lib/feedback-questions.ts` - **"shadcn"**, **"UI Primitives"**, **"Design System Components"** → `src/components/ui/` When the requested concept is ambiguous (e.g. "task card" could mean the editable or the read-only variant), ask which view (Edit vs Grading) the user means before editing. diff --git a/apps/examlense/CLAUDE.md b/apps/examlense/CLAUDE.md index 7e0e7e9..671b0b5 100644 --- a/apps/examlense/CLAUDE.md +++ b/apps/examlense/CLAUDE.md @@ -46,11 +46,13 @@ npm run preview # Preview production build locally React 18 + TypeScript SPA for exam authoring, AI-powered parsing/solving/grading, and results analysis. Built with Vite (SWC), Tailwind CSS, shadcn/ui (Radix primitives). -The backend is a single **Spring Boot service** (`backend/`, JDK 21) talking to a plain **PostgreSQL** database (Docker, host port 5433). It owns everything: CRUD + ownership checks, the `parse-exam-pdf` / `solve-*` / admin endpoints, file storage (local filesystem behind `StorageService`, served via HMAC-signed time-limited URLs), and realtime via **SSE** (`/api/exams/{id}/events`, `/api/exams/events`). Auth is a **static bearer token** (`app.auth.token`, env `API_AUTH_TOKEN`) plus a per-IP rate limiter — single-user for now; `owner_id`/`user_id`/`graded_by` and storage paths are stamped server-side from the authenticated principal. (Supabase — auth, Postgres+RLS, storage, realtime, edge functions — has been fully removed; see `docs/supabase-migration.md`.) `src/lib/api-client.ts` is the single typed transport and `src/lib/sse.ts` is the SSE client. AI calls use provider-pinned strategies for GWDG/OpenAI-compatible, OpenAI, Anthropic, and Gemini models; the client-side catalog mirror lives in `src/lib/llm-models.ts`. The solver model is chosen at exam creation and locked for the run. +The backend is a single **Spring Boot service** (`backend/`, JDK 21) talking to a plain **PostgreSQL** database (Docker, host port 5433). It owns everything: CRUD + ownership checks, the `parse-exam-pdf` / `solve-*` / admin endpoints, file storage (local filesystem behind `StorageService`, served via HMAC-signed time-limited URLs), and realtime via **SSE** (`/api/exams/{id}/events`, `/api/exams/events`). Auth is a **static bearer token** (`app.auth.token`, env `API_AUTH_TOKEN`) plus a per-IP rate limiter — single-user for now; `owner_id`/`user_id`/`graded_by` and storage paths are stamped server-side from the authenticated principal. (Supabase — auth, Postgres+RLS, storage, realtime, edge functions — has been fully removed; see `docs/supabase-migration.md`.) `src/lib/api/api-client.ts` is the single typed transport and `src/lib/api/sse.ts` is the SSE client. AI calls use provider-pinned strategies for GWDG/OpenAI-compatible, OpenAI, Anthropic, and Gemini models; the client-side catalog mirror lives in `src/lib/exam/llm-models.ts`. The solver model is chosen at exam creation and locked for the run. ### Data Layer -All server state flows through **React Query** hooks in `src/hooks/`. Each hook exports query keys for cache invalidation. No Redux or global state library — UI state lives in component `useState`. There is no client-side auth hook; the backend is gated by a static bearer token (see `src/lib/api-client.ts`) and treats every request as the single seeded user. +All server state flows through **React Query** hooks in `src/hooks/data/` (the `use-*` hooks below). Each hook exports query keys for cache invalidation. No Redux or global state library — UI state lives in component `useState`. There is no client-side auth hook; the backend is gated by a static bearer token (see `src/lib/api/api-client.ts`) and treats every request as the single seeded user. + +Hooks and lib are grouped by concern: `src/hooks/{data,ui}/` (server-state/realtime vs generic UI hooks) and `src/lib/{api,exam,grading,learning-goals,parsing,utils}/`. - `use-exam.ts` — `useExam(id)`, `useTasks(id)` queries - `use-sections.ts` — `useSections(id)`, `useSectionBlocks(id)`, `useSectionFigures(id)` @@ -59,39 +61,41 @@ All server state flows through **React Query** hooks in `src/hooks/`. Each hook - `use-exam-progress.ts` — evaluation progress via the exam SSE `progress` event - `use-learning-goals.ts` — `useExamLearningGoals(examId)` resolved goals via the backend LGH proxy, `useLghCourses()` course picker data, `goalsByIds(all, ids)` resolver -**Realtime** is SSE: `src/lib/sse.ts` (`subscribeExam`, `subscribeExamsList`) opens an `EventSource` (token via `?token=` query param) and invalidates React Query caches on each event. Domain types are hand-defined in `src/lib/exam-helpers.ts` / `src/lib/grading.ts` (the old auto-generated Supabase DB types are gone). +**Realtime** is SSE: `src/lib/api/sse.ts` (`subscribeExam`, `subscribeExamsList`) opens an `EventSource` (token via `?token=` query param) and invalidates React Query caches on each event. Domain types are hand-defined in `src/lib/exam/exam-helpers.ts` / `src/lib/grading/grading.ts` (the old auto-generated Supabase DB types are gone). ### Core Domain Types -Defined in `src/lib/exam-helpers.ts`: +Defined in `src/lib/exam/exam-helpers.ts`: - **Exam** — status lifecycle: `parsing → draft → ready → evaluating → grading → finished` (or `failed`) - **Task** — types: `single_choice`, `multiple_choice`, `text`; belongs to an exam and optionally a section - **Section** — groups tasks with context blocks and figure blocks, ordered by position - **BlockItem** — union type produced by `mergeSectionItems()` to interleave tasks, context blocks, and figure blocks by position for rendering -Grading logic in `src/lib/grading.ts`: `autoGradeChoiceTask()` scores MC questions; `effectiveScore()` merges auto/manual grades. +Grading logic in `src/lib/grading/grading.ts`: `autoGradeChoiceTask()` scores MC questions; `effectiveScore()` merges auto/manual grades. ### Learning Goals Derived automatically per task by the external **LearningGoalHub** (LGH) service (`apps/learninggoalhub`, VPN-only; base URL via backend env `LGH_BASE_URL`). Flow: (1) at exam creation the user links an LGH course in `StartExamDialog` (`exams.lgh_course_id`, optional — skipping it disables goal insights); (2) when a section is confirmed, the backend (`app/lgh/TaskGoalGenerationService`, `lghExecutor` pool, CAS lock on `sections.goals_started_at`) posts the section's context blocks + tasks to LGH's `POST /api/courses/{id}/exam-tasks/learning-goals` and stores the returned goal ids on `tasks.learning_goal_ids` (jsonb, ids only); re-confirm deletes the old LGH goals first (LGH doesn't dedup), unconfirm clears + best-effort deletes; (3) goals render read-only in the grading view (`ReadOnlyTaskCard`) and roll up per goal on the results "Learning Goals" tab (`LearningGoalsCard`). -Goal text/Bloom/SOLO is resolved at render through the backend proxy (`GET /api/exams/{id}/learning-goals`, `GET /api/lgh/courses` — `app/lgh/LghController`); when LGH is unreachable the UI degrades to "Goal #id" placeholders (metrics still work, ids live on our tasks). Frontend types in `src/lib/learning-goals.ts`; the SSE `tasks` event signals goal generation finished. +Goal text/Bloom/SOLO is resolved at render through the backend proxy (`GET /api/exams/{id}/learning-goals`, `GET /api/lgh/courses` — `app/lgh/LghController`); when LGH is unreachable the UI degrades to "Goal #id" placeholders (metrics still work, ids live on our tasks). Frontend types in `src/lib/learning-goals/learning-goals.ts`; the SSE `tasks` event signals goal generation finished. ### Page Routes (React Router v6) -- `/` — landing page +- `/` — redirects to `/exams` - `/exams` — exam list/management -- `/exam/:id/edit` — drag-and-drop exam editor (dnd-kit for reordering) -- `/exam/:id/grade` — AI answer grading interface -- `/exam/:id/results` — results dashboard (Recharts) +- `/exams/:id/edit` — drag-and-drop exam editor (dnd-kit for reordering) +- `/exams/:id/grade` — AI answer grading interface +- `/exams/:id/results` — results dashboard (Recharts) +- `/admin` — internal review dashboard ### Component Organization +Page-centric colocation: **`src/components/` holds only shared, reusable code; every page owns its private components under `src/pages//components/`.** The rule is self-explaining — a component in `components/` is shared across views; one under `pages/X/` is private to page X. Reusing a page-private component elsewhere means deliberately promoting it into `components/shared/`. + - `src/components/ui/` — shadcn/ui primitives (do not edit manually; managed by shadcn CLI) -- `src/components/exam-edit/` — exam editor components including `grading/` subdirectory -- `src/components/exam-results/` — results dashboard cards -- `src/components/landing/` — marketing/landing page sections +- `src/components/shared/` — cross-page building blocks: `exam-content/` (section/block renderers shared by edit, grading, and results — incl. `read-only/` variants and `BlockHeader`), `chrome/` (sticky header/footer shell + `HelpDialog`/`IntroStepGuide`), and shared atoms (`ModelLogo`, `ThemeToggle`) +- `src/pages//.tsx` — the route entry; `src/pages//components/` — that page's private components. Pages: `exams/` (+ `start-exam/` creation wizard), `exam-edit/`, `exam-grading/` (`GradingView` + `GradeRoute`), `exam-results/`, `admin/` ### Styling @@ -99,7 +103,7 @@ HESTIA design system built on Tailwind with custom HSL color tokens, defined in ### Copy / strings -The app is **English-only** — there is no i18n layer. UI strings are hardcoded directly in components. Shared enum labels live in `src/lib/labels.ts` (task types, Bloom/SOLO levels, grade sources); reuse these instead of re-inlining. Note: the per-exam **content** `language` field (`en`/`de`/`other`, set in `StartExamDialog`) is unrelated to UI copy — it only tells the backend solver which language to answer that exam's PDF in. +The app is **English-only** — there is no i18n layer. UI strings are hardcoded directly in components. Shared enum labels live in `src/lib/exam/labels.ts` (task types, Bloom/SOLO levels, grade sources); reuse these instead of re-inlining. Note: the per-exam **content** `language` field (`en`/`de`/`other`, set in `StartExamDialog`) is unrelated to UI copy — it only tells the backend solver which language to answer that exam's PDF in. ## Common Prompt Phrases & File Pointers @@ -109,69 +113,68 @@ When the user uses any of these phrases, treat them as references to the listed ### Top-level views / "modes" -- **"Landing"**, **"Home"**, **"Hero"**, **"Landing Page"** → `src/pages/Index.tsx` and `src/components/landing/` -- **"Dashboard"**, **"Exam List"**, **"Your Exams"**, **"My Exams"**, **"Exams Page"** → `src/pages/Exams.tsx` -- **"Evaluate Page"**, **"Start Exam"**, **"Upload PDF"**, **"Create From Scratch"**, **"Parse Exam"** → `src/pages/Evaluate.tsx` (entry); PDF pipeline in `backend/src/main/java/app/parse/` (`ParseExamService`) -- **"Edit View"**, **"Edit Mode"**, **"Editor"**, **"Exam Editor"**, **"Authoring View"** → `src/pages/ExamEdit.tsx` + components under `src/components/exam-edit/` (excluding `grading/`) -- **"Grading View"**, **"Grading Mode"**, **"Grade Mode"**, **"Grade Page"** → `src/pages/GradingView.tsx` (routed via `src/pages/GradeRoute.tsx`) + `src/components/exam-edit/grading/` -- **"Final Overview"**, **"Final Screen"**, **"Scoring Overview"**, **"Results"**, **"Results Dashboard"**, **"Insights"** → `src/pages/ExamResults.tsx` + `src/components/exam-results/` -- **"Admin"**, **"Admin Dashboard"**, **"Feedback Page"** → `src/pages/AdminDashboard.tsx` (route `/admin`). A tabbed dashboard that aggregates internal review data: parsing-quality survey responses and parser performance metrics. Panels live in `src/components/admin/`. -- **"Evaluating View"**, **"Evaluating Screen"** (the "solving in progress" splash) → `src/components/exam-edit/EvaluatingView.tsx` -- **"Parsing View"**, **"Parsing Screen"** (the "reading your exam" splash) → `src/components/exam-edit/EditorLoadingView.tsx` -- **"Intro"**, **"Intro Slide"** (first-time editor intro) → `src/components/exam-edit/IntroSlide.tsx` +- **"Dashboard"**, **"Exam List"**, **"Your Exams"**, **"My Exams"**, **"Exams Page"** → `src/pages/exams/Exams.tsx` + `src/pages/exams/components/` +- **"Start Exam"**, **"Upload PDF"**, **"Create From Scratch"**, **"Parse Exam"** → `src/pages/exams/start-exam/StartExamDialog.tsx` (the creation wizard); PDF pipeline in `backend/src/main/java/app/parse/` (`ParseExamService`) +- **"Edit View"**, **"Edit Mode"**, **"Editor"**, **"Exam Editor"**, **"Authoring View"** → `src/pages/exam-edit/ExamEdit.tsx` + `src/pages/exam-edit/components/` (shared section/block renderers live in `src/components/shared/exam-content/` and shell chrome in `src/components/shared/chrome/`) +- **"Grading View"**, **"Grading Mode"**, **"Grade Mode"**, **"Grade Page"** → `src/pages/exam-grading/GradingView.tsx` (routed via `src/pages/exam-grading/GradeRoute.tsx`) + `src/pages/exam-grading/components/` and the shared read-only renderers in `src/components/shared/exam-content/read-only/` +- **"Final Overview"**, **"Final Screen"**, **"Scoring Overview"**, **"Results"**, **"Results Dashboard"**, **"Insights"** → `src/pages/exam-results/ExamResults.tsx` + `src/pages/exam-results/components/` +- **"Admin"**, **"Admin Dashboard"**, **"Feedback Page"** → `src/pages/admin/AdminDashboard.tsx` (route `/admin`). A tabbed dashboard that aggregates internal review data: parsing-quality survey responses and parser performance metrics. Panels live in `src/pages/admin/components/`. +- **"Evaluating View"**, **"Evaluating Screen"** (the "solving in progress" splash) → `src/pages/exam-edit/components/EvaluatingView.tsx` +- **"Parsing View"**, **"Parsing Screen"** (the "reading your exam" splash) → `src/components/shared/exam-content/EditorLoadingView.tsx` +- **"Intro"**, **"Intro Slide"** (first-time editor intro) → `src/pages/exam-edit/components/IntroSlide.tsx` ### Shared section / block UI (used by Edit and Grading) -- **"Section Layout"**, **"Section Card"** (legacy name — now flat) → `src/components/exam-edit/SectionLayout.tsx` -- **"Section Title"**, **"Section Name"** (editable) → `src/components/exam-edit/SectionTitleInput.tsx` -- **"Section Tabs"**, **"Tabs Bar"** (top tabs above the section) → `src/components/exam-edit/SectionTabs.tsx` -- **"Carousel"**, **"Slide"** (single-section container) → `src/components/exam-edit/SectionCarousel.tsx` -- **"Confirm Button"**, **"Confirm Section"**, **"Section Status Chip"** → `src/components/exam-edit/ConfirmSectionButton.tsx` -- **"Block Row"**, **"Task Row"**, **"Collapsed Row"** → `src/components/exam-edit/BlockRow.tsx` (uses `BlockHeader.tsx` for the row layout) -- **"Add Task"**, **"+ Add"**, **"Add Block"** (inline popover) → `src/components/exam-edit/AddTaskInline.tsx` -- **"Chrome Header"**, **"Top Bar"**, **"Sticky Header"** → `src/components/exam-edit/chrome/ChromeHeader.tsx` -- **"Chrome Footer"**, **"Bottom Bar"**, **"Status Bar"** → `src/components/exam-edit/chrome/ChromeFooter.tsx` -- **"Utility Cluster"**, **"Header Actions"** (right-side controls) → `src/components/exam-edit/chrome/ChromeUtilityCluster.tsx` -- **"Save Status"**, **"Saving Indicator"** → `src/components/SaveStatus.tsx` +- **"Section Layout"**, **"Section Card"** (legacy name — now flat) → `src/components/shared/exam-content/SectionLayout.tsx` +- **"Section Title"**, **"Section Name"** (editable) → `src/pages/exam-edit/components/SectionTitleInput.tsx` +- **"Section Tabs"**, **"Tabs Bar"** (top tabs above the section) → `src/pages/exam-edit/components/SectionTabs.tsx` +- **"Carousel"**, **"Slide"** (single-section container) → `src/components/shared/exam-content/SectionCarousel.tsx` +- **"Confirm Button"**, **"Confirm Section"**, **"Section Status Chip"** → `src/pages/exam-edit/components/ConfirmSectionButton.tsx` +- **"Block Row"**, **"Task Row"**, **"Collapsed Row"** → `src/pages/exam-edit/components/BlockRow.tsx` (uses `BlockHeader` from `src/components/shared/exam-content/` for the row layout) +- **"Add Task"**, **"+ Add"**, **"Add Block"** (inline popover) → `src/pages/exam-edit/components/AddTaskInline.tsx` +- **"Chrome Header"**, **"Top Bar"**, **"Sticky Header"** → `src/components/shared/chrome/ChromeHeader.tsx` +- **"Chrome Footer"**, **"Bottom Bar"**, **"Status Bar"** → `src/components/shared/chrome/ChromeFooter.tsx` +- **"Utility Cluster"**, **"Header Actions"** (right-side controls) → `src/components/shared/chrome/ChromeUtilityCluster.tsx` +- **"Save Status"**, **"Saving Indicator"** → `src/pages/exam-edit/components/SaveStatus.tsx` ### Edit-only components -- **"Task Card"** (expanded editor) → `src/components/exam-edit/TaskCard.tsx` -- **"Context Block"** (editable) → `src/components/exam-edit/ContextBlockCard.tsx` -- **"Figure Block"** (editable) → `src/components/exam-edit/FigureBlockCard.tsx` -- **"Item Collapse State"**, **"Expand/Collapse Memory"** → `src/hooks/use-item-collapse-state.ts` -- **"Section Confirmations"** (pre-solve + goal-generation trigger) → `src/hooks/use-section-confirmations.ts` +- **"Task Card"** (expanded editor) → `src/pages/exam-edit/components/TaskCard.tsx` +- **"Context Block"** (editable) → `src/pages/exam-edit/components/ContextBlockCard.tsx` +- **"Figure Block"** (editable) → `src/pages/exam-edit/components/FigureBlockCard.tsx` +- **"Item Collapse State"**, **"Expand/Collapse Memory"** → `src/hooks/ui/use-item-collapse-state.ts` +- **"Section Confirmations"** (pre-solve + goal-generation trigger) → `src/hooks/data/use-section-confirmations.ts` ### Grading-only components -- **"Read-Only Question"**, **"Question Block"** (grading: the static, card-less question — prompt + learning goals + optional reference answer) → `src/components/exam-edit/grading/ReadOnlyQuestionBlock.tsx` -- **"Read-Only Task"**, **"Task Preview"** (the single question+panel card — now used by the **results** view `AllTasksList`; grading composes `ReadOnlyQuestionBlock` + `TaskGradingPanel` instead) → `src/components/exam-edit/grading/ReadOnlyTaskCard.tsx` -- **"Read-Only Context"** → `src/components/exam-edit/grading/ReadOnlyContextBlock.tsx` -- **"Read-Only Figure"** → `src/components/exam-edit/grading/ReadOnlyFigureBlock.tsx` -- **"Grading Panel"**, **"Score Panel"**, **"Manual Grade"** (grading: the gradable AI-answer + score, rendered as the `primary` card) → `src/components/exam-edit/grading/TaskGradingPanel.tsx` +- **"Read-Only Question"**, **"Question Block"** (grading: the static, card-less question — prompt + learning goals + optional reference answer) → `src/pages/exam-grading/components/ReadOnlyQuestionBlock.tsx` +- **"Read-Only Task"**, **"Task Preview"** (the single question+panel card — shared; used by the **results** view `AllTasksList`; grading composes `ReadOnlyQuestionBlock` + `TaskGradingPanel` instead) → `src/components/shared/exam-content/read-only/ReadOnlyTaskCard.tsx` +- **"Read-Only Context"** → `src/components/shared/exam-content/read-only/ReadOnlyContextBlock.tsx` +- **"Read-Only Figure"** → `src/components/shared/exam-content/read-only/ReadOnlyFigureBlock.tsx` +- **"Grading Panel"**, **"Score Panel"**, **"Manual Grade"** (grading: the gradable AI-answer + score, rendered as the `primary` card) → `src/pages/exam-grading/components/TaskGradingPanel.tsx` ### Results dashboard cards -- **"Overall Score"** → `src/components/exam-results/OverallScoreCard.tsx` -- **"By Question Type"** → `src/components/exam-results/ByQuestionTypeCard.tsx` -- **"Task Breakdown"**, **"Per-Task Table"** → `src/components/exam-results/TaskBreakdownTable.tsx` -- **"Task Score Chart"** → `src/components/exam-results/TaskScoreBarChart.tsx` -- **"Learning Goals"** → `src/components/exam-results/LearningGoalsCard.tsx` -- **"Figures Comparison"** → `src/components/exam-results/FiguresComparisonCard.tsx` +- **"Overall Score"** → `src/pages/exam-results/components/OverallScoreCard.tsx` +- **"By Question Type"** → `src/pages/exam-results/components/ByQuestionTypeCard.tsx` +- **"Task Breakdown"**, **"Per-Task Table"** → `src/pages/exam-results/components/TaskBreakdownTable.tsx` +- **"Task Score Chart"** → `src/pages/exam-results/components/TaskScoreBarChart.tsx` +- **"Learning Goals"** → `src/pages/exam-results/components/LearningGoalsCard.tsx` +- **"Figures Comparison"** → `src/pages/exam-results/components/FiguresComparisonCard.tsx` ### Domain / data -- **"Exam Type"**, **"Task Type"**, **"Section Type"**, **"Block Item"** → `src/lib/exam-helpers.ts` -- **"Auto Grade"**, **"Effective Score"**, **"Grading Logic"** → `src/lib/grading.ts` -- **"Solver Model"**, **"AI Model"**, **"LLM Models"** → `src/lib/llm-models.ts` -- **"Learning Goals"**, **"Goals API"**, **"LGH"**, **"LearningGoalHub"**, **"Bloom"**, **"SOLO"** → `src/lib/learning-goals.ts` (types), `src/hooks/use-learning-goals.ts` (hooks); backend integration in `backend/src/main/java/app/lgh/` -- **"Backend Client"**, **"Spring Boot API"**, **"API Client"** → `src/lib/api-client.ts` -- **"Realtime"**, **"SSE"**, **"Progress Channel"** → `src/lib/sse.ts`, `src/hooks/use-exam-progress.ts` +- **"Exam Type"**, **"Task Type"**, **"Section Type"**, **"Block Item"** → `src/lib/exam/exam-helpers.ts` +- **"Auto Grade"**, **"Effective Score"**, **"Grading Logic"** → `src/lib/grading/grading.ts` +- **"Solver Model"**, **"AI Model"**, **"LLM Models"** → `src/lib/exam/llm-models.ts` +- **"Learning Goals"**, **"Goals API"**, **"LGH"**, **"LearningGoalHub"**, **"Bloom"**, **"SOLO"** → `src/lib/learning-goals/learning-goals.ts` (types), `src/hooks/data/use-learning-goals.ts` (hooks); backend integration in `backend/src/main/java/app/lgh/` +- **"Backend Client"**, **"Spring Boot API"**, **"API Client"** → `src/lib/api/api-client.ts` +- **"Realtime"**, **"SSE"**, **"Progress Channel"** → `src/lib/api/sse.ts`, `src/hooks/data/use-exam-progress.ts` ### Styling / system - **"Design Tokens"**, **"HESTIA Tokens"**, **"Theme"** → `src/index.css`, `tailwind.config.ts` -- **"Strings"**, **"Copy"**, **"Labels"** → strings are inlined in components (English-only, no i18n); shared enum labels in `src/lib/labels.ts` +- **"Strings"**, **"Copy"**, **"Labels"** → strings are inlined in components (English-only, no i18n); shared enum labels in `src/lib/exam/labels.ts` - **"shadcn"**, **"UI Primitives"**, **"Design System Components"** → `src/components/ui/` When the requested concept is ambiguous (e.g. "task card" could mean the editable or the read-only variant), ask which view (Edit vs Grading) the user means before editing. diff --git a/apps/examlense/frontend/src/App.tsx b/apps/examlense/frontend/src/App.tsx index bac4d06..25ecbf3 100644 --- a/apps/examlense/frontend/src/App.tsx +++ b/apps/examlense/frontend/src/App.tsx @@ -5,14 +5,11 @@ import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { Navigate } from "react-router-dom"; import NotFound from "./pages/NotFound.tsx"; -import Exams from "./pages/Exams.tsx"; -import ExamEdit from "./pages/ExamEdit.tsx"; -import GradeRoute from "./pages/GradeRoute.tsx"; -import ExamResults from "./pages/ExamResults.tsx"; -import AdminDashboard from "./pages/AdminDashboard.tsx"; -import Auth from "./pages/Auth.tsx"; -import ResetPassword from "./pages/ResetPassword.tsx"; -import { RequireAuth } from "./components/RequireAuth"; +import Exams from "./pages/exams/Exams.tsx"; +import ExamEdit from "./pages/exam-edit/ExamEdit.tsx"; +import GradeRoute from "./pages/exam-grading/GradeRoute.tsx"; +import ExamResults from "./pages/exam-results/ExamResults.tsx"; +import AdminDashboard from "./pages/admin/AdminDashboard.tsx"; const queryClient = new QueryClient(); @@ -26,13 +23,11 @@ const AppRoutes = () => { } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/apps/examlense/frontend/src/components/HestiaMark.tsx b/apps/examlense/frontend/src/components/HestiaMark.tsx deleted file mode 100644 index 17954ac..0000000 --- a/apps/examlense/frontend/src/components/HestiaMark.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import logoLight from "@/assets/logo-light.svg"; -import logoDark from "@/assets/logo-dark.svg"; -import { useTheme } from "@/hooks/use-theme"; - -interface Props { - size?: number; - className?: string; -} - -export const HestiaMark = ({ size = 32, className }: Props) => { - const { theme } = useTheme(); - const src = theme === "dark" ? logoDark : logoLight; - return ( - HESTIA flame mark - ); -}; diff --git a/apps/examlense/frontend/src/components/NavLink.tsx b/apps/examlense/frontend/src/components/NavLink.tsx deleted file mode 100644 index a561a95..0000000 --- a/apps/examlense/frontend/src/components/NavLink.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { NavLink as RouterNavLink, NavLinkProps } from "react-router-dom"; -import { forwardRef } from "react"; -import { cn } from "@/lib/utils"; - -interface NavLinkCompatProps extends Omit { - className?: string; - activeClassName?: string; - pendingClassName?: string; -} - -const NavLink = forwardRef( - ({ className, activeClassName, pendingClassName, to, ...props }, ref) => { - return ( - - cn(className, isActive && activeClassName, isPending && pendingClassName) - } - {...props} - /> - ); - }, -); - -NavLink.displayName = "NavLink"; - -export { NavLink }; diff --git a/apps/examlense/frontend/src/components/RequireAuth.tsx b/apps/examlense/frontend/src/components/RequireAuth.tsx deleted file mode 100644 index f146167..0000000 --- a/apps/examlense/frontend/src/components/RequireAuth.tsx +++ /dev/null @@ -1,4 +0,0 @@ -/** Single-user mode: no auth gate. Kept as a passthrough so routes don't change. */ -export const RequireAuth = ({ children }: { children: React.ReactNode }) => { - return <>{children}; -}; diff --git a/apps/examlense/frontend/src/components/exam-edit/MetaChip.tsx b/apps/examlense/frontend/src/components/exam-edit/MetaChip.tsx deleted file mode 100644 index 05b7b2f..0000000 --- a/apps/examlense/frontend/src/components/exam-edit/MetaChip.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { ReactNode, useState } from "react"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; - -interface Props { - label: string; - value: string | null; - emptyLabel: string; - formatter?: (v: string) => string; - children: (close: () => void) => ReactNode; -} - -export const MetaChip = ({ label, value, emptyLabel, formatter, children }: Props) => { - const [open, setOpen] = useState(false); - const display = value ? (formatter ? formatter(value) : value) : null; - - return ( - - - - - {children(() => setOpen(false))} - - ); -}; \ No newline at end of file diff --git a/apps/examlense/frontend/src/components/landing/Footer.tsx b/apps/examlense/frontend/src/components/landing/Footer.tsx deleted file mode 100644 index c384c8b..0000000 --- a/apps/examlense/frontend/src/components/landing/Footer.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { HestiaMark } from "@/components/HestiaMark"; - -export const Footer = () => { - return ( -
-
-
- - - ExamLense · part of HESTIA - -
-
-
- ); -}; diff --git a/apps/examlense/frontend/src/components/landing/Hero.tsx b/apps/examlense/frontend/src/components/landing/Hero.tsx deleted file mode 100644 index 7f973ff..0000000 --- a/apps/examlense/frontend/src/components/landing/Hero.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { Link } from "react-router-dom"; -import { ArrowRight } from "lucide-react"; - -const scrollToHowItWorks = () => { - const el = document.getElementById("how-it-works"); - if (!el) return; - const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; - el.scrollIntoView({ behavior: reduce ? "auto" : "smooth", block: "start" }); -}; - -export const Hero = () => { - return ( -
-
-

- See which parts of your exam AI can already solve. -

-

- ExamLense helps university instructors stress-test their exams against GPT and Claude. Paste an exam, review the AI-generated solutions, and get resilience metrics for every task. -

-
- - Open your dashboard - - - -
-
-
- ); -}; diff --git a/apps/examlense/frontend/src/components/landing/HowItWorks.tsx b/apps/examlense/frontend/src/components/landing/HowItWorks.tsx deleted file mode 100644 index e5ba1b3..0000000 --- a/apps/examlense/frontend/src/components/landing/HowItWorks.tsx +++ /dev/null @@ -1,52 +0,0 @@ -const STEPS = [ - { - title: "Paste your exam", - body: "Drop in the full text of an existing exam. ExamLense parses it into individual tasks automatically.", - }, - { - title: "Enrich the tasks", - body: "Review the parsed tasks and add metadata like task type, topic, and points.", - }, - { - title: "Generate AI solutions", - body: "Run each task through GPT and Claude in one click.", - }, - { - title: "Grade and get insights", - body: "Score the AI's answers and see which tasks are most vulnerable.", - }, -]; - -export const HowItWorks = () => { - return ( -
-
-

- Workflow -

-

- Four steps from exam to insight. -

-
- -
- {STEPS.map((step, i) => ( -
- - {String(i + 1).padStart(2, "0")} - -

- {step.title} -

-

- {step.body} -

-
- ))} -
-
- ); -}; diff --git a/apps/examlense/frontend/src/components/landing/Navbar.tsx b/apps/examlense/frontend/src/components/landing/Navbar.tsx deleted file mode 100644 index cc0cc28..0000000 --- a/apps/examlense/frontend/src/components/landing/Navbar.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useEffect, useState } from "react"; -import { HestiaMark } from "@/components/HestiaMark"; -import { ThemeToggle } from "@/components/ThemeToggle"; - -export const Navbar = () => { - const [scrolled, setScrolled] = useState(false); - useEffect(() => { - const onScroll = () => setScrolled(window.scrollY > 8); - onScroll(); - window.addEventListener("scroll", onScroll, { passive: true }); - return () => window.removeEventListener("scroll", onScroll); - }, []); - - return ( -
-
-
- - - ExamLense - -
- - -
-
- ); -}; diff --git a/apps/examlense/frontend/src/components/landing/WhyThisMatters.tsx b/apps/examlense/frontend/src/components/landing/WhyThisMatters.tsx deleted file mode 100644 index e275951..0000000 --- a/apps/examlense/frontend/src/components/landing/WhyThisMatters.tsx +++ /dev/null @@ -1,18 +0,0 @@ -export const WhyThisMatters = () => { - return ( -
-
-
-
- ); -}; diff --git a/apps/examlense/frontend/src/components/ModelLogo.tsx b/apps/examlense/frontend/src/components/shared/ModelLogo.tsx similarity index 90% rename from apps/examlense/frontend/src/components/ModelLogo.tsx rename to apps/examlense/frontend/src/components/shared/ModelLogo.tsx index fd7c4c7..e86e35f 100644 --- a/apps/examlense/frontend/src/components/ModelLogo.tsx +++ b/apps/examlense/frontend/src/components/shared/ModelLogo.tsx @@ -1,5 +1,5 @@ -import { cn } from "@/lib/utils"; -import { solverModelMeta } from "@/lib/solver-model-meta"; +import { cn } from "@/lib/utils/utils"; +import { solverModelMeta } from "@/lib/exam/solver-model-meta"; interface Props { modelId: string; diff --git a/apps/examlense/frontend/src/components/ThemeToggle.tsx b/apps/examlense/frontend/src/components/shared/ThemeToggle.tsx similarity index 91% rename from apps/examlense/frontend/src/components/ThemeToggle.tsx rename to apps/examlense/frontend/src/components/shared/ThemeToggle.tsx index 49694c3..ee756ab 100644 --- a/apps/examlense/frontend/src/components/ThemeToggle.tsx +++ b/apps/examlense/frontend/src/components/shared/ThemeToggle.tsx @@ -1,5 +1,5 @@ import { Moon, Sun } from "lucide-react"; -import { useTheme } from "@/hooks/use-theme"; +import { useTheme } from "@/hooks/ui/use-theme"; export const ThemeToggle = () => { const { theme, toggle } = useTheme(); diff --git a/apps/examlense/frontend/src/components/exam-edit/chrome/ChromeFooter.tsx b/apps/examlense/frontend/src/components/shared/chrome/ChromeFooter.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-edit/chrome/ChromeFooter.tsx rename to apps/examlense/frontend/src/components/shared/chrome/ChromeFooter.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/chrome/ChromeHeader.tsx b/apps/examlense/frontend/src/components/shared/chrome/ChromeHeader.tsx similarity index 97% rename from apps/examlense/frontend/src/components/exam-edit/chrome/ChromeHeader.tsx rename to apps/examlense/frontend/src/components/shared/chrome/ChromeHeader.tsx index b5371ee..05391b9 100644 --- a/apps/examlense/frontend/src/components/exam-edit/chrome/ChromeHeader.tsx +++ b/apps/examlense/frontend/src/components/shared/chrome/ChromeHeader.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from "react"; import { Link } from "react-router-dom"; import { ArrowLeft } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; interface Props { /** Title slot — typically an InlineTitle or StaticTitle. */ diff --git a/apps/examlense/frontend/src/components/exam-edit/chrome/ChromeUtilityCluster.tsx b/apps/examlense/frontend/src/components/shared/chrome/ChromeUtilityCluster.tsx similarity index 87% rename from apps/examlense/frontend/src/components/exam-edit/chrome/ChromeUtilityCluster.tsx rename to apps/examlense/frontend/src/components/shared/chrome/ChromeUtilityCluster.tsx index d3ecba0..cef5660 100644 --- a/apps/examlense/frontend/src/components/exam-edit/chrome/ChromeUtilityCluster.tsx +++ b/apps/examlense/frontend/src/components/shared/chrome/ChromeUtilityCluster.tsx @@ -1,4 +1,4 @@ -import { ThemeToggle } from "@/components/ThemeToggle"; +import { ThemeToggle } from "@/components/shared/ThemeToggle"; import { HelpDialog } from "./HelpDialog"; interface Props { diff --git a/apps/examlense/frontend/src/components/exam-edit/chrome/HelpDialog.tsx b/apps/examlense/frontend/src/components/shared/chrome/HelpDialog.tsx similarity index 98% rename from apps/examlense/frontend/src/components/exam-edit/chrome/HelpDialog.tsx rename to apps/examlense/frontend/src/components/shared/chrome/HelpDialog.tsx index cc5ac37..18a66a9 100644 --- a/apps/examlense/frontend/src/components/exam-edit/chrome/HelpDialog.tsx +++ b/apps/examlense/frontend/src/components/shared/chrome/HelpDialog.tsx @@ -12,7 +12,7 @@ import { StepGuide, EDIT_STEPS, GRADING_STEPS, -} from "../IntroStepGuide"; +} from "./IntroStepGuide"; interface Props { variant: "edit" | "grading"; diff --git a/apps/examlense/frontend/src/components/exam-edit/chrome/InlineTitle.tsx b/apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-edit/chrome/InlineTitle.tsx rename to apps/examlense/frontend/src/components/shared/chrome/InlineTitle.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/IntroStepGuide.tsx b/apps/examlense/frontend/src/components/shared/chrome/IntroStepGuide.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-edit/IntroStepGuide.tsx rename to apps/examlense/frontend/src/components/shared/chrome/IntroStepGuide.tsx index 6816eaf..9cec3ff 100644 --- a/apps/examlense/frontend/src/components/exam-edit/IntroStepGuide.tsx +++ b/apps/examlense/frontend/src/components/shared/chrome/IntroStepGuide.tsx @@ -14,7 +14,7 @@ import { SlidersHorizontal, Sparkles, } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; export interface GuideStep { title: string; diff --git a/apps/examlense/frontend/src/components/exam-edit/BlockCard.tsx b/apps/examlense/frontend/src/components/shared/exam-content/BlockCard.tsx similarity index 98% rename from apps/examlense/frontend/src/components/exam-edit/BlockCard.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/BlockCard.tsx index 5493884..a334eec 100644 --- a/apps/examlense/frontend/src/components/exam-edit/BlockCard.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/BlockCard.tsx @@ -1,5 +1,5 @@ import type { CSSProperties, ReactNode } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; export type BlockCardVariant = "primary" | "muted"; diff --git a/apps/examlense/frontend/src/components/exam-edit/BlockHeader.tsx b/apps/examlense/frontend/src/components/shared/exam-content/BlockHeader.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-edit/BlockHeader.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/BlockHeader.tsx index 4907137..c4da3df 100644 --- a/apps/examlense/frontend/src/components/exam-edit/BlockHeader.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/BlockHeader.tsx @@ -1,6 +1,6 @@ import type { HTMLAttributes, ReactNode } from "react"; import { ChevronDown, ChevronRight, GripVertical, AlertCircle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; interface Props { expanded: boolean; diff --git a/apps/examlense/frontend/src/components/exam-edit/EditorLoadingView.tsx b/apps/examlense/frontend/src/components/shared/exam-content/EditorLoadingView.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-edit/EditorLoadingView.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/EditorLoadingView.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/MarkdownView.tsx b/apps/examlense/frontend/src/components/shared/exam-content/MarkdownView.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-edit/MarkdownView.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/MarkdownView.tsx index 6a45a32..15e56f3 100644 --- a/apps/examlense/frontend/src/components/exam-edit/MarkdownView.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/MarkdownView.tsx @@ -3,7 +3,7 @@ import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; import "katex/dist/katex.min.css"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; interface Props { content: string; diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionCarousel.tsx b/apps/examlense/frontend/src/components/shared/exam-content/SectionCarousel.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-edit/SectionCarousel.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/SectionCarousel.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionFigures.tsx b/apps/examlense/frontend/src/components/shared/exam-content/SectionFigures.tsx similarity index 94% rename from apps/examlense/frontend/src/components/exam-edit/SectionFigures.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/SectionFigures.tsx index 681412d..d040a7f 100644 --- a/apps/examlense/frontend/src/components/exam-edit/SectionFigures.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/SectionFigures.tsx @@ -1,11 +1,11 @@ import { useRef, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { ImagePlus, Trash2, Loader2 } from "lucide-react"; -import { uploadFigure, patchFigure, deleteFigure } from "@/lib/api-client"; -import { useFigureUrl } from "@/hooks/use-figure-url"; -import { useSectionFigures, figuresKey } from "@/hooks/use-sections"; -import type { SectionFigure } from "@/lib/exam-helpers"; -import { useToast } from "@/hooks/use-toast"; +import { uploadFigure, patchFigure, deleteFigure } from "@/lib/api/api-client"; +import { useFigureUrl } from "@/hooks/data/use-figure-url"; +import { useSectionFigures, figuresKey } from "@/hooks/data/use-sections"; +import type { SectionFigure } from "@/lib/exam/exam-helpers"; +import { useToast } from "@/hooks/ui/use-toast"; const MAX_BYTES = 5 * 1024 * 1024; const MIME = ["image/png", "image/jpeg", "image/webp", "image/gif"]; diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionLayout.tsx b/apps/examlense/frontend/src/components/shared/exam-content/SectionLayout.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-edit/SectionLayout.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/SectionLayout.tsx index 7683c21..4b9baec 100644 --- a/apps/examlense/frontend/src/components/exam-edit/SectionLayout.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/SectionLayout.tsx @@ -6,7 +6,7 @@ import { import { GripVertical } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; export type SectionStatus = "draft" | "ready" | "confirmed"; diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionSidebar.tsx b/apps/examlense/frontend/src/components/shared/exam-content/SectionSidebar.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-edit/SectionSidebar.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/SectionSidebar.tsx index a28af8b..6114397 100644 --- a/apps/examlense/frontend/src/components/exam-edit/SectionSidebar.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/SectionSidebar.tsx @@ -2,9 +2,9 @@ import { useMemo, type ReactNode } from "react"; import { Link } from "react-router-dom"; import { ArrowLeft, Plus, X } from "lucide-react"; -import { isSectionReady, totalPoints, type Section, type Task } from "@/lib/exam-helpers"; -import { effectiveScore, type TaskAnswer, type TaskGrade } from "@/lib/grading"; -import { cn } from "@/lib/utils"; +import { isSectionReady, totalPoints, type Section, type Task } from "@/lib/exam/exam-helpers"; +import { effectiveScore, type TaskAnswer, type TaskGrade } from "@/lib/grading/grading"; +import { cn } from "@/lib/utils/utils"; export type EntryStatus = | "draft" diff --git a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyContextBlock.tsx b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyContextBlock.tsx similarity index 85% rename from apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyContextBlock.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyContextBlock.tsx index 487da82..2563a58 100644 --- a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyContextBlock.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyContextBlock.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; -import type { SectionBlock } from "@/lib/exam-helpers"; +import type { SectionBlock } from "@/lib/exam/exam-helpers"; import { MarkdownView } from "../MarkdownView"; -import { BlockHeader } from "../BlockHeader"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; interface Props { block: SectionBlock; diff --git a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyFigureBlock.tsx b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyFigureBlock.tsx similarity index 83% rename from apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyFigureBlock.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyFigureBlock.tsx index d5cc5a2..cd4d4a1 100644 --- a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyFigureBlock.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyFigureBlock.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; -import { useFigureUrl } from "@/hooks/use-figure-url"; -import { useSectionFigures } from "@/hooks/use-sections"; -import type { SectionBlock, SectionFigure } from "@/lib/exam-helpers"; -import { BlockHeader } from "../BlockHeader"; +import { useFigureUrl } from "@/hooks/data/use-figure-url"; +import { useSectionFigures } from "@/hooks/data/use-sections"; +import type { SectionBlock, SectionFigure } from "@/lib/exam/exam-helpers"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; interface Props { block: SectionBlock; diff --git a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyTaskCard.tsx b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyTaskCard.tsx similarity index 95% rename from apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyTaskCard.tsx rename to apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyTaskCard.tsx index 5e1091b..5cdd464 100644 --- a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyTaskCard.tsx +++ b/apps/examlense/frontend/src/components/shared/exam-content/read-only/ReadOnlyTaskCard.tsx @@ -6,13 +6,13 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { cn } from "@/lib/utils"; -import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/labels"; -import type { Task } from "@/lib/exam-helpers"; -import type { TaskAnswer } from "@/lib/grading"; -import type { BloomLevel, SoloLevel } from "@/lib/learning-goals"; +import { cn } from "@/lib/utils/utils"; +import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/exam/labels"; +import type { Task } from "@/lib/exam/exam-helpers"; +import type { TaskAnswer } from "@/lib/grading/grading"; +import type { BloomLevel, SoloLevel } from "@/lib/learning-goals/learning-goals"; import { MarkdownView } from "../MarkdownView"; -import { BlockHeader } from "../BlockHeader"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; import { BlockCard } from "../BlockCard"; /** A resolved (or placeholder, when LGH is unreachable) learning goal to display. */ diff --git a/apps/examlense/frontend/src/components/ui/accordion.tsx b/apps/examlense/frontend/src/components/ui/accordion.tsx index 1e7878c..bd93ec2 100644 --- a/apps/examlense/frontend/src/components/ui/accordion.tsx +++ b/apps/examlense/frontend/src/components/ui/accordion.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as AccordionPrimitive from "@radix-ui/react-accordion"; import { ChevronDown } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Accordion = AccordionPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/alert-dialog.tsx b/apps/examlense/frontend/src/components/ui/alert-dialog.tsx index 6dfbfb4..e594eee 100644 --- a/apps/examlense/frontend/src/components/ui/alert-dialog.tsx +++ b/apps/examlense/frontend/src/components/ui/alert-dialog.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { buttonVariants } from "@/components/ui/button"; const AlertDialog = AlertDialogPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/alert.tsx b/apps/examlense/frontend/src/components/ui/alert.tsx index 2efc3c8..fdaf0c4 100644 --- a/apps/examlense/frontend/src/components/ui/alert.tsx +++ b/apps/examlense/frontend/src/components/ui/alert.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const alertVariants = cva( "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", diff --git a/apps/examlense/frontend/src/components/ui/avatar.tsx b/apps/examlense/frontend/src/components/ui/avatar.tsx index 68d21bb..4cc846b 100644 --- a/apps/examlense/frontend/src/components/ui/avatar.tsx +++ b/apps/examlense/frontend/src/components/ui/avatar.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as AvatarPrimitive from "@radix-ui/react-avatar"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Avatar = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/badge.tsx b/apps/examlense/frontend/src/components/ui/badge.tsx index 0853c44..1065325 100644 --- a/apps/examlense/frontend/src/components/ui/badge.tsx +++ b/apps/examlense/frontend/src/components/ui/badge.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const badgeVariants = cva( "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", diff --git a/apps/examlense/frontend/src/components/ui/breadcrumb.tsx b/apps/examlense/frontend/src/components/ui/breadcrumb.tsx index ca91ff5..a3cfc7d 100644 --- a/apps/examlense/frontend/src/components/ui/breadcrumb.tsx +++ b/apps/examlense/frontend/src/components/ui/breadcrumb.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { ChevronRight, MoreHorizontal } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Breadcrumb = React.forwardRef< HTMLElement, diff --git a/apps/examlense/frontend/src/components/ui/button.tsx b/apps/examlense/frontend/src/components/ui/button.tsx index cdedd4f..0793267 100644 --- a/apps/examlense/frontend/src/components/ui/button.tsx +++ b/apps/examlense/frontend/src/components/ui/button.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const buttonVariants = cva( "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", diff --git a/apps/examlense/frontend/src/components/ui/calendar.tsx b/apps/examlense/frontend/src/components/ui/calendar.tsx index 900a69e..002eb52 100644 --- a/apps/examlense/frontend/src/components/ui/calendar.tsx +++ b/apps/examlense/frontend/src/components/ui/calendar.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { DayPicker } from "react-day-picker"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { buttonVariants } from "@/components/ui/button"; export type CalendarProps = React.ComponentProps; diff --git a/apps/examlense/frontend/src/components/ui/card.tsx b/apps/examlense/frontend/src/components/ui/card.tsx index e282748..f8eaeae 100644 --- a/apps/examlense/frontend/src/components/ui/card.tsx +++ b/apps/examlense/frontend/src/components/ui/card.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Card = React.forwardRef>(({ className, ...props }, ref) => (
diff --git a/apps/examlense/frontend/src/components/ui/carousel.tsx b/apps/examlense/frontend/src/components/ui/carousel.tsx index 3aa0f31..1b8161c 100644 --- a/apps/examlense/frontend/src/components/ui/carousel.tsx +++ b/apps/examlense/frontend/src/components/ui/carousel.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import useEmblaCarousel, { type UseEmblaCarouselType } from "embla-carousel-react"; import { ArrowLeft, ArrowRight } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { Button } from "@/components/ui/button"; type CarouselApi = UseEmblaCarouselType[1]; diff --git a/apps/examlense/frontend/src/components/ui/chart.tsx b/apps/examlense/frontend/src/components/ui/chart.tsx index 08d40d9..b444cd5 100644 --- a/apps/examlense/frontend/src/components/ui/chart.tsx +++ b/apps/examlense/frontend/src/components/ui/chart.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as RechartsPrimitive from "recharts"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; // Format: { THEME_NAME: CSS_SELECTOR } const THEMES = { light: "", dark: ".dark" } as const; diff --git a/apps/examlense/frontend/src/components/ui/checkbox.tsx b/apps/examlense/frontend/src/components/ui/checkbox.tsx index 759a41c..14cc306 100644 --- a/apps/examlense/frontend/src/components/ui/checkbox.tsx +++ b/apps/examlense/frontend/src/components/ui/checkbox.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; import { Check } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Checkbox = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/command.tsx b/apps/examlense/frontend/src/components/ui/command.tsx index 68d5378..ab5d0ca 100644 --- a/apps/examlense/frontend/src/components/ui/command.tsx +++ b/apps/examlense/frontend/src/components/ui/command.tsx @@ -3,7 +3,7 @@ import { type DialogProps } from "@radix-ui/react-dialog"; import { Command as CommandPrimitive } from "cmdk"; import { Search } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { Dialog, DialogContent } from "@/components/ui/dialog"; const Command = React.forwardRef< diff --git a/apps/examlense/frontend/src/components/ui/context-menu.tsx b/apps/examlense/frontend/src/components/ui/context-menu.tsx index b5d9db0..847bf0f 100644 --- a/apps/examlense/frontend/src/components/ui/context-menu.tsx +++ b/apps/examlense/frontend/src/components/ui/context-menu.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const ContextMenu = ContextMenuPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/dialog.tsx b/apps/examlense/frontend/src/components/ui/dialog.tsx index 8249e2f..879e7ac 100644 --- a/apps/examlense/frontend/src/components/ui/dialog.tsx +++ b/apps/examlense/frontend/src/components/ui/dialog.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Dialog = DialogPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/drawer.tsx b/apps/examlense/frontend/src/components/ui/drawer.tsx index a2a435d..a9bb654 100644 --- a/apps/examlense/frontend/src/components/ui/drawer.tsx +++ b/apps/examlense/frontend/src/components/ui/drawer.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { Drawer as DrawerPrimitive } from "vaul"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Drawer = ({ shouldScaleBackground = true, ...props }: React.ComponentProps) => ( diff --git a/apps/examlense/frontend/src/components/ui/dropdown-menu.tsx b/apps/examlense/frontend/src/components/ui/dropdown-menu.tsx index ddabcfd..ee0f27d 100644 --- a/apps/examlense/frontend/src/components/ui/dropdown-menu.tsx +++ b/apps/examlense/frontend/src/components/ui/dropdown-menu.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const DropdownMenu = DropdownMenuPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/form.tsx b/apps/examlense/frontend/src/components/ui/form.tsx index 439029f..1b7a50b 100644 --- a/apps/examlense/frontend/src/components/ui/form.tsx +++ b/apps/examlense/frontend/src/components/ui/form.tsx @@ -3,7 +3,7 @@ import * as LabelPrimitive from "@radix-ui/react-label"; import { Slot } from "@radix-ui/react-slot"; import { Controller, ControllerProps, FieldPath, FieldValues, FormProvider, useFormContext } from "react-hook-form"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { Label } from "@/components/ui/label"; const Form = FormProvider; diff --git a/apps/examlense/frontend/src/components/ui/hover-card.tsx b/apps/examlense/frontend/src/components/ui/hover-card.tsx index 6ee6892..13aa9b1 100644 --- a/apps/examlense/frontend/src/components/ui/hover-card.tsx +++ b/apps/examlense/frontend/src/components/ui/hover-card.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as HoverCardPrimitive from "@radix-ui/react-hover-card"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const HoverCard = HoverCardPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/input-otp.tsx b/apps/examlense/frontend/src/components/ui/input-otp.tsx index 0e3fa02..645996f 100644 --- a/apps/examlense/frontend/src/components/ui/input-otp.tsx +++ b/apps/examlense/frontend/src/components/ui/input-otp.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { OTPInput, OTPInputContext } from "input-otp"; import { Dot } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const InputOTP = React.forwardRef, React.ComponentPropsWithoutRef>( ({ className, containerClassName, ...props }, ref) => ( diff --git a/apps/examlense/frontend/src/components/ui/input.tsx b/apps/examlense/frontend/src/components/ui/input.tsx index 09700f6..29906a6 100644 --- a/apps/examlense/frontend/src/components/ui/input.tsx +++ b/apps/examlense/frontend/src/components/ui/input.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Input = React.forwardRef>( ({ className, type, ...props }, ref) => { diff --git a/apps/examlense/frontend/src/components/ui/label.tsx b/apps/examlense/frontend/src/components/ui/label.tsx index 80e30ee..454a903 100644 --- a/apps/examlense/frontend/src/components/ui/label.tsx +++ b/apps/examlense/frontend/src/components/ui/label.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as LabelPrimitive from "@radix-ui/react-label"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const labelVariants = cva("text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"); diff --git a/apps/examlense/frontend/src/components/ui/menubar.tsx b/apps/examlense/frontend/src/components/ui/menubar.tsx index 15687e4..6796b13 100644 --- a/apps/examlense/frontend/src/components/ui/menubar.tsx +++ b/apps/examlense/frontend/src/components/ui/menubar.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as MenubarPrimitive from "@radix-ui/react-menubar"; import { Check, ChevronRight, Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const MenubarMenu = MenubarPrimitive.Menu; diff --git a/apps/examlense/frontend/src/components/ui/navigation-menu.tsx b/apps/examlense/frontend/src/components/ui/navigation-menu.tsx index c9fcd7a..092acb4 100644 --- a/apps/examlense/frontend/src/components/ui/navigation-menu.tsx +++ b/apps/examlense/frontend/src/components/ui/navigation-menu.tsx @@ -3,7 +3,7 @@ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"; import { cva } from "class-variance-authority"; import { ChevronDown } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const NavigationMenu = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/pagination.tsx b/apps/examlense/frontend/src/components/ui/pagination.tsx index 31ae1be..b4be618 100644 --- a/apps/examlense/frontend/src/components/ui/pagination.tsx +++ b/apps/examlense/frontend/src/components/ui/pagination.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { ButtonProps, buttonVariants } from "@/components/ui/button"; const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => ( diff --git a/apps/examlense/frontend/src/components/ui/popover.tsx b/apps/examlense/frontend/src/components/ui/popover.tsx index 59dd525..ec96fa5 100644 --- a/apps/examlense/frontend/src/components/ui/popover.tsx +++ b/apps/examlense/frontend/src/components/ui/popover.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as PopoverPrimitive from "@radix-ui/react-popover"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Popover = PopoverPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/progress.tsx b/apps/examlense/frontend/src/components/ui/progress.tsx index f9f131e..7da8dbe 100644 --- a/apps/examlense/frontend/src/components/ui/progress.tsx +++ b/apps/examlense/frontend/src/components/ui/progress.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as ProgressPrimitive from "@radix-ui/react-progress"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Progress = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/radio-group.tsx b/apps/examlense/frontend/src/components/ui/radio-group.tsx index 5d32e58..e035015 100644 --- a/apps/examlense/frontend/src/components/ui/radio-group.tsx +++ b/apps/examlense/frontend/src/components/ui/radio-group.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; import { Circle } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const RadioGroup = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/resizable.tsx b/apps/examlense/frontend/src/components/ui/resizable.tsx index 4a1bd8c..34f0234 100644 --- a/apps/examlense/frontend/src/components/ui/resizable.tsx +++ b/apps/examlense/frontend/src/components/ui/resizable.tsx @@ -1,7 +1,7 @@ import { GripVertical } from "lucide-react"; import * as ResizablePrimitive from "react-resizable-panels"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const ResizablePanelGroup = ({ className, ...props }: React.ComponentProps) => ( , diff --git a/apps/examlense/frontend/src/components/ui/select.tsx b/apps/examlense/frontend/src/components/ui/select.tsx index 6f25254..4fbef46 100644 --- a/apps/examlense/frontend/src/components/ui/select.tsx +++ b/apps/examlense/frontend/src/components/ui/select.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as SelectPrimitive from "@radix-ui/react-select"; import { Check, ChevronDown, ChevronUp } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Select = SelectPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/separator.tsx b/apps/examlense/frontend/src/components/ui/separator.tsx index e20c3f4..fc761c7 100644 --- a/apps/examlense/frontend/src/components/ui/separator.tsx +++ b/apps/examlense/frontend/src/components/ui/separator.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as SeparatorPrimitive from "@radix-ui/react-separator"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Separator = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/sheet.tsx b/apps/examlense/frontend/src/components/ui/sheet.tsx index c414714..5893d31 100644 --- a/apps/examlense/frontend/src/components/ui/sheet.tsx +++ b/apps/examlense/frontend/src/components/ui/sheet.tsx @@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Sheet = SheetPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/sidebar.tsx b/apps/examlense/frontend/src/components/ui/sidebar.tsx index beb1a59..2f38933 100644 --- a/apps/examlense/frontend/src/components/ui/sidebar.tsx +++ b/apps/examlense/frontend/src/components/ui/sidebar.tsx @@ -3,8 +3,8 @@ import { Slot } from "@radix-ui/react-slot"; import { VariantProps, cva } from "class-variance-authority"; import { PanelLeft } from "lucide-react"; -import { useIsMobile } from "@/hooks/use-mobile"; -import { cn } from "@/lib/utils"; +import { useIsMobile } from "@/hooks/ui/use-mobile"; +import { cn } from "@/lib/utils/utils"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; diff --git a/apps/examlense/frontend/src/components/ui/skeleton.tsx b/apps/examlense/frontend/src/components/ui/skeleton.tsx index 6690a13..f5dde22 100644 --- a/apps/examlense/frontend/src/components/ui/skeleton.tsx +++ b/apps/examlense/frontend/src/components/ui/skeleton.tsx @@ -1,4 +1,4 @@ -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; function Skeleton({ className, ...props }: React.HTMLAttributes) { return
; diff --git a/apps/examlense/frontend/src/components/ui/slider.tsx b/apps/examlense/frontend/src/components/ui/slider.tsx index 7f8d06e..20c054b 100644 --- a/apps/examlense/frontend/src/components/ui/slider.tsx +++ b/apps/examlense/frontend/src/components/ui/slider.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as SliderPrimitive from "@radix-ui/react-slider"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Slider = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/switch.tsx b/apps/examlense/frontend/src/components/ui/switch.tsx index a76b761..a7f97a2 100644 --- a/apps/examlense/frontend/src/components/ui/switch.tsx +++ b/apps/examlense/frontend/src/components/ui/switch.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as SwitchPrimitives from "@radix-ui/react-switch"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Switch = React.forwardRef< React.ElementRef, diff --git a/apps/examlense/frontend/src/components/ui/table.tsx b/apps/examlense/frontend/src/components/ui/table.tsx index 991cdbf..da1db78 100644 --- a/apps/examlense/frontend/src/components/ui/table.tsx +++ b/apps/examlense/frontend/src/components/ui/table.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Table = React.forwardRef>( ({ className, ...props }, ref) => ( diff --git a/apps/examlense/frontend/src/components/ui/tabs.tsx b/apps/examlense/frontend/src/components/ui/tabs.tsx index ba20fbb..786bb56 100644 --- a/apps/examlense/frontend/src/components/ui/tabs.tsx +++ b/apps/examlense/frontend/src/components/ui/tabs.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as TabsPrimitive from "@radix-ui/react-tabs"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const Tabs = TabsPrimitive.Root; diff --git a/apps/examlense/frontend/src/components/ui/textarea.tsx b/apps/examlense/frontend/src/components/ui/textarea.tsx index 4a5643e..5a8134e 100644 --- a/apps/examlense/frontend/src/components/ui/textarea.tsx +++ b/apps/examlense/frontend/src/components/ui/textarea.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; export interface TextareaProps extends React.TextareaHTMLAttributes {} diff --git a/apps/examlense/frontend/src/components/ui/toast.tsx b/apps/examlense/frontend/src/components/ui/toast.tsx index 0699548..d43b844 100644 --- a/apps/examlense/frontend/src/components/ui/toast.tsx +++ b/apps/examlense/frontend/src/components/ui/toast.tsx @@ -3,7 +3,7 @@ import * as ToastPrimitives from "@radix-ui/react-toast"; import { cva, type VariantProps } from "class-variance-authority"; import { X } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const ToastProvider = ToastPrimitives.Provider; diff --git a/apps/examlense/frontend/src/components/ui/toaster.tsx b/apps/examlense/frontend/src/components/ui/toaster.tsx index 2cba1c8..9874e6c 100644 --- a/apps/examlense/frontend/src/components/ui/toaster.tsx +++ b/apps/examlense/frontend/src/components/ui/toaster.tsx @@ -1,4 +1,4 @@ -import { useToast } from "@/hooks/use-toast"; +import { useToast } from "@/hooks/ui/use-toast"; import { Toast, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport } from "@/components/ui/toast"; export function Toaster() { diff --git a/apps/examlense/frontend/src/components/ui/toggle-group.tsx b/apps/examlense/frontend/src/components/ui/toggle-group.tsx index 8deae16..0cc6693 100644 --- a/apps/examlense/frontend/src/components/ui/toggle-group.tsx +++ b/apps/examlense/frontend/src/components/ui/toggle-group.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"; import { type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { toggleVariants } from "@/components/ui/toggle"; const ToggleGroupContext = React.createContext>({ diff --git a/apps/examlense/frontend/src/components/ui/toggle.tsx b/apps/examlense/frontend/src/components/ui/toggle.tsx index de5dfc5..2421111 100644 --- a/apps/examlense/frontend/src/components/ui/toggle.tsx +++ b/apps/examlense/frontend/src/components/ui/toggle.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import * as TogglePrimitive from "@radix-ui/react-toggle"; import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const toggleVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", diff --git a/apps/examlense/frontend/src/components/ui/tooltip.tsx b/apps/examlense/frontend/src/components/ui/tooltip.tsx index 72ab73c..f64c5de 100644 --- a/apps/examlense/frontend/src/components/ui/tooltip.tsx +++ b/apps/examlense/frontend/src/components/ui/tooltip.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; const TooltipProvider = TooltipPrimitive.Provider; diff --git a/apps/examlense/frontend/src/components/ui/use-toast.ts b/apps/examlense/frontend/src/components/ui/use-toast.ts index b0aef21..5e7355c 100644 --- a/apps/examlense/frontend/src/components/ui/use-toast.ts +++ b/apps/examlense/frontend/src/components/ui/use-toast.ts @@ -1,3 +1,3 @@ -import { useToast, toast } from "@/hooks/use-toast"; +import { useToast, toast } from "@/hooks/ui/use-toast"; export { useToast, toast }; diff --git a/apps/examlense/frontend/src/hooks/use-exam-progress.ts b/apps/examlense/frontend/src/hooks/data/use-exam-progress.ts similarity index 95% rename from apps/examlense/frontend/src/hooks/use-exam-progress.ts rename to apps/examlense/frontend/src/hooks/data/use-exam-progress.ts index eba6830..e5c7f87 100644 --- a/apps/examlense/frontend/src/hooks/use-exam-progress.ts +++ b/apps/examlense/frontend/src/hooks/data/use-exam-progress.ts @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { listAnswers, listTasks } from "@/lib/api-client"; -import { subscribeExam } from "@/lib/sse"; +import { listAnswers, listTasks } from "@/lib/api/api-client"; +import { subscribeExam } from "@/lib/api/sse"; /** * Progress signal for an exam being evaluated. diff --git a/apps/examlense/frontend/src/hooks/use-exam.ts b/apps/examlense/frontend/src/hooks/data/use-exam.ts similarity index 86% rename from apps/examlense/frontend/src/hooks/use-exam.ts rename to apps/examlense/frontend/src/hooks/data/use-exam.ts index d01c27e..3ce68ef 100644 --- a/apps/examlense/frontend/src/hooks/use-exam.ts +++ b/apps/examlense/frontend/src/hooks/data/use-exam.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { getExam, listTasks, ApiError } from "@/lib/api-client"; -import type { Exam, Task } from "@/lib/exam-helpers"; +import { getExam, listTasks, ApiError } from "@/lib/api/api-client"; +import type { Exam, Task } from "@/lib/exam/exam-helpers"; export const examKey = (id: string) => ["exam", id] as const; export const tasksKey = (id: string) => ["tasks", id] as const; diff --git a/apps/examlense/frontend/src/hooks/use-figure-url.ts b/apps/examlense/frontend/src/hooks/data/use-figure-url.ts similarity index 92% rename from apps/examlense/frontend/src/hooks/use-figure-url.ts rename to apps/examlense/frontend/src/hooks/data/use-figure-url.ts index 702194a..2cfbd67 100644 --- a/apps/examlense/frontend/src/hooks/use-figure-url.ts +++ b/apps/examlense/frontend/src/hooks/data/use-figure-url.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { apiBaseUrl, getFigureSignedUrl } from "@/lib/api-client"; +import { apiBaseUrl, getFigureSignedUrl } from "@/lib/api/api-client"; /** * Resolve a short-lived, directly-loadable `` URL for a figure. diff --git a/apps/examlense/frontend/src/hooks/use-learning-goals.ts b/apps/examlense/frontend/src/hooks/data/use-learning-goals.ts similarity index 94% rename from apps/examlense/frontend/src/hooks/use-learning-goals.ts rename to apps/examlense/frontend/src/hooks/data/use-learning-goals.ts index 565d8cd..470c5f3 100644 --- a/apps/examlense/frontend/src/hooks/use-learning-goals.ts +++ b/apps/examlense/frontend/src/hooks/data/use-learning-goals.ts @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { createLghCourse, getExamLearningGoals, listLghCourses } from "@/lib/api-client"; -import type { LearningGoalResponse } from "@/lib/learning-goals"; +import { createLghCourse, getExamLearningGoals, listLghCourses } from "@/lib/api/api-client"; +import type { LearningGoalResponse } from "@/lib/learning-goals/learning-goals"; export const examLearningGoalsKey = (examId: string) => ["exam-learning-goals", examId] as const; diff --git a/apps/examlense/frontend/src/hooks/use-parse-survey.ts b/apps/examlense/frontend/src/hooks/data/use-parse-survey.ts similarity index 94% rename from apps/examlense/frontend/src/hooks/use-parse-survey.ts rename to apps/examlense/frontend/src/hooks/data/use-parse-survey.ts index eb0b23c..a405a1e 100644 --- a/apps/examlense/frontend/src/hooks/use-parse-survey.ts +++ b/apps/examlense/frontend/src/hooks/data/use-parse-survey.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { adminSurvey, adminSurveyByModel } from "@/lib/api-client"; +import { adminSurvey, adminSurveyByModel } from "@/lib/api/api-client"; export interface ParseSurveyRow { id: string; diff --git a/apps/examlense/frontend/src/hooks/use-parsing-metrics.ts b/apps/examlense/frontend/src/hooks/data/use-parsing-metrics.ts similarity index 86% rename from apps/examlense/frontend/src/hooks/use-parsing-metrics.ts rename to apps/examlense/frontend/src/hooks/data/use-parsing-metrics.ts index dd0ed11..3785576 100644 --- a/apps/examlense/frontend/src/hooks/use-parsing-metrics.ts +++ b/apps/examlense/frontend/src/hooks/data/use-parsing-metrics.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { fetchParsingMetrics } from "@/lib/parsing-metrics-api"; +import { fetchParsingMetrics } from "@/lib/api/parsing-metrics-api"; export const parsingMetricsKey = ["admin-parsing-metrics"] as const; diff --git a/apps/examlense/frontend/src/hooks/use-section-confirmations.ts b/apps/examlense/frontend/src/hooks/data/use-section-confirmations.ts similarity index 95% rename from apps/examlense/frontend/src/hooks/use-section-confirmations.ts rename to apps/examlense/frontend/src/hooks/data/use-section-confirmations.ts index 88e925d..1df2cd8 100644 --- a/apps/examlense/frontend/src/hooks/use-section-confirmations.ts +++ b/apps/examlense/frontend/src/hooks/data/use-section-confirmations.ts @@ -3,11 +3,11 @@ import { useQueryClient } from "@tanstack/react-query"; import { confirmSection as apiConfirmSection, unconfirmSection as apiUnconfirmSection, -} from "@/lib/api-client"; -import { sectionsKey } from "@/hooks/use-sections"; -import { taskAnswersKey } from "@/hooks/use-task-answers"; -import type { Section, Task } from "@/lib/exam-helpers"; -import { solveSection } from "@/lib/api-solve"; +} from "@/lib/api/api-client"; +import { sectionsKey } from "@/hooks/data/use-sections"; +import { taskAnswersKey } from "@/hooks/data/use-task-answers"; +import type { Section, Task } from "@/lib/exam/exam-helpers"; +import { solveSection } from "@/lib/api/api-solve"; /** * DB-backed section confirmation. diff --git a/apps/examlense/frontend/src/hooks/use-sections.ts b/apps/examlense/frontend/src/hooks/data/use-sections.ts similarity index 97% rename from apps/examlense/frontend/src/hooks/use-sections.ts rename to apps/examlense/frontend/src/hooks/data/use-sections.ts index 233f319..1c0b825 100644 --- a/apps/examlense/frontend/src/hooks/use-sections.ts +++ b/apps/examlense/frontend/src/hooks/data/use-sections.ts @@ -1,6 +1,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { listSections, listBlocks, listFigures } from "@/lib/api-client"; -import type { Section, SectionBlock, SectionFigure } from "@/lib/exam-helpers"; +import { listSections, listBlocks, listFigures } from "@/lib/api/api-client"; +import type { Section, SectionBlock, SectionFigure } from "@/lib/exam/exam-helpers"; export const sectionsKey = (examId: string) => ["sections", examId] as const; export const blocksKey = (examId: string) => ["section-blocks", examId] as const; diff --git a/apps/examlense/frontend/src/hooks/use-task-answers.ts b/apps/examlense/frontend/src/hooks/data/use-task-answers.ts similarity index 78% rename from apps/examlense/frontend/src/hooks/use-task-answers.ts rename to apps/examlense/frontend/src/hooks/data/use-task-answers.ts index aa8fd8f..10d19e0 100644 --- a/apps/examlense/frontend/src/hooks/use-task-answers.ts +++ b/apps/examlense/frontend/src/hooks/data/use-task-answers.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; -import { listAnswers } from "@/lib/api-client"; -import type { TaskAnswer } from "@/lib/grading"; +import { listAnswers } from "@/lib/api/api-client"; +import type { TaskAnswer } from "@/lib/grading/grading"; export const taskAnswersKey = (examId: string) => ["task-answers", examId] as const; diff --git a/apps/examlense/frontend/src/hooks/use-task-grades.ts b/apps/examlense/frontend/src/hooks/data/use-task-grades.ts similarity index 90% rename from apps/examlense/frontend/src/hooks/use-task-grades.ts rename to apps/examlense/frontend/src/hooks/data/use-task-grades.ts index 2c6daa1..bf6964e 100644 --- a/apps/examlense/frontend/src/hooks/use-task-grades.ts +++ b/apps/examlense/frontend/src/hooks/data/use-task-grades.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { listGrades, upsertGrade } from "@/lib/api-client"; -import type { TaskGrade } from "@/lib/grading"; +import { listGrades, upsertGrade } from "@/lib/api/api-client"; +import type { TaskGrade } from "@/lib/grading/grading"; export const taskGradesKey = (examId: string) => ["task-grades", examId] as const; diff --git a/apps/examlense/frontend/src/hooks/use-autosize-textarea.ts b/apps/examlense/frontend/src/hooks/ui/use-autosize-textarea.ts similarity index 100% rename from apps/examlense/frontend/src/hooks/use-autosize-textarea.ts rename to apps/examlense/frontend/src/hooks/ui/use-autosize-textarea.ts diff --git a/apps/examlense/frontend/src/hooks/use-debounced-callback.ts b/apps/examlense/frontend/src/hooks/ui/use-debounced-callback.ts similarity index 100% rename from apps/examlense/frontend/src/hooks/use-debounced-callback.ts rename to apps/examlense/frontend/src/hooks/ui/use-debounced-callback.ts diff --git a/apps/examlense/frontend/src/hooks/use-item-collapse-state.ts b/apps/examlense/frontend/src/hooks/ui/use-item-collapse-state.ts similarity index 100% rename from apps/examlense/frontend/src/hooks/use-item-collapse-state.ts rename to apps/examlense/frontend/src/hooks/ui/use-item-collapse-state.ts diff --git a/apps/examlense/frontend/src/hooks/use-mobile.tsx b/apps/examlense/frontend/src/hooks/ui/use-mobile.tsx similarity index 100% rename from apps/examlense/frontend/src/hooks/use-mobile.tsx rename to apps/examlense/frontend/src/hooks/ui/use-mobile.tsx diff --git a/apps/examlense/frontend/src/hooks/use-theme.ts b/apps/examlense/frontend/src/hooks/ui/use-theme.ts similarity index 100% rename from apps/examlense/frontend/src/hooks/use-theme.ts rename to apps/examlense/frontend/src/hooks/ui/use-theme.ts diff --git a/apps/examlense/frontend/src/hooks/use-toast.ts b/apps/examlense/frontend/src/hooks/ui/use-toast.ts similarity index 100% rename from apps/examlense/frontend/src/hooks/use-toast.ts rename to apps/examlense/frontend/src/hooks/ui/use-toast.ts diff --git a/apps/examlense/frontend/src/lib/api-client.ts b/apps/examlense/frontend/src/lib/api/api-client.ts similarity index 98% rename from apps/examlense/frontend/src/lib/api-client.ts rename to apps/examlense/frontend/src/lib/api/api-client.ts index 192495b..296d02b 100644 --- a/apps/examlense/frontend/src/lib/api-client.ts +++ b/apps/examlense/frontend/src/lib/api/api-client.ts @@ -6,9 +6,9 @@ * * Base URL comes from `VITE_API_BASE_URL` (default http://localhost:8081). */ -import type { Exam, Section, SectionBlock, SectionFigure, Task } from "@/lib/exam-helpers"; -import type { TaskAnswer, TaskGrade } from "@/lib/grading"; -import type { BloomLevel, LearningGoalResponse, LghCourse, SoloLevel } from "@/lib/learning-goals"; +import type { Exam, Section, SectionBlock, SectionFigure, Task } from "@/lib/exam/exam-helpers"; +import type { TaskAnswer, TaskGrade } from "@/lib/grading/grading"; +import type { BloomLevel, LearningGoalResponse, LghCourse, SoloLevel } from "@/lib/learning-goals/learning-goals"; const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? "").replace(/\/$/, "") || "http://localhost:8081"; diff --git a/apps/examlense/frontend/src/lib/api-models.ts b/apps/examlense/frontend/src/lib/api/api-models.ts similarity index 98% rename from apps/examlense/frontend/src/lib/api-models.ts rename to apps/examlense/frontend/src/lib/api/api-models.ts index cbee51a..e7e56a8 100644 --- a/apps/examlense/frontend/src/lib/api-models.ts +++ b/apps/examlense/frontend/src/lib/api/api-models.ts @@ -15,14 +15,14 @@ import { apiRequest, ApiClientNotConfiguredError, isApiClientConfigured, -} from "@/lib/api-client"; +} from "@/lib/api/api-client"; import { DEFAULT_PARSER_MODEL_ID, DEFAULT_SOLVER_MODEL_ID, PARSER_MODELS, SOLVER_MODELS, type LlmModel, -} from "@/lib/llm-models"; +} from "@/lib/exam/llm-models"; interface ModelListResponse { models: Array<{ id: string; label: string; description?: string | null }>; diff --git a/apps/examlense/frontend/src/lib/api-parse.ts b/apps/examlense/frontend/src/lib/api/api-parse.ts similarity index 94% rename from apps/examlense/frontend/src/lib/api-parse.ts rename to apps/examlense/frontend/src/lib/api/api-parse.ts index 62ff2ff..66bd734 100644 --- a/apps/examlense/frontend/src/lib/api-parse.ts +++ b/apps/examlense/frontend/src/lib/api/api-parse.ts @@ -4,7 +4,7 @@ * The backend is now the only transport (Supabase is gone), so this always * routes to `POST /api/parse-exam-pdf`. */ -import { apiRequest } from "@/lib/api-client"; +import { apiRequest } from "@/lib/api/api-client"; export interface ParseExamArgs { examId: string; diff --git a/apps/examlense/frontend/src/lib/api-solve.ts b/apps/examlense/frontend/src/lib/api/api-solve.ts similarity index 95% rename from apps/examlense/frontend/src/lib/api-solve.ts rename to apps/examlense/frontend/src/lib/api/api-solve.ts index 11bd5ac..a054978 100644 --- a/apps/examlense/frontend/src/lib/api-solve.ts +++ b/apps/examlense/frontend/src/lib/api/api-solve.ts @@ -4,7 +4,7 @@ * The backend is now the only transport (Supabase is gone), so these always * route to Spring. */ -import { apiRequest } from "@/lib/api-client"; +import { apiRequest } from "@/lib/api/api-client"; /** * Generate an answer for a single task. diff --git a/apps/examlense/frontend/src/lib/parsing-metrics-api.ts b/apps/examlense/frontend/src/lib/api/parsing-metrics-api.ts similarity index 86% rename from apps/examlense/frontend/src/lib/parsing-metrics-api.ts rename to apps/examlense/frontend/src/lib/api/parsing-metrics-api.ts index 74266d6..317017a 100644 --- a/apps/examlense/frontend/src/lib/parsing-metrics-api.ts +++ b/apps/examlense/frontend/src/lib/api/parsing-metrics-api.ts @@ -6,8 +6,8 @@ * bundled mock so previews still render. Callers only depend on * `fetchParsingMetrics()` returning `Promise`. */ -import { apiRequest, isApiClientConfigured } from "@/lib/api-client"; -import { MOCK_PARSING_METRICS, type ParsingMetrics } from "@/lib/parsing-metrics"; +import { apiRequest, isApiClientConfigured } from "@/lib/api/api-client"; +import { MOCK_PARSING_METRICS, type ParsingMetrics } from "@/lib/parsing/parsing-metrics"; export async function fetchParsingMetrics(): Promise { if (isApiClientConfigured()) { diff --git a/apps/examlense/frontend/src/lib/sse.ts b/apps/examlense/frontend/src/lib/api/sse.ts similarity index 96% rename from apps/examlense/frontend/src/lib/sse.ts rename to apps/examlense/frontend/src/lib/api/sse.ts index ecf42b4..a379b45 100644 --- a/apps/examlense/frontend/src/lib/sse.ts +++ b/apps/examlense/frontend/src/lib/api/sse.ts @@ -4,7 +4,7 @@ * static token rides along as a `?token=` query param (the backend's auth * filter accepts it). EventSource auto-reconnects on transient errors. */ -import { apiBaseUrl, apiToken } from "@/lib/api-client"; +import { apiBaseUrl, apiToken } from "@/lib/api/api-client"; export interface ExamEventHandlers { /** exam status/phase changed — refetch exam + children. */ diff --git a/apps/examlense/frontend/src/lib/exam-helpers.ts b/apps/examlense/frontend/src/lib/exam/exam-helpers.ts similarity index 100% rename from apps/examlense/frontend/src/lib/exam-helpers.ts rename to apps/examlense/frontend/src/lib/exam/exam-helpers.ts diff --git a/apps/examlense/frontend/src/lib/exam-progress.test.ts b/apps/examlense/frontend/src/lib/exam/exam-progress.test.ts similarity index 97% rename from apps/examlense/frontend/src/lib/exam-progress.test.ts rename to apps/examlense/frontend/src/lib/exam/exam-progress.test.ts index 42c34d4..73baf3d 100644 --- a/apps/examlense/frontend/src/lib/exam-progress.test.ts +++ b/apps/examlense/frontend/src/lib/exam/exam-progress.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import type { ExamListItem } from "./api-client"; +import type { ExamListItem } from "@/lib/api/api-client"; import { examProgress, progressSortValue } from "./exam-progress"; const exam = (over: Partial): ExamListItem => ({ diff --git a/apps/examlense/frontend/src/lib/exam-progress.ts b/apps/examlense/frontend/src/lib/exam/exam-progress.ts similarity index 93% rename from apps/examlense/frontend/src/lib/exam-progress.ts rename to apps/examlense/frontend/src/lib/exam/exam-progress.ts index 469702c..0820a0e 100644 --- a/apps/examlense/frontend/src/lib/exam-progress.ts +++ b/apps/examlense/frontend/src/lib/exam/exam-progress.ts @@ -1,5 +1,5 @@ -import type { ExamListItem } from "@/lib/api-client"; -import { parsePhasePercent, parsePhaseLabel } from "@/hooks/use-exam-progress"; +import type { ExamListItem } from "@/lib/api/api-client"; +import { parsePhasePercent, parsePhaseLabel } from "@/hooks/data/use-exam-progress"; /** * Unified "current-phase" progress for an exam row in the dashboard table. diff --git a/apps/examlense/frontend/src/lib/labels.ts b/apps/examlense/frontend/src/lib/exam/labels.ts similarity index 94% rename from apps/examlense/frontend/src/lib/labels.ts rename to apps/examlense/frontend/src/lib/exam/labels.ts index cd262ae..d54bad8 100644 --- a/apps/examlense/frontend/src/lib/labels.ts +++ b/apps/examlense/frontend/src/lib/exam/labels.ts @@ -3,8 +3,8 @@ * catalog (`taskType.*`, `learningGoals.bloom.*`, `learningGoals.solo.*`, * `grading.source.*`); the app is English-only, so they are plain constants. */ -import type { BloomLevel, SoloLevel } from "@/lib/learning-goals"; -import type { TaskType } from "@/lib/exam-helpers"; +import type { BloomLevel, SoloLevel } from "@/lib/learning-goals/learning-goals"; +import type { TaskType } from "@/lib/exam/exam-helpers"; export const TASK_TYPE_LABELS: Record = { single_choice: "Single Choice", diff --git a/apps/examlense/frontend/src/lib/llm-models.ts b/apps/examlense/frontend/src/lib/exam/llm-models.ts similarity index 100% rename from apps/examlense/frontend/src/lib/llm-models.ts rename to apps/examlense/frontend/src/lib/exam/llm-models.ts diff --git a/apps/examlense/frontend/src/lib/solver-model-meta.ts b/apps/examlense/frontend/src/lib/exam/solver-model-meta.ts similarity index 100% rename from apps/examlense/frontend/src/lib/solver-model-meta.ts rename to apps/examlense/frontend/src/lib/exam/solver-model-meta.ts diff --git a/apps/examlense/frontend/src/lib/grading.ts b/apps/examlense/frontend/src/lib/grading/grading.ts similarity index 98% rename from apps/examlense/frontend/src/lib/grading.ts rename to apps/examlense/frontend/src/lib/grading/grading.ts index 770dc1d..ab181cc 100644 --- a/apps/examlense/frontend/src/lib/grading.ts +++ b/apps/examlense/frontend/src/lib/grading/grading.ts @@ -1,4 +1,4 @@ -import type { Task, TaskOption } from "./exam-helpers"; +import type { Task, TaskOption } from "@/lib/exam/exam-helpers"; export interface TaskAnswer { id: string; diff --git a/apps/examlense/frontend/src/lib/resilience.ts b/apps/examlense/frontend/src/lib/grading/resilience.ts similarity index 100% rename from apps/examlense/frontend/src/lib/resilience.ts rename to apps/examlense/frontend/src/lib/grading/resilience.ts diff --git a/apps/examlense/frontend/src/lib/score-color.ts b/apps/examlense/frontend/src/lib/grading/score-color.ts similarity index 100% rename from apps/examlense/frontend/src/lib/score-color.ts rename to apps/examlense/frontend/src/lib/grading/score-color.ts diff --git a/apps/examlense/frontend/src/lib/learning-goals.ts b/apps/examlense/frontend/src/lib/learning-goals/learning-goals.ts similarity index 100% rename from apps/examlense/frontend/src/lib/learning-goals.ts rename to apps/examlense/frontend/src/lib/learning-goals/learning-goals.ts diff --git a/apps/examlense/frontend/src/lib/parsing-metrics.ts b/apps/examlense/frontend/src/lib/parsing/parsing-metrics.ts similarity index 97% rename from apps/examlense/frontend/src/lib/parsing-metrics.ts rename to apps/examlense/frontend/src/lib/parsing/parsing-metrics.ts index 32d2038..9e4cb04 100644 --- a/apps/examlense/frontend/src/lib/parsing-metrics.ts +++ b/apps/examlense/frontend/src/lib/parsing/parsing-metrics.ts @@ -5,7 +5,7 @@ * panel can resolve human labels. The mock lives here; the data access (swap * point onto a real backend) is in `parsing-metrics-api.ts`. */ -import { PARSER_MODELS } from "@/lib/llm-models"; +import { PARSER_MODELS } from "@/lib/exam/llm-models"; export interface ModelParsingStat { modelId: string; diff --git a/apps/examlense/frontend/src/lib/fuzzy.test.ts b/apps/examlense/frontend/src/lib/utils/fuzzy.test.ts similarity index 100% rename from apps/examlense/frontend/src/lib/fuzzy.test.ts rename to apps/examlense/frontend/src/lib/utils/fuzzy.test.ts diff --git a/apps/examlense/frontend/src/lib/fuzzy.ts b/apps/examlense/frontend/src/lib/utils/fuzzy.ts similarity index 100% rename from apps/examlense/frontend/src/lib/fuzzy.ts rename to apps/examlense/frontend/src/lib/utils/fuzzy.ts diff --git a/apps/examlense/frontend/src/lib/utils.ts b/apps/examlense/frontend/src/lib/utils/utils.ts similarity index 100% rename from apps/examlense/frontend/src/lib/utils.ts rename to apps/examlense/frontend/src/lib/utils/utils.ts diff --git a/apps/examlense/frontend/src/pages/Auth.tsx b/apps/examlense/frontend/src/pages/Auth.tsx deleted file mode 100644 index e52a45f..0000000 --- a/apps/examlense/frontend/src/pages/Auth.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Navigate } from "react-router-dom"; - -/** Auth is disabled in single-user mode; this route just lands on the exam list. */ -const Auth = () => ; - -export default Auth; diff --git a/apps/examlense/frontend/src/pages/Evaluate.tsx b/apps/examlense/frontend/src/pages/Evaluate.tsx deleted file mode 100644 index e950407..0000000 --- a/apps/examlense/frontend/src/pages/Evaluate.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; - -/** - * Legacy /evaluate route — the "How do you want to start?" flow now lives in a - * modal opened from the dashboard. We redirect any direct hits to /exams with a - * flag so the dashboard auto-opens the modal. - */ -const Evaluate = () => { - const navigate = useNavigate(); - useEffect(() => { - navigate("/exams?new=1", { replace: true }); - }, [navigate]); - return null; -}; - -export default Evaluate; diff --git a/apps/examlense/frontend/src/pages/Index.tsx b/apps/examlense/frontend/src/pages/Index.tsx deleted file mode 100644 index a5c6fc2..0000000 --- a/apps/examlense/frontend/src/pages/Index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Navbar } from "@/components/landing/Navbar"; -import { Hero } from "@/components/landing/Hero"; -import { HowItWorks } from "@/components/landing/HowItWorks"; -import { WhyThisMatters } from "@/components/landing/WhyThisMatters"; -import { Footer } from "@/components/landing/Footer"; - -const Index = () => { - return ( -
- -
- - - -
-
-
- ); -}; - -export default Index; diff --git a/apps/examlense/frontend/src/pages/ResetPassword.tsx b/apps/examlense/frontend/src/pages/ResetPassword.tsx deleted file mode 100644 index 503103a..0000000 --- a/apps/examlense/frontend/src/pages/ResetPassword.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { Navigate } from "react-router-dom"; - -/** No password reset in single-user mode; redirect to the exam list. */ -const ResetPassword = () => ; - -export default ResetPassword; diff --git a/apps/examlense/frontend/src/pages/AdminDashboard.tsx b/apps/examlense/frontend/src/pages/admin/AdminDashboard.tsx similarity index 83% rename from apps/examlense/frontend/src/pages/AdminDashboard.tsx rename to apps/examlense/frontend/src/pages/admin/AdminDashboard.tsx index d1fbe40..64a493b 100644 --- a/apps/examlense/frontend/src/pages/AdminDashboard.tsx +++ b/apps/examlense/frontend/src/pages/admin/AdminDashboard.tsx @@ -1,6 +1,6 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import ParsingMetricsPanel from "@/components/admin/ParsingMetricsPanel"; -import ParseSurveyPanel from "@/components/admin/ParseSurveyPanel"; +import ParsingMetricsPanel from "@/pages/admin/components/ParsingMetricsPanel"; +import ParseSurveyPanel from "@/pages/admin/components/ParseSurveyPanel"; const AdminDashboard = () => { return ( diff --git a/apps/examlense/frontend/src/components/admin/ParseSurveyPanel.tsx b/apps/examlense/frontend/src/pages/admin/components/ParseSurveyPanel.tsx similarity index 97% rename from apps/examlense/frontend/src/components/admin/ParseSurveyPanel.tsx rename to apps/examlense/frontend/src/pages/admin/components/ParseSurveyPanel.tsx index 4a21afc..02d63c2 100644 --- a/apps/examlense/frontend/src/components/admin/ParseSurveyPanel.tsx +++ b/apps/examlense/frontend/src/pages/admin/components/ParseSurveyPanel.tsx @@ -1,10 +1,10 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { parserModelLabel } from "@/lib/llm-models"; +import { parserModelLabel } from "@/lib/exam/llm-models"; import { useParseSurvey, useParseSurveyByModel, type ParseSurveyRow, -} from "@/hooks/use-parse-survey"; +} from "@/hooks/data/use-parse-survey"; type Aspect = "speed" | "content_correctness" | "structure"; diff --git a/apps/examlense/frontend/src/components/admin/ParsingMetricsPanel.tsx b/apps/examlense/frontend/src/pages/admin/components/ParsingMetricsPanel.tsx similarity index 95% rename from apps/examlense/frontend/src/components/admin/ParsingMetricsPanel.tsx rename to apps/examlense/frontend/src/pages/admin/components/ParsingMetricsPanel.tsx index 9ff8f78..528c50c 100644 --- a/apps/examlense/frontend/src/components/admin/ParsingMetricsPanel.tsx +++ b/apps/examlense/frontend/src/pages/admin/components/ParsingMetricsPanel.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { parserModelLabel } from "@/lib/llm-models"; -import { pdfModeLabel } from "@/lib/labels"; -import { useParsingMetrics } from "@/hooks/use-parsing-metrics"; +import { parserModelLabel } from "@/lib/exam/llm-models"; +import { pdfModeLabel } from "@/lib/exam/labels"; +import { useParsingMetrics } from "@/hooks/data/use-parsing-metrics"; const modelLabel = (id: string) => parserModelLabel(id); diff --git a/apps/examlense/frontend/src/pages/ExamEdit.tsx b/apps/examlense/frontend/src/pages/exam-edit/ExamEdit.tsx similarity index 95% rename from apps/examlense/frontend/src/pages/ExamEdit.tsx rename to apps/examlense/frontend/src/pages/exam-edit/ExamEdit.tsx index f841f21..ea53b19 100644 --- a/apps/examlense/frontend/src/pages/ExamEdit.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/ExamEdit.tsx @@ -40,33 +40,33 @@ import { deleteBlocksBySection, listAnswers, cancelExam, -} from "@/lib/api-client"; -import { subscribeExam } from "@/lib/sse"; -import { useExam, useTasks, examKey, tasksKey } from "@/hooks/use-exam"; -import { useSections, useSectionBlocks, sectionsKey, blocksKey } from "@/hooks/use-sections"; -import { SaveStatusProvider, useSaveStatus, SaveIndicator } from "@/components/SaveStatus"; -import { useToast } from "@/hooks/use-toast"; -import { examLearningGoalsKey } from "@/hooks/use-learning-goals"; -import { TaskCard } from "@/components/exam-edit/TaskCard"; -import { ContextBlockCard } from "@/components/exam-edit/ContextBlockCard"; -import { FigureBlockCard } from "@/components/exam-edit/FigureBlockCard"; -import { AddTaskInline } from "@/components/exam-edit/AddTaskInline"; -import { SectionLayout } from "@/components/exam-edit/SectionLayout"; -import { SectionTitleInput } from "@/components/exam-edit/SectionTitleInput"; -import { useItemCollapseState } from "@/hooks/use-item-collapse-state"; -import { BlockRow } from "@/components/exam-edit/BlockRow"; -import { SortableItem } from "@/components/exam-edit/SortableItem"; -import { ExamEditFooter } from "@/components/exam-edit/ExamEditFooter"; -import { ScoreNeededIndicator } from "@/components/exam-edit/ScoreNeededIndicator"; -import { InlineTitle } from "@/components/exam-edit/chrome/InlineTitle"; -import { DEFAULT_SOLVER_MODEL_ID } from "@/lib/llm-models"; -import { solveExam } from "@/lib/api-solve"; -import { EvaluatingView } from "@/components/exam-edit/EvaluatingView"; -import { EditorLoadingView } from "@/components/exam-edit/EditorLoadingView"; -import { SectionCarousel, type CarouselSlide } from "@/components/exam-edit/SectionCarousel"; -import { IntroSlide } from "@/components/exam-edit/IntroSlide"; -import { ManualIntroSlide } from "@/components/exam-edit/ManualIntroSlide"; -import { useSectionConfirmations } from "@/hooks/use-section-confirmations"; +} from "@/lib/api/api-client"; +import { subscribeExam } from "@/lib/api/sse"; +import { useExam, useTasks, examKey, tasksKey } from "@/hooks/data/use-exam"; +import { useSections, useSectionBlocks, sectionsKey, blocksKey } from "@/hooks/data/use-sections"; +import { SaveStatusProvider, useSaveStatus, SaveIndicator } from "@/pages/exam-edit/components/SaveStatus"; +import { useToast } from "@/hooks/ui/use-toast"; +import { examLearningGoalsKey } from "@/hooks/data/use-learning-goals"; +import { TaskCard } from "@/pages/exam-edit/components/TaskCard"; +import { ContextBlockCard } from "@/pages/exam-edit/components/ContextBlockCard"; +import { FigureBlockCard } from "@/pages/exam-edit/components/FigureBlockCard"; +import { AddTaskInline } from "@/pages/exam-edit/components/AddTaskInline"; +import { SectionLayout } from "@/components/shared/exam-content/SectionLayout"; +import { SectionTitleInput } from "@/pages/exam-edit/components/SectionTitleInput"; +import { useItemCollapseState } from "@/hooks/ui/use-item-collapse-state"; +import { BlockRow } from "@/pages/exam-edit/components/BlockRow"; +import { SortableItem } from "@/pages/exam-edit/components/SortableItem"; +import { ExamEditFooter } from "@/pages/exam-edit/components/ExamEditFooter"; +import { ScoreNeededIndicator } from "@/pages/exam-edit/components/ScoreNeededIndicator"; +import { InlineTitle } from "@/components/shared/chrome/InlineTitle"; +import { DEFAULT_SOLVER_MODEL_ID } from "@/lib/exam/llm-models"; +import { solveExam } from "@/lib/api/api-solve"; +import { EvaluatingView } from "@/pages/exam-edit/components/EvaluatingView"; +import { EditorLoadingView } from "@/components/shared/exam-content/EditorLoadingView"; +import { SectionCarousel, type CarouselSlide } from "@/components/shared/exam-content/SectionCarousel"; +import { IntroSlide } from "@/pages/exam-edit/components/IntroSlide"; +import { ManualIntroSlide } from "@/pages/exam-edit/components/ManualIntroSlide"; +import { useSectionConfirmations } from "@/hooks/data/use-section-confirmations"; import { convertTaskType, examModePath, @@ -83,14 +83,14 @@ import { type SectionBlock, type Task, type TaskType, -} from "@/lib/exam-helpers"; -import { TASK_TYPE_LABELS } from "@/lib/labels"; +} from "@/lib/exam/exam-helpers"; +import { TASK_TYPE_LABELS } from "@/lib/exam/labels"; import { Badge } from "@/components/ui/badge"; import { SectionSidebar, useEditSectionEntries, type SectionEntry, -} from "@/components/exam-edit/SectionSidebar"; +} from "@/components/shared/exam-content/SectionSidebar"; import { AlertDialog, AlertDialogAction, diff --git a/apps/examlense/frontend/src/components/exam-edit/AddTaskInline.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/AddTaskInline.tsx similarity index 96% rename from apps/examlense/frontend/src/components/exam-edit/AddTaskInline.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/AddTaskInline.tsx index 1bf2c4b..e630ba5 100644 --- a/apps/examlense/frontend/src/components/exam-edit/AddTaskInline.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/AddTaskInline.tsx @@ -1,8 +1,8 @@ import { useState } from "react"; import { Plus, FileText, Image as ImageIcon, ChevronDown } from "lucide-react"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { TASK_TYPES, type TaskType } from "@/lib/exam-helpers"; -import { TASK_TYPE_LABELS } from "@/lib/labels"; +import { TASK_TYPES, type TaskType } from "@/lib/exam/exam-helpers"; +import { TASK_TYPE_LABELS } from "@/lib/exam/labels"; export type AddBlockChoice = | { kind: "task"; type: TaskType } diff --git a/apps/examlense/frontend/src/components/exam-edit/BlockRow.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/BlockRow.tsx similarity index 93% rename from apps/examlense/frontend/src/components/exam-edit/BlockRow.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/BlockRow.tsx index 8a598cc..35ef5d0 100644 --- a/apps/examlense/frontend/src/components/exam-edit/BlockRow.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/BlockRow.tsx @@ -1,7 +1,7 @@ import type { CSSProperties, HTMLAttributes, ReactNode } from "react"; -import { cn } from "@/lib/utils"; -import { BlockHeader } from "./BlockHeader"; -import { BlockCard } from "./BlockCard"; +import { cn } from "@/lib/utils/utils"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; +import { BlockCard } from "@/components/shared/exam-content/BlockCard"; export type BlockRowKind = "task" | "context" | "figure"; diff --git a/apps/examlense/frontend/src/components/exam-edit/ContextBlockCard.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/ContextBlockCard.tsx similarity index 91% rename from apps/examlense/frontend/src/components/exam-edit/ContextBlockCard.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/ContextBlockCard.tsx index 91408b0..63fa79b 100644 --- a/apps/examlense/frontend/src/components/exam-edit/ContextBlockCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/ContextBlockCard.tsx @@ -17,13 +17,13 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { useAutosizeTextarea } from "@/hooks/use-autosize-textarea"; -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { cn } from "@/lib/utils"; -import type { SectionBlock } from "@/lib/exam-helpers"; -import { MarkdownView, markdownTextareaClassName } from "./MarkdownView"; -import { BlockHeader } from "./BlockHeader"; -import { BlockCard } from "./BlockCard"; +import { useAutosizeTextarea } from "@/hooks/ui/use-autosize-textarea"; +import { useDebouncedCallback } from "@/hooks/ui/use-debounced-callback"; +import { cn } from "@/lib/utils/utils"; +import type { SectionBlock } from "@/lib/exam/exam-helpers"; +import { MarkdownView, markdownTextareaClassName } from "@/components/shared/exam-content/MarkdownView"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; +import { BlockCard } from "@/components/shared/exam-content/BlockCard"; interface Props { block: SectionBlock; diff --git a/apps/examlense/frontend/src/components/exam-edit/EvaluatingView.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/EvaluatingView.tsx similarity index 97% rename from apps/examlense/frontend/src/components/exam-edit/EvaluatingView.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/EvaluatingView.tsx index c464509..b0d8609 100644 --- a/apps/examlense/frontend/src/components/exam-edit/EvaluatingView.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/EvaluatingView.tsx @@ -2,11 +2,11 @@ import { Link } from "react-router-dom"; import { ArrowLeft, ArrowRight, CheckCircle2, Loader2 } from "lucide-react"; import { Progress } from "@/components/ui/progress"; import { Button } from "@/components/ui/button"; -import { ParsingQualitySurvey } from "@/components/exam-edit/ParsingQualitySurvey"; +import { ParsingQualitySurvey } from "@/pages/exam-edit/components/ParsingQualitySurvey"; import { useEvaluationProgress, parsePhasePercent, -} from "@/hooks/use-exam-progress"; +} from "@/hooks/data/use-exam-progress"; type ProcessingKind = "parsing" | "evaluating"; diff --git a/apps/examlense/frontend/src/components/exam-edit/ExamEditFooter.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/ExamEditFooter.tsx similarity index 92% rename from apps/examlense/frontend/src/components/exam-edit/ExamEditFooter.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/ExamEditFooter.tsx index 8273007..f6127dd 100644 --- a/apps/examlense/frontend/src/components/exam-edit/ExamEditFooter.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/ExamEditFooter.tsx @@ -1,4 +1,4 @@ -import { solverModelLabel as resolveSolverModelLabel } from "@/lib/llm-models"; +import { solverModelLabel as resolveSolverModelLabel } from "@/lib/exam/llm-models"; import { AlertDialog, AlertDialogAction, @@ -9,10 +9,10 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { ChromeFooter } from "./chrome/ChromeFooter"; -import { ChromeUtilityCluster } from "./chrome/ChromeUtilityCluster"; +import { ChromeFooter } from "@/components/shared/chrome/ChromeFooter"; +import { ChromeUtilityCluster } from "@/components/shared/chrome/ChromeUtilityCluster"; import { SectionProgressButton } from "./SectionProgressButton"; -import type { Exam, Task } from "@/lib/exam-helpers"; +import type { Exam, Task } from "@/lib/exam/exam-helpers"; interface Props { exam: Exam; diff --git a/apps/examlense/frontend/src/components/exam-edit/FigureBlockCard.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/FigureBlockCard.tsx similarity index 93% rename from apps/examlense/frontend/src/components/exam-edit/FigureBlockCard.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/FigureBlockCard.tsx index a03fb37..6a00ae6 100644 --- a/apps/examlense/frontend/src/components/exam-edit/FigureBlockCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/FigureBlockCard.tsx @@ -17,14 +17,14 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { uploadFigure, deleteFigure } from "@/lib/api-client"; -import { useFigureUrl } from "@/hooks/use-figure-url"; -import { useSectionFigures, figuresKey } from "@/hooks/use-sections"; -import { useToast } from "@/hooks/use-toast"; -import { cn } from "@/lib/utils"; -import type { SectionBlock, SectionFigure } from "@/lib/exam-helpers"; -import { BlockHeader } from "./BlockHeader"; -import { BlockCard } from "./BlockCard"; +import { uploadFigure, deleteFigure } from "@/lib/api/api-client"; +import { useFigureUrl } from "@/hooks/data/use-figure-url"; +import { useSectionFigures, figuresKey } from "@/hooks/data/use-sections"; +import { useToast } from "@/hooks/ui/use-toast"; +import { cn } from "@/lib/utils/utils"; +import type { SectionBlock, SectionFigure } from "@/lib/exam/exam-helpers"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; +import { BlockCard } from "@/components/shared/exam-content/BlockCard"; const MAX_BYTES = 5 * 1024 * 1024; const MIME = ["image/png", "image/jpeg", "image/webp", "image/gif"]; diff --git a/apps/examlense/frontend/src/components/exam-edit/IntroSlide.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/IntroSlide.tsx similarity index 91% rename from apps/examlense/frontend/src/components/exam-edit/IntroSlide.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/IntroSlide.tsx index 04829ec..9567a8d 100644 --- a/apps/examlense/frontend/src/components/exam-edit/IntroSlide.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/IntroSlide.tsx @@ -1,5 +1,5 @@ import { FileText } from "lucide-react"; -import { StepGuide, PARSED_STEPS } from "./IntroStepGuide"; +import { StepGuide, PARSED_STEPS } from "@/components/shared/chrome/IntroStepGuide"; export const IntroSlide = () => { return ( diff --git a/apps/examlense/frontend/src/components/exam-edit/ManualIntroSlide.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/ManualIntroSlide.tsx similarity index 93% rename from apps/examlense/frontend/src/components/exam-edit/ManualIntroSlide.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/ManualIntroSlide.tsx index 61e9aab..51e0189 100644 --- a/apps/examlense/frontend/src/components/exam-edit/ManualIntroSlide.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/ManualIntroSlide.tsx @@ -1,5 +1,5 @@ import { Sparkles } from "lucide-react"; -import { StepGuide, EDIT_STEPS } from "./IntroStepGuide"; +import { StepGuide, EDIT_STEPS } from "@/components/shared/chrome/IntroStepGuide"; /** * First-open intro for manually-created exams. Walks the author through the diff --git a/apps/examlense/frontend/src/components/exam-edit/ParsingQualitySurvey.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/ParsingQualitySurvey.tsx similarity index 97% rename from apps/examlense/frontend/src/components/exam-edit/ParsingQualitySurvey.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/ParsingQualitySurvey.tsx index 6243ed5..79eaadc 100644 --- a/apps/examlense/frontend/src/components/exam-edit/ParsingQualitySurvey.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/ParsingQualitySurvey.tsx @@ -4,8 +4,8 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Slider } from "@/components/ui/slider"; -import { submitParseSurvey } from "@/lib/api-client"; -import { useToast } from "@/hooks/use-toast"; +import { submitParseSurvey } from "@/lib/api/api-client"; +import { useToast } from "@/hooks/ui/use-toast"; const ASPECTS = ["speed", "contentCorrectness", "structure"] as const; type Aspect = (typeof ASPECTS)[number]; diff --git a/apps/examlense/frontend/src/components/SaveStatus.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/SaveStatus.tsx similarity index 100% rename from apps/examlense/frontend/src/components/SaveStatus.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/SaveStatus.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/ScoreNeededIndicator.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/ScoreNeededIndicator.tsx similarity index 98% rename from apps/examlense/frontend/src/components/exam-edit/ScoreNeededIndicator.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/ScoreNeededIndicator.tsx index ee7b86a..2b709f5 100644 --- a/apps/examlense/frontend/src/components/exam-edit/ScoreNeededIndicator.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/ScoreNeededIndicator.tsx @@ -1,6 +1,6 @@ import { useLayoutEffect, useRef, useState } from "react"; import { ChevronDown, ChevronUp } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; interface Props { /** The scrolling content container the target lives inside. */ diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionProgressButton.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/SectionProgressButton.tsx similarity index 98% rename from apps/examlense/frontend/src/components/exam-edit/SectionProgressButton.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/SectionProgressButton.tsx index c541f34..7f658df 100644 --- a/apps/examlense/frontend/src/components/exam-edit/SectionProgressButton.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/SectionProgressButton.tsx @@ -1,6 +1,6 @@ import { ArrowDown, ArrowRight, Check } from "lucide-react"; -import type { Task } from "@/lib/exam-helpers"; -import { cn } from "@/lib/utils"; +import type { Task } from "@/lib/exam/exam-helpers"; +import { cn } from "@/lib/utils/utils"; interface Props { /** Tasks of the currently visible section. */ diff --git a/apps/examlense/frontend/src/components/exam-edit/SectionTitleInput.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/SectionTitleInput.tsx similarity index 97% rename from apps/examlense/frontend/src/components/exam-edit/SectionTitleInput.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/SectionTitleInput.tsx index 0cc2104..fbc8d32 100644 --- a/apps/examlense/frontend/src/components/exam-edit/SectionTitleInput.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/SectionTitleInput.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; import { Pencil } from "lucide-react"; -import type { Section } from "@/lib/exam-helpers"; +import type { Section } from "@/lib/exam/exam-helpers"; interface Props { section: Section; diff --git a/apps/examlense/frontend/src/components/exam-edit/SortableItem.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/SortableItem.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-edit/SortableItem.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/SortableItem.tsx diff --git a/apps/examlense/frontend/src/components/exam-edit/TaskCard.tsx b/apps/examlense/frontend/src/pages/exam-edit/components/TaskCard.tsx similarity index 96% rename from apps/examlense/frontend/src/components/exam-edit/TaskCard.tsx rename to apps/examlense/frontend/src/pages/exam-edit/components/TaskCard.tsx index ad6be85..ebc941d 100644 --- a/apps/examlense/frontend/src/components/exam-edit/TaskCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-edit/components/TaskCard.tsx @@ -35,17 +35,17 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { TASK_TYPE_LABELS } from "@/lib/labels"; -import { useAutosizeTextarea } from "@/hooks/use-autosize-textarea"; -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { cn } from "@/lib/utils"; +import { TASK_TYPE_LABELS } from "@/lib/exam/labels"; +import { useAutosizeTextarea } from "@/hooks/ui/use-autosize-textarea"; +import { useDebouncedCallback } from "@/hooks/ui/use-debounced-callback"; +import { cn } from "@/lib/utils/utils"; import { MarkdownView, markdownSurfaceClassName, markdownTextareaClassName, -} from "./MarkdownView"; -import { BlockHeader } from "./BlockHeader"; -import { BlockCard } from "./BlockCard"; +} from "@/components/shared/exam-content/MarkdownView"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; +import { BlockCard } from "@/components/shared/exam-content/BlockCard"; import { TASK_TYPES, mcWarning, @@ -53,7 +53,7 @@ import { type Task, type TaskOption, type TaskType, -} from "@/lib/exam-helpers"; +} from "@/lib/exam/exam-helpers"; const preventNumberWheelChange = (event: React.WheelEvent) => { event.currentTarget.blur(); diff --git a/apps/examlense/frontend/src/pages/GradeRoute.tsx b/apps/examlense/frontend/src/pages/exam-grading/GradeRoute.tsx similarity index 77% rename from apps/examlense/frontend/src/pages/GradeRoute.tsx rename to apps/examlense/frontend/src/pages/exam-grading/GradeRoute.tsx index c44ed8e..3bdab24 100644 --- a/apps/examlense/frontend/src/pages/GradeRoute.tsx +++ b/apps/examlense/frontend/src/pages/exam-grading/GradeRoute.tsx @@ -1,9 +1,9 @@ import { useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; -import { useExam } from "@/hooks/use-exam"; -import { examModePath, examModeSlug } from "@/lib/exam-helpers"; -import { GradingView } from "@/pages/GradingView"; -import { EditorLoadingView } from "@/components/exam-edit/EditorLoadingView"; +import { useExam } from "@/hooks/data/use-exam"; +import { examModePath, examModeSlug } from "@/lib/exam/exam-helpers"; +import { GradingView } from "@/pages/exam-grading/GradingView"; +import { EditorLoadingView } from "@/components/shared/exam-content/EditorLoadingView"; const GradeRoute = () => { const { id } = useParams<{ id: string }>(); diff --git a/apps/examlense/frontend/src/pages/GradingView.tsx b/apps/examlense/frontend/src/pages/exam-grading/GradingView.tsx similarity index 89% rename from apps/examlense/frontend/src/pages/GradingView.tsx rename to apps/examlense/frontend/src/pages/exam-grading/GradingView.tsx index 0023b7d..edd6a57 100644 --- a/apps/examlense/frontend/src/pages/GradingView.tsx +++ b/apps/examlense/frontend/src/pages/exam-grading/GradingView.tsx @@ -1,33 +1,33 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { ClipboardCheck, CheckCircle2 } from "lucide-react"; -import { patchExam } from "@/lib/api-client"; +import { patchExam } from "@/lib/api/api-client"; import { useQueryClient } from "@tanstack/react-query"; -import { useExam, useTasks, tasksKey } from "@/hooks/use-exam"; -import { useSections, useSectionBlocks } from "@/hooks/use-sections"; -import { useTaskAnswers } from "@/hooks/use-task-answers"; -import { useTaskGrades } from "@/hooks/use-task-grades"; -import { useExamLearningGoals, examLearningGoalsKey } from "@/hooks/use-learning-goals"; -import { subscribeExam } from "@/lib/sse"; -import type { LearningGoalResponse } from "@/lib/learning-goals"; -import { type TaskGoalDisplay } from "@/components/exam-edit/grading/ReadOnlyTaskCard"; -import { ReadOnlyQuestionBlock } from "@/components/exam-edit/grading/ReadOnlyQuestionBlock"; -import { ReadOnlyContextBlock } from "@/components/exam-edit/grading/ReadOnlyContextBlock"; -import { ReadOnlyFigureBlock } from "@/components/exam-edit/grading/ReadOnlyFigureBlock"; -import { TaskGradingPanel } from "@/components/exam-edit/grading/TaskGradingPanel"; -import { ChromeFooter } from "@/components/exam-edit/chrome/ChromeFooter"; -import { ChromeUtilityCluster } from "@/components/exam-edit/chrome/ChromeUtilityCluster"; -import { StaticTitle } from "@/components/exam-edit/chrome/InlineTitle"; +import { useExam, useTasks, tasksKey } from "@/hooks/data/use-exam"; +import { useSections, useSectionBlocks } from "@/hooks/data/use-sections"; +import { useTaskAnswers } from "@/hooks/data/use-task-answers"; +import { useTaskGrades } from "@/hooks/data/use-task-grades"; +import { useExamLearningGoals, examLearningGoalsKey } from "@/hooks/data/use-learning-goals"; +import { subscribeExam } from "@/lib/api/sse"; +import type { LearningGoalResponse } from "@/lib/learning-goals/learning-goals"; +import { type TaskGoalDisplay } from "@/components/shared/exam-content/read-only/ReadOnlyTaskCard"; +import { ReadOnlyQuestionBlock } from "@/pages/exam-grading/components/ReadOnlyQuestionBlock"; +import { ReadOnlyContextBlock } from "@/components/shared/exam-content/read-only/ReadOnlyContextBlock"; +import { ReadOnlyFigureBlock } from "@/components/shared/exam-content/read-only/ReadOnlyFigureBlock"; +import { TaskGradingPanel } from "@/pages/exam-grading/components/TaskGradingPanel"; +import { ChromeFooter } from "@/components/shared/chrome/ChromeFooter"; +import { ChromeUtilityCluster } from "@/components/shared/chrome/ChromeUtilityCluster"; +import { StaticTitle } from "@/components/shared/chrome/InlineTitle"; import { SectionSidebar, useGradingSectionEntries, -} from "@/components/exam-edit/SectionSidebar"; -import { EditorLoadingView } from "@/components/exam-edit/EditorLoadingView"; -import { SectionLayout } from "@/components/exam-edit/SectionLayout"; +} from "@/components/shared/exam-content/SectionSidebar"; +import { EditorLoadingView } from "@/components/shared/exam-content/EditorLoadingView"; +import { SectionLayout } from "@/components/shared/exam-content/SectionLayout"; import { SectionCarousel, type CarouselSlide, -} from "@/components/exam-edit/SectionCarousel"; +} from "@/components/shared/exam-content/SectionCarousel"; import { figureLabelsForBlocks, letterLabel, @@ -35,13 +35,13 @@ import { type Section, type SectionBlock, type Task, -} from "@/lib/exam-helpers"; +} from "@/lib/exam/exam-helpers"; import { effectiveScore, examTotals, type TaskAnswer, type TaskGrade, -} from "@/lib/grading"; +} from "@/lib/grading/grading"; import { Button } from "@/components/ui/button"; import { AlertDialog, diff --git a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyQuestionBlock.tsx b/apps/examlense/frontend/src/pages/exam-grading/components/ReadOnlyQuestionBlock.tsx similarity index 91% rename from apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyQuestionBlock.tsx rename to apps/examlense/frontend/src/pages/exam-grading/components/ReadOnlyQuestionBlock.tsx index cb168a0..6205326 100644 --- a/apps/examlense/frontend/src/components/exam-edit/grading/ReadOnlyQuestionBlock.tsx +++ b/apps/examlense/frontend/src/pages/exam-grading/components/ReadOnlyQuestionBlock.tsx @@ -6,11 +6,11 @@ import { CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; -import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/labels"; -import type { Task } from "@/lib/exam-helpers"; -import { MarkdownView } from "../MarkdownView"; -import { BlockHeader } from "../BlockHeader"; -import type { TaskGoalDisplay } from "./ReadOnlyTaskCard"; +import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/exam/labels"; +import type { Task } from "@/lib/exam/exam-helpers"; +import { MarkdownView } from "@/components/shared/exam-content/MarkdownView"; +import { BlockHeader } from "@/components/shared/exam-content/BlockHeader"; +import type { TaskGoalDisplay } from "@/components/shared/exam-content/read-only/ReadOnlyTaskCard"; interface Props { task: Task; diff --git a/apps/examlense/frontend/src/components/exam-edit/grading/TaskGradingPanel.tsx b/apps/examlense/frontend/src/pages/exam-grading/components/TaskGradingPanel.tsx similarity index 96% rename from apps/examlense/frontend/src/components/exam-edit/grading/TaskGradingPanel.tsx rename to apps/examlense/frontend/src/pages/exam-grading/components/TaskGradingPanel.tsx index 2ab2500..5ddf669 100644 --- a/apps/examlense/frontend/src/components/exam-edit/grading/TaskGradingPanel.tsx +++ b/apps/examlense/frontend/src/pages/exam-grading/components/TaskGradingPanel.tsx @@ -4,15 +4,15 @@ import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Slider } from "@/components/ui/slider"; import { useQueryClient } from "@tanstack/react-query"; -import { useUpsertTaskGrade } from "@/hooks/use-task-grades"; -import { taskAnswersKey } from "@/hooks/use-task-answers"; -import { autoGradeChoiceTask, type TaskAnswer, type TaskGrade } from "@/lib/grading"; -import { MarkdownView } from "../MarkdownView"; -import { BlockCard } from "../BlockCard"; -import type { Task } from "@/lib/exam-helpers"; -import { cn } from "@/lib/utils"; -import { solveTask } from "@/lib/api-solve"; -import { GRADE_SOURCE_LABELS } from "@/lib/labels"; +import { useUpsertTaskGrade } from "@/hooks/data/use-task-grades"; +import { taskAnswersKey } from "@/hooks/data/use-task-answers"; +import { autoGradeChoiceTask, type TaskAnswer, type TaskGrade } from "@/lib/grading/grading"; +import { MarkdownView } from "@/components/shared/exam-content/MarkdownView"; +import { BlockCard } from "@/components/shared/exam-content/BlockCard"; +import type { Task } from "@/lib/exam/exam-helpers"; +import { cn } from "@/lib/utils/utils"; +import { solveTask } from "@/lib/api/api-solve"; +import { GRADE_SOURCE_LABELS } from "@/lib/exam/labels"; const preventNumberWheelChange = (event: React.WheelEvent) => { event.currentTarget.blur(); diff --git a/apps/examlense/frontend/src/pages/ExamResults.tsx b/apps/examlense/frontend/src/pages/exam-results/ExamResults.tsx similarity index 85% rename from apps/examlense/frontend/src/pages/ExamResults.tsx rename to apps/examlense/frontend/src/pages/exam-results/ExamResults.tsx index c362406..37146c6 100644 --- a/apps/examlense/frontend/src/pages/ExamResults.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/ExamResults.tsx @@ -2,33 +2,33 @@ import { useMemo, useState } from "react"; import { Navigate, useNavigate, useParams } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; import { BarChart3, CheckCircle2, GraduationCap, ListChecks, PencilLine, Table2 } from "lucide-react"; -import { useExam, useTasks, examKey } from "@/hooks/use-exam"; -import { patchExam } from "@/lib/api-client"; -import { useSections, useSectionBlocks } from "@/hooks/use-sections"; -import { useTaskAnswers } from "@/hooks/use-task-answers"; -import { useTaskGrades } from "@/hooks/use-task-grades"; -import { EditorLoadingView } from "@/components/exam-edit/EditorLoadingView"; -import { ResilienceOverview } from "@/components/exam-results/ResilienceOverview"; -import { ByQuestionTypeCard } from "@/components/exam-results/ByQuestionTypeCard"; -import { FiguresComparisonCard } from "@/components/exam-results/FiguresComparisonCard"; -import { TaskBreakdownTable } from "@/components/exam-results/TaskBreakdownTable"; -import { TaskScoreBarChart } from "@/components/exam-results/TaskScoreBarChart"; -import { LearningGoalsCard } from "@/components/exam-results/LearningGoalsCard"; -import { AllTasksList } from "@/components/exam-results/AllTasksList"; +import { useExam, useTasks, examKey } from "@/hooks/data/use-exam"; +import { patchExam } from "@/lib/api/api-client"; +import { useSections, useSectionBlocks } from "@/hooks/data/use-sections"; +import { useTaskAnswers } from "@/hooks/data/use-task-answers"; +import { useTaskGrades } from "@/hooks/data/use-task-grades"; +import { EditorLoadingView } from "@/components/shared/exam-content/EditorLoadingView"; +import { ResilienceOverview } from "@/pages/exam-results/components/ResilienceOverview"; +import { ByQuestionTypeCard } from "@/pages/exam-results/components/ByQuestionTypeCard"; +import { FiguresComparisonCard } from "@/pages/exam-results/components/FiguresComparisonCard"; +import { TaskBreakdownTable } from "@/pages/exam-results/components/TaskBreakdownTable"; +import { TaskScoreBarChart } from "@/pages/exam-results/components/TaskScoreBarChart"; +import { LearningGoalsCard } from "@/pages/exam-results/components/LearningGoalsCard"; +import { AllTasksList } from "@/pages/exam-results/components/AllTasksList"; import { ResultsSidebar, type ResultsViewItem, -} from "@/components/exam-results/ResultsSidebar"; -import { ChromeFooter } from "@/components/exam-edit/chrome/ChromeFooter"; -import { ChromeUtilityCluster } from "@/components/exam-edit/chrome/ChromeUtilityCluster"; -import { StaticTitle } from "@/components/exam-edit/chrome/InlineTitle"; +} from "@/pages/exam-results/components/ResultsSidebar"; +import { ChromeFooter } from "@/components/shared/chrome/ChromeFooter"; +import { ChromeUtilityCluster } from "@/components/shared/chrome/ChromeUtilityCluster"; +import { StaticTitle } from "@/components/shared/chrome/InlineTitle"; import { Button } from "@/components/ui/button"; import { examTotals, type TaskAnswer, type TaskGrade, -} from "@/lib/grading"; -import { examModePath, examModeSlug, letterLabel } from "@/lib/exam-helpers"; +} from "@/lib/grading/grading"; +import { examModePath, examModeSlug, letterLabel } from "@/lib/exam/exam-helpers"; type ResultView = "overview" | "learningGoals" | "details" | "allTasks"; diff --git a/apps/examlense/frontend/src/components/exam-results/AllTasksList.tsx b/apps/examlense/frontend/src/pages/exam-results/components/AllTasksList.tsx similarity index 92% rename from apps/examlense/frontend/src/components/exam-results/AllTasksList.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/AllTasksList.tsx index 6c5e9cf..ed81dd2 100644 --- a/apps/examlense/frontend/src/components/exam-results/AllTasksList.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/AllTasksList.tsx @@ -1,17 +1,17 @@ import { useEffect, useMemo, useState } from "react"; -import { ReadOnlyContextBlock } from "@/components/exam-edit/grading/ReadOnlyContextBlock"; -import { ReadOnlyFigureBlock } from "@/components/exam-edit/grading/ReadOnlyFigureBlock"; -import { ReadOnlyTaskCard } from "@/components/exam-edit/grading/ReadOnlyTaskCard"; -import { MarkdownView } from "@/components/exam-edit/MarkdownView"; -import { SectionLayout } from "@/components/exam-edit/SectionLayout"; +import { ReadOnlyContextBlock } from "@/components/shared/exam-content/read-only/ReadOnlyContextBlock"; +import { ReadOnlyFigureBlock } from "@/components/shared/exam-content/read-only/ReadOnlyFigureBlock"; +import { ReadOnlyTaskCard } from "@/components/shared/exam-content/read-only/ReadOnlyTaskCard"; +import { MarkdownView } from "@/components/shared/exam-content/MarkdownView"; +import { SectionLayout } from "@/components/shared/exam-content/SectionLayout"; import { SectionCarousel, type CarouselSlide, -} from "@/components/exam-edit/SectionCarousel"; +} from "@/components/shared/exam-content/SectionCarousel"; import { SectionSidebar, useGradingSectionEntries, -} from "@/components/exam-edit/SectionSidebar"; +} from "@/components/shared/exam-content/SectionSidebar"; import { figureLabelsForBlocks, letterLabel, @@ -19,14 +19,14 @@ import { type Section, type SectionBlock, type Task, -} from "@/lib/exam-helpers"; +} from "@/lib/exam/exam-helpers"; import { effectiveScore, type TaskAnswer, type TaskGrade, -} from "@/lib/grading"; -import { SCORE_FILL_CLASS, scoreTier } from "@/lib/score-color"; -import { cn } from "@/lib/utils"; +} from "@/lib/grading/grading"; +import { SCORE_FILL_CLASS, scoreTier } from "@/lib/grading/score-color"; +import { cn } from "@/lib/utils/utils"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/components/exam-results/ByQuestionTypeCard.tsx b/apps/examlense/frontend/src/pages/exam-results/components/ByQuestionTypeCard.tsx similarity index 89% rename from apps/examlense/frontend/src/components/exam-results/ByQuestionTypeCard.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/ByQuestionTypeCard.tsx index f8ce1fc..1165c60 100644 --- a/apps/examlense/frontend/src/components/exam-results/ByQuestionTypeCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/ByQuestionTypeCard.tsx @@ -1,7 +1,7 @@ -import type { Task } from "@/lib/exam-helpers"; -import type { TaskGrade, TaskAnswer } from "@/lib/grading"; -import { effectiveScore } from "@/lib/grading"; -import { TASK_TYPE_LABELS } from "@/lib/labels"; +import type { Task } from "@/lib/exam/exam-helpers"; +import type { TaskGrade, TaskAnswer } from "@/lib/grading/grading"; +import { effectiveScore } from "@/lib/grading/grading"; +import { TASK_TYPE_LABELS } from "@/lib/exam/labels"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/components/exam-results/ExamResilienceBanner.tsx b/apps/examlense/frontend/src/pages/exam-results/components/ExamResilienceBanner.tsx similarity index 99% rename from apps/examlense/frontend/src/components/exam-results/ExamResilienceBanner.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/ExamResilienceBanner.tsx index 558ff72..8a9babc 100644 --- a/apps/examlense/frontend/src/components/exam-results/ExamResilienceBanner.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/ExamResilienceBanner.tsx @@ -4,7 +4,7 @@ import { resilienceScore, resilienceTier, type ResilienceTier, -} from "@/lib/resilience"; +} from "@/lib/grading/resilience"; interface Props { earned: number; diff --git a/apps/examlense/frontend/src/components/exam-results/FiguresComparisonCard.tsx b/apps/examlense/frontend/src/pages/exam-results/components/FiguresComparisonCard.tsx similarity index 92% rename from apps/examlense/frontend/src/components/exam-results/FiguresComparisonCard.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/FiguresComparisonCard.tsx index e027e91..ce8563f 100644 --- a/apps/examlense/frontend/src/components/exam-results/FiguresComparisonCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/FiguresComparisonCard.tsx @@ -1,6 +1,6 @@ -import type { Task, SectionBlock } from "@/lib/exam-helpers"; -import type { TaskGrade, TaskAnswer } from "@/lib/grading"; -import { effectiveScore } from "@/lib/grading"; +import type { Task, SectionBlock } from "@/lib/exam/exam-helpers"; +import type { TaskGrade, TaskAnswer } from "@/lib/grading/grading"; +import { effectiveScore } from "@/lib/grading/grading"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/components/exam-results/LearningGoalsCard.tsx b/apps/examlense/frontend/src/pages/exam-results/components/LearningGoalsCard.tsx similarity index 91% rename from apps/examlense/frontend/src/components/exam-results/LearningGoalsCard.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/LearningGoalsCard.tsx index fde12ec..6ec47f5 100644 --- a/apps/examlense/frontend/src/components/exam-results/LearningGoalsCard.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/LearningGoalsCard.tsx @@ -1,12 +1,12 @@ import { useMemo } from "react"; import { Target } from "lucide-react"; -import type { Task } from "@/lib/exam-helpers"; -import type { TaskGrade, TaskAnswer } from "@/lib/grading"; -import { goalRollup, scoreRollup } from "@/lib/grading"; +import type { Task } from "@/lib/exam/exam-helpers"; +import type { TaskGrade, TaskAnswer } from "@/lib/grading/grading"; +import { goalRollup, scoreRollup } from "@/lib/grading/grading"; import { Badge } from "@/components/ui/badge"; -import { useExamLearningGoals } from "@/hooks/use-learning-goals"; -import type { LearningGoalResponse } from "@/lib/learning-goals"; -import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/labels"; +import { useExamLearningGoals } from "@/hooks/data/use-learning-goals"; +import type { LearningGoalResponse } from "@/lib/learning-goals/learning-goals"; +import { BLOOM_LABELS, SOLO_LABELS } from "@/lib/exam/labels"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/components/exam-results/OverallScoreCard.tsx b/apps/examlense/frontend/src/pages/exam-results/components/OverallScoreCard.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exam-results/OverallScoreCard.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/OverallScoreCard.tsx diff --git a/apps/examlense/frontend/src/components/exam-results/ResilienceOverview.tsx b/apps/examlense/frontend/src/pages/exam-results/components/ResilienceOverview.tsx similarity index 93% rename from apps/examlense/frontend/src/components/exam-results/ResilienceOverview.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/ResilienceOverview.tsx index 6e2d9f6..1b2bd08 100644 --- a/apps/examlense/frontend/src/components/exam-results/ResilienceOverview.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/ResilienceOverview.tsx @@ -7,12 +7,12 @@ import { Target, } from "lucide-react"; import type { LucideIcon } from "lucide-react"; -import type { Exam, Task } from "@/lib/exam-helpers"; -import type { TaskAnswer, TaskGrade } from "@/lib/grading"; -import { goalRollup } from "@/lib/grading"; -import { solverModelLabel } from "@/lib/llm-models"; -import { useExamLearningGoals } from "@/hooks/use-learning-goals"; -import { ModelLogo } from "@/components/ModelLogo"; +import type { Exam, Task } from "@/lib/exam/exam-helpers"; +import type { TaskAnswer, TaskGrade } from "@/lib/grading/grading"; +import { goalRollup } from "@/lib/grading/grading"; +import { solverModelLabel } from "@/lib/exam/llm-models"; +import { useExamLearningGoals } from "@/hooks/data/use-learning-goals"; +import { ModelLogo } from "@/components/shared/ModelLogo"; import { ExamResilienceBanner } from "./ExamResilienceBanner"; export type ResultsNavTarget = "learningGoals" | "details" | "allTasks"; diff --git a/apps/examlense/frontend/src/components/exam-results/ResultsSidebar.tsx b/apps/examlense/frontend/src/pages/exam-results/components/ResultsSidebar.tsx similarity index 98% rename from apps/examlense/frontend/src/components/exam-results/ResultsSidebar.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/ResultsSidebar.tsx index b600728..2f2540d 100644 --- a/apps/examlense/frontend/src/components/exam-results/ResultsSidebar.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/ResultsSidebar.tsx @@ -2,7 +2,7 @@ import type { LucideIcon } from "lucide-react"; import { ArrowLeft } from "lucide-react"; import { Link } from "react-router-dom"; import type { ReactNode } from "react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; export interface ResultsViewItem { key: string; diff --git a/apps/examlense/frontend/src/components/exam-results/TaskBreakdownTable.tsx b/apps/examlense/frontend/src/pages/exam-results/components/TaskBreakdownTable.tsx similarity index 93% rename from apps/examlense/frontend/src/components/exam-results/TaskBreakdownTable.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/TaskBreakdownTable.tsx index 41f11f9..c323c75 100644 --- a/apps/examlense/frontend/src/components/exam-results/TaskBreakdownTable.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/TaskBreakdownTable.tsx @@ -1,10 +1,10 @@ import { useMemo, useState } from "react"; import { ArrowUpDown } from "lucide-react"; -import { cn } from "@/lib/utils"; -import type { Task } from "@/lib/exam-helpers"; -import type { TaskGrade, TaskAnswer } from "@/lib/grading"; -import { effectiveScore } from "@/lib/grading"; -import { TASK_TYPE_LABELS } from "@/lib/labels"; +import { cn } from "@/lib/utils/utils"; +import type { Task } from "@/lib/exam/exam-helpers"; +import type { TaskGrade, TaskAnswer } from "@/lib/grading/grading"; +import { effectiveScore } from "@/lib/grading/grading"; +import { TASK_TYPE_LABELS } from "@/lib/exam/labels"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/components/exam-results/TaskScoreBarChart.tsx b/apps/examlense/frontend/src/pages/exam-results/components/TaskScoreBarChart.tsx similarity index 96% rename from apps/examlense/frontend/src/components/exam-results/TaskScoreBarChart.tsx rename to apps/examlense/frontend/src/pages/exam-results/components/TaskScoreBarChart.tsx index 2a77176..856f3a0 100644 --- a/apps/examlense/frontend/src/components/exam-results/TaskScoreBarChart.tsx +++ b/apps/examlense/frontend/src/pages/exam-results/components/TaskScoreBarChart.tsx @@ -8,9 +8,9 @@ import { XAxis, YAxis, } from "recharts"; -import type { Task } from "@/lib/exam-helpers"; -import { effectiveScore, type TaskAnswer, type TaskGrade } from "@/lib/grading"; -import { SCORE_FILL_HSL, scoreTier } from "@/lib/score-color"; +import type { Task } from "@/lib/exam/exam-helpers"; +import { effectiveScore, type TaskAnswer, type TaskGrade } from "@/lib/grading/grading"; +import { SCORE_FILL_HSL, scoreTier } from "@/lib/grading/score-color"; interface Props { tasks: Task[]; diff --git a/apps/examlense/frontend/src/pages/Exams.tsx b/apps/examlense/frontend/src/pages/exams/Exams.tsx similarity index 96% rename from apps/examlense/frontend/src/pages/Exams.tsx rename to apps/examlense/frontend/src/pages/exams/Exams.tsx index 69652f1..8515fde 100644 --- a/apps/examlense/frontend/src/pages/Exams.tsx +++ b/apps/examlense/frontend/src/pages/exams/Exams.tsx @@ -1,7 +1,7 @@ import { Link, useSearchParams } from "react-router-dom"; import { useEffect, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { parseExamPdf } from "@/lib/api-parse"; +import { parseExamPdf } from "@/lib/api/api-parse"; import { FileUp, Loader2, PenLine, Search } from "lucide-react"; import { Input } from "@/components/ui/input"; import { @@ -10,14 +10,14 @@ import { deleteExam, duplicateExam as apiDuplicateExam, type ExamListItem, -} from "@/lib/api-client"; -import { subscribeExamsList } from "@/lib/sse"; -import { useToast } from "@/hooks/use-toast"; -import { ThemeToggle } from "@/components/ThemeToggle"; +} from "@/lib/api/api-client"; +import { subscribeExamsList } from "@/lib/api/sse"; +import { useToast } from "@/hooks/ui/use-toast"; +import { ThemeToggle } from "@/components/shared/ThemeToggle"; import { StartExamDialog, type StartExamMode, -} from "@/components/start-exam/StartExamDialog"; +} from "@/pages/exams/start-exam/StartExamDialog"; import { AlertDialog, AlertDialogAction, @@ -28,8 +28,8 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { ExamsTable } from "@/components/exams/ExamsTable"; -import type { ExamRowHandlers } from "@/components/exams/ExamTableRow"; +import { ExamsTable } from "@/pages/exams/components/ExamsTable"; +import type { ExamRowHandlers } from "@/pages/exams/components/ExamTableRow"; import wordmarkLight from "@/assets/hestia-wordmark-light.svg"; import wordmarkDark from "@/assets/hestia-wordmark-dark.svg"; diff --git a/apps/examlense/frontend/src/components/exams/ExamActionsMenu.tsx b/apps/examlense/frontend/src/pages/exams/components/ExamActionsMenu.tsx similarity index 100% rename from apps/examlense/frontend/src/components/exams/ExamActionsMenu.tsx rename to apps/examlense/frontend/src/pages/exams/components/ExamActionsMenu.tsx diff --git a/apps/examlense/frontend/src/components/exams/ExamProgressCell.tsx b/apps/examlense/frontend/src/pages/exams/components/ExamProgressCell.tsx similarity index 87% rename from apps/examlense/frontend/src/components/exams/ExamProgressCell.tsx rename to apps/examlense/frontend/src/pages/exams/components/ExamProgressCell.tsx index bdfae6b..1958ccc 100644 --- a/apps/examlense/frontend/src/components/exams/ExamProgressCell.tsx +++ b/apps/examlense/frontend/src/pages/exams/components/ExamProgressCell.tsx @@ -1,7 +1,7 @@ -import type { ExamListItem } from "@/lib/api-client"; -import { examProgress } from "@/lib/exam-progress"; +import type { ExamListItem } from "@/lib/api/api-client"; +import { examProgress } from "@/lib/exam/exam-progress"; import { Progress } from "@/components/ui/progress"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; /** * Compact progress indicator for the dashboard table. Renders a small bar + a diff --git a/apps/examlense/frontend/src/components/ExamStatusBadge.tsx b/apps/examlense/frontend/src/pages/exams/components/ExamStatusBadge.tsx similarity index 97% rename from apps/examlense/frontend/src/components/ExamStatusBadge.tsx rename to apps/examlense/frontend/src/pages/exams/components/ExamStatusBadge.tsx index cf315c6..5cc3e4b 100644 --- a/apps/examlense/frontend/src/components/ExamStatusBadge.tsx +++ b/apps/examlense/frontend/src/pages/exams/components/ExamStatusBadge.tsx @@ -1,5 +1,5 @@ import { CheckCircle2, FileText, Gavel, Loader2, Sparkles } from "lucide-react"; -import type { Exam } from "@/lib/exam-helpers"; +import type { Exam } from "@/lib/exam/exam-helpers"; /** * Unified status badge shown on every exam card in the dashboard. Covers the diff --git a/apps/examlense/frontend/src/components/exams/ExamTableRow.tsx b/apps/examlense/frontend/src/pages/exams/components/ExamTableRow.tsx similarity index 92% rename from apps/examlense/frontend/src/components/exams/ExamTableRow.tsx rename to apps/examlense/frontend/src/pages/exams/components/ExamTableRow.tsx index 3b87fd1..b418d68 100644 --- a/apps/examlense/frontend/src/components/exams/ExamTableRow.tsx +++ b/apps/examlense/frontend/src/pages/exams/components/ExamTableRow.tsx @@ -2,11 +2,11 @@ import { useNavigate } from "react-router-dom"; import { formatDistanceToNow } from "date-fns"; import { enUS } from "date-fns/locale"; import { AlertTriangle } from "lucide-react"; -import type { ExamListItem } from "@/lib/api-client"; -import { examModePath } from "@/lib/exam-helpers"; -import { ExamStatusBadge } from "@/components/ExamStatusBadge"; -import { ModelLogo } from "@/components/ModelLogo"; -import { solverModelLabel } from "@/lib/llm-models"; +import type { ExamListItem } from "@/lib/api/api-client"; +import { examModePath } from "@/lib/exam/exam-helpers"; +import { ExamStatusBadge } from "@/pages/exams/components/ExamStatusBadge"; +import { ModelLogo } from "@/components/shared/ModelLogo"; +import { solverModelLabel } from "@/lib/exam/llm-models"; import { TableCell, TableRow } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { ExamProgressCell } from "./ExamProgressCell"; diff --git a/apps/examlense/frontend/src/components/exams/ExamsTable.tsx b/apps/examlense/frontend/src/pages/exams/components/ExamsTable.tsx similarity index 97% rename from apps/examlense/frontend/src/components/exams/ExamsTable.tsx rename to apps/examlense/frontend/src/pages/exams/components/ExamsTable.tsx index fd5e499..09f6126 100644 --- a/apps/examlense/frontend/src/components/exams/ExamsTable.tsx +++ b/apps/examlense/frontend/src/pages/exams/components/ExamsTable.tsx @@ -1,8 +1,8 @@ import { useEffect, useMemo, useState } from "react"; import { ArrowDown, ArrowUp, ChevronsUpDown } from "lucide-react"; -import type { ExamListItem } from "@/lib/api-client"; -import { fuzzyMatch } from "@/lib/fuzzy"; -import { progressSortValue } from "@/lib/exam-progress"; +import type { ExamListItem } from "@/lib/api/api-client"; +import { fuzzyMatch } from "@/lib/utils/fuzzy"; +import { progressSortValue } from "@/lib/exam/exam-progress"; import { Table, TableBody, @@ -19,7 +19,7 @@ import { PaginationNext, PaginationPrevious, } from "@/components/ui/pagination"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { ExamTableRow, type ExamRowHandlers } from "./ExamTableRow"; const PAGE_SIZE = 10; diff --git a/apps/examlense/frontend/src/components/start-exam/CoursePickerStep.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/CoursePickerStep.tsx similarity index 98% rename from apps/examlense/frontend/src/components/start-exam/CoursePickerStep.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/CoursePickerStep.tsx index 590aed5..0334602 100644 --- a/apps/examlense/frontend/src/components/start-exam/CoursePickerStep.tsx +++ b/apps/examlense/frontend/src/pages/exams/start-exam/CoursePickerStep.tsx @@ -1,9 +1,9 @@ import type { ReactNode } from "react"; import { Check, ExternalLink, Plus } from "lucide-react"; -import { useLghCourses } from "@/hooks/use-learning-goals"; +import { useLghCourses } from "@/hooks/data/use-learning-goals"; import { Input } from "@/components/ui/input"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; /** Sentinel selection value for "no course" (nothing picked → skippable). */ export const NO_COURSE = "none"; diff --git a/apps/examlense/frontend/src/components/start-exam/Field.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/Field.tsx similarity index 100% rename from apps/examlense/frontend/src/components/start-exam/Field.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/Field.tsx diff --git a/apps/examlense/frontend/src/components/start-exam/MetadataStep.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/MetadataStep.tsx similarity index 100% rename from apps/examlense/frontend/src/components/start-exam/MetadataStep.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/MetadataStep.tsx diff --git a/apps/examlense/frontend/src/components/start-exam/ParserModelStep.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/ParserModelStep.tsx similarity index 98% rename from apps/examlense/frontend/src/components/start-exam/ParserModelStep.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/ParserModelStep.tsx index 884e31e..f99de12 100644 --- a/apps/examlense/frontend/src/components/start-exam/ParserModelStep.tsx +++ b/apps/examlense/frontend/src/pages/exams/start-exam/ParserModelStep.tsx @@ -1,8 +1,8 @@ import { Check, Zap } from "lucide-react"; import { Switch } from "@/components/ui/switch"; -import { useParserModels } from "@/lib/api-models"; -import { cn } from "@/lib/utils"; +import { useParserModels } from "@/lib/api/api-models"; +import { cn } from "@/lib/utils/utils"; interface Props { selectedIds: string[]; diff --git a/apps/examlense/frontend/src/components/start-exam/SolverModelStep.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/SolverModelStep.tsx similarity index 94% rename from apps/examlense/frontend/src/components/start-exam/SolverModelStep.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/SolverModelStep.tsx index f6c3f7c..d30e770 100644 --- a/apps/examlense/frontend/src/components/start-exam/SolverModelStep.tsx +++ b/apps/examlense/frontend/src/pages/exams/start-exam/SolverModelStep.tsx @@ -1,12 +1,12 @@ import { Check } from "lucide-react"; -import { useSolverModels } from "@/lib/api-models"; -import { cn } from "@/lib/utils"; -import { ModelLogo } from "@/components/ModelLogo"; +import { useSolverModels } from "@/lib/api/api-models"; +import { cn } from "@/lib/utils/utils"; +import { ModelLogo } from "@/components/shared/ModelLogo"; import { SOLVER_MODEL_META as MODEL_META, SOLVER_MODEL_ORDER as MODEL_ORDER, -} from "@/lib/solver-model-meta"; +} from "@/lib/exam/solver-model-meta"; interface Props { value: string; diff --git a/apps/examlense/frontend/src/components/start-exam/StartExamDialog.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/StartExamDialog.tsx similarity index 97% rename from apps/examlense/frontend/src/components/start-exam/StartExamDialog.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/StartExamDialog.tsx index 2d01784..f6dd796 100644 --- a/apps/examlense/frontend/src/components/start-exam/StartExamDialog.tsx +++ b/apps/examlense/frontend/src/pages/exams/start-exam/StartExamDialog.tsx @@ -1,10 +1,10 @@ import { useEffect, useState, type ReactNode } from "react"; import { useNavigate } from "react-router-dom"; import { useQueryClient } from "@tanstack/react-query"; -import { parseExamPdf } from "@/lib/api-parse"; -import { useSolverModels } from "@/lib/api-models"; -import { resolveSelectableDefault, selectableModels } from "@/lib/llm-models"; -import { useToast } from "@/hooks/use-toast"; +import { parseExamPdf } from "@/lib/api/api-parse"; +import { useSolverModels } from "@/lib/api/api-models"; +import { resolveSelectableDefault, selectableModels } from "@/lib/exam/llm-models"; +import { useToast } from "@/hooks/ui/use-toast"; import { createExam, deleteExam, @@ -13,14 +13,14 @@ import { createSection, createBlock, createTask, -} from "@/lib/api-client"; +} from "@/lib/api/api-client"; import { WizardShell } from "./WizardShell"; import { UploadStep } from "./UploadStep"; import { ParserModelStep } from "./ParserModelStep"; import { SolverModelStep } from "./SolverModelStep"; import { MetadataStep, type ExamLanguage } from "./MetadataStep"; import { CoursePickerStep, CREATE_COURSE, NO_COURSE } from "./CoursePickerStep"; -import { useCreateLghCourse } from "@/hooks/use-learning-goals"; +import { useCreateLghCourse } from "@/hooks/data/use-learning-goals"; export type StartExamMode = "pdf" | "manual"; diff --git a/apps/examlense/frontend/src/components/start-exam/UploadStep.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/UploadStep.tsx similarity index 100% rename from apps/examlense/frontend/src/components/start-exam/UploadStep.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/UploadStep.tsx diff --git a/apps/examlense/frontend/src/components/start-exam/WizardShell.tsx b/apps/examlense/frontend/src/pages/exams/start-exam/WizardShell.tsx similarity index 99% rename from apps/examlense/frontend/src/components/start-exam/WizardShell.tsx rename to apps/examlense/frontend/src/pages/exams/start-exam/WizardShell.tsx index 69b3889..79fb011 100644 --- a/apps/examlense/frontend/src/components/start-exam/WizardShell.tsx +++ b/apps/examlense/frontend/src/pages/exams/start-exam/WizardShell.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from "react"; import { ArrowLeft, Loader2 } from "lucide-react"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/utils/utils"; import { Dialog, DialogContent, diff --git a/apps/examlense/frontend/src/components/start-exam/shared.ts b/apps/examlense/frontend/src/pages/exams/start-exam/shared.ts similarity index 100% rename from apps/examlense/frontend/src/components/start-exam/shared.ts rename to apps/examlense/frontend/src/pages/exams/start-exam/shared.ts diff --git a/apps/examlense/frontend/src/test/example.test.ts b/apps/examlense/frontend/src/test/example.test.ts deleted file mode 100644 index 52e6d2f..0000000 --- a/apps/examlense/frontend/src/test/example.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from "vitest"; - -describe("example", () => { - it("should pass", () => { - expect(true).toBe(true); - }); -}); diff --git a/apps/examlense/frontend/src/test/resilience.test.ts b/apps/examlense/frontend/src/test/resilience.test.ts index 0e6e2de..b245d53 100644 --- a/apps/examlense/frontend/src/test/resilience.test.ts +++ b/apps/examlense/frontend/src/test/resilience.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { resilienceScore, resilienceTier } from "@/lib/resilience"; +import { resilienceScore, resilienceTier } from "@/lib/grading/resilience"; describe("resilienceScore", () => { it("inverts the AI percentage", () => {