Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .scratch/recurring-tasks-v3/issues/01-month-days.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 01 每月的几号(monthDays)

Status: implemented

## Problem

`unit=month` 只能按计划日期当天推进:「每月 1 号和 15 号」要拆两条链;31 号起算的链钳到短月月末后一直停在 28 / 30 号。

## Design

见 spec 第 1 节。要点:

- `RepeatRule.monthDays?: number[]`(shared DTO),仅 `unit=month`,1–31 升序去重。
- `normalizeRepeatRule` 处理 monthDays;`canonicalRepeatRule` 键序 `unit, interval, weekdays?, monthDays?, anchor, until?`,缺省不出现,存量规则的规范串与派生 id 不变。
- `nextOccurrenceDate` 新增 `nextMonthlyOccurrence`:锚点月为第 0 月,k×interval 月活跃;日子按当月钳制、合并重复;取严格晚于锚点的最小出现日。无 monthDays 时走原路径。
- REST `RepeatRuleDto` 加 monthDays 校验;助手 `repeatRuleSchema` 与描述、错误提示同步。无数据库迁移(repeatRule 为 JSON 字符串)。
- `RepeatRuleField`:month 时显示 1–31 日子网格;未选 = 按计划日期当天;离开 month 清除 monthDays。扩展现有 `formatRepeatSummary` 支持 monthDays(02 的候选行复用)。
- CONTEXT.md 更新 Repeat Rule 词条。

## Acceptance

- engine 测试:规范化、规范串锁定、`nextOccurrenceDate` 矩阵(见 spec Testing),skip / preview / 重复项目各一条 monthDays 用例。
- backend:DTO 校验、REST 完成派生日期正确、助手参数校验。
- UI:日子网格选择、切换单位清除、下一次预览正确、`formatRepeatSummary` 中英文。
- 各包 typecheck 与测试通过。

## Comments

### 2026-10-13 — 实现

- engine:`normalizeRepeatRule` / `canonicalRepeatRule` 支持 monthDays;新增 `nextMonthlyOccurrence`(只搜第 0、第 1 个活跃月,后者必有出现日)。无 monthDays 时走原 `addMonthsClamped` 路径。
- 规范串回归:锁定了 month / year 规则的现有规范串,并验证空 monthDays 与缺省派生同一 id。
- 后端:`RepeatRuleDto.monthDays`(1–31 校验);助手 `repeatRuleSchema` 与非法规则提示加上 monthDays。
- UI:`RepeatRuleField` 在 month 时显示 7 列 1–31 网格;未选时提示「按计划日期当天」,选了 29–31 时提示短月落在月末;离开 month 剥离 monthDays。`formatRepeatSummary` 显示「每月 · 1、15 号」,31 显示为「最后一天」。
- 与 spec 的差异:没有新建 `formatRepeatRule`,复用已有的 `formatRepeatSummary`;规则编辑器顶部不加摘要行(编辑器本身已完整展示规则)。
- CONTEXT.md 的 Repeat Rule 词条已更新。
- 测试:engine 310、api 679、ui 1153、backend 401(含临时 Postgres 上的真库 e2e)、desktop 79、mobile 108、frontend 28 全部通过;全仓 typecheck 与改动文件 eslint 通过。尚未在真实应用里手动验证。
35 changes: 35 additions & 0 deletions .scratch/recurring-tasks-v3/issues/02-repeat-query.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 02 When 输入框识别重复说法(Repeat Query)

Status: implemented
Blocked by: 01

## Problem

When 输入框能识别「下周五 9点」,但识别不了「每周五」「每月 15 号」,设重复要先选日期再逐项点规则编辑器。

## Design

见 spec 第 2 节。要点:

- 新纯函数 `parseRepeatQuery`(`packages/shared/src/repeat-query.ts`),复用 when-query 的归一化、数字 / 星期词表与 `extractTime`;输出规范形规则 + 第一次出现日(≥ 今天)+ 可选时刻。
- `WhenCandidate` 加入 `repeat` 分支;`parseWhenQuery` 新增 `allowRepeat`,以重复触发词开头时只出重复候选。
- `ScheduledDateField` / `WhenQueryInput` 新增 `allowRepeat`,只在单个实体的计划日期入口打开(清单见 spec)。
- 候选行:↻ + `formatRepeatSummary` 摘要 + 「从 X 开始」+ 可选时刻;选中写 `{ start: ANYTIME, scheduledDate, repeatRule, reminderTime? }` 并关闭弹层。
- i18n 与 placeholder;CONTEXT.md 新增 Repeat Query 词条。

