Back to work
case study 02

Flowboard

A Kanban board where everyone moves together, live.

No sign-up. Open it in two tabs and watch them sync.

Role
Solo. Design, frontend, data, deploy
Type
Realtime collaboration app
Stack
Next.js 16, React 19, Supabase
Proof
Drag tested end to end on a real Supabase

The problem

Most team boards feel like a shared file. Someone moves a card, you refresh, and you find out an hour later. The work happens together, but you can't see it happen.

I wanted a board that feels like a room: you see who is there, where they point and what they move, as it happens.

Three decisions that made it work

01

Three realtime tools, three jobs

Presence tracks who is online. Broadcast streams cursors and drags. Postgres changes push every saved move to every open tab. Each tool does the one thing it is best at.

presence · broadcast · postgres_changes
02

Order without renumbering

Cards store a fractional position. Dropping a card between two others takes the midpoint, so a move writes one row instead of rewriting the whole column.

(before + after) / 2
03

Rules live with the data

Row Level Security is on every table. Access runs through one database function that allows the public demo board or boards you own.

is_demo or owner_id = auth.uid()
From the repolib/ordering.ts
export function positionForIndex(sorted: { position: number }[], index: number): number {
  const clamped = Math.max(0, Math.min(index, sorted.length));
  const before = clamped > 0 ? sorted[clamped - 1].position : undefined;
  const after = clamped < sorted.length ? sorted[clamped].position : undefined;

  if (before === undefined && after === undefined) return POSITION_GAP;
  if (before === undefined) return after! - POSITION_GAP;
  if (after === undefined) return before + POSITION_GAP;
  return (before + after) / 2;
}

A pure function with its own unit tests. The UI, the database and the realtime echo all agree on order because they all read the same number.

Designed so people stand out

Violet#7C5CFC
Cyan#0EA4BD
Rose#F2477F
Ink#1B2230

The board stays quiet so people can be loud. A neutral canvas and soft cards, with four bright accents saved for the humans on it.

Cards and cursors move on springs, not timers, so a drag feels physical. Everyone gets a friendly name like Swift Otter, no account needed.

Proof, not promises

Unit testedOrdering, the board reducer and presence logic, with Vitest
Real backendPlaywright drags a card on a local Supabase, reloads, checks it stuck
Locked downRow Level Security on every table
CI on every pushTypecheck, lint, tests and build, then browser tests

What I'd build next

  • Private boards with sign-in and invites
  • Undo, plus a rollback when a save fails
  • Throttled cursors for very busy boards

Want your product to feel
this alive?

Let's work together Next project: Encore