Skip to content

Commit bf97d59

Browse files
authored
Merge pull request #33 from gensyn/copilot/feature-repeat-every-vs-after
Add repeat_every (fixed-schedule) mode with guided multi-step flow alongside existing repeat_after (completion-coupled) mode
2 parents 654798d + efcf5df commit bf97d59

23 files changed

Lines changed: 4596 additions & 798 deletions

README.md

Lines changed: 105 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ A powerful Home Assistant custom component for managing recurring tasks with aut
1010

1111
## ✨ Features
1212

13-
-**Recurring Task Management** - Create tasks with customizable intervals (days, weeks, months, years)
13+
-**Recurring Task Management** - Create tasks with flexible repeat modes: after completion or on a fixed schedule
1414
- 📅 **Automatic Due Date Tracking** - Never forget when a task needs to be done
1515
- 📝 **Todo List Integration** - Automatically sync with Home Assistant's Local Todo lists
1616
- 🎨 **Custom Lovelace Card** - Beautiful task display in Lovelace
@@ -45,17 +45,63 @@ A powerful Home Assistant custom component for managing recurring tasks with aut
4545

4646
### 🆕 Task Creation
4747

48-
To create a new task:
48+
To create a new task:
4949

5050
1. Navigate to `Settings > Devices & Services`
5151
2. Click `Add Integration`
5252
3. Search for **Task Tracker**
5353
4. Fill in the task details:
5454
- **Name** - Display name for your task
55-
- **Task Interval** - How often the task repeats (combined with interval unit)
56-
- **Task Interval Unit** - Day, week, month or year
55+
- **Repeat Mode** - How the due date is recalculated (see [Repeat Modes](#-repeat-modes) below)
5756

58-
![Create task](assets/1_create.png)
57+
<table>
58+
<tr>
59+
<td><img src="assets/1a_create.png" alt="Enter name and select mode"/><br/><b>Enter name and select mode</b></td>
60+
<td><img src="assets/1b_create.png" alt="Repeat After Completion"/><br/><b>Repeat After Completion</b></td>
61+
</tr>
62+
<tr>
63+
<td><img src="assets/1c_create.png" alt="Repeat on a Fixed Schedule"/><br/><b>Repeat on a Fixed Schedule</b></td>
64+
<td><img src="assets/1d_create.png" alt="Repeat Every N Weeks on a Weekday"/><br/><b>Example for Repeat After Completion:<br/>Repeat Every N Weeks on a Weekday</b></td>
65+
</tr>
66+
</table>
67+
68+
Depending on the chosen repeat mode, you will be guided through one or more additional steps to configure the schedule details (see [Repeat Modes](#-repeat-modes)).
69+
70+
---
71+
72+
## 🔄 Repeat Modes
73+
74+
Task Tracker supports two repeat modes, selected during task creation and changeable at any time via the task options.
75+
76+
### Repeat after completion
77+
78+
The next due date is calculated **relative to when the task was last completed**. For example, if a task has a 7-day interval and you complete it on a Wednesday, the next due date will be the following Wednesday — regardless of the original schedule.
79+
80+
**Schedule configuration:** Choose a numeric interval and a unit (Day / Week / Month / Year).
81+
82+
| Field | Description |
83+
|-------|-------------|
84+
| **Task Interval** | How many units between completions |
85+
| **Task Interval Unit** | Day, Week, Month, or Year |
86+
87+
### Repeat every (fixed schedule)
88+
89+
The task repeats on a **fixed calendar schedule**, independent of when it was completed. Completing early or late does not shift the next due date.
90+
91+
**Schedule types:**
92+
93+
| Schedule Type | Description | Example |
94+
|---------------|-------------|---------|
95+
| **Every Nth weekday** | Every *N* weeks on a chosen day of the week | Every week on Monday; Every 2 weeks on Friday |
96+
| **Every Nth day of the month** | A fixed day number each month | Every 15th of the month |
97+
| **Every Nth weekday of the month** | A specific occurrence of a weekday each month | Every 2nd Tuesday; Every last Friday |
98+
| **N days before month end** | A fixed number of days before the last day of the month | 3 days before month end |
99+
100+
#### Mark as done behaviour for fixed schedules
101+
102+
- When a task is **due** (or overdue): marking it done records the most recent past occurrence as the completion date.
103+
- When a task is **due soon**: marking it done records the next upcoming occurrence, so no occurrence is accidentally skipped.
104+
- When a task is **done**: marking it done has no effect.
59105

60106
---
61107

@@ -69,18 +115,49 @@ Access task settings through the cog icon ⚙️ on the integration page.
69115

70116
![Task settings](assets/3_options.png)
71117

72-
| Option | Description |
73-
|--------|-------------------------------------------------------------------------------------------|
74-
| **Active** | Pause tasks when disabled (sensor shows `inactive` state) |
75-
| **Active Override** | Select an `input_boolean` helper to override the Active setting at runtime |
76-
| **Task Interval & Unit** | Modify how often the task repeats |
118+
| Option | Description |
119+
|---------------------------|-----------------------------------------------------------------------------------------------------------------|
120+
| **Active** | Pause tasks when disabled (sensor shows `inactive` state) |
121+
| **Active Override** | Select an `input_boolean` helper to override the Active setting at runtime |
122+
| **Icon** | Choose an icon for the sensor (available as attribute for notifications) |
123+
| **Tags** | Add keywords for filtering in automations/templates (e.g., assignees, notification times) |
124+
| **Todo Lists** | Select Todo lists for automatic task addition when due or due soon |
125+
| **Due Soon** | Number of days before due date when the sensor switches to `due_soon` state and the task is added to todo lists |
126+
| **Due Soon Override** | Select an `input_number` helper (value in days) to override the Due Soon threshold at runtime |
127+
| **Notification Interval** | Reference value for automation/template notification timing |
128+
129+
**Options specific to *Repeat after completion*:**
130+
131+
| Option | Description |
132+
|----------------------------|------------------------------------------------------------------------------------------|
133+
| **Task Interval & Unit** | Modify how often the task repeats |
77134
| **Task Interval Override** | Select an `input_number` helper (value in days) to override the Task Interval at runtime |
78-
| **Material Design Icon** | Choose an icon for the sensor (available as attribute for notifications) |
79-
| **Tags** | Add keywords for filtering in automations/templates (e.g., assignees, notification times) |
80-
| **Todo Lists** | Select Todo lists for automatic task addition when due or due soon |
81-
| **Due Soon** | Number of days before due date when the sensor switches to `due_soon` state and the task is added to todo lists |
82-
| **Due Soon Override** | Select an `input_number` helper (value in days) to override the Due Soon threshold at runtime |
83-
| **Notification Interval** | Reference value for automation/template notification timing |
135+
136+
**Options specific to *Repeat Every Nth weekday*:**
137+
138+
| Option | Description |
139+
|-------------------|-----------------------------------------------|
140+
| **Weekday** | Modify the weekday |
141+
| **Every (weeks)** | Modify the number of weeks between occurences |
142+
143+
**Options specific to *Repeat Every Nth Day of the Month*:**
144+
145+
| Option | Description |
146+
|---------------------|-----------------------|
147+
| **Day Of Month** | Modify the day number |
148+
149+
**Options specific to *Repeat Every Nth weekday of the month *:**
150+
151+
| Option | Description |
152+
|---------------|-------------------------------------|
153+
| **Weekday** | Modify the weekday |
154+
| **Occurence** | Modify the occurence of the weekday |
155+
156+
**Options specific to *Repeat Every N Days Before the End of the Month*:**
157+
158+
| Option | Description |
159+
|---------------------------|--------------------------------------------------|
160+
| **Days Before Month end** | Modify the number of days before the month's end |
84161

85162
> **Note:** Tags and notification intervals require you to implement filtering logic in your own automations.
86163
>
@@ -97,6 +174,11 @@ type: custom:task-tracker-card
97174
entity: sensor.task_tracker_mow_the_lawn
98175
```
99176
177+
The card shows the schedule in a human-readable form that reflects the repeat mode:
178+
179+
- **Repeat after completion** — displays the interval, e.g. *Every 3 days*, *Every 2 weeks*
180+
- **Repeat every (fixed schedule)** — displays the calendar schedule, e.g. *Every week on Monday*, *Every 15th*, *Every 2nd Tuesday*, *3 days before month end*
181+
100182
#### Card States
101183
102184
<table>
@@ -126,7 +208,7 @@ The panel shows all tasks in one place with live state filtering:
126208
| **Done** | Tasks completed within their current interval |
127209
| **Inactive** | Tasks with the *Active* option turned off |
128210
129-
Each task card displays the same information as the Lovelace card (status, interval, last done date, due date, days until due / overdue by) and includes a ✓ button to mark the task as done immediately.
211+
Each task card displays the same information as the Lovelace card (status, schedule, last done date, due date, days until due / overdue by) and includes a ✓ button to mark the task as done immediately.
130212
131213
#### Disabling the sidebar panel
132214
@@ -158,7 +240,12 @@ Task Tracker provides the following services in the `task_tracker` domain:
158240

159241
#### `task_tracker.mark_as_done`
160242

161-
Marks a task as completed by setting the last done date to today and recalculating the next due date.
243+
Marks a task as completed by setting the last done date to:
244+
- today, for mode "repeat after completion"
245+
- the date of the previous occurrence (might be today), for mode "repeat every (fixed schedule)" when in state DUE
246+
- the date of the next occurrence, for mode "repeat every (fixed schedule)" when in state DUE_SOON
247+
248+
Then recalculates the next due date.
162249

163250
**Example:**
164251
```yaml

__init__.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from .const import DOMAIN, CONF_TASK_INTERVAL_VALUE, CONF_DAY, CONF_TASK_INTERVAL_TYPE, CONF_NOTIFICATION_INTERVAL, \
1717
CONF_DUE_SOON_DAYS, CONF_DUE_SOON_OVERRIDE, CONF_TAGS, CONF_ACTIVE, CONF_TODO_LISTS, SERVICE_MARK_AS_DONE, \
1818
SERVICE_MARK_AS_DONE_SCHEMA, SERVICE_SET_LAST_DONE_DATE, SERVICE_SET_LAST_DONE_DATE_SCHEMA, CONF_DATE, \
19-
CONF_SHOW_PANEL
19+
CONF_SHOW_PANEL, CONF_REPEAT_MODE, CONF_REPEAT_AFTER, \
20+
CONF_REPEAT_EVERY_TYPE, CONF_REPEAT_WEEKDAY, CONF_REPEAT_WEEKS_INTERVAL, \
21+
CONF_REPEAT_MONTH_DAY, CONF_REPEAT_NTH_OCCURRENCE, CONF_REPEAT_DAYS_BEFORE_END
2022
from .coordinator import TaskTrackerCoordinator
2123
from .frontend import TaskTrackerCardRegistration
2224

@@ -118,7 +120,17 @@ async def async_update_entities(entity_ids: list[str], hass: HomeAssistant) -> d
118120

119121
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
120122
"""Set up Task Tracker from a config entry."""
121-
coordinator = TaskTrackerCoordinator(entry.entry_id)
123+
coordinator = TaskTrackerCoordinator(
124+
entry.entry_id,
125+
repeat_mode=entry.options.get(CONF_REPEAT_MODE, CONF_REPEAT_AFTER),
126+
repeat_every_type=entry.options.get(CONF_REPEAT_EVERY_TYPE),
127+
repeat_weekday=entry.options.get(CONF_REPEAT_WEEKDAY),
128+
repeat_weeks_interval=entry.options.get(CONF_REPEAT_WEEKS_INTERVAL, 1),
129+
repeat_month_day=entry.options.get(CONF_REPEAT_MONTH_DAY, 1),
130+
repeat_nth_occurrence=entry.options.get(CONF_REPEAT_NTH_OCCURRENCE, "1"),
131+
repeat_days_before_end=entry.options.get(CONF_REPEAT_DAYS_BEFORE_END, 0),
132+
due_soon_days=entry.options.get(CONF_DUE_SOON_DAYS, 0),
133+
)
122134
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
123135
await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)
124136

@@ -136,7 +148,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
136148
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
137149
"""Migrate old config entry."""
138150

139-
if entry.version > 1 or entry.minor_version > 3:
151+
if entry.version > 1 or entry.minor_version > 6:
140152
# This means the user has downgraded from a later version
141153
return False
142154

@@ -174,4 +186,44 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
174186
minor_version=3,
175187
)
176188

189+
if entry.version == 1 and entry.minor_version == 3:
190+
# 1.3 Add repeat_mode option (default: repeat_after to preserve existing behavior)
191+
new_options = dict(entry.options)
192+
new_options.setdefault(CONF_REPEAT_MODE, CONF_REPEAT_AFTER)
193+
194+
hass.config_entries.async_update_entry(
195+
entry,
196+
options=new_options,
197+
version=1,
198+
minor_version=4,
199+
)
200+
201+
if entry.version == 1 and entry.minor_version == 4:
202+
# 1.4 Add repeat_every schedule fields (None for all existing repeat_after entries)
203+
new_options = dict(entry.options)
204+
new_options.setdefault(CONF_REPEAT_EVERY_TYPE, None)
205+
new_options.setdefault(CONF_REPEAT_WEEKDAY, None)
206+
new_options.setdefault(CONF_REPEAT_WEEKS_INTERVAL, None)
207+
new_options.setdefault(CONF_REPEAT_MONTH_DAY, None)
208+
new_options.setdefault(CONF_REPEAT_NTH_OCCURRENCE, None)
209+
210+
hass.config_entries.async_update_entry(
211+
entry,
212+
options=new_options,
213+
version=1,
214+
minor_version=5,
215+
)
216+
217+
if entry.version == 1 and entry.minor_version == 5:
218+
# 1.5 Add repeat_days_before_end field (None for all existing entries)
219+
new_options = dict(entry.options)
220+
new_options.setdefault(CONF_REPEAT_DAYS_BEFORE_END, None)
221+
222+
hass.config_entries.async_update_entry(
223+
entry,
224+
options=new_options,
225+
version=1,
226+
minor_version=6,
227+
)
228+
177229
return True

assets/1_create.png

-23 KB
Binary file not shown.

assets/1a_create.png

25.2 KB
Loading

assets/1b_create.png

33 KB
Loading

assets/1c_create.png

36.2 KB
Loading

assets/1d_create.png

35.7 KB
Loading

0 commit comments

Comments
 (0)