Quadro
A team task board for Dodil. Boards, lists and numbered cards on a web page with drag and drop, and the same board inside Claude or ChatGPT through one MCP endpoint. Everyone signs in through the app's Dodil user pool, and every change lands in the board history under their name.
Built by Guilherme Pereira, GHP Solutions. MIT licensed.
What it does
Boards and people
- Boards with ordered lists (
To do,Doing,Doneto start). Lists can be added, renamed, reordered and deleted when empty. Any list can be marked as a done list. - Three roles per board. The owner invites people and changes roles. A member edits cards and lists. A viewer can read everything and change nothing, on the page and over MCP alike.
- People who are not on a board cannot see it, and cannot tell that it exists.
Cards
- Each card has a short reference such as
OPS-12, plus a title, description, assignee, due date and labels. Numbers are never reused, even after a card is archived. - Drag and drop between and within lists. Positions use gaps, so a move rewrites one card.
- Comments, and a checklist written as
- [ ]lines in the description. The boxes can be ticked on the page. - Blocked by: a card can wait on other cards on the same board. Loops are refused. "What is blocking OPS-14?" walks the whole chain and lists the open cards in it.
- Overdue flag: an open card past its due date is flagged on the board, in My work and in the Overdue view. It is computed when the card is read, so nothing needs a scheduled job to stay correct.
- Stale edits are refused. Every card carries a version. If two people edit the same card,
the second save gets
409and the page reloads the card, so nobody overwrites a change they never saw.
History
- Every change records who made it, when, and the before and after of each field (for
example
list: Doing → Doneordue date: none → 2026-11-12). It appears per card and per board.
Dodil Models
- Suggest steps drafts a checklist for a card. Nothing is saved until someone picks the steps to keep.
- Similar cards compares cards by meaning with
jina-embeddings-v4. Vectors are cached per card content in DataK3, and the app falls back to shared words if the model is unavailable. The page says which method answered. - Daily summary gives a short stand-up note for a board. It is written from the board facts (counts per list, overdue, due soon, blocked, last 24 h). If the model does not answer in time, you get the facts themselves, labelled as such.
Ask it from Claude or ChatGPT
The app serves MCP at https://<app host>/mcp. The tools are actions, not tables:
| Ask | Tool |
|---|---|
| "What's on my plate this week?" | my_plate: my open cards, split into overdue, due within 7 days, later |
| "Add a card 'Send invoice' to Doing on the Ops board" | add_card(board="Ops", list="Doing", title="Send invoice") |
| "Move card 12 to Done" | move_card(card="12", list="Done"). 12 works when only one of your boards has a card 12, otherwise use OPS-12 |
| "What's overdue?" | overdue_cards across every board you can see |
| "What is blocking the launch card?" | what_is_blocking |
The full set is 24 tools:
- Read:
my_boards,show_board,card_details,search_cards,board_activity,my_plate,overdue_cards. - Cards:
add_card,update_card,assign_card,move_card,comment_on_card,delete_card. - Dependencies:
block_card,unblock_card,what_is_blocking. - Boards and people:
create_board,add_list,invite_member,remove_member. - Dodil Models:
suggest_checklist,add_checklist,similar_cards,board_summary.
Each tool runs as the signed-in person. The model only sees that person's boards and is held to their role. Its changes show up in the history under the person's name.
Connecting. In Claude, go to Settings → Connectors → Add custom connector. In ChatGPT,
add a connector in developer mode. Paste the /mcp URL in either one. The client registers
itself with the pool and is sent to sign in. New clients arrive pending. The pool admin
approves each one once:
dodil appid client list <repo>-users
dodil appid client approve <repo>-users <client-id>
Remix it
dodil git remix <org>/quadro --to-org <your-org> --name <your-repo> # name: 18 characters or less
The remix creates these resources in your org:
- the repo;
- the DataK3 bucket
<repo>-data, with the tables fromschema.sql; - the user pool
<repo>-users; - a service account with
k3.editoron the bucket andignite.model-user.
CI then tests, builds and deploys to Ignite. The app also creates any missing tables at start-up, so an older bucket keeps working.
After the remix, letting people sign in:
- The deploy registers the app's sign-in callback
(
https://<app host>/.dodil/auth/callback) on the pool by itself. If the deploy log says the callback was not registered, run the command printed on that line, once. - Open the app URL and sign up through the pool. Whoever creates a board owns it and
invites the others by email from Members. If public signup is off on your pool, add
people with
dodil appid user create <repo>-users <email>. - For Claude or ChatGPT, approve the client once, as shown above.
Run it locally
cd app
python -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt pytest
# A seeded board in memory, signed in as you. Nothing to set up.
STORE=memory DEV_USER=you@example.com uvicorn boards.main:app --port 8080
# Or against any Postgres-compatible database.
DATABASE_URL=postgresql://localhost/quadro DEV_USER=you@example.com uvicorn boards.main:app --port 8080
DEV_USER stands in for the gateway's X-Dodil-User header. Use it for local runs only.
With no sign-in, every route except /healthz and the sign-in metadata answers 401.
Tests
cd app && python -m pytest -q ../tests # memory store, no account needed
TEST_DATABASE_URL=postgresql://localhost/quadro_test python -m pytest -q ../tests
The tests cover:
- the domain rules;
- every use case, run against both repositories: in memory, and SQL against a real database;
- the web routes, including
401,403,409and415; - the MCP door: listing tools, calling them as different people, and the four example questions from the brief.
Layout
app/boards/
domain/model.py Board, BoardList, Card, Role and the rules
application/use_cases.py every action; the web page and MCP both call these
application/ports.py what the use cases need: a repository and an assistant
infrastructure/repository.py DataK3 (Postgres wire) repository, pooled connections
infrastructure/memory.py in-memory repository for tests and local runs
infrastructure/dodil.py service-account token, DataK3 connection, Dodil Models
infrastructure/demo.py sample boards for STORE=memory
doors/web.py JSON API + the page (doors/static)
doors/mcp.py MCP tools
doors/identity.py who is calling, from the gateway header
main.py wiring, /mcp, sign-in metadata, /healthz
docs/DESIGN.md design notes and trade-offs
Notes
- Membership is per board. There is no separate workspace or organization level. The org boundary is the Dodil pool itself.
- Archiving a card hides it. Its history stays, and other cards stop waiting on it.
- Model calls can take tens of seconds. The page shows progress, and every model feature has a plain fallback, so a slow model never blocks the board.
- Drag and drop needs a mouse or trackpad. On touch screens, change a card's list from the card itself.
- The SQL repository keeps to the DataK3 SQL rules: no sequences, full-row upserts and
WHEREon every delete. Its tests run against PostgreSQL.