damien-kaiser/taskloft

Taskloft — team task boards (owner / member / viewer), card history, my plate, overdue, blockers, AI checklists and summaries, usable from Claude and ChatGPT over MCP.

A remix template · 0 remixes · Remix damien-kaiser/taskloft

README.md

Taskloft

Team task boards you can remix into your own Dodil organization and use from Claude or ChatGPT.

Someone says "I need a board for the team": remix this repo, sign in, invite people. The same board works on the web page (drag and drop) and from your AI over MCP: "what's on my plate this week?", "add a card 'Send invoice' to Doing on the Ops board", "move card 12 to Done", "what's overdue?", "what is blocking the launch?".

Built for the Dodil Boards Challenge. MIT licence.

What it does

PeopleThe owner invites people by email as member (edits) or viewer (reads only), changes roles, removes people (their cards become unassigned). Only the owner manages people. Nobody sees a board they are not on: for them it doesn't exist (404, never 403).
Boards → lists → cardsEach board has its own lists (default To do / Doing / Done; add and rename lists; a list can count as finished). A card has a number (#12), title, description, assignee, due date and labels. Cards move between lists and inside a list by drag and drop.
HistoryEvery change to a card is recorded: who, when, and what changed, field by field (due_date: 2026-11-02 → 2026-11-05). Board activity feed too.
My plate / OverdueCards assigned to me and not finished: overdue first, then due this week, then undated. Overdue cards are flagged automatically everywhere (web and MCP).
Blockers"Card 7 is blocked by card 9". Loops and cross-board links are refused. What is blocking the launch? follows the whole chain and ignores what is already finished.
ChecklistsAdd, tick, untick. Turn this card into a checklist: Dodil Models drafts the steps and adds them.
Daily summaryDodil Models writes a 3–6 line summary of a board from the board's own facts (done, overdue, blocked, what needs attention).
SearchBy words (title, description, labels, assignee), or by meaning: "getting paid" finds "Connect the payment provider" and "Send invoice to ACME". Find cards like this one from any card. Embeddings from Dodil Models (jina-embeddings-v4) in a DataK3 VECTOR column, filtered to the boards you can see; a card is re-embedded only when its text changes.

Remix it (the one command)

dodil git remix damien-kaiser/taskloft --to-org <your-org> --name <your-repo>   # name: 18 characters max

The remix creates the repo, the bucket <repo>-data (tables from schema.sql), the user pool <repo>-users, and the app's service account (DataK3 editor on that bucket + Dodil Models), then CI tests, builds and deploys it to Ignite.

After the remix: the one sign-in step

A remixed user pool starts with no sign-in connection ("connection is not enabled for this pool" on the login page; dodil appid connection list <repo>-users says no connections). Turn on email + password once:

echo '{"require_email_verification":true}' | dodil appid connection add <repo>-users local --config - --enabled

The sign-in callback (https://<app host>/.dodil/auth/callback) is registered on the pool by the deploy itself (git ≥ 1.6.11; the deploy log says "already lists the sign-in callback"). If the log says it was not registered, run the command that line prints, once.

Then open the app, create an account on the pool's page (or invite: dodil appid user create <repo>-users <email>), and create your first board: you are its owner. Invite your team from People on the board; they sign in with that email.

The app's address is the -8080 host shown on the app's page in the console (https://<repo>-<org>-8080.ignite.dodil.cloud).

Use it from Claude or ChatGPT

Add https://<app host>/mcp as a custom connector. It asks you to sign in through the pool (the app publishes its sign-in metadata at /.well-known/oauth-protected-resource). The first time, approve the client once:

dodil appid client list <repo>-users
dodil appid client approve <repo>-users <client-id>

The AI acts as you: it sees exactly the boards you see and can do exactly what your role allows.

You sayTool
what's on my plate this week?my_plate (days=7)
what's overdue?overdue
add a card 'Send invoice' to Doing on the Ops boardadd_card (board by title, list by name)
move card 12 to Donemove_card (card by number)
give #12 to bo@acme.com / take it myselfassign_card
push #12 to next Friday, label it financeupdate_card
#7 can't start before #9 · what is blocking the launch?set_blocked_by · what_is_blocking
turn #12 into a checklist · tick step 2turn_into_checklist · tick_checklist_item
summarise the Ops board for todayboard_summary
find cards like #4 · anything about getting paid?similar_cards · search_by_meaning
who changed #12?card_history
invite / remove / make viewerinvite_member · remove_member (owner only)

Also: my_boards, show_board, search_cards, card_details, board_activity, create_board, add_list, board_members, add_checklist_items, suggest_checklist. Tools are actions (use cases), never table access.

How it's built

Python + FastAPI, light domain-driven design. The web routes and the MCP tools are two thin doors onto the same use cases, so a rule cannot be skipped by using the other door.

app/boards/
  domain/model.py              Board, BoardList, Card, Role and every rule (who may do what,
                               list names, labels, due dates, blocker loops, field-by-field diffs)
  application/use_cases.py     every action: boards, people, lists, cards, my plate, overdue,
                               search, history, blockers, checklists, AI checklist + summary
  application/ports.py         what the use cases need (repository, AI assistant)
  infrastructure/dodil.py      service-account token, DataK3 connection, Dodil Models (chat)
  infrastructure/repository.py DataK3 tables, card-number claims, additive migrations, retries
  doors/web.py                 JSON routes        -> use cases
  doors/mcp.py                 MCP tools          -> the same use cases
  doors/page.py                the board page (one HTML file, no build step, no external assets)
  doors/identity.py            who is calling (the gateway's X-Dodil-User header)
  main.py                      wiring, /mcp, sign-in metadata, /healthz, POST / self-check
tests/test_boards.py           the rules, through the use cases and both doors (13 tests)
schema.sql                     the tables the remix creates
.dodil/remix.yaml              what a remix creates
.dodil/deploy.yaml             how CI deploys every copy
.github/workflows/ci.yml       test -> build -> deploy (Dodil Git CI)

Only Dodil services: DataK3 for all data, the appid pool for sign-in (no login code in the app), Ignite to run it, Dodil Models for AI. Design decisions: NOTES.md.

Run the tests and the app locally

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

Against your real bucket (local only): BUCKET=<repo>-data DODIL_TOKEN=<your token> uvicorn boards.main:app --port 8080, and send X-Dodil-User: {"email": "you@acme.com"} (or set DEV_USER). In production the gateway sets that header and strips any copy a client sends.

POST / (what dodil ignite invoke calls) opens the bucket with the app's own credential and returns a row count per table plus the migration status; /healthz deliberately touches nothing.

What I'd improve next

  • Comments and @mentions on cards; a due-date reminder sent to the assignee.
  • Archive instead of delete for boards, lists and cards (the history keeps everything already).
  • Optimistic concurrency on card edits (send the version; a stale edit gets 409).