Skip to content

PRD-040: Capture (highlights + notes)

  • Status: Shipped — v2.7 / Epic P2 (#1112; children #1114–#1119)
  • Authors: Marko
  • Target Release: v2.7 (Phase P2)
  • Parent PRD: docs/prd/PRD-035-learning-platform.md
  • Depends on: PRD-036 / RFC-098 §3 (per-user files), PRD-039 (capture entry points)
  • Feeds: PRD-041 (Consolidation)

As-shipped note (v2.7). This PRD is reconciled with what landed; FR1–FR4 are live. The data model (FR3) shipped as specified. Markdown export shipped (it had been a v2.7 non-goal — see the revised Non-Goals). Transcript capture supports both whole-line and sub-segment character selection (FR1.2 — select a phrase, tap save). A fixed-palette colour picker + colour filter shipped (FR1.4 / FR4.2). The one remaining gap, called out honestly: the global view's topic filter (part of FR4.2) is not built — highlights don't carry topic metadata, so it needs a per-highlight topic projection (tracked, P2+). See §"As shipped" for the FR→code map.


Summary

Capture turns a moment of listening into a durable, grounded artifact. While playing — or reading the transcript — a user can highlight a transcript span or an insight (Kindle-style) and attach a note. Every capture is grounded: it carries the episode slug, timestamp, and transcript character offsets, so it can be replayed, cited, and later woven into the user's personal knowledge corpus (PRD-041). Capture must be effortless — one interaction in the common case — or it won't happen.

Background & Context

  • Listening alone doesn't make knowledge stick (PRD-035 thesis). Capture is the first half of the retention loop: marking what mattered in the moment. Consolidation (PRD-041) is the second half.
  • The pipeline already grounds insights and quotes to exact offsets/timestamps (RFC-049). Capture reuses that grounding so a user highlight is as precise and replayable as a pipeline quote.
  • Captures are per-user overlay (Principle 3): private, attached to shared episode artifacts.

Goals

  • Capture a highlight in ≤1 interaction during playback ("mark this moment").
  • Highlight arbitrary transcript spans (Kindle-style selection) and whole insights.
  • Attach freeform notes to an episode, a highlight, or an insight.
  • Ground every capture (slug + timestamp + char offsets + optional speaker) for replay and citation.
  • Let users review and manage their captures per-episode and globally.

Non-Goals

  • Not collaborative/shared highlights (no social in v2.7).
  • Not rich-text/markdown editing beyond plain notes (keep it frictionless; richer formats later).
  • Not third-party integration (Readwise/Obsidian/etc.) in v2.7 — candidate follow-up. (Plain Markdown export of all highlights + notes did ship — see API summary; the integrations remain out.)
  • Not auto-highlighting — capture is user-driven (pipeline insights are a separate, suggested layer).

Personas

Active learner (mark what resonates, revisit later) and researcher (capture citable evidence).

User Stories

  • As a listener, I can tap once to highlight the moment I'm hearing right now.
  • As a reader, I can select a transcript passage and save it as a highlight.
  • As a learner, I can save a pipeline insight to my highlights with one tap.
  • As a note-taker, I can attach a thought to a highlight or to the whole episode.
  • As a returning user, I can see all my highlights for an episode, and across everything, and jump back to the exact audio moment of any of them.

Functional Requirements

FR1: Highlighting

  • FR1.1: During playback, a single control captures the current moment as a highlight anchored to the active segment (slug + timestamp + segment id + offsets).
  • FR1.2: In the transcript, selecting a span (one or more segments) creates a highlight over the exact character range, carrying start/end timestamps.
  • FR1.3: An insight card (PRD-039 FR4.3) has a "save to highlights" action; the saved highlight retains the insight→quote grounding (text, quote, speaker, timestamp).
  • FR1.4: Highlights have an optional colour/label (small fixed palette) for lightweight categorisation.

FR2: Notes

  • FR2.1: A note (plain text) can attach to a highlight, an insight, or the episode as a whole.
  • FR2.2: Notes are editable and deletable by their owner.

FR3: Grounding & data model

  • FR3.1: highlight records: id, user, episode_slug, kind(span|moment|insight), start_ms, end_ms, char_start, char_end, segment_ids[], quote_text, speaker?, source_insight_id?, color?, created_at.
  • FR3.1a — Survive re-transcription: timestamps (start_ms/end_ms) are the stable anchor; segment_ids and char offsets may shift on re-scrape, so a highlight re-anchors by timestamp on read (re-locating the nearest segment), retaining quote_text for display + drift verification. A re-scrape never silently drops a highlight (see RFC-098 §7).
  • FR3.2: note rows: id, user, target(highlight|insight|episode), target_id, text, created_at, updated_at.
  • FR3.3: Both are per-user overlay rows (PRD-036 FR2); authz on every access.

FR4: Review & management

  • FR4.1: Per-episode highlights list, each with a jump-to-moment link (seeks the player).
  • FR4.2: A global "My highlights" view across all episodes, filterable by podcast/topic/colour and sortable by recency.
  • FR4.3: Edit/delete any highlight or note.
  • FR4.4: Highlights and notes are the raw material consumed by Consolidation (PRD-041).

API summary

All routes are auth-gated and live under the consumer /api/app/* namespace (RFC-098), not the /api/user · /api/episodes paths this PRD originally sketched. Episode scoping is a ?episode= query parameter, not a path segment. As shipped (src/podcast_scraper/server/routes/app_capture.py):

Method Path Description
GET /api/app/highlights All highlights; ?episode=<slug> scopes to one episode
POST /api/app/highlights Create a highlight (201; route mints id + created_at)
PATCH /api/app/highlights/{id} Edit color / quote_text (404 if absent)
DELETE /api/app/highlights/{id} Remove a highlight; returns the remaining list
GET /api/app/notes All notes; ?target=&target_id= scopes them
POST /api/app/notes Create a note (target + target_id; text min length 1)
PATCH /api/app/notes/{id} Edit a note's text (404 if absent)
DELETE /api/app/notes/{id} Remove a note; returns the remaining list
GET /api/app/highlights/export.md Markdown export of all highlights + attached notes

As shipped (v2.7 / Epic P2)

FR Shipped as Where
FR1.1 moment One-tap "mark this moment" in the player hero (tags the active speaker) web/learning-player/src/views/PlayerView.vue, #1116
FR1.2 span Save a transcript line (segment-granular) or a selected phraseselectionSubRange() reads the live selection for exact char offsets + verbatim quote TranscriptList.vue (canCapture) + player/transcriptCapture.ts, #1116 + delta
FR1.3 insight "Save to highlights" on each Knowledge-panel insight card (keeps source_insight_id grounding) web/learning-player/src/components/KnowledgePanel.vue, #1116
FR1.4 colour Fixed palette (amber/rose/sky/emerald/violet): per-highlight swatch picker + coloured card border; color via PATCH HighlightsView.vue + utils/highlightColors.ts, delta
FR2 notes Add / edit / delete plain-text notes per highlight in the Library view web/learning-player/src/views/HighlightsView.vue, #1117
FR3 grounding highlight + note records exactly per FR3.1/FR3.2; per-user JSON files src/podcast_scraper/server/app_user_state.py, #1114
FR3.1a re-anchor reanchor_highlight() re-locates positional fields by timestamp; a drifted span is flagged (anchor_status), never dropped (RFC-098 §7) app_user_state.py, #1114
FR4.1 per-episode Highlights grouped by episode with jump-to-moment (?t=) HighlightsView.vue, #1117
FR4.2 global view Global Library "Highlights" tab (grouped by episode) + a colour filter. Podcast scoping is the grouping; a topic filter is the one remaining gap (highlights carry no topic metadata yet). LibraryView.vue / HighlightsView.vue, #1117 + delta
FR4.3 edit/delete Delete highlights; add/edit/delete notes #1117
FR4.4 → Consolidation Highlights + notes are the per-user corpus P3 (PRD-041) reads feeds PRD-041
Export GET /highlights/export.md + a Library "Export Markdown" link app_capture_export.py, #1115

Tests: unit (store + re-anchor + Markdown renderer), integration (route CRUD + export over a fixture corpus), component (capture surfaces + Highlights view), and an e2e covering the full listen→capture→review loop on the committed validation corpus (#1114#1118).

Success Metrics

  • A highlight is captured in ≤1 interaction during playback and appears immediately in "My highlights".
  • Every highlight jumps back to within 0.5s of its original moment.
  • Highlights survive across sessions and devices (per-user persistence).
  • A saved insight retains full grounding (quote + speaker + timestamp).

Dependencies

  • PRD-036 (per-user store, grounding data from artifacts).
  • PRD-039 (capture entry points in the player/transcript/insight cards).

Open Questions

  • ~~Export to external tools (Readwise/Obsidian/Markdown)~~ — Markdown export shipped in v2.7; third-party integrations (Readwise/Obsidian) remain a follow-up PRD.
  • ~~Tag taxonomy: free tags vs. fixed colours/labels~~ — fixed colour palette shipped (picker + colour filter); free-text tags remain a possible later addition.
  • ~~Sub-segment character-range selection (FR1.2)~~ — shipped (select a phrase, tap save).
  • Remaining: a topic filter on the global Highlights view needs a per-highlight topic projection (highlights carry no topic metadata today) — tracked for P2+.

References

  • docs/prd/PRD-035-learning-platform.md, PRD-039-player.md, PRD-041-consolidation.md
  • docs/rfc/RFC-049-grounded-insight-layer-core.md