A Trello-lite board where every change syncs live across clients. Drag a card in one tab and it moves in every other tab in real time. Built to demonstrate real-time state sync, optimistic UI, and concurrency on a .NET 10 + SignalR + React + SQLite stack.
- Real-time sync — SignalR broadcasts every mutation to all clients viewing the board; open two tabs to watch them stay in lockstep.
- Drag & drop — reorder cards within a column or move them across columns
(
@dnd-kit), with a drag overlay and drop-target highlighting. - Optimistic UI — local state updates instantly on every action; the server's authoritative echo reconciles positions so clients converge.
- Durable — boards, columns, and cards persist to SQLite via EF Core and survive restarts.
- Editing — click a card to edit its title/description or delete it; add cards and columns inline.
- Connection state — live / reconnecting / offline badge, with automatic reconnect + group rejoin.
backend/ .NET 10 minimal API
Domain.cs entities (Board/Column/Card) + DTOs + mapping
KanbanDbContext EF Core + SQLite, cascade deletes
BoardService all mutations; returns authoritative post-change DTOs,
reindexing positions to stay contiguous
BoardHub SignalR: JoinBoard + Create/Edit/Delete/Move card, CreateColumn;
broadcasts patches to the board group
Seeder demo "Product Roadmap" board on first run
frontend/ Vite + React 19 + TypeScript
hooks/useBoard SignalR connection, board state, optimistic actions +
reconciliation (boardState / columnsPatched / cardPatched)
components/Board DnD orchestration (@dnd-kit): drag start/over/end,
cross-column moves, drag overlay
components/… ColumnView, CardView, CardEditor, AddCard, AddColumn
Hub /hubs/board. Client → server: JoinBoard(boardId),
CreateCard(boardId, columnId, title),
MoveCard(boardId, cardId, toColumnId, toIndex),
EditCard(boardId, cardId, title, description),
DeleteCard(boardId, cardId), CreateColumn(boardId, title).
Server → client:
boardState(BoardDto)— full board, sent on joincolumnsPatched(ColumnDto[])— authoritative card lists for affected columnscardPatched(CardDto)— a single card's fields changed
REST: GET /api/health, GET /api/boards, GET /api/boards/{id}.
Backend (http://localhost:5199, creates kanban.db on first run):
cd backend
dotnet runFront end (http://localhost:5173):
cd frontend
npm install
npm run devOpen http://localhost:5173 in two tabs and drag cards to see live sync.
Override the backend URL with VITE_API_BASE if needed.