Shared Kanban task boards in a mobile-first PWA. Create a board, share it with a join code, and track tasks across four fixed columns with priorities and multiple assignees, synced in real time. Each board also carries collaborative notes.
- Boards you create and share with a short, typable join code.
- Fixed columns: Open, To Do, In Progress, Done.
- Tasks with title, description, priority (Low / Medium / High), due date, and one or more assignees.
- Drag and drop between columns and reordering within a column, applied optimistically and synced across members in real time.
- Notes per board, written in a rich-text editor with markdown shortcuts
(type
##for a heading). Members edit the same note together with live cursors in their own colour, and concurrent edits merge without loss, even when two people wrote offline. - Passwordless sign-in by magic link or 6-digit OTP. Self-serve signup is disabled; an operator provisions users.
- Installable PWA that works offline: boards render from cache on a cold offline launch, and task and note edits queue and replay on reconnect.
| Area | Choice |
|---|---|
| Frontend | React 19, TypeScript, Vite, Tailwind CSS 4 |
| UI | shadcn/ui (Base UI), theme Modern Minimal |
| Data / state | TanStack Query, TanStack Router (file-based) |
| Drag & drop | dnd-kit |
| Notes | Tiptap 3 (ProseMirror) with Yjs CRDT sync over Supabase Realtime |
| Offline | react-query-persist-client over an idb-keyval persister |
| Backend | Self-hosted Supabase (Postgres, Auth, Realtime) |
| Tooling | Ultracite (Biome) for lint/format, Vitest, Playwright |
| Deployment | Coolify (static frontend + self-hosted Supabase) |
- Node 22 and pnpm 10.18 (
corepack enableactivates the pinned pnpm) - Docker with Compose v2
The local backend runs as a self-hosted Supabase stack in docker-compose.yml,
the same stack that deploys to production. There is no dependency on the
Supabase CLI's supabase start.
corepack enable
pnpm install
pnpm dev:up # Boot the backend (Postgres, kong, auth, rest, realtime, studio, mailpit) + seed demo data
pnpm dev # Vite dev serverOpen http://localhost:5173. The default apps/web/.env already points the
frontend at the local gateway with the public Supabase demo anon key, so there
is no manual configuration step (copy apps/web/.env.example if it is missing).
Stop the backend with pnpm dev:down (keeps the database volume) or
pnpm dev:reset (wipes it).
| URL | Service |
|---|---|
| http://localhost:5173 | The app (Vite) |
| http://localhost:8000 | Supabase API gateway (kong) |
| http://localhost:8001 | Supabase Studio |
| http://localhost:8025 | Mailpit (captures outgoing mail) |
pnpm dev:up seeds three demo users:
| Role | Board | |
|---|---|---|
alice@dev.local |
owner | Team-Board |
bob@dev.local |
member | Team-Board |
charlie@dev.local |
owner | Charlies Board |
Sign in by entering one of these addresses, then read the magic link or 6-digit
OTP from Mailpit (http://localhost:8025). There are no passwords. To add a
real user, create it in Studio (Authentication → Users) and set its display name
on the public.profiles row; signup is disabled by design.
apps/web/ The Vite PWA
├── src/app/ Router, entry shell, theme, env parsing. No business logic.
│ └── routes/ File-based TanStack routes (routeTree.gen.ts is generated)
├── src/features/<name>/ Vertical slices: components → hooks → api, with a public index.ts
├── src/shared/ Cross-feature primitives (ui/, lib/, hooks/)
└── e2e/ Playwright specs, page objects, and helpers
apps/mcp/ Remote MCP connector server (verifies the caller's JWT, acts as that user)
packages/contracts/ @pinnwand/contracts: generated database types + fixed column constants
supabase/migrations/ Append-only SQL migrations (RLS enabled with each table)
docker/ Dockerfiles, kong + Postgres init config, Caddyfile
docs/ Deployment guide and design specs
Layer boundaries and conventions live in .claude/rules/ and
CLAUDE.md.
# Develop
pnpm dev # Vite dev server on :5173
pnpm dev:up # Boot the backend stack + seed demo data
pnpm dev:down # Stop the stack (keep the db volume)
pnpm dev:reset # Stop the stack and wipe the db volume
pnpm dev:logs # Tail backend logs
pnpm dev:psql # Open a psql shell into the dev database
pnpm seed:dev # Re-seed demo users and board (idempotent)
# Quality
pnpm check # Lint (Ultracite / Biome)
pnpm fix # Auto-fix lint and formatting
pnpm build # Type-check and production build
pnpm test # Vitest unit tests
# Database
pnpm db:migration:new <name> # Create a migration under supabase/migrations/
pnpm db:push # Apply pending migrations to the dev database
pnpm db:reset # Drop and re-apply every migration
pnpm db:types # Regenerate packages/contracts/src/database.ts
# End-to-end
pnpm e2e # Build and run the sealed Playwright stack (docker-compose.e2e.yml)
pnpm e2e:down # Tear the e2e stack down- Unit tests run with Vitest, co-located with the code under test.
- End-to-end tests run with Playwright against a sealed Docker stack
(
docker-compose.e2e.yml) that is isolated from your dev stack.pnpm e2ebuilds the web image, applies migrations, and runs the specs across desktop and mobile browser projects. The development approach is outside-in: an acceptance test describes the behavior first and drives the implementation.
Schema changes are append-only SQL files under supabase/migrations/. Every
table enables row-level security in the same migration that creates it, and
access is scoped through board_members. A one-shot migrate init container
applies pending migrations (supabase db push --include-all) before any service
starts, both locally and in production, so the schema is never stale. After a
migration, regenerate the typed client with pnpm db:types.
Production runs on Coolify from docker-compose.coolify.yml: the static
frontend (served by Caddy) plus the self-hosted Supabase services, fronted by
Coolify's reverse proxy. Required secrets are gated with ${VAR:?} so a missing
value fails the deploy before any container starts. A manual GitHub Actions
workflow (.github/workflows/deploy-coolify.yml) triggers a redeploy.
See docs/deployment.md for secret generation, the full
environment variable list, domains, Studio access, and the first-deploy
checklist.
Read CLAUDE.md for the architecture and invariants, and
.claude/rules/ for the detailed conventions (security,
migrations, TanStack Query, PWA, and more). Lint and formatting run
automatically; keep pnpm check green before opening a change.
MIT © Maik Basel