## Acceptance

- 解析器:规则表逐条、中英文、时刻、前缀补全与排序、`allowRepeat: false` 不出重复候选、原有 when-query 测试全部通过、与 engine 交叉校验。
- UI:重复候选写出的 patch(含 reminderTime、Project 忽略时刻);未开 `allowRepeat` 的入口不出重复候选。
- 各包 typecheck 与测试通过。

## Comments

### 2026-10-13 — 实现

- 重构:把归一化、数字、时刻提取、星期 / 月份名、`exactDate` 从 `when-query.ts` 移到 `packages/shared/src/query-lexicon.ts`,两个解析器共用;原有 122 个 when-query 测试不改即通过。
- 解析器 `packages/shared/src/repeat-query.ts`:`parseRepeatQuery` 与 `parseRepeat`(后者另给出 `exclusive`)。完全匹配走中英文语法;补全用一张已归一化的说法表做前缀匹配,通用说法(每天 / 每周…)排在具体说法(每周五…)之前,同档按单位、第一次日期排序。第一次出现日在解析器内用 PlainDate 计算,不依赖 engine。
- `parseWhenQuery`:`allowRepeat` 为真时,若重复说法完全匹配或以 每 / 完成后 / every / each 开头,只出重复候选;否则重复补全接在日期候选之后,总数仍不超过 6。
- UI:`ScheduledDateField` / `WhenQueryInput` 新增 `allowRepeat`。打开的入口:TaskContextMenu(非多选)、TaskRowExpanded(两处)、SwipeWhenPicker、ProjectContextMenu(单个项目)、ProjectMetaRow、KeyboardFieldPicker(单个目标)。候选行为 ↻ + `formatRepeatSummary` + 「从 X 开始」+ 可选时刻;选中写 `{ start, scheduledDate, repeatRule, reminderTime? }` 并关闭。`allowRepeat` 时占位文字换成「输入日期或重复,如「下周五」「每周一」」。
- CONTEXT.md 新增 Repeat Query 词条。
- 测试:api 新增 `repeat-query.test.ts`(105 条:规则表、钳制、非法输入、补全、与 engine 交叉校验、parseWhenQuery 集成);ui 新增 5 条(候选写入、提醒、忽略时刻、未开入口、占位文字)。全仓:engine 310、api 784、ui 1158、backend 401(临时 Postgres 真库)、desktop 79、mobile 108、frontend 28 通过;typecheck、eslint、prettier 通过。尚未在真实应用里手动验证。
34 changes: 34 additions & 0 deletions .scratch/recurring-tasks-v3/issues/03-skip-last-ends-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# 03 跳过最后一次即结束重复

Status: implemented

## Problem

`until` 已到(例如设为今天)时「跳过本次」被禁用,用户无法用它表达「这次不做、重复到此为止」,只能想到去取消。

## Design

见 spec 第 3 节。要点:

- `planRepeatSkip` 没有下一次时返回取消 patch 与 `ends: true`;`next-exists` 仍优先阻止。
- 设备端与 REST 的跳过在 `ends` 时不重置 Subtask。
- 重复项目没有取消态:最后一次跳过即完成项目、未了结任务取消,不派生。
- UI 入口不再禁用,最后一次文案「跳过本次并结束重复」;助手工具描述与返回的 `status`。

## Acceptance

- domain:until 为今天、已错过多轮且 until 已过 → 取消 patch;有派生实例 → next-exists。
- engine 后端与 REST e2e:最后一次跳过后任务 CANCELLED、提醒清除、计划日期不变、不派生、Subtask 不动。
- UI:任务与项目在最后一次时入口可用、文案正确、点击调用 skip。
- 项目:domain、engine 后端、REST e2e 覆盖「项目完成、未了结任务取消、不派生」。

## Comments

### 2026-10-13 — 实现

- 按上述设计实现;CONTEXT.md 的 Skip Occurrence 词条已更新。

### 2026-10-14 — 重复项目

- 选择方案「完成项目、剩余任务取消」:`planProjectRepeatSkip` 在 `ends` 时复用 `planProjectComplete(…, 'cancelled')`;设备端 `skipProject` 与 REST `ProjectsService.skip` 原样写入,无需改动。
- `useSkipProjectOccurrence` 新增 `last`,项目右键菜单入口不再禁用。
Loading
Loading