From f59f8bf9bdec607de51f7ba73e13c1737bdb620e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:34:52 +0000 Subject: [PATCH 01/16] Initial plan From f6086a309388a0e448bd7749ed2e4d3da69f2af4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:36:34 +0000 Subject: [PATCH 02/16] feat: add notification automation blueprint for due tasks Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- .../task_tracker/notify_due_tasks.yaml | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 blueprints/automation/task_tracker/notify_due_tasks.yaml diff --git a/blueprints/automation/task_tracker/notify_due_tasks.yaml b/blueprints/automation/task_tracker/notify_due_tasks.yaml new file mode 100644 index 0000000..dd082db --- /dev/null +++ b/blueprints/automation/task_tracker/notify_due_tasks.yaml @@ -0,0 +1,75 @@ +blueprint: + name: Task Tracker - Notify about due tasks + description: >- + Send a mobile app notification for each due task at a specified time each day. + Only tasks whose overdue_by value is a multiple of their notification_interval + will trigger a notification, preventing repeated daily alerts. + + The notify action and user tag can be customised via the blueprint inputs. + domain: automation + input: + notification_time: + name: Notification time + description: Time of day to check for due tasks and send notifications. + default: "08:00:00" + selector: + time: {} + notify_action: + name: Notify action + description: >- + The notify action to call (e.g. notify.mobile_app_my_phone). + selector: + action: {} + user_tag: + name: User tag + description: >- + The tag that must be present in a task's tags attribute for the + notification to be sent. + default: user_tag + selector: + text: {} + +triggers: + - trigger: time + at: !input notification_time + +conditions: [] + +actions: + - variables: + tasks: |- + [ {%- for entity_id in integration_entities('task_tracker') %} + {%- if "sensor." in entity_id and states[entity_id].state == "due" %} + {%- set last_done = as_datetime(states[entity_id].attributes.last_done) if 'last_done' in states[entity_id].attributes else None %} + {%- set overdue_by = states[entity_id].attributes.overdue_by if 'overdue_by' in states[entity_id].attributes else None %} + {%- set notification_interval = states[entity_id].attributes.notification_interval if 'notification_interval' in states[entity_id].attributes else None %} + {%- if last_done is not none and overdue_by is not none and notification_interval is not none %} + {%- if overdue_by % notification_interval == 0 %} + "{{ entity_id }}"{% if not loop.last %},{% endif %} + {%- endif %} + {%- endif %} + {%- endif %} + {%- endfor %} ] + configured_user_tag: !input user_tag + - repeat: + for_each: "{{ tasks }}" + sequence: + - alias: Notify User + if: + - condition: template + value_template: "{{ configured_user_tag in states[repeat.item].attributes.tags }}" + then: + - action: !input notify_action + data: + title: "{{ states[repeat.item].attributes.friendly_name }}" + message: >- + {%- set overdue_by = states[repeat.item].attributes.overdue_by + %} due since {{ 'today' if overdue_by == 0 else ('yesterday' + if overdue_by == 1 else overdue_by ~ ' days') }} + data: + tag: "{{ repeat.item }}" + color: green + group: "{{ repeat.item }}" + notification_icon: "{{ states[repeat.item].attributes.icon }}" + +mode: single From 84f6bea0f8e6fddb59c6947b0745c2926d5981fd Mon Sep 17 00:00:00 2001 From: gensyn Date: Wed, 5 Aug 2026 19:37:24 +0200 Subject: [PATCH 03/16] Fixed README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e4ad30..7ce1f71 100644 --- a/README.md +++ b/README.md @@ -119,14 +119,14 @@ Access task settings through the cog icon ⚙️ on the integration page. | Option | Description | |---------------------------|-----------------------------------------------------------------------------------------------------------------| | **Active** | Pause tasks when disabled (sensor shows `inactive` state) | -| **Active Override** | Select an `input_boolean` helper to override the Active setting at runtime | +| **Active Override** | Select an `input_boolean` or `binary_sensor` helper to override the Active setting at runtime | | **Icon** | Choose an icon for the sensor (available as attribute for notifications) | | **Tags** | Add keywords for filtering in automations/templates (e.g., assignees, notification times) | | **Todo Lists** | Select Todo lists for automatic task addition when due or due soon | | **Due Soon** | Number of days before due date when the sensor switches to `due_soon` state and the task is added to todo lists | | **Due Soon Override** | Select an `input_number` helper (value in days) to override the Due Soon threshold at runtime | | **Notification Interval** | Reference value for automation/template notification timing | -| **Dependencies** | Select other Task Tracker sensors this task depends on | +| **Dependencies** | Select other Task Tracker sensors this task depends on | **Options specific to *Repeat after completion*:** From c8bd7a92471ddcdf0553938a3a8eebf35d3f7356 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:49:29 +0000 Subject: [PATCH 04/16] fix: register blueprints programmatically via async_setup so they appear in the HA UI Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- __init__.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/__init__.py b/__init__.py index 68e4adc..eb62c45 100644 --- a/__init__.py +++ b/__init__.py @@ -3,8 +3,10 @@ from __future__ import annotations import logging +import pathlib import voluptuous as vol +from homeassistant.components import blueprint from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform, CONF_ICON, CONF_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall @@ -103,9 +105,38 @@ async def async_set_last_done_date(service_call: ServiceCall): cards = TaskTrackerCardRegistration(hass) await cards.async_register(show_panel=show_panel) + await _async_register_blueprints(hass) + return True +async def _async_register_blueprints(hass: HomeAssistant) -> None: + """Register bundled automation blueprints into the HA config directory.""" + from homeassistant.components.automation.helpers import async_get_blueprints # noqa: PLC0415 + from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER # noqa: PLC0415 + from homeassistant.components.blueprint.errors import FileAlreadyExists # noqa: PLC0415 + from homeassistant.util.yaml import load_yaml_dict # noqa: PLC0415 + + domain_blueprints = async_get_blueprints(hass) + blueprints_path = pathlib.Path(__file__).parent / BLUEPRINT_FOLDER / "automation" / DOMAIN + for blueprint_file in blueprints_path.glob("*.yaml"): + blueprint_rel_path = f"{DOMAIN}/{blueprint_file.name}" + try: + bp_data = await hass.async_add_executor_job(load_yaml_dict, blueprint_file) + bp = blueprint.Blueprint( + bp_data, + expected_domain="automation", + path=blueprint_rel_path, + schema=domain_blueprints._blueprint_schema, + ) + await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=False) + _LOGGER.debug("Registered blueprint %s", blueprint_rel_path) + except FileAlreadyExists: + _LOGGER.debug("Blueprint %s already exists, skipping", blueprint_rel_path) + except Exception as err: # noqa: BLE001 + _LOGGER.warning("Failed to register blueprint %s: %s", blueprint_rel_path, err) + + async def async_update_entities(entity_ids: list[str], hass: HomeAssistant) -> dict | None: """Update the entity.""" try: From a7771379061c0105e3d18de07e7818f57b41c73b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 18:03:43 +0000 Subject: [PATCH 05/16] fix: always overwrite blueprints on setup and guard against missing blueprints directory Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- __init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__init__.py b/__init__.py index eb62c45..727ee2d 100644 --- a/__init__.py +++ b/__init__.py @@ -114,11 +114,13 @@ async def _async_register_blueprints(hass: HomeAssistant) -> None: """Register bundled automation blueprints into the HA config directory.""" from homeassistant.components.automation.helpers import async_get_blueprints # noqa: PLC0415 from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER # noqa: PLC0415 - from homeassistant.components.blueprint.errors import FileAlreadyExists # noqa: PLC0415 from homeassistant.util.yaml import load_yaml_dict # noqa: PLC0415 domain_blueprints = async_get_blueprints(hass) blueprints_path = pathlib.Path(__file__).parent / BLUEPRINT_FOLDER / "automation" / DOMAIN + if not blueprints_path.is_dir(): + _LOGGER.debug("No bundled blueprints found at %s", blueprints_path) + return for blueprint_file in blueprints_path.glob("*.yaml"): blueprint_rel_path = f"{DOMAIN}/{blueprint_file.name}" try: @@ -129,10 +131,8 @@ async def _async_register_blueprints(hass: HomeAssistant) -> None: path=blueprint_rel_path, schema=domain_blueprints._blueprint_schema, ) - await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=False) + await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=True) _LOGGER.debug("Registered blueprint %s", blueprint_rel_path) - except FileAlreadyExists: - _LOGGER.debug("Blueprint %s already exists, skipping", blueprint_rel_path) except Exception as err: # noqa: BLE001 _LOGGER.warning("Failed to register blueprint %s: %s", blueprint_rel_path, err) From 636fbd175774e39d24e331c82d6a5e2c7f3cebbc Mon Sep 17 00:00:00 2001 From: gensyn Date: Wed, 5 Aug 2026 20:08:45 +0200 Subject: [PATCH 06/16] Fixed notify selector --- blueprints/automation/task_tracker/notify_due_tasks.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blueprints/automation/task_tracker/notify_due_tasks.yaml b/blueprints/automation/task_tracker/notify_due_tasks.yaml index dd082db..f63614e 100644 --- a/blueprints/automation/task_tracker/notify_due_tasks.yaml +++ b/blueprints/automation/task_tracker/notify_due_tasks.yaml @@ -19,7 +19,8 @@ blueprint: description: >- The notify action to call (e.g. notify.mobile_app_my_phone). selector: - action: {} + entity: + domain: notify user_tag: name: User tag description: >- From b2349c6a95058b21bdbdbac9de223260b3d0d839 Mon Sep 17 00:00:00 2001 From: gensyn Date: Wed, 5 Aug 2026 20:11:59 +0200 Subject: [PATCH 07/16] Add blueprint to dependencies --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 03cad9f..bd99b93 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,7 @@ "@gensyn" ], "config_flow": true, - "dependencies": [], + "dependencies": ["blueprint"], "documentation": "https://github.com/gensyn/task_tracker", "homekit": {}, "iot_class": "calculated", From e7a3976d42ee4483d478a58ee75f29f7fb1a62b2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:09:26 +0000 Subject: [PATCH 08/16] feat: add EN/DE translations to blueprint using mui technique Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- .../task_tracker/notify_due_tasks.yaml | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/blueprints/automation/task_tracker/notify_due_tasks.yaml b/blueprints/automation/task_tracker/notify_due_tasks.yaml index f63614e..a637233 100644 --- a/blueprints/automation/task_tracker/notify_due_tasks.yaml +++ b/blueprints/automation/task_tracker/notify_due_tasks.yaml @@ -5,7 +5,7 @@ blueprint: Only tasks whose overdue_by value is a multiple of their notification_interval will trigger a notification, preventing repeated daily alerts. - The notify action and user tag can be customised via the blueprint inputs. + The notify action, user tag, and language can be customised via the blueprint inputs. domain: automation input: notification_time: @@ -19,8 +19,7 @@ blueprint: description: >- The notify action to call (e.g. notify.mobile_app_my_phone). selector: - entity: - domain: notify + action: {} user_tag: name: User tag description: >- @@ -29,6 +28,15 @@ blueprint: default: user_tag selector: text: {} + language: + name: Language + description: Language used for notification messages. + default: en + selector: + language: + languages: + - en + - de triggers: - trigger: time @@ -38,6 +46,22 @@ conditions: [] actions: - variables: + language_tmp: !input language + language: > + {{ + language_tmp + if language_tmp is string and language_tmp in ["de"] + else "en" + }} + mui: + en: + due_since_today: "due today" + due_since_yesterday: "due since yesterday" + due_since_days: "due since %d days" + de: + due_since_today: "heute fällig" + due_since_yesterday: "seit gestern fällig" + due_since_days: "seit %d Tagen fällig" tasks: |- [ {%- for entity_id in integration_entities('task_tracker') %} {%- if "sensor." in entity_id and states[entity_id].state == "due" %} @@ -64,9 +88,14 @@ actions: data: title: "{{ states[repeat.item].attributes.friendly_name }}" message: >- - {%- set overdue_by = states[repeat.item].attributes.overdue_by - %} due since {{ 'today' if overdue_by == 0 else ('yesterday' - if overdue_by == 1 else overdue_by ~ ' days') }} + {%- set overdue_by = states[repeat.item].attributes.overdue_by -%} + {%- if overdue_by == 0 -%} + {{ mui[language].due_since_today }} + {%- elif overdue_by == 1 -%} + {{ mui[language].due_since_yesterday }} + {%- else -%} + {{ mui[language].due_since_days | replace('%d', overdue_by | string) }} + {%- endif -%} data: tag: "{{ repeat.item }}" color: green From b32a6eb3601f83f42e6a90e0c7c9a32139319acd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 07:12:26 +0000 Subject: [PATCH 09/16] Add French language support to notify_due_tasks blueprint Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- blueprints/automation/task_tracker/notify_due_tasks.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/blueprints/automation/task_tracker/notify_due_tasks.yaml b/blueprints/automation/task_tracker/notify_due_tasks.yaml index a637233..6846797 100644 --- a/blueprints/automation/task_tracker/notify_due_tasks.yaml +++ b/blueprints/automation/task_tracker/notify_due_tasks.yaml @@ -37,6 +37,7 @@ blueprint: languages: - en - de + - fr triggers: - trigger: time @@ -50,7 +51,7 @@ actions: language: > {{ language_tmp - if language_tmp is string and language_tmp in ["de"] + if language_tmp is string and language_tmp in ["de", "fr"] else "en" }} mui: @@ -62,6 +63,10 @@ actions: due_since_today: "heute fällig" due_since_yesterday: "seit gestern fällig" due_since_days: "seit %d Tagen fällig" + fr: + due_since_today: "à faire aujourd'hui" + due_since_yesterday: "dû depuis hier" + due_since_days: "dû depuis %d jours" tasks: |- [ {%- for entity_id in integration_entities('task_tracker') %} {%- if "sensor." in entity_id and states[entity_id].state == "due" %} From 13835fac82df17a9d109c2de2692117d143db88e Mon Sep 17 00:00:00 2001 From: gensyn Date: Sun, 9 Aug 2026 16:53:06 +0200 Subject: [PATCH 10/16] Finished blueprint --- __init__.py | 32 ++++++++----------- ...ue_tasks.yaml => task_tracker_notify.yaml} | 21 ++++++------ 2 files changed, 23 insertions(+), 30 deletions(-) rename blueprints/{automation/task_tracker/notify_due_tasks.yaml => task_tracker_notify.yaml} (87%) diff --git a/__init__.py b/__init__.py index 727ee2d..32ce4d9 100644 --- a/__init__.py +++ b/__init__.py @@ -117,24 +117,20 @@ async def _async_register_blueprints(hass: HomeAssistant) -> None: from homeassistant.util.yaml import load_yaml_dict # noqa: PLC0415 domain_blueprints = async_get_blueprints(hass) - blueprints_path = pathlib.Path(__file__).parent / BLUEPRINT_FOLDER / "automation" / DOMAIN - if not blueprints_path.is_dir(): - _LOGGER.debug("No bundled blueprints found at %s", blueprints_path) - return - for blueprint_file in blueprints_path.glob("*.yaml"): - blueprint_rel_path = f"{DOMAIN}/{blueprint_file.name}" - try: - bp_data = await hass.async_add_executor_job(load_yaml_dict, blueprint_file) - bp = blueprint.Blueprint( - bp_data, - expected_domain="automation", - path=blueprint_rel_path, - schema=domain_blueprints._blueprint_schema, - ) - await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=True) - _LOGGER.debug("Registered blueprint %s", blueprint_rel_path) - except Exception as err: # noqa: BLE001 - _LOGGER.warning("Failed to register blueprint %s: %s", blueprint_rel_path, err) + blueprint_file = pathlib.Path(__file__).parent / BLUEPRINT_FOLDER / "task_tracker_notify.yaml" + blueprint_rel_path = f"{DOMAIN}/{blueprint_file.name}" + try: + bp_data = await hass.async_add_executor_job(load_yaml_dict, blueprint_file) + bp = blueprint.Blueprint( + bp_data, + expected_domain="automation", + path=blueprint_rel_path, + schema=domain_blueprints._blueprint_schema, + ) + await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=True) + _LOGGER.debug("Registered blueprint %s", blueprint_rel_path) + except Exception as err: # noqa: BLE001 + _LOGGER.warning("Failed to register blueprint %s: %s", blueprint_rel_path, err) async def async_update_entities(entity_ids: list[str], hass: HomeAssistant) -> dict | None: diff --git a/blueprints/automation/task_tracker/notify_due_tasks.yaml b/blueprints/task_tracker_notify.yaml similarity index 87% rename from blueprints/automation/task_tracker/notify_due_tasks.yaml rename to blueprints/task_tracker_notify.yaml index 6846797..525b38f 100644 --- a/blueprints/automation/task_tracker/notify_due_tasks.yaml +++ b/blueprints/task_tracker_notify.yaml @@ -5,7 +5,7 @@ blueprint: Only tasks whose overdue_by value is a multiple of their notification_interval will trigger a notification, preventing repeated daily alerts. - The notify action, user tag, and language can be customised via the blueprint inputs. + The notify action, tags, and language can be customised via the blueprint inputs. domain: automation input: notification_time: @@ -16,16 +16,13 @@ blueprint: time: {} notify_action: name: Notify action - description: >- - The notify action to call (e.g. notify.mobile_app_my_phone). + description: The notify action to call (e.g. notify.mobile_app_my_phone). selector: - action: {} - user_tag: - name: User tag - description: >- - The tag that must be present in a task's tags attribute for the - notification to be sent. - default: user_tag + text: {} + tags: + name: Tags + description: The tags that must be present in a task's tags attribute for the notification to be sent. + default: tag selector: text: {} language: @@ -80,14 +77,14 @@ actions: {%- endif %} {%- endif %} {%- endfor %} ] - configured_user_tag: !input user_tag + configured_tags: !input tags - repeat: for_each: "{{ tasks }}" sequence: - alias: Notify User if: - condition: template - value_template: "{{ configured_user_tag in states[repeat.item].attributes.tags }}" + value_template: "{{ configured_tags in states[repeat.item].attributes.tags }}" then: - action: !input notify_action data: From 4affaab1599d81184e70f8e57182808f2ecf14ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 14:56:23 +0000 Subject: [PATCH 11/16] Fix unit tests: move blueprint import inside _async_register_blueprints Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 32ce4d9..4cd8739 100644 --- a/__init__.py +++ b/__init__.py @@ -6,7 +6,6 @@ import pathlib import voluptuous as vol -from homeassistant.components import blueprint from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform, CONF_ICON, CONF_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall @@ -112,6 +111,7 @@ async def async_set_last_done_date(service_call: ServiceCall): async def _async_register_blueprints(hass: HomeAssistant) -> None: """Register bundled automation blueprints into the HA config directory.""" + from homeassistant.components import blueprint # noqa: PLC0415 from homeassistant.components.automation.helpers import async_get_blueprints # noqa: PLC0415 from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER # noqa: PLC0415 from homeassistant.util.yaml import load_yaml_dict # noqa: PLC0415 From d4067b48dbb19b489c12c1854a001de929033006 Mon Sep 17 00:00:00 2001 From: gensyn Date: Sun, 9 Aug 2026 16:57:35 +0200 Subject: [PATCH 12/16] Cleaned up imports --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 4cd8739..4389da9 100644 --- a/__init__.py +++ b/__init__.py @@ -10,7 +10,7 @@ from homeassistant.const import Platform, CONF_ICON, CONF_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.exceptions import ServiceValidationError, HomeAssistantError -from homeassistant.helpers import entity_registry, config_validation as cv +from homeassistant.helpers import entity_registry from homeassistant.helpers.event import async_track_time_change from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util From 0ee0ecee71f16dc4252759e66ddd2ced908d5bdb Mon Sep 17 00:00:00 2001 From: gensyn <36128035+gensyn@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:10:15 +0200 Subject: [PATCH 13/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- blueprints/task_tracker_notify.yaml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/blueprints/task_tracker_notify.yaml b/blueprints/task_tracker_notify.yaml index 525b38f..5a3f7ae 100644 --- a/blueprints/task_tracker_notify.yaml +++ b/blueprints/task_tracker_notify.yaml @@ -65,19 +65,19 @@ actions: due_since_yesterday: "dû depuis hier" due_since_days: "dû depuis %d jours" tasks: |- - [ {%- for entity_id in integration_entities('task_tracker') %} - {%- if "sensor." in entity_id and states[entity_id].state == "due" %} - {%- set last_done = as_datetime(states[entity_id].attributes.last_done) if 'last_done' in states[entity_id].attributes else None %} - {%- set overdue_by = states[entity_id].attributes.overdue_by if 'overdue_by' in states[entity_id].attributes else None %} - {%- set notification_interval = states[entity_id].attributes.notification_interval if 'notification_interval' in states[entity_id].attributes else None %} - {%- if last_done is not none and overdue_by is not none and notification_interval is not none %} - {%- if overdue_by % notification_interval == 0 %} - "{{ entity_id }}"{% if not loop.last %},{% endif %} - {%- endif %} - {%- endif %} - {%- endif %} - {%- endfor %} ] - configured_tags: !input tags + {%- set ns = namespace(items=[]) -%} + {%- for entity_id in integration_entities('task_tracker') -%} + {%- if entity_id.startswith('sensor.') and states[entity_id].state == "due" -%} + {%- set overdue_by = states[entity_id].attributes.overdue_by if 'overdue_by' in states[entity_id].attributes else None -%} + {%- set notification_interval = states[entity_id].attributes.notification_interval if 'notification_interval' in states[entity_id].attributes else None -%} + {%- if overdue_by is not none and notification_interval is not none and (overdue_by % notification_interval == 0) -%} + {%- set ns.items = ns.items + [entity_id] -%} + {%- endif -%} + {%- endif -%} + {%- endfor -%} + {%- for item in ns.items -%} + - {{ item }} + {%- endfor -%} - repeat: for_each: "{{ tasks }}" sequence: From afc84f413dd34ec8110d258111efaf523cbf8cc6 Mon Sep 17 00:00:00 2001 From: gensyn <36128035+gensyn@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:10:42 +0200 Subject: [PATCH 14/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 4389da9..a2feb18 100644 --- a/__init__.py +++ b/__init__.py @@ -110,7 +110,7 @@ async def async_set_last_done_date(service_call: ServiceCall): async def _async_register_blueprints(hass: HomeAssistant) -> None: - """Register bundled automation blueprints into the HA config directory.""" + """Register bundled automation blueprints with Home Assistant's blueprint manager.""" from homeassistant.components import blueprint # noqa: PLC0415 from homeassistant.components.automation.helpers import async_get_blueprints # noqa: PLC0415 from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER # noqa: PLC0415 From e88ffd13529706821752b4ea35bb87cdde708da2 Mon Sep 17 00:00:00 2001 From: gensyn <36128035+gensyn@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:11:17 +0200 Subject: [PATCH 15/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- __init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index a2feb18..706cef2 100644 --- a/__init__.py +++ b/__init__.py @@ -129,8 +129,8 @@ async def _async_register_blueprints(hass: HomeAssistant) -> None: ) await domain_blueprints.async_add_blueprint(bp, blueprint_rel_path, allow_override=True) _LOGGER.debug("Registered blueprint %s", blueprint_rel_path) - except Exception as err: # noqa: BLE001 - _LOGGER.warning("Failed to register blueprint %s: %s", blueprint_rel_path, err) + except Exception: # noqa: BLE001 + _LOGGER.exception("Failed to register blueprint %s", blueprint_rel_path) async def async_update_entities(entity_ids: list[str], hass: HomeAssistant) -> dict | None: From b5a55d6c3b0fc199477fe8ab8b089cb3083769ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 15:17:35 +0000 Subject: [PATCH 16/16] test: cover blueprint registration and hoist imports Co-authored-by: gensyn <36128035+gensyn@users.noreply.github.com> --- __init__.py | 9 ++-- .../components/automation/__init__.py | 0 .../components/automation/helpers.py | 2 + .../components/blueprint/__init__.py | 6 +++ .../components/blueprint/const.py | 1 + .../homeassistant/util/yaml.py | 2 + tests/unit_tests/test_init.py | 42 ++++++++++++++++++- 7 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 tests/unit_tests/homeassistant_mock/homeassistant/components/automation/__init__.py create mode 100644 tests/unit_tests/homeassistant_mock/homeassistant/components/automation/helpers.py create mode 100644 tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/__init__.py create mode 100644 tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/const.py create mode 100644 tests/unit_tests/homeassistant_mock/homeassistant/util/yaml.py diff --git a/__init__.py b/__init__.py index 706cef2..f0e4b9e 100644 --- a/__init__.py +++ b/__init__.py @@ -6,6 +6,9 @@ import pathlib import voluptuous as vol +from homeassistant.components import blueprint +from homeassistant.components.automation.helpers import async_get_blueprints +from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER from homeassistant.config_entries import ConfigEntry from homeassistant.const import Platform, CONF_ICON, CONF_ENTITY_ID from homeassistant.core import HomeAssistant, ServiceCall @@ -14,6 +17,7 @@ from homeassistant.helpers.event import async_track_time_change from homeassistant.helpers.typing import ConfigType from homeassistant.util import dt as dt_util +from homeassistant.util.yaml import load_yaml_dict from .const import DOMAIN, CONF_TASK_INTERVAL_VALUE, CONF_DAY, CONF_TASK_INTERVAL_TYPE, CONF_NOTIFICATION_INTERVAL, \ CONF_DUE_SOON_DAYS, CONF_DUE_SOON_OVERRIDE, CONF_TAGS, CONF_ACTIVE, CONF_TODO_LISTS, SERVICE_MARK_AS_DONE, \ @@ -111,11 +115,6 @@ async def async_set_last_done_date(service_call: ServiceCall): async def _async_register_blueprints(hass: HomeAssistant) -> None: """Register bundled automation blueprints with Home Assistant's blueprint manager.""" - from homeassistant.components import blueprint # noqa: PLC0415 - from homeassistant.components.automation.helpers import async_get_blueprints # noqa: PLC0415 - from homeassistant.components.blueprint.const import BLUEPRINT_FOLDER # noqa: PLC0415 - from homeassistant.util.yaml import load_yaml_dict # noqa: PLC0415 - domain_blueprints = async_get_blueprints(hass) blueprint_file = pathlib.Path(__file__).parent / BLUEPRINT_FOLDER / "task_tracker_notify.yaml" blueprint_rel_path = f"{DOMAIN}/{blueprint_file.name}" diff --git a/tests/unit_tests/homeassistant_mock/homeassistant/components/automation/__init__.py b/tests/unit_tests/homeassistant_mock/homeassistant/components/automation/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit_tests/homeassistant_mock/homeassistant/components/automation/helpers.py b/tests/unit_tests/homeassistant_mock/homeassistant/components/automation/helpers.py new file mode 100644 index 0000000..cc15fcd --- /dev/null +++ b/tests/unit_tests/homeassistant_mock/homeassistant/components/automation/helpers.py @@ -0,0 +1,2 @@ +def async_get_blueprints(hass): + return getattr(hass, "automation_blueprints", None) diff --git a/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/__init__.py b/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/__init__.py new file mode 100644 index 0000000..f53f8d6 --- /dev/null +++ b/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/__init__.py @@ -0,0 +1,6 @@ +class Blueprint: + def __init__(self, data, expected_domain, path, schema): + self.data = data + self.expected_domain = expected_domain + self.path = path + self.schema = schema diff --git a/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/const.py b/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/const.py new file mode 100644 index 0000000..2a194ed --- /dev/null +++ b/tests/unit_tests/homeassistant_mock/homeassistant/components/blueprint/const.py @@ -0,0 +1 @@ +BLUEPRINT_FOLDER = "blueprints" diff --git a/tests/unit_tests/homeassistant_mock/homeassistant/util/yaml.py b/tests/unit_tests/homeassistant_mock/homeassistant/util/yaml.py new file mode 100644 index 0000000..7d661e7 --- /dev/null +++ b/tests/unit_tests/homeassistant_mock/homeassistant/util/yaml.py @@ -0,0 +1,2 @@ +def load_yaml_dict(path): + return {} diff --git a/tests/unit_tests/test_init.py b/tests/unit_tests/test_init.py index 619cabd..76ebb1b 100644 --- a/tests/unit_tests/test_init.py +++ b/tests/unit_tests/test_init.py @@ -1,7 +1,7 @@ import sys import unittest from pathlib import Path -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock, MagicMock, patch absolute_mock_path = str(Path(__file__).parent / "homeassistant_mock") sys.path.insert(0, absolute_mock_path) @@ -12,7 +12,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_ICON -from task_tracker import async_migrate_entry, _get_coordinator +from task_tracker import _async_register_blueprints, async_migrate_entry, _get_coordinator from task_tracker.const import ( CONF_ACTIVE, CONF_TASK_INTERVAL_VALUE, CONF_TASK_INTERVAL_TYPE, CONF_TAGS, CONF_TODO_LISTS, CONF_DUE_SOON_DAYS, CONF_DUE_SOON_OVERRIDE, @@ -419,3 +419,41 @@ def test_raises_when_coordinator_not_in_hass_data(self): with self.assertRaises(ValueError): _get_coordinator(mock_hass, "sensor.task_tracker_my_task") + +class TestRegisterBlueprints(unittest.IsolatedAsyncioTestCase): + + async def test_registers_bundled_blueprint(self): + mock_hass = MagicMock() + mock_hass.async_add_executor_job = AsyncMock(return_value={"blueprint": {}}) + domain_blueprints = MagicMock() + domain_blueprints._blueprint_schema = {} + domain_blueprints.async_add_blueprint = AsyncMock() + + with ( + patch("task_tracker.async_get_blueprints", return_value=domain_blueprints), + patch("task_tracker.blueprint.Blueprint", return_value=MagicMock()) as mock_blueprint, + ): + await _async_register_blueprints(mock_hass) + + mock_hass.async_add_executor_job.assert_awaited_once() + async_job_args = mock_hass.async_add_executor_job.call_args.args + self.assertGreaterEqual(len(async_job_args), 2) + blueprint_file = async_job_args[1] + self.assertEqual(blueprint_file.name, "task_tracker_notify.yaml") + mock_blueprint.assert_called_once() + domain_blueprints.async_add_blueprint.assert_awaited_once() + + async def test_logs_and_continues_when_blueprint_registration_fails(self): + mock_hass = MagicMock() + mock_hass.async_add_executor_job = AsyncMock(side_effect=RuntimeError("boom")) + domain_blueprints = MagicMock() + domain_blueprints._blueprint_schema = {} + + with ( + patch("task_tracker.async_get_blueprints", return_value=domain_blueprints), + patch("task_tracker._LOGGER.exception") as mock_exception, + ): + await _async_register_blueprints(mock_hass) + + mock_exception.assert_called_once() + domain_blueprints.async_add_blueprint.assert_not_called()