# Study views

Inside `/my-courses/{slug}` after Overview. Shell tabs and chapter picker are in [04-dashboard-and-workspace.md](./04-dashboard-and-workspace.md). Heartbeat `CourseStudyHeartbeat` writes `study_sessions` while a study tab is open (not a labeled button).

Student IA (restored from Git `c7fc162`): **Overview** plus content tabs **Notes**, **Formula sheet**, **Flashcards**, **Quiz**, **Exam**, **Exam review**, **Mistake fixes**, **Exercises**. `module_pack_items.item_type` stays on `tab=` URLs. Learn / Practice / Review is not the primary navigation.

Ask Tutor remains the course-grounded AI panel (`?panel=ai`). Quiz/flashcard/exam **viewers** keep later backend (mastery, FSRS, exam timer). Course outline column from the later redesign is not shown.

Shared empty / gating copy (`StudyWorkspaceState`):

- `No study content yet` / `Return to the course overview or check back later.`
- `Not published yet` / `This chapter does not have any published study material yet.` / `Pick another chapter…`
- `{Tool} is not in this chapter` / `Open {tool} instead.` / `Return to the course overview.`
- `Loading study tool` / `Preparing this study tool.` / `Hang tight — it will appear in a moment.`
- `{Tool} for this chapter is being prepared.`
- `Content is empty` / `Content unavailable` / `This study tool could not be loaded.` / `Try another tab…`

Shared reader chrome (markdown tabs): `Version` select; `Save to library` / `Remove from library`; `Print`; reader chapters list is mobile / below `lg`; `On this page`; empty headings `Headings from this page will appear here as you read.`; mobile `Chapters` drawer title `Chapters` / `Close contents`.

---

PAGE: /my-courses/{slug} (Overview)
Purpose: Next recommended action, course roadmap, progress, upcoming assessment, flashcard (weak-area) summary.
Access: logged-in + owns course
Entry points: course card; breadcrumb course code; locked-tab fallbacks

SECTION: Continue
  Copy: `Start here` or `Pick up where you left off`. Empty `No study content is ready yet. Check back when new notes are published.` CTA `Start learning →` / `Continue learning →`.
  Data shown: `content_progress`, next playable `module_pack_items`

  ELEMENT: Start learning → / Continue learning →
    Action: navigate
    Destination: study href for that tab (`tab=` remains the content identity)
    Writes to: none
    Reads from: next content item

SECTION: Course roadmap
  Copy: `Course roadmap`. Row `Chapter {n}` status `Not started` / `{pct}%` / `Done`. Content-type availability uses restored tab names (`Notes`, `Quiz`, …). Fallback `Study the core ideas and practice problems for {title}.`
  Data shown: `course_modules`, chapter progress, pack item types

  ELEMENT: Chapter row
    Action: toggle
    Destination: expands available study tools for that chapter
    Writes to: none
    Reads from: `course_modules.title`

  ELEMENT: Notes / Formula sheet / Flashcards / Quiz / Exam / Exam review / Mistake fixes / Exercises
    Action: navigate
    Destination: first matching `tab=` href for that type
    Writes to: none
    Reads from: pack item types

SECTION: Your progress
  Copy: `Your progress` `{n}% complete` `{started} of {total} chapter(s) started`
  Data shown: aggregated chapter / content progress
  ELEMENT: none (display)

SECTION: Flashcard cue (embedded)
  Copy: `Flashcards` `{n} due · ~{m} min` `Review now`
  Data shown: due count + session duration estimate (median when enough history, otherwise ~30s/card). Does not duplicate the flashcard landing dashboard.

  ELEMENT: Review now
    Action: navigate
    Destination: flashcards `?scope=course&autostart=scheduled`
    Writes to: none until session starts
    Reads from: due count

Empty course: `No published study material yet` + back to dashboard.

---

PAGE: /my-courses/{slug}?tab=notes
Purpose: Chapter notes. Renders EducationalBlocks when `blocks` exist; markdown is the fallback.
Access: logged-in + owns course
Entry points: Overview, resume **Resume lessons**. Deep link `tab=notes` survives.

