Skip to content

Commit e49e7aa

Browse files
Edwin ChanEdwin Chan
authored andcommitted
Add sandboxed diagrams and multiple choice
1 parent 3ae9e1d commit e49e7aa

49 files changed

Lines changed: 2238 additions & 196 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CENTRAL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@ Stack: Next.js, NextAuth (Google + dev bypass), Prisma, KaTeX for math, Lucide i
2929

3030
### Now (this session)
3131

32-
- No active half-finished feature after the safety and maintainability pass landed.
32+
- No half-finished feature is currently tracked here.
3333

3434
### Recently landed
3535

36+
- **Asymptote diagrams and multiple choice** — staff create/edit forms and JSON imports can compile `<asy>...</asy>` into validated PNG assets through a fail-closed Asymptote plus OS sandbox. Problems can use `MULTIPLE_CHOICE` with two to 20 LaTeX/image-capable choices across standard sets, Tests layouts, Practice, JSON import/export, and backup export. Production requires the documented Asymptote/bubblewrap packages, environment opt-in, and the Prisma migration adding `Problem.options` (sources: `lib/asymptote.ts`, `app/api/admin/asymptote/render/route.ts`, `app/admin/problem-authoring-controls.tsx`, `lib/import/json-import.ts`, `prisma/schema.prisma`, `docs/asymptote.md`).
3637
- **Clickable, contained Students table** — the admin Students list no longer has a redundant View action column. Each row navigates to the student detail page, while the student name remains a native keyboard-accessible link. Route-specific fixed table sizing prevents desktop horizontal scrolling, and narrower viewports switch to labeled two-column student cards instead of a minimum-width scrolling table (sources: `app/admin/students/page.tsx`, `app/admin/students/student-table-row.tsx`, `app/globals.css`).
3738
- **Unframed profile summary grid** — the Strongest topics, Recent completions, and Bookmarked sets cards now sit directly in the profile layout without a redundant outer `profile-section` card around their existing individual borders (source: `app/users/[username]/page.tsx`).
3839
- **Immediate content rendering** — removed the global `AnimeRouteEffects` stagger, analytics card/bar entrance pass, trend-chart drawing animation, page fade, smooth scrolling, FTW result-row fade, and floating confetti motion. Routes and analytics now render at their final state immediately. Anime.js remains only for the shared math loading indicator and typewriter caret; ordinary hover/focus and functional feedback transitions remain (sources: `app/layout.tsx`, `app/admin/analytics/page.tsx`, `app/admin/sets/[id]/analytics/page.tsx`, `app/admin/analytics/trend-chart.tsx`, `app/globals.css`).

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-19
3+
updated: 2026-08-04
44
type: project-index
55
tags:
66
- project
@@ -30,6 +30,7 @@ This is the starting index for the [[dbsmo]] codebase knowledge base, generated
3030
- [[Glossary]] - project-specific terms and abbreviations.
3131
- [[Attempt Review]] - saved submission review UI, authorization, data flow, and entry points.
3232
- [[Performance Analytics]] - shared Mastery Index, component metrics, validation, and change guidance.
33+
- [[Asymptote and Multiple Choice]] - diagram sandbox, variable choices, image-backed options, authoring/import flow, and deploy constraints.
3334

3435
## Fast Orientation
3536

@@ -42,6 +43,7 @@ This is the starting index for the [[dbsmo]] codebase knowledge base, generated
4243
- 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`).
4344
- 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`).
4445
- Cross-set student metrics use the shared evidence-aware [[Performance Analytics]] model; Mastery Index combines proficiency, breadth, and a consistency floor while keeping best-set average/mastery rate visible (sources: `lib/analytics.ts`, `app/leaderboard/page.tsx`).
46+
- Authoring and imports support sandboxed Asymptote-to-PNG diagrams plus `MULTIPLE_CHOICE` problems with two to 20 LaTeX/image-capable choices; see [[Asymptote and Multiple Choice]] (sources: `lib/asymptote.ts`, `lib/problem-set-authoring.ts`, `lib/import/json-import.ts`, `app/admin/problem-authoring-controls.tsx`).
4547

