Skip to content

Phase 14: student portal to-do list + progress radar#248

Merged
NesiciCoding merged 5 commits into
mainfrom
claude/sad-heyrovsky-677954
Jul 2, 2026
Merged

Phase 14: student portal to-do list + progress radar#248
NesiciCoding merged 5 commits into
mainfrom
claude/sad-heyrovsky-677954

Conversation

@NesiciCoding

@NesiciCoding NesiciCoding commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a combined "My Work" to-do list to the student portal — every assigned essay and test in one place, grouped into Overdue / Planned / Completed, each with status (not started / in progress / submitted) and due date.
  • Adds a "My Progress" radar chart of the student's own per-criterion scores, combined across every graded rubric (criteria with matching titles averaged together) or filtered to a single rubric.
  • Roadmap items 14.1 (stats), 14.2 (planned/overdue list), and 14.5 (to-do list) implemented as one cohesive feature rather than three separate ones, since 14.2 and 14.5 are the same underlying data sliced two ways.

Why a new test_assignments table

Tests had no persisted assignment record — a teacher-generated share link was the only way a student ever learned a test existed, so there was nothing to list. Added supabase/migrations/044_test_assignments.sql:

  • test_assignments table mirroring essay_assignments' proven RLS pattern (get_my_test_assignment_ids(), portal-scoped via student email → local student ID).
  • Read-only policy on student_tests so the portal can show a student's own submission status.
  • A scoped read policy on tests so the portal can embed full test content into a self-contained "Open" link (the same offline/embedded-content URL shape TestAssignmentModal already produces), sidestepping StudentTestPage's separate disconnected client.

Bug fix bundled in

TestAssignmentModal shared a single teacherKey across an entire class batch — a "DB mode" link would have pointed every student at the same row. Now mints one teacherKey/DB row per student, matching essay_assignments' 1:1-per-teacherKey constraint. Verified in the browser that all 9 generated links now decode to distinct teacherKeys.

Docs

Updated per CLAUDE.md's documentation-maintenance rule: DocsPage.tsx (new "Student Portal" section), README.md, LandingPage.tsx (new student feature card), and all 5 locale files (0 missing/extra key parity verified).

Deliberately out of scope

StudentTestPage's own share-link flow still can't submit/fetch via DB mode — it opens a disconnected Supabase client and never authenticates, a pre-existing bug unrelated to this feature (the portal's "Open" button sidesteps it by embedding content directly). Planned as its own follow-up PR — full technical plan written into the wiki roadmap under "Deferred from Phase 14" (mirrors the essay flow's signInAnonymously() + get-essay-assignment/submit-essay edge-function pattern).

Test plan

  • npm run typecheck — clean
  • npm run lint — clean (only pre-existing warnings elsewhere, unrelated e2e/fixtures errors pre-date this branch)
  • npm test — 2105/2105 tests pass (4 new)
  • i18n parity — 0 missing/extra keys across en/nl/fr/de/es
  • Manually verified in the browser with seeded demo data: portal radar renders (combined + per-rubric switch), per-student test-assignment links decode to distinct teacherKeys
  • supabase db reset to verify migration 044 applies cleanly — not run, no Docker daemon available in this sandbox; please verify locally before merging

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added My Work in the Student Portal to show a combined to-do list of assigned essays and tests grouped by Overdue / Planned / Completed.
    • Added My Progress with a radar chart (including combined rubric performance) and rubric filtering.
    • Students can now open assigned tests directly from the portal.
  • Bug Fixes
    • Improved DB-backed test assignment saving/loading, including Save all to DB support with saved counts and partial-failure messaging.
    • Ensured generated teacher keys are distinct per student and remain stable when revisiting previously opened share items.
  • Documentation
    • Updated Student Portal documentation and guided tour text to match the new views and flows.
  • Tests
    • Expanded coverage for test assignments, portal grouping, and progress calculations.

Adds two new sections to the student portal:
- "My Work" — a combined essay+test to-do list grouped into
  Overdue/Planned/Completed, each item showing status (not started/in
  progress/submitted) and due date.
