feat(exercises): show students their past AI task results, legibly (#581) - #582
Merged
Merged
Conversation
) A student returning to a finished lesson checkpoint or standalone exercise saw a bare score badge. The AI's feedback was never missing — it was always written to `lesson_checkpoint_attempts.evaluation` and `exercise_evaluations. ai_result`; the loaders simply never read those columns back, and every engine held its result in component state that a reload discarded. Restore path - `loadLessonCheckpoints` now selects `response` and `evaluation`, and defensive parsers turn those two free-form jsonb columns back into the student's own answer plus the feedback, next-step hint and per-question explanations. Four writer shapes exist (ai / deterministic / fallback / external sync); anything unrecognised yields "nothing to show" rather than being trusted into the UI. - The standalone exercise page needed no new query: artifact and essay rows were already in the `exercise_evaluations` result set fetched for the audio/video history and were simply never passed to their components. - Per-question feedback rendered `correctValue` raw, so a multiple-choice miss read "Incorrect — 1" (the option *index*). It now maps to option text and localized true/false. - Coding challenges write no evaluation row at all — they are graded by their test runner and store a hardcoded completion. The card says so instead of rendering an empty result. Legibility (issue #581, found by an /impeccable critique verified live at 390x844 and 1440x900, light and dark, en and es) - Six WCAG AA contrast failures in light and four in dark are fixed; the card now measures clean in both themes. The worst was the PASSED badge at 2.47:1 — white on emerald-500, the single most important word on the card. The "AI Feedback" label no longer uses `text-primary`: tenants override that token, so small text in it cannot be guaranteed to clear AA. - The artifact panel printed the score over the PASS MARK, twice: "62 / 70" reads as 89% when 62 is a failure. That whole block was a near-copy of ExerciseResultSummary, so it now renders the shared card instead. - A failed checkpoint showed a green tick, because its tone came from `completed` and a failed attempt is still completed. - The checkpoint restored behind a closed disclosure. It now opens itself for the one state that asks something of the student — a graded attempt they did not pass — and an explicit toggle still wins. - The retry field sat between the answer and the grade for that answer, and looked identical to the read-only answer above it. Order is now answer → result → retry, and the restored answer is borderless so it does not read as an editable field. - Adding the result card inside `lg:sticky lg:top-6` took that container to 1078px against a 900px viewport; `sticky` does not pin an element taller than its scrollport, so the chat input scrolled away unreachable. The card is now a sibling above it, which also puts the result first on mobile instead of 1157px down the page behind instructions already followed. - Prose is capped at 68ch (was 85-90), the header stacks below `sm:`, long unbroken input wraps, and on phones the retry button is 36px tall and inputs are 16px so iOS Safari stops zooming the page on focus. 23 unit tests pin the parsers and the auto-expand rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUBJNELGqnwbQbQNp5Qd9h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #581.
A student who came back to a finished lesson checkpoint or standalone exercise saw a bare score badge. The AI's feedback was never missing — it has always been written to
lesson_checkpoint_attempts.evaluationandexercise_evaluations.ai_result. The loaders just never read those columns back, and every engine held its result in component state that a reload discarded. This was a display gap, not a storage gap.This PR restores it, then fixes what an
/impeccable critiquefound wrong with how it read.Walkthrough
Same student, two surfaces: the standalone essay they passed, then the in-lesson checkpoint they did not. 390px.
Restoring the result
loadLessonCheckpointsnow selectsresponseandevaluation. Defensive parsers turn those two free-form jsonb columns back into the student's own answer plus the feedback, next-step hint and per-question explanations. Four writer shapes exist (ai / deterministic / fallback / external sync); anything unrecognised yields "nothing to show" rather than being trusted into the UI.exercise_evaluationsresult set fetched for the audio/video history, and were simply never passed to their components.correctValueraw, so a multiple-choice miss read "Incorrect — 1" — the option index. It now maps to option text, and booleans to localized true/false.Making it legible
Verified live at 390×844 and 1440×900, light and dark,
/enand/es, on cold loads.Accessibility. Six WCAG AA text-contrast failures in light and four in dark, measured canvas-sampled over the real composited background. All now clear; 14/14 elements pass in both themes.
PASSEDbadge, white onemerald-500/ 100,text-muted-foreground/60STRENGTHS/AREAS TO IMPROVE+/-list markersAI FEEDBACK,text-primaryThe
AI Feedbacklabel no longer usestext-primaryat all: tenants override that token, so small text in it cannot be guaranteed to clear AA for any school. The icon keeps the accent — non-text UI only needs 3:1.The artifact panel printed the score over the pass mark, twice.
62 / 70reads as 89%, but 62 against a pass mark of 70 is a failure. That whole block was a near-copy ofExerciseResultSummary, so it now renders the shared card and the duplicate is gone.A failed checkpoint showed a green tick, because its tone came from
completedand a failed attempt is still completed. And the restored feedback sat behind a closed disclosure, so a returning student saw a score chip and nothing else. It now opens itself for the one state that asks something of them — a graded attempt they did not pass — and an explicit toggle still wins.The retry field also sat between the answer and the grade for that answer, looking identical to the read-only answer above it. Order is now answer → result → retry, and the restored answer is borderless so it stops reading as an editable field.
A sticky-column regression I introduced. Putting the result card inside
lg:sticky lg:top-6took that container to 1078px against a 900px viewport.position: stickydoes not pin an element taller than its scrollport, so the chat input scrolled away unreachable. The card is now a sibling above it (650px, sticks again) — which also puts the result first on mobile instead of 1157px down the page, behind instructions the student has already followed.Measure and touch. Prose capped at 68ch (was 85–90). Header stacks below
sm:, where the metadata line was being squeezed to 161px and two lines. Long unbroken input wraps. On phones the retry button is 36px tall (was 24) and inputs are 16px, so iOS Safari stops zooming the page on focus.Verification
npm run buildexit 0 ·npm run typecheckclean · 669 unit tests pass (53 files, 23 new covering the parsers and the auto-expand rule)npx impeccable detectclean on all touched filesNot in scope
The artifact page restores a grade while its own answer fields sit empty — checkpoints show "Your answer", the artifact engine has no equivalent. Noted in #581 as deserving its own issue.