feat(task-board): let a column say what it means - #6718
Merged
Conversation
The board decides where a finished card retires to, instead of every caller writing `"archived"` and hoping the board has one. That literal was a real hole on an org board. The sweep writes through storage, so neither the write-path check nor the foreign key catches it — a card would be filed under a column that does not exist, which is invisible rather than archived. A board with nowhere to retire a card now leaves it in Done, which is wrong-but-visible instead of right-but-gone. `TASK_BOARD_COLUMN_ROLE_SET` is how an org says which of its columns means what. A column mirrored from a tracker is a name and a position; nothing tells us "Code Review" is where review happens or that "Arquivado" retires a card. Two roles today, because two are what automation reads. A column carries at most one and most carry none, which stays the safe default for a column we did not invent. Studio's own lanes are not configurable — they already mean what they say, and `setRole` finds no row to update on a board that has none. Rejects a role on a column the board does not have, for the same reason the automation upsert does: a rule that can never fire still reads as configured.
decocms Bot
pushed a commit
that referenced
this pull request
Aug 28, 2026
PR: #6718 feat(task-board): let a column say what it means Bump type: minor - decocms (apps/api/package.json): 4.299.1 -> 4.300.0 - @decocms/native (apps/native/package.json): 4.299.1 -> 4.300.0 - @decocms/shared (packages/shared/package.json): 0.72.1 -> 0.73.0 Deploy-Scope: server
4 tasks
4 tasks
pedrofrxncx
added a commit
that referenced
this pull request
Aug 29, 2026
Follows #6718. setRole() wrote the new role onto the target column but left it on whichever column held it before, so re-pointing "archived" (or "in_review") to a second column left two columns quietly claiming the same role. archiveColumn()/automationFor() pick the first match by position, so which one actually fires becomes arbitrary and the old column silently keeps acting archived/in_review. Fix: setRole() now clears the role from any other column in the org that holds it, in the same transaction, before writing it to the target column.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this contribution about?
This is the piece @viktormarinho asked for in place of a status mapping: you tell each column what it means, once, instead of maintaining a translation between two vocabularies.
It also closes a hole I flagged in #6716 and would not want anyone to enable the flag over.
The hole
archive-merged.tswrote{ status: "archived" }directly. On a board whose columns are the org's own there is no such column — and nothing catches it:assertBoardHasColumnnever runs;board_column_orgis null for those rows, so the foreign key sleeps.The card would be filed under a column that does not exist: invisible, not archived. The board decides now, and a board with nowhere to retire a card leaves it in Done — wrong-but-visible beats right-but-gone.
Same nullable shape as
automationFor, for the same reason.Roles
TASK_BOARD_COLUMN_ROLE_SETis how an org says which of its columns means what. A column mirrored from a tracker is a name and a position — nothing tells us "Code Review" is where review happens, or that "Arquivado" retires a card.Two roles today (
in_review,archived), because two are what automation actually reads. A column carries at most one, and most carry none — that stays the safe default for a column we did not invent.Studio's own lanes are deliberately not configurable: they already mean what they say, and
setRolefinds no row to update on a board that has no rows, which the tool turns into a clear error rather than a silent no-op.It rejects a role on a column the board does not have, for the same reason
TASK_BOARD_AUTOMATION_UPSERTdoes — a rule that can never fire still reads as configured to whoever set it.How did you verify your code works?
Real-Postgres cases in
board-handler.integration.test.ts, one per board:archived, as it always did;The existing archive-sweep integration tests keep passing — and one of them needed real work rather than a workaround: its context was built with only
taskBoard, and the sweep now asks the board (which asks the org which board it is). I gave the test the storages instead of moving the lookup to dodge it, the same call as in #6710.4200 pass / 72 fail across
apps/api/srcand the board's web tests — the same 72 pre-existing order-dependent failures measured against baseline earlier. Every*.integration.test.tsalso run individually infind | sortorder, the way CI runs them, all green.bun run check0 errors,bun run lintat baseline,knipclean (it caughtCOLUMN_ROLESbeing exported for no one — narrowed),fmt:checkclean.Not verified, flagging honestly:
in_reviewis defined and settable but nothing reads it yet — the review-phase checks still compare to the literal"in_review". It is here because setting it is half of what makes the sync useful, but I want to be plain that it is inert today;archivedis the one that does something.Screenshots/Demonstration
Not applicable — no visible change. Studio's own board answers exactly as before.
How to Test
org_board_columnson and some columns, confirm the sweep leaves finished cards in Done — it has nowhere to retire them.TASK_BOARD_COLUMN_ROLE_SET { columnKey: "<one of theirs>", role: "archived" }, then confirm the sweep retires to that column.role: nulland confirm it stops again.Migration Notes
None. No schema change —
task_board_columns.rolehas existed since #6698 and this is the first thing to write it.Review Checklist
Summary by cubic
Fixes the archive sweep so a finished card is never filed under a column the board doesn't have. The sweep now asks the board where to retire a card instead of writing the literal
"archived"— a board with no column marked as such leaves merged cards in Done (wrong-but-visible) instead of filing them under a nonexistent column (invisible).TASK_BOARD_COLUMN_ROLE_SETto mark one of the org's columns asarchivedorin_review, or clear it withnull.Written for commit 1544edd. Summary will update on new commits.