Skip to content

feat(tasks): accept journalDate on task update to plan a task (#352) - #377

Merged
nevridge merged 2 commits into
mainfrom
feature/task-update-journal-date
Oct 31, 2026
Merged

nevridge merged 2 commits into
mainfrom
feature/task-update-journal-date

Conversation

@nevridge

Copy link
Copy Markdown
Owner

Summary

PUT and PATCH /api/v1/TaskItems/{id} now accept an optional journalDate, mirroring create. A client can change a task's fields and plan it on a day's journal in one atomic request.

  • Absent or null: the planned day is unchanged (PATCH: explicit null also means unchanged; un-planning stays on DELETE /JournalEntries/{entryId}/todos/{taskItemId}).
  • Same day as the current plan: succeeds, and other changes apply. This check runs before the past-day check, so re-sending a stored past date succeeds.
  • Different valid day: the task is moved as the todos endpoint does (old link removed, MoveCount +1, FirstTaggedDate kept, ReassignedToJournalDay event). A first assignment is not a move, so MoveCount stays 0, as on the todos endpoint.
  • Past day (relative to the user's today from TimezoneOffsetMinutes, UTC fallback): 422 TASK_CREATION_PAST_DAY_NOT_ALLOWED with the message "You cannot plan a task on a past journal day.", and nothing is changed.
  • Other update validation failures: the existing error is returned and the task is not planned. The entity validator runs before the past-day check, so a 400 wins over a 422.
  • Atomic: the field update, the get-or-create of the journal entry and the link run in one repository-level transaction (ITaskRepository.BeginTransactionAsync / IRepositoryTransaction). If the link fails, everything rolls back, including a newly created empty entry. A race-only AlreadyLinked returns 409 TASK_JOURNAL_ASSIGNMENT_CONFLICT. No transaction is opened when no planning is needed.
  • Response: re-read from storage for every PUT and PATCH.
  • No carry-forward on update, planning a parent does not plan its subtasks, and no web UI change (useUpdateTaskMutation still sends no journalDate).

Fixes and behavior changes to review

  • AssignToJournalDateAsync now returns AddTodoResult. This fixes the known defect where the PastDayNotAllowed result was ignored. Create still ignores the result on purpose (the task is already committed, so the 201 is unchanged), with a comment saying why.
  • Duplicate-date race bug: JournalEntryRepository.AddAsync left the failed duplicate entry tracked. The next save re-attempted that insert, the catch-all turned the failure into AlreadyLinked, and the link was silently lost. The entry is now detached on a duplicate. A real-SQLite test fails without the fix.
  • AddTodoAsync catch narrowed to unique-constraint violations. POST .../todos now returns 500 instead of a misleading 409 for other DbUpdateExceptions.
  • IsUniqueConstraintViolation also recognises SQLite extended code 1555 (primary-key constraint). The join table's composite key raises 1555 rather than 2067, so the real concurrent-link race would otherwise escape as a 500. AddAsync's behavior for the JournalEntries.Date index is unchanged.
  • SQLite has a single writer, so a request holding the explicit transaction could see SQLITE_BUSY under concurrent writes. This is unlikely for a single-user app.

Files changed

  • Production: TaskItemsController.cs, UpdateTaskItemDto.cs, PatchTaskItemDto.cs, ITaskRepository.cs, TaskRepository.cs, IRepositoryTransaction.cs (new), JournalEntryRepository.cs
  • Generated client: TaskFlow.Web/src/api/client/types.gen.ts (+2 lines, journalDate on UpdateTaskItemDto and PatchTaskItemDto), regenerated and not hand-edited
  • Docs: docs/API.md (create, reopen/offset, PUT and PATCH sections; the PUT section's statusId is corrected to status)
  • Tests: TaskItemsControllerJournalDateMockedTests.cs (new), TaskItemsControllerJournalDateRealStackTests.cs (new), TaskRepositoryTransactionTests.cs (new), JournalEntryRepositoryTests.cs, JsonSerializerOptionsProviderTests.cs
  • No schema change and no migration.

Test results

  • dotnet build -c Release: 0 warnings, 0 errors
  • dotnet test --project TaskFlow.Api.Tests: 829 of 829 pass (47 new), 94.43% line coverage
  • dotnet format TaskFlow.sln --verify-no-changes: clean
  • npm run type-check: pass
  • npm run test -- --run --coverage: 1567 tests pass, 97.31% lines

Residual findings (Minor, for awareness)

  • M-02: the guard-error matrix, the midnight-race PastDayNotAllowed mapping and a real repository-raised AlreadyLinked inside the controller transaction are covered by Moq tests, not against the real SQLite stack. Rollback itself is proven on SQLite.
  • M-03: TryAutoCompleteParentAsync runs after the transaction commits. This is unchanged from the existing behavior.
  • M-04: the same-day check reads CurrentJournalEntryId, while AddTodoAsync checks the join row. Legacy data where the two disagree would get a 409 on a same-day re-send. This is speculative.

Part of #366. Closes #352

@nevridge nevridge left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Review: 💬 Comments

No issues found in this diff.


Reviewed at 8df0134 · findings from pr-review + code-review, verified and triaged by @nevridge with Claude Code

@nevridge
nevridge merged commit 880ba33 into main Oct 31, 2026
15 checks passed
@nevridge
nevridge deleted the feature/task-update-journal-date branch October 31, 2026 02:29
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.

Accept journalDate on task update to plan a task

1 participant