From 4e8d12f59384df2581d4767bb1d408572730e30d Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 17 Feb 2026 12:30:14 -0700 Subject: [PATCH] docs: add MODULE_SPEC.md with permission model and capabilities Co-Authored-By: Claude Opus 4.6 --- MODULE_SPEC.md | 128 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 MODULE_SPEC.md diff --git a/MODULE_SPEC.md b/MODULE_SPEC.md new file mode 100644 index 0000000..96b7a4a --- /dev/null +++ b/MODULE_SPEC.md @@ -0,0 +1,128 @@ +# rNotes — Collaborative Notebooks + +**Module ID:** `rnotes` +**Domain:** `rnotes.online` +**Version:** 0.1.0 +**Framework:** Next.js 14 / React 18 / Prisma / PostgreSQL / TipTap +**Status:** Active + +## Purpose + +Rich note-taking with notebooks, tags, file uploads, voice transcription, and canvas integration. Supports per-notebook collaboration with role-based access. Notes can embed as shapes on the rSpace canvas. + +## Data Model + +### Core Entities (Prisma) + +| Model | Key Fields | Relationships | +|-------|-----------|---------------| +| **User** | id, did (EncryptID DID), username | has many Notebook, Note, NotebookCollaborator | +| **Notebook** | id, title, slug (unique), description, coverColor, isPublic, canvasSlug, canvasShapeId | has many Note, NotebookCollaborator, SharedAccess | +| **NotebookCollaborator** | userId, notebookId, role (OWNER/EDITOR/VIEWER) | belongs to User, Notebook | +| **Note** | id, title, content, contentPlain, type (enum), url, isPinned, sortOrder, canvasSlug, canvasShapeId | belongs to Notebook, User (author) | +| **Tag** | id, name (unique), color | many-to-many with Note via NoteTag | +| **SharedAccess** | notebookId, sharedByUserId, sharedWithDID, role | cross-user sharing | + +### Note Types + +NOTE, CLIP, BOOKMARK, CODE, IMAGE, FILE, AUDIO + +### Collaborator Roles + +OWNER, EDITOR, VIEWER (per-notebook) + +## Permission Model + +### Space Integration + +rNotes currently operates at the notebook level, not the space level. The migration path adds a space-level role that sets the default notebook access. + +- **SpaceVisibility:** Mapped to notebook `isPublic` flag (public → true) +- **Default role for open spaces:** PARTICIPANT (can create notebooks and edit own notes) + +### Capabilities + +| Capability | Required SpaceRole | AuthLevel | Description | +|-----------|-------------------|-----------|-------------| +| `view_notebooks` | VIEWER | BASIC | See notebook list and read notes | +| `create_notebook` | PARTICIPANT | STANDARD | Create new notebooks | +| `edit_own_notes` | PARTICIPANT | STANDARD | Edit/delete own notes in any shared notebook | +| `edit_any_notes` | MODERATOR | STANDARD | Edit/delete any user's notes | +| `manage_notebooks` | ADMIN | ELEVATED | Delete notebooks, manage collaborators | + +### Module-Specific Overrides + +Per-notebook `CollaboratorRole` overrides the space-level default: +- Space PARTICIPANT + Notebook OWNER → full notebook control +- Space PARTICIPANT + Notebook VIEWER → read-only on that notebook +- Space MODERATOR → EDITOR on all notebooks (override) +- Space ADMIN → OWNER on all notebooks (override) + +### Current Auth Implementation + +- EncryptID DID from JWT claims +- `getAuthUser(request)` → extracts DID, upserts User +- `requireAuth(request)` → returns 401 if no valid token +- `getNotebookRole(userId, notebookId)` → looks up CollaboratorRole +- First authenticated user auto-claims orphaned notebooks + +## API Endpoints + +| Method | Path | Auth Required | Capability | Description | +|--------|------|---------------|------------|-------------| +| GET | /api/notebooks | Yes | view_notebooks | List user's notebooks | +| POST | /api/notebooks | Yes | create_notebook | Create notebook | +| GET | /api/notebooks/[id] | Depends | view_notebooks | Get notebook details | +| PUT | /api/notebooks/[id] | Yes | edit_own_notes | Update notebook | +| DELETE | /api/notebooks/[id] | Yes | manage_notebooks | Delete notebook | +| GET | /api/notebooks/[id]/notes | Depends | view_notebooks | List notes | +| GET | /api/notebooks/[id]/canvas | Yes | view_notebooks | Get canvas shape data | +| PUT | /api/notebooks/[id]/canvas | Yes | edit_own_notes | Update canvas binding | +| GET | /api/notes | Yes | view_notebooks | Global notes list | +| POST | /api/notes | Yes | edit_own_notes | Create note | +| GET | /api/notes/[id] | Depends | view_notebooks | Get note | +| PUT | /api/notes/[id] | Yes | edit_own_notes | Update note | +| DELETE | /api/notes/[id] | Yes | edit_own_notes | Delete own note | +| GET | /api/notes/search | Yes | view_notebooks | Full-text search | +| POST | /api/uploads | Yes | edit_own_notes | Upload file | +| GET | /api/uploads/[filename] | Depends | view_notebooks | Download file | +| POST | /api/voice/transcribe | Yes | edit_own_notes | Audio→text | +| POST | /api/voice/diarize | Yes | edit_own_notes | Speaker diarization | + +## Canvas Integration + +Notes and notebooks embed as shapes on the rSpace canvas: +- **`folk-note`**: Individual note card (title + content preview) +- **`folk-notebook`**: Notebook container showing note count +- Bidirectional binding via `canvasSlug` and `canvasShapeId` fields +- Editing a note shape on canvas updates the note in PostgreSQL +- Creating a note in rNotes can auto-place a shape on canvas + +## Cross-Module Dependencies + +| Module | Integration | +|--------|------------| +| **rSpace** | Canvas shape embedding (folk-note, folk-notebook) | +| **EncryptID** | DID-based identity and authentication | +| **rFiles** | File attachments referenced in notes | + +## Local-First / Offline Support + +- Currently server-authoritative (Prisma/PostgreSQL) +- Client state managed with Zustand +- Local-first Transformers.js for on-device AI inference +- Future: TipTap Y.js integration for offline collaborative editing +- Future: IndexedDB cache for offline note access + +## Migration Plan + +1. Add space concept: link notebooks to a space slug (optional, backwards-compatible) +2. Import `SpaceRole` from SDK for space-level role resolution +3. Add `resolveSpaceRole()` call in `getAuthUser()` or a new middleware layer +4. Cascade space role → default notebook role: + - Space PARTICIPANT → Notebook EDITOR (on newly accessed notebooks) + - Space VIEWER → Notebook VIEWER + - Space MODERATOR → Notebook EDITOR (all notebooks) + - Space ADMIN → Notebook OWNER (all notebooks) +5. Keep per-notebook `CollaboratorRole` overrides for fine-grained control +6. Replace direct role checks with `hasCapability()` in API route handlers