- "My Progress" — a radar chart of the student's own per-criterion
  scores, combined across graded rubrics or filtered to one.

Tests had no persisted assignment record at all (only a share-code
URL the teacher had to hand out manually), so there was nothing to
list. Adds `test_assignments` (migration 044), mirroring
`essay_assignments`' RLS pattern, plus a read-only policy on
`student_tests` for submission status and a scoped `tests` read
policy so the portal can embed test content into a self-contained
"Open" link.

Also fixes a real bug found along the way: TestAssignmentModal shared
one teacherKey across an entire class batch, so a DB-mode link
pointed every student at the same row — now mints one per student.

Docs updated per CLAUDE.md (DocsPage, README, LandingPage, all 5
locales). Deliberately left out: fixing StudentTestPage's own
disconnected-client auth (a separate, pre-existing bug in the bare
share-link flow, scoped out as its own follow-up in the wiki roadmap).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@NesiciCoding, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 34 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 26ac60aa-65ee-4b28-9ae7-b8f3ac1ed63e

📥 Commits

Reviewing files that changed from the base of the PR and between 2f3aea2 and 6eb2cf0.

📒 Files selected for processing (4)
  • src/components/Tests/TestAssignmentModal.tsx
  • src/components/Tests/__tests__/TestAssignmentModal.test.tsx
  • src/pages/StudentPortalPage.tsx
  • src/pages/__tests__/StudentPortalPage.test.tsx
📝 Walkthrough

Walkthrough

This PR adds database-backed test assignment persistence, a teacher modal save-all flow, a unified student portal for essays and tests, and matching docs and locale updates for the new portal work/progress views.

Changes

Test assignments and portal backend

