From 5fc52657c005b61df0f22a565d07132e9205a11f Mon Sep 17 00:00:00 2001 From: Xiaotian Date: Tue, 9 Jun 2026 17:00:06 +0800 Subject: [PATCH 01/29] Fix: show exercise question text + improve exercise UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - adaptExercise now falls back to `problemStatement` field (used in 6 newer exercise files — was the root cause of blank exercise cards) - Add `title?: string` to Exercise type; use it as card preview label instead of stripped/mangled math — gives clean Chinese titles on cards - Wrap problem in `.problem-statement` callout box with accent border; add `.answer-label` separator before textarea - Add `.is-primary` button variant for AI-grade submit action - Include previously unflushed JSON syntax fixes for 7 exercise files Co-Authored-By: Claude Sonnet 4.6 --- app/globals.css | 65 ++++++++++++++++++++++++++++- components/course/ExercisePanel.tsx | 29 ++++++++----- lib/course/loadCourse.ts | 5 ++- lib/course/types.ts | 1 + 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/app/globals.css b/app/globals.css index 678944d..6ac1d6e 100644 --- a/app/globals.css +++ b/app/globals.css @@ -558,6 +558,19 @@ button:disabled { cursor: progress; } +button.is-primary { + background: var(--accent); + border-color: var(--accent-deep); + color: white; + font-weight: 600; +} + +button.is-primary:hover:not(:disabled) { + background: var(--accent-deep); + border-color: var(--accent-deep); + color: white; +} + textarea { width: 100%; min-height: 150px; @@ -995,7 +1008,7 @@ details summary { min-width: 0; display: flex; flex-direction: column; - gap: 4px; + gap: 5px; } .exercise-card-preview { @@ -1008,6 +1021,18 @@ details summary { white-space: nowrap; } +.exercise-card-title { + margin: 0; + font-size: 14px; + line-height: 1.45; + color: var(--ink); + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: var(--serif-body); +} + .difficulty-pill { align-self: flex-start; padding: 2px 8px; @@ -1042,6 +1067,44 @@ details summary { border-top: 1px solid var(--rule); } +.problem-statement { + margin: 0 0 20px; + padding: 18px 20px 16px; + background: var(--paper-page); + border: 1px solid var(--rule); + border-left: 3px solid var(--accent); + border-radius: 0 var(--radius) var(--radius) 0; + box-shadow: var(--shadow-page); +} + +.problem-title { + margin: 0 0 10px; + font-family: var(--serif-display); + font-size: 13px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--accent-deep); + opacity: 0.8; +} + +.problem-body { + font-family: var(--serif-body); + font-size: 16px; + line-height: 1.85; + color: var(--ink); +} + +.answer-label { + margin: 0 0 8px; + font-family: var(--serif-display); + font-size: 12px; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--ink-faint); +} + details summary:hover { color: var(--accent-deep); } diff --git a/components/course/ExercisePanel.tsx b/components/course/ExercisePanel.tsx index 0574698..8da0501 100644 --- a/components/course/ExercisePanel.tsx +++ b/components/course/ExercisePanel.tsx @@ -33,13 +33,14 @@ function difficultyClass(d: string) { return d === "basic" ? "basic" : d === "medium" ? "medium" : "advanced"; } -function previewText(text: string, max = 72): string { - const stripped = text - .replace(/\$\$[\s\S]*?\$\$/g, "[式]") - .replace(/\$[^$\n]+\$/g, "[式]") +function cardLabel(exercise: Exercise): string { + if (exercise.title) return exercise.title; + const stripped = exercise.prompt + .replace(/\$\$[\s\S]*?\$\$/g, "") + .replace(/\$[^$\n]+\$/g, "") .replace(/\n+/g, " ") .trim(); - return stripped.length > max ? stripped.slice(0, max) + "…" : stripped; + return stripped.slice(0, 64) + (stripped.length > 64 ? "…" : ""); } export function ExercisePanel({ @@ -212,10 +213,10 @@ export function ExercisePanel({ > {index + 1}
+

{cardLabel(exercise)}

{difficultyLabel(exercise.difficulty)} -

{previewText(exercise.prompt)}

))} @@ -224,9 +225,15 @@ export function ExercisePanel({ {activeExercise && (
-

- -

+
+ {activeExercise.title && ( +

{activeExercise.title}

+ )} +
+ +
+
+

解答