# Dashboard & workspace

Student home after login. Course study tabs are in [06-study-views.md](./06-study-views.md). Settings overlay is [05-settings.md](./05-settings.md). Flows: [00-how-everything-connects.md](./00-how-everything-connects.md).

**Access:** Workspace layout requires logged-in + onboarded (else `/welcome` or `/login?next=/dashboard`).  
`/dashboard` is the only student home. Course workspaces (`/my-courses/{slug}`) stay entitlement-gated (proxy → `/pricing` if unpaid). Admins count as subscribed for that gate. Marketing `/` is separate: only users with **current active access** are redirected there from `/`.

**Canonical home:** `/dashboard`. `/my-courses` and `/dashboard/my-packs` redirect here (query `q` and `error=no_access` preserved). Course study URLs stay at `/my-courses/{slug}`.

**Redirects:** `/my-courses/library|progress|semester-plan` → `/dashboard/…`. `/dashboard/course/[slug]/…` → `/my-courses/[slug]`.

---

## Shared chrome (every workspace page)

SECTION: Desktop sidebar
  Copy: Expanded wordmark `Gradeful`; collapsed `G`. Nav labels below. Streak card `This week` plus a dynamic rhythm title. Account chip shows `profiles.full_name` or `Your account`, campus line or email.
  Data shown: `profiles.*`; streak from `user_streaks` / `study_sessions`; feature flag `aiTutorEnabled`; `profiles.role` for Admin

  ELEMENT: Gradeful / G (brand)
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: none

  ELEMENT: Collapse / Expand sidebar
    Action: toggle
    Destination: none
    Writes to: cookie via `setWorkspaceSidebarCollapsed` (except on course routes: local only)
    Reads from: cookie

  ELEMENT: Dashboard
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: active on `/dashboard` (aliases `/my-courses`, `/dashboard/my-packs` redirect here)

  ELEMENT: Library
    Action: navigate
    Destination: `/dashboard/library`
    Writes to: none
    Reads from: none

  ELEMENT: Progress
    Action: navigate
    Destination: `/dashboard/progress`
    Writes to: none
    Reads from: none

  ELEMENT: Semester Plan
    Action: navigate
    Destination: `/dashboard/semester-plan`
    Writes to: none
    Reads from: none

  ELEMENT: AI Tutor
    Action: navigate
    Destination: On a course: same course URL + `?panel=ai`; else `/dashboard/ai-tutor`
    Writes to: none
    Reads from: Visible when `aiTutorEnabled`

  ELEMENT: Admin dashboard
    Action: navigate
    Destination: `/admin`
    Writes to: none
    Reads from: `profiles.role` is admin

  ELEMENT: Streak card / flame (collapsed)
    Action: open modal
    Destination: calendar dialog (month grid; legend `Streak day` `Missed` `Today`)
    Writes to: none
    Reads from: `user_streaks`, `study_sessions`, `profiles`, platform `weeklyStudyGoalMinutes`

  ELEMENT: Account chip (opens flyout)
    Action: open modal
    Destination: account menu
    Writes to: none
    Reads from: `profiles.avatar_url`, `full_name`, campus fields, Auth email

  ELEMENT: Settings (flyout)
    Action: open modal
    Destination: Settings tab `profile`
    Writes to: none
    Reads from: none

  ELEMENT: Get help (flyout)
    Action: open modal
    Destination: Settings tab `help`
    Writes to: none
    Reads from: none

  ELEMENT: Upgrade plan (flyout)
    Action: navigate
    Destination: `/pricing`
    Writes to: none
    Reads from: none

  ELEMENT: Dashboard (flyout)
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: none

  ELEMENT: Learn more (flyout)
    Action: toggle
    Destination: submenu
    Writes to: none
    Reads from: none

  ELEMENT: About Gradeful / Privacy policy / Terms of service / Refund policy / Academic integrity / All policies
    Action: navigate
    Destination: `/about` `/privacy` `/terms` `/refunds` `/academic-integrity` `/policies`
    Writes to: none
    Reads from: none

  ELEMENT: Log out / Logging out…
    Action: submit
    Destination: `/`
    Writes to: clears session
    Reads from: none

SECTION: Mobile header
  Copy: Logo `Gradeful`
  Data shown: same as sidebar account

  ELEMENT: Open workspace menu (hamburger)
    Action: toggle
    Destination: drawer with full sidebar (`showCollapseToggle=false`)
    Writes to: none
    Reads from: none

  ELEMENT: Close menu / backdrop / Escape
    Action: toggle
    Destination: closes drawer
    Writes to: none
    Reads from: none

  ELEMENT: Gradeful
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: none

  ELEMENT: Open settings (gear)
    Action: open modal
    Destination: Settings `profile`
    Writes to: none
    Reads from: none

  ELEMENT: Open account menu
    Action: open modal
    Destination: same flyout as sidebar
    Writes to: none
    Reads from: `profiles.*`

SECTION: Course right panel (course player only)
  Copy: Collapsed `Open stats` / `Open AI tutor`. Expanded tabs `Stats` `AI Tutor`. Stats labels `Notes` `Quizzes` `Exam prep` plus notes %, flashcards due, quiz scores, exam days. AI loading `Loading AI tutor...` Modes `Explain` `Guide me` `Practice me` `Review my mistake` `Quiz me`. Suggestions `Explain this concept` `Help me practice` `What should I review for my upcoming exam?`
  Data shown: `content_progress`, flashcard due counts, quiz scores, `course_events`

  ELEMENT: Open stats / Open AI tutor / Collapse panel
    Action: toggle
    Destination: none
    Writes to: cookie `setWorkspaceAiPanelExpanded`
    Reads from: cookie; `aiTutorEnabled`

  ELEMENT: Stats / AI Tutor tabs
    Action: toggle
    Destination: none
    Writes to: none
    Reads from: none

  ELEMENT: Module list row
    Action: navigate
    Destination: `/my-courses/{slug}?module=&tab=notes`
    Writes to: none
    Reads from: `course_modules`

  ELEMENT: View all chapters →
    Action: navigate
    Destination: course overview
    Writes to: none
    Reads from: none

  ELEMENT: AI suggestion chips / chat send
    Action: submit
    Destination: none
    Writes to: POST `/api/ai-tutor` with course/module IDs (usage metrics only; no transcript table)
    Reads from: entitled Gradeful pack items, student progress, student-entered exam dates

  ELEMENT: Ask AI Tutor (floating)
    Action: toggle
    Destination: opens AI panel
    Writes to: none
    Reads from: `aiTutorEnabled`

---

PAGE: /dashboard
Purpose: Restored pre-redesign student home (Git `89d0991`): greeting, resume card, exam-mode banner, course cards, what needs attention, Today calendar, study hours, recent activity. Not the later next-study-action redesign.
Access: logged-in + onboarded (not subscriber-gated). Unpaid accounts see the empty/onboarding pack state, not a marketing homepage inside the workspace.
Entry points: `/` when the user has active access; `/auth/home`; welcome submit; login `next`; sidebar Dashboard; public header Dashboard; checkout **Go to my courses** → `/dashboard`

SECTION: Top bar
  Copy: Greeting `Good morning, {firstName}`. Search `Search your courses...`
  Data shown: `profiles.full_name` first name

  ELEMENT: Search
    Action: submit
    Destination: paid `/dashboard?q=…`; free `/courses?q=…`
    Writes to: none
    Reads from: query `q` filters owned courses on the paid home

  ELEMENT: Notification bell
    Action: toggle
    Destination: notification panel
    Writes to: `notifications.is_read` (`markAllNotificationsRead`; auto after 2s open)
    Reads from: `notifications` (limit 20) + realtime INSERT
    Empty: `No notifications yet`
    Badge: unread count
    Row: navigates `action_url` when set
    Note: evening streak-risk notifications are not generated

  ELEMENT: Mark all read
    Action: submit
    Destination: none
    Writes to: `notifications.is_read`
    Reads from: unread rows

  ELEMENT: Open settings (md+)
    Action: open modal
    Destination: Settings `profile`
    Writes to: none
    Reads from: none

  ELEMENT: AccountChrome (mobile top bar)
    Action: open modal
    Destination: account flyout / settings
    Writes to: none
    Reads from: `profiles.*`

SECTION: Access error
  Copy: `We couldn't open that course. Make sure it's in your plan, or try opening it again from the list below.`
  Data shown: query `error=no_access`

SECTION: Resume where you left off
  Copy: `Resume where you left off` plus last content type / chapter.
  Data shown: latest `content_progress.last_accessed_at` among owned modules
  ELEMENT: Resume CTA
    Action: navigate
    Destination: `/my-courses/{slug}?module=&tab=`
    Writes to: none
    Reads from: `content_progress`

SECTION: Exam mode banner
  Copy: `Exam mode` plus nearest exam headline. CTA into that course.
  Data shown: `exam_schedules` / `course_events` on owned courses
  ELEMENT: Open course
    Action: navigate
    Destination: `/my-courses/{slug}`
    Writes to: none
    Reads from: exam-mode builder

SECTION: Your courses
  Copy: `Your courses` or `Search your courses`. Cards: progress bar, `Content coming soon`, flashcards due, exam badge. Empty: `Your study space is waiting.` / `No courses yet` with `Browse the catalog and get access to your first pack.`
  Data shown: `user_access.course_ids` via `getCachedUserOwnedCourses`; `content_progress`; flashcard due counts
  ELEMENT: Course card
    Action: navigate
    Destination: `/my-courses/{slug}`
    Writes to: none
    Reads from: owned courses
  ELEMENT: View all courses / Show fewer / Clear search
    Action: toggle / navigate
    Destination: `/dashboard` for clear search
    Writes to: none
    Reads from: owned course count

SECTION: What needs attention
  Copy: `What needs attention` / `You're all caught up for now.` Rows for due flashcards, quizzes, exams, unpublished content.
  Data shown: `lib/workspace/attention-data.ts` plus flashcard due/leech counts
  ELEMENT: Attention row
    Action: navigate
    Destination: study tab href when set
    Writes to: none
    Reads from: attention items
  ELEMENT: View all
    Action: navigate
    Destination: `/dashboard/progress`
    Writes to: none
    Reads from: attention total count

SECTION: Today (right column)
  Copy: `Today` plus month label, week strip, `You're all caught up` / `{n} items need attention`. Link `Open semester plan`.
  Data shown: calendar from now; `stats.tasksDueToday`
  ELEMENT: Open semester plan
    Action: navigate
    Destination: `/dashboard/semester-plan`
    Writes to: none
    Reads from: none

SECTION: Study hours
  Copy: weekly hours vs goal, 7-day bars.
  Data shown: `study_sessions.active_minutes`; platform `weeklyStudyGoalMinutes`

SECTION: Recent activity
  Copy: `Recent activity` / `Activity appears after you open lessons, quizzes, or flashcards.` (tool names follow restored Notes / Quiz / Flashcards labels.)
  Data shown: recent `content_progress` rows

Public weekly rank / “students studying now” are **not** shown (leaderboards stay retired). Schedule-confirm and syllabus-upload gates stay retired.

---

PAGE: /my-courses
Purpose: Alias of `/dashboard` (redirect, query preserved).
Access: logged-in + onboarded. Home path is not subscriber-gated.
Entry points: old bookmarks; emails
ELEMENT: none — redirect only. `/my-courses/{slug}` remains the course workspace.

---

PAGE: /dashboard/my-packs
Purpose: Alias of `/dashboard` (redirect, query preserved).
Access: logged-in + onboarded
Entry points: old checkout CTA
ELEMENT: none — redirect only

---

PAGE: /dashboard/library
Purpose: Saved notes, favorited flashcards/quizzes, personal uploads.
Access: logged-in + onboarded
Entry points: Sidebar Library. Alias `/my-courses/library` redirects here.

SECTION: Header
  Copy: `My Library` `Saved notes, flashcards, and your own materials`
  Data shown: static

  ELEMENT: All / Notes / Flashcards / Uploads
    Action: toggle
    Destination: none (filter)
    Writes to: none
    Reads from: `library_items.item_type`

SECTION: Cards
  Copy: Empty `Nothing saved here yet`. Uploads empty `No uploads yet`. Upload zone `Upload your own materials` PDF·images·Word ≤10MB.
  Data shown: `library_items` + course/module joins

  ELEMENT: View →
    Action: navigate
    Destination: notes study href
    Writes to: none
    Reads from: saved note target

  ELEMENT: Review →
    Action: navigate
    Destination: flashcards (+ optional `&card=`)
    Writes to: none
    Reads from: flashcard library item

  ELEMENT: Retake →
    Action: navigate
    Destination: quiz
    Writes to: none
    Reads from: quiz library item

  ELEMENT: Delete
    Action: submit
    Destination: none
    Writes to: deletes `library_items` (`deleteLibraryItem`)
    Reads from: item id

  ELEMENT: Upload file
    Action: submit
    Destination: none
    Writes to: `library_items` via `uploadLibraryFile`
    Reads from: file

---

PAGE: /dashboard/progress
Purpose: Weekly streak objective, course breakdown, 12-week heatmap, quiz notes.
Access: logged-in + onboarded
Entry points: Sidebar Progress. Alias `/my-courses/progress` redirects here.

SECTION: This week / Progress
  Copy: `This week` `Progress` plus objective copy (weekly goal + miss budget). Summary: all-time screen time, current streak, course completion. Course bars: Notes / Flashcards / Quizzes. `Last 12 weeks`. Quiz notes table headers Module, Attempts, Best, Latest, Direction. How streaks work (explanatory copy in the view).
  Data shown: `profiles`, owned courses, `course_modules`, `study_sessions`, `course_xp`, `quiz_attempts`, `course_events`, streak dates

  ELEMENT: Keep studying
    Action: navigate
    Destination: first course overview or `/courses`
    Writes to: none
    Reads from: owned courses

  ELEMENT: Open course →
    Action: navigate
    Destination: `/my-courses/{slug}`
    Writes to: none
    Reads from: `courses.slug`

---

PAGE: /dashboard/semester-plan
Purpose: Upcoming assessments, recommended prep, course progress. Not a giant calendar. Syllabus is optional and private.
Access: logged-in + onboarded
Entry points: Sidebar Semester Plan. Alias `/my-courses/semester-plan` redirects here.

SECTION: Header
  Copy: `Semester Plan` `Upcoming assessments, a short prep list, and course progress.` CTA `Add exam` session length `20 min` `45 min` `90 min` `Exam cram`
  Data shown: cookie `gradeful_study_session`; owned courses

  ELEMENT: Add exam
    Action: open dialog
    Destination: AddExamDateDialog
    Writes to: `exam_schedules` (`addStudentExamDate`)
    Reads from: owned courses

  ELEMENT: Session length
    Action: submit
    Destination: none
    Writes to: cookie via `setStudySessionLength`
    Reads from: cookie

SECTION: Upcoming assessments
  Copy: empty `No assessments yet` `Add an exam in a few seconds.` list shows course, title, when, optional topics, source when dates disagree
  Data shown: merged `exam_schedules` + `course_events` (`lib/workspace/assessments.ts`)

  ELEMENT: Remove
    Action: submit
    Destination: none
    Writes to: deletes student `exam_schedules` row
    Reads from: student-owned assessment id

SECTION: Recommended prep
  Copy: session titles from `generateStudyPlan`; empty `Add an exam or open a course`
  Data shown: assessments, progress, flashcard due counts
  ELEMENT: Open →
    Action: navigate
    Destination: course overview or study href
    Writes to: none
    Reads from: session.href

SECTION: Course progress
  Copy: `{code}` `{n}% complete`
  Data shown: `content_progress`
  ELEMENT: Open →
    Action: navigate
    Destination: `/my-courses/{slug}`

SECTION: Syllabus (optional)
  Copy: `Private to you.` `Upload` / `Delete`
  Data shown: `syllabuses`
  ELEMENT: Upload
    Action: open modal
    Destination: SyllabusUploadModal (skippable)
    Writes to: `syllabuses`
  ELEMENT: Delete
    Action: submit
    Destination: none
    Writes to: deletes storage object + `syllabuses` row

---

PAGE: /dashboard/ai-tutor
Purpose: Standalone AI page when opened off-course.
Access: logged-in; if `aiTutorEnabled` false → `/dashboard`
Entry points: Sidebar AI Tutor when not in a course.

SECTION: Help
  Copy: `AI Tutor` plus instruction to open a course and use the right AI panel
  Data shown: static

  ELEMENT: Back to dashboard
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: none

---

PAGE: /my-courses/[courseSlug]
Purpose: Course overview + in-place study player (`?module=&tab=&variant=&chapter=&panel=`).
Access: logged-in + onboarded; must own the course else `/dashboard?error=no_access`; anonymous → login with `next`
Entry points: dashboard cards, resume, attention, progress, library, right panel, public catalog formula-sheet preview (may bounce to login)

SECTION: Course shell header
  Copy: Breadcrumb `Dashboard` / `{courseCode}` / compact chapter picker / current study tool / progress. Tabs: `Overview` `Notes` `Formula sheet` `Flashcards` `Quiz` `Exam` `Exam review` `Mistake fixes` `Exercises` `Ask Tutor` (Ask Tutor hidden unless `aiTutorEnabled`). Locked title `Not published yet`. Picker trigger `Ch. {n} · {title}` or `Select chapter`. Layout: restored pre-redesign tab strip + chapter picker (Git `c7fc162`). No Learn / Practice / Review strip and no course-outline column. Right AI panel is a full-screen drawer on small screens.
  Data shown: `courses.code`, `course_modules`, available `module_pack_items` mapped through `lib/workspace/learning-intent.ts`

  ELEMENT: Dashboard
    Action: navigate
    Destination: `/dashboard`
    Writes to: none
    Reads from: none

  ELEMENT: {courseCode}
    Action: navigate
    Destination: `/my-courses/{slug}` overview
    Writes to: none
    Reads from: `courses.slug`

  ELEMENT: Course settings (gear)
    Action: navigate
    Destination: `/my-courses/{slug}/settings`
    Writes to: none
    Reads from: none

  ELEMENT: Overview / Notes / Formula sheet / Flashcards / Quiz / Exam / Exam review / Mistake fixes / Exercises
    Action: navigate
    Destination: overview, or `/my-courses/{slug}?module={id}&tab={item_type}`. Ask Tutor still uses `?panel=ai`. Legacy `?intent=learn` URLs may still resolve to the first matching tab, but Learn / Practice / Review is not the student IA.
    Writes to: none
    Reads from: published `module_pack_items` for the chapter

  ELEMENT: Chapter option `Ch. {n} · {title}`
    Action: navigate
    Destination: same intent, new module
    Writes to: none
    Reads from: `course_modules`

Study body for each tab: [06-study-views.md](./06-study-views.md).

---

PAGE: /my-courses/[courseSlug]/settings
Purpose: Per-course flashcard scheduler, syllabus, exam dates.
Access: logged-in; must own course else public `/courses/{slug}`
Entry points: Course shell settings gear. `/dashboard/course/{slug}/settings` redirects here.

SECTION: Header
  Copy: `{code} settings`
  Data shown: `courses.code`, `courses.title`

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

SECTION: Flashcard study
  Copy: `Flashcard study` `How well should cards stick?` `New cards per day` `More options` `Maximum reviews per day` placeholder `No limit` `Study order` options mixed / `Reviews first` / `New cards first` related-cards checkbox `Timezone` `Developer settings`
  Data shown: `flashcard_deck_settings`. Raw scheduler parameter vectors are not shown.

  ELEMENT: Save flashcard settings / Saving…
    Action: submit
    Destination: none
    Writes to: `flashcard_deck_settings` (`updateFlashcardDeckSettings`)
    Reads from: form
    Toast: `Flashcard settings saved.`

SECTION: Syllabus
  Copy: `Syllabus` `Optional and private.` empty `No syllabus uploaded.`
  Data shown: `syllabuses`

  ELEMENT: Upload / Replace
    Action: open modal
    Destination: SyllabusUploadModal
    Writes to: `syllabuses`
    Reads from: existing row

  ELEMENT: Delete syllabus
    Action: submit
    Destination: none
    Writes to: deletes storage object + `syllabuses` row

SECTION: Exams
  Copy: `Exams` empty `No exams yet.` `Add exam`
  Data shown: merged `exam_schedules` + `course_events`
  ELEMENT: Add exam
    Action: open dialog
    Destination: AddExamDateDialog
    Writes to: `exam_schedules`