Layer / File(s) Summary
Types, sync, and migration
src/types/index.ts, src/context/AppContext.tsx, src/services/database/StorageSync.ts, src/services/database/SupabaseAdapter.ts, supabase/migrations/044_test_assignments.sql, supabase/CLAUDE.md
Adds test-assignment types and backend methods, plus the test_assignments migration and portal read policies.
Teacher modal DB save flow
src/components/Tests/TestAssignmentModal.tsx, src/components/Tests/__tests__/TestAssignmentModal.test.tsx
Adds per-student teacher keys, batch DB saving, save-state UI, and coverage for persistence and key stability.
Unified student portal work and progress
src/pages/StudentPortalPage.tsx, src/pages/__tests__/StudentPortalPage.test.tsx
Loads essays and tests together, opens assigned tests, and renders combined work groups and radar progress views with matching tests.
Docs and landing page
README.md, src/pages/DocsPage.tsx, src/pages/LandingPage.tsx
Documents the new portal views and adds a student-facing feature card.
Locale updates
src/locales/*.json
Adds and revises translated strings for test assignment saving, portal work/progress labels, route descriptions, and guided-tour copy.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Teacher
  participant TestAssignmentModal
  participant AppContext
  participant SupabaseAdapter
  participant Database

  Teacher->>TestAssignmentModal: enable DB embedding
  TestAssignmentModal->>TestAssignmentModal: generate per-student teacherKeys
  loop for each unsaved student
    TestAssignmentModal->>AppContext: saveTestAssignment(assignment)
    AppContext->>SupabaseAdapter: saveTestAssignment(assignment)
    SupabaseAdapter->>Database: upsert test_assignments
    Database-->>SupabaseAdapter: result
    SupabaseAdapter-->>AppContext: SyncResult
    AppContext-->>TestAssignmentModal: SyncResult
  end
  TestAssignmentModal-->>Teacher: show saved count / error indicator
Loading
sequenceDiagram
  participant Student
  participant StudentPortalPage
  participant AppContext
  participant SupabaseAdapter

  Student->>StudentPortalPage: load portal
  StudentPortalPage->>AppContext: fetchMyTestAssignments()
  AppContext->>SupabaseAdapter: fetchMyTestAssignments()
  SupabaseAdapter-->>AppContext: assignment summaries
  AppContext-->>StudentPortalPage: test rows
  StudentPortalPage->>StudentPortalPage: merge essay + test rows into allWork
  StudentPortalPage-->>Student: render overdue/planned/completed groups
  Student->>StudentPortalPage: click open test
  StudentPortalPage->>AppContext: fetchAssignedTestContent(testId)
  AppContext->>SupabaseAdapter: fetchAssignedTestContent(testId)
  SupabaseAdapter-->>AppContext: test content
  AppContext-->>StudentPortalPage: test content
  StudentPortalPage-->>Student: navigate to test via hash route
Loading

Possibly related PRs

Poem

A rabbit hopped through work and scrolls,
With portal lists and radar goals.
Two little ears, one DB save,
Then test and essay both behaved. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a Phase 14 student portal update adding a to-do list and progress radar.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sad-heyrovsky-677954

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

CI's format:check caught line-width violations prettier's own default
config didn't flag locally until run explicitly. Whitespace only, no
logic change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 71.55% (🎯 65%) 7183 / 10039
🔵 Statements 69.78% (🎯 65%) 8182 / 11724
🔵 Functions 63.09% (🎯 60%) 2559 / 4056
🔵 Branches 61.98% (🎯 58%) 5957 / 9611
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/components/Tests/TestAssignmentModal.tsx 75.67% 77.77% 68% 77.61% 117, 140-156, 216-247, 352
src/context/AppContext.tsx 57.27% 39.85% 51.04% 58.88% 155, 206-225, 271-287, 337, 375, 378, 390, 393, 426, 468, 476, 489, 496-502, 520-625, 630, 812-831, 845, 872-879, 936-937, 947-1015, 1023, 1027-1031, 1032-1033, 1039-1045, 1061-1086, 1099-1127, 1157-1158, 1189-1190, 1209, 1267, 1381, 1393-1396, 1400, 1403, 1406, 1409, 1412, 1416, 1420, 1425, 1429-1430, 1433-1434, 1437-1438, 1441-1442, 1448-1467, 1474-1475, 1479, 1483-1487, 1493, 1497-1504, 1508, 1511, 1513, 1517, 1521, 1525, 1528, 1529, 1531, 1535, 1538, 1539, 1540, 1541, 1545-1546, 1550-1551, 1556-1558, 1564, 1568, 1572, 1576-1579, 1585-1599, 1610, 1612, 1615, 1618, 1621, 1622, 1624, 1628, 1633-1644, 1769
src/pages/DocsPage.tsx 100% 100% 100% 100%
src/pages/LandingPage.tsx 69.69% 82.75% 84.61% 68.75% 180-187, 343, 372
src/pages/StudentPortalPage.tsx 74.66% 67.7% 76.92% 75.13% 50, 86, 89, 97-99, 116, 161-166, 170-181, 215, 230, 277-304, 332-334, 361-363, 464-534, 660, 810-833, 942-947, 950-954, 1014, 1058-1062, 1064-1068, 1071-1076, 1079-1084
Generated in workflow #720 for commit 6eb2cf0 by the Vitest Coverage Report Action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/Tests/__tests__/TestAssignmentModal.test.tsx (1)

71-115: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

LGTM on the added coverage. Consider also adding a regression test that changes classId mid-session and re-asserts saveTestAssignment is called with the currently rendered teacherKey for each student — this would have caught the class-switch bug flagged in TestAssignmentModal.tsx.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Tests/__tests__/TestAssignmentModal.test.tsx` around lines 71
- 115, Add a regression test around TestAssignmentModal that simulates changing
classId during the same session and verifies saveTestAssignment uses the
currently rendered teacherKey for each student. Update the existing
TestAssignmentModal test suite to re-render with a new classId, then assert the
saved assignments still match the visible per-student links from
decodeTestAssignment and that each row uses the refreshed teacherKey rather than
a stale one.
src/components/Tests/TestAssignmentModal.tsx (1)

28-113: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Switching the class dropdown back and forth silently un-syncs DB-mode links from persisted rows.

teacherKeys regenerates fresh nanoid()s whenever classStudents gets a new array reference — which happens on every classId change, even re-selecting a class already visited (since useMemo only remembers the last computation, not per-input history). Meanwhile savedStudentIds tracks raw student ids and is never reset, so handleSaveAllToDb's if (nowSaved.has(s.id)) continue; skips re-saving a student the first time their id was recorded — even though their teacherKey has since changed. Net effect: the link shown/copied for that student (buildUrl) points at a test_assignments row that was never actually persisted, so the student's link fails to resolve. The saved-count/disabled-button UI is similarly thrown off since savedStudentIds.size accumulates across every class visited in the session, not just the current one.

Track "saved" state by the actual teacherKey (not raw student id), and/or make teacherKeys stable across class switches by keying off the full students list instead of the filtered classStudents.

🐛 Sketch of a fix
- const teacherKeys = useMemo(() => {
-     const map: Record<string, string> = {};
-     classStudents.forEach((s) => {
-         map[s.id] = nanoid();
-     });
-     return map;
- }, [classStudents]);
+ const teacherKeys = useMemo(() => {
+     const map: Record<string, string> = {};
+     students.forEach((s) => {
+         map[s.id] = nanoid();
+     });
+     return map;
+ }, [students]);
- const [savedStudentIds, setSavedStudentIds] = useState<Set<string>>(new Set());
+ const [savedTeacherKeys, setSavedTeacherKeys] = useState<Set<string>>(new Set());
 ...
  const handleSaveAllToDb = useCallback(async () => {
-     const nowSaved = new Set(savedStudentIds);
+     const nowSaved = new Set(savedTeacherKeys);
      for (const s of classStudents) {
-         if (nowSaved.has(s.id)) continue;
+         const key = teacherKeys[s.id];
+         if (nowSaved.has(key)) continue;
          ...
-             nowSaved.add(s.id);
+             nowSaved.add(key);
      }
-     setSavedStudentIds(nowSaved);
+     setSavedTeacherKeys(nowSaved);

Want me to draft the full patch (including the disabled/count UI) and add a regression test that toggles classId A→B→A and asserts saveTestAssignment was called with the currently-displayed teacherKey for each student?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Tests/TestAssignmentModal.tsx` around lines 28 - 113, The
DB-mode save tracking in TestAssignmentModal is keyed by student id, but
teacherKeys are regenerated when classStudents changes, so switching classes can
leave the UI marked as saved while the current buildUrl points to an unpersisted
row. Update the saving logic in handleSaveAllToDb and the saved state to track
the actual teacherKey (or make teacherKeys stable across class switches using
the full students list), and ensure the saved-count/disabled state reflects only
the current class’s assignments rather than accumulating stale ids.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Tests/TestAssignmentModal.tsx`:
- Around line 76-103: The `handleSaveAllToDb` flow in `TestAssignmentModal` is
saving each student assignment sequentially because `saveTestAssignment` is
awaited inside the loop. Refactor this logic to build the pending saves for
`classStudents` (skipping `savedStudentIds`) and execute them in parallel with
`Promise.all` or `Promise.allSettled`, then aggregate successes/failures to
update `savedStudentIds`, `saveErrorCount`, and `saving` consistently. Keep the
existing `TestAssignment` construction and preserve the current error-count
behavior while removing the one-by-one network round trips.

In `@src/pages/__tests__/StudentPortalPage.test.tsx`:
- Around line 82-95: The radar test fixture only grades criterion c1, so the “3+
criteria have been graded” case is not actually exercised. Update the
StudentPortalPage.test fixture to add scored entries for c2 and c3 alongside c1,
using the existing rubric definitions and the radar data setup so the test
validates multiple graded criteria instead of missing/zero-filled entries.

In `@src/pages/StudentPortalPage.tsx`:
- Line 532: Remove the redundant JSX section comments in StudentPortalPage that
only restate the visible markup, including the “My Work: combined essay + test
to-do list” comment and the similar comment at the later section. Keep the JSX
structure and identifiers as-is, but delete comments that simply describe what
the headings/section names already communicate.
- Around line 268-288: The handleOpenTest flow in StudentPortalPage leaves the
loading state stuck if fetchAssignedTestContent rejects because
setOpeningTestKey(null) is only reached on success. Update handleOpenTest to
reset the opening state in a failure path as well, preferably by wrapping the
fetchAssignedTestContent call in try/catch or try/finally so
setOpeningTestKey(null) always runs and setTestOpenErrorKey(row.teacherKey) is
set when loading content fails.
- Around line 989-992: The button in StudentPortalPage’s row action should not
default to form submission; update the button used with onOpen(row) so it
explicitly uses a plain button type. Locate the button in the StudentPortalPage
component and set its type so it won’t submit if rendered inside a form, while
keeping the existing onClick and disabled behavior unchanged.
- Around line 212-219: The per-rubric radar selection in selectedRadarData only
uses the first matching history entry for a rubric, so later attempts are
dropped. Update the useMemo logic to collect all history rows matching
radarRubricId, then compute each criterion’s average across those rows before
building the CriterionRadarDataPoint list. Use selectedRadarData, history, and
criterionPct as the key symbols when adjusting this aggregation.

In `@supabase/migrations/044_test_assignments.sql`:
- Around line 19-29: The `tests_student_select` policy currently allows access
through `test_assignments` without verifying the test’s owner. Update the policy
to require that `test_assignments.owner_id` matches `tests.owner_id` when
joining assignments to tests, so only the assignment creator’s tests can be
exposed. Use the `tests_student_select` policy and the `test_assignments`
relationship in the migration to locate the change.

---

Outside diff comments:
In `@src/components/Tests/__tests__/TestAssignmentModal.test.tsx`:
- Around line 71-115: Add a regression test around TestAssignmentModal that
simulates changing classId during the same session and verifies
saveTestAssignment uses the currently rendered teacherKey for each student.
Update the existing TestAssignmentModal test suite to re-render with a new
classId, then assert the saved assignments still match the visible per-student
links from decodeTestAssignment and that each row uses the refreshed teacherKey
rather than a stale one.

In `@src/components/Tests/TestAssignmentModal.tsx`:
- Around line 28-113: The DB-mode save tracking in TestAssignmentModal is keyed
by student id, but teacherKeys are regenerated when classStudents changes, so
switching classes can leave the UI marked as saved while the current buildUrl
points to an unpersisted row. Update the saving logic in handleSaveAllToDb and
the saved state to track the actual teacherKey (or make teacherKeys stable
across class switches using the full students list), and ensure the
saved-count/disabled state reflects only the current class’s assignments rather
than accumulating stale ids.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: dec81820-fa9c-403a-9871-fa080cbc8f06

📥 Commits

Reviewing files that changed from the base of the PR and between 745fe4e and beb88ef.

📒 Files selected for processing (18)
  • README.md
  • src/components/Tests/TestAssignmentModal.tsx
  • src/components/Tests/__tests__/TestAssignmentModal.test.tsx
  • src/context/AppContext.tsx
  • src/locales/de.json
  • src/locales/en.json
  • src/locales/es.json
  • src/locales/fr.json
  • src/locales/nl.json
  • src/pages/DocsPage.tsx
  • src/pages/LandingPage.tsx
  • src/pages/StudentPortalPage.tsx
  • src/pages/__tests__/StudentPortalPage.test.tsx
  • src/services/database/StorageSync.ts
  • src/services/database/SupabaseAdapter.ts
  • src/types/index.ts
  • supabase/CLAUDE.md
  • supabase/migrations/044_test_assignments.sql

Comment thread src/components/Tests/TestAssignmentModal.tsx Outdated
Comment thread src/pages/__tests__/StudentPortalPage.test.tsx
Comment thread src/pages/StudentPortalPage.tsx
Comment thread src/pages/StudentPortalPage.tsx
Comment thread src/pages/StudentPortalPage.tsx Outdated
Comment thread src/pages/StudentPortalPage.tsx
Comment thread supabase/migrations/044_test_assignments.sql
- security (critical): tests_student_select RLS policy didn't verify
  test_assignments.owner_id matched tests.owner_id, letting an
  assignment row leak another teacher's test content to a student
- critical (found outside diff): TestAssignmentModal regenerated
  teacherKeys on every class-dropdown switch while savedStudentIds
  tracked by student id, silently un-syncing displayed links from
  persisted rows on a class revisit; keyed teacherKeys off the full
  students list instead so they stay stable across class switches
- perf: parallelized per-student saveTestAssignment calls with
  Promise.all instead of a sequential await loop
- correctness: per-rubric radar only used the first graded attempt of
  a rubric, dropping later re-grades; now averages every matching
  attempt per criterion, same as the combined view
- robustness: handleOpenTest left the card stuck in a loading state if
  fetchAssignedTestContent rejected; wrapped in try/catch/finally
- test quality: radar fixture only graded one of three criteria despite
  the test asserting "3+ criteria graded"; added real entries for all three
- style: removed two comments that only restated their JSX section's
  heading, and added an explicit button type

Added regression tests for the two critical/major bugs (class-switch
teacherKey stability, per-rubric radar aggregation).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/StudentPortalPage.tsx (1)

317-320: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Filter fetched work rows to the portal student.

fetchMyTestAssignments() can return all rows allowed by the current session; under teacher preview that can include assignments for other students. Since allWork does not filter by student.id, Alice’s portal can show Bob’s test rows. Filter both assignment arrays before mapping.

Proposed fix
 const allWork: WorkEntry[] = [
-    ...essayRows.map((row) => ({ kind: 'essay' as const, key: `essay-${row.teacherKey}`, row })),
-    ...testRows.map((row) => ({ kind: 'test' as const, key: `test-${row.teacherKey}`, row })),
+    ...essayRows
+        .filter((row) => row.studentId === student.id)
+        .map((row) => ({ kind: 'essay' as const, key: `essay-${row.teacherKey}`, row })),
+    ...testRows
+        .filter((row) => row.studentId === student.id)
+        .map((row) => ({ kind: 'test' as const, key: `test-${row.teacherKey}`, row })),
 ];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/StudentPortalPage.tsx` around lines 317 - 320, The student portal
work list is assembling rows without restricting them to the current student, so
other students’ assignments can appear in `allWork`. Update `StudentPortalPage`
to filter both `essayRows` and `testRows` by `student.id` before the existing
`map` calls that build the `WorkEntry[]`, keeping the `allWork` construction
localized to the current portal student.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Tests/TestAssignmentModal.tsx`:
- Around line 85-106: The batch save in TestAssignmentModal’s save flow can
leave the modal stuck in the saving state if any saveTestAssignment call rejects
before Promise.all completes. Update the async block that builds results and
calls setSaving so it uses try/finally around the batch, and switch the
Promise.all handling to Promise.allSettled (or equivalent rejection-safe logic)
so rejected saves are counted in errors while still updating saved IDs for
successful entries and always clearing saving at the end.
- Around line 83-106: The current save flow in TestAssignmentModal uses
savedStudentIds as a global skip list, which causes stale rows and incorrect
progress when the class changes or assignment metadata like expiresAt changes.
Update the auto-save logic around the Promise.all block to track a per-student
payload fingerprint (or otherwise invalidate/re-save when test assignment fields
change) so pending saves are based on the current assignment payload, not only
student IDs. Also keep savedStudentIds for progress display, but derive the
shown saved count from the current classStudents list intersected with that set
so the modal reflects the active class correctly.

In `@src/pages/__tests__/StudentPortalPage.test.tsx`:
- Around line 359-373: The regression test in StudentPortalPage.test.tsx only
verifies that the rubric view renders axis labels, so it would still pass with
the first-attempt-only bug. Mock CriterionRadarChart so the test can inspect its
data prop after selecting rubric r1 in renderAt('s1'). Then assert a criterion
value that can only be correct if both sr1 and sr2 are included in the average,
using the existing mocked student rubric data to locate the affected behavior in
the per-rubric radar flow.

---

Outside diff comments:
In `@src/pages/StudentPortalPage.tsx`:
- Around line 317-320: The student portal work list is assembling rows without
restricting them to the current student, so other students’ assignments can
appear in `allWork`. Update `StudentPortalPage` to filter both `essayRows` and
`testRows` by `student.id` before the existing `map` calls that build the
`WorkEntry[]`, keeping the `allWork` construction localized to the current
portal student.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a9163d0e-55e7-4bed-a1c5-c240a1f982d5

📥 Commits

Reviewing files that changed from the base of the PR and between beb88ef and 2f3aea2.

📒 Files selected for processing (5)
  • src/components/Tests/TestAssignmentModal.tsx
  • src/components/Tests/__tests__/TestAssignmentModal.test.tsx
  • src/pages/StudentPortalPage.tsx
  • src/pages/__tests__/StudentPortalPage.test.tsx
  • supabase/migrations/044_test_assignments.sql

Comment thread src/components/Tests/TestAssignmentModal.tsx Outdated
Comment thread src/components/Tests/TestAssignmentModal.tsx Outdated
Comment thread src/pages/__tests__/StudentPortalPage.test.tsx Outdated
- major: savedStudentIds tracked save state globally across every
  class visited this session, so the "saved N/total" display and the
  save-button's disabled state read against the lifetime count instead
  of the current class (e.g. "3/1 saved" after visiting a 2-student
  then a 1-student class). Derive the displayed/disabled count from
  classStudents intersected with the saved set instead.
- major: changing the deadline after TestAssignmentModal's initial
  auto-save left already-persisted rows silently stuck on their
  original expiry, since the skip check only looked at student id.
  Re-keyed saved-state tracking to `${studentId}::${expiresAt}` so a
  changed deadline is treated as a new payload and re-saved, and added
  expiresAt to the auto-save effect's dependency array.
- major: a single rejected saveTestAssignment call would abort the
  whole Promise.all batch, leaving the modal stuck in the saving
  state. Switched to Promise.allSettled (rejections count as failures
  rather than aborting the batch) wrapped in try/finally.
- minor: the per-rubric radar regression test only checked that
  criteria rendered as axis labels, which would still pass under the
  original bug. Mocked CriterionRadarChart to expose its data prop and
  assert the actual averaged value (80%, not the single-attempt 90%).

Added a saved-count assertion to the existing class-switch regression
test to cover the first fix directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fetchMyEssayAssignments()/fetchMyTestAssignments() are scoped by the
authenticated session's email via get_my_student_ids() (RLS), which
can resolve to more than one Student record if two students share a
login email (siblings, a roster data-entry mistake) — the portal page
was trusting the fetch already matched the URL's studentId and never
filtered client-side, so it could show one student's work on another's
portal page. Filter both essayRows/testRows to student.id before
building allWork.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@NesiciCoding

Copy link
Copy Markdown
Owner Author

Re: "Filter fetched work rows to the portal student" (posted as an outside-diff-range finding, so it has no dedicated review thread to reply on inline) — fixed in src/pages/StudentPortalPage.tsx: essayRows/testRows are now filtered to row.studentId === student.id before building allWork, since fetchMy*Assignments() is scoped by the authenticated session's email (via get_my_student_ids()), which can resolve to more than one Student record if two students share a login email. Added a regression test proving another student's row doesn't leak onto this portal page.

🤖 Addressed by Claude Code

@NesiciCoding NesiciCoding merged commit 3335a77 into main Jul 2, 2026
8 checks passed
@NesiciCoding NesiciCoding deleted the claude/sad-heyrovsky-677954 branch July 2, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant