# 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 markdown (Learn).
Access: logged-in + owns course
Entry points: Overview, Learn tab, 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` (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: markdown headings

  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 markdown.
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: Multiple-choice practice with immediate explanation.
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-problem markdown.
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` |
