sirenamc/stackline

Stackline, a team task board you run from the web or from Claude/ChatGPT. FastAPI + DataK3 + appid + Models + MCP.

A remix template · 0 remixes · Remix sirenamc/stackline

README.md

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.

  1. Sign in as the viewer. You see the Launch board but not Ops, and no card moves.
  2. Sign in as the member. Drag a card to Done, open Activity: who, when, from, to.
  3. Sign in as the owner. Open Members, change a role. Reset the demo puts everything back for the next person.
  4. In Claude: add a custom connector with <live URL>/mcp, OAuth client ID stackline-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 asksWhereTest
owner / member / viewer; viewer can't edit; only the owner manages membersdomain/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 onuse_cases.py _board, _cardtest_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 dropuse_cases.py, doors/page.htmltest_web_refuses_without_sign_in_and_viewer_cannot_edit
every change recorded: who, when, what_log, table activitytest_every_change_is_logged_with_who_what_before_and_after
MCP with real actions, the four sentencesdoors/mcp.py (28 tools, 3 prompts)test_the_four_brief_sentences_work_over_mcp
tools are actions, never table accessdoors/mcp.pytest_mcp_needs_sign_in_and_lists_action_tools, test_every_tool_says_what_it_does_to_the_board
AI checklist, search by meaning, daily summaryuse_cases.py, infrastructure/dodil.pytest_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 launchcheck_blocker, what_is_blockingtest_set_up_board_over_mcp_then_ask_what_blocks_the_launch
overdue flagged automaticallyCard.is_overdue, red edge on the boardtest_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_board builds 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

PeopleThe 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.
BoardsBoards → 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.
HistoryEach change is one log line: who, when, field, before, after. On the card and per board.
AttentionMy 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

  1. 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.sh covers the sign-in steps I could test on my own pool.
  2. kimi-k2.6 took 28 to 40 s on a short daily summary, close to Ignite's 60 s request limit. kimi-k2.7-code is 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.
  3. dodil data connect prints the session token inside the pg URL. It's easy to paste into a log or a chat by accident.
  4. Remixing the starter gives you the starter's tables, and schema.sql runs only when a bucket is created, so changing the schema meant dropping the empty tables and creating mine by hand.
  5. DataK3 runs on DuckDB: a DATE comes back as a timestamp and at is a reserved word. Neither is in dodil://k3/sql yet.

Licence

MIT. See LICENSE. Started from the Dodil Boards Challenge starter.