Skip to content

Fix todo update dropping untouched fields - #413

Merged
robzolkos merged 1 commit into
mainfrom
fix/todo-update-preserves-fields
Jun 29, 2026
Merged

Fix todo update dropping untouched fields#413
robzolkos merged 1 commit into
mainfrom
fix/todo-update-preserves-fields

Conversation

@robzolkos

@robzolkosrobzolkos commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Live verification

Tested against the Rob Zolkos account in the Verdant Coffee Roasters project,
Fusilli todolist, using a locally built rebased branch.

Fixes#412
Fixes#458
Fixes#485


Summary by cubic

Fixes partial todos update wiping untouched fields. The command now fetches the existing todo and seeds the PUT body so content, description, due/start dates, and assignees are preserved.

  • Bug Fixes
    • Preload the update request with existing values before applying overrides to prevent BC3’s clear-by-omission behavior; matches clear-flag behavior.
    • Added tests for --due-only and title-only updates to confirm content, description, due_on, starts_on, and assignee_ids are retained.

Written for commit 49cdc81. Summary will update on new commits.

Review in cubic

CopilotAI review requested due to automatic review settings April 8, 2026 22:14
@github-actionsgithub-actionsBot added commands CLI command implementations tests Tests (unit and e2e) labels Apr 8, 2026

@cubic-dev-aicubic-dev-aiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 2 files

CopilotAI 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.

Pull request overview

This PR fixes a bug where partial updates to todos were dropping untouched fields. The BC3 API clears fields by omission, so when a partial update was made (e.g., only updating the due date), other fields weren't being included in the request and would be cleared by the API.

Changes:

  • Modified the non-clear update path to fetch the existing todo and seed the UpdateTodoRequest with current values before applying user overrides
  • Added two new tests (TestTodosUpdateDueDatePreservesExistingFields and TestTodosUpdateTitlePreservesExistingFields) to verify that partial updates preserve untouched fields

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

FileDescription
internal/commands/todos.goSeeds UpdateTodoRequest with existing todo values before applying user overrides to prevent BC3 API from clearing untouched fields
internal/commands/todos_test.goAdds test coverage for due date-only and title-only updates to verify preservation of untouched fields

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@Nmans01

Copy link
Copy Markdown

It would be a huge improvement to have this merged 👍

The non-clear update path sent only user-provided fields in the PUT body.
Because the BC3 API clears fields by omission, this caused partial updates
(e.g. --due alone) to silently wipe content, description, dates, and
assignees. Fetch the existing todo first and seed the request with its
current values before applying overrides — matching what the clear path
already does.
@robzolkos
robzolkosforce-pushed the fix/todo-update-preserves-fields branch from 666c3e8 to 49cdc81CompareJune 29, 2026 15:01
@github-actionsgithub-actionsBot added the bug Something isn't working label Jun 29, 2026
@robzolkos
robzolkos merged commit f0ae099 into mainJun 29, 2026
25 checks passed
@robzolkos
robzolkos deleted the fix/todo-update-preserves-fields branch June 29, 2026 15:07
jeremy added a commit that referenced this pull request Jul 18, 2026
* Fix todos update silently clearing completion subscribers (#538)
The BC3 todos PUT has replace semantics: any field omitted from the body
is cleared. Neither branch of todos update included
completion_subscriber_ids, so every update wiped the todo's "When done,
notify" list. The #412/#413 read-modify-write merge couldn't preserve it
because the SDK's Todo model drops completion_subscribers in
todoFromGenerated (basecamp/basecamp-sdk#355) — the merge had nothing to
carry forward.
Preservation now works via a raw GET of the flat /todos/{id}.json route
(which serves the field the SDK model drops), feeding the current
subscriber ids into both the typed-merge and raw-clear PUT paths. The
read fails closed: an HTTP error, malformed JSON, or a response missing
the completion_subscribers key aborts the command before any PUT rather
than risking a silent clear. The helper is commented as temporary and
goes away once the SDK round-trips the field.
Alongside the fix, subscribers become directly editable:
- todos create/update --notify-on-completion <names or ids> sets them
(comma-separated, with people-name tab completion)
- todos update --no-notify-on-completion clears them (by omission —
explicit intent skips the preservation read entirely)
Unit tests cover preservation in both branches, the flat-route
assertion, explicit set/clear bypassing the read, the fail-closed
matrix (missing key / malformed JSON / HTTP 500 in each branch), flag
conflicts, and the create body. A live smoke test exercises the full
sequence: create with subscriber, title-only update preserves, --no-due
preserves, --no-notify-on-completion clears.
.surface gains the three new flag records by hand (verified identical
to fresh generation); a full regeneration is deferred because the
snapshot has unrelated drift since #499 changed the generator script.
* Use completion-subscriber wording in --notify-on-completion errors
--notify-on-completion resolved people through resolveAssigneeIDs, so
failures surfaced as assignee errors ("No valid assignees provided",
"Assignee ID must be a positive number") — misleading for a flag that
sets completion subscribers.
Parameterize the resolver with a role label: resolvePersonRoleID(s)
carry the wording, and resolveAssigneeID(s) stay as assignee-labeled
wrappers so existing call sites and messages are unchanged. The
subscriber call sites now go through resolveCompletionSubscriberIDs.
Tests cover both subscriber-worded messages and assert no PUT occurs
on resolution failure.
* Test the name-resolution failure path for --notify-on-completion
The subscriber-wording tests covered the numeric-validation and
empty-list errors but not the ResolvePerson miss, so the "failed to
resolve completion subscriber" formatting was only exercised indirectly.
The update mock now matches the flat preservation route exactly
(/todos/999.json) instead of any .json GET, and serves /people routes an
empty directory so name resolution deterministically misses. New subtest
asserts the subscriber-worded resolve error and that no PUT occurs. The
explicit set/clear tests tighten their no-preservation-read assertions
to the exact route.
* Address review: SDK error conversion + exact mock route match
The preservation read's GET error now runs through convertSDKError
before wrapping, so structured codes/hints (rate limit, circuit
breaker) survive with the subscriber-specific context — matching every
other raw Account() call site.
The update mock's preservation branch now matches the account-scoped
flat route exactly (/99999/todos/999.json) instead of by suffix, so a
bucket-scoped GET can never satisfy it.
* Reject missing or invalid subscriber ids in the preservation read
A completion_subscribers element without a positive id would have put a
zero into completion_subscriber_ids and reached the PUT, slipping past
the fail-closed contract. The preservation read now errors on any
non-positive id, and the fail-closed test matrix covers the case.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugSomething isn't workingcommandsCLI command implementationstestsTests (unit and e2e)

Projects

None yet

3 participants

@robzolkos@Nmans01