A softer way to study.
Evidence-based learning wrapped in a cozy pixel-art world β where your companion grows as you master what you study.
Most study apps are timers with a coat of paint, or AI that does the work for you. Nora is neither.
It's a study operating system built on six learning strategies from cognitive science, and a pixel-art world that only grows when your understanding does. The AI never writes your assignments β it asks questions, finds your gaps, and helps you plan. Your pet's mood reflects your real habits, not a streak counter you can game.
Learning first. AI as a tutor, never the author. Grounded data only β never fabricated claims.
Explain a concept in plain words and an AI "Inquisitive Student" probes it back β colour-coded gap analysis (green / amber / red), clarifying questions, and one-click flashcards from your own explanation.
Source grounding (new): Attach an indexed paper, video transcript, or pasted notes to any topic. Evaluation then grades your explanation strictly against those passages, citing the specific passage that each amber/red segment contradicts or omits. Without a source, feedback is clearly labelled "unverified (no source attached)" β no fabricated specifics presented as fact.
A full FSRS-6 scheduler (via ts-fsrs, MIT) drives the review queue. FSRS reduces review load ~20β30% vs SM-2 and eliminates "Ease Hell" by using a DSR memory model (Difficulty, Stability, Retrievability).
- Four-button grading: Again / Hard / Good / Easy (mapped to FSRS Rating).
- Intra-session relearning: A lapsed card (Again) re-queues within the current session.
- Timezone-safe due dates: "Due today" is determined by
profiles.timezone, never the server's local clock. - Cards from Feynman, research, video study, and manual entry all flow into the same FSRS queue.
SM-2 columns were preserved during backfill and removed after verification (migration 016).
Mixed practice queue built on the Brunmair & Richter (2019) meta-analysis:
- verbal_vocabulary topics β blocked (never interleaved) β interleaving hurts word-list recall (g = β0.39).
- procedural_math and visual_discrimination β interleaved within confusable same-subject topics (g = 0.34β0.67).
- Queue weighted toward weakest topics using FSRS difficulty (70%) and inverse Feynman comprehension score (30%).
- Queue size scales to the actual due-card load (no fixed cap).
Set each topic's material type in Settings β Subjects.
YouTube search with educational filtering, transcript extraction (with Groq Whisper fallback), timestamp-aware AI notes in a Tiptap editor with clickable time marks, inline ghost-text completions, and "explain what you just watched" Feynman evaluated against the transcript.
Web research now uses real academic APIs instead of Wikipedia/Open Library:
| API | Role | License |
|---|---|---|
| OpenAlex | Primary β CC0 scholarly catalog | CC0 |
| Crossref | Supplementary DOI/metadata | Free Polite Pool |
| Unpaywall | Open-access PDF discovery | Free |
Every citation maps to a retrieved source. When fewer than 2 sources are found the system says so β it never fabricates a literature review. Synthesis is constrained to retrieved abstracts; model supplementation is clearly labelled "unverified."
One-click "Ingest Open Access PDF": looks up a DOI via Unpaywall β SSRF-checks the URL β runs it through the full Paper RAG pipeline.
Dual-mode retrieval fused with Reciprocal Rank Fusion (RRF):
- Vector leg β pgvector cosine similarity (
text-embedding-3-small, 1536 dims). - Lexical leg β
ts_rank_cdover a generatedtsvectorGIN index (always available). - When no embedding key is configured β ranked lexical-only (never an unranked scan).
- Citations reference the actual retrieved chunk (paper title, section, chunk index) β no default-to-zero fallback.
Tell Nora your university, faculty, department, year, and term. It finds your institution's official academic data β academic calendar, registration/add-drop/midterm/final dates, holidays, and curriculum β stores the official PDFs, indexes them, and uses them to build a semester-aware study plan and an academic RAG you can ask about your own semester.
- Diacritic-insensitive Turkish-aware matching (ODTΓ / METU first launch)
- Never invents dates β ungrounded dates are dropped; missing official dates are stored as
unreleased - Background ingestion via an
ingestion_jobsqueue; manual upload always works
Session distribution using the Cepeda et al. (2008) temporal ridgeline β optimal interstudy gap as a function of days-until-exam:
- Sessions spread across the week with expanding gaps (not crammed to today).
- Asymmetric cost principle β when the ideal gap is uncertain, gaps err wider (under-spacing is far costlier than over-spacing).
- Near-exam boost β subjects with exams within 14 days get FSRS
request_retention = 0.95instead of capping intervals. - Missed-session forward-fill β marking a session missed reschedules it to the next free day without compressing the rest.
- Academic event merge: confirmed semester events surface as calendar chips and warning strips.
Pick from 12 animated companions. Your pet evolves with your level and its mood mirrors your last few days of study. Daily missions tie directly to real study actions.
Create or join study groups, share weekly quests, and send cheers. Missed days become compassionate "help quests" for the group instead of punishing resets.
Weekly stats, 30-day charts, a GitHub-style consistency heatmap, and topic mastery bars.
| Action | XP | Coins | Affinity |
|---|---|---|---|
| Feynman explanation | +15 | +5 | +3 |
| Card review (Good/Easy/Hard) | +3 | +1 | +1 |
| Card review (Again β lapse) | +1 | β | β |
| Card created | +2 | β | β |
| Study session complete | +10 | +3 | +2 |
| All daily missions | +20 | +10 | +5 |
Level: floor(sqrt(xp / 50)) + 1 β Lv2 at 50 XP, Lv3 at 200, Lv4 at 450, Lv5 at 800.
8-bit sound effects generated live with the Web Audio API β no audio files, just procedural oscillators with a sidebar mute toggle.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Server Actions) |
| Language | TypeScript strict β Node.js β₯ 20 |
| UI | React 19, Tailwind CSS v4, custom pixel-UI components |
| Database | Supabase β Postgres, pgvector, RLS, Storage |
| Auth | Supabase Auth (gated via proxy.ts) |
| Spaced Repetition | ts-fsrs (MIT) β FSRS-6 DSR model |
| AI (primary) | Groq Cloud (Llama 3.3 70B) |
| AI (fallback) | OpenRouter (free tier) |
| Academic Sources | OpenAlex (CC0), Crossref Polite Pool, Unpaywall |
| RAG retrieval | pgvector cosine + ts_rank_cd FTS β RRF |
| Rich text | Tiptap v3 (custom timestamp mark) |
| PDF parsing | pdf-parse (default); unpdf optional (Node β₯ 22) |
| Testing | Vitest + fast-check (property-based) β 332 tests |
- Node.js β₯ 20 (required by ts-fsrs)
- A Supabase project (free tier works)
- A Groq API key (free at console.groq.com)
git clone https://github.com/lxcario/Nora.git
cd Nora
npm install
cp .env.example .env.local
# Edit .env.local with your keysSee the full variable reference in .env.example. Minimum to run:
NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
GROQ_API_KEY=...| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
β | Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
β | Supabase anon key |
GROQ_API_KEY |
β | LLM β Feynman, research, synthesis |
OPENROUTER_API_KEY |
optional | LLM fallback |
OPENAI_API_KEY |
optional | Enables pgvector RAG (FTS used otherwise) |
OPENAI_EMBEDDING_MODEL |
optional | Defaults to text-embedding-3-small (1536 dims) |
ACADEMIC_API_MAILTO |
recommended | OpenAlex + Crossref Polite Pool enrollment |
ACADEMIC_API_EMAIL |
recommended | Unpaywall API access β required for OA PDF ingestion |
NEXT_PUBLIC_SUPPORT_EMAIL |
fallback | Used if specific mailto/email vars are absent |
FIRECRAWL_API_KEY |
optional | University auto-discovery (manual upload always works) |
YOUTUBE_API_KEY |
optional | YouTube search in Study Room |
NEXT_PUBLIC_SITE_URL |
optional | OpenRouter attribution headers |
Academic APIs (OpenAlex, Crossref, Unpaywall) are free with no key required, but passing your email in
ACADEMIC_API_MAILTO/ACADEMIC_API_EMAILenrolls the app in the Polite Pool for higher rate limits and is required by Unpaywall's terms of service.
Run all migrations in order via the Supabase SQL Editor or supabase db push:
supabase/migrations/001_initial_schema.sql # core schema
supabase/migrations/002_social_parties.sql # party tables
supabase/migrations/003_avatar_storage.sql # avatar storage
supabase/migrations/003_rag_extensions.sql # FTS, match_paper_chunks RPC
supabase/migrations/003b_storage_bucket.sql # PDF storage bucket
supabase/migrations/004_study_room.sql # video / transcript tables
supabase/migrations/005_party_rls_fix.sql # RLS fix
supabase/migrations/006_feynman_scoring.sql # comprehension score column
supabase/migrations/007_university_onboarding.sql # academic profile / events
supabase/migrations/008_ingestion_jobs.sql # background job queue
supabase/migrations/009_academic_sweeper.sql # academic data sweeper
supabase/migrations/010_fsrs_scheduling.sql # FSRS columns on cards
supabase/migrations/011_material_type.sql # topic material_type
supabase/migrations/012_hybrid_search.sql # match_paper_chunks_hybrid RPC
supabase/migrations/013_research_sources.sql # doi + oa_url on papers
supabase/migrations/014_feynman_source_attachment.sql # feynman_source_ref on topics
supabase/migrations/015_planner_skips.sql # planner missed-session records
supabase/migrations/016_drop_sm2_columns.sql # removes SM-2 columns (run LAST)
Migration 016 is destructive. Run it only after verifying that all production cards have non-NULL
duevalues (i.e., after Task 2'sinitFromSM2backfill has completed successfully).
Then seed the university registry:
supabase/seed_university_registry.sql
npm run devOpen http://localhost:3000, sign up, and start studying.
npm test # run all tests once (332 passing)
npm run test:watch # watch modeTests use Vitest + fast-check (property-based). Coverage includes:
- FSRS scheduling (FSRS-1, FSRS-2 properties)
- SM-2 β FSRS migration backfill (no NaN/negative; no all-same-day)
- Timezone-safe due-date helpers (DUE-1 across 9 IANA zones + DST)
- RRF fusion ordering on known inputs
- Citation validation β every emitted citation resolves to a retrieved chunk (RAG-1)
- Academic search API clients with mocked fetch (graceful no-key/empty handling)
- Research citation validation β no unsupported
[N]markers (RESEARCH-1) - Feynman grounding helpers (passage building, prompt constraints)
- Study Mix queue builder β vocab blocking (MIX-1), weakness ordering (MIX-2)
- Spacing math β non-decreasing gaps, never past the exam (SPACING-1)
- Planner forward-fill β result strictly after original, never in occupied set
src/
βββ app/
β βββ (auth)/ # Login / signup
β βββ (protected)/app/
β βββ _actions/ # Server actions
β β βββ review.ts # FSRS review queue + submit
β β βββ feynman.ts # Grounded Feynman evaluation + source attachment
β β βββ research.ts # Grounded synthesis (OpenAlex/Crossref/Unpaywall)
β β βββ rag.ts # Hybrid RAG (RRF retrieval)
β β βββ planner.ts # Spacing-aware planner + missed-session reschedule
β β βββ study-session.ts # Evidence-based Study Mix queue
β β βββ subjects.ts # Material-type management
β β βββ academic/ # University onboarding, registry, ingest, jobs
β β βββ rag/ # parser, chunker, embedder sub-modules
β βββ feynman/ # Feynman editor with source attachment UI
β βββ review/ # FSRS review session (4-button, intra-session relearning)
β βββ research/ # Research Desk (OpenAlex + OA PDF ingestion)
β βββ settings/ # Settings including material-type selector
β βββ β¦
βββ lib/
β βββ fsrs.ts # Pure FSRS module (scheduleReview, initFromSM2)
β βββ due.ts # Timezone-safe due-date helpers
β βββ rrf.ts # Reciprocal Rank Fusion (mirrors SQL function)
β βββ feynman-grounding.ts # Passage building + grounded prompt construction
β βββ study-mix.ts # Evidence-based queue builder (buildQueue)
β βββ spacing.ts # Cepeda ridgeline + distributeSessions
β βββ academic-search/
β β βββ openalex.ts # OpenAlex client (CC0)
β β βββ crossref.ts # Crossref Polite Pool client
β β βββ unpaywall.ts # Unpaywall OA lookup + enrichWithUnpaywall
β β βββ types.ts # Shared AcademicWork / UnpaywallResult types
β βββ academic/ # University pure libs (registry, load, extractβ¦)
β βββ sm2.ts # SM-2 (retained as migration reference, no longer used)
β βββ supabase/ # Typed clients
βββ supabase/
β βββ migrations/ # 001β016
β βββ tests/
β βββ 012_hybrid_search.test.sql # SQL fusion-ordering test (run with psql)
βββ proxy.ts # Auth gate
The following tools are not required and not part of the runtime path. They are documented here for operators who want to extend the pipeline.
Docling provides higher-fidelity PDF parsing (tables, figures, complex layouts) than pdf-parse. It is a Python service that runs outside Next.js. To use it:
- Run Docling as a sidecar service (Docker).
- Call its REST API from a custom server action before calling
chunkText. - Docling is never a hard dependency β
pdf-parseis always the default.
Ragas is a Python library for evaluating retrieval-augmented generation pipelines. It can score faithfulness, answer relevance, and context precision of queryRag responses offline. To use it:
- Export a sample of
(question, answer, contexts)triples from your production logs. - Run Ragas in a Python notebook or CI script against those triples.
- Ragas does not run inside Next.js and is not installed as a dependency.
- Learning over engagement β every mechanic maps to real study behavior.
- AI as tutor, not author β it questions and guides; never writes your work.
- Grounded data only β official academic sources only; no invented dates, no fabricated citations, ever.
- Compassionate design β no punishment for missed days; the group helps instead.
- Evidence-based defaults β interleaving, FSRS, and spacing choices are backed by published meta-analyses with explicit citations in the code.
- Reuse over rebuild β new features ride the existing Supabase + Server Actions architecture.
| Feature | Source |
|---|---|
| FSRS scheduler | Ye (2022), DSR model β reduces review load ~20β30% vs SM-2 |
| Vocab blocking | Brunmair & Richter (2019) β g = β0.39 for word lists |
| Math/visual interleaving | Brunmair & Richter (2019) β g = 0.34 (math), 0.67 (discrimination) |
| Spacing ridgeline | Cepeda et al. (2008) β optimal lag ratio by retention interval |
| Asymmetric spacing cost | Cepeda et al. (2008) β under-spacing harms far more than over-spacing |
| Self-explanation grounding | Chi (2000); Fiorella & Mayer (2016) β strongest when checked against sources |
- ts-fsrs β MIT β FSRS-6 scheduling library
- Sprout Lands UI Pack by Cup Nooble β pixel font and UI elements
- Lucide β MIT icons
- Tiptap β MIT rich text editor
- OpenAlex β CC0 scholarly metadata
- Crossref β free Polite Pool metadata service
- Unpaywall β free open-access PDF registry
University academic data is sourced from official institutional portals and treated as untrusted, read-only input.
MIT
Built with pixels, science, and a lot of coffee.
Nora β a softer way to study.

