Skip to content

About

RecitRadar is a collaborative recitation tracking platform that helps law students manage subject-based index cards, track recitation rotations, and prepare smarter. Copy

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

RecitRadar

Your block's recitation index cards, digitized. Beadles log who gets called; every classmate sees which cards are still in each subject's deck and gets a cautious, explained study-priority hint.

Students don't need accounts. Each block has a read-only class link (/c/<token>) the beadle posts in the group chat; classmates tap their own card once (remembered on their phone). Only beadles and class reps sign in, to log calls and manage subjects.

Mobile-first PWA, entirely on Cloudflare: Workers (Hono API + static assets) · D1 · Durable Objects (live updates) · Better Auth.

Production: https://recitradar.buildwithanthony.com (Worker custom domain in wrangler.jsonc; the workers.dev URL is disabled so class links and sessions live on one host).

Quick start

npm install
cp .dev.vars.example .dev.vars          # then set BETTER_AUTH_SECRET (openssl rand -hex 32)
npm run db:migrate:local
npm run dev                             # http://localhost:5173 — Vite + the Worker in workerd
Command What it does
npm run dev Frontend + Worker + local D1 + Durable Objects, hot reload
npm test Engine unit tests + API integration tests inside workerd (real D1 migrations)
npm run typecheck App, Worker, config, and test projects
npm run db:generate New SQL migration from worker/db/schema.ts (Drizzle Kit)
npm run deploy Build, apply remote migrations, deploy

Deploying

One-time setup:

npx wrangler d1 create recitradar               # paste the database_id into wrangler.jsonc
openssl rand -hex 32 | npx wrangler secret put BETTER_AUTH_SECRET
npm run deploy

Optional Google sign-in: create an OAuth client with redirect URI https://<your-host>/api/auth/callback/google, then wrangler secret put GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET. The button appears automatically.

CI (.github/workflows/ci.yml) typechecks, tests, and builds on every push/PR. Deploys run on manual dispatch with CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID repo secrets.

Tenancy

School            shared tenant root (seeded list of PH law schools; reps can add one)
 └─ Block         a section, e.g. 1-D — keeps its roster, members, and class link across years
     └─ Term      AY + semester + year level; exactly one active, the rest read-only history
         └─ Subject → deck → rounds → calls
  • Other sections: any rep creates a block under an existing school. Blocks are isolated from each other — members of one get 404 for another, and a class link only reaches its own block.
  • Next semester / next year: Block → Subjects → Next term archives the current term (its subjects become read-only, still browsable) and starts a new one. Roster, members, and class link carry over; rename the block if its label changes (1-D → 2-D) and mark students who left as inactive so they get no new cards.

Access model

Who How Can
Classmates Class link, no account View this term's decks; pick "this is me" for a personal card + outlook. Never sees notes, recorders, student numbers, or emails. "Counts only" subjects show counts, not cards.
Beadles / reps Account + join code, then made officer (whole block) or subject beadle Log/void calls, reset rounds, manage roster, subjects, rules, terms, links
Block admin Created the block All of the above + roles, archive subjects

How it works

React PWA ──HTTP──▶ Worker (Hono) ──▶ D1   ← single source of truth
    ▲                     │
    └──WebSocket── SubjectRoom DO ◀─ notify(version) after each committed write
  • Cards belong to roster entries, not accounts. A beadle pastes the class list once; every student gets a card in every subject immediately, whether or not they've signed up. Students later claim their card. (students.user_id is nullable.)
  • Every write is one D1 batch (one transaction). Logging a call resolves the active round, today's meeting (auto-created, Manila date), and an active enrollment inside a single INSERT … SELECT, so a racing reset or drop can't attach a call to the wrong round. A unique idempotency_key makes client retries safe.
  • Round resets are conditional on the round the beadle was looking at (expectedRoundId), with a partial unique index guaranteeing one active round per subject. Two beadles resetting at once → one wins, the other gets 409 ROUND_CHANGED.
  • Corrections void, never delete. Voided events stay visible (struck through) and in audit_logs.
  • Durable Objects only carry "something changed" hints. Clients refetch from D1 on every hint, on reconnect, and on tab focus, so a dropped socket can't leave a screen stale. Sockets hibernate, so idle classrooms cost nothing.
  • The outlook engine is pure (shared/engine/outlook.ts) and unit-tested. It never outputs percentages; "Lower priority" requires an observed no-repeat (or sequential) rule.

Layout

shared/engine/     card states, outlook rules, Manila time, roster parsing (pure TS)
shared/            API view models + zod validators shared by Worker and UI
worker/            Hono API, Better Auth, access control, D1 queries, SubjectRoom DO
src/components/    index card system (IndexCard, MiniCard, stamps, tallies, deck stack)
src/pages/         screens
migrations/        SQL applied by wrangler (generated by Drizzle Kit)
tests/             vitest: engine.test.ts, api.test.ts (runs in workerd)

Privacy defaults

  • Notes on calls: visible to beadles and the card's owner only.
  • Per subject, beadles choose Whole class (everyone sees who has been called, like the physical deck) or Counts only (students see counts and their own card).
  • Student numbers and member emails: officers only. Non-members get 404 for any block resource.

About

RecitRadar is a collaborative recitation tracking platform that helps law students manage subject-based index cards, track recitation rotations, and prepare smarter. Copy

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages