Section 1: Overview
1.1 What is Academy?
Vision Statement
For product designers and product managers who want to ship features using AI tools but lack structured learning resources,
Academy is an online learning platform that delivers practical, hands-on courses for AI-assisted development.
Unlike scattered blog posts and documentation, our product provides a structured path from beginner to shipping real code.
Essential Elements
| Element |
Answer |
| Value Proposition |
Structured courses that take non-developers from zero to shipping features |
| Target Audience |
Technically curious designers and PMs with basic terminal/Git knowledge |
| Problem Context |
AI tools like Claude Code are powerful but learning resources are fragmented |
| Key Differentiator |
Hands-on, text-based learning path (not videos) specifically for non-developers |
1.2 Problem Statement
When I want to ship a feature using AI tools like Claude Code,
but existing resources are scattered across blog posts, videos, and docs that assume too much technical knowledge,
help me follow a structured learning path with practical exercises,
so I can successfully build real products without becoming a full-time developer.
Problem Details
| Question |
Answer |
| Who has this problem? |
Product designers and PMs who are technically curious |
| What is the problem? |
No structured path to learn AI-assisted development |
| How are they solving it today? |
Piecing together blog posts, YouTube videos, trial and error |
| Why is that inadequate? |
Wastes time, leads to frustration, incomplete understanding |
| What's the cost of not solving it? |
Designers/PMs give up or develop inefficient workflows |
1.3 Success Metrics
| Metric |
Baseline |
Target |
Measurement Method |
| Course completion rate |
N/A (new) |
>60% |
Users who finish all sessions / users who start |
| Time to first feature |
N/A |
<2 weeks |
Days from signup to first shipped feature (self-reported) |
| Comment engagement |
N/A |
>3 comments/session avg |
Total comments / total sessions |
| Return visits |
N/A |
>50% weekly return |
Users who return within 7 days |
Guardrail Metrics (What Shouldn't Get Worse)
| Metric |
Threshold |
| Page load time |
<2 seconds |
| Mobile usability score |
>90 (Lighthouse) |
1.4 Scope Boundaries
✅ In Scope (v1)
- Course catalog and detail pages
- Session content (markdown/MDX rendering)
- User authentication (email + OAuth)
- Comments on sessions
- Basic search
- Responsive design
❌ Out of Scope (Explicitly NOT Building)
- ❌ Video content — Text-based only for v1
- ❌ Course marketplace — We create all content internally
- ❌ Certificates/badges — No credentialing system
- ❌ Progress tracking — No saved progress indicators (v1)
- ❌ Live features — No real-time chat, video calls, or live classes
- ❌ Mobile app — Web only, responsive design
- ❌ Payment/subscriptions — Free access for v1
- ❌ Instructor tools — Content managed via Git, not CMS
Section 2: Users & Personas
2.1 Primary Persona
| Attribute |
Description |
| Role |
Senior Product Designer |
| Tech Level |
Expert product designer, low experience of development and AI |
Section 3: User Flows
3.1 Core User Journey
┌─────────────────────────────────────────────────────────────────────────────┐
│ LEARNER JOURNEY │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. DISCOVER 2. EXPLORE 3. LEARN 4. ENGAGE │
│ ─────────── ────────── ──────── ───────── │
│ Land on home → Browse courses → Read session → Comment & │
│ page View details Follow along discuss │
│ with repo │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Home │ → │ Catalog │ → │ Session │ → │ Comments │ │
│ │ Page │ │ Page │ │ Page │ │ Section │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │ Course │ │
│ │ Detail │ │
│ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
3.2 Key User Flows
| Flow |
Description |
Pages Involved |
| Browse courses |
User explores available courses from catalog |
Home → Catalog → Course Detail |
| Take a session |
User reads session content and follows along |
Course Detail → Session |
| Search content |
User searches for specific topic |
Any page → Search results |
| Comment on session |
User asks question or shares insight |
Session → Comments |
| Sign in |
User authenticates to access features |
Any page → Auth → Return |
Flow Detail: Browse Courses
1. User lands on Home page
2. User clicks "View Courses" or navigates to Catalog
3. User sees list of available courses with:
- Course title
- Description
- Number of sessions
- Estimated time
4. User clicks on a course
5. User sees Course Detail with:
- Full description
- Session list
- Progress indicator (if signed in)
6. User clicks "Start Course" or selects a session
Flow Detail: Take a Session
1. User is on Course Detail page
2. User clicks on a session
3. Session page loads with:
- Session title and metadata
- Sidebar navigation (all sessions)
- Breadcrumbs (Home > Course > Session)
- Session content (rendered markdown)
- Comments section at bottom
4. User reads content, follows along with repo
5. User can navigate to next/previous session
6. User can comment or ask questions
Section 4: Features & Requirements
4.1 Feature Overview
| # |
Feature |
Status |
Description |
| F1 |
Home Page |
|
Academy overview and entry point |
| F2 |
Course Catalog |
|
List of all available courses |
| F3 |
Course Detail |
|
Course overview with session list |
| F4 |
Session Page |
|
Session content rendered from MDX |
| F5 |
Navigation |
|
Header, sidebar, and breadcrumbs |
| F6 |
User Authentication |
|
Sign in/sign up with Supabase Auth |
| F7 |
Comments |
|
Discussion on session pages |
| F8 |
Search |
|
Search across courses and sessions |
Feature Details
Each feature has its own file in the features/ directory, following PRD best practices:
- User stories in INVEST format
- Requirements with MoSCoW priorities
- Acceptance criteria in Given-When-Then format
- Edge cases and error states documented
Description
Landing page that introduces Academy and directs users to courses. First impression for new visitors—must communicate value proposition within 5 seconds.
User Stories
| As a... |
I want to... |
So that... |
| Visitor |
Understand what Academy offers |
I can decide if it's relevant to me |
| Visitor |
See featured courses |
I can quickly find popular content |
| Visitor |
Navigate to course catalog |
I can browse all available courses |
| Returning user |
Access courses quickly |
I don't waste time on marketing content |
Requirements
| ID |
Requirement |
| F1.1 |
Hero section with headline and tagline |
| F1.2 |
Primary CTA button ("Browse Courses" or "Get Started") |
| F1.3 |
Brief value proposition (2-3 bullet points) |
| F1.4 |
Featured courses section (2-3 course cards) |
| F1.5 |
Link to full course catalog |
| F1.6 |
Footer with basic links |
UI Components
| Component |
Description |
| Hero |
Full-width section with headline, subhead, CTA |
| CourseCard |
Reusable card showing course title, description, metadata |
| Footer |
Site-wide footer with links |
Layout
┌─────────────────────────────────────────────────────────┐
│ Header (Logo, Nav, Sign In) │
├─────────────────────────────────────────────────────────┤
│ │
│ HERO SECTION │
│ "Learn to ship with AI" │
│ [Browse Courses] │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ FEATURED COURSES (3 cards) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Course │ │ Course │ │ Course │ │
│ │ 1 │ │ 2 │ │ 3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ [View All Courses →] │
│ │
├─────────────────────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: Visitor understands Academy's value proposition
Given I am a new visitor on the home page
When the page loads
Then I see a headline "Learn to ship with AI"
And I see a tagline explaining the target audience
And I see a primary CTA button
Scenario: Visitor navigates to course catalog
Given I am on the home page
When I click the "Browse Courses" button
Then I am navigated to /courses
Scenario: Visitor clicks a featured course
Given I am on the home page
And there are featured courses displayed
When I click on a course card
Then I am navigated to /courses/[course-slug]
Scenario: Home page loads within performance budget
Given I am accessing the home page
When the page loads
Then the Largest Contentful Paint is under 2 seconds
And no layout shift occurs after initial render
Edge Cases & Error States
| Scenario |
Expected Behavior |
| No featured courses exist |
Hide featured section, show only CTA to catalog |
| Images fail to load |
Show placeholder with course title |
| Slow network |
Show skeleton loading states |
Technical Notes
- Static page, can be fully server-rendered
- Featured courses pulled from course metadata (marked as
featured: true)
| Status |
Defined |
| Dependencies |
None |
Description
Page listing all available courses. Primary browse experience for discovering content.
User Stories
| As a... |
I want to... |
So that... |
| Visitor |
See all available courses |
I can find one that interests me |
| Visitor |
See course metadata (duration, sessions) |
I can estimate time commitment |
| Visitor |
Click into a course |
I can learn more about it |
Requirements
| ID |
Requirement |
| F2.1 |
Display all published courses |
| F2.2 |
Course card with title, description |
| F2.3 |
Course card with session count |
| F2.4 |
Course card with estimated time |
| F2.5 |
Grid layout (responsive) |
| F2.6 |
Empty state if no courses |
UI Components
| Component |
Description |
| CourseCard |
Title, description, session count, time estimate, link |
| CourseGrid |
Responsive grid container |
| PageHeader |
"Courses" title |
Layout
┌─────────────────────────────────────────────────────────┐
│ Header │
├─────────────────────────────────────────────────────────┤
│ │
│ Courses │
│ ───────────────────────────── │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Course │ │ Course │ │ Course │ │
│ │ Card 1 │ │ Card 2 │ │ Card 3 │ │
│ │ │ │ │ │ │ │
│ │ 12 sessions │ │ 8 sessions │ │ 6 sessions │ │
│ │ ~4 hours │ │ ~3 hours │ │ ~2 hours │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Course │ │ Course │ │
│ │ Card 4 │ │ Card 5 │ │
│ └─────────────┘ └─────────────┘ │
│ │
├─────────────────────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: Visitor sees all published courses
Given there are 5 courses with status "published"
And there are 2 courses with status "draft"
When I visit /courses
Then I see exactly 5 course cards
And I do not see any draft courses
Scenario: Visitor views course metadata
Given there is a course with 12 sessions and 4 hours total time
When I view the course card
Then I see "12 sessions"
And I see "~4 hours"
Scenario: Visitor clicks a course card
Given I am on the catalog page
When I click on a course card for "The Shipping AI Designer"
Then I am navigated to /courses/shipping-ai-designer
Scenario: Grid responds to screen size
Given I am on the catalog page
When my viewport is 1200px wide
Then courses display in 3 columns
When my viewport is 800px wide
Then courses display in 2 columns
When my viewport is 400px wide
Then courses display in 1 column
Scenario: Empty state when no courses
Given there are no published courses
When I visit /courses
Then I see a message "No courses available yet"
And I see a link to go back home
Edge Cases & Error States
| Scenario |
Expected Behavior |
| No published courses |
Show EmptyState component |
| Course missing required fields |
Skip that course, log warning |
| Very long course title |
Truncate with ellipsis after 2 lines |
Technical Notes
- Courses loaded from filesystem (
/courses/*/course.json)
- Only show courses with
status: "published"
Description
Overview page for a single course showing full description and session list. Helps users understand the learning path before committing.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US3.1 |
Visitor |
Read the full course description |
I can decide if it's right for me |
| US3.2 |
Visitor |
See all sessions in order |
I understand the learning path |
| US3.3 |
Visitor |
Start the course easily |
I can begin learning immediately |
| US3.4 |
Visitor |
Jump to a specific session |
I can skip content I already know |
Requirements
| ID |
Requirement |
| F3.1 |
Course title |
| F3.2 |
Full course description (markdown rendered) |
| F3.3 |
Session list with titles in order |
| F3.4 |
Session descriptions (truncated) |
| F3.5 |
Estimated time per session |
| F3.6 |
"Start Course" CTA linking to first session |
| F3.7 |
Total course duration display |
| F3.8 |
Breadcrumbs: Home > Courses > [Course] |
UI Components
| Component |
Props |
Description |
CourseHeader |
title, description, sessionCount, totalTime |
Course overview |
SessionList |
sessions[] |
Ordered session list |
SessionItem |
number, title, description, time, href |
Single session row |
Breadcrumbs |
items[] |
Navigation trail |
Layout
┌─────────────────────────────────────────────────────────┐
│ Header │
├─────────────────────────────────────────────────────────┤
│ Home > Courses > Course Name │
├─────────────────────────────────────────────────────────┤
│ │
│ Course Title │
│ ══════════════════════════════════════ │
│ │
│ Full course description goes here. Can be │
│ multiple paragraphs with markdown formatting. │
│ │
│ 12 sessions · ~4 hours total │
│ │
│ [Start Course →] │
│ │
├─────────────────────────────────────────────────────────┤
│ Sessions │
│ ───────── │
│ │
│ 1. Introduction to AI-Assisted Development ~20 min │
│ Get started with the basics... │
│ │
│ 2. Setting Up Your Environment ~30 min │
│ Install and configure the tools... │
│ │
│ 3. Your First Feature ~45 min │
│ Build something real... │
├─────────────────────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: Visitor views course details
Given there is a course with slug "shipping-ai-designer"
When I visit /courses/shipping-ai-designer
Then I see the course title
And I see the full course description rendered from markdown
And I see the total session count and duration
Scenario: Visitor sees session list
Given the course has 12 sessions
When I view the course detail page
Then I see all 12 sessions listed in order
And each session shows its title and estimated time
Scenario: Visitor starts the course
Given I am on a course detail page
And the first session has slug "introduction"
When I click "Start Course"
Then I am navigated to /courses/[slug]/introduction
Scenario: Visitor jumps to specific session
Given I am on a course detail page
When I click on "Session 5: Advanced Patterns"
Then I am navigated to /courses/[slug]/advanced-patterns
Scenario: Breadcrumb navigation works
Given I am on the course detail page for "The Shipping AI Designer"
When I click "Courses" in the breadcrumbs
Then I am navigated to /courses
Scenario: Invalid course slug returns 404
Given there is no course with slug "nonexistent-course"
When I visit /courses/nonexistent-course
Then I see a 404 error page
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Course has no sessions |
Show message "Sessions coming soon" |
| Invalid slug |
Return 404 page |
| Session missing time estimate |
Show "—" instead of time |
| Very long description |
Render full markdown, no truncation |
Technical Notes
- Dynamic route:
/courses/[slug]
- Data from
course.json + session MDX frontmatter
- Use
generateStaticParams for static generation
Description
The core learning experience. Displays session content rendered from MDX with sidebar navigation and comments. This is where users spend most of their time—must be readable, navigable, and engaging.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US4.1 |
Learner |
Read well-formatted content |
I can learn the material clearly |
| US4.2 |
Learner |
See code with syntax highlighting |
I can understand and copy examples |
| US4.3 |
Learner |
Navigate to next/previous session |
I can progress through the course |
| US4.4 |
Learner |
See my position in the course |
I know how much is left |
| US4.5 |
Learner |
Ask questions via comments |
I can get help when stuck |
Requirements
| ID |
Requirement |
| Content |
|
| F4.1 |
Session title |
| F4.2 |
Reading time estimate |
| F4.3 |
Last updated date |
| Markdown Rendering |
|
| F4.4 |
Headings (h1-h6) with anchor links |
| F4.5 |
Paragraphs, bold, italic, strikethrough |
| F4.6 |
Bullet and numbered lists |
| F4.7 |
Code blocks with syntax highlighting |
| F4.8 |
Code blocks with copy button |
| F4.9 |
Inline code styling |
| F4.10 |
Images with alt text |
| F4.11 |
Links (internal and external, external open new tab) |
| F4.12 |
Callout boxes: <Tip>, <Warning>, <Info> |
| F4.13 |
Tables with responsive scroll |
| Navigation |
|
| F4.14 |
Previous session link (hide on first session) |
| F4.15 |
Next session link (or "Complete" on last) |
| F4.16 |
Sidebar with all sessions, current highlighted |
| F4.17 |
Breadcrumbs: Home > Course > Session |
| Integration |
|
| F4.18 |
Comments section at bottom |
UI Components
| Component |
Description |
| SessionHeader |
Title, metadata (time, date) |
| MDXContent |
Rendered markdown content |
| CodeBlock |
Syntax-highlighted code with copy button |
| Callout |
Styled tip/warning/info boxes |
| SessionNav |
Previous/Next navigation |
| CommentSection |
Comments list and form (F7) |
Layout
┌─────────────────────────────────────────────────────────────────┐
│ Header │
├─────────────────────────────────────────────────────────────────┤
│ Home > Course > Session │
├────────────────┬────────────────────────────────────────────────┤
│ │ │
│ SIDEBAR │ Session Title │
│ ───────── │ ════════════════════════════ │
│ │ 15 min read · Updated Jan 16 │
│ 1. Intro ● │ │
│ 2. Setup │ Content goes here. This is the main │
│ 3. First │ learning content rendered from markdown. │
│ 4. [Current] │ │
│ 5. Testing │ ## Heading │
│ 6. Deploy │ │
│ ... │ Paragraph text with **bold** and *italic*. │
│ │ │
│ │ ```javascript │
│ │ const example = "code"; │
│ │ ``` │
│ │ │
│ │ ┌─────────────────────────────────┐ │
│ │ │ 💡 TIP: This is a callout box │ │
│ │ └─────────────────────────────────┘ │
│ │ │
│ │ [← Previous] [Next →] │
│ │ │
│ ├────────────────────────────────────────────────┤
│ │ │
│ │ Comments (3) │
│ │ ──────────── │
│ │ │
│ │ [Comment form - requires sign in] │
│ │ │
│ │ ┌─────────────────────────────────┐ │
│ │ │ User1 · 2 hours ago │ │
│ │ │ Great explanation! │ │
│ │ └─────────────────────────────────┘ │
│ │ │
├────────────────┴────────────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────────────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: Learner views session content
Given I am on session "setting-up-environment" of course "shipping-ai-designer"
When the page loads
Then I see the session title "Setting Up Your Environment"
And I see the reading time estimate
And I see the session content rendered from MDX
Scenario: Code blocks render with highlighting
Given the session contains a JavaScript code block
When I view the session
Then the code block has syntax highlighting
And I see a "Copy" button on the code block
When I click the "Copy" button
Then the code is copied to my clipboard
And the button shows "Copied!" temporarily
Scenario: Callout boxes render correctly
Given the session contains a <Tip> component
When I view the session
Then I see a styled callout box with a lightbulb icon
And the callout has a distinct background color (blue)
Scenario: Learner navigates to next session
Given I am on session 3 of 12
When I click "Next" at the bottom
Then I am navigated to session 4
And the sidebar updates to highlight session 4
Scenario: Learner on first session
Given I am on session 1 of the course
When I view the navigation
Then I do not see a "Previous" button
And I see a "Next" button
Scenario: Learner on last session
Given I am on session 12 (last session)
When I view the navigation
Then I see a "Previous" button
And I see a "Complete Course" button instead of "Next"
Scenario: Sidebar shows course progress
Given I am on session 5 of a 12-session course
When I view the sidebar
Then I see all 12 sessions listed
And session 5 is visually highlighted as current
Scenario: Mobile sidebar behavior
Given I am viewing on a mobile device (< 768px)
When the page loads
Then the sidebar is hidden by default
When I tap the menu toggle button
Then the sidebar slides in as an overlay
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Invalid session slug |
Return 404 page |
| MDX syntax error |
Show error boundary, log to console |
| Image fails to load |
Show alt text in placeholder |
| Very long code block |
Horizontal scroll within code block |
| External link |
Opens in new tab with rel="noopener" |
Technical Notes
- Route:
/courses/[slug]/[session]
- Content from MDX files in
/courses/[slug]/session-*.mdx
- Use
next-mdx-remote for MDX rendering
- Syntax highlighting:
shiki (better) or rehype-prism
- Reading time: calculated from word count (~200 words/min)
- Copy button: use
navigator.clipboard.writeText()
Description
Site-wide navigation including header, sidebar (on session pages), and breadcrumbs. Enables users to move around the site and understand their location.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US5.1 |
User |
See where I am on the site |
I don't get lost |
| US5.2 |
User |
Navigate to any main section |
I can move around easily |
| US5.3 |
Learner |
See my position in a course |
I know how much is left |
| US5.4 |
User |
Access my account from anywhere |
I can sign in/out easily |
Requirements
| ID |
Requirement |
| Header |
|
| F5.1 |
Logo linking to home |
| F5.2 |
"Courses" link to catalog |
| F5.3 |
Search input (see F8) |
| F5.4 |
"Sign In" button (logged out) |
| F5.5 |
User avatar dropdown (logged in) |
| F5.6 |
Mobile hamburger menu (<768px) |
| Breadcrumbs |
|
| F5.7 |
Show on all pages except home |
| F5.8 |
Format: Home > Course > Session |
| F5.9 |
All segments are clickable links |
| Sidebar |
|
| F5.10 |
Show on session pages only |
| F5.11 |
List all sessions in order |
| F5.12 |
Visually highlight current session |
| F5.13 |
Collapse to toggle on mobile |
UI Components
| Component |
Props |
Description |
Header |
user? |
Logo, nav, search, user menu |
MobileMenu |
isOpen, links[] |
Hamburger slide-out menu |
Breadcrumbs |
segments[] |
Navigation trail |
Sidebar |
sessions[], currentSlug |
Session list |
UserMenu |
user |
Avatar with dropdown |
Desktop (>768px):
┌─────────────────────────────────────────────────────────┐
│ 🎓 Academy Courses [🔍 Search] [Sign In] │
└─────────────────────────────────────────────────────────┘
Logged in:
┌─────────────────────────────────────────────────────────┐
│ 🎓 Academy Courses [🔍 Search] [👤▾] │
└─────────────────────────────────────────────────────────┘
Mobile (<768px):
┌─────────────────────────────────────────────────────────┐
│ [☰] 🎓 Academy [Sign In] │
└─────────────────────────────────────────────────────────┘
Breadcrumbs
Home > Courses > The Shipping AI Designer > Session 3
[link] [link] [link] [current]
┌──────────────────────┐
│ Sessions │
│ ───────── │
│ ○ 1. Introduction │
│ ○ 2. Setup │
│ ● 3. First Feature │ ← Current (highlighted)
│ ○ 4. Testing │
│ ○ 5. Deployment │
└──────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: Header displays on all pages
Given I am on any page of the site
When the page loads
Then I see the header with logo, nav, and user area
Scenario: Logo navigates to home
Given I am on the course catalog page
When I click the Academy logo
Then I am navigated to the home page (/)
Scenario: User menu shows sign in when logged out
Given I am not signed in
When I view the header
Then I see a "Sign In" button
And I do not see a user avatar
Scenario: User menu shows avatar when logged in
Given I am signed in as "alex@example.com"
When I view the header
Then I see my avatar
When I click my avatar
Then I see a dropdown with "Profile" and "Sign Out"
Scenario: Breadcrumbs show correct path
Given I am on session "first-feature" of course "shipping-ai-designer"
When I view the breadcrumbs
Then I see "Home > Courses > The Shipping AI Designer > First Feature"
And "Home", "Courses", and course name are clickable links
Scenario: Sidebar highlights current session
Given I am on session 3 of a 5-session course
When I view the sidebar
Then I see all 5 sessions listed
And session 3 is visually highlighted (filled circle, bold text)
Scenario: Mobile hamburger menu
Given I am on a mobile device (<768px)
When I tap the hamburger icon (☰)
Then a slide-out menu appears with navigation links
When I tap "Courses"
Then the menu closes and I navigate to /courses
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Very long course name |
Truncate in breadcrumbs with ellipsis |
| 20+ sessions in sidebar |
Scrollable sidebar, sticky header |
| User avatar fails to load |
Show initials fallback |
Technical Notes
- Header: Use
<header> in root layout
- Breadcrumbs: Generate from
usePathname() + data lookup
- Sidebar: Pass sessions as prop from page data
- Mobile menu: Use Radix or Headless UI for accessibility
Description
User sign up, sign in, and session management. Required for commenting and future features like progress tracking.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US6.1 |
Visitor |
Create an account |
I can access member features |
| US6.2 |
Visitor |
Sign in with email |
I can access my account |
| US6.3 |
Visitor |
Sign in with GitHub |
I can use my existing account |
| US6.4 |
User |
Stay signed in |
I don't have to log in every visit |
| US6.5 |
User |
Sign out |
I can secure my session |
| US6.6 |
User |
Reset my password |
I can recover my account |
Requirements
| ID |
Requirement |
| F6.1 |
Sign up with email/password |
| F6.2 |
Email verification |
| F6.3 |
Sign in with email/password |
| F6.4 |
Sign in with GitHub OAuth |
| F6.5 |
Sign in with Google OAuth |
| F6.6 |
Password reset via email |
| F6.7 |
Session persistence (30 days) |
| F6.8 |
Sign out |
| F6.9 |
Protected routes redirect to sign-in |
| F6.10 |
Return to previous page after sign-in |
Auth Flows
Sign Up:
1. User clicks "Sign Up"
2. User enters email and password (or clicks OAuth)
3. User verifies email (if required)
4. User is signed in and redirected
Sign In:
1. User clicks "Sign In"
2. User enters email/password or clicks OAuth
3. User is signed in and redirected to previous page
Password Reset:
1. User clicks "Forgot Password"
2. User enters email
3. User receives reset link via email
4. User clicks link and sets new password
5. User is signed in
UI Components
| Component |
Props |
Description |
| Sign In Page |
/sign-in |
Supabase Auth UI or custom form |
| Sign Up Page |
/sign-up |
Supabase Auth UI or custom form |
<UserButton> |
afterSignOutUrl |
Avatar with dropdown |
Acceptance Criteria (Given-When-Then)
Scenario: User signs up with email
Given I am on the sign-up page
When I enter a valid email and password
And I click "Sign Up"
Then my account is created
And I am redirected to the home page
And I see my avatar in the header
Scenario: User signs in with GitHub
Given I am on the sign-in page
When I click "Continue with GitHub"
And I authorize the application on GitHub
Then I am signed in
And I am redirected to my previous page
Scenario: User signs out
Given I am signed in
When I click my avatar in the header
And I click "Sign Out"
Then I am signed out
And I see "Sign In" button in the header
Scenario: Password reset flow
Given I am on the sign-in page
When I click "Forgot Password"
And I enter my registered email
And I click "Send Reset Link"
Then I see a confirmation message
And I receive an email with a reset link
Scenario: Protected route redirects
Given I am not signed in
When I try to access /profile
Then I am redirected to /sign-in
And after signing in, I am redirected to /profile
Scenario: Session persists across visits
Given I signed in yesterday
When I return to the site today
Then I am still signed in
And I see my avatar in the header
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Invalid email format |
Show inline validation error |
| Wrong password |
Show "Invalid credentials" message |
| Email already registered |
Show "Email already in use" |
| OAuth cancelled |
Return to sign-in page |
| Reset link expired |
Show error with option to request new link |
Technical Notes
- Using Supabase Auth (Google OAuth)
- Supabase Auth with
@supabase/ssr for Next.js
- Middleware for protected routes in
middleware.ts
- User ID from Supabase Auth for comments relation
Description
Discussion section on session pages for questions, answers, and community engagement. Enables learners to help each other and builds community.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US7.1 |
Learner |
Ask a question about content |
I can get help when stuck |
| US7.2 |
Learner |
Read others' questions |
I can learn from their answers |
| US7.3 |
Learner |
Reply to a comment |
I can help others or discuss |
| US7.4 |
Visitor |
Read comments without signing in |
I can see community activity |
Requirements
| ID |
Requirement |
| F7.1 |
Display comments below session content |
| F7.2 |
Show comment count in section header |
| F7.3 |
Show commenter name and avatar |
| F7.4 |
Show relative timestamp ("2 hours ago") |
| F7.5 |
Comment form for signed-in users |
| F7.6 |
"Sign in to comment" prompt for visitors |
| F7.7 |
Basic markdown in comments (bold, italic, code, links) |
| F7.8 |
Reply to comments (1 level nesting max) |
UI Components
| Component |
Props |
Description |
CommentSection |
sessionId, comments[] |
Container with count, form, list |
CommentForm |
onSubmit, placeholder |
Textarea and submit button |
Comment |
author, avatar, content, timestamp, replies[] |
Single comment |
CommentReply |
author, avatar, content, timestamp |
Nested reply (indented) |
SignInPrompt |
redirectUrl |
"Sign in to comment" CTA |
Layout
┌─────────────────────────────────────────────────────────┐
│ Comments (5) │
│ ─────────── │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Write a comment... │ │
│ │ │ │
│ │ [Post] │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 👤 Alex · 2 hours ago │ │
│ │ │ │
│ │ Great explanation! How does this work with TS? │ │
│ │ │ │
│ │ [Reply] │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ 👤 Sam · 1 hour ago │ │ │
│ │ │ Good question! You would need to... │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Data Model
interface Comment {
id: string;
content: string; // Markdown
user_id: string; // → User (Supabase Auth)
sessionId: string; // → Session
parentId: string | null; // → Comment (for replies)
createdAt: Date;
}
Acceptance Criteria (Given-When-Then)
Scenario: Signed-in user posts a comment
Given I am signed in
And I am on a session page
When I type "Great explanation!" in the comment form
And I click "Post"
Then my comment appears at the top of the list
And I see my avatar and name
And I see "just now" as the timestamp
Scenario: Visitor sees sign-in prompt
Given I am not signed in
And I am on a session page
When I view the comments section
Then I see existing comments
And I see "Sign in to comment" instead of the form
When I click "Sign in to comment"
Then I am redirected to /sign-in
Scenario: User replies to a comment
Given I am signed in
And there is an existing comment
When I click "Reply" on that comment
And I type my reply
And I click "Post"
Then my reply appears nested under the original comment
And my reply is visually indented
Scenario: Comment renders markdown
Given I post a comment with "Use `const` instead of **let**"
When the comment is displayed
Then "const" appears in code formatting
And "let" appears in bold formatting
Scenario: Comments load on session page
Given a session has 5 comments
When I visit that session page
Then I see "Comments (5)" as the section header
And I see all 5 comments in reverse chronological order
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Empty comment submitted |
Show validation error, disable submit |
| Comment too long (>2000 chars) |
Show character count, disable submit |
| Network error on post |
Show error toast, keep content in form |
| User deleted their account |
Show "Deleted User" for their comments |
| Malicious HTML in comment |
Sanitize, only allow safe markdown |
Technical Notes
- Comments stored in Supabase PostgreSQL
- Use Server Actions for posting (no API route needed)
- Optimistic UI: show comment immediately, rollback on error
- Rate limiting: max 10 comments per minute per user
- Sanitize markdown with
rehype-sanitize
Description
Full-text search across courses and session content. Allows users to quickly find relevant content without browsing.
User Stories
| ID |
As a... |
I want to... |
So that... |
| US8.1 |
Learner |
Search for a topic |
I can find relevant content quickly |
| US8.2 |
Learner |
See context in results |
I know which result is most relevant |
| US8.3 |
Visitor |
Search before signing up |
I can verify the content I need exists |
Requirements
| ID |
Requirement |
| F8.1 |
Search input in header (all pages) |
| F8.2 |
Search results page at /search |
| F8.3 |
Index course titles and descriptions |
| F8.4 |
Index session titles and descriptions |
| F8.5 |
Index session content (body text) |
| F8.6 |
Highlight matching text in snippets |
| F8.7 |
Link to course/session from each result |
| F8.8 |
"No results" empty state |
| F8.9 |
Debounced input (300ms) |
UI Components
| Component |
Props |
Description |
SearchInput |
placeholder, onSubmit |
Input with search icon in header |
SearchResults |
query, results[] |
Results list container |
SearchResult |
title, snippet, href, type |
Single result card |
NoResults |
query |
Empty state with suggestions |
Layout
┌─────────────────────────────────────────────────────────┐
│ Header with [🔍 Search input] │
├─────────────────────────────────────────────────────────┤
│ │
│ Search results for "claude code" │
│ ───────────────────────────────── │
│ 12 results │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 📘 The Shipping AI Designer [Course] │ │
│ │ Learn to ship features using **Claude Code**... │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 📄 Session 3: Your First Feature [Session] │ │
│ │ The Shipping AI Designer │ │
│ │ ...use **Claude Code** to implement the... │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 📄 Session 5: Advanced Patterns [Session] │ │
│ │ The Shipping AI Designer │ │
│ │ ...advanced **Claude Code** workflows... │ │
│ └─────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
Acceptance Criteria (Given-When-Then)
Scenario: User searches from header
Given I am on any page
When I type "claude code" in the search input
And I press Enter
Then I am navigated to /search?q=claude%20code
And I see results matching "claude code"
Scenario: Search results display correctly
Given there are courses and sessions containing "typescript"
When I search for "typescript"
Then I see matching courses with a "Course" badge
And I see matching sessions with a "Session" badge
And each result shows a highlighted snippet
Scenario: User clicks a search result
Given I have search results displayed
When I click on a session result
Then I am navigated to that session page
Scenario: No results found
Given there is no content matching "xyznonexistent"
When I search for "xyznonexistent"
Then I see "No results found for 'xyznonexistent'"
And I see suggestions like "Try different keywords"
Scenario: Search is performant
Given the search index contains 100 sessions
When I search for "react"
Then results appear in under 500ms
Scenario: Empty search query
Given I am on the search page
When I submit an empty search query
Then I see a prompt to enter a search term
And no results are displayed
Edge Cases & Error States
| Scenario |
Expected Behavior |
| Very long query (>200 chars) |
Truncate to 200 chars |
| Special characters in query |
Escape/sanitize for search |
| Search service unavailable |
Show friendly error, suggest browsing |
| Query with only stopwords |
Show "Try more specific terms" |
Technical Notes
Search Provider Comparison:
| Provider |
Pros |
Cons |
Recommendation |
| PostgreSQL FTS |
Built-in, no extra cost |
Basic ranking, no typo tolerance |
✅ Start here |
| Meilisearch |
Fast, typo-tolerant, self-hosted |
Extra service to maintain |
Upgrade path |
| Algolia |
Excellent UX, fully managed |
Costs at scale |
If budget allows |
Implementation (PostgreSQL):
-- Add search vector column
ALTER TABLE sessions ADD COLUMN search_vector tsvector;
-- Create index
CREATE INDEX sessions_search_idx ON sessions USING GIN(search_vector);
-- Search query
SELECT * FROM sessions
WHERE search_vector @@ plainto_tsquery('english', $1)
ORDER BY ts_rank(search_vector, plainto_tsquery('english', $1)) DESC;
Recommendation: Start with PostgreSQL full-text search. Migrate to Meilisearch if search quality becomes a user complaint.
5.1 Navigation Structure
Academy
├── Home (/)
├── Courses (/courses)
│ └── [Course] (/courses/[slug])
│ └── [Session] (/courses/[slug]/[session])
├── Search (/search)
├── Sign In (/sign-in)
├── Sign Up (/sign-up)
└── Profile (/profile)
5.2 Page Inventory
| Page |
Path |
Auth Required |
Description |
| Home |
/ |
Yes |
Landing page with featured courses |
| Course Catalog |
/courses |
Yes |
Browse all courses |
| Course Detail |
/courses/[slug] |
Yes |
Course overview and session list |
| Session |
/courses/[slug]/[session] |
Yes |
Session content with comments |
| Search |
/search |
Yes |
Search results |
| Sign In |
/sign-in |
No |
Authentication |
| Sign Up |
/sign-up |
No |
Registration |
| Profile |
/profile |
Yes |
User settings |
Auth Rule: All pages require authentication except /sign-in and /sign-up.
5.3 Content Model
Course
| Field |
Type |
Required |
Description |
id |
string |
Yes |
Unique identifier |
slug |
string |
Yes |
URL-friendly identifier |
title |
string |
Yes |
Course title |
description |
string |
Yes |
Short description (for cards) |
longDescription |
markdown |
No |
Full description (for detail page) |
sessions |
Session[] |
Yes |
Ordered list of sessions |
estimatedTime |
string |
No |
e.g., "4 hours" |
status |
enum |
Yes |
draft, published |
Session
| Field |
Type |
Required |
Description |
id |
string |
Yes |
Unique identifier |
slug |
string |
Yes |
URL-friendly identifier |
title |
string |
Yes |
Session title |
content |
markdown |
Yes |
Session content (from .md file) |
order |
number |
Yes |
Position in course |
estimatedTime |
string |
No |
e.g., "30 min" |
courseId |
string |
Yes |
Parent course |
User
| Field |
Type |
Required |
Description |
id |
uuid |
Yes |
Supabase auth user ID |
email |
string |
Yes |
Email address |
display_name |
string |
No |
Display name |
avatar_url |
string |
No |
Profile image URL |
| Field |
Type |
Required |
Description |
id |
uuid |
Yes |
Unique identifier |
content |
string |
Yes |
Comment text (markdown supported) |
user_id |
uuid |
Yes |
Author (FK to User) |
session_id |
string |
Yes |
Parent session |
parent_id |
uuid |
No |
Parent comment (for replies, 1 level only) |
created_at |
datetime |
Yes |
When posted |
5.4 Data Relationships
Course (1) ──────< Session (N)
│
└────< Comment (N) >──── User (1)
│
└── parent_id (self-reference, 1 level)
- Course has many Sessions (1:N)
- Session has many Comments (1:N)
- User has many Comments (1:N)
- Comment can have child Comments (1 level only)
Note: Course and Session content is stored in Markdown files (/courses), not in the database. Only User and Comment data is in Supabase.
Section 6: User Experience
6.1 Branding
| Element |
Value |
| Brand Name |
Academy (by Samsen Studio) |
| Tagline |
"Learn to ship with AI" |
| Logo |
TBD |
| Voice |
Friendly, practical, encouraging |
| Personality |
Expert friend who explains things clearly |
6.2 Key Screens
| Screen |
Purpose |
Key Elements |
| Home |
Entry point, explain value |
Hero, featured courses, CTA |
| Catalog |
Browse courses |
Course cards grid |
| Course Detail |
Course overview |
Description, session list |
| Session |
Learn content |
Sidebar, content, comments |
6.3 Design System
Spacing
Using Tailwind default spacing scale (4px base).
Components
Using shadcn/ui as base, customized to brand:
- Button
- Card
- Input
- Navigation Menu
- Breadcrumb
- Avatar
- Badge
6.4 Responsive Design
All pages and components must adapt gracefully to different screen sizes using a mobile-first approach.
Breakpoints
| Name |
Width |
Tailwind |
| Mobile |
<768px |
Default |
| Tablet |
768-1024px |
md: |
| Desktop |
>1024px |
lg: |
Requirements
| ID |
Requirement |
| R7.1 |
Mobile-first CSS approach |
| R7.2 |
No horizontal scroll on any page |
| R7.3 |
Touch targets minimum 44×44px |
| R7.4 |
Body font size minimum 16px |
| R7.5 |
Images scale with container |
| R7.6 |
Code blocks horizontal scroll |
Component Behaviors
| Component |
Mobile (<768px) |
Tablet (768-1024px) |
Desktop (>1024px) |
| Header |
Hamburger menu |
Full nav |
Full nav |
| Course Grid |
1 column |
2 columns |
3 columns |
| Session Sidebar |
Hidden (toggle) |
Visible (200px) |
Visible (260px) |
| Content Width |
100% - padding |
Max 720px centered |
Max 720px centered |
Mobile Layout (Session Page)
┌─────────────────────┐
│ ☰ Academy [Sign] │ ← Compact header (56px)
├─────────────────────┤
│ Home > Course > ... │ ← Breadcrumbs (truncated)
├─────────────────────┤
│ │
│ Session Title │
│ ═══════════════ │
│ │
│ Content flows │
│ full width with │
│ 16px padding. │
│ │
│ ┌───────────────┐ │
│ │ code block │→│ ← Horizontal scroll
│ └───────────────┘ │
│ │
│ [← Prev] [Next →] │
│ │
├─────────────────────┤
│ [≡ Show Sessions] │ ← Toggle sidebar
├─────────────────────┤
│ Comments... │
└─────────────────────┘
Acceptance Criteria
Scenario: Mobile layout renders correctly
Given I am viewing on a 375px wide screen
When I visit any page
Then content fits within the viewport
And there is no horizontal scrollbar
And text is readable without zooming
Scenario: Course grid adapts to screen size
Given there are 6 courses in the catalog
When I view on mobile (<768px)
Then courses display in 1 column
When I view on tablet (768-1024px)
Then courses display in 2 columns
When I view on desktop (>1024px)
Then courses display in 3 columns
Scenario: Touch targets are accessible
Given I am on a touch device
When I try to tap any interactive element
Then the tap target is at least 44×44 pixels
Technical Notes
- Use Tailwind responsive prefixes:
sm: (640px), md: (768px), lg: (1024px)
- Test on real devices, not just Chrome DevTools
- Lighthouse mobile score target: >90
- Core Web Vitals: LCP <2.5s, CLS <0.1