Skip to content

Commit b2ac593

Browse files
Edwin ChanEdwin Chan
authored andcommitted
Add attempt review workflow
1 parent 271402e commit b2ac593

23 files changed

Lines changed: 1130 additions & 19 deletions

CENTRAL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Stack: Next.js, NextAuth (Google + dev bypass), Prisma, KaTeX for math, Lucide i
3333

3434
### Recently landed
3535

36+
- **Attempt review** — `/attempts/[id]` presents a DBSOJ-inspired submission summary and dense expandable question rows with correct/incorrect/skipped state, submitted and normalized answers, accepted answers, awarded marks, topic, statement/assets, grader notes, and explanations. The route is owner-only for ordinary students and permits staff with `admin:analytics`; unauthorized attempt IDs return 404. Review links appear immediately after submit, on solved-set locks and set attempt history, on student dashboard history, and in student/per-set admin analytics. Pure summary/status helpers live in `lib/attempt-review.ts`, and `proxy.ts` now includes `/attempts/:path*`. Local Chrome QA covered student submission, expansion, light/dark rendering, history discovery, horizontal overflow, and cross-student denial. The local-only bypass controls are again surfaced when `AUTH_DEV_BYPASS=true` (sources: `app/attempts/[id]/page.tsx`, `app/problem-sets/[slug]/answer-grid.tsx`, `app/problem-sets/[slug]/page.tsx`, `app/dashboard/page.tsx`, `app/admin/students/[id]/page.tsx`, `app/admin/sets/[id]/analytics/page.tsx`, `app/page.tsx`, `proxy.ts`).
3637
- **Application-wide safety and maintainability pass** — all API JSON/multipart bodies now use bounded streamed readers and route-specific schemas; auth uses exact school-domain matching and an explicit non-production bypass opt-in; staff routing and private-profile/leaderboard authorization use the documented permissions. Submission, friendship, role mutation, problem-set asset, and FTW room transitions are race-safe; FTW room codes use cryptographic randomness. Grading preserves exact large integer/fraction/decimal identity instead of collapsing through IEEE-754. Imports stage storage before atomic metadata attachment, server and browser ZIP paths enforce actual expanded-byte limits, replacement/deletion paths compensate for failures, and file reads verify size/checksum under hard limits. Exports/restores are paginated and capped, CSV formula cells are neutralized, expensive GETs reject cross-site browser requests, and formerly unbounded assignment/practice-tag scans are computed in bounded SQL. Formerly floating `latest` dependencies are pinned, full and production dependency audits report zero vulnerabilities, and response security headers are enabled. The UI, feature set, and database schema are unchanged. Regression coverage spans 29 test files/195 tests, including policy, body, grading, concurrency, storage, and import cases.
3738
- **Hand-drawn visual system** — `app/globals.css` now provides paper/ink design tokens, graph-paper backgrounds, asymmetric squircle cards and controls, marker-color accents, and irregular badges/tabs. Shared surfaces use native asymmetric borders instead of `border-image`, which created rigid grey rectangles outside rounded controls. Metric cards use the same neutral border on every edge instead of decorative colored strips. Dark structural borders are intentionally quieter, search inputs are borderless at rest with one cyan focus stroke, and selected problem-set tag counts use opaque ink/paper colors for readable contrast. Global wavy eyebrow/title underlines remain removed. A signed-in production Chrome audit of `/problem-sets/1991-ajhsme` also removed percentage shape paths from variable-height panels, fixed oversized panel-header geometry, and removed the blue statement band. Desktop navigation is a 64 px icon rail that expands to 240 px on hover/focus, while mobile retains the stable off-canvas sheet. The simplified Sigma in `public/dbsmo-mark.svg` is shared by favicon metadata and the landing brand. `app/layout.tsx` loads Shantell Sans for display/control text while Inter remains the body and math font. Route-specific coverage was visually audited across dashboard, problem catalog/detail, writeups, practice, classes, leaderboard, users/profiles, settings, and admin surfaces; FTW and Playground were intentionally excluded. Unsupported browsers retain ordinary border/radius fallbacks; details are in `docs/visual-system.md`.
3839
- **Mobile/classes cleanup** — fixed mobile `/problem-sets` hiding task rows by restoring the responsive card table, stabilized the mobile sidebar grid to avoid icon reflow on tap/focus, and made dashboard auth/actions render as a compact mobile account card. `/classes` now has a teacher/admin `Announcements` subtab that lists existing class messages and supports author/admin deletion. Display-name fallbacks now treat literal `"null"`/`"undefined"` strings as empty via `lib/display-name.ts`.

