Skip to content

feat(completion): explicit completion-date control menu#2147

Open
renatomen wants to merge 15 commits into
callumalpass:mainfrom
renatomen:feat/completion-date-control-menu
Open

feat(completion): explicit completion-date control menu#2147
renatomen wants to merge 15 commits into
callumalpass:mainfrom
renatomen:feat/completion-date-control-menu

Conversation

@renatomen

Copy link
Copy Markdown
Contributor

Summary

Lets users choose, per action, which date a task's completion is recorded against — without opening the task.

Stacked on #2146. This branch's first three commits are #2146 (the skip/occurrence bugfix); they drop out once that merges. The feature commits to review start at feat(completion): add four-mode completion-date resolver.

Screenshots

The four completion actionsCompleted today / on schedule / on due date / on (pick date) — under a "Mark complete or skip" submenu ("Mark complete" for non-recurring tasks). A mode renders disabled when its source date is absent (here on schedule, since the task has no scheduled date) rather than silently substituting another date:

Completion actions in a submenu

The same actions can render flat in the menu (submenu grouping turned off), divider-delimited:

Completion actions flat in the menu

A new appearance setting — Appearance & UI → Task cards → "Group complete and skip actions in a submenu" (default on) — switches between the two layouts:

Appearance setting

What's in it

  • resolveCompletionDate(task, mode, ctx) resolves today / asScheduled / onDue / onPicked to a concrete date, reporting unavailability instead of substituting (so options can be disabled). asScheduled prefers the clicked occurrence and never falls through to due; completion-anchored recurrences resolve asScheduled to scheduled (explicit re-anchor).
  • An optional completionDate threaded through the status pipeline so non-recurring completions record a chosen completedDate (default today; recurring unaffected by the existing status && !recurring guard).
  • Four explicit completion actions with a per-type un-complete affordance (recurring: per-date toggle; non-recurring: a single "Mark incomplete" reverting to the default status). "Completed on (pick date)" opens the shared date picker; completion is recorded date-only.
  • Actions nested under the "Mark complete or skip" submenu, with the appearance setting above (default on) to switch to a flat, divider-delimited menu. Uniform sentence-case labels.
  • Rescheduling a recurring task reactivates occurrences: clears every completed/skipped instance dated on or after the new scheduled date (handles accidental off-schedule completions), preserving older history. A confirmation dialog lists the affected dates before clearing; cancel aborts the reschedule. Covers the context-menu and scheduled-date-modal paths.

Docs

Adds user documentation for the menu and the setting: a Completing Tasks section in task-management.md, the toggle under settings/appearance.md, and reschedule-reactivation notes in recurring-tasks.md (with screenshots in docs/assets/, reusable for release notes).

Testing

New/updated unit suites: completionDateResolver, taskContextMenu.complete, taskContextMenu.pickDate, TaskService (completedDate threading, reschedule-clear, confirm callback), issue-1724. All green; typecheck and lint (ts/architecture) green.

Known follow-ups (not blocking)

  • i18n: the new contextMenus.task.completion.* and appearance-setting keys are English-only; the other locales fall back to English (tracked in i18n.state.json).
  • Calendar drag/resize reschedule still clears instances silently (no confirmation) — deferred (needs drag-revert-on-cancel handling).
  • HTTP/CLI/MCP exposure of the non-recurring completion-date parameter is deferred.

renatomen added 15 commits July 4, 2026 15:27
Add a distinct optional occurrenceDate to the task context menu options,
set by occurrence-addressed views (Calendar) and omitted by list/board
producers, so occurrence-aware logic never infers occurrence-ness from the
overloaded targetDate.

Skip now passes the occurrenceDate when present (Calendar), otherwise no
explicit date so the service's anchor-aware default resolves it - scheduled for
scheduled-anchored recurrences (fixing the List/Kanban 'records today' bug) and
today for completion-anchored recurrences (unchanged).
Adds the List/Kanban unskip-label path (occurrenceDate absent + resolved
scheduled date already skipped), a due-only recurring case, and a
showTaskContextMenu occurrenceDate-forwarding test; makes the completion-anchored
assertion timezone-deterministic via getTodayString().
resolveCompletionDate(task, mode, ctx) maps today/asScheduled/onDue/onPicked
to a concrete date, reporting unavailability (via i18n reason key) instead of
substituting a fallback so the menu can disable an option. asScheduled prefers
the clicked occurrence and never falls through to due; completion-anchored
recurrences resolve asScheduled to scheduled (explicit re-anchor).
…line

