From 967b9e8c48fd7d714b50a5ae46b103ec5119baa3 Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Fri, 6 Mar 2026 12:15:37 +0100 Subject: [PATCH 1/2] feat(task-repeat-cfg): add skipOverdue option to recurring tasks When enabled on a repeat config, missed/overdue instances (scheduled dates before today) are silently skipped instead of being created as overdue tasks. The lastTaskCreationDay watermark is still advanced on skip to prevent the same past date from being re-evaluated on every subsequent app open. Closes #4928, relates to #4931 --- .../task-repeat-cfg-form.const.ts | 9 +++++++++ .../task-repeat-cfg/task-repeat-cfg.model.ts | 3 +++ .../task-repeat-cfg.service.ts | 20 +++++++++++++++++++ src/app/t.const.ts | 2 ++ src/assets/i18n/en.json | 2 ++ 5 files changed, 36 insertions(+) diff --git a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts index 87c45ea64c..f9bb6dd287 100644 --- a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts +++ b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts @@ -290,4 +290,13 @@ export const TASK_REPEAT_CFG_ADVANCED_FORM_CFG: FormlyFieldConfig[] = [ }, className: 'sp-formly-child-option', }, + { + key: 'skipOverdue', + type: 'checkbox', + defaultValue: false, + templateOptions: { + label: T.F.TASK_REPEAT.F.SKIP_OVERDUE, + description: T.F.TASK_REPEAT.F.SKIP_OVERDUE_DESCRIPTION, + }, + }, ]; diff --git a/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts b/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts index 830f3dff62..1d2fecb872 100644 --- a/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts +++ b/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts @@ -65,6 +65,8 @@ export interface TaskRepeatCfgCopy { }[]; // Exception list for deleted instances (ISO date strings YYYY-MM-DD) deletedInstanceDates?: string[]; + // When true, missed/overdue instances are silently skipped instead of being created + skipOverdue?: boolean; } export type TaskRepeatCfg = Readonly; @@ -101,4 +103,5 @@ export const DEFAULT_TASK_REPEAT_CFG: Omit = { notes: undefined, shouldInheritSubtasks: false, disableAutoUpdateSubtasks: false, + skipOverdue: false, }; diff --git a/src/app/features/task-repeat-cfg/task-repeat-cfg.service.ts b/src/app/features/task-repeat-cfg/task-repeat-cfg.service.ts index 1a5f3e5dc9..61d0249dcd 100644 --- a/src/app/features/task-repeat-cfg/task-repeat-cfg.service.ts +++ b/src/app/features/task-repeat-cfg/task-repeat-cfg.service.ts @@ -228,6 +228,26 @@ export class TaskRepeatCfgService { if (taskRepeatCfg.deletedInstanceDates?.includes(targetDateStr)) { return []; } + // If skipOverdue is enabled, silently skip instances that are in the past (before today). + // We still dispatch updateTaskRepeatCfg to advance lastTaskCreationDay so that the same + // overdue date is not re-evaluated on every subsequent app open (which would stall progress + // permanently for non-daily or every-N-day schedules where today may not be a scheduled day). + if ( + taskRepeatCfg.skipOverdue && + targetDateStr < getDbDateStr(new Date(targetDayDate)) + ) { + return [ + updateTaskRepeatCfg({ + taskRepeatCfg: { + id: taskRepeatCfg.id, + changes: { + lastTaskCreation: targetCreated.getTime(), + lastTaskCreationDay: targetDateStr, + }, + }, + }), + ]; + } const { task, isAddToBottom } = this._getTaskRepeatTemplate( taskRepeatCfg, diff --git a/src/app/t.const.ts b/src/app/t.const.ts index 62d5aaedc7..27dfa790fb 100644 --- a/src/app/t.const.ts +++ b/src/app/t.const.ts @@ -1694,6 +1694,8 @@ const T = { REPEAT_EVERY: 'F.TASK_REPEAT.F.REPEAT_EVERY', SATURDAY: 'F.TASK_REPEAT.F.SATURDAY', SCHEDULE_TYPE_LABEL: 'F.TASK_REPEAT.F.SCHEDULE_TYPE_LABEL', + SKIP_OVERDUE: 'F.TASK_REPEAT.F.SKIP_OVERDUE', + SKIP_OVERDUE_DESCRIPTION: 'F.TASK_REPEAT.F.SKIP_OVERDUE_DESCRIPTION', START_DATE: 'F.TASK_REPEAT.F.START_DATE', START_TIME: 'F.TASK_REPEAT.F.START_TIME', START_TIME_DESCRIPTION: 'F.TASK_REPEAT.F.START_TIME_DESCRIPTION', diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e281f54243..9ade08cc8e 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1647,6 +1647,8 @@ "REPEAT_EVERY": "Recur every", "SATURDAY": "Saturday", "SCHEDULE_TYPE_LABEL": "Schedule type", + "SKIP_OVERDUE": "Skip overdue instances", + "SKIP_OVERDUE_DESCRIPTION": "When enabled, if the app is opened after missing a scheduled occurrence (e.g. a weekly task where today is not a scheduled day), the missed instance will be silently skipped instead of being created as an overdue task", "START_DATE": "Start date", "START_TIME": "Scheduled start time", "START_TIME_DESCRIPTION": "E.g. 15:00. Leave blank for an all day task", From a2f34a0e9d6fb2c5b12b0d50a7b568447f98b52c Mon Sep 17 00:00:00 2001 From: Johannes Millan Date: Fri, 6 Mar 2026 12:22:09 +0100 Subject: [PATCH 2/2] refactor(task-repeat-cfg): deprecate order field and remove from config UI The order field was confusing and not user-friendly. It is removed from the repeat config dialog and translation keys. The field and its runtime logic (isAddToBottom, sortRepeatableTaskCfgs) are kept intact for backwards compatibility with existing configs that have a non-zero order. order is also removed from SCHEDULE_AFFECTING_FIELDS since it is no longer editable via the UI. --- .../task-repeat-cfg-form.const.ts | 9 --------- .../task-repeat-cfg/store/task-repeat-cfg.effects.ts | 1 - .../features/task-repeat-cfg/task-repeat-cfg.model.ts | 4 ++++ src/app/t.const.ts | 2 -- src/assets/i18n/en.json | 2 -- 5 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts index f9bb6dd287..8454ce206e 100644 --- a/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts +++ b/src/app/features/task-repeat-cfg/dialog-edit-task-repeat-cfg/task-repeat-cfg-form.const.ts @@ -249,15 +249,6 @@ export const TASK_REPEAT_CFG_FORM_CFG_BEFORE_TAGS: FormlyFieldConfig[] = [ updateOn: 'blur', }, }, - { - key: 'order', - type: 'input', - templateOptions: { - label: T.F.TASK_REPEAT.F.ORDER, - type: 'number', - description: T.F.TASK_REPEAT.F.ORDER_DESCRIPTION, - }, - }, ]; export const TASK_REPEAT_CFG_ADVANCED_FORM_CFG: FormlyFieldConfig[] = [ diff --git a/src/app/features/task-repeat-cfg/store/task-repeat-cfg.effects.ts b/src/app/features/task-repeat-cfg/store/task-repeat-cfg.effects.ts index bd02e13961..f18a7c4ab8 100644 --- a/src/app/features/task-repeat-cfg/store/task-repeat-cfg.effects.ts +++ b/src/app/features/task-repeat-cfg/store/task-repeat-cfg.effects.ts @@ -54,7 +54,6 @@ const SCHEDULE_AFFECTING_FIELDS: (keyof TaskRepeatCfgCopy)[] = [ 'friday', 'saturday', 'sunday', - 'order', // controls top/bottom placement of created tasks, which affects rescheduling behavior 'isPaused', ]; diff --git a/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts b/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts index 1d2fecb872..62ee45d1ea 100644 --- a/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts +++ b/src/app/features/task-repeat-cfg/task-repeat-cfg.model.ts @@ -29,6 +29,10 @@ export interface TaskRepeatCfgCopy { lastTaskCreationDay?: string; title: string | null; tagIds: string[]; + /** + * @deprecated No longer configurable via UI. Kept for backwards compatibility. + * order<=0 → task inserted at top; order>0 → task inserted at bottom. + */ order: number; defaultEstimate?: number; startTime?: string; diff --git a/src/app/t.const.ts b/src/app/t.const.ts index 27dfa790fb..c3efc667df 100644 --- a/src/app/t.const.ts +++ b/src/app/t.const.ts @@ -1677,8 +1677,6 @@ const T = { INHERIT_SUBTASKS_DESCRIPTION: 'F.TASK_REPEAT.F.INHERIT_SUBTASKS_DESCRIPTION', MONDAY: 'F.TASK_REPEAT.F.MONDAY', NOTES: 'F.TASK_REPEAT.F.NOTES', - ORDER: 'F.TASK_REPEAT.F.ORDER', - ORDER_DESCRIPTION: 'F.TASK_REPEAT.F.ORDER_DESCRIPTION', QUICK_SETTING: 'F.TASK_REPEAT.F.QUICK_SETTING', Q_CUSTOM: 'F.TASK_REPEAT.F.Q_CUSTOM', Q_DAILY: 'F.TASK_REPEAT.F.Q_DAILY', diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 9ade08cc8e..e3eec88473 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1630,8 +1630,6 @@ "INHERIT_SUBTASKS_DESCRIPTION": "When enabled, subtasks from the most recent task instance will be recreated with the recurring task", "MONDAY": "Monday", "NOTES": "Default notes", - "ORDER": "Order", - "ORDER_DESCRIPTION": "Creation order of recurring tasks. Only affects recurring tasks created at the same time. A lower value means a task will be created higher up the list; a higher number means it will be further down. A value greater than 0 means tasks are created below normal tasks.", "QUICK_SETTING": "Recurring Config", "Q_CUSTOM": "Custom recurring config", "Q_DAILY": "Every day",