DBSMO/Projects/dbsmo/00 Start Here.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
date: 2026-06-26
3-
updated: 2026-07-18
3+
updated: 2026-07-19
44
type: project-index
55
tags:
66
- project
@@ -28,6 +28,7 @@ This is the starting index for the [[dbsmo]] codebase knowledge base, generated
2828
- [[Common Tasks]] - where to edit for likely future changes.
2929
- [[Risks and Pitfalls]] - fragile or confusing areas to avoid breaking.
3030
- [[Glossary]] - project-specific terms and abbreviations.
31+
- [[Attempt Review]] - saved submission review UI, authorization, data flow, and entry points.
3132

3233
## Fast Orientation
3334

@@ -38,6 +39,7 @@ This is the starting index for the [[dbsmo]] codebase knowledge base, generated
3839
- Main UI routes live under `app/`; API handlers live under `app/api/`.
3940
- Current import notes include optional same-name image ZIPs for JSON imports, per-problem image uploads in the problem maker, tolerant JSON editor drafts, and compressed/actual-expanded archive limits shared through `lib/import/zip-entry.ts` (sources: `lib/import/json-import.ts`, `lib/import/image-zip.ts`, `lib/import/zip-dry-run.ts`, `app/admin/create/page-client.tsx`).
4041
- Current class/community notes include authored tasks, a mastery heatmap, problem-set writeups with image uploads/voting/deletion, and class announcements pinned on dashboards (sources: `app/users/[username]/page.tsx`, `app/problem-sets/[slug]/writeups/page.tsx`, `app/writeups/page.tsx`, `app/classes/announcement-composer.tsx`, `app/dashboard/page.tsx`, `prisma/schema.prisma`).
42+
- Saved submissions have an owner/staff-gated [[Attempt Review]] with links from submit results, solved-set locks, set/dashboard history, student detail, and per-set analytics (sources: `app/attempts/[id]/page.tsx`, `app/problem-sets/[slug]/answer-grid.tsx`, `app/dashboard/page.tsx`).
4143

4244
## Source Inspection Basis
4345

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
date: 2026-07-19
3+
updated: 2026-07-19
4+
type: feature
5+
tags: [project, architecture, attempts, grading, ui, dbsmo]
6+
ai-first: true
7+
project: "[[dbsmo]]"
8+
confidence: high
9+
scanned-commit: 271402e
10+
---
11+
12+
## Purpose
13+
14+
`/attempts/[id]` is the durable review for one submitted `Attempt`. Its information architecture follows a competitive-programming submission page: a compact identity/verdict header, score metrics, and dense per-question rows that expand into grading detail. It uses the shared DBSMO paper/ink visual system rather than copying another product's branding (source: `app/attempts/[id]/page.tsx`, `app/globals.css`).
15+
16+
## Authorization
17+
18+
- The current user can review an attempt when `Attempt.userId` matches their database user ID.
19+
- Staff with `admin:analytics` can review another user's attempt. This currently covers Admin, Teacher, and Analyst according to `lib/permissions.ts`.
20+
- Unknown and unauthorized attempt IDs both call `notFound()` so the route does not disclose whether another student's submission exists.
21+
- `proxy.ts` includes `/attempts/:path*` as the broad signed-in boundary, but the page query remains authoritative (sources: `app/attempts/[id]/page.tsx`, `proxy.ts`, `lib/permissions.ts`).
22+
23+
## Data Display
24+
25+
The page loads `Attempt`, its `User`, `ProblemSet` assets/problem file, and `Response.problem` records. Rows are sorted by `Problem.number`. The summary shows verdict, score, percentage, correct count, skipped count, and duration. An expanded row shows:
26+
27+
- problem statement and `[[img:key]]` assets through `LatexStatement`;
28+
- raw submitted answer and a normalized answer when grading changed its representation;
29+
- deduplicated `Problem.answerKey` plus `Problem.acceptedAnswers` rendered with KaTeX;
30+
- `Response.pointsAwarded` against `Problem.points`, `Response.graderNote`, topic tags, and `Problem.explanationNote`;
31+
- links back to the set/question, the set's writeups, and the original problem file when present.
32+
33+
Sources: `app/attempts/[id]/page.tsx`, `lib/attempt-review.ts`, `app/problem-sets/[slug]/latex-statement.tsx`, `prisma/schema.prisma`.
34+
35+
## Entry Points
36+
37+
- Immediate submit result and solved-set lock: `app/problem-sets/[slug]/answer-grid.tsx`.
38+
- Five most recent attempts on a set: `app/problem-sets/[slug]/page.tsx`.
39+
- Student dashboard history: `app/dashboard/page.tsx`.
40+
- Staff student history: `app/admin/students/[id]/page.tsx`.
41+
- Per-set recent attempts: `app/admin/sets/[id]/analytics/page.tsx`.
42+
43+
## Testing
44+
45+
Pure behavior is covered by `tests/attempt-review.test.ts`; solved-lock review-link output is covered by `tests/answer-grid.test.ts`. The 2026-07-19 Chrome QA used an isolated local Postgres database and verified immediate review navigation, correct/incorrect/skipped rows, row expansion, accepted-answer math, light/dark rendering, set-history discovery, absence of horizontal overflow, and cross-student 404 behavior.
46+
47+
Related: [[Components]], [[Data and Storage]], [[Entry Points]], [[Common Tasks]], [[Risks and Pitfalls]].

DBSMO/Projects/dbsmo/Common Tasks.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
date: 2026-06-26
3-
updated: 2026-07-18
3+
updated: 2026-07-19
44
type: common-tasks
55
tags: [project, architecture, maintenance, dbsmo]
66
ai-first: true
@@ -39,6 +39,10 @@ Watch for uppercase Prisma enum values versus lowercase `lib/grading.ts` values
3939

4040
Edit `lib/grading.ts` first. Preserve the bounded `BigInt` normalization for integers/fractions and canonical base-10 comparison for zero-tolerance decimals; converting those paths directly through `Number` can merge distinct answers above `Number.MAX_SAFE_INTEGER`. Then inspect all callers: full submission, practice submission, admin regrade, FTW solo submit, and FTW room submit (sources: `app/api/submit/route.ts`, `app/api/practice/submit/route.ts`, `app/api/admin/sets/[id]/regrade/route.ts`, `app/api/ftw/matches/[id]/submit/route.ts`, `app/api/ftw/rooms/[code]/submit/route.ts`). Add/update `tests/grading.test.ts`.
4141

42+
## Change Attempt Review
43+
44+
Start with [[Attempt Review]]. The server page and exact owner/staff authorization live in `app/attempts/[id]/page.tsx`; summary/status helpers and unit tests live in `lib/attempt-review.ts` and `tests/attempt-review.test.ts`. Entry links are spread across `app/problem-sets/[slug]/answer-grid.tsx`, `app/problem-sets/[slug]/page.tsx`, `app/dashboard/page.tsx`, `app/admin/students/[id]/page.tsx`, and `app/admin/sets/[id]/analytics/page.tsx`. Keep `/attempts/:path*` in `proxy.ts`, but do not rely on middleware for the per-attempt ownership check.
45+
4246
## Change LaTeX Statement Support
4347

4448
Edit `lib/latex-compat.ts` for source normalization, table/display conversion, and conservative compatibility macros. Edit `app/problem-sets/[slug]/latex-statement.tsx` for the escape-aware tokenizer, HTML math-tag normalization, and KaTeX security options. Keep `trust: false`, `globalGroup: false`, fresh macros per expression, and finite `maxSize`/`maxExpand` limits. Add normal syntax, production-regression, and hostile-input tests in `tests/latex-statement.test.ts`. Do not replace the tokenizer with delimiter regexes or treat `\usepackage` as permission to load code/files; document supported fallbacks in `docs/latex-support.md` (sources: named files).

DBSMO/Projects/dbsmo/Components.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
date: 2026-06-26
3-
updated: 2026-07-13
3+
updated: 2026-07-19
44
type: components
55
tags: [project, architecture, components, ui, dbsmo]
66
ai-first: true
@@ -33,8 +33,9 @@ This note maps important [[dbsmo]] UI/components to their source files and usage
3333
- `MetricCard` in `app/dashboard/page.tsx`: local component for dashboard metric cards. Dashboard and admin metric cards intentionally share a neutral border without per-card colored edge accents (sources: `app/dashboard/page.tsx`, `app/admin/analytics/page.tsx`, `app/admin/feedback/page.tsx`, `app/admin/sets/[id]/analytics/page.tsx`, `app/admin/students/[id]/page.tsx`, `app/globals.css`).
3434
- `AssignmentsWidget` in `app/dashboard/assignments-widget.tsx`: client component that fetches `/api/assignments/mine`, sorts assignments, and renders up to five dashboard assignment links.
3535
- `ProblemSetsPage` in `app/problem-sets/page.tsx`: route page that handles catalog filtering/sorting/views/recommendations/pagination and renders set cards/links.
36-
- `ProblemSetPage` in `app/problem-sets/[slug]/page.tsx`: route page that loads a set by slug and chooses inline-statement vs PDF/file layout.
37-
- `AnswerGrid` in `app/problem-sets/[slug]/answer-grid.tsx`: client answer form, autosave, review-later state, submit-to-`/api/submit`, result display, missed-topic next action, and feedback report dialog. When `ProblemSetPage` detects the set tag `Tests`, it passes the test layout so answer-only/PDF sets render as a 20×3 test answer sheet for 60 underlying `Problem` rows.
36+
- `ProblemSetPage` in `app/problem-sets/[slug]/page.tsx`: route page that loads a set by slug, chooses inline-statement vs PDF/file layout, and exposes the current user's five most recent saved attempt reviews.
37+
- `AnswerGrid` in `app/problem-sets/[slug]/answer-grid.tsx`: client answer form, autosave, review-later state, submit-to-`/api/submit`, result display, direct saved-review links, missed-topic next action, and feedback report dialog. When `ProblemSetPage` detects the set tag `Tests`, it passes the test layout so answer-only/PDF sets render as a 20×3 test answer sheet for 60 underlying `Problem` rows.
38+
- `AttemptReviewPage` in `app/attempts/[id]/page.tsx`: server-rendered [[Attempt Review]] with a submission identity/verdict summary and expandable question-result rows. It renders statements/assets and accepted answers through `LatexStatement`, and uses `lib/attempt-review.ts` for status, percentage, answer-list, and duration helpers.
3839
- `BookmarkButton` in `app/problem-sets/[slug]/bookmark-button.tsx`: client bookmark toggle backed by `/api/problem-sets/[id]/bookmark`.
3940
- Writeup header link in `app/problem-sets/[slug]/page.tsx`: icon link next to `BookmarkButton` that opens `/problem-sets/[slug]/writeups`.
4041
- `WriteupsPage` and `WriteupsClient` in `app/problem-sets/[slug]/writeups/`: server/client pair for set writeups. The server page handles auth, set visibility, sorting, and initial data; the client component handles the composer, image selection, optimistic voting, confirm-delete controls, and feed cards rendered with `LatexStatement`.

DBSMO/Projects/dbsmo/Data and Storage.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ Deployment docs currently use `npx prisma db push` and `npx prisma generate`, no
5252
4. Finds previous attempts and blocks new submission if a perfect attempt already exists.
5353
5. Grades each answer with `gradeAnswer(...)`.
5454
6. Creates one `Attempt` and many `Response` records in a transaction.
55-
7. Returns attempt number, score, percentage, and per-problem result summary.
55+
56+
Saved attempts are read by `/attempts/[id]` for [[Attempt Review]]. The page joins `Attempt.user`, `Attempt.problemSet` assets/file, and each `Response.problem`, then sorts responses by `Problem.number`. It exposes raw/normalized answers, correctness, points, accepted answers, grader notes, and explanations only to the attempt owner or a user with `admin:analytics`; unauthorized IDs resolve through `notFound()` (sources: `app/attempts/[id]/page.tsx`, `lib/attempt-review.ts`, `lib/permissions.ts`). 7. Returns attempt number, score, percentage, and per-problem result summary.
5657

5758
Sources: `app/api/submit/route.ts`, `lib/grading.ts`, `prisma/schema.prisma`.
5859

DBSMO/Projects/dbsmo/Entry Points.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Deployment flow is documented in `SETUP.md`: install dependencies, run `npx pris
3232

3333
- `app/api/auth/[...nextauth]/route.ts` exposes NextAuth.
3434
- `lib/auth.ts` configures providers, session callback, JWT callback, allowed email domains, allowed email exceptions, and dev bypass.
35-
- `proxy.ts` protects `/admin/:path*`, `/dashboard`, `/dashboard/:path*`, and `/problem-sets/:path*`; it requires a token and redirects non-ADMIN users away from `/admin`.
35+
- `proxy.ts` protects `/admin/:path*`, `/attempts/:path*`, `/dashboard`, `/dashboard/:path*`, and `/problem-sets/:path*`; it requires a token and applies the broad staff gate to `/admin`. Individual pages remain responsible for exact permissions.
3636
- `lib/permissions.ts` defines finer admin permissions used inside pages/APIs.
3737

3838
## Public/Auth App Routes
@@ -48,6 +48,7 @@ Deployment flow is documented in `SETUP.md`: install dependencies, run `npx pris
4848

4949
- `/problem-sets` - browse/filter/sort problem sets with recommendations, assignments/bookmarks/practice views, media/status/category filters, search, and pagination (source: `app/problem-sets/page.tsx`).
5050
- `/problem-sets/[slug]` - set detail and answer entry, with inline statements or PDF fallback and file/video/solution display (source: `app/problem-sets/[slug]/page.tsx`).
51+
- `/attempts/[id]` - saved [[Attempt Review]] for the attempt owner or staff with `admin:analytics`; unauthorized and unknown IDs both return 404 (source: `app/attempts/[id]/page.tsx`).
5152
- `/problem-sets/[slug]/writeups` - set writeup feed/composer with latest/top sorting, image attachments, and voting (sources: `app/problem-sets/[slug]/writeups/page.tsx`, `app/problem-sets/[slug]/writeups/writeups-client.tsx`).
5253
- `/writeups` - global writeups directory from the sidebar with latest/top views and problem-set search (source: `app/writeups/page.tsx`).
5354
- `/practice` - practice-mode UI backed by tags/next/submit APIs (source: `app/practice/page.tsx`).

DBSMO/Projects/dbsmo/Risks and Pitfalls.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
date: 2026-06-26
3-
updated: 2026-07-18
3+
updated: 2026-07-19
44
type: risks
55
tags: [project, architecture, risks, dbsmo]
66
ai-first: true
@@ -37,7 +37,11 @@ Optional image ZIP imports derive asset keys from image filenames by lowercasing
3737

3838
## Full-Set Perfect Score Locks Further Attempts
3939

40-
`POST /api/submit` blocks new attempts if any previous attempt has `score === maxScore`; the check and next attempt number are computed inside a serializable transaction with bounded retries. `ProblemSetPage` passes `lockedAttemptNumber` to `AnswerGrid`. The locked UI must still render problem statements/PDF context and only remove answer entry/submission controls. Any change to attempt/retake semantics needs to update both server logic and UI messaging (sources: `app/api/submit/route.ts`, `lib/submission.ts`, `app/problem-sets/[slug]/page.tsx`, `app/problem-sets/[slug]/answer-grid.tsx`).
40+
`POST /api/submit` blocks new attempts if any previous attempt has `score === maxScore`; the check and next attempt number are computed inside a serializable transaction with bounded retries. `ProblemSetPage` passes the locked attempt ID/number to `AnswerGrid` so the student can still review that saved submission. The locked UI must still render problem statements/PDF context and only remove answer entry/submission controls. Any change to attempt/retake semantics needs to update both server logic and UI messaging (sources: `app/api/submit/route.ts`, `lib/submission.ts`, `app/problem-sets/[slug]/page.tsx`, `app/problem-sets/[slug]/answer-grid.tsx`).
41+
42+
## Attempt Reviews Expose Answer Keys
43+
44+
`/attempts/[id]` intentionally shows accepted answers and explanations after submission. Its database join therefore handles assessment-sensitive data. Preserve both checks in `app/attempts/[id]/page.tsx`: ordinary users must own the `Attempt`, and non-owners must have `admin:analytics`. Keep unauthorized IDs on the same `notFound()` path as missing IDs, and do not move answer keys into a client API without an equivalent exact authorization boundary (sources: `app/attempts/[id]/page.tsx`, `lib/permissions.ts`, [[Attempt Review]]).
4145

4246
Writeups intentionally remain accessible even when submissions are locked or the user has not submitted. Do not reuse submission-lock logic to hide `/problem-sets/[slug]/writeups`; only normal auth and set visibility should gate that page (sources: `app/problem-sets/[slug]/writeups/page.tsx`, `app/api/problem-sets/[id]/writeups/route.ts`).
4347

0 commit comments

Comments
 (0)