Skip to content

feat(web): add the daily quiz at /quiz - #234

Open
a2751012962 wants to merge 1 commit into
mainfrom
feat/quiz
Open

a2751012962 wants to merge 1 commit into
mainfrom
feat/quiz

Conversation

@a2751012962

Copy link
Copy Markdown
Member

What

A daily quiz at iguide.chat/quiz: each signed-in student gets one quiz per day built from what their courses are covering right now on Canvas, plus a few questions they missed before.

How Canvas data gets in

UIUC does not let students create Canvas access tokens, and the Worker should never hold a student's Canvas or NetID session. So a small Chrome extension (extensions/canvas-connect) reads Canvas in the student's own browser with their normal Canvas sign-in:

  • GET only, one request at a time, 150 ms apart
  • reads the most recently started term: upcoming assignments/exams, recent announcements, open modules, the newest lecture pages, the syllabus
  • hands the /quiz page plain-text digests, which the page uploads with the student's iGuide token (PUT /api/quiz/courses)
  • never uses the cookies API; if Canvas is signed out, it opens Canvas in a normal tab and the student signs in on Canvas/the university's own pages

No Canvas credential reaches the Worker or Supabase.

Worker: /api/quiz/*

Endpoint
GET /status courses, today's quiz if any, open mistake count
PUT /courses replace the synced digests (max 12 courses, 12k chars each)
PATCH /courses/:id { enabled }
POST /today today's quiz, generated on the first call
POST /today/answers { questionId, choice }; the first answer counts
GET /mistakes open mistake bank with answers
  • Signed-in only: guests get 401 SIGN_IN_REQUIRED before any storage or DeepSeek call, so no provider call is relayed without auth.
  • One quiz per America/Chicago day: 8 new questions from one DeepSeek call (deepseek-chat, JSON mode) plus up to 4 open mistakes. Explanations follow the site language.
  • Questions are validated (4 distinct options, valid index, known course) and their options shuffled server-side.
  • A mistake leaves the bank after two correct answers in a row.

Data

apps/web/scripts/migrations/create_quiz_tables.sql (re-runnable) adds quiz_courses, quiz_questions and quiz_daily. They are owner-only: anon gets nothing, authenticated gets auth.uid() = user_id policies. The Worker uses the caller's own token, as with the dorm API; no service-role key.

SPA

/quiz page (src/components/quiz/**, src/services/quizApi.ts), a sidebar entry, and the extension zip at public/downloads/iguide-canvas-connect.zip. With the extension installed, the page refreshes the course digests in the background at most every 20 hours (no tab opened); Connect Canvas / Sync now runs an interactive sync.

Testing

  • node --test "worker/**/*.test.ts" "src/**/*.test.ts" (apps/web): 162/164 pass; the 2 failures are the known loop.test.ts / loop.baseline.test.ts pair from AGENTS.md. New: worker/quiz/service.test.ts (generation, daily reuse, mistake rules, upload validation, DeepSeek failure) and worker/routes/quiz.test.ts (guest 401).
  • node --test extensions/canvas-connect/canvas.test.mjs: 6/6.
  • vp build passes. vp check reports only the 6 errors already on main in packages/dorm/scrapers/mapsScraper.ts.
  • Local vp dev: /quiz renders with the sidebar entry and the sign-in prompt; GET /api/quiz/status answers 401 for a guest.

Not yet verified end to end: reading a real UIUC Canvas account through the extension, real DeepSeek output, and writes against the real Supabase project. These need the migration applied and a signed-in run.

Rollout

  1. Run create_quiz_tables.sql in the Supabase SQL editor.
  2. Deploy the Worker (no new secrets; DEEPSEEK_API_KEY is already set).
  3. Load extensions/canvas-connect unpacked in Chrome, sign in at /quiz, and click Connect Canvas.

The extension's content script matches iguide.chat and localhost:5173 only, so a workers.dev preview cannot talk to it.

A per-student daily quiz built from each course's current Canvas progress,
with a mistake bank that keeps missed questions coming back.

- Canvas: UIUC does not issue student access tokens, so the new
  extensions/canvas-connect Chrome extension reads the current term in the
  student's own browser (GET only, sequential, 150 ms apart) and hands the
  page plain-text course digests. No Canvas credential reaches the Worker
  or the database.
- Worker: /api/quiz/* (signed-in only; guests get 401 before any storage or
  DeepSeek call). One quiz per America/Chicago day: 8 new questions from a
  single DeepSeek call plus up to 4 open mistakes; a mistake closes after two
  correct answers in a row. Options are shuffled server-side.
- Data: create_quiz_tables.sql adds quiz_courses, quiz_questions and
  quiz_daily with owner-only RLS; the Worker forwards the caller's token.
- SPA: /quiz page, sidebar entry, and the extension download under
  public/downloads.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Failed ❌

View logs ↗
c434f0f 2026-10-14T02:21:01.250Z View logs ↗

@github-actions

Copy link
Copy Markdown

React Doctor found 2 new issues in 2 files · 2 warnings · score 92 / 100 (Great) · 0 fixed · vs main

2 warnings

src/components/quiz/QuizScreen.tsx

  • ⚠️ L61 React function has high control-flow complexity no-high-complexity-react-function

worker/quiz/service.ts

  • ⚠️ L100 Intl formatter rebuilt each call js-hoist-intl

Reviewed by React Doctor for commit c434f0f. See inline comments for fixes.

)
}

export const QuizScreen: React.FC<QuizScreenProps> = ({ language }) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/no-high-complexity-react-function (warning)

QuizScreen has cyclomatic complexity 15, cognitive complexity 22, and maximum nesting depth 4, so its React logic is hard to understand and change. Extract independent branches into components or hooks.

Fix → Extract independent render branches and state logic into focused components or hooks until the control flow is easy to follow.

Docs


/** `YYYY-MM-DD` in Urbana-Champaign, so a quiz turns over at local midnight. */
export function quizDate(now: Date): string {
return new Intl.DateTimeFormat("en-CA", {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/js-hoist-intl (warning)

This is slow because new Intl.DateTimeFormat() rebuilds on every call inside a function, so move it to the top of the file, or wrap it in useMemo

Fix → Move new Intl.NumberFormat(...) to the top of the file or wrap it in useMemo. Building one is slow, so don't redo it on every call

Docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant