Claude's One Shot w/ Fable for a Project Manager like Trello
  • Go 49.6%
  • Svelte 28.5%
  • TypeScript 17.6%
  • CSS 2.5%
  • Dockerfile 0.7%
  • Other 1.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Brandon 580429e70b build: Docker image, compose stack, and full documentation
Three-stage Dockerfile (SPA build, CGO-free Go build with the embedweb
tag, alpine runtime with CA certs) whose entrypoint chowns the bind-
mounted /data and drops to an unprivileged user — bind mounts stay the
backup story. Compose maps ./data and reads an optional .env. README
covers the no-credit-card Brevo SMTP setup with SPF/DKIM deliverability
notes, Unsplash keys, live backups, the theme-scheme recipe, and the
complete API reference for external apps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvxAC6h2E7wxjhLXStXAQY
2026-09-09 20:37:26 -04:00
server test: Go integration suite and Vitest frontend tests 2026-09-09 20:33:42 -04:00
web test: Go integration suite and Vitest frontend tests 2026-09-09 20:33:42 -04:00
.env.example chore: scaffold repository 2026-09-09 18:46:09 -04:00
.gitignore chore: scaffold repository 2026-09-09 18:46:09 -04:00
docker-compose.yml build: Docker image, compose stack, and full documentation 2026-09-09 20:37:26 -04:00
docker-entrypoint.sh build: Docker image, compose stack, and full documentation 2026-09-09 20:37:26 -04:00
Dockerfile build: Docker image, compose stack, and full documentation 2026-09-09 20:37:26 -04:00
LICENSE chore: scaffold repository 2026-09-09 18:46:09 -04:00
Makefile build: Docker image, compose stack, and full documentation 2026-09-09 20:37:26 -04:00
README.md build: Docker image, compose stack, and full documentation 2026-09-09 20:37:26 -04:00

Bread

A self-hosted kanban board that stays out of your way. Svelte 5 + Go + SQLite, shipped as a single container. Boards sync live between everyone looking at them; your data is one directory you can copy to back up.

  • Boards with unlimited columns and cards, drag-and-drop for both (with a dedicated touch flow), and an owner-set lock that freezes column structure.
  • Cards carry a title, markdown description, image attachments, and per-board custom properties (text, number, select, checkbox, date).
  • Sharing: invite people by email; every move appears for everyone instantly over WebSockets.
  • Backgrounds: built-in colors, or Unsplash photos with author credit.
  • Two color schemes — Bakery and Porcelain — each with light and dark modes, chosen per account. All colors are CSS tokens in one file, so new schemes are one CSS block away.
  • API-first: everything the UI does is a documented JSON API; personal access tokens let you build external apps against it.

Run it

git clone <this repo> bread && cd bread
cp .env.example .env       # optional; empty works for a local try-out
docker compose up --build

Open http://localhost:8080, create an account, and you have a board. All state lives in ./data (bread.db + uploads/).

Without SMTP configured, Bread prints outgoing email (verification links, invites) to the container log — docker compose logs -f bread — which is fine for trying it out and useless for production.

Configuration

Everything is environment variables, read from .env (never committed — the repo ignores it). See .env.example for the full annotated list. The short version:

Variable What it does
PORT Listen port (default 8080)
DATA_DIR Where SQLite + uploads live (default ./data, /data in Docker)
APP_URL Public URL used inside emails
SECURE_COOKIES true behind HTTPS
SMTP_HOST/PORT/USERNAME/PASSWORD/FROM Any SMTP provider
UNSPLASH_ACCESS_KEY Enables photo backgrounds

Email that doesn't land in spam (and needs no credit card)

Bread speaks plain SMTP, so any provider works. A good free start is Brevo — 300 emails/day, no card required:

  1. Create a Brevo account and, under SMTP & API → SMTP, generate an SMTP key.
  2. Put it in .env:
    SMTP_HOST=smtp-relay.brevo.com
    SMTP_PORT=587
    SMTP_USERNAME=<your brevo login email>
    SMTP_PASSWORD=<the smtp key>
    SMTP_FROM=Bread <bread@yourdomain.com>
    APP_URL=https://bread.yourdomain.com
    
  3. Deliverability: authenticate your sending domain in Brevo (they walk you through adding SPF and DKIM DNS records). This is the part that keeps verification emails out of spam — do it before inviting real people. Sending from a domain you own beats sending from a freemail address.

Bread's emails are deliberately plain text with a single link — the format spam filters distrust least.

Unsplash backgrounds

Create a free app at unsplash.com/developers (no card) and set UNSPLASH_ACCESS_KEY. The key stays on the server; the frontend only talks to Bread's proxy. Photos show author attribution on the board, and selection fires the download event the Unsplash terms require. Without a key, the color backgrounds still work.

Backups

DATA_DIR is the whole story: bread.db (SQLite, WAL mode) plus uploads/.

# Consistent snapshot while the app is running:
sqlite3 data/bread.db ".backup data-backup/bread.db"
cp -r data/uploads data-backup/uploads

Or stop the container and copy data/ wholesale. Restoring is copying it back. For inspection: sqlite3 data/bread.db .tables.

Development

Requirements: Go 1.27+, Node 22+.

make dev-server   # Go API on :8080 (console email driver)
make dev-web      # SvelteKit dev server on :5173, proxies /api
make test         # Go + Vitest suites
make build        # single binary with the SPA embedded -> bin/bread

The repo layout: server/ is the Go module (config, sqlite+migrations, auth, http handlers, websocket hub, embedded SPA), web/ is the SvelteKit SPA (theme tokens in src/app.css, board logic in src/lib/board/).

Adding a color scheme

  1. Add [data-scheme='yours'] and [data-scheme='yours'][data-mode='dark'] blocks in web/src/app.css defining the same token set.
  2. Register it in schemes in web/src/lib/theme.svelte.ts.

That's it — the settings page picks it up, and the server stores any scheme name without needing changes.

API

Base URL /api/v1. Authenticate with the session cookie (browser) or a personal access token created under Settings → API tokens:

curl -H "Authorization: Bearer brd_..." https://your-bread/api/v1/projects

Tokens are shown once and stored hashed. Errors are {"error": "message"} with a fitting status code.

Method & path Purpose
POST /auth/register {email,password,displayName} Create account (starts a session)
POST /auth/login / POST /auth/logout Session in / out
GET /auth/me / PATCH /auth/me Profile incl. themeScheme, themeMode
POST /auth/verify {token} · POST /auth/forgot {email} · POST /auth/reset {token,password} Email flows
GET/POST /tokens · DELETE /tokens/{id} Personal access tokens
GET/POST /projects List / create boards
GET /projects/{id} Whole board: project, columns, cards, propertyDefs, members
PATCH /projects/{id} {name?,isLocked?} · DELETE /projects/{id} Update / delete
POST /projects/{id}/background {type:"color"|"unsplash", value} Set background
GET /projects/{id}/members · POST /projects/{id}/invites {email} · DELETE /projects/{id}/members/{userId} People
POST /invites/accept {token} Join a board from an invite email
POST /projects/{id}/columns {name} · PATCH /columns/{id} · DELETE /columns/{id} Columns (locked boards refuse create/delete)
POST /columns/{id}/move {index} Reorder (refused when locked)
POST /columns/{id}/cards {title} Create card
PATCH /cards/{id} {title?,description?,properties?} · DELETE /cards/{id} Update / delete card
POST /cards/{id}/move {toColumnId,index} Move card
POST /projects/{id}/properties {name,kind,options?} · PATCH /properties/{id} · DELETE /properties/{id} Custom properties
POST /cards/{id}/attachments (multipart file) · GET/DELETE /attachments/{id} Images (PNG/JPEG/GIF/WebP ≤ 10 MB)
GET /unsplash/search?q= · POST /unsplash/track-download Background photos
GET /ws?project={id} WebSocket of board events (?token= for PATs)

index in move calls counts the target list without the moving item. Realtime events are {type, projectId, originId, actorId, payload} where type is e.g. card.moved, column.created, project.updated — send an X-Bread-Origin header with your mutations and skip events echoing your own originId.

Security notes

Passwords are argon2id with per-user salts; sessions, API tokens, and email tokens are stored as SHA-256 digests; login timing doesn't reveal whether an account exists; attachments are content-sniffed (SVG rejected) and served auth-checked with nosniff; board ids return 404 rather than 403 to non-members.

License

MIT — see LICENSE.