Detailed task breakdowns, file paths, acceptance criteria, and recommended execution order for all remaining phases. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20 KiB
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
-
Apply RLS policies to the live database
- File:
scripts/setup-rls.sql(already written) - Run via
docker execon the db container, or add todocker-entrypoint.shafterprisma db push - Every tenant-scoped table gets
ENABLE ROW LEVEL SECURITY+ a policy checkingcurrent_setting('app.current_tenant_id')
- File:
-
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 doesSET LOCAL app.current_tenant_id = '<id>'bypassRLS()for site admin cross-tenant operations- Update all API routes and server load functions to use
forTenant(locals.tenant.id)instead of rawprisma
- File:
-
Create a shared helper for tenant-scoped DB access
- File:
src/lib/server/db.ts— addexport function tenantDb(locals: App.Locals)that returnsforTenant(locals.tenant!.id) - All routes call
tenantDb(locals)instead of importingprismadirectly
- File:
-
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
-
Create route guards
- File:
src/lib/server/guards.ts requireAuth(locals)— throws 401 if not logged inrequireTenantAdmin(locals)— throws 403 if not tenant ADMINrequireSiteAdmin(locals)— throws 403 if not SITE_ADMINrequireBoardAccess(locals, boardId, minRole?)— checks membership
- File:
-
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
-
Public board anonymous access
GET /api/boards/[boardId]— ifvisibility === '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)
-
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
- File:
-
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
- File:
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
-
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
- API:
-
Categories (board grouping)
- API:
CRUD /api/categories(tenant-scoped) - Board listing page: group boards by category, filter dropdown
- Board create/edit: optional category selector
- API:
-
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
-
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
dueDatefield (already supported) - Use
date-fns(already installed) for relative time
-
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)
-
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
-
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
-
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/uploadsDocker 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
-
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)
- File:
-
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
- File:
-
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— helperbroadcast(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
-
Client-side event handlers on board view
- File:
src/routes/boards/[boardId]/+page.svelte - Listen for socket events and update local
columnsstate - Handle: new cards appearing, cards moving, columns reordering, deletions
- Ignore events triggered by the current user (deduplicate via socket ID)
- File:
-
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
-
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
-
Activity logging service
- File:
src/lib/server/activity.ts logActivity(boardId, userId, action, metadata?, cardId?)— writes toactivitiestable- 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
- File:
-
Activity feed API
GET /api/boards/[boardId]/activity— paginated, returns recent activity for a boardGET /api/boards/[boardId]/columns/[columnId]/cards/[cardId]/activity— card-specific- Include user name, action description, timestamp, link
-
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"
- File:
-
Card activity in modal
- Show activity timeline in card modal below comments
- Interleave comments and activity chronologically
-
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
notificationstable
- File:
-
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
-
Real-time notification push
- When a notification is created, emit via Socket.IO to the target user
- User's socket joins
user:<userId>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
-
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
-
Global search with PostgreSQL full-text search
- Add
tsvectorcolumn tocardstable (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=<query>— 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
- Add
-
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=<id>(create from template) - Template data stored as JSON in
board_templates.templatecolumn - Board creation UI: "Start from template" option
- Seed system templates in
prisma/seed.ts
-
Archive/unarchive UI
- Board listing: toggle to show archived boards
- Board header: archive/unarchive button (OWNER only)
- Card: archive button in modal (already
archivedfield 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
-
Dark mode
- Tailwind v4 dark mode via
classstrategy - Toggle button in navbar (sun/moon icon)
- Persist preference in localStorage + cookie (for SSR)
- File:
src/lib/stores/theme.ts - Update
app.csswith dark variants for custom colors <html>element getsclass="dark"from layout
- Tailwind v4 dark mode via
-
Keyboard shortcuts
n— new card (in focused column)Escape— close modal/cancel editb— 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)
-
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
-
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
-
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
-
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
- Global error page:
-
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
-
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
- Route:
-
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
- Route:
-
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
- Route:
-
Structured logging
- Replace
console.logwith 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)
- Replace
-
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
-
Docker health check
- Add
HEALTHCHECKto Dockerfile pointing at/api/health - Update
docker-compose.ymlwith healthcheck for app service
- Add
-
Backup documentation
- File:
docs/backup.md - PostgreSQL:
pg_dumpcommand for the kanban database - Uploads volume:
docker cpor volume backup - Restore procedure
- File:
Acceptance Criteria
- Site admin can see all tenants and their stats
- Tenant admin can manage users and tags
/api/healthreturns 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