chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
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

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425) - #467

Merged
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism
Jul 30, 2026
Merged

chore(hygiene): seam + determinism sweep (#413 #248 #306 #426 #425)#467
AndresL230 merged 2 commits into
mainfrom
fix/b5-seam-determinism

Conversation

@AndresL230

Copy link
Copy Markdown
Collaborator

What

Bundle B5 — five hygiene issues in one coordinated PR (the #248 sweep changes the timestamp wire format, so it can't land piecemeal). Per-issue detail in the commit message; highlights:

Verification

Backend 1400 passed + ruff clean (seam tests re-run green on the lock-pinned pydantic-ai 1.107 venv); frontend 286 passed, tsc + lint clean; the preflight check driven by an 11/11 scratch harness (match/mismatch/empty/env-precedence/quoting cases). Red-first tests throughout. Full local e2e cycle pre-merge (it also exercises the new preflight for real); results below.

Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.

🤖 Generated with Claude Code

@supabase

supabaseBot commented Jul 30, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project ybgqdonkoqftwrmweuyv because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Jul 30, 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:17 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: e475216e-ccae-4fea-a3de-e97811600cf6

📥 Commits

Reviewing files that changed from the base of the PR and between b9b0b89 and 6d3bc4f.

📒 Files selected for processing (25)
  • backend/agents/quiz_context.py
  • backend/prompts/quiz_context_update.txt
  • backend/routes/calendar.py
  • backend/routes/documents.py
  • backend/routes/flashcards.py
  • backend/routes/learn.py
  • backend/routes/quiz.py
  • backend/scripts/backfill_document_chunks.py
  • backend/scripts/ingest_catalog.py
  • backend/services/graph_service.py
  • backend/services/quiz_context_service.py
  • backend/services/rag_service.py
  • backend/services/social_cache_service.py
  • backend/tests/test_document_indexing.py
  • backend/tests/test_graph_service.py
  • backend/tests/test_quiz_routes.py
  • backend/tests/test_timestamps_tz_aware.py
  • docs/local-supabase.md
  • frontend/src/app/(shell)/notetaker/page.testmode.test.tsx
  • frontend/src/app/(shell)/notetaker/page.tsx
  • frontend/src/components/Gradebook/AssignmentList.tsx
  • frontend/src/components/screens/Calendar.tsx
  • frontend/src/components/screens/Gradebook/Landing.testmode.test.tsx
  • frontend/src/components/screens/Gradebook/Landing.tsx
  • scripts/e2e-up.sh

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 30, 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-staging6d3bc4fCommit Preview URL

Branch Preview URL
Jul 30 2026, 07:34 AM

@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Review pass complete: two agents clean, four sub-80 findings all fixed — e2e-up's resolver now accepts export-prefixed and CRLF .env lines (both were false preflight failures), the backfill script routes through the public embed wrapper (making its exclusivity claim true), and the stale relevance-gate test docstring updated. Backend suite + ruff green. e2e cycle next.

AndresL230 added a commit that referenced this pull request Jul 30, 2026
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AndresL230and others added 2 commits July 30, 2026 00:31
Five hygiene issues, one coordinated PR (the utcnow sweep must land whole —
the ISO wire format changes from naive to tz-aware):
- #413: the documents.py RAG-indexing fallback no longer constructs a raw
genai.Client — it routes through rag_service's sanctioned #439 seam
(new embed_document_text wrapper: lazy shared client, dummy-key posture,
real-mode gate), closing the keyless real-mode hole where a ValueError
at construction silently degraded to no-index. scripts/ingest_catalog.py
stays a documented exemption (offline ops CLI, meaningless without real
embeddings) but gets a lazy client + actionable keyless exit. Grep
acceptance: zero raw empty-string-key client constructions remain.
- #248: all 16 datetime.utcnow() sites across 7 backend files →
datetime.now(timezone.utc). Consumer audit: all swept columns are
TIMESTAMPTZ; the frontend parses via new Date() (aware ISO is more
correct); no string-prefix consumers affected. The sweep surfaced and
fixed a REAL latent bug: end_session's naive-minus-aware arithmetic
raised TypeError on PostgREST reads and silently reported
time_spent_minutes=0 (_elapsed_minutes now normalizes legacy-naive);
_compute_velocity gets the same normalization. Pytest deprecation
warnings drop 80 → 5.
- #306: the dead trailing JSON-schema block in quiz_context_update.txt
removed (output_type owns the shape); instructional text and every
placeholder the route fills kept, guarded by new template tests.
- #426: test-mode clock leaks — Gradebook Landing's term preselect and the
notetaker's edit stamps (plus two more found sweeping: AssignmentList's
'Synced Xm ago', Calendar's export filename) now route through the
frozen now(); deliberate skips documented in the tests/PR (presence
liveness, session expiry, cooldowns, uniqueness keys must stay real).
- #425: e2e-up preflight now verifies the SESSION_SECRET the backend will
resolve (env-beats-.env, mirroring load_dotenv) MATCHES the one the
frontend's start:test literal pins — mismatch or empty fails fast with
sources named and sha256 fingerprints only, never values. Plus an
informational note when SAPLING_MODEL_MODE is unset (the live-billing
footgun). docs/local-supabase.md updated.
Suites: backend 1400 passed + ruff clean (lock-venv seam checks green);
frontend 286 passed + tsc + lint clean; script harness 11/11.
Closes#413. Closes#248. Closes#306. Closes#426. Closes#425.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- e2e-up SESSION_SECRET resolver accepts dotenv's optional 'export ' prefix
and strips trailing CR from CRLF-edited .env files (both were false
preflight failures); harness case added.
- backfill_document_chunks now routes through the public
rag_service.embed_document_text wrapper instead of the private
_embed_document, making the wrapper's exclusivity docstring true.
- Stale test docstring updated (the relevance-gate site no longer
constructs a raw client since this PR's own #413 change).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@AndresL230
AndresL230force-pushed the fix/b5-seam-determinism branch from 443ddae to 6d3bc4fCompareJuly 30, 2026 07:31
@AndresL230

Copy link
Copy Markdown
CollaboratorAuthor

Pre-merge gate at the rebased head (over B8's #466): full lane 26/26 passed + oracles clean — and the boot itself exercised the new SESSION_SECRET preflight. Merging.

@AndresL230
AndresL230 merged commit d0cd993 into mainJul 30, 2026
5 checks passed
@AndresL230
AndresL230 deleted the fix/b5-seam-determinism branch July 30, 2026 07:36
AndresL230 added a commit that referenced this pull request Jul 30, 2026
…ing contract (ToastProvider stub, getGpa mock, summary gpa/semester fields)
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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment