Allow moving cards to different boards #111

Closed
opened 2026-08-19 02:20:15 +00:00 by Grandiras · 2 comments
Owner

Needs some interop work, as e.g. tags might not exist, but could be handy.

Needs some interop work, as e.g. tags might not exist, but could be handy.
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/308); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-111/run-308.md) — updating every ~5 minutes while the task runs.
Collaborator

Cards can now move between boards. Branch pushed: claude/issue-111.

What it does

The card dialog has a fourth details panel, "Move to another board" — a menu of the other boards you can see, a sentence saying what the move would do, and a Move button.

The card dialog with a destination board picked: the note says which column the card lands in and which tag the other board has no word for

The interop the issue flagged is answered by one sentence, and both halves of it follow the same rule: the card keeps whatever the other board also has a word for.

  • It lands in the column named the same as the one it is leaving (DoingDoing), or in that board's first column if there is no such name.
  • It arrives wearing the target board's own tags of the same names — the same word to anybody reading either board, a different row, and that board's colour.
  • What has no counterpart is left behind, never created: one member moving one card is not the person who decides what words another board files its work under.
  • Matching is by name, trimmed and case-insensitive with exact spelling preferred. It cannot be anything else — the names are ciphertext with a random nonce, so the database can neither join nor compare them.
  • Nothing is dropped quietly: the dialog names the tags that will not come along before the move (disabled until it can say so), and the toast names them after it.
  • Assignees and comments come along untouched — members are the deployment's, not a board's.

How it is built

  • Services/CardMove.cs is the rule itself, pure and static like BoardFilter, so CardMoveTests pins the cases that would cost a board apiece in Playwright (same word in another case, two tags that are one word over there, no such column, no columns at all).
  • CardService.MoveCardToBoardAsync performs it; PlanMoveAsync answers the same question without writing, which is what the sentence on screen is. Both take the actor — this is a card edit (it changes what the card is tagged with and takes it off the board its readers were on), so unlike a drag it follows the card's own author-or-admin rule — and both check the target board against the viewer, since it is the one board id in that service a member puts on the wire.
  • A target board with no columns is refused by name (BoardHasNoColumnsException) with its own copy, because an admin really can delete every column. Both boards are notified; the one being left is the one that would otherwise go stale.
  • Pressing Move saves the dialog first, so a retitle made before deciding to re-file the card goes with it.
  • The calendar's copy of the dialog passes no boards, for the reason it passes IsNew="false".
  • The boards list is loaded when a card is opened, not in ReloadAsync — that runs in every circuit on every notifier event.

Docs updated in the same commit: a new Moving a card to another board section in docs/boards-and-permissions.md (the old "a card cannot be dragged to another board" bullet now says what it actually refuses), the card-dialog section of docs/ui.md, the "nothing is created from it" bullet in docs/calendar.md, the service table in docs/orientation.md, and a changelog line in both locales. New strings are in en and de.

Verified

  • dotnet build Ponente.sln and dotnet test Ponente.sln322 passed, 0 failed (10 of them the new CardMoveTests).
  • The new e2e test in tests/e2e/boards.spec.js — two boards, a tag both have and a tag only one has, a card in Done — passes against a real app instance driven by Playwright, and the screenshot above is that instance.
  • Ran boards.spec.js board.spec.js filter.spec.js share.spec.js alongside it: 47 passed, 2 failed. One is share.spec.js asserting the literal localhost:8090 origin of the compose stack (I ran on 127.0.0.1:5000). The other, boards.spec.js "the nav switches boards", fails identically on an unmodified tree here — I stashed the change and re-ran to confirm — and is the prerender/circuit race docs/testing.md already describes for runs outside the compose stack. No container engine is available in this job, so the compose stack itself could not be used.

No follow-up issues filed.


⏱ 22m 19s · 119 turns · tokens: 208.6k in / 70.9k out (+17735.9k cache-read)
📜 Full session transcript
→ Opened PR: #127

Cards can now move between boards. Branch pushed: **`claude/issue-111`**. ## What it does The card dialog has a fourth details panel, **"Move to another board"** — a menu of the other boards you can see, a sentence saying what the move would do, and a Move button. ![The card dialog with a destination board picked: the note says which column the card lands in and which tag the other board has no word for](https://git.grandiras.net/attachments/85a83cdd-9abe-4593-8d35-fefc3334b57f) The interop the issue flagged is answered by one sentence, and both halves of it follow the same rule: **the card keeps whatever the other board also has a word for.** - It lands in the column named the same as the one it is leaving (`Doing` → `Doing`), or in that board's first column if there is no such name. - It arrives wearing the target board's *own* tags of the same names — the same word to anybody reading either board, a different row, and that board's colour. - What has no counterpart is **left behind, never created**: one member moving one card is not the person who decides what words another board files its work under. - Matching is by name, trimmed and case-insensitive with exact spelling preferred. It cannot be anything else — the names are ciphertext with a random nonce, so the database can neither join nor compare them. - Nothing is dropped quietly: the dialog names the tags that will not come along *before* the move (disabled until it can say so), and the toast names them after it. - Assignees and comments come along untouched — members are the deployment's, not a board's. ## How it is built - **`Services/CardMove.cs`** is the rule itself, pure and static like `BoardFilter`, so `CardMoveTests` pins the cases that would cost a board apiece in Playwright (same word in another case, two tags that are one word over there, no such column, no columns at all). - **`CardService.MoveCardToBoardAsync`** performs it; **`PlanMoveAsync`** answers the same question without writing, which is what the sentence on screen is. Both take the actor — this *is* a card edit (it changes what the card is tagged with and takes it off the board its readers were on), so unlike a drag it follows the card's own author-or-admin rule — and both check the **target** board against the viewer, since it is the one board id in that service a member puts on the wire. - A target board with no columns is refused by name (`BoardHasNoColumnsException`) with its own copy, because an admin really can delete every column. Both boards are notified; the one being *left* is the one that would otherwise go stale. - Pressing Move saves the dialog first, so a retitle made before deciding to re-file the card goes with it. - The calendar's copy of the dialog passes no boards, for the reason it passes `IsNew="false"`. - The boards list is loaded when a card is opened, not in `ReloadAsync` — that runs in every circuit on every notifier event. Docs updated in the same commit: a new *Moving a card to another board* section in `docs/boards-and-permissions.md` (the old "a card cannot be dragged to another board" bullet now says what it actually refuses), the card-dialog section of `docs/ui.md`, the "nothing is created from it" bullet in `docs/calendar.md`, the service table in `docs/orientation.md`, and a changelog line in both locales. New strings are in `en` and `de`. ## Verified - `dotnet build Ponente.sln` and `dotnet test Ponente.sln` — **322 passed, 0 failed** (10 of them the new `CardMoveTests`). - The new e2e test in `tests/e2e/boards.spec.js` — two boards, a tag both have and a tag only one has, a card in `Done` — passes against a real app instance driven by Playwright, and the screenshot above is that instance. - Ran `boards.spec.js board.spec.js filter.spec.js share.spec.js` alongside it: 47 passed, 2 failed. One is `share.spec.js` asserting the literal `localhost:8090` origin of the compose stack (I ran on `127.0.0.1:5000`). The other, `boards.spec.js` "the nav switches boards", fails **identically on an unmodified tree here** — I stashed the change and re-ran to confirm — and is the prerender/circuit race `docs/testing.md` already describes for runs outside the compose stack. No container engine is available in this job, so the compose stack itself could not be used. No follow-up issues filed. --- ⏱ 22m 19s · 119 turns · tokens: 208.6k in / 70.9k out (+17735.9k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-111/run-308.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/127
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#111
No description provided.