Driftwood: An Open Source Design Research Platform for Web Apps

Software drifts.
Driftwood catches it.

A small, self-contained, config-driven Kanban I designed and built end-to-end to run a real beta test — the shore patrol for Project Lookout. This is what it does, how it’s built, and the thinking underneath.

Role
Developer · Designer · Project manager
Year
2026
Stack
FastAPI · React · Cloud Run
Assist
Claude Code (Opus 4.8)
Live at
cmd-z.com

One Docker image

Backend, built frontend, and the guide — served from a single container.

One config file

Columns, areas, types, phases — retarget the whole board without touching code.

One endpoint

POST /api/ingest and reports drift in with page, screenshot & console errors.

Zero seat licenses

Self-hosted, yours to fork — no per-tester bill, ever.

1triage Stream
5board lanes
2idea drawers
14system areas
3views: Board · List · Table
7-dayauto-Backlog sweep
throwaway demo sandboxes
Why this thing exists

I was running a beta out of a Google Sheet. It wasn’t going well.

My “bug tracker” was a spreadsheet everyone ignored and a Slack channel that swallowed every report by Tuesday. I didn’t want Jira. I wanted one page a non-technical tester could open, point at what’s wrong, and get on with their day — and one board I could actually run the fix from.

So that’s all Driftwood is: The Stream for raw reports, a board with real working lanes, and two drawers for the wild ideas that always show up and never fit. No sprints, no story points, no forty-field forms. It runs from one container and one config file — and the whole thing is built on a single idea: software drifts. Every release knocks pieces loose; they float downstream, and someone has to sight them, reel them in, bring them ashore, and build them back into the structure they came from.

“Sight it. Reel it in. Bring it ashore. Build it back in. If a tool needs a manual to do that, it’s the wrong tool.”
Stephen Walter · the design brief, in one line
One loop, four beats

Follow a piece downstream

Everything in Driftwood is one of these four beats. It’s drifting on its own — tap any stop to take the wheel.

Stop 1 of 4 · The Stream

Sight it

Every release shakes something loose — a rotted link, a number that lies, a label that wandered off. It drifts in here, raw. Your only job is to notice, and say so.

Not a mockup — the running app

See it running

Real screenshots, straight from the live board — the same coastal design system this page is dressed in.

cmd-z.com/board
The Driftwood board — Bugs, Chores, In Progress and Completed lanes, each with real cards, area chips and priority pills
The Board. Drag-and-drop working lanes only — Bugs → Chores → In Progress → Completed → Backlog. Every card carries a type, a system area (MATCH, INFRA, TRUST…), a priority and its age. Drop one in Completed and it’s celebrated — brought ashore.
cmd-z.com/stream
The Stream — raw feedback reports, each with a steer-it-to panel
The Stream. Raw reports drift in uncategorized. Each gets a “steer it to…” panel — Bug, Chore, Feature or Bouncy House, plus area and priority — and one tap hauls it onto the board.
cmd-z.com/home
The home dashboard — Driftwood board stats over a live Project Lookout analytics layer
The home dashboard. Board status at a glance, over a live Project Lookout analytics layer. Note the “illustrative — synthetic” badge: the honesty gate labels any figure not wired to a real source instead of faking it.
Everything, nothing extra

What’s in the tackle box

Six surfaces, each one a beat in the drift cycle — and not one screen more than the job needs.

The Stream

A calm place to sight raw reports as they float by — never a board column. Look at each one and steer it: Bug, Chore, Feature, or Bouncy House.

The Board

Just the working lanes: Bugs → Chores → In Progress → Completed → Backlog. Drop a card in Completed and the whole thing celebrates. Brought ashore.

Idea drawers

New Features and the Bouncy House live off to the side in slide-out drawers, so wild ideas never clutter the board — but they never get lost either.

Field Guide

A whole interactive testing playbook baked in — the drift cycle, a first-patrol checklist, a severity quiz you can actually flunk, and the golden rules.

Feedback ingest

One little POST turns a raw report — message, page, screenshot, browser gunk — into a Stream card ready to triage. It even de-dupes the retries for you.

Config-driven

Columns, areas, types, priorities, phases — all in one file. Point it at a different product and the whole board becomes theirs. No code surgery required.

By the numbers

A small tool, honestly measured

Designed, built and shipped solo, end to end — and already running the live beta it was made for.

7,269lines of code, one repo
32API endpoints (FastAPI)
22React views & components
14hand-built animations, no UI kit
1Docker image → one-command deploy
$0per-tester cost, self-hosted
Your turn — a real interaction, lifted straight from the app

Fish one out of the Stream

Three raw reports just drifted in. Give each a name and steer it — watch it fling itself onto the board. This is the real triage move, shrunk down.

The Stream3 adrift
IN-014feedback/app

Loved the morning digest — but the “Highway Guardrail Replacement” at the top isn’t painting work.

from owner@roxburypainting

Steer it to…
Where they land
Bugs0
Chores0
New Features0
Bouncy House0

0 of 3 sighted & steered

The rules I designed against

Six principles, held on purpose

Every screen, endpoint and animation had to answer to these. They’re why the thing feels calm instead of clever.

01

Opinionated on purpose

A Stream, a board, two drawers, one guide. It isn’t trying to run your company — just your beta — so it says no to nearly everything a “real” PM tool would add.

02

One metaphor, all the way down

Software drifts, so every surface is a beat in the drift cycle and the whole design system is coastal — warm paper, river blue, tide lines, a bobbing piece of driftwood. The theme is the mental model.

03

Motion is superficial, never load-bearing

Content is always in the DOM; reveal, parallax and ripples are decoration you can switch off. Every animation is prefers-reduced-motion- and print-safe.

04

Honest or omitted

Never present a synthetic number as live. Placeholder data is labelled “illustrative — synthetic”; the live views only include figures with a real source. A tile with no honest number is dropped, not faked.

05

Config over code

One file — columns, areas, card types, phases — retargets the entire board to a new product. Driftwood ships pointed at Project Lookout as a worked example, with zero hard dependency on it.

06

Tenant-scope everything

Every row carries a tenant; every query is scoped by it. Cross-tenant reads 404 instead of leaking. Reads never mutate — the stats endpoint won’t run the sweep that the board endpoint owns.

The palette, verbatim
sand#F7F5F0 ink#2A2E33 sea#2C74B3 sea-deep#1E5687 wood#8C6F52 amber#C7862B rose#B5462F green#2E7D53 purple#6B4E9E
Under the hull

How it’s built

Boring on purpose where it counts, so the interesting parts stay small. One repo, one image, one database.

Backend

Framework
Python 3.11 · FastAPI · SQLAlchemy 2.x.
Data
SQLite in dev, PostgreSQL (Cloud SQL) in prod — multi-tenant on one DB, every row and query tenant-scoped.
Auth
bcrypt + a JWT in an httpOnly cookie. Roles admin / dev, plus an orthogonal engineer capability that gates the Ops Runbook.

Frontend

Framework
React 18 · Vite · Tailwind · react-router · @dnd-kit for drag-and-drop.
Shape
A single-page app served by FastAPI itself — no separate host.
Design system
A tiny set of palette tokens + hand-rolled dw-* keyframes. SVG/CSS charts, no chart library.

Schema & seed

No migration tool
create_all makes missing tables; idempotent helpers add new columns on upgrade. Small surface, no Alembic.
Seeding
First-run + idempotent-upgrade seeding of an admin, a sample board, and example experiments — guarded so a default-password admin never lands in prod.

Deploy

Build
Docker multi-stage: Node builds the React app, Python serves it. The dist/ is built in the image — nothing committed.
Run
Cloud Run (us-east4) on durable Cloud SQL Postgres, secrets in Secret Manager, custom domain cmd-z.com.

The path a report walks

The board is the downstream half of the app’s own feedback machinery. One POST, and a raw report becomes a card you can steer.

App feedback
A tester taps “Send feedback” — page, URL, screenshot & console errors attach automatically.
POST /api/ingest
Bearer-token endpoint. De-dupes on external_id so retries don’t double-file.
The Stream
Lands uncategorized. A human sights it and steers: Bug / Chore / Feature / Idea.
The Board → Completed
Owner, priority, lane. Idle > 7 days → auto-Backlog. Shipped → ashore.

Live data  — The home dashboard shows real, anonymized Project Lookout metrics without ever touching Lookout’s raw tables. Driftwood reads two purpose-built read-only SQL views as a locked-down role, with a 4-second timeout that falls back to a clearly-labelled placeholder on any error. Never a 500, never half-real data — the badge tells you which you’re looking at.

How it got made

Development strategy

A handful of bets that kept a solo build shippable — and reusable — from the first commit.

Config-first from commit one

The board config was the very first file. Building against Lookout as a “worked example” — never a dependency — forced every feature to stay retargetable.

One container, one command

Multi-stage Docker → a single Cloud Run service. No orchestration, no separate frontend host, no CDN to invalidate. Deploy is one line of gcloud.

No migration tool, on purpose

Idempotent bootstrap adds missing tables and columns on boot. For a small schema that upgrades in place, it’s less machinery to break than a migration framework.

Fast checks over heavy suites

FastAPI’s TestClient for quick backend smoke tests; the startup seed is idempotent so tests and cold starts share one honest path.

Dogfooded on a live beta

Driftwood runs the actual Project Lookout beta. Every rough edge in triage got felt first-hand, then filed into its own Stream — the tool tests itself.

Zero-friction evaluation

A one-tap live demo pours a throwaway tenant from a scrubbed snapshot and signs you in — real drag-and-drop, auto-reaped. Nobody has to take my word for it.

The knots worth untying

Challenges & the calls I made

The parts that took real thought — mostly about staying honest, isolated, and safe to ship.

Real data without ever lying

Showing live Lookout metrics meant a decoupled contract: read-only views, a locked-down role that can’t see raw tables, a 4s timeout, and a labelled placeholder fallback. “Live” vs “illustrative” is always on the badge.

Multi-tenant on a single DB

One board is real; demo sandboxes are throwaway tenants on the same database. Isolation is a tenant column on every row plus a scope on every query — cross-tenant reads must 404, not leak.

Evolving schema without migrations

Trading Alembic for idempotent column-adds keeps the surface tiny, but every new column has to be additive and safe to run on every boot. The discipline lives in code review, not a tool.

Alive, but accessible

The river should feel like it’s moving without ever hiding content or fighting a screen reader. So motion is layered on top of a fully-static DOM and disappears under reduced-motion and in print.

Shipping into a shared project

Driftwood shares a Cloud SQL instance and GCP project with the production Lookout service. Deploys are a scoped, single-service source-deploy — verified on the real domain — so the sibling app is never at risk.

Keeping GETs pure

The auto-Backlog sweep mutates, so it lives only on the board fetch that owns it — never on the stats endpoint. A read should never quietly rewrite the board underneath you.

Outward-in

Roll-out & deployment

The beta widens in four phases — from the person who built it to the members it serves — each with an honest exit gate.

1

In-House

solo dogfoodOne full shakedown

Walk every happy path a member or staffer will walk, then deliberately attack the historically-fragile edges: matching honesty, links, timezones, mobile, light mode, EN/ES, races, empty states.

Exit: every check green or filed as a card · zero open trust breaks · one supervised deep sweep done.

2

Alpha — with the client

2–3 staff, me in the roomGuided sessions

Validate the admin console, the member-intake flow, and the digest content with the real client — and name & route the data gate before any member arrives.

Exit: staff run it unaided · the pool feels relevant to their trades · the data gate is named & routed.

3

Beta 1 — 10 members

10 real business owners~15–20 min/day, ~2 weeks

The headline test. Real owners, in their own trades, judge whether “best fits” name work they could win — and whether a non-technical owner gets value in ~10 minutes on a phone.

Exit: best fits read “mostly mine” across trades · 10-minute onboarding · zero trust breaks.

4

Beta 2 — 50 members

the operation industrializesWeekly cadence

Scale, a weekly cadence, a tiering signal, and reliability at volume. Fifty people generate a real queue — and this board becomes the single source of truth for it.

Exit: relevance-miss reports trending down · open P1s under the ceiling · a digest open-rate baseline.

The deploy, in one line
# plain source deploy — preserves env, secrets, Cloud SQL & scaling; only the image changes
gcloud run deploy driftwood --source . \
  --project lookout-becma-beta --region us-east4

Always scoped to the right project — a bare deploy would mint a stray service on ephemeral SQLite that the real domain never points at. Verification happens on https://cmd-z.com, not the run.app URL, with a confirm that the sibling projectlookout.app is unaffected. Rollback is a one-command traffic shift back to the previous revision.

Driftwood

A little board for a leaky beta. Sight it, reel it in, bring it ashore, build it back in — and let the tool wash clean on its own.

Weird Machine

Designed & developed by Stephen Walter with Claude Code · 2026 · fork it freely on GitHub

Previous
Previous

Project Lookout: A Platform for Black Economic Mobility

Next
Next

The Community AI Project