SECTION: Notes reader
  Copy: Empty `Notes for this chapter are being prepared.` Type label `Notes`.
  Data shown: `module_pack_items.content_data` (`blocks` or markdown); `content_progress` (`content_type=notes`)

  ELEMENT: Version
    Action: navigate
    Destination: same tab with `variant=`
    Writes to: none
    Reads from: pack variants

  ELEMENT: Save to library / Remove from library
    Action: submit
    Destination: none
    Writes to: `library_items` (`item_type=saved_note`)
    Reads from: saved-note status

  ELEMENT: Print
    Action: toggle
    Destination: none (`window.print`)
    Writes to: none
    Reads from: none

  ELEMENT: Chapters list `Ch. {n} · {title}`
    Action: navigate
    Destination: notes href for that module (flushes notes progress first)
    Writes to: `content_progress` (notes, debounced)
    Reads from: `course_modules`

  ELEMENT: On this page heading
    Action: navigate
    Destination: `#headingId`
    Writes to: localStorage notes resume heading
    Reads from: lesson headings (blocks, otherwise markdown)

  ELEMENT: Previous / Next
    Action: toggle
    Destination: in-page section
    Writes to: `content_progress` (notes)
    Reads from: heading outline

  ELEMENT: Practice this concept →
    Action: navigate
    Destination: `tab=exercises` else `tab=quiz`
    Writes to: none
    Reads from: available practice types

  ELEMENT: Open reference →
    Action: navigate
    Destination: `tab=formula_sheet` (Review intent; contextual from Learn)
    Writes to: none
    Reads from: none

---

PAGE: /my-courses/{slug}?tab=formula_sheet
Purpose: Printable formula sheet. Renders EducationalBlocks when `blocks` exist; markdown is the fallback.
Access: logged-in + owns course (public catalog may deep-link a free-preview module; unauthenticated users hit login)

SECTION: Formula sheet reader
  Copy: Type `Formula sheet`. Empty `Formula sheet for this chapter is being prepared.` Same Version / Save / Print / Chapters / On this page as notes. Lesson Previous/Next **does not** write `content_progress`.
  Data shown: `module_pack_items.content_data`

  ELEMENT: Version / Save to library / Print / Chapters / On this page / Previous / Next
    Action: same as Notes except progress write
    Destination: formula_sheet href / print / in-page
    Writes to: `library_items` on save only
    Reads from: content_data

---

PAGE: /my-courses/{slug}?tab=flashcards
Purpose: Spaced-repetition + practice/browse.
Access: logged-in + owns course
Entry points: tab strip; Overview **Review {n} due**; library **Review →**; resume **Review flashcards**

SECTION: Landing
  Copy: `{n} due · ~{m} min` `Review due cards` `Learn new cards` `More options` `Browse all` `Needs attention` `Flashcard settings` chips `Due` `Learning` `New` caught-up `You're caught up` `Next review {time}` empty `Nothing to study right now.` loading `Loading your flashcard deck...` `Loading course flashcard schedule...` `Could not load course flashcard schedule.` `Flashcards for this chapter are being prepared.`
  Hidden from default UI: mature cards, leeches, FSRS, raw retention, coverage dashboard.
  Data shown: `flashcard_card_states`, `flashcard_deck_settings`, review logs

  ELEMENT: Review due cards
    Action: submit
    Destination: reviewing phase (mode `scheduled`)
    Writes to: `flashcard_sessions` insert
    Reads from: due queue

  ELEMENT: Learn new cards
    Action: submit
    Destination: reviewing (`learn_new`)
    Writes to: `flashcard_sessions`
    Reads from: new-card budget

  ELEMENT: Browse all
    Action: toggle
    Destination: browse phase
    Writes to: none
    Reads from: cards

  ELEMENT: Needs attention
    Action: submit
    Destination: reviewing (`practice`)
    Writes to: `flashcard_sessions`
    Reads from: weakCardIds + `is_leech` / `attention_flagged_at`

  ELEMENT: Back to course
    Action: navigate
    Destination: overview
    Writes to: none
    Reads from: empty state

SECTION: Browse
  Copy: `Browse all cards` `Unscheduled browsing — ratings will not change your schedule.` badge `Needs attention`
  Data shown: cards + session flagged set

  ELEMENT: Back
    Action: toggle
    Destination: landing
    Writes to: none
    Reads from: none

  ELEMENT: Card row (front text)
    Action: toggle
    Destination: reviewing that card
    Writes to: may start browse session
    Reads from: card front

SECTION: Session header
  Copy: `Scheduled review` / `Learn new` / `Practice` / `Browse`. Counters `Learning {n}` `Review {n}` `New {n}`
  Data shown: session queue

  ELEMENT: Undo
    Action: submit
    Destination: previous card
    Writes to: `undo_last_flashcard_rating` → `flashcard_card_states` / review logs
    Reads from: last rated card
    SR: `Last rating undone.`

  ELEMENT: End session
    Action: submit
    Destination: landing or completion
    Writes to: `flashcard_sessions` update
    Reads from: session stats

SECTION: Reviewer
  Copy: Labels `Question` `Answer` `Hint`. Helper `Again: missed it. Hard: recalled with effort. Good: knew it. Easy: too easy.` Group aria `Rate your recall`. Keyboard `Space reveals · 1–4 rate…` Banner `Needs attention`. Link `Review this in the lesson`. Confirm `Reset this card's scheduling?…`
  Data shown: current card; coarse interval previews on rating buttons

  ELEMENT: Card actions
    Action: open modal
    Destination: menu
    Writes to: none
    Reads from: none

  ELEMENT: Bury for today
    Action: submit
    Destination: next card
    Writes to: `bury_flashcard_card` → `flashcard_card_states`
    Reads from: card id
    Toast: `Card buried for today.`

  ELEMENT: Needs attention / Remove flag
    Action: submit
    Destination: none
    Writes to: `flag_flashcard_attention` → `flashcard_card_states.attention_flagged_at`
    Reads from: flagged set + `is_leech`

  ELEMENT: Report issue
    Action: submit
    Destination: report dialog
    Writes to: `study_content_reports` (fallback `content_reports`)
    Reads from: card front/back + category
    Toast: `Thanks — we logged this card for review.`

  ELEMENT: Advanced → Reset scheduling
    Action: submit
    Destination: next card
    Writes to: `reset_flashcard_scheduling`
    Reads from: card
    Toast: `Card scheduling reset.`

  ELEMENT: Advanced → Suspend card
    Action: submit
    Destination: next / complete
    Writes to: `suspend_flashcard_card`
    Reads from: card
    Toast: `Card suspended.`

  ELEMENT: Hint / Hide hint
    Action: toggle
    Destination: none
    Writes to: none
    Reads from: `card.hint`

  ELEMENT: Show answer
    Action: toggle
    Destination: none
    Writes to: none
    Reads from: none

  ELEMENT: Review this in the lesson
    Action: navigate
    Destination: notes `#section-{key}` (stable content anchor; hash omitted if heading is gone)
    Writes to: none
    Reads from: `sourceHeadingId` / `notesSource` / `conceptIds`

  ELEMENT: Again / Hard / Good / Easy (`1 · {interval}` …)
    Action: submit
    Destination: next card / waiting / complete
    Writes to: Scheduled/learn: `commit_flashcard_review` → `flashcard_card_states`, `flashcard_review_logs`, then `content_progress` (flashcards). Practice/browse: no schedule write.
    Reads from: rating previews

SECTION: Waiting
  Copy: `Waiting for learning card` + countdown

  ELEMENT: Finish for now
    Action: submit
    Destination: completion
    Writes to: `flashcard_sessions`
    Reads from: stats

  ELEMENT: Study ahead
    Action: submit
    Destination: reviewing (+30m ahead)
    Writes to: may continue `flashcard_sessions`
    Reads from: queue

SECTION: Completion
  Copy: `Session complete` / `Practice complete` `{n} cards reviewed` `Recall` `Time spent` `Ratings` `Still due` `Next review {time}` `{n} card(s) marked needs attention this session` `Does not change your schedule` (practice again)

  ELEMENT: Back to flashcards
    Action: toggle
    Destination: landing
    Writes to: none (refresh reads)
    Reads from: none

  ELEMENT: Return to course
    Action: navigate
    Destination: module flashcards or overview
    Writes to: none
    Reads from: backHref

  ELEMENT: Practice again
    Action: submit
    Destination: reviewing (practice)
    Writes to: `flashcard_sessions`
    Reads from: none

Session errors (toasts): could not load/open/start/save/undo/bury/suspend/reset; `Nothing to study in this mode right now.`

---

PAGE: /my-courses/{slug}?tab=quiz
Purpose: Practice assessment. Legacy multiple choice still works. Numeric, short text, worked, symbolic, multipart, and the other student answer formats are graded on the server. Practice still shows explanation after the answer.
Access: logged-in + owns course

SECTION: Intro
  Copy: Eyebrow `{moduleTitle}` `Practice quiz` `{n} questions · Immediate feedback` `Finishing this checks it off. Passing and mastery are separate.` Empty `Quiz for this chapter is being prepared.`
  Data shown: `module_pack_items.content_data.questions`

  ELEMENT: Start quiz →
    Action: toggle
    Destination: answering (server presentation order when signed in)
    Writes to: `quiz_attempts` in_progress (`client_attempt_id`)
    Reads from: pack questions

