BACK-604 - Allow explicit unassign in CLI and TUI when defaultAssignee is set - #880
Merged
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d272dec3a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
MrLesk
added a commit
that referenced
this pull request
Aug 9, 2026
…ask (#886) ## Summary Since BACK-604 (#880) the web create modal omits the assignee field when the chip input is blank, so `defaultAssignee` still applies — which left the create form unable to express "explicitly unassigned" while a default is configured. Edit mode could already do it via an explicit empty list. The create form now pre-fills the assignee field with the configured `defaultAssignee` as ordinary removable chips: - chips left alone → those values are sent → the default applies - chips removed → explicit `assignee: []` → unassigned - a name typed → that name is sent - no `defaultAssignee` configured → blank field, field omitted, unchanged No new UI, controls, or copy: the existing `ChipInput` renders the prefilled values exactly as it renders any other chips. Edit mode is untouched — an opened task always shows its own assignees, and clearing them still sends an explicit empty list. ## Why this payload shape The create payload sends the assignee list explicitly whenever a default was prefilled or the user typed something, and omits the field only when no default is configured and the field is blank. That keeps the three states unambiguous at the payload level (`[]` = unassigned, `[names]` = explicit, absent = the project has no default), and the created task matches exactly what the form showed even if the config changed between page load and submit. `absent` vs `[]` is already honored by `createTaskFromInput`, so no server or core change was needed. ## Details - `defaultAssignee` reaches the client on the existing `GET /api/config` response (it returns the whole `BacklogConfig`), so `App.tsx` just passes `config?.defaultAssignee` down like `availablePriorities` — no new endpoint. Passing it unwrapped keeps the prop identity stable so the modal's reset effect does not re-run on every `App` render. - The reset effect takes the memoized default, so a config that lands after the modal mounted still prefills, while the existing `preserveDirtyRefreshValue` keeps a user's edit — including a removal — across the config refresh `App` triggers on every reload. - `hasCreateModeEntries` now compares the assignee against the prefilled default instead of `length > 0`, so a pristine prefilled create form is not treated as unsaved work while a removal still is. ## Test plan New `src/test/web-task-details-modal-default-assignee.test.tsx` renders the modal and captures the submitted payload for each state: - default applied when the prefilled chips are left alone - explicit `[]` once every prefilled chip is removed - explicit name when the default is replaced - field omitted for a project with no default and a blank input, and still sent when typed there - a removal survives a refresh that re-renders the open form with a fresh config array - a late-arriving config prefills an untouched open form - edit-mode guard: an opened task shows its own assignees (not the default) and clearing still sends `[]` `bunx tsc --noEmit` clean, `bun run check .` clean, full `bun run test` green (2150 pass, 6 skip, 0 fail). Task: BACK-614
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.
Problem
With
defaultAssigneeconfigured there was no way to create or edit a task so that it endsup unassigned.
-a ""was parsed byparseDelimitedStringList, which collapses a blankvalue to
undefined, so an explicitly empty-awas indistinguishable from an omitted one:task create "T" -a ""still got the configured default, andtask edit T -a ""was asilent no-op.
The behaviour that was assumed to already exist on the other surfaces turned out to be
partly missing too (verified live before the fix):
task create/draft create-a ""task edit -a ""task_createassignee: []task_editassignee: []buildTaskUpdateInputrannormalizeStringList, which drops[]PUT /api/tasks/:idassignee: []POST /api/tasksassignee: []So
core.editTaskOrDraftalready honoured an explicit empty list, but only the Web editpayload could actually reach it, and
createTaskFromInputtreated an empty list as absenton every surface.
Fix
One rule, applied in the shared core path: an absent assignee means "no opinion" (the
configured
defaultAssigneeapplies on create, the existing value is kept on edit) andan explicit empty assignee means "unassigned".
src/core/backlog.ts—createTaskFromInputkeys the default offinput.assignee === undefinedinstead of "the normalized list is empty".
src/utils/task-builders.ts— newparseClearableStringListpreserves the absent-vs-explicit-emptydistinction coming out of Commander (
undefinedwhen the flag is absent,[]when it is presentwith only blank values).
src/cli.ts—task create,draft createandtask edituse it for-a;task editnow setsthe field even when the parsed list is empty.
-a someoneis unchanged.src/utils/task-edit-builder.ts— assignee resolves through the existingsanitizeClearableStringArrayhelper already used for dependencies/references/documentation, whichfixes MCP
task_editin the same place as the CLI.src/web/components/TaskDetailsModal.tsx— the create payload omitsassigneewhen the chip inputis blank. The modal always sent
assignee: [], so honouring the explicit empty at core wouldotherwise have silently disabled
defaultAssigneefor every Web-created task. A blank field oncreate now means "no opinion", exactly like an omitted
-a; Web edit still sends[]and clears.task_create/task_editschema descriptions,ADVANCED-CONFIG.mdand theshipped
task-creationinstructions document the empty-value convention.No new flags: the empty value works fine through Commander, so there is no
--clear-assignee.Not done: the TUI acceptance criterion
AC #3 ("The TUI can clear the assignee of a task") is left unchecked because the TUI has no
assignee editing surface to extend:
src/ui/components/task-composer.ts) has only title, description,status, type and priority;
src/ui/board.ts,src/ui/task-viewer-with-search.ts→core.editTaskInTui)shells out to
$EDITORon the task markdown, so the only TUI route to an assignee today is editingthe frontmatter by hand;
assigneereference undersrc/ui/is display or filter-only.The interactive assignee prompts that do exist are the clack CLI wizards in
src/commands/task-wizard.ts(task create/task editwith no flags in a TTY), not the TUI. Theedit wizard pre-fills the current assignees and already clears them when the field is blanked, so
interactive CLI clearing works today. Adding a TUI assignee field would be a new UI surface, so it is
left for a product decision.
Evidence
bunx tsc --noEmitclean,bun run check .clean, fullbun run testgreen.Tests added:
src/test/core.test.ts— explicit empty assignee overridesdefaultAssigneefor tasks and drafts(the existing "applies defaultAssignee when created without an assignee" test is the control).
src/test/cli-init-create.test.ts—task create -a ""withdefaultAssigneeset is unassigned.src/test/draft-create-consistency.test.ts— same fordraft create.src/test/cli-task-view-edit.test.ts—task edit -a ""clears; a title-only edit keeps theexisting assignees.
src/test/mcp-tasks.test.ts—task_createapplies the default,task_createwithassignee: []does not,
task_editwithassignee: []clears.src/test/web-task-types.test.tsx— the create payload omits a blank assignee.src/test/cli-guidance.test.ts— help text documents the empty value.Manual check with
defaultAssignee: ["@alice","@bob"]:For review
-a ""now means "clear", while #878 (BACK-603) made a blank value an error for--dep/--ref/--docon create, with
--clear-refs/--clear-deps/--clear-docsas the clearing mechanism on edit. Assigneehas no
--clear-assigneeflag and the empty value is the mechanism approved for this task, so the twolist-flag conventions now differ. Flagging in case you want them aligned later.