diff --git a/.gitignore b/.gitignore index 87420ad..8c0c879 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,7 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +# Project planning (local only) +PLANS.md +MEMORY.md diff --git a/PLANS.md b/PLANS.md deleted file mode 100644 index 245c5ba..0000000 --- a/PLANS.md +++ /dev/null @@ -1,512 +0,0 @@ -# Pounce — Implementation Plans (Phases 2–9) - -> Reference doc for implementing remaining features. Each phase is self-contained -> with file paths, code strategies, and acceptance criteria. - ---- - -## Phase 2: Row-Level Security & Multi-Tenancy Hardening - -### Goal -Enforce tenant isolation at the database layer so even application bugs can't leak data across tenants. - -### Tasks - -1. **Apply RLS policies to the live database** - - File: `scripts/setup-rls.sql` (already written) - - Run via `docker exec` on the db container, or add to `docker-entrypoint.sh` after `prisma db push` - - Every tenant-scoped table gets `ENABLE ROW LEVEL SECURITY` + a policy checking `current_setting('app.current_tenant_id')` - -2. **Wire up the Prisma RLS client extension** - - File: `src/lib/server/rls.ts` (already written but unused) - - `forTenant(tenantId)` wraps queries in a transaction that does `SET LOCAL app.current_tenant_id = ''` - - `bypassRLS()` for site admin cross-tenant operations - - Update all API routes and server load functions to use `forTenant(locals.tenant.id)` instead of raw `prisma` - -3. **Create a shared helper for tenant-scoped DB access** - - File: `src/lib/server/db.ts` — add `export function tenantDb(locals: App.Locals)` that returns `forTenant(locals.tenant!.id)` - - All routes call `tenantDb(locals)` instead of importing `prisma` directly - -4. **Test multi-tenant isolation** - - Access via two different hostnames (two tenants auto-created) - - Create boards on each — verify they don't appear on the other - - Verify API endpoints can't fetch cross-tenant data by ID - -### Acceptance Criteria -- All API routes use tenant-scoped Prisma client -- Direct SQL query with wrong tenant_id returns 0 rows -- Two separate hostnames show fully isolated data - ---- - -## Phase 3: Roles & Permissions Enforcement - -### Goal -Enforce GlobalRole (SITE_ADMIN, USER), TenantRole (ADMIN, MEMBER), and BoardMemberRole (OWNER, EDITOR, VIEWER) throughout the app. - -### Tasks - -1. **Create route guards** - - File: `src/lib/server/guards.ts` - - `requireAuth(locals)` — throws 401 if not logged in - - `requireTenantAdmin(locals)` — throws 403 if not tenant ADMIN - - `requireSiteAdmin(locals)` — throws 403 if not SITE_ADMIN - - `requireBoardAccess(locals, boardId, minRole?)` — checks membership - -2. **Apply guards to all API routes** - - Board CRUD: owners can delete, editors can modify, viewers read-only - - Column/Card CRUD: inherit from board membership - - Already partially done in routes — audit each for consistency - -3. **Public board anonymous access** - - `GET /api/boards/[boardId]` — if `visibility === 'PUBLIC'`, allow without auth - - Board view page server load — same check (already done, verify) - - Board listing — anonymous users see only PUBLIC boards (already done, verify) - -4. **Board member management UI** - - File: `src/routes/boards/[boardId]/members/+page.svelte` - - API: `POST/DELETE /api/boards/[boardId]/members` - - Add members by email (must be registered on same tenant) - - Change member role (OWNER/EDITOR/VIEWER) - - Remove members - - Only OWNER can manage members - -5. **Tenant admin panel (basic)** - - File: `src/routes/admin/+page.svelte` (guarded by tenantRole === 'ADMIN') - - List all users in tenant, change their tenantRole - - List all boards in tenant - -### Acceptance Criteria -- VIEWER cannot create/edit/delete cards or columns -- Non-members cannot access PRIVATE boards -- Anonymous can view PUBLIC boards but not edit -- Board owner can add/remove/change member roles -- Tenant admin can see all boards and manage users - ---- - -## Phase 4: Rich Card Features - -### Goal -Cards get labels, categories, markdown descriptions, due dates, assignees, checklists, comments, and file attachments. - -### Tasks - -1. **Tags/Labels system** - - API: `CRUD /api/tags` (tenant-scoped) - - API: `POST/DELETE /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/labels` - - UI: Color picker for tag creation, label pills on cards - - Card modal: add/remove labels section - - File: `src/lib/components/TagPicker.svelte` - -2. **Categories (board grouping)** - - API: `CRUD /api/categories` (tenant-scoped) - - Board listing page: group boards by category, filter dropdown - - Board create/edit: optional category selector - -3. **Markdown description editor** - - Card modal: replace plain textarea with markdown editor - - Use `marked` (already installed) for rendering - - Toggle between edit and preview modes - - File: `src/lib/components/MarkdownEditor.svelte` - -4. **Due dates with urgency indicators** - - Card modal: date picker input for due date - - Card thumbnail: colored badge (green = future, yellow = soon, red = overdue) - - API: PATCH card with `dueDate` field (already supported) - - Use `date-fns` (already installed) for relative time - -5. **Assignees** - - Card modal: dropdown to assign board members - - API: `POST/DELETE /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/assignees` - - Show avatar circles on card thumbnails (already rendering if present) - -6. **Checklists** - - Card modal: add checklists with items, toggle completion - - API: `CRUD /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/checklists` - - API: `CRUD .../checklists/[checklistId]/items` - - Progress bar on card thumbnail showing X/Y items done - - File: `src/lib/components/Checklist.svelte` - -7. **Comments** - - Card modal: comment list + add comment form (markdown) - - API: `CRUD /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/comments` - - Show author name, avatar, timestamp, edit/delete own comments - - File: `src/lib/components/CommentList.svelte` - -8. **File attachments** - - Card modal: drag-drop or file picker to upload - - API: `POST /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/attachments` (multipart) - - API: `GET /api/attachments/[id]` to download - - Store in `/app/uploads` Docker volume (already mounted) - - Show file list with name, size, download link - - File: `src/lib/components/Attachments.svelte` - -### Acceptance Criteria -- All features visible in the card modal -- Labels appear as colored pills on board view cards -- Due date badge with color coding -- Checklist progress bar on card thumbnails -- Comments with markdown rendering -- File upload/download working via Docker volume - ---- - -## Phase 5: Real-Time Updates (Socket.IO) - -### Goal -Multiple users viewing the same board see live updates: card moves, new cards, column changes, comments, presence. - -### Tasks - -1. **Socket.IO server integration** - - File: `server.js` (production) — already has basic setup - - File: `src/socket/index.ts` — auth middleware, room management (already written) - - Add Vite dev middleware for Socket.IO (simple HTTP server attach in dev) - -2. **Client-side socket connection** - - File: `src/lib/stores/socket.ts` (already written) - - Connect on app load (layout), disconnect on unload - - Join board room when entering board view, leave when exiting - -3. **Broadcast events from API routes** - - After every write operation (create/update/delete), emit to the board room - - Events: `card:created`, `card:updated`, `card:moved`, `card:deleted`, `column:created`, `column:updated`, `column:moved`, `column:deleted`, `comment:created`, etc. - - File: `src/lib/server/realtime.ts` — helper `broadcast(boardId, event, data)` - - Import and call from each API route's POST/PATCH/DELETE handler - - In dev, use a global event emitter; in prod, use Socket.IO directly - -4. **Client-side event handlers on board view** - - File: `src/routes/boards/[boardId]/+page.svelte` - - Listen for socket events and update local `columns` state - - Handle: new cards appearing, cards moving, columns reordering, deletions - - Ignore events triggered by the current user (deduplicate via socket ID) - -5. **Optimistic UI with rollback** - - Card moves: update local state immediately, send API request - - If API fails: revert to previous state, show toast error - - Other users see the final state from the socket event - -6. **Presence indicators** - - Track which users are viewing a board (via socket room) - - Show avatar dots in board header: "Also viewing: Alice, Bob" - - Events: `user-joined`, `user-left` (already emitted in socket/index.ts) - -### Acceptance Criteria -- Open two browser tabs on the same board -- Move a card in tab A → appears in tab B within 1 second -- Add a column in tab B → appears in tab A -- Presence shows both tabs' users -- If API fails, optimistic update rolls back - ---- - -## Phase 6: Activity Log & Notifications - -### Goal -Track all significant actions, provide activity feeds, and notify users of relevant events. - -### Tasks - -1. **Activity logging service** - - File: `src/lib/server/activity.ts` - - `logActivity(boardId, userId, action, metadata?, cardId?)` — writes to `activities` table - - Actions: `board.created`, `card.created`, `card.moved`, `card.updated`, `card.deleted`, `column.created`, `column.deleted`, `comment.added`, `member.added`, `member.removed` - - Call from every API route after successful writes - -2. **Activity feed API** - - `GET /api/boards/[boardId]/activity` — paginated, returns recent activity for a board - - `GET /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/activity` — card-specific - - Include user name, action description, timestamp, link - -3. **Board activity sidebar** - - File: `src/lib/components/ActivityFeed.svelte` - - Toggleable sidebar on board view showing recent actions - - "Alice moved 'Fix login bug' from To Do to In Progress — 5 min ago" - -4. **Card activity in modal** - - Show activity timeline in card modal below comments - - Interleave comments and activity chronologically - -5. **Notification generation** - - File: `src/lib/server/notifications.ts` - - Generate notifications for: assigned to card, mentioned in comment (@username), due date approaching (daily cron or on-access check) - - Write to `notifications` table - -6. **Notification UI** - - Bell icon in navbar with unread count badge - - Dropdown showing recent notifications - - Click to navigate to relevant card/board - - Mark as read (individual + mark all) - - API: `GET /api/notifications`, `PATCH /api/notifications/[id]` (mark read), `POST /api/notifications/read-all` - - File: `src/lib/components/NotificationBell.svelte` - -7. **Real-time notification push** - - When a notification is created, emit via Socket.IO to the target user - - User's socket joins `user:` room on connect - - Bell icon updates live without page refresh - -### Acceptance Criteria -- Board sidebar shows chronological activity -- Card modal shows activity + comments interleaved -- Bell icon shows unread notification count -- Assigning a user to a card generates a notification -- Notifications appear in real-time via socket - ---- - -## Phase 7: Search, Filters & Templates - -### Goal -Find cards quickly with filters and search. Create boards from templates. - -### Tasks - -1. **Board-level card filters** - - Filter bar above columns: by label, assignee, due date range - - Client-side filtering (hide non-matching cards, don't remove from state) - - File: `src/lib/components/BoardFilters.svelte` - - Filter state stored in URL search params for shareability - -2. **Global search with PostgreSQL full-text search** - - Add `tsvector` column to `cards` table (or use generated column) - - Create GIN index on the tsvector - - Migration: `ALTER TABLE cards ADD COLUMN search_vector tsvector GENERATED ALWAYS AS (to_tsvector('english', coalesce(title,'') || ' ' || coalesce(description,''))) STORED;` - - `CREATE INDEX cards_search_idx ON cards USING gin(search_vector);` - - API: `GET /api/search?q=` — returns matching cards with board/column info - - UI: Search bar in navbar, results dropdown with card titles + board names - - File: `src/lib/components/SearchBar.svelte` - -3. **Board templates** - - System templates: "Basic Kanban" (To Do, In Progress, Done), "Scrum Sprint" (Backlog, Sprint, In Review, Done), "Bug Tracker" (New, Confirmed, In Progress, Resolved, Closed) - - Tenant templates: save any board as a template - - API: `GET /api/templates`, `POST /api/templates` (save board as template), `POST /api/boards?templateId=` (create from template) - - Template data stored as JSON in `board_templates.template` column - - Board creation UI: "Start from template" option - - Seed system templates in `prisma/seed.ts` - -4. **Archive/unarchive UI** - - Board listing: toggle to show archived boards - - Board header: archive/unarchive button (OWNER only) - - Card: archive button in modal (already `archived` field in schema) - - Archived cards hidden from board view (already filtered in queries) - - Board view: toggle to show archived cards (grayed out) - -### Acceptance Criteria -- Filter cards by label → only matching cards visible -- Search "login" → finds cards with "login" in title or description -- Create board from "Basic Kanban" template → 3 columns pre-created -- Archive a card → disappears from board, visible in archive view - ---- - -## Phase 8: Polish & UX - -### Goal -Make the app feel production-quality with dark mode, keyboard shortcuts, responsive design, loading states, and accessibility. - -### Tasks - -1. **Dark mode** - - Tailwind v4 dark mode via `class` strategy - - Toggle button in navbar (sun/moon icon) - - Persist preference in localStorage + cookie (for SSR) - - File: `src/lib/stores/theme.ts` - - Update `app.css` with dark variants for custom colors - - `` element gets `class="dark"` from layout - -2. **Keyboard shortcuts** - - `n` — new card (in focused column) - - `Escape` — close modal/cancel edit - - `b` — back to boards - - `/` — focus search bar - - `?` — show keyboard shortcuts help modal - - File: `src/lib/components/KeyboardShortcuts.svelte` - - Register in layout, context-aware (don't trigger in input fields) - -3. **Responsive / mobile design** - - Board view: horizontal scroll on mobile (already works with overflow-x) - - Column width: responsive (narrower on small screens) - - Card modal: full-screen on mobile - - Navbar: hamburger menu on mobile - - Board listing: single column grid on mobile - -4. **Loading skeletons** - - Board listing: card-shaped skeleton placeholders - - Board view: column skeleton placeholders - - Card modal: skeleton for async-loaded details - - File: `src/lib/components/Skeleton.svelte` - -5. **Toast notifications** - - Success/error toasts for user actions (card created, error saving, etc.) - - Auto-dismiss after 3–5 seconds - - Stack in bottom-right corner - - File: `src/lib/components/Toast.svelte`, `src/lib/stores/toasts.ts` - -6. **Error handling** - - Global error page: `src/routes/+error.svelte` - - API error responses: consistent JSON `{ error: string }` format - - Client-side fetch wrapper with automatic error toast - -7. **Accessibility** - - ARIA labels on all icon-only buttons (partially done) - - Keyboard navigation for drag-and-drop (arrow keys to move cards) - - Focus management in modals (trap focus, restore on close) - - Screen reader announcements for drag-and-drop results - -### Acceptance Criteria -- Dark mode toggles and persists across sessions -- All pages usable on a 375px mobile screen -- Keyboard shortcuts work (test: press `/` → search focuses) -- Loading skeletons appear on slow connections -- Toasts appear for create/delete/error actions - ---- - -## Phase 9: Admin & DevOps - -### Goal -Admin dashboards for site-wide and per-tenant management. Production readiness. - -### Tasks - -1. **Site Admin dashboard** - - Route: `src/routes/admin/site/+page.svelte` (guarded: SITE_ADMIN only) - - Cross-tenant statistics: total tenants, users, boards, cards - - Tenant list with user counts, board counts, created date - - Create/edit/delete tenants - - Impersonate user (login as any user for debugging) - - Uses `bypassRLS()` Prisma client - -2. **Tenant Admin dashboard** - - Route: `src/routes/admin/+page.svelte` (guarded: tenantRole ADMIN) - - Tenant settings: name, allowed domains - - User management: list, change roles, deactivate - - Tag management: create/edit/delete tenant-scoped tags - - Category management: create/edit/delete categories - - Board overview: all boards with member counts - -3. **Health check endpoint** - - Route: `src/routes/api/health/+server.ts` - - Returns `{ status: 'ok', db: true/false, uptime: seconds }` - - Check DB connectivity with a simple query - - Used by Docker HEALTHCHECK and monitoring - -4. **Structured logging** - - Replace `console.log` with a logger (pino or simple structured wrapper) - - Log format: JSON with timestamp, level, message, metadata - - Request logging middleware in hooks.server.ts (method, path, status, duration) - -5. **Rate limiting** - - In-memory rate limiter for auth endpoints (login, register) - - File: `src/lib/server/ratelimit.ts` - - 10 attempts per minute per IP for login - - 5 registrations per hour per IP - - Return 429 Too Many Requests - -6. **Docker health check** - - Add `HEALTHCHECK` to Dockerfile pointing at `/api/health` - - Update `docker-compose.yml` with healthcheck for app service - -7. **Backup documentation** - - File: `docs/backup.md` - - PostgreSQL: `pg_dump` command for the kanban database - - Uploads volume: `docker cp` or volume backup - - Restore procedure - -### Acceptance Criteria -- Site admin can see all tenants and their stats -- Tenant admin can manage users and tags -- `/api/health` returns 200 with DB status -- Login rate-limited to 10/min per IP -- Docker health check passes - ---- - -## File Structure After All Phases - -``` -src/ -├── hooks.server.ts -├── app.css -├── app.d.ts -├── app.html -├── socket/ -│ └── index.ts -├── lib/ -│ ├── server/ -│ │ ├── db.ts -│ │ ├── auth.ts -│ │ ├── tenant.ts -│ │ ├── rls.ts -│ │ ├── permissions.ts -│ │ ├── guards.ts (Phase 3) -│ │ ├── validation.ts -│ │ ├── activity.ts (Phase 6) -│ │ ├── notifications.ts (Phase 6) -│ │ ├── realtime.ts (Phase 5) -│ │ └── ratelimit.ts (Phase 9) -│ ├── stores/ -│ │ ├── socket.ts -│ │ ├── theme.ts (Phase 8) -│ │ └── toasts.ts (Phase 8) -│ ├── utils/ -│ │ └── fractional-index.ts -│ └── components/ -│ ├── CardModal.svelte -│ ├── TagPicker.svelte (Phase 4) -│ ├── MarkdownEditor.svelte (Phase 4) -│ ├── Checklist.svelte (Phase 4) -│ ├── CommentList.svelte (Phase 4) -│ ├── Attachments.svelte (Phase 4) -│ ├── ActivityFeed.svelte (Phase 6) -│ ├── NotificationBell.svelte (Phase 6) -│ ├── BoardFilters.svelte (Phase 7) -│ ├── SearchBar.svelte (Phase 7) -│ ├── Skeleton.svelte (Phase 8) -│ ├── Toast.svelte (Phase 8) -│ └── KeyboardShortcuts.svelte (Phase 8) -└── routes/ - ├── +layout.svelte - ├── +layout.server.ts - ├── +page.svelte - ├── +error.svelte (Phase 8) - ├── auth/ - │ ├── login/+page.svelte - │ └── register/+page.svelte - ├── boards/ - │ ├── +page.svelte - │ ├── +page.server.ts - │ └── [boardId]/ - │ ├── +page.svelte - │ ├── +page.server.ts - │ └── members/ (Phase 3) - │ └── +page.svelte - ├── admin/ (Phase 3/9) - │ ├── +page.svelte - │ └── site/ - │ └── +page.svelte - └── api/ - ├── health/+server.ts (Phase 9) - ├── search/+server.ts (Phase 7) - ├── tags/+server.ts (Phase 4) - ├── categories/+server.ts (Phase 4) - ├── templates/+server.ts (Phase 7) - ├── notifications/+server.ts (Phase 6) - ├── auth/... - └── boards/... -``` - ---- - -## Implementation Order Recommendation - -Phases can be partially parallelized: -- **Phase 2** first (RLS) — foundational security -- **Phase 3** next (Roles) — needed before multi-user features -- **Phase 4** (Rich Cards) — the biggest feature payload, can start during Phase 3 -- **Phase 5** (Real-Time) — depends on Phase 4 for full value -- **Phase 6** (Activity/Notifications) — depends on Phase 5 -- **Phase 7** (Search/Filters/Templates) — independent, can be done anytime after Phase 4 -- **Phase 8** (Polish) — do alongside or after feature phases -- **Phase 9** (Admin/DevOps) — can start admin panels during Phase 3, finish last