theaio/theaio-boards

Laneway, team task boards you can run from Claude or ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 0 remixes · Remix theaio/theaio-boards

README.md

Laneway

Team task boards you can run from Claude or ChatGPT. Boards hold lists, lists hold cards, and every card has a short number ("card 12"), an assignee, a due date, labels, a full change history and optional "blocked by" links. The same actions work on a quiet web page (drag and drop between lists) and over MCP, so you can say "what's on my plate this week?" to your assistant and get a real answer from your boards.

Built on Dodil: data in DataK3, sign-in through an appid user pool, running on Ignite, AI through Dodil Models. Remix it and it is yours. An entry for the Dodil Boards Challenge.

What it does

  • People and roles. The owner invites people as member (can edit) or viewer (read only), changes roles and removes people. Everyone on a board can see who is on it. Nobody sees a board they are not on: it answers "not found", on the page and over MCP.
  • Boards → lists → cards. A new board starts with To do / Doing / Done. Lists can be added, renamed and reordered; the last list counts as done. Cards have a title, description, assignee (an owner or member of the board), due date, labels and a position, and move between lists by drag and drop or by number.
  • Card numbers. Each card gets the next number on its board, assigned without database sequences. Refer to a card by number, id or title.
  • History. Every change is recorded: who, when, which field, old and new value. Comments live in the same timeline.
  • Overdue cards are flagged on the board, in My cards and in MCP answers.
  • Blocked by links between cards, with loops refused, and "what is blocking the launch?" for a card or a whole board.
  • AI (Dodil Models): a suggested checklist for a card, find similar cards by meaning (jina-embeddings-v4 vectors in DataK3, ranked with pgvector <=>), and a short daily board summary.

Use it from Claude or ChatGPT (MCP)

The app serves one MCP endpoint at https://<your-app-host>/mcp.

  1. Add that URL as a custom connector in Claude or ChatGPT.
  2. You are sent to your pool's sign-in page. Sign in as yourself: the assistant acts as you and sees only your boards.
  3. The first time, the client arrives pending. The org admin approves it once: dodil appid client list <repo>-users, then dodil appid client approve <repo>-users <client-id>.

Then just ask:

You sayThe assistant calls
What's on my plate this week?my_cards(within_days=7)
Add a card 'Send invoice' to Doing on the Ops boardadd_card(board="Ops", title="Send invoice", list_name="Doing")
Move card 12 to Donemove_card(card="12", to_list="Done")
What's overdue?list_overdue()
Assign card 7 to me, due Fridayassign_card, set_due_date
What is blocking the launch?what_is_blocking("launch")
Invite sam@acme.com to Ops as a viewerinvite_member (owner only)
Is there already a card like card 4?find_similar_cards
Summarise the Ops board for todayboard_summary

All tools: my_boards, show_board, create_board, add_list, rename_list, move_list, add_card, show_card, move_card, assign_card, set_due_date, label_card, describe_card, rename_card, comment_card, card_history, my_cards, list_overdue, invite_member, set_member_role, remove_member, block_card, unblock_card, what_is_blocking, suggest_checklist, find_similar_cards, board_summary. They are actions, never table access, and they call exactly the same use cases as the web page. A refusal comes back as a sentence a person can read ("Not allowed: viewers can look but can't change the board; ask the owner.").

Remix it

dodil update                     # CLI 0.1.89 or later
dodil git remix <org>/<repo> --to-org <your-org> --name <your-repo>

Keep <your-repo> to 18 characters or less: the remix creates a DataK3 bucket <your-repo>-data (25 characters at most), a user pool <your-repo>-users, and a service account with access to the bucket and to Dodil Models. The bucket gets its tables from schema.sql; CI tests, builds and deploys the copy.

After the remix: let people sign in

The one step: the app's sign-in callback (https://<app host>/.dodil/auth/callback) must be on the pool. The deploy registers it itself (Dodil Git 1.6.11 and later). Once the remix run has succeeded, in the org it went into:

  1. If the deploy log says the callback was "not registered", run the command that line prints, once.
  2. Let the first people in. A new pool allows public signup by default; if it is off, invite them: dodil appid user create <repo>-users <email> --roles member (no --password: they set one through the forgot-password flow).
  3. Signing in through Claude.ai or another MCP client: approve the client once with dodil appid client list <repo>-users, then dodil appid client approve <repo>-users <client-id>.

Then open the app, create a board, and invite your team by email from People. They see the board as soon as they sign in with that email.

Upgrading a deployment of the original starter

A bucket created by the starter already has boards, memberships, cards and activity. Run migrations/001_board_upgrade.sql once against it (Postgres wire; dodil data connect <repo>-data prints the endpoint). It only adds columns and new tables. Old boards get their list rows, and old cards their numbers, the first time the app opens them.

Run it locally

cd app
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt pytest
python -m pytest -q ../tests      # no Dodil account needed: fakes for data and AI

The tests cover the rules through the use cases, both doors (web status codes and MCP tool errors), and the repository's real SQL plus the upgrade migration against an in-memory SQLite database standing in for DataK3.

To run the server against your remixed bucket, set BUCKET, DODIL_SERVICE_ACCOUNT_ID, DODIL_SERVICE_ACCOUNT_SECRET and DEV_USER=you@example.com (acts as that person without the gateway; local only; never set it in a deployment), then:

uvicorn boards.main:app --port 8080     # open http://localhost:8080

Optional env: CHAT_MODEL (default kimi-k2.6), EMBED_MODEL (default jina-embeddings-v4), MODELS_BASE, PG_HOST.

What's inside

app/boards/
  domain/model.py              Board, BoardList, Card, roles and every rule
  application/use_cases.py     every action; visibility enforced in one place
  application/ports.py         what the use cases need (repository, AI assistant)
  infrastructure/repository.py DataK3 tables: keyed upserts, plain-INSERT number claims
  infrastructure/dodil.py      service-account token, DataK3 connection, Dodil Models
  doors/web.py + page.html     JSON routes and the page  -> call the use cases
  doors/mcp.py                 MCP tools                 -> call the same use cases
  doors/identity.py            who is calling (the gateway's X-Dodil-User header)
  main.py                      wires it together, /mcp, sign-in metadata, /healthz
tests/                         domain, use cases, both doors, schema + SQL
schema.sql                     the full schema, created by the remix
migrations/001_board_upgrade.sql  starter schema -> this one
.dodil/remix.yaml              what a remix creates
.dodil/deploy.yaml             how CI deploys every copy
DESIGN.md                      why it is built this way

What I'd improve

  • Archive and delete for cards and lists (today a finished card stays in the last list).
  • Fewer round trips: one connection per request and a repository query for "my cards" instead of walking each visible board.
  • Optimistic versions on cards, so two people editing the same card get "changed since you opened it" instead of last-write-wins.
  • A POST / self-check that opens the bucket with the app's own credential and reports row counts, for dodil ignite invoke.
  • Notifications (assigned to you, due tomorrow) and checklists as real items instead of text in the description.
  • Verify the pgvector path and embedding size against a live bucket (it is covered offline by fakes only).

Licence

MIT. See LICENSE.