fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186) - #463

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures
Jul 30, 2026
Merged

fix(frontend): surface the five swallowed failures — closes the #113 audit tail (#166 #185 #184 #183 #186)#463
AndresL230 merged 2 commits into
mainfrom
fix/b2-swallowed-failures

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B2 of the backlog clear — the last five children of the frontend UI-audit epic #113, all the same shape: a failure rendered as a plausible success. Each fix pairs with a red-first component test pinning the failure path (per-issue detail in the commit message). Implemented as five parallel single-surface changes:

Verification

Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.

🤖 Generated with Claude Code

…, calendar load, empty quiz, paste-tab wipe, inline course (#166#185#184#183#186)
Every remaining #113 audit child is the same shape: a failure rendered as a
plausible success. Each fix pairs with a red-first component test pinning
the failure path.
- #166 gradebook: AssignmentModal/EditWeightsModal/LetterScaleEditor saves
(and AssignmentModal delete) catch + toast via humanizeError and STAY
OPEN on failure; same treatment for the in-file CurveSettingsModal and
LetterScaleEditor's floating reset. Parents keep bare awaits (rejections
propagate into the modal's catch — documented).
- #185 calendar: load() failure now renders a loadError banner
(calendar-load-error / calendar-load-retry, registered in
docs/frontend-testids.md) distinct from the legitimate empty state;
retry re-runs load through the skeleton.
- #184 quiz: an empty generated-questions response warns and stays in the
select phase instead of stranding the user on a control-less blank panel.
- #183 flashcards: PasteTab's live-reparse effect is gated on a per-mount
dirty ref — a bare (re)mount never emits onCards([]) over the shared
deck; deleting typed text still clears intentionally. First flashcards
test file covers the tab-switch regression + both controls.
- #186 upload modal: inline-added courses land in local extraCourses,
merged (deduped) into the per-file dropdown, the courses[0] default and
the <10 gate; reset on close; parent refresh via onComplete unchanged.
Suites: vitest 257 passed (31 files; 15 new tests), tsc clean, lint 0
errors, backend suite untouched-green.
Closes#166. Closes#185. Closes#184. Closes#183. Closes#186.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in:2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ca34ec3c-381b-4df0-9356-3836589b1da7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b000b5 and c8f4a3f.

📒 Files selected for processing (19)
  • docs/frontend-testids.md
  • frontend/e2e/quiz.spec.ts
  • frontend/e2e/upload.spec.ts
  • frontend/eslint-suppressions.json
  • frontend/eslint.config.mjs
  • frontend/src/components/DocumentUploadModal.test.tsx
  • frontend/src/components/DocumentUploadModal.tsx
  • frontend/src/components/Gradebook/AssignmentModal.tsx
  • frontend/src/components/Gradebook/EditWeightsModal.tsx
  • frontend/src/components/Gradebook/LetterScaleEditor.tsx
  • frontend/src/components/Gradebook/modals.dialog.test.tsx
  • frontend/src/components/QuizPanel.test.tsx
  • frontend/src/components/QuizPanel.tsx
  • frontend/src/components/flashcards/FlashcardImportModal.test.tsx
  • frontend/src/components/flashcards/tabs/PasteTab.tsx
  • frontend/src/components/screens/Calendar.test.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Course.curveModal.test.tsx
  • frontend/src/components/screens/Gradebook/Course.tsx

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pagesBot commented Jul 29, 2026

Copy link
Copy Markdown

Deploying with Cloudflare Workers Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

StatusNameLatest CommitPreview URLUpdated (UTC)
✅ Deployment successful!
View logs
frontend-stagingc8f4a3fCommit Preview URL

Branch Preview URL
Jul 29 2026, 08:16 PM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Code review

Found 2 issues:

  1. A failing background reload now blanks a populated calendar behind the full-page error banner. loadError is set by every load() failure, but load is also the reload path for assignment edit/delete/export (AssignmentTable onReload), the Google-Calendar reconnect effect, and upload completion — and the render branch shows the banner whenever loadError is set, hiding already-loaded data. The sibling Gradebook screen gates its banner on fetchError && !data, and this PR's own testids-doc entry scopes the banner to "when the initial assignments/courses fetch fails". Empirically reproduced (successful load → failing post-delete reload → whole view replaced by the banner).

<motion.div
key={loading ? "loading" : loadError ? "error" : view}
initial={prefersReduced ? false : {opacity: 0,y: 8}}
animate={{opacity: 1,y: 0}}
exit={prefersReduced ? {opacity: 1} : {opacity: 0,y: -8}}
transition={{duration: prefersReduced ? 0 : 0.22,ease: [0.2,0.85,0.35,1]}}
>
{loading ? (
<CalendarMonthSkeleton/>
) : loadError ? (
<LoadErrorBannermessage={loadError}onRetry={retryLoad}/>
) : view==="month" ? (

  1. The new calendar testid surface is documented but not enforced: src/components/screens/Calendar.tsx was not added to the files array of the no-restricted-syntax lint block, which docs/frontend-testids.md's "Adding a surface" procedure explicitly requires (step 2) — future untagged interactive elements in Calendar.tsx won't be caught, unlike every other registered surface. The "Where the testids live" table also lacks a calendar row.

files: [
"src/components/SignInModal.tsx",
"src/app/pending/page.tsx",
"src/components/DocumentUploadModal.tsx",
"src/components/ChatPanel.tsx",
"src/components/QuizPanel.tsx",
"src/components/KnowledgeGraph.tsx",
"src/components/KnowledgeGraph2D.tsx",
"src/components/KnowledgeGraph3D.tsx",
"src/components/ShellFrame.tsx",
"src/components/screens/Social.tsx",
"src/components/screens/Dashboard.tsx",
"src/components/screens/Learn.tsx",
"src/components/screens/Library.tsx",
],

Lower-confidence items (below threshold, noted for the author): #186 (DocumentUploadModal) and #184 (QuizPanel) are in e2e-covered territory (upload.spec.ts / quiz.spec.ts) but ship Vitest-only regression coverage — the CLAUDE.md convention pairs such fixes with promoted journeys; the PR description names CurveSettingsModal and LetterScaleEditor's "Reset to default" as covered #166 instances but neither catch path has a test; two test-file headers (modals.dialog.test.tsx, Calendar.test.tsx) were made stale by this PR's own additions.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

- Calendar: gate the full-page error banner to the INITIAL load only
(hasLoadedRef) — a failing background reload (post-delete/export,
reconnect, upload onComplete) now toasts and keeps the loaded view
instead of blanking real data behind the banner; regression test drives
a table-row delete whose reload fails.
- Register the calendar surface for lint enforcement: Calendar.tsx added
to eslint.config.mjs's no-restricted-syntax files array (20 pre-existing
untagged elements baselined via lint:baseline) + the missing 'Where the
testids live' table row.
- Promoted journeys for the two fixes in e2e-covered territory: quiz.spec
(#184 — route-intercepted zero-question generation stays on select with
the warning) and upload.spec (#186 — inline-add of the unenrolled seeded
HIST200 through the REAL catalog search/enroll routes, immediately
selectable in the file row's dropdown, enrollment asserted in Postgres).
- The two #166 catch paths the PR named but didn't test are now tested:
LetterScaleEditor's Reset-to-default rejection, and CurveSettingsModal
(exported from Course.tsx for its new dedicated test file).
- Refreshed both stale test-file headers (modals.dialog.test.tsx,
Calendar.test.tsx).
vitest 273 passed (34 files), tsc clean, lint 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

All five review findings fixed in c8f4a3f: (1) the error banner is now gated to the initial load — a failing background reload toasts and keeps the loaded view (regression test drives a table delete whose reload fails); (2) Calendar.tsx registered in the eslint testid-enforcement files array (pre-existing untagged elements baselined) + the missing owning-file table row; (3) promoted journeys added for both e2e-covered fixes — quiz.spec.ts (zero-question generation via route interception) and upload.spec.ts (inline-add of unenrolled seeded HIST200 through the real catalog/enroll routes, DB-asserted); (4) LetterScaleEditor Reset-to-default and CurveSettingsModal rejection paths now tested (modal exported for its dedicated test file); (5) both stale test headers refreshed. vitest 273/34 green, tsc clean, lint 0 errors. Full e2e cycle queued; results to follow.

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge e2e gate: full lane 19/19 passed (including the two newly promoted journeys — quiz zero-question guard, upload inline-course) + oracles clean (0 findings, 1 allowlisted). Merging.

@AndresL230
AndresL230 merged commit 6a88a7d into mainJul 30, 2026
6 checks passed
@AndresL230
AndresL230 deleted the fix/b2-swallowed-failures branch July 30, 2026 05:41
AndresL230 added a commit that referenced this pull request Jul 30, 2026
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
#468)
* feat(gradebook): term-aware course links + transcript & GPA (#139)
The gradebook's term switcher was half-wired: the landing had semester
chips, but the course-card link and getGradebookCourse dropped the
selected term, so _resolve_enrollment(user, course, None) resolved the
CURRENT term — a course also taken in an archived term (rich seed:
CS101 in both fall-2025 and spring-2026) 404'd from that term's chip.
And the backend's GPA endpoints had no frontend consumer at all.
- CourseCard carries the selected term (?semester=<label>) on the card
href; the course screen reads it off location (same pattern as
Landing's deep-link param) and passes it through getGradebookCourse.
- GradebookSummary stops discarding the summary's gpa/semester; the
landing surfaces "Term GPA x.xx" next to the chips
(gradebook-term-gpa, hidden while null).
- New TranscriptModal (gradebook-transcript-open) over the previously
unconsumed GET /api/gradebook/gpa: cumulative GPA
(gradebook-transcript-gpa) + per-semester sections, in-progress rows
listed but excluded from the math. Load failure toasts + inline
retry (#463 pattern).
- New pure lib/transcript.ts (buildTranscript/weightedGpa) mirroring
backend gradebook_service.weighted_gpa exactly (null grade_points
skipped, null/zero credits count as 1, empty -> null); term ordering
reuses lib/semesters (new compareTermLabels export).
- Testid surface `gradebook` registered in docs/frontend-testids.md +
the four owning files joined the eslint no-restricted-syntax block;
suppressions baseline regenerated for the pre-existing untagged
elements (Course.tsx 9, Landing.tsx 1).
- e2e/gradebook.spec.ts (authored, not run here): the promoted #139
regression journey (DB-truth precondition via queryRaw, then both
chips' CS101 cards resolve their own term's categories) + the
transcript journey.
- vitest: lib/transcript.test.ts, TranscriptModal.test.tsx (dialog
contract + loading/error/retry), Course.semester.test.tsx (param
plumbing), Landing.test.tsx extended (term GPA, real CourseCard href
under test via a next/link stub).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(review): term-scope gradebook mutations + transcript credits display (#468 review)
Closes the three findings from the #468 5-agent review of the #139 work.
1 (major) — read/write term split: getGradebookCourse was semester-aware
but every course-keyed mutation from the Course screen still resolved
term-blind, so editing a Fall 2025 CS101 page wrote into the Spring 2026
enrollment silently (backend _resolve_enrollment(course, None) = current
term). The backend body models already accept `semester`; now the
frontend sends it: optional `semester` on createCategory,
bulkUpdateCategories, createGradedAssignment, setLetterScale and
setCurveSettings in lib/api.ts (body field, JSON.stringify drops it when
unset), passed at every Course.tsx call site (curve toggle + curve
settings + weights + create-assignment + letter scale). The id-keyed
calls (deleteCategory, update/deleteGradedAssignment) resolve by row
ownership — verified against routes/gradebook.py — and stay as-is.
- Course.semester.test.tsx: 4 new tests drive the REAL
EditWeightsModal/AssignmentModal down to Save and assert
bulkUpdateCategories/createGradedAssignment get the URL's semester
when ?semester= is set, and undefined when absent.
- e2e/gradebook.spec.ts: the Fall leg now also creates an assignment
through the UI and queryRaw-polls that the new assignments row hangs
off rich-enr-active-cs101-f25, not the spring enrollment. New testids
gradebook-add-assignment / gradebook-assignment-title /
gradebook-assignment-save; AssignmentList.tsx + AssignmentModal.tsx
joined the eslint enforcement array (pre-existing untagged elements
baselined: 7 + 12) and the docs inventory.
2 — transcript credits display: the modal showed `credits ?? 1` while
the GPA math treated 0/negative as 1. New effectiveCredits() export in
lib/transcript.ts is now the ONE place the rule lives, used by both
weightedGpa and the "N cr" display; tests cover 0/negative/null and the
rendered "1 cr" for a zero-credit row.
3 — ?semester= is read at FETCH time: the mount-frozen useState
initializer became currentSemesterParam(), read inside refresh AND
inside every mutation callback (keeps the no-Suspense plain-location
pattern).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(gradebook): adapt #467's Landing.testmode suite to the #139 Landing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230 added a commit that referenced this pull request Jul 30, 2026
… raw admin page (#478)
* feat(analytics): day bucketing + wire-format fix for the range object (#121 backend)
- Optional ?bucket=day on /usage/summary, /llm/cost, /errors adds a sparse
UTC-day series (days with no rows omitted; client zero-fills from range).
The errors series runs its own capped scan and surfaces truncation.
- Range now serializes as {"from", "to"} — the query param and the wire key
agree before any TS client freezes on the accidental "from_".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(frontend): admin analytics data layer + raw /admin/analytics page (#121)
- Typed wrappers (adminUsageSummary/ByUser/LlmCost/Errors) over the #120 API,
omitting unset params so backend defaults stay server-owned; response
types in types.ts named clear of the legacy AnalyticsOverview family.
- Data hooks (useAdminAnalytics.ts) on the house useCallback+useEffect
pattern; presetRange routes through lib/testMode's clock.
- /admin/analytics renders live data as raw tables: usage summary, top
users, LLM cost with group-by toggle, error feed. One range drives every
panel; per-panel error && !data banners with retry; truncation badges.
The admin gate sits OUTSIDE the hook-owning body so a non-admin visit
fires zero 403ing requests.
- admin-analytics testid surface registered in BOTH docs/frontend-testids.md
and the eslint no-restricted-syntax files array.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(analytics): address PR #478 review — reload toast, stale-response guard, range clamp, testid rename
- useAnalyticsQuery: monotonic request sequence so an out-of-order response
can't overwrite newer data; background-reload failures keep the loaded
view and toast (#463 Calendar convention) instead of vanishing.
- Custom range inputs clamp the other edge (backend 422s from > to) and
carry min/max bounds.
- admin-analytics-costgroup-* -> admin-analytics-cost-group-* per the
testids kebab convention (code + doc inventory).
- TruncatedBadge copy made meaning-neutral for the errors panel's
series-only truncation (#122 note).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment