Stackline
A team task board you remix into your own Dodil organization and run from the web page or straight from Claude or ChatGPT. Tell Claude "set up a board for the website launch on the 20th, it's me, Ola and Max" and the cards, due dates, owners and blockers are there. Then run it from the chat: "what's late?", "move card 12 to Done", "what is blocking the launch?"
Built on Dodil only: DataK3 for data, an appid user pool for sign-in, Ignite to run it, Dodil Models for the AI. No other services, no API keys.
Judge it in 60 seconds
Live: https://stackline-sirenamc-8080.ignite.dodil.cloud. The front page lists three demo accounts (owner, member, viewer) and their shared password.
- Sign in as the viewer. You see the Launch board but not Ops, and no card moves.
- Sign in as the member. Drag a card to Done, open Activity: who, when, from, to.
- Sign in as the owner. Open Members, change a role. Reset the demo puts everything back for the next person.
- In Claude: add a custom connector with
<live URL>/mcp, OAuth client IDstackline-claude. Sign in as the member and try/plan_my_week, then "what is blocking the launch?"
On your own remix it is the same, minus the demo accounts: sign up, press Open the example board, and every feature has a late card, a blocker chain and history to show.
| The brief asks | Where | Test |
|---|---|---|
| owner / member / viewer; viewer can't edit; only the owner manages members | domain/model.py (require_edit, require_owner) | test_viewer_can_read_but_not_change_anything, test_only_owner_manages_members, test_a_viewer_is_refused_every_change_over_mcp |
| nobody sees boards they're not on | use_cases.py _board, _card | test_strangers_see_nothing_not_even_by_title_or_card_number, test_a_stranger_finds_nothing_over_mcp, test_demo_reset_tells_the_roles_story_and_can_run_again |
| boards, lists, cards, drag and drop | use_cases.py, doors/page.html | test_web_refuses_without_sign_in_and_viewer_cannot_edit |
| every change recorded: who, when, what | _log, table activity | test_every_change_is_logged_with_who_what_before_and_after |
| MCP with real actions, the four sentences | doors/mcp.py (28 tools, 3 prompts) | test_the_four_brief_sentences_work_over_mcp |
| tools are actions, never table access | doors/mcp.py | test_mcp_needs_sign_in_and_lists_action_tools, test_every_tool_says_what_it_does_to_the_board |
| AI checklist, search by meaning, daily summary | use_cases.py, infrastructure/dodil.py | test_ai_checklist_is_only_a_suggestion_until_kept, test_similar_cards_by_meaning_and_embeddings_are_cached, test_daily_summary_counts_facts_itself, tests/live_check.py (11/11 on a real bucket) |
| blocked by, what is blocking the launch | check_blocker, what_is_blocking | test_set_up_board_over_mcp_then_ask_what_blocks_the_launch |
| overdue flagged automatically | Card.is_overdue, red edge on the board | test_example_board_has_late_cards_and_a_blocked_launch |
What's new in this version
The first entry did the brief, but you couldn't touch it without making an account, and an empty board after the remix showed nothing. This one:
- A board from one sentence.
set_up_boardbuilds lists, cards, due dates counted back from the deadline, owners and "blocked by" links in one call. Claude drafts the steps itself and passes them in, so the person sees the plan; on the web page Dodil Models drafts it. A wrong plan writes nothing. - An example board for any empty copy, and a public demo with three roles and a reset.
- Slash commands in Claude:
/standup,/plan_my_week,/unblock(MCP prompts). - Every tool is labelled read-only, changing or removing, so Claude asks before a delete.
- One command after the remix:
scripts/after_remix.sh <repo>. - A calmer look: numbered cards on paper, readable on a phone.
What it does
| People | The board owner invites people as member (can edit) or viewer (read only), changes roles and removes people. Only the owner manages members. Nobody sees a board they're not on: not by id, not by title, not through search, similar cards or a card number. |
| Boards | Boards → lists → cards. New boards start with To do / Doing / Done; add and rename lists. A card has a title, description, assignee, due date, labels and a checklist. Drag and drop between lists and inside a list. |
| History | Each change is one log line: who, when, field, before, after. On the card and per board. |
| Attention | My plate: your open cards that are overdue, due in the next 7 days, or undated. Overdue: every late card across your boards, flagged red on the board. |
| Dependencies | "Blocked by" links inside a board, loops refused, and what is blocking this? follows the whole chain. |
AI (Dodil Models, kimi-k2.7-code) | A first plan for a new board from its goal and deadline, checklist suggestions you accept or not, find cards like this one by meaning (jina-embeddings-v4, cached per card), and a daily stand-up summary of a board. |
Use it from Claude or ChatGPT
The app serves one MCP endpoint at https://<your-app-host>/mcp. Its tools are actions, never
table access, and they run as the person who signed in, so the AI sees exactly that person's
boards.
Try:
- "What's on my plate this week?"
- "Add a card 'Send invoice' to Doing on the Ops board, due Friday, assign it to me."
- "Move card 12 to Done."
- "What's overdue?"
- "What is blocking the launch?"
- "Turn card 7 into a checklist."
- "Give me today's stand-up for Ops."
Tools: set_up_board, start_example_board, my_boards, show_board, my_plate,
overdue_cards, find_cards, card_details, board_activity, add_card, move_card,
assign_card, update_card, delete_card, block_card, unblock_card, what_is_blocking,
suggest_checklist, add_checklist, check_item, similar_cards, board_summary,
create_board, add_list, rename_list, board_members, invite_member, remove_member.
Each carries MCP annotations (read-only, idempotent, destructive).
Prompts (slash commands in Claude): standup <board>, plan_my_week, unblock <card>.
Claude: Settings > Connectors > Add custom connector > paste the /mcp URL. Under
Advanced settings, set the OAuth client ID to stackline-claude: scripts/after_remix.sh
registers it as a first-party client of your pool, so there is no approval step. (Without it,
Claude registers itself and you approve it once: dodil appid client approve <repo>-users <client-id> --yes.)
ChatGPT: add the same URL as a connector (developer mode), client ID stackline-chatgpt.
Remix it
dodil update # CLI 0.1.89 or later
dodil git remix <org>/stackline --to-org <your-org> --name <your-repo>
Keep <your-repo> to 18 characters or less (the bucket is <repo>-data, 25 max).
The remix creates, in your org: the repo, a DataK3 bucket <repo>-data with the tables from
schema.sql, a user pool <repo>-users, and a service account that can use the bucket and
Dodil Models. CI then tests, builds and deploys the app to Ignite.
The one step after the remix
Once the remix run has succeeded, run this once with the Dodil CLI signed in to your org:
scripts/after_remix.sh <your-repo>
It turns on email and password sign-in for the pool if it is off, registers Claude and ChatGPT as first-party clients, prints the app URL and runs the self-check. Then open the app URL, sign up, and press Open the example board. You are its owner.
The deploy registers the app's own sign-in callback on the pool by itself. If its log says the callback was "not registered", run the command that line prints, once.
To invite people: they sign up at the same URL, and the board owner adds their email under Members as member or viewer.
Health check with the app's own credential: dodil ignite invoke <repo> returns the row count
of every table.
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
Against a real bucket and Dodil Models, the main rules end to end (one board owned by made-up
@example.test people): PYTHONPATH=. BUCKET=<repo>-data DODIL_SERVICE_ACCOUNT_ID=... DODIL_SERVICE_ACCOUNT_SECRET=... python ../tests/live_check.py. Use a service account of your
own with k3.editor on the bucket and ignite.model-user.
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), then uvicorn boards.main:app --port 8080.
Layout
app/boards/
domain/model.py Board, BoardList, Card, Role and the rules
application/use_cases.py every action; web and MCP both call these and nothing else
application/ports.py what the use cases need (repository, AI)
infrastructure/dodil.py service-account token, DataK3 connection, Dodil Models
infrastructure/repository.py boards, lists, cards, activity, embeddings in DataK3
doors/web.py + page.html JSON routes and the board page
doors/mcp.py MCP tools for Claude/ChatGPT
doors/identity.py who is calling (the gateway's X-Dodil-User header)
main.py wires it together: /, /api, /mcp, sign-in metadata, /healthz
tests/ the rules through the use cases, and through both doors
scripts/after_remix.sh the one step after a remix
schema.sql the tables, created by the remix
.dodil/remix.yaml what a remix creates
.dodil/deploy.yaml how CI deploys every copy
Design notes: DESIGN.md.
What I'd do next
- Card comments and @mentions, with the same activity log.
- Move the similarity search into DataK3's own vector search once a board has thousands of cards; today the vectors are cached per card and compared in the app, which is plenty for a team board.
- A scheduled morning summary posted to the board, instead of on demand.
- Turn meeting notes or an email into proposed cards that a person confirms.
- Workspace-wide roles on top of board roles, for teams with many boards.
Notes for the Dodil team: what was hard
- The Free plan allows one user pool, so I couldn't remix my own template into a second copy to
rehearse your gate end to end. The bucket and schema part did run: all 7 tables came up.
scripts/after_remix.shcovers the sign-in steps I could test on my own pool. kimi-k2.6took 28 to 40 s on a short daily summary, close to Ignite's 60 s request limit.kimi-k2.7-codeis usually 2 to 7 s, but a 10-step plan still took 24 to 40 s on some days and Free answers 429 after a few calls in a row. So the plan from Claude is drafted by Claude and only the web page asks Dodil Models.dodil data connectprints the session token inside the pg URL. It's easy to paste into a log or a chat by accident.- Remixing the starter gives you the starter's tables, and
schema.sqlruns only when a bucket is created, so changing the schema meant dropping the empty tables and creating mine by hand. - DataK3 runs on DuckDB: a DATE comes back as a timestamp and
atis a reserved word. Neither is in dodil://k3/sql yet.
Licence
MIT. See LICENSE. Started from the Dodil Boards Challenge starter.