Skip to content

Commit 65dc904

Browse files
Edwin ChanEdwin Chan
authored andcommitted
added renderer in answer box
1 parent b20b4a7 commit 65dc904

7 files changed

Lines changed: 103 additions & 21 deletions

File tree

CENTRAL.md

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

3636
- **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`.
3737
- **Mobile sidebar focus fix** — mobile sidebar sheet geometry now remains stable under `:hover`, `:focus`, and `:focus-within`, preventing the nav grid from jumping upward when a mobile tap focuses one of the upper links (source: `app/globals.css`).
38-
- **Practice answer preview and LaTeX grading** — Practice now renders a small KaTeX preview above the answer input using `mathInputToTex(...)`. `lib/grading.ts` normalizes math delimiters and common LaTeX forms through `lib/math-input.ts`, so expression answers and answer keys can use `$...$`, `\sqrt{...}`, `\frac{...}{...}`, braced powers, and `\pi`.
38+
- **Answer previews and LaTeX grading** — Practice and problem-set answer boxes now render a small KaTeX preview above the input using `mathInputToTex(...)`. `lib/grading.ts` normalizes math delimiters and common LaTeX forms through `lib/math-input.ts`, so expression answers and answer keys can use `$...$`, `\sqrt{...}`, coefficients before functions like `5\sqrt{2}-7`, `\frac{...}{...}`, braced powers, and `\pi`.
3939
- **Anime.js animation pass** — the shared `MathCurveLoader` now drives SVG path drawing, rotation, and dot pulses with Anime.js v4 instead of CSS keyframes, `AnimeRouteEffects` adds reduced-motion-aware route reveal animations for key panels/rows/actions, and analytics surfaces use `AnalyticsMotion` plus animated `TrendChart` path/dots for growing bars and drawn charts. `TypewriterGreeting` keeps the existing typed/deleted text state machine and only animates the caret, avoiding per-character text tween flicker. Dependency: `animejs` in `package.json`.
4040
- **Per-set analytics access** — admins can open `/admin/sets/[id]/analytics` directly from the student-facing problem set header and the admin set editor; the editor delete action uses full-size topbar button sizing while the list keeps compact row actions.
4141
- **Problem-set writeups**`/problem-sets/[slug]/writeups` lets signed-in users post LaTeX/HTML solution notes with up to four images, then sort by latest/top and upvote/downvote posts. Authors can delete their own writeups with confirmation; admins can delete any writeup. `/writeups` is the sidebar directory for latest/top writeups and problem-set search. Persistence uses new `Writeup`, `WriteupImage`, and `WriteupVote` Prisma models plus `lib/writeup-images.ts`; image bytes are stored through the existing storage layer and streamed via `/api/files/[id]`.

DBSMO/Projects/dbsmo/Components.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This note maps important [[dbsmo]] UI/components to their source files and usage
4343
## Practice and Games
4444

4545
- `PracticePage` in `app/practice/page.tsx`: client practice workflow using `/api/practice/tags`, `/api/practice/next`, and `/api/practice/submit`; renders `LatexStatement` and shows a live KaTeX answer preview above the answer input using `mathInputToTex(...)`.
46+
- `AnswerGrid` in `app/problem-sets/[slug]/answer-grid.tsx`: client answer sheet for standard, inline-statement, and test layouts; answer inputs now show the same live KaTeX preview above each active answer box.
4647
- `FtwLobbyForm` in `app/ftw/lobby-form.tsx`: FTW mode entry. It filters tags, creates solo matches through `/api/ftw/matches`, creates rooms through `/api/ftw/rooms`, and joins rooms through `/api/ftw/rooms/[code]/join`.
4748
- `FtwMatchClient` in `app/ftw/match/[id]/match-client.tsx`: solo FTW problem/submit client.
4849
- `FtwRoomClient` in `app/ftw/room/[code]/room-client.tsx`: multiplayer room UI with polling state, host actions, answer submission, locked/revealed answers, scores, and current problem rendering through `LatexStatement`.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,6 @@ Session updates from the CodeGraph/Second Brain indexing pass onward:
4747
- Fixed mobile problem-set task cards/sidebar tap layout/dashboard account controls, added the `/classes` Announcements subtab with existing-message deletion, and normalized literal `null` display names.
4848
- Added direct per-set Analytics buttons on both the student-facing set header for admins and the admin set editor, and made the set-editor Delete button match the other topbar action sizing.
4949
- Fixed the mobile sidebar tap/focus jump by keeping the opened sheet geometry stable when nav items receive focus.
50-
- Added a live rendered answer preview in Practice and expanded expression grading to understand common LaTeX input/keys such as `$5$`, `\sqrt{5}`, `\frac{1}{2}`, and `2^{1/2}`.
50+
- Added live rendered answer previews in Practice and problem-set answer boxes, and expanded expression grading to understand common LaTeX input/keys such as `$5$`, `\sqrt{5}`, `5\sqrt{2}-7`, `\frac{1}{2}`, and `2^{1/2}`.
5151

5252
© 2026 Cosmic Crusader

app/globals.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,6 +1598,59 @@ img.avatar {
15981598
font-weight: 800;
15991599
}
16001600

1601+
.set-answer-preview,
1602+
.answer-cell-preview,
1603+
.test-answer-preview {
1604+
display: inline-flex;
1605+
align-items: center;
1606+
justify-content: center;
1607+
min-width: 72px;
1608+
min-height: 46px;
1609+
max-width: 100%;
1610+
padding: 10px 14px;
1611+
border: 1px solid var(--color-border);
1612+
border-bottom: 0;
1613+
border-radius: 12px 12px 0 0;
1614+
background: color-mix(in srgb, var(--meter-track) 84%, var(--color-card-bg));
1615+
color: var(--color-text-strong);
1616+
overflow-x: auto;
1617+
}
1618+
1619+
.set-answer-preview {
1620+
justify-self: start;
1621+
margin-bottom: -8px;
1622+
}
1623+
1624+
.answer-cell-preview {
1625+
width: calc(100% - 20px);
1626+
justify-self: center;
1627+
margin: 10px 10px -1px;
1628+
}
1629+
1630+
.test-answer-preview {
1631+
width: 100%;
1632+
min-height: 38px;
1633+
margin-bottom: -1px;
1634+
padding: 8px 10px;
1635+
border-radius: 10px 10px 0 0;
1636+
font-size: 0.86rem;
1637+
}
1638+
1639+
.set-answer-preview .katex-display,
1640+
.answer-cell-preview .katex-display,
1641+
.test-answer-preview .katex-display {
1642+
margin: 0;
1643+
}
1644+
1645+
.set-answer-preview .katex,
1646+
.answer-cell-preview .katex {
1647+
font-size: 1.08em;
1648+
}
1649+
1650+
.test-answer-preview .katex {
1651+
font-size: 1em;
1652+
}
1653+
16011654
.question-answer-input:focus {
16021655
border-color: var(--color-blue);
16031656
outline: none;

app/problem-sets/[slug]/answer-grid.tsx

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
55
import { createPortal } from "react-dom";
66
import { CheckCircle2, MessageSquareWarning, RotateCcw, XCircle } from "lucide-react";
77
import { MathCurveLoader } from "@/app/math-curve-loader";
8+
import { mathInputToTex } from "@/lib/math-input";
89
import { LatexStatement } from "./latex-statement";
910

1011
type SubmitResult = {
@@ -381,6 +382,17 @@ export function AnswerGrid({
381382
return "";
382383
}
383384

385+
function answerPreview(problemNumber: number, className = "set-answer-preview") {
386+
const tex = mathInputToTex(answers[problemNumber] ?? "");
387+
if (!tex) return null;
388+
389+
return (
390+
<div className={className} aria-live="polite">
391+
<LatexStatement statement={`$${tex}$`} />
392+
</div>
393+
);
394+
}
395+
384396
function renderJumpGrid(className?: string) {
385397
return (
386398
<div className={`problem-jump-grid${className ? ` ${className}` : ""}`}>
@@ -582,14 +594,17 @@ export function AnswerGrid({
582594
<CheckCircle2 size={16} className="grade-icon correct-icon" />
583595
</div>
584596
) : (
585-
<input
586-
className="question-answer-input"
587-
aria-label={`Answer ${number}`}
588-
name={`answer-${number}`}
589-
placeholder="Enter answer"
590-
value={answers[number] ?? ""}
591-
onChange={(e) => onAnswerChange(number, e.target.value)}
592-
/>
597+
<>
598+
{answerPreview(number)}
599+
<input
600+
className="question-answer-input"
601+
aria-label={`Answer ${number}`}
602+
name={`answer-${number}`}
603+
placeholder="Enter answer"
604+
value={answers[number] ?? ""}
605+
onChange={(e) => onAnswerChange(number, e.target.value)}
606+
/>
607+
</>
593608
)}
594609
</label>
595610

@@ -667,14 +682,17 @@ export function AnswerGrid({
667682
<CheckCircle2 size={14} className="grade-icon correct-icon" />
668683
</div>
669684
) : (
670-
<input
671-
className="test-answer-input"
672-
aria-label={`Answer ${cell.label}`}
673-
name={`answer-${cell.problemNumber}`}
674-
placeholder={cell.label}
675-
value={answers[cell.problemNumber] ?? ""}
676-
onChange={(e) => onAnswerChange(cell.problemNumber, e.target.value)}
677-
/>
685+
<>
686+
{answerPreview(cell.problemNumber, "test-answer-preview")}
687+
<input
688+
className="test-answer-input"
689+
aria-label={`Answer ${cell.label}`}
690+
name={`answer-${cell.problemNumber}`}
691+
placeholder={cell.label}
692+
value={answers[cell.problemNumber] ?? ""}
693+
onChange={(e) => onAnswerChange(cell.problemNumber, e.target.value)}
694+
/>
695+
</>
678696
)}
679697
</td>
680698
);
@@ -829,6 +847,7 @@ export function AnswerGrid({
829847
{problemNumbers.map((number) => (
830848
<label className="answer-cell" key={number}>
831849
<span className="answer-cell-label">Question {number}</span>
850+
{answerPreview(number, "answer-cell-preview")}
832851
<input
833852
className="answer-cell-input"
834853
aria-label={`Answer ${number}`}

lib/grading.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ function insertImplicitMultiplication(tokens: MathToken[]): MathToken[] {
273273
const current = tokens[index];
274274
const previous = result[result.length - 1];
275275

276-
if (previous && shouldInsertMultiplication(previous, current, tokens[index + 1])) {
276+
if (previous && shouldInsertMultiplication(previous, current)) {
277277
result.push({ type: "operator", value: "*" });
278278
}
279279

@@ -286,7 +286,6 @@ function insertImplicitMultiplication(tokens: MathToken[]): MathToken[] {
286286
function shouldInsertMultiplication(
287287
previous: MathToken,
288288
current: MathToken,
289-
next: MathToken | undefined,
290289
): boolean {
291290
const previousCanEndValue =
292291
previous.type === "number" ||
@@ -299,7 +298,7 @@ function shouldInsertMultiplication(
299298
return false;
300299
}
301300

302-
return !(current.type === "identifier" && next?.type === "leftParen");
301+
return true;
303302
}
304303

305304
class MathExpressionParser {

tests/grading.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ describe("gradeAnswer", () => {
7272
expect(result.isCorrect).toBe(true);
7373
});
7474

75+
it("evaluates coefficients before LaTeX functions", () => {
76+
const result = gradeAnswer({
77+
answerType: "expression",
78+
answerKey: "5\\sqrt{2}-7",
79+
rawAnswer: "5sqrt(2)-7",
80+
});
81+
82+
expect(result.isCorrect).toBe(true);
83+
});
84+
7585
it("evaluates LaTeX fractions and braced powers in expressions", () => {
7686
expect(
7787
gradeAnswer({

0 commit comments

Comments
 (0)