Skip to content

feat(repeat): month days, repeat query, skip last ends repeat - #232

Merged
MaplumeX merged 2 commits into
mainfrom
feat/recurring-task-improvements
Oct 14, 2026
Merged

MaplumeX merged 2 commits into
mainfrom
feat/recurring-task-improvements

Conversation

@MaplumeX

Copy link
Copy Markdown
Owner

Summary

Repeating Tasks v3 (.scratch/recurring-tasks-v3/spec.md), three related improvements to recurring tasks and projects.

1. Month days (monthDays)

Monthly rules could only advance on the scheduled date's day-of-month, and a chain anchored on the 31st got clamped to 28/30 in short months and never recovered.

  • RepeatRule.monthDays?: number[] (1–31, ascending, deduped) in the shared DTO; only meaningful for unit=month.
  • normalizeRepeatRule filters/sorts/dedupes and drops monthDays for other units; canonicalRepeatRule key order becomes unit, interval, weekdays?, monthDays?, anchor, until?. Missing optional keys are omitted, so existing canonical strings and derived IDs are unchanged (locked by regression tests).
  • New nextMonthlyOccurrence: anchor month is month 0, every interval-th month is active, days past the end of a month clamp to its last day (so [31] means "last day" and does not drift). No monthDays → original path, behavior unchanged.
  • REST RepeatRuleDto gains monthDays validation; agent repeatRuleSchema, descriptions and error message updated. No DB migration (repeatRule is a JSON string).
  • RepeatRuleField shows a 1–31 day grid for month mode, with hints for the default (scheduled day) and 29–31 clamping; switching units strips monthDays.
  • formatRepeatSummary renders month days, showing 31 as "last day".

2. Natural-language repeat query

  • New pure parser packages/shared/src/repeat-query.ts (parseRepeatQuery / parseRepeat) reusing the when-query lexicon, now extracted into packages/shared/src/query-lexicon.ts (normalization, numbers, time extraction, weekday/month names). The existing when-query tests pass unchanged.
  • Supports a broad zh/en rule table: 每天/daily, 每周五/every fri, 每周一三五, every 2 weeks on wed, 每个工作日, 每周末, 每月15号, 每月1号和15号, 每月最后一天, 每两个月10号, 每年3月8日, 完成后每3天 / 3 days after completion, plus optional times.
  • WhenCandidate gains a repeat branch; parseWhenQuery takes allowRepeat. Queries starting with a repeat trigger (每 / 完成后 / every / each) yield only repeat candidates; otherwise repeat completions are appended after date candidates (max 6).
  • ScheduledDateField / WhenQueryInput accept allowRepeat, enabled only on single-entity scheduled-date entry points (task/project context menus, expanded task row, swipe picker, project meta row, keyboard picker with one target). Multi-select, due dates, and quick add do not.
  • Selecting a repeat candidate writes { start: ANYTIME, scheduledDate, repeatRule, reminderTime? } and closes the picker; project entries ignore the time.
  • i18n and placeholder updates; new CONTEXT.md glossary entry.

3. Skip last occurrence ends the repeat

When until was already reached, "Skip Occurrence" was disabled, so "not this time, and stop repeating" had no obvious action.

  • planRepeatSkip now returns { patch: taskCancelPatch(now), ends: true } when the chain has no next occurrence, instead of blocked: 'no-next'. next-exists still blocks first.
  • Device and REST skip paths do not reset subtasks when ends; the task enters the logbook as cancelled and derivation stops.
  • Projects have no cancelled state: planProjectRepeatSkip reuses planProjectComplete(…, 'cancelled') — the project completes, remaining to-dos are cancelled, nothing is derived.
  • UI: useSkipOccurrence / useSkipProjectOccurrence expose last; entries are no longer disabled and are relabeled "Skip and End Repeat" with explanatory hints.
  • Agent skipOccurrence description and returned status updated.

Testing

  • engine: normalizeRepeatRule / canonicalRepeatRule (canonical-string lock, ID stability), nextOccurrenceDate monthDays matrix (intervals, clamping without drift, merging, leap year, completion anchor, until), skip/preview/project instance for monthDays, and last-occurrence skip for tasks and projects.
  • api: 105 new repeat-query.test.ts cases (rule table zh/en, clamping, invalid input, completion, parseWhenQuery integration, cross-check against engine).
  • backend: DTO validation, REST derivation for monthDays, last-occurrence skip e2e (real Postgres), agent tool parameter validation.
  • ui: repeat-query candidate patches (reminder / project time ignored / disabled entry), month-days grid, formatRepeatSummary zh/en, last-occurrence menu entries.

All package typecheck, tests (engine, api, ui, backend e2e, desktop, mobile, frontend) and lint pass. Not yet manually verified in the running app.

Notes

  • No database migration required.
  • Old clients drop monthDays and derive different dates/IDs; the compatibility window is limited to pre-upgrade clients (ADR-0026) and is accepted.
  • Docs: CONTEXT.md entries for Repeat Rule, Repeat Query and Skip Occurrence updated.

@MaplumeX
MaplumeX merged commit b5841b4 into main Oct 14, 2026
4 checks passed
@MaplumeX
MaplumeX deleted the feat/recurring-task-improvements branch October 14, 2026 05:24
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