updateProperty/updateTaskProperty accept an optional completionDate that
overrides the getCompletionDateForTask default (task.occurrence_date || today)
for non-recurring status completions. Parameter is optional so all existing
status writes and the status-icon path are unchanged; recurring tasks remain
unaffected by the existing property===status && !recurring guard. External
HTTP/CLI/MCP exposure is deferred.
Replace the single recurring 'Mark complete for this date' item with four
explicit actions (Complete today / as scheduled / on due date / on…), and add
them net-new for non-recurring tasks. Each resolves via the four-mode resolver
and dispatches the native mechanism: recurring -> toggleRecurringTaskComplete;
non-recurring -> a status change carrying the completion date. Unavailable
modes render disabled with a reason tooltip. Un-complete is per task type:
recurring toggles the resolved date out per action; a completed non-recurring
task collapses to one 'Mark incomplete' that reverts to the default status.
Recurring labels carry an (Instance) prefix.

'Complete on…' opens the shared date picker and records the chosen date
(date-only, matching completedDate/complete_instances storage); cancel is a
no-op.

Adds contextMenus.task.completion i18n keys (en); other locales fall back to
English pending translation (tracked in i18n.state.json).
Non-recurring completion/un-completion now surface a neutral failure notice
(the old key wrongly said 'recurring task'). Adds tests for the no-completed-
status guard, recurring 'Complete on due date' dispatch, and non-recurring
'Complete today' dispatch.
Local runs in non-UTC timezones (e.g. Pacific/Auckland, UTC+13) fail date/
day-boundary tests that pass on CI (GitHub Actions runs in UTC). Set
process.env.TZ='UTC' before the config is exported so Jest's worker processes
inherit it and read the zone at startup, before any Date is constructed.
Overridable via 'TZ=... jest' for timezone-specific testing.
…le reactivates occurrence

Post-review UX refinements:
- Nest the four completion actions (and, for recurring, skip) under a single
  'Complete or skip' submenu ('Complete' for non-recurring) to keep the
  top-level menu compact; the occurrence note stays top-level.
- Uniform sentence-case labels for both task types (no per-type qualifier):
  Completed today / Completed on schedule / Completed on due date /
  Completed on (pick date); Skip instance; Mark incomplete.
- Rescheduling a recurring task onto a date removes that date from
  complete_instances/skipped_instances, so moving the scheduled date back
  reactivates the occurrence without manually editing those lists.
…dule clears instances on or after the new date

- Rename the submenu 'Complete or skip' -> 'Mark complete or skip' ('Complete'
  -> 'Mark complete' for the non-recurring variant).
- Rescheduling a recurring task now drops every complete_instances/
  skipped_instances entry on or after the new scheduled date (was: exact-date
  match only), so an accidental off-schedule completion recorded on a later date
  is also cleared when moving the schedule back. Entries strictly before the new
  date are preserved as genuine history.
Rescheduling a recurring task that would clear completed/skipped instances (on
or after the new date) now prompts for confirmation, listing the affected dates;
declining aborts the reschedule entirely (nothing is written). Threaded via an
optional confirmClearInstances callback on updateProperty: the context-menu and
scheduled-date-modal paths supply a ConfirmationModal; the headless API (and any
silent update) omit it and keep the prior behavior. Calendar drag still clears
silently (revert-on-cancel deferred).
… menu

Adds an appearance setting 'Group complete and skip actions in a submenu'
(default on = current submenu behavior). When off, the completion actions (and
skip for recurring tasks) render directly in the task context menu instead of
nested under 'Mark complete or skip'. Read via settings.completionMenuAsSubmenu,
defaulting to the submenu when unset (backward compatible).
In flat menu mode (submenu setting off), wrap the completion/skip actions with a
separator before and after so the block is visually delimited from the rest of
the task context menu.
The four completion-date actions in the task context menu, the "Group
complete and skip actions in a submenu" appearance toggle, and the
reschedule-clears-later-instances behavior for recurring tasks. Also maps
the completion-menu source files to task-management.md.
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