4648
## Source Inspection Basis
4749

DBSMO/Projects/dbsmo/Architecture.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-19
3+
updated: 2026-08-04
44
type: architecture
55
tags: [project, architecture, system-design, dbsmo]
66
ai-first: true
@@ -76,6 +76,8 @@ Writeup images reuse the existing storage/file-serving boundary: `lib/writeup-im
7676

7777
Manual/admin authoring uses `lib/problem-set-authoring.ts` for slug validation, uploaded PDF payload schema, create/patch schemas, problem normalization, answer-key splitting, and duplicate problem-number checks (source: `lib/problem-set-authoring.ts`). The admin edit route updates problem set metadata and replaces/updates/deletes child problems inside a Prisma transaction (source: `app/api/admin/sets/[id]/route.ts`).
7878

79+
Asymptote and multiple-choice authoring are a focused extension of this path. Raw `<asy>` source is accepted only on staff create/edit/import surfaces, compiled to validated PNG in `lib/asymptote.ts`, and persisted through ordinary `ProblemSetAsset` records; students never execute or receive source. `MULTIPLE_CHOICE` persists two to 20 strings in `Problem.options`, with image tokens sharing the existing asset resolver. See [[Asymptote and Multiple Choice]] (sources: `app/admin/problem-authoring-controls.tsx`, `app/api/admin/asymptote/render/route.ts`, `prisma/schema.prisma`).
80+
7981
JSON import uses `lib/import/json-import.ts`: it validates a bounded problem-set JSON payload, supports import dry-run, import commit, and import-to-editor draft, normalizes statement format and answer types, validates assets, persists problem sets/problems, and can replace an existing set while preserving its ID/order path where applicable. Image bytes are staged first and attached to locked set/file metadata in the same database transaction; rollback removes staged objects and successful replacement cleans unreferenced objects best-effort (sources: `lib/import/json-import.ts`, `lib/import/persist-image-assets.ts`, `lib/problem-set-locks.ts`, `lib/imported-file-cleanup.ts`).
8082

8183
ZIP import uses `lib/import/zip-dry-run.ts` and `lib/import/zip-import.ts`: it checks signature/compressed size/entry count, blocks absolute/traversal/overlong paths, reads strict UTF-8 manifest/CSV through `lib/import/zip-entry.ts`, bounds actual inflated bytes instead of trusting archive metadata, and validates schema/row/file totals before persistence. Referenced objects are staged one at a time under a random batch prefix and removed if metadata creation fails. The browser JSON batch panel applies its own compressed/entry/per-file/aggregate limits and extracts sequentially through `lib/import/client-zip-entry.ts` before sending each child to the same server validators (sources: `lib/import/zip-dry-run.ts`, `lib/import/zip-import.ts`, `lib/import/zip-entry.ts`, `lib/import/client-zip-entry.ts`, `lib/import/zip-path.ts`, `lib/import/manifest-schema.ts`, `lib/import/answer-schema.ts`, `app/admin/import/json-zip-import-panel.tsx`).
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
date: 2026-08-04
3+
updated: 2026-08-04
4+
type: feature-architecture
5+
tags: [project, architecture, authoring, security, dbsmo]
6+
ai-first: true
7+
project: "[[dbsmo]]"
8+
confidence: high
9+
---
10+
11+
## Purpose
12+
13+
This note covers two related authoring additions: server-rendered Asymptote diagrams and variable-length multiple-choice questions. They meet in the existing problem image pipeline, because generated diagrams and choice images both become keyed `ProblemSetAsset` records referenced with `[[img:key]]` (sources: `lib/asymptote.ts`, `lib/import/image-assets.ts`, `prisma/schema.prisma`). See [[Architecture]], [[Data and Storage]], and [[Risks and Pitfalls]] for surrounding boundaries.
14+
15+
## Asymptote Flow
16+
17+
Staff enter source in the shared control at `app/admin/problem-authoring-controls.tsx` or place complete `<asy>...</asy>` blocks in JSON statements. `app/api/admin/asymptote/render/route.ts` provides authorized manual preview; create/edit routes and `lib/import/json-import.ts` also compile any still-embedded blocks server-side so clients cannot bypass conversion. `renderEmbeddedAsymptoteStatements(...)` deduplicates source by SHA-256-derived key, returns validated PNG assets, and rewrites each block to `[[img:asy-...]]` (sources: `lib/asymptote.ts`, `app/api/admin/create-set/route.ts`, `app/api/admin/sets/[id]/route.ts`).
18+
19+
Production Linux launches `/usr/bin/asy` through `prlimit` and bubblewrap. The sandbox unshares network/process namespaces, exposes only read-only runtime/TeX/font trees, gives the compiler one disposable writable work directory, and combines Asymptote `-safe` with CPU/address-space/file/process/descriptor/wall/workspace limits. Output must be one valid PNG within 4 MB, 4096 px per side, and 16 million pixels. `ASYMPTOTE_ENABLED=true` is an explicit opt-in after host dependencies are installed; otherwise the route fails closed. macOS `sandbox-exec` support is only a local-development convenience, not a production boundary (sources: `lib/asymptote.ts`, `SETUP.md`, `docs/asymptote.md`).
20+
21+
Uncertainty: Linux bubblewrap behavior is covered by command construction, tests, and the deploy verification recipe, but the 2026-08-04 local live render ran on macOS because that was the available host. Production must run the documented PM2-user smoke test before enabling the feature (source: `SETUP.md`).
22+
23+
## Multiple-Choice Flow
24+
25+
`AnswerType.MULTIPLE_CHOICE` and `Problem.options String[]` are persisted in `prisma/schema.prisma`. Authoring validation requires two to 20 non-empty unique options and an `answerKey` equal to one option. The GUI permits adding/removing options, selecting the correct radio, rendering LaTeX, and attaching an image to any option. Standard sets, compact Tests cells, and Practice all render radios instead of free-response fields (sources: `lib/problem-set-authoring.ts`, `app/admin/problem-authoring-controls.tsx`, `app/problem-sets/[slug]/answer-grid.tsx`, `app/practice/page.tsx`).
26+
27+
JSON accepts string options or `{ "text": "...", "imageRef": "file.png" }` objects. `correctOption` is a 1-based index and is preferred when an image token would make a literal answer key awkward. Image files use the same top-level inline asset or same-name companion ZIP rules as statement images. Export emits `options` plus `correctOption`; backup export retains raw options (sources: `lib/import/json-import.ts`, `lib/import/problem-set-json-export.ts`, `lib/admin-exports.ts`, `docs/import-format.md`).
28+
29+
Grading compares the exact case-sensitive stored option. The browser submits the stored option string, while server grading remains authoritative. `MULTIPLE` remains the older free-response mode for alternate accepted answers and must not be conflated with `MULTIPLE_CHOICE` (sources: `lib/grading.ts`, `docs/grading.md`).
30+
31+
## Change Checklist
32+
33+
- Schema/migration: `prisma/schema.prisma`, `prisma/migrations/20260804090000_add_multiple_choice/migration.sql`.
34+
- Authoring: `app/admin/problem-authoring-controls.tsx`, create/edit clients and API routes, `lib/problem-set-authoring.ts`.
35+
- Import/export: `lib/import/json-import.ts`, `lib/import/json-draft-storage.ts`, `lib/import/problem-set-json-export.ts`, `lib/admin-exports.ts`.
36+
- Student UI: `app/problem-sets/[slug]/answer-grid.tsx`, `app/practice/page.tsx`, Practice API routes.
37+
- Grading callers: `lib/grading.ts`, Practice, FTW match, and FTW room answer-type maps.
38+
- Security/deploy: `lib/asymptote.ts`, `app/api/admin/asymptote/render/route.ts`, `SETUP.md`, `docs/asymptote.md`.
39+
- Verification: `tests/asymptote.test.ts`, `tests/problem-set-authoring.test.ts`, `tests/json-import.test.ts`, `tests/grading.test.ts`, then typecheck/lint/test/build.

DBSMO/Projects/dbsmo/Common Tasks.md

Lines changed: 7 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-19
3+
updated: 2026-08-04
44
type: common-tasks
55
tags: [project, architecture, maintenance, dbsmo]
66
ai-first: true
@@ -39,6 +39,8 @@ 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+
For multiple-choice changes, also update `Problem.options` in `prisma/schema.prisma`, schemas in `lib/problem-set-authoring.ts` and `lib/import/json-import.ts`, shared controls in `app/admin/problem-authoring-controls.tsx`, standard/Test rendering in `app/problem-sets/[slug]/answer-grid.tsx`, and Practice rendering/API payloads. Choices may contain `[[img:key]]`; preserve the normal asset resolver and the two-to-20 bound. See [[Asymptote and Multiple Choice]].
43+
4244
## Change Attempt Review
4345

4446
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.
@@ -47,6 +49,10 @@ Start with [[Attempt Review]]. The server page and exact owner/staff authorizati
4749

4850
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).
4951

52+
## Change Asymptote Rendering
53+
54+
Start with `lib/asymptote.ts` and [[Asymptote and Multiple Choice]]. Keep the authorization/rate limit in `app/api/admin/asymptote/render/route.ts`, automatic create/edit/import conversion, PNG validation, process/time/workspace limits, and Linux bubblewrap boundary aligned. Any new package, executable, environment variable, or sandbox mount also requires a same-commit `SETUP.md` update. Never serve raw source or relax production to Asymptote `-safe` without the OS sandbox.
55+
5056
## Add a Problem Set Field
5157

5258
Edit:

DBSMO/Projects/dbsmo/Components.md

Lines changed: 3 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-26
3+
updated: 2026-08-04
44
type: components
55
tags: [project, architecture, components, ui, dbsmo]
66
ai-first: true
@@ -53,9 +53,9 @@ This note maps important [[dbsmo]] UI/components to their source files and usage
5353

5454
## Admin Content Components
5555

56-
- `CreateSetPageClient` in `app/admin/create/page-client.tsx`: client form for manual set creation and import-draft editing, backed by `/api/admin/create-set`; supports per-problem image uploads and preview through `LatexStatement`. Uploaded images are appended as `[[img:key]]` tokens for preview/save when not already referenced.
56+
- `CreateSetPageClient` in `app/admin/create/page-client.tsx`: client form for manual set creation and import-draft editing, backed by `/api/admin/create-set`; supports per-problem images, Asymptote-to-image attachment, and variable multiple-choice options through the shared controls in `app/admin/problem-authoring-controls.tsx`. Option images use the same `[[img:key]]` preview/save path as statement images.
5757
- `StatementPreview` in `app/admin/create/page-client.tsx`: local preview for statement content/format, including problem image assets rendered by `LatexStatement`.
58-
- `SetEditForm` in `app/admin/sets/[id]/set-edit-form.tsx`: edit form for metadata, tags, PDF upload, image upload, status, problem list, answer keys/types, points, explanations, and save to `/api/admin/sets/[id]`. It shares the same per-problem image-token append behavior before save.
58+
- `SetEditForm` in `app/admin/sets/[id]/set-edit-form.tsx`: edit form for metadata, tags, PDF/image upload, Asymptote diagrams, multiple-choice options, status, problems, answers, points, explanations, and save to `/api/admin/sets/[id]`. See [[Asymptote and Multiple Choice]].
5959
- `DeleteSetButton` in `app/admin/sets/delete-set-button.tsx`: client delete action used by set management/detail flows and backed by `DELETE /api/admin/sets/[id]`; it supports compact row sizing and full topbar sizing.
6060
- `JsonZipImportPanel` in `app/admin/import/json-zip-import-panel.tsx`: batch JSON ZIP import UI; unpacks `.json` files plus optional same-basename nested image ZIPs and runs dry-run/draft/commit per entry.
6161
- `ZipImportPanel` in `app/admin/import/zip-import-panel.tsx`: single JSON import UI; accepts optional same-basename image ZIP and runs dry-run/draft/commit flow.

0 commit comments

Comments
 (0)