Feature: Structured Notes System
Metadata
- Issue ID: FEAT-59
- Status: Done
- Owner: Kzu0-afk
- Related PRs:
59-notes-system->dev
Overview
Structured Notes gives authenticated users a study-planning workspace where they can create notes, attach references to documents/courses/institutions, track deadlines, and organize note cards in a visual canvas. It is not a plain text-notes module — its value is connecting StudyBoost catalog content to user planning: save a document, course, or institution as a structured reference; preserve whether a referenced document is free, paid, premium, locked, or unavailable; attach personal context; set note-level and reference-level dates; and arrange notes spatially in canvas mode. The backend is the source of truth, and all user-owned data is scoped to the authenticated user from FEAT-31.
Frontend Behavior
- Users open
/notesto view, search, create, edit, soft-delete, and organize their own notes. - A note has a title, freeform body, optional deadline/reminder date, and optional initial references.
- References can target a document, course, or institution by
idor slug-derived lookup, with optional contextual text per reference. - "Add to Notes" quick-add actions are available on document, course, and institution detail pages: a lightweight modal lets the user pick an existing note or create a new one, pre-fills reference metadata from the public slug, requires auth (FEAT-31 redirect to
/login?next=<target>for unauthenticated users), shows inline success/failure feedback, and defaults to new-note mode when the user has no notes. - Notes list shows title, deadline state, and reference count. The note editor handles title, body, references, dates, and soft-delete controls, plus empty, loading, and retryable error states.
- Canvas mode renders notes as cards in a scrollable surface; cards are moved with lightweight directional controls and positions persist via
PATCH /notes/:id/canvas. List/detail mode remains fully usable when canvas mode is unavailable. - Document references display access state:
free,paid,premium, orunavailable. Referencing a document never unlocks it or bypasses subscription/payment checks. - UI follows StudyBoost design-system spacing, cards, radius, and typography; controls are keyboard-accessible and mobile-first responsive.
Backend Behavior
A dedicated NestJS module lives under backend/src/notes/ (controller, module, service, dto/, enums/). All notes endpoints use CookieAuthGuard and derive user_id from request.user; the API never accepts arbitrary user_id in request bodies.
Endpoints (all auth required):
GET /notes— paginated notes for the authenticated user; query paramspage,limit,q,status,has_deadline,sort; excludes archived/deleted by default.POST /notes— creates a note; accepts title, body, optional deadline/reminder, optional initial references and canvas metadata; rejects empty/whitespace-only titles.GET /notes/:id— returns one owned note with references and canvas metadata;404if missing or owned by another user.PATCH /notes/:id— updates title/body/status/deadline/reminder; owned notes only; rejects empty titles.DELETE /notes/:id— soft-deletes withstatus=deletedanddeleted_at.POST /notes/:id/references— adds a document/course/institution reference; validates type and target existence; supports slug-based linking and reference-level dates.PATCH /notes/:id/references/:referenceId— updates context, order, dates, or archived status.DELETE /notes/:id/references/:referenceId— archives a reference witharchived_atinstead of hard-deleting.PATCH /notes/:id/canvas— persists card position, size, and optional group label; validates numeric bounds.
Data model — migration 202605021540_add_structured_notes_tables: structured_notes, structured_note_references, structured_note_canvas_items. Additive only, with guarded IF NOT EXISTS operations and indexes on (user_id, status, updated_at), (user_id, deadline_at), (note_id), and reference target columns.
Business rules and failure modes: a note belongs to exactly one user; references belong to exactly one note; locked/paid/premium targets may be referenced but never granted content access; deadline/reminder timestamps are stored in UTC; invalid reference targets are rejected on create; deleted targets render as unavailable on read; DTOs are validated with class-validator and body/context lengths are bounded; ownership is enforced at the service layer for every read/write.
QA Test Scenarios
| Scenario ID | Description | Steps | Input | Expected Result |
|---|---|---|---|---|
| FEAT-59-01 | Create note happy path | Login, open /notes, create a note with title/body | Valid title and body | Note is saved for the current user and appears in the list |
| FEAT-59-02 | Create note with document reference | Open document detail, click Add to Notes, create a new note | Valid document slug/id | Note is created with the document reference attached |
| FEAT-59-03 | Add locked document reference | Add a paid/premium document to a note | Paid or premium document | Reference is saved and shown as locked/paid/premium; document is not unlocked |
| FEAT-59-04 | Add course reference | Open course detail, Add to Notes, select existing note | Valid course slug/id | Course reference is appended to the selected note |
| FEAT-59-05 | Add institution reference | Open institution detail, Add to Notes, select existing note | Valid institution slug/id | Institution reference is appended to the selected note |
| FEAT-59-06 | Deadline on note | Create/edit a note with a future deadline | Valid UTC date | Deadline is saved and displayed in note list/detail |
| FEAT-59-07 | Deadline on reference | Add a reference-level date | Valid UTC date | Reference displays its own date without overwriting the note deadline |
| FEAT-59-08 | Canvas position save | Move a note card in canvas mode and save | Valid x/y coordinates | Position persists after refresh |
| FEAT-59-09 | Unauthenticated quick add | Open a document page while logged out and click Add to Notes | No auth cookies | User is redirected to /login?next=<target> |
| FEAT-59-10 | Cross-user access blocked | User A requests User B's note by ID | Valid note ID owned by another user | API returns 404 without note data |
| FEAT-59-11 | Invalid reference target | Add a reference with an unknown document/course/institution ID or slug | Nonexistent target | API rejects with validation/not-found error; no reference created |
| FEAT-59-12 | Invalid canvas coordinates | Submit extreme/NaN/oversized coordinates | Invalid canvas payload | API rejects with 400 and keeps the previous layout |
| FEAT-59-13 | Backend failure during save | Simulate API failure while saving a note | Valid edit, backend unavailable | UI shows a retryable error and does not falsely mark the note saved |
| FEAT-59-14 | Empty state | New user opens the notes page | No notes | UI shows a helpful empty state and a create action |
| FEAT-59-15 | Archive/delete behavior | Archive/delete an owned note | Existing note | Note disappears from the default list but ownership data is not exposed |
| FEAT-59-16 | Search/filter notes | Search by note title/body/reference context | Query string | List returns only owned matching notes |
Edge Cases
- Deleted or unpublished referenced document: keep the reference row and render it as unavailable; the migration allows hard-deleted targets to null out without deleting the reference.
- Course/institution slug changes: stable UUID references are stored in the backend and the current slug/name is resolved on read.
- Locked document state changes: live access state is recomputed where possible while preserving
access_state_snapshotfor historical context. - Concurrent edits: last write wins for v1.
- Very long note bodies / many references: max lengths are enforced in DTOs and columns; long reference lists may be paginated.
- Canvas on mobile: list/detail fallback or constrained movement controls instead of forcing desktop drag behavior.
- Offline or flaky network: save operations show retryable failures and never pretend data is persisted.
Notes
- Auth dependency: FEAT-31 secure cookie auth is required for all note APIs and quick-add actions.
- Catalog dependency: references integrate with existing document/course/institution public catalog endpoints.
- Subscription/access dependency: locked/paid/premium states must not bypass existing access rules.
- Database dependency: requires additive migrations for notes, references, and canvas layout.
- Feature flag: consider
enable_structured_notesbefore public rollout if FEAT-35 is required by the target release. - Known limitations: real-time collaboration, rich block editor, in-note file uploads, and full Milanote parity are out of scope for v1.
Security Notes
- No API accepts arbitrary
user_idin request bodies; all notes endpoints require the auth guard. - Reference resolution supports slug-based linking and validates target existence.
- Locked/paid/premium document references are stored as references only and do not grant content access.
Canonical Feature Document
Implementation and QA requirements are tracked in: docs/features/FEAT-59-Structured-Notes-System.md