SECTION: Answering / review
  Copy: `Question {i} of {n}` explanation after select. Keys `Keys 1-4 select / Enter continues / R retakes`
  Data shown: current question options

  ELEMENT: Option 1…n
    Action: toggle
    Destination: review state for that question
    Writes to: draft + `saveQuizProgress` until complete
    Reads from: `question.options`

  ELEMENT: Next question →
    Action: toggle
    Destination: next question
    Writes to: none
    Reads from: none

  ELEMENT: See results
    Action: submit
    Destination: complete
    Writes to: `quiz_attempts` (idempotent `client_attempt_id`); `quiz_attempt_answers`; `content_progress` completed at 100% on submit regardless of score; merged `user_concept_mastery`; optional `user_mistake_patterns`. Pass if score ≥ 80 (`lib/study/quiz-policy.ts`). Mastery is concept-level, not quiz completion.
    Reads from: answers array; pack item questions on the server

SECTION: Complete
  Copy: `Quiz submitted` `{n} / {total} correct` plus honest percent. `Attempted is not the same as mastered.` Pass copy from `attemptHeadline`. Concept summary from merged evidence: Strong / Developing / Needs work. One recommended next action. Per-question reasoning, not only the correct option.

  ELEMENT: Retake quiz
    Action: toggle
    Destination: answering (new attempt id, server shuffle)
    Writes to: new `quiz_attempts` row on start
    Reads from: none

  ELEMENT: Practice this weakness
    Action: toggle then submit
    Destination: follow-up question for that concept
    Writes to: practice concept outcomes (not a new full attempt)
    Reads from: question bank

---

PAGE: /my-courses/{slug}?tab=exam
Purpose: Practice exam. Answers after submit. Optional timer.
Access: logged-in + owns course

SECTION: Intro
  Copy: `Practice exam` `{n} questions · Answers after you submit`
  ELEMENT: Start untimed / Timed
    Writes to: `quiz_attempts` `mode=exam`
    Browser payload has no answer keys (`stripExamAnswerKey`).

SECTION: Complete
  Copy: `Exam submitted` plus score. Same mastery / attempted language as quiz.
  Scoring against pack item on the server. Retake starts a new UUID.

---

PAGE: /my-courses/{slug}?tab=exam_review
Purpose: Exam-week markdown.
Access: logged-in + owns course

SECTION: Reader
  Copy: Type `Exam review`. Empty `Exam review for this chapter is being prepared.`
  Data shown: `module_pack_items.content_data`

  ELEMENT: Version / Save to library / Print / Chapters / On this page
    Action: same as formula sheet
    Destination: exam_review href / print
    Writes to: `library_items` on save only
    Reads from: content_data

---

PAGE: /my-courses/{slug}?tab=mistake_fixes
Purpose: Recurring mistake patterns from tagged distractors, plus chapter trap markdown.
Access: logged-in + owns course

SECTION: Your mistake patterns
  Copy: `Your mistake patterns` empty `After a few quizzes, recurring slips show up here.`
  Data shown: `user_mistake_patterns`, `user_concept_mastery`

SECTION: Reader
  Copy: Type `Common traps`. Empty `Common traps for this chapter are being prepared.`
  ELEMENT: Version / Save to library / Print / Chapters / On this page
    Action: same as exam review
    Destination: mistake_fixes href
    Writes to: `library_items` on save
    Reads from: content_data

---

PAGE: /my-courses/{slug}?tab=exercises
Purpose: Practice problems. Markdown reader, or structured items when `items` exist. Structured items use the same server-graded answer formats as quiz.
Access: logged-in + owns course

SECTION: Reader
  Copy: Type `Exercises`. Empty `Exercises for this chapter are being prepared.`
  ELEMENT: Version / Save to library / Print / Chapters / On this page
    Action: same as exam review
    Destination: exercises href
    Writes to: `library_items` on save
    Reads from: content_data

---

## Write cheat sheet

| Surface | Writes |
|---|---|
| Notes scroll/sections | `content_progress` (notes); localStorage resume heading; optional `library_items` |
| Formula / exam review / mistake fixes / exercises | optional `library_items` only |
| Flashcards scheduled/learn ratings | `flashcard_sessions`, `flashcard_card_states`, `flashcard_review_logs`, `content_progress` (flashcards) |
| Flashcards bury/suspend/reset/undo | RPCs on card states / logs |
| Flashcards practice/browse ratings | session row; no schedule |
| Quiz/Exam complete | `quiz_attempts`, `content_progress` (quiz) |
| Open study tab | `study_sessions` via heartbeat |
| Course settings Save flashcard settings | `flashcard_deck_settings` |
