Skip to content

TM-2: Moving tasks between boards - #54

Open
wdembinski wants to merge 7 commits into
developmentfrom
feat/moving-tasks-between-boards
Open

wdembinski wants to merge 7 commits into
developmentfrom
feat/moving-tasks-between-boards

Conversation

@wdembinski

Copy link
Copy Markdown
Owner
  1. User should be able to move tasks between boards/projects (the same way as it is done in the Atlassian JIRA).
  2. Boards, projects should be the same feature. Currently when creating a task I see I can select a Board and select a Project - this is the same. If not in the code, fix it to be the same.

wdembinski and others added 7 commits September 19, 2026 19:24
`moveTaskToBoard(taskId, toBoardId)` moves a card onto a different board in one
transaction, modelled on `createTicketTx`: a destination that owns tickets always
allocates a FRESH number off its own counter (never the source's old number),
while a keyless destination (Personal, or any board with no prefix) freezes the
key exactly as it was. `epicTaskId`/`milestoneId` are cleared (both name rows
scoped to the source project), `projectTagId` follows the card to its new board,
`"order"` becomes the destination's own next slot, and `task_events`/
`task_activity` are re-pointed so a card's timeline never splits across two
projects.

store.ts has zero vitest coverage (better-sqlite3's addon is built for
Electron's ABI, confirmed failing under plain Node here), so the scenarios live
in scripts/verify-move-task-to-board.mjs, run under ELECTRON_RUN_AS_NODE like
every other store verification script.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`task:setBoard(taskId, boardId)` moves a card onto a different board — the
sibling of `task:setProject`, but where that one only tags a card `setBoard`
actually moves `Task.projectId`. Guards mirror `task:assignAgent`'s style:
refuses a card mid-run, one resting on a plan-driven board (its cards come
from the plan file, not a hand move), and one synced from JIRA or GitHub
(its board is recomputed from its Project tag on every sync, so a move here
would not survive the next poll). The destination just has to exist — no
`ownsBoard` gate, since every project is a valid board now, Personal
included. A no-op if the destination is already the card's own board.
Delegates to `store.moveTaskToBoard` (added in the prior step) and fires
`task:changed` plus `project:tasksChanged` for BOTH the source and
destination boards, so the source drops the card and the destination gains
it on the same tick.

Registered in `IpcApi` and marked `'relay'` in `ipcRelay.ts`, same as
`task:setProject`.

`ipc.ts` has no test file (`registerIpcHandlers` needs a real Electron
app/BrowserWindow), so the handler's guards are mirrored — line for line,
same shape `verify-jira-move.mjs` uses for `task:move` — in a new section of
`verify-move-task-to-board.mjs`, against the real store: the running-card,
plan, JIRA and GitHub refusals, the unknown-destination refusal, and that a
successful move fires exactly the three events (one `task:changed`, two
`project:tasksChanged`) naming the right boards.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
board:scopes (desktop) and its web mirror in BoardScreen.tsx now return
Personal plus every other project, dropping the ownsBoard(project) filter.
A bare repo or a keyless personal-space project is a board too now; its
cards simply carry no ticket key. Plan-driven projects also surface as
boards as a consequence — their plan-step cards remain unmovable per
Phase 2's refusal.

Extends verify-move-task-to-board.mjs with a section mirroring
board:scopes, asserting the plan-driven and keyless projects earlier
sections created (previously dropped by ownsBoard) are now included.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Repurposes TaskDetailsCell's filing "Project" dropdown into a single
"Project / Board" control that calls task:setBoard, removing the old
redundancy between filing a card and moving it between boards. The
control is disabled with an explanatory tooltip for a plan/jira/github
card or while a run is live, mirroring task:setBoard's own guards, and
shows a short "Re-keyed to <newKey>" caption when a move gives the
card a new ticket key. Threaded a boards: BoardScope[] prop down from
TaskDetail.tsx to both the desktop (MyTasks.tsx) and web (BoardScreen.tsx)
call sites, replacing the old projects prop there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Collapse the Add-task dialog's separate "Board" and "Project (optional)"
selects into one "Project / Board" picker: Personal (default) plus every
project. addTaskPlan now routes on the destination's ownsTickets capability
(BoardScope.ownsTickets, newly exposed by board:scopes) rather than on
"is it Personal" — a keyless non-Personal board writes an ordinary card
(task:create, tagged with the board itself), a ticket-owning board writes a
native ticket (ticket:create), regardless of which one is named Personal.

Drops the now-redundant filingProjects/selectFilingProjects plumbing in
MyTasks.tsx and BoardScreen.tsx that only ever fed the dialog's old Project
field — it excluded repo-less ticket projects by construction, so it could
never have reached ticket:create for them anyway.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… end

Adds section 7 to verify-move-task-to-board.mjs: one card, walked through the
exact flow plan step 7 describes — an adhoc card moves onto a freshly created
prefixed board (asserting the fresh key + source:'ticket' from step 2), then
onto a freshly created keyless board (asserting the key freezes at what the
FIRST move gave it, not what it was born with). Sections 1 and 3 each already
covered half of this on separate cards but never chained the two moves on one,
which is the specific regression this section guards. Both hops go through the
setBoard mirror so project:tasksChanged is re-checked for both boards on each
hop, and board:scopes is re-read afterwards to confirm both new boards appear.

Proved live by mutating moveTaskToBoardTx three ways and reverting each time:
inverting the destPrefix guard reddens sections 1-2 (then crashes the run);
freezing with null instead of the old key/number reddens section 3 twice and
section 7's chained-freeze check; dropping the task_events/task_activity
project follow reddens both history checks in sections 1 and 3. Findings
recorded in the script's docstring.

Ran the monorepo gates fresh (turbo --force, no cache): format:check,
typecheck (9/9 tasks, including @tm/web:typecheck against the new `boards`
prop and relay entry), vitest (3720 tests / 222 files), and build (6/6 tasks)
all green. No lint script exists in this repo (no eslint config anywhere) so
that gate has nothing to run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…enter

Step 8 of "moving tasks between boards" — the plan's last step. Updates the
"Project tag vs agent project" entry, written before this branch and now
stale: it didn't say what a board move (task:setBoard/store.moveTaskToBoard)
does to projectId vs projectTagId vs agentProjectId, and nothing warned
against ever merging projectId/projectTagId back into one column.

Records three things for whoever touches this next: the colour stripe keeps
reading projectTagId unchanged because a board move keeps it in sync with
projectId in the same transaction; delegation (agentProjectId) is untouched
by a board move — still written only by Assign Agent, so a card can still
live on Personal while delegated elsewhere; and why the projectId/projectTagId
split can't be collapsed — it's load-bearing for delegation, the JIRA/GitHub
reconcilers' Personal-only fallback (resolveOwningBoardProject), and every
board-scope query. Also updates "My Tasks" to say a card's board is no longer
fixed to Personal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant