practice slice 5 — UI: list page, exam page, creation dialog, sidebar entries
components/practice-dialog.tsx: portal dialog (delete-session shape), three dial rows (SettingSlider markup, anchor text as display: flashcard-competition / near the lesson-far transfer / theory- application, defaults 0.5), session picker (generic mode, /api/chats) or preselected lock (right-click path); 400 empty_selection renders 'This session has no propositions yet', 503 renders the server line + Open settings deep link (useSettingsDialog, zero-keys precedent). Pure body exported for SSR pins. components/practice-list.tsx: rows (title, date, ready/total problems, graded count, status word; destructive tint on error), click-through, delete affordance → portaled confirm (DeleteSetBody: no mode checkbox — a set has no preserve-graph question), 2s reveal-poll while any set is partitioning/generating. components/practice-exam.tsx: numbered problems; generating stubs (BrailleSpinner + 'Generating problem…', propositions never surface); ready statements/solutions/rubrics/ feedback as MessageResponse markdown with restoreMathEscapes at the display seam; per-problem textarea + useDictation instance with the DictationBar rendered only on box focus; one Submit answers POST per set; graded reveal (verdict chip, feedback, model solution, rubric) with read-only answer; polling while not ready OR grading-live (activity feed practice rows — dropped grades terminate the poll). app/practice/page.tsx (force-dynamic, listPracticeSets) + app/practice/ [id]/page.tsx (getPracticeSet + notFound). Sidebar: quiet Practice nav row under New session (GraduationCapIcon, active on /practice) + 'Practice this session's propositions' in the session right-click below the hairline, above CoverageMenuBlock — opens the dialog preselected. tests/render-practice.test.tsx — 15 pins: dials + anchors, picker selection gating, preselected lock, empty-selection line, 503 deep link, list rows/counts/empty, delete confirm + inline failure, exam generating (no proposition text, no answer boxes, no submit), ready (markdown statement, gated submit), graded reveal + read-only + sibling answerable, failed problem/set lines, math healing. 1129/1129, tsc 0, lint 0, build 0.
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* One exam page (slice 5): the set server-prefetched, notFound() on an
|
||||
* unknown id; the client body owns polling (generation + grading),
|
||||
* answering, and the reveal.
|
||||
*/
|
||||
import { notFound } from "next/navigation";
|
||||
import { getPracticeSet } from "@/src/server";
|
||||
import { PracticeExam } from "@/components/practice-exam";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const set = getPracticeSet(id);
|
||||
if (!set) notFound();
|
||||
return <PracticeExam initial={set} />;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* The practice list page (slice 5): the set list, server-prefetched
|
||||
* (force-dynamic — a static list would go stale between requests), the
|
||||
* client body owns polling + dialogs. Reads through the server barrel
|
||||
* only (app/ never touches src/store directly).
|
||||
*/
|
||||
import { listPracticeSets } from "@/src/server";
|
||||
import { PracticeListPage } from "@/components/practice-list";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function Page() {
|
||||
return <PracticeListPage initialSets={listPracticeSets()} />;
|
||||
}
|
||||
Reference in New Issue
Block a user