Repository navigation
Workflow Diagrams
Applies to integration 2.0.x · branch
ha2026_09_dev· version2.0.0-beta.2This page holds the visual documentation: the repository map produced with GitDiagram plus the hand-maintained component, sequence and state diagrams. The reasoning behind them lives in the Architecture Design Document and the Software Design Document.
Generated with GitDiagram — 15 components · 23 connections, generation re-verified 2026-09-21.
Provenance note. GitDiagram indexes the repository default branch, which is
mainand therefore still v1.1.3. The map below is the generated artefact as-is; it shows the 1.1.3 topology (api.py,calendar.py,config_flow.py,coordinator.py,sensor.py,__init__.py, create-only write support). You can tell which revision a generated map came from by its links: every node here points atblob/main/....The branch cannot be selected. Verified on 2026-09-21:
…?branch=ha2026_09_devis ignored (the default-branch diagram is served unchanged) and the path formgitdiagram.com/acdcnow/timetree-import-for-home-assistant/ha2026_09_devreturns 404. A fresh regeneration on that date still produced this 15-componentmainmap, so the artefact is current formain— it is the branch, not the diagram, that trails.The 2.0.x topology is a superset: it adds the domain modules (
models.py,recurrence.py,conflicts.py), the synchronisation modules (export.py,store.py,services.py,options.py,entity.py,diagnostics.py) and the brand/translation assets. That delta is drawn in section 2 and described in ADD §8. Once the release branch is merged intomain, regenerate as described in section 4 to make this map match the current line.
flowchart TD
subgraph group_configuration["Configuration"]
node_config_flow["Config Flow<br/>[config_flow.py]"]
node_options_flow["Options Flow<br/>[config_flow.py]"]
node_config_entry["Config Entry<br/>[__init__.py]"]
end
subgraph group_runtime["Sync Runtime"]
node_api_client["TimeTree API<br/>[api.py]"]
node_coordinator["Data Coordinator<br/>[coordinator.py]"]
node_event_parser["Event Parser<br/>[api.py]"]
end
subgraph group_entities["Home Assistant Entities"]
node_calendar_entity["Calendar Entity<br/>[calendar.py]"]
node_updated_sensor["Last Updated Sensor<br/>[sensor.py]"]
end
subgraph group_integration["Integration Lifecycle"]
node_integration_setup["Integration Setup<br/>[__init__.py]"]
node_calendar_platform["Calendar Platform<br/>[calendar.py]"]
node_sensor_platform["Sensor Platform<br/>[sensor.py]"]
end
node_ha_user(("Home Assistant User"))
node_ha_automation(("HA Automation"))
node_timetree_cloud{{"TimeTree Cloud"}}
node_ha_platforms["HA Platforms"]
node_ha_user -->|"configures"| node_config_flow
node_ha_automation -->|"creates events"| node_calendar_entity
node_config_flow -->|"validates credentials"| node_api_client
node_api_client -->|"logs in"| node_timetree_cloud
node_api_client -->|"lists calendars"| node_timetree_cloud
node_config_flow -->|"creates entry"| node_config_entry
node_ha_user -->|"adjusts interval"| node_options_flow
node_options_flow -->|"updates options"| node_config_entry
node_config_entry -->|"loads entry"| node_integration_setup
node_integration_setup -->|"constructs client"| node_api_client
node_integration_setup -->|"constructs coordinator"| node_coordinator
node_integration_setup -->|"initial refresh"| node_coordinator
node_integration_setup -->|"forwards setups"| node_ha_platforms
node_coordinator -->|"fetches events"| node_api_client
node_api_client -->|"syncs events"| node_timetree_cloud
node_coordinator -->|"parses events"| node_event_parser
node_calendar_platform -->|"creates entity"| node_calendar_entity
node_sensor_platform -->|"creates entity"| node_updated_sensor
node_calendar_entity -->|"reads events"| node_coordinator
node_calendar_entity -->|"creates event"| node_api_client
node_calendar_entity -->|"requests refresh"| node_coordinator
node_updated_sensor -->|"reads status"| node_coordinator
node_coordinator -->|"notifies update"| node_updated_sensor
classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_config_flow,node_options_flow,node_config_entry,node_ha_user toneBlue
class node_api_client,node_coordinator,node_event_parser toneAmber
class node_calendar_entity,node_updated_sensor toneMint
class node_integration_setup,node_calendar_platform,node_sensor_platform toneRose
class node_ha_automation,node_timetree_cloud,node_ha_platforms toneIndigo
(The click links of the exported diagram are omitted here; the generated diagram links
every node to the file on main.)
Maintained by hand from the actual module layout of ha2026_09_dev: 21 components ·
34 connections. Node labels carry the file that implements them, so the map stays in sync
with the SDD module inventory.
flowchart TD
subgraph group_domain["Domain model (Home Assistant free)"]
node_models["Event / Calendar Models<br/>[models.py]"]
node_recurrence["Recurrence Expansion<br/>[recurrence.py]"]
node_conflicts["Change Detection and Decision Table<br/>[conflicts.py]"]
node_const["Constants<br/>[const.py]"]
end
subgraph group_transport["Transport"]
node_api["TimeTree API Client<br/>[api.py]"]
node_csrf["CSRF + Browser Headers<br/>[api.py]"]
node_transport["Transport Factory (curl_cffi / requests)<br/>[api.py]"]
end
subgraph group_orchestration["Orchestration"]
node_setup["Entry Setup and Runtime Data<br/>[__init__.py]"]
node_options["Options and 1.x Migration<br/>[options.py]"]
node_coordinator["Data Coordinator<br/>[coordinator.py]"]
node_services["Services<br/>[services.py]"]
end
subgraph group_entities["Entities"]
node_entity_base["Entity and Device Bases<br/>[entity.py]"]
node_calendar["Calendar Entity<br/>[calendar.py]"]
node_sensors["Sensors: last updated, conflicts,<br/>last export, export status<br/>[sensor.py]"]
end
subgraph group_config["Configuration"]
node_config_flow["Config Flow<br/>[config_flow.py]"]
node_options_flow["Options Flow (menu)<br/>[config_flow.py]"]
end
subgraph group_sync["Synchronisation"]
node_exporter["Export Manager (loop)<br/>[export.py]"]
node_target["Calendar Target Adapter<br/>[export.py]"]
node_store["Sync Store<br/>[store.py]"]
end
subgraph group_ops["Operational"]
node_diagnostics["Diagnostics<br/>[diagnostics.py]"]
node_i18n["Strings and Translations<br/>[strings.json, translations/]"]
node_brand["Brand Assets<br/>[brand/]"]
end
node_ha_user(("Home Assistant User"))
node_ha_auto(("Automation / Script"))
node_tt_api{{"TimeTree API"}}
node_export_target{{"Export target entity<br/>(e.g. Google Calendar)"}}
node_ha_user -->|"configures"| node_config_flow
node_config_flow -->|"validates credentials"| node_api
node_config_flow -->|"stores calendars"| node_setup
node_ha_user -->|"changes settings"| node_options_flow
node_options_flow -->|"reloads entry"| node_setup
node_setup -->|"reads options"| node_options
node_setup -->|"creates hub device"| node_entity_base
node_setup -->|"starts"| node_exporter
node_setup -->|"registers"| node_services
node_setup -->|"creates"| node_coordinator
node_coordinator -->|"reads + writes"| node_api
node_coordinator -->|"parses"| node_models
node_api -->|"uses"| node_transport
node_api -->|"authenticates, syncs, writes"| node_tt_api
node_api -->|"write requests"| node_csrf
node_calendar -->|"expands occurrences"| node_recurrence
node_calendar -->|"maps"| node_models
node_calendar -->|"reads snapshots"| node_coordinator
node_calendar -->|"create / update / delete"| node_coordinator
node_ha_auto -->|"calendar.create_event"| node_calendar
node_coordinator -->|"notifies"| node_sensors
node_sensors -->|"reads conflicts and reports"| node_store
node_exporter -->|"reads source events"| node_coordinator
node_exporter -->|"writes back (two_way)"| node_coordinator
node_exporter -->|"create / update / delete"| node_target
node_target -->|"entity API"| node_export_target
node_exporter -->|"fingerprints, records, conflicts"| node_conflicts
node_exporter -->|"persists"| node_store
node_services -->|"export_now, resolve_conflict, ..."| node_exporter
node_services -->|"refresh_now"| node_coordinator
node_conflicts -->|"change classification"| node_models
node_recurrence -->|"occurrences"| node_models
node_const -->|"keys, defaults, limits"| node_options
node_diagnostics -->|"reads"| node_coordinator
node_diagnostics -->|"reads"| node_store
node_diagnostics -->|"redacts"| node_options
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_config_flow,node_options_flow,node_ha_user toneBlue
class node_setup,node_options,node_coordinator,node_services toneAmber
class node_models,node_recurrence,node_conflicts toneMint
class node_calendar,node_entity_base,node_sensors toneRose
class node_api,node_csrf,node_transport,node_tt_api toneIndigo
class node_exporter,node_target,node_store,node_export_target toneTeal
class node_diagnostics,node_i18n,node_brand,node_ha_auto toneBlue
flowchart TD
start(["User adds the integration"]) --> creds["Enter e-mail + password"]
creds --> login{"Login and<br/>calendar list OK?"}
login -->|"invalid_auth"| creds
login -->|"cannot_connect"| creds
login -->|"ok"| pick["Select one or more calendars"]
pick --> entry["Config entry created<br/>data: email, password, calendars[]"]
entry --> first{"First refresh<br/>succeeds?"}
first -->|"ConfigEntryAuthFailed"| reauth["Home Assistant starts<br/>the reauth flow"]
first -->|"UpdateFailed"| retry["Entities unavailable,<br/>retry on the next interval"]
first -->|"ok"| runtime["Runtime data:<br/>api, coordinator, store, exporter, hub device"]
runtime --> platforms["Calendar + sensor platforms<br/>one calendar entity and device per calendar"]
runtime --> services["6 services registered"]
runtime --> loop{"Export loop<br/>enabled?"}
loop -->|"yes"| schedule["Interval timer + startup run after 45 s"]
loop -->|"no"| idle["Idle until export_enabled is set"]
sequenceDiagram
autonumber
participant T as HA timer (scan interval)
participant CO as TimeTreeCoordinator
participant API as TimeTreeApi
participant E as Entities / Sensors
T->>CO: _async_update_data()
CO->>API: async_get_calendars() [executor]
API-->>CO: names, members, alias codes
loop per selected calendar
CO->>API: async_fetch_events(...)
API->>API: GET events/sync, follow "chunk" cursor (max 100 pages)
API->>API: GET labels (+ activities when include_comments)
API-->>CO: events (birthdays filtered, deleted dropped)
end
CO->>CO: build TimeTreeData snapshots
CO-->>E: update notifications
E->>E: calendar rebuilds lookahead (180 days, 25 occurrences)
E->>E: sensors expose last update, conflicts, export status
flowchart TD
req(["async_get_events(start, end)"]) --> gather["Collect all events of the calendar"]
gather --> each{"For every event"}
each -->|"not recurring"| single["Single occurrence<br/>+ overlap test"]
each -->|"recurring"| rule{"Usable RRULE?<br/>FREQ present"}
rule -->|"no"| fallback["Fall back to the stored occurrence"]
rule -->|"yes"| normalize["Normalise date-only UNTIL<br/>to 23:59:59 (timed events)"]
normalize --> set["Build rruleset:<br/>rrule + RDATE - EXDATE"]
set --> iterate["xafter(window - duration, max 1000)"]
iterate --> occ["Occurrence objects<br/>start, end, recurrence_id"]
single --> map
occ --> map["Map to CalendarEvent<br/>uid, recurrence_id, rrule"]
fallback --> map
map --> out(["Sorted list for the requested range"])
flowchart TD
call(["calendar.create_event / update_event / delete_event"]) --> feature{"Entity supports<br/>the feature?"}
feature -->|"no"| err1["HomeAssistantError"]
feature -->|"yes"| guard{"Single occurrence of<br/>a recurring series?"}
guard -->|"yes"| err2["Refused: TimeTree only<br/>exposes whole series"]
guard -->|"no"| times["_resolve_times()<br/>dtstart/dtend or legacy keys"]
times --> payload["build_event_payload()<br/>all-day: -1 day + UTC midnight<br/>timed: ms + timezone name"]
payload --> auth["Login if needed<br/>scrape CSRF token"]
auth --> send["POST/PUT/DELETE /calendar/{id}/event[/{uuid}]"]
send --> status{"Accepted?"}
status -->|"401"| relogin["Re-login once and retry"] --> send
status -->|"404 / 405"| plural["Try the plural resource"] --> send
status -->|"422"| retry["Refresh CSRF token, retry once"]
retry --> fail["TimeTreeWriteError →<br/>HomeAssistantError for the user"]
status -->|"200 / 201 / 204"| refresh["Request a coordinator refresh"]
refresh --> done(["Entities show the new state"])
flowchart TD
tick(["Timer / timetree.export_now"]) --> enabled{"export_target set<br/>and entity found?"}
enabled -->|"no"| err["ExportError in the report<br/>export status = error"]
enabled -->|"yes"| window["Read target events in the window<br/>now - past_days … now + future_days"]
window --> index["Index by marker<br/>[timetree:uuid], count unmanaged"]
index --> loop{"For every known uuid"}
loop --> classify["EventState(source) + EventState(target)<br/>+ stored SyncRecord"]
classify --> decide{"decide()"}
decide -->|"CREATE / UPDATE / DELETE"| apply["Apply on the target<br/>(create before delete if needed)"]
apply --> record["Store expected fingerprints"]
decide -->|"UPDATE / CREATE / DELETE source"| source["Write back into TimeTree (two_way)"]
source --> record
decide -->|"CONFLICT"| conflict["Queue conflict, notify,<br/>fire timetree_conflict_detected"]
decide -->|"NONE / IGNORE"| skip["Count as unchanged / skipped"]
record --> next(["Next event"])
conflict --> next
skip --> next
next --> loop
loop -->|"done"| rebase{"Something changed<br/>and not a dry run?"}
rebase -->|"yes"| refresh["Refresh TimeTree + re-read target,<br/>store observed fingerprints"]
rebase -->|"no"| report
refresh --> report["Store last_report + last_export,<br/>fire timetree_export_completed"]
flowchart TD
detected(["Divergence detected"]) --> source_changed{"Actually changed<br/>on both sides?"}
source_changed -->|"only source"| upd["Update the exported copy"]
source_changed -->|"only target"| policy{"Policy and direction"}
source_changed -->|"both"| policy
policy -->|"two_way"| back["Write the target version into TimeTree"]
policy -->|"source_wins"| overwrite["Overwrite the target copy"]
policy -->|"target_wins"| back
policy -->|"manual / newest_wins + both changed"| queue["Conflict queued:<br/>sensor attribute, notification, event"]
queue --> decide{"User resolves"}
decide -->|"use_timetree"| win_src["Target gets the TimeTree version<br/>(or the copy is deleted)"]
decide -->|"use_export_target"| win_tgt["TimeTree gets the copy version<br/>(or the source is deleted)"]
decide -->|"use_newest"| newest["Compare updated_at where available,<br/>then as above"]
decide -->|"skip"| skip["Accept the divergence as the new baseline"]
decide -->|"ignore_forever"| ignore["Mark the event ignored,<br/>never sync it again"]
win_src --> resolved(["Conflict removed"])
win_tgt --> resolved
newest --> resolved
skip --> resolved
ignore --> resolved
stateDiagram-v2
[*] --> Unmanaged: TimeTree event exists
Unmanaged --> Exported: CREATE_TARGET (marker written)
Exported --> InSync: re-baseline (both fingerprints stored)
InSync --> SourceChanged: TimeTree edited
InSync --> TargetChanged: exported copy edited
SourceChanged --> InSync: UPDATE_TARGET
TargetChanged --> Conflict: manual policy
TargetChanged --> InSync: two_way / source_wins
SourceChanged --> Conflict: target changed too
Conflict --> InSync: use_timetree / use_export_target / use_newest
Conflict --> Divergent: skip (re-baselined, both versions kept)
Conflict --> Ignored: ignore_forever
InSync --> SourceRemoved: TimeTree event deleted
InSync --> TargetRemoved: exported copy deleted
SourceRemoved --> Conflict: copy changed meanwhile
SourceRemoved --> InSync: DELETE_TARGET / CREATE_SOURCE
TargetRemoved --> InSync: recreate_removed
TargetRemoved --> Conflict: manual policy
Divergent --> [*]
Ignored --> [*]
- Open https://gitdiagram.com/acdcnow/timetree-import-for-home-assistant.
- Use Regenerate to analyse the current default branch (GitDiagram reads the default
branch, so merge the release branch into
mainfirst if you want the map to describe the current line). There is no branch or commit selector — the?branch=query parameter is ignored and a/owner/repo/branchURL is a 404 (verified 2026-09-21). -
Copy Mermaid and paste the result into section 1
(keep the
clicklinks or drop them, as preferred). - Update the component/connection counters and the provenance note in section 1 with the new generation date.
The authored map in section 2 has to be updated manually whenever a module is added or a connection changes; the SDD module inventory is the source of truth.
| Document | Contents |
|---|---|
| Architecture Design Document | requirements, context, components, 14 decisions, deltas, risks |
| Software Design Document | modules, contracts, component design, data model, error matrix, traceability |
| Documentation home | which document for which situation |
| Archived 1.1.3 design | the former guide plus the defect list |
🟢 Current — 2.0.x
(branch ha2026_09_dev, HA 2026.9+)
🗄️ Archived — 1.1.3
(branch main)
🔗 Links