Skip to content

BACK-604 - Allow explicit unassign in CLI and TUI when defaultAssignee is set - #880

Merged
MrLesk merged 2 commits into
mainfrom
tasks/back-604-explicit-unassign
Aug 8, 2026
Merged

BACK-604 - Allow explicit unassign in CLI and TUI when defaultAssignee is set#880
MrLesk merged 2 commits into
mainfrom
tasks/back-604-explicit-unassign

Conversation

@MrLesk

@MrLesk MrLesk commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Problem

With defaultAssignee configured there was no way to create or edit a task so that it ends
up unassigned. -a "" was parsed by parseDelimitedStringList, which collapses a blank
value to undefined, so an explicitly empty -a was indistinguishable from an omitted one:
task create "T" -a "" still got the configured default, and task edit T -a "" was a
silent 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):

Surface Explicit empty assignee, before this PR
CLI task create / draft create -a "" default applied (blank collapsed to absent)
CLI task edit -a "" no-op
MCP task_create assignee: [] default applied
MCP task_edit assignee: [] no-opbuildTaskUpdateInput ran normalizeStringList, which drops []
Web PUT /api/tasks/:id assignee: [] cleared (the only surface that worked)
Web POST /api/tasks assignee: [] default applied
TUI no assignee editing surface at all

So core.editTaskOrDraft already honoured an explicit empty list, but only the Web edit
payload could actually reach it, and createTaskFromInput treated an empty list as absent
on every surface.

Fix

One rule, applied in the shared core path: an absent assignee means "no opinion" (the
configured defaultAssignee applies on create, the existing value is kept on edit) and
an explicit empty assignee means "unassigned".

  • src/core/backlog.tscreateTaskFromInput keys the default off input.assignee === undefined
    instead of "the normalized list is empty".
  • src/utils/task-builders.ts — new parseClearableStringList preserves the absent-vs-explicit-empty
    distinction coming out of Commander (undefined when the flag is absent, [] when it is present
    with only blank values).
  • src/cli.tstask create, draft create and task edit use it for -a; task edit now sets
    the field even when the parsed list is empty. -a someone is unchanged.
  • src/utils/task-edit-builder.ts — assignee resolves through the existing
    sanitizeClearableStringArray helper already used for dependencies/references/documentation, which
    fixes MCP task_edit in the same place as the CLI.
  • src/web/components/TaskDetailsModal.tsx — the create payload omits assignee when the chip input
    is blank. The modal always sent assignee: [], so honouring the explicit empty at core would
    otherwise have silently disabled defaultAssignee for every Web-created task. A blank field on
    create now means "no opinion", exactly like an omitted -a; Web edit still sends [] and clears.
  • Help text, the MCP task_create/task_edit schema descriptions, ADVANCED-CONFIG.md and the
    shipped task-creation instructions 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
:

  • the TUI create composer (src/ui/components/task-composer.ts) has only title, description,
    status, type and priority;
  • the TUI edit action (src/ui/board.ts, src/ui/task-viewer-with-search.tscore.editTaskInTui)
    shells out to $EDITOR on the task markdown, so the only TUI route to an assignee today is editing
    the frontmatter by hand;
  • every other assignee reference under src/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 edit with no flags in a TTY), not the TUI. The
edit 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 --noEmit clean, bun run check . clean, full bun run test green.

Tests added:

  • src/test/core.test.ts — explicit empty assignee overrides defaultAssignee for tasks and drafts
    (the existing "applies defaultAssignee when created without an assignee" test is the control).
  • src/test/cli-init-create.test.tstask create -a "" with defaultAssignee set is unassigned.
  • src/test/draft-create-consistency.test.ts — same for draft create.
  • src/test/cli-task-view-edit.test.tstask edit -a "" clears; a title-only edit keeps the
    existing assignees.
  • src/test/mcp-tasks.test.tstask_create applies the default, task_create with assignee: []
    does not, task_edit with assignee: [] 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"]:

task create "Control default"              -> assignee: ['@alice', '@bob']
task create "Explicit unassigned" -a ""    -> assignee: []
task create "Explicit carol" -a @carol     -> assignee: ['@carol']
draft create "Draft unassigned" -a ""      -> assignee: []
task edit task-3 -a ""                     -> assignee: []
task edit task-1 -t "renamed"              -> assignee: ['@alice', '@bob'] (unchanged)

For review

-a "" now means "clear", while #878 (BACK-603) made a blank value an error for --dep/--ref/--doc
on create, with --clear-refs/--clear-deps/--clear-docs as the clearing mechanism on edit. Assignee
has no --clear-assignee flag and the empty value is the mechanism approved for this task, so the two
list-flag conventions now differ. Flagging in case you want them aligned later.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/test/cli-init-create.test.ts
@MrLesk
MrLesk merged commit 04fa805 into main Aug 8, 2026
10 checks passed
@MrLesk
MrLesk deleted the tasks/back-604-explicit-unassign branch August 8, 2026 21:51
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
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