Skip to content

Architecture Design Document

acdcnow edited this page Sep 21, 2026 · 3 revisions

Architecture Design Document (ADD)

Applies to integration 2.0.x · branch ha2026_09_dev · version 2.0.0-beta.2 · Home Assistant 2026.9+

Supersedes Design Documentation 1.1.3 (Archived). Companion documents: Software Design Document · Workflow Diagrams · Documentation home.


1. Purpose and scope

This document explains the architecture of the TimeTree integration: the forces that shaped it, the components it is made of, the decisions that were taken and the alternatives that were rejected. It is the reference for anyone who maintains, reviews or extends the integration.

1.1 In scope

  • The integration inside custom_components/timetree/ — everything from the config flow to the export loop.
  • The contract with Home Assistant (config entries, entities, services, diagnostics, storage).
  • The contract with the TimeTree web API (endpoints, payloads, authentication, anti-bot measures).
  • The synchronisation model between TimeTree, Home Assistant and an optional second calendar ("export target") including conflict handling.

1.2 Out of scope

  • TimeTree's own backend behaviour, rate limits and terms of service.
  • The implementation of other integrations used as export targets (Google Calendar, local calendar, another TimeTree account, …).
  • Card/frontend behaviour in Home Assistant (the integration only produces CalendarEvent objects).
  • Hardware, HAOS installation and HACS packaging details (see the README).

2. Stakeholders and requirements

Stakeholder Interest
Home Assistant user see and manage the real TimeTree schedule in Home Assistant; keep a second calendar (Google) in sync without losing data
Maintainer a codebase that survives TimeTree API drift and Home Assistant releases; automated verification without a live HA instance
Home Assistant core an integration that follows the config-entry, entity, service and diagnostics conventions of 2026.9
TimeTree an API client that behaves like the official web client and does not hammer the backend

2.1 Functional requirements

ID Requirement Where it is implemented
FR-1 Import every event of every selected TimeTree calendar, including all chunked pages api.TimeTreeApi._get_events, coordinator.TimeTreeCoordinator
FR-2 Expose each calendar as a selectable calendar.* entity with its own device calendar.TimeTreeCalendarEntity, entity.py, __init__.py
FR-3 Create, update and delete events from Home Assistant calendar.py (entity API), api.py (write endpoints)
FR-4 Handle recurring events: expand occurrences per query range, keep the series rule recurrence.py
FR-5 Handle all-day events with correct inclusive/exclusive semantics in both directions models.py (ha_end, build_event_payload)
FR-6 Enrich events with labels, colours, attendees, locations, URLs and optional comments api.py, models.py, calendar.py
FR-7 Export events into a second calendar on a selectable schedule export.ExportManager, config_flow.py
FR-8 Detect divergence between TimeTree and the exported copy and queue a conflict when data would be lost conflicts.decide, export.ExportManager
FR-9 Let a human resolve a conflict (either side, newest, skip, ignore forever) export.ExportManager.async_resolve_conflict, services.py
FR-10 Persist sync state across restarts store.TimeTreeSyncStore
FR-11 Provide diagnostics and status entities for troubleshooting diagnostics.py, sensor.py
FR-12 Support multiple accounts/entries and multiple calendars per entry config flow, options flow, per-calendar devices

2.2 Non-functional requirements

ID Requirement Target
NFR-1 Home Assistant compatibility 2026.9+ (entry.runtime_data, ConfigFlowResult, device registry API)
NFR-2 No blocking work in the event loop every HTTP call and every recurrence expansion runs in an executor
NFR-3 Bounded work per refresh MAX_OCCURRENCES = 1000, MAX_EXPANSION_DAYS = 1098, chunked sync aborts after 100 pages
NFR-4 Secrets never leak credentials redacted in diagnostics and never logged; debug logs carry payloads only at debug level
NFR-5 Failure isolation one broken event/calendar must not break a refresh or an export run
NFR-6 Idempotent exports repeated runs must not duplicate events
NFR-7 Dependency policy only what Home Assistant does not already provide: curl_cffi, python-dateutil
NFR-8 Testability without HA HA-free core modules plus a stub harness (tests/)
NFR-9 Language support all user visible strings translated (strings.json, translations/en.json, translations/de.json)

3. Context

3.1 Business context

TimeTree is one of the most widely used shared calendar apps, especially for families, but it has no public API and no Home Assistant integration. Users who want their family calendar on a dashboard — and who additionally keep a Google Calendar for other purposes — had no way to see TimeTree in Home Assistant, let alone to write into it or keep two calendars aligned.

The integration closes that gap by acting as the official web client: it signs in with the user's e-mail and password, speaks the same internal JSON API the web app uses, and behaves like a normal Home Assistant calendar entity.

3.2 Technical context and constraints

Constraint Consequence
No public API; TimeTree's internal https://timetreeapp.com/api/v1 is used endpoints can change without notice → defensive parsing, endpoint fallbacks, extensive debug logging
Session cookie _session_id from PUT /auth/email/signin sessions expire → transparent re-login on 401
Write endpoints sit behind a WAF that fingerprints the TLS client hello plain requests gets a generic 422 → curl_cffi with impersonate="firefox135", browsery headers, scraped X-CSRF-Token
Home Assistant expects expanded occurrences with an exclusive end the integration owns expansion and both day-boundary conversions
Exported copies must be recognisable in a foreign calendar marker [timetree:<uuid>] appended to the description
Export targets differ in capability (Google: create + delete, local calendar: create + update + delete) capability detection on the target entity and a create-before-delete fallback
Wi-Fi/pre-1970/negative timestamps and ZoneInfo availability epoch arithmetic instead of fromtimestamp, get_zone() fallback to UTC

4. System context

flowchart LR
  subgraph users["People"]
    user(("Home Assistant user"))
    automation(("Automation / script"))
  end

  subgraph ha["Home Assistant runtime"]
    subgraph tt["custom_components/timetree (2.0.x)"]
      flow["Config & options flow"]
      coord["TimeTreeCoordinator"]
      cal["Calendar entities (one per calendar)"]
      sens["Sensors: last updated, conflicts, last export, export status"]
      exp["ExportManager (loop + conflict engine)"]
      store[("Sync store<br/>timetree.sync.entry_id.json")]
    end
    core["HA core: config entries, entity/device registry,<br/>services, event bus, dispatcher, persistent notification"]
    target["Export target entity<br/>(e.g. Google Calendar)"]
  end

  ttcloud{{"TimeTree web API<br/>timetreeapp.com/api/v1"}}
  google{{"Google Calendar"}}

  user -->|"setup, options, services"| flow
  automation -->|"calendar.create_event / get_events"| cal
  user -->|"resolve_conflict, export_now"| exp
  flow --> coord
  coord -->|"REST + cookie session"| ttcloud
  cal -->|"REST writes (curl_cffi)"| ttcloud
  coord --> cal
  coord --> sens
  exp -->|"get_events / create / update / delete"| target
  exp -->|"write back (two_way)"| coord
  exp <--> store
  target -->|"Google sync"| google
  tt -.-> core
Loading

5. Component decomposition

Group Components Responsibility
Configuration config_flow.TimeTreeConfigFlow, config_flow.TimeTreeOptionsFlow, options.TimeTreeOptions credentials, calendar selection, all tunables, legacy 1.x layout migration
Integration lifecycle __init__.py (runtime data, hub device, platforms, services, options listener), services.py wiring, reload behaviour, service surface
Transport api.TimeTreeApi, transport factory, CSRF helper, error taxonomy authentication, session handling, read and write endpoints, rate-limit friendly logging
Domain model models.py, recurrence.py, conflicts.py parsing, all-day and timezone semantics, occurrence expansion, change detection and the decision table
Orchestration coordinator.TimeTreeCoordinator, coordinator.TimeTreeData / CalendarData polling, per calendar snapshots, write passthrough with refresh
Presentation entity.py, calendar.py, sensor.py devices, calendar entity (read/write), status and conflict sensors
Synchronisation export.ExportManager, export.CalendarTarget, export.TargetWrite, store.TimeTreeSyncStore export loop, target adapter, marker pairing, conflict queue, persisted state, re-baseline
Operational diagnostics.py, strings.json, translations/*, brand/* supportability, localisation, branding
flowchart TD
  subgraph config["Configuration"]
    cf["Config / options flow"]
    opt["TimeTreeOptions"]
  end
  subgraph runtime["Orchestration"]
    init["__init__ / runtime data"]
    coord["TimeTreeCoordinator"]
    svc["services.py"]
  end
  subgraph domain["Domain (HA free)"]
    models["models.py"]
    rec["recurrence.py"]
    conf["conflicts.py"]
  end
  subgraph net["Transport"]
    api["TimeTreeApi"]
  end
  subgraph present["Presentation"]
    ent["entity.py"]
    cal["calendar.py"]
    sen["sensor.py"]
  end
  subgraph sync["Synchronisation"]
    exp["ExportManager"]
    tgt["CalendarTarget"]
    store["TimeTreeSyncStore"]
  end
  tt{{"TimeTree API"}}
  oth{{"Target calendar"}}

  cf --> opt --> init
  init --> coord
  init --> exp
  init --> svc
  coord --> api
  api --> tt
  api --> models
  models --> rec
  coord --> cal
  ent --> cal
  coord --> sen
  cal --> api
  cal --> rec
  exp --> coord
  exp --> tgt --> oth
  exp --> conf
  exp <--> store
  svc --> exp
  svc --> coord
Loading

6. Architectural decisions

AD-1 — Hub style config entry: one entry per account, N calendars

Context. 1.1.3 created one config entry per calendar, duplicating credentials and sessions. Users with a family account have three or more calendars.

Decision. One config entry holds the account (email/password) and a list of selected calendars (CONF_CALENDARS); every calendar gets its own calendar.* entity, its own device and its own diagnostic sensor. Options can add or remove calendars at any time.

Rationale. One login, one API session, one coordinator fetch pass for N calendars; the user picks calendars once. unique_id is the e-mail address, which prevents double setup.

Consequences. Entity ids are derived from the calendar name/device, so they change relative to 1.1.3. Removing a calendar in the options flow removes its entity and device.

Alternatives rejected. (a) Keep one entry per calendar — duplicated credentials and sessions; (b) a "sub-entry" style design — not supported by HA.

AD-2 — Continue to use the internal web API, but isolate it

Context. There is no public, documented TimeTree API. The web client API is the only option and it changes occasionally (for example the events → event write route).

Decision. All HTTP knowledge lives in api.py, spoken through one TimeTreeApi class. Parsing never happens there; it is delegated to models.py, which is Home-Assistant-free.

Rationale. API drift can be absorbed inside one file; the domain model stays testable.

Consequences. Endpoint changes require a release, but the fallback route list (/event/{uuid} → /events/{uuid}) softens that.

AD-3 — Browser-shaped writes via curl_cffi

Context. TimeTree's write endpoints are behind a WAF that checks the TLS fingerprint (issue #5): the identical request fails with requests/curl and succeeds with curl_cffi impersonating Firefox.

Decision. The transport is built by create_transport(): prefer curl_cffi.requests.Session(impersonate="firefox135"), fall back to requests.Session. Writes additionally send Origin, Referer, Sec-Fetch-* and a scraped X-CSRF-Token. browser_transport is exposed in diagnostics so support can see which stack is active.

Rationale. Reads work on both stacks, so the cheap fallback keeps the integration usable even if curl_cffi cannot be installed; writes degrade with a clear log warning and a readable error instead of a mysterious 422.

Consequences. A native dependency ships with the integration; the impersonation profile must be kept in sync with curl_cffi releases (documented risk R-1).

AD-4 — Own persistence in a versioned HA store

Context. 1.1.3 kept everything in hass.data; the synchronisation requirements (fingerprints, target uids, conflicts) need to survive restarts and reloads.

Decision. TimeTreeSyncStore wraps homeassistant.helpers.storage.Store with the key timetree.sync.<entry_id> and a versioned schema (records, conflicts, meta), written with async_delay_save(…, 10) and async_save_now() for conflict decisions.

Rationale. HA's store handles atomic writes and versioning; a debounce avoids a write per event; the data stays local (no cloud sync).

Consequences. The store can grow with the number of managed events; records are dropped when an event disappears on both sides.

AD-5 — Marker based pairing for exported copies

Context. calendar.create_event returns nothing usable, targets generate their own ids, and Google's ical_uuid cannot be set through the service API.

Decision. Every exported copy carries [timetree:<uuid>] at the end of its description. On each run the target window is indexed by that marker; the TimeTree uuid is the single identity key across records, markers and conflicts. A discovered target uid is stored opportunistically for update/delete calls.

Rationale. It works for every target that round-trips description (Google does), needs no extra API, and survives restarts.

Consequences. The description of exported copies contains a marker (documented in the README). Targets that rewrite descriptions lose the pairing (documented limitation).

AD-6 — Fingerprint snapshots instead of timestamps

Context. Neither Home Assistant's CalendarEvent nor most targets expose a reliable modification timestamp. "Who changed last?" cannot be answered generically.

Decision. EventState projects the synchronised fields (summary, description, location, start, end, all_day, rrule) into a stable SHA-256 fingerprint; the store keeps the fingerprints of the last synced state per side. decide() derives source-changed and target-changed by comparison.

Rationale. Deterministic, independent of clock skew and target metadata; works for any target.

Consequences. newest_wins can only use the TimeTree updated_at (see AD-7).

AD-7 — Conflicts are first-class and manual by default

Context. A one-way export is easy; the moment the user edits the exported copy, silent overwrites lose data.

Decision. decide() returns a Decision; ambiguous cases become a Conflict that is stored, surfaced (sensor attributes, persistent notification, timetree_conflict_detected event) and resolved manually through timetree.resolve_conflict with use_timetree, use_export_target, use_newest, skip or ignore_forever. Policies (manual, source_wins, target_wins, newest_wins) allow automation for users who do not want to be asked.

Rationale. "Never lose data silently" is the requirement that drove the feature. A queued conflict plus a visible sensor is more honest than last-write-wins.

Consequences. A user who ignores conflicts accumulates them; skip re-baselines, ignore_forever excludes the event permanently.

AD-8 — Occurrence expansion inside the integration

Context. Home Assistant renders exactly the CalendarEvent list an integration returns for a requested range. 1.1.3 returned one event with a malformed rule, which broke every view (issue #2).

Decision. recurrence.py expands occurrences for the requested window with dateutil.rrule/rruleset (supporting EXDATE, RDATE, COUNT, UNTIL, all-day series), sets recurrence_id per occurrence and hands the bare rule to Home Assistant for the master event. Unsupported (lunar) rules fall back to the stored occurrence.

Rationale. Matches Home Assistant core (local_calendar materialises a timeline and returns occurrences), keeps dashboards correct and never breaks a whole view because of one odd rule.

Consequences. Expansion runs in an executor and is bounded (MAX_OCCURRENCES, MAX_EXPANSION_DAYS).

AD-9 — All-day semantics converted at the model boundary

Context. TimeTree stores all-day events as UTC midnights with an inclusive end (a one day event has start_at == end_at); Home Assistant expects an exclusive end (issue #5, both directions).

Decision. TimeTreeEvent.ha_end returns the exclusive end (+1 day), and build_event_payload() converts back (-1 day, UTC midnight, start_timezone = "UTC").

Rationale. One place for the conversion keeps read, write and export consistent and matches the captured web-client payload.

Consequences. Exported all-day copies use Home Assistant's exclusive convention, which is correct for Google.

AD-10 — Target-agnostic export adapter

Context. The requirement names Google Calendar, but Home Assistant has many calendar entities with different feature sets (UPDATE_EVENT is optional).

Decision. The options flow selects any calendar.* entity. CalendarTarget wraps the entity object, detects CREATE_EVENT/UPDATE_EVENT/DELETE_EVENT, and, when a target cannot update in place, creates the replacement first and deletes the old copy afterwards.

Rationale. One implementation serves Google, local calendars, other TimeTree accounts and future integrations. Create-before-delete means a failed write never loses data.

Consequences. Since Google only supports create/delete, one event change costs two API calls. Deletion failures are logged, not fatal.

AD-11 — Re-baseline after every write instead of trusting round trips

Context. Targets normalise what we write (line breaks, extra properties) and TimeTree adjusts timestamps. Assuming "what we sent is what they store" produced false conflicts.

Decision. After a run with writes, _async_rebaseline() refreshes the coordinator and re-reads the target window, then stores the observed fingerprints/uids for the touched events.

Rationale. The next run compares real state with real state; false "both changed" conflicts disappear.

Consequences. One extra read per side per changed run (only when something changed).

AD-12 — Recurrence and comments are opt-in where they are expensive

Context. Event comments require one request per event; recurrence expansion can explode.

Decision. include_comments is off by default and fetched with a bounded thread pool (MAX_COMMENT_WORKERS = 4); expansion is capped; the export window is configurable (past/future days, MAX_EXPORT_DAYS = 730).

Rationale. A refresh must stay fast and must not trigger TimeTree rate limits.

AD-13 — Service surface instead of hidden magic

Context. Users need to trigger an export, inspect conflicts and decide about them.

Decision. Six services (refresh_now, export_now, list_conflicts, resolve_conflict, resolve_all_conflicts, get_event) with voluptuous schemas, response support where it is useful (SupportsResponse.ONLY|OPTIONAL) and services.yaml plus translations. Two bus events (timetree_export_completed, timetree_conflict_detected) allow automations to react.

Rationale. Everything the integration does can also be observed and driven from automations; nothing requires editing files in .storage.

AD-14 — Home Assistant 2026.9 API conformance

Context. HA 2026.8/2026.9 deprecated several APIs (hass.data for integration state, via_device, default_name, merge_identifiers, async_get_device, the OptionsFlow constructor taking the entry).

Decision. Use entry.runtime_data, ConfigFlowResult, DeviceInfo with identifiers/via_device_id, dr.async_get_or_create, AddConfigEntryEntitiesCallback and an OptionsFlow that reads self.config_entry. The manifest declares integration_type, iot_class, issue_tracker, loggers, homeassistant: 2026.9.0.

Rationale. The 1.1.3 code did not run on 2026.9 without these changes (issues #1, #3).

Consequences. Hard minimum version 2026.9 — users on older HA must stay on 1.1.3.


7. Cross-cutting concerns

7.1 Error handling strategy

Layer Strategy
Transport (api.py) error taxonomy TimeTreeError → TimeTreeAuthError / TimeTreeApiError / TimeTreeWriteError; 401 triggers one transparent re-login; -702 (Temporarily blocked) becomes a readable auth message
Coordinator ConfigEntryAuthFailed for credential problems (HA starts the reauth flow), UpdateFailed for transient errors, per-calendar error collection so one broken calendar does not hide the others
Entities HomeAssistantError with an actionable text for create/update/delete; occurrence-level edits of recurring events are refused explicitly
Export loop per-event try/except (one bad event never aborts the run), report carries errors, run state exposed in the export status sensor
Persistence store writes are delayed/debounced; a corrupt store is reported by diagnostics rather than crashing setup

7.2 Dependency policy

Dependency Why it is needed Why it is acceptable
curl_cffi>=0.7.0 writes need a browser TLS fingerprint (WAF) reads fall back to requests; the fallback is logged and visible in diagnostics
python-dateutil>=2.8.2 recurrence expansion (rrule, rruleset) same library Home Assistant core uses in its calendar component
requests fallback transport provided by Home Assistant core

7.3 Logging

  • debug on custom_components.timetree shows login attempts, payloads, endpoint paths, conflict decisions and marker matches — never credentials.
  • Warnings are reserved for actionable problems (write rejected, conflict detected, some calendars failed, target not found).
  • The export run always records an ExportReport (counters + errors) in the store and in the timetree_export_completed event.

7.4 Security and privacy

  • Credentials live only in the config entry (HA encrypted storage); they are never logged and are redacted in diagnostics (async_redact_data).
  • The session cookie is held in memory by the API client.
  • The sync store contains event metadata (titles/times/fingerprints), not credentials.
  • The marker [timetree:<uuid>] is the only TimeTree specific content written into a foreign calendar.

7.5 Time and timezone handling

  • Timestamps are converted with epoch arithmetic (EPOCH + timedelta) so negative values (pre-1970) work everywhere.
  • Timed events keep the timezone of the TimeTree event; Home Assistant converts to the local zone for display.
  • All-day events are UTC midnights; the date is derived with the event's timezone and the end is exclusive on the Home Assistant side.
  • get_zone() degrades to UTC when a zone is unknown (for example on hosts without a tz database) instead of raising.

7.6 Idempotency and bounded work

  • Exports are idempotent: matching is by uuid marker, and unchanged fingerprints produce Action.NONE.
  • The sync cursor chain is followed to the end but aborts after 100 pages.
  • Expansion, comment fetching, export windows and conflict lists are all bounded.

8. Deltas versus 1.1.3

Area 1.1.3 2.0.x
Modules 7 (api, calendar, config_flow, const, coordinator, sensor, __init__) 15 (+ models, recurrence, conflicts, export, store, services, options, entity, diagnostics, strings.json, services.yaml, brand/, translations)
Calendars per entry 1 N (multi-select, options flow)
Event source first page of events/sync only full cursor chain
Recurrence raw RRULE: string handed to HA → view broke expanded occurrences + bare rule + EXDATE/RDATE
All-day off by one in both directions inclusive ⇄ exclusive conversion, UTC midnights
Write create only, wrong kwargs, wrong route, 422 create/update/delete, browser payload, CSRF, curl_cffi, route fallback
State hass.data[DOMAIN][entry_id] entry.runtime_data dataclass
Persistence none versioned Store with records, conflicts, meta
Second calendar – export loop (ExportManager)
Conflicts – decision table, policies, sensor, notification, services
Labels/attendees ignored parsed and exposed (issue #6)
Diagnostics – redacted dump
Branding – brand/icon@2x, logo@2x, dark variants
Minimum HA 2025.12 2026.9
Verification manual 41 tests / 224 checks in tests/

9. Quality attributes

Attribute Scenario How it is met
Correctness of schedules a daily recurring event with one EXDATE is queried for a month rruleset expansion, EXDATE respected (tested)
Correctness of day boundaries an all-day event on 5 Oct is created in HA and read back from TimeTree -1 day on write, +1 day on read (round-trip tested)
Availability TimeTree returns 401 mid-run one transparent re-login, then UpdateFailed
Data safety both TimeTree and Google changed conflict queued, nothing written (tested)
Recoverability TimeTree is unreachable for hours last known snapshot stays; sensor shows stale Last updated
Performance a calendar with 500 events and 50 recurring series executor jobs, bounded expansion, single fetch pass per refresh
Supportability a user reports "nothing syncs" diagnostics (transport, counters, records, conflicts) + debug logging
Extensibility a user wants a second target calendar change the target entity in the options; a per-target mapping is stored

10. Risks and trade-offs

ID Risk Impact Mitigation
R-1 curl_cffi's impersonation profile ages out; TimeTree tightens its WAF writes fail with 422 profile is a single constant, diagnostics show the active transport, README documents the symptom; reads are unaffected
R-2 The update/delete routes (PUT/DELETE /calendar/{id}/event/{uuid}) were inferred from the create route and are not confirmed against a live account update/delete could fail plural fallback is attempted automatically, error is surfaced as HomeAssistantError; verification item on the release checklist
R-3 TimeTree changes the payload requirements again writes fail payload mirrors the captured web-client payload (label_id, attendees, attachment, recurrences, alerts); all fields are in one builder
R-4 Internal API change of the read shape import breaks parsing is defensive (missing keys default), labels/comments are optional
R-5 Marker lost (target rewrites descriptions) duplicate copies instead of updates duplicates are reported as duplicate exported copy; ignore_forever can retire an event
R-6 newest_wins cannot compare target timestamps policy may pick the wrong side it only auto-resolves single-sided changes and still asks when both changed (ADR 7)
R-7 Export loop + HA restart timing a missed run the loop schedules a run 45 s after setup and every interval thereafter
R-8 Store growth for large calendars slow saves debounced writes, records dropped when both sides deleted the event

11. Extension points and roadmap

Extension Touch points
Another export target type nothing to change — any calendar.* entity works
Per-calendar export settings options.py (per CalendarOption), export.py (target resolution)
Import unmanaged target events (already flagged, default off) export.py (_async_import_unmanaged), decide() TARGET_ONLY
Single-occurrence edits / deletes calendar.py guard + TimeTree API research (TimeTree currently exposes series only)
Comments/images as attachments api.py activity endpoints, models.py (TimeTreeEvent.comments)
Public calendars (/api/v2/public_calendars/...) new read path in api.py, no auth
Additional conflict resolutions (merge fields) conflicts.resolution_to_action, export._async_apply
HACS default repository hacs.json, README, brand assets — already in place

12. References

Reference Link
Software Design Document Software-Design-Document
Workflow diagrams (GitDiagram + authored) Workflow-Diagrams
Archived 1.1.3 design + defect list Archive-1.1.3-Design-Documentation
Issue #2 (rrule), #3 (create kwargs), #5 (write/422/all-day), #6 (labels), #7 (recurrence PR) https://github.com/acdcnow/Timetree-Import-for-Home-Assistant/issues
TimeTree-Exporter (API reference implementation) https://github.com/eoleedi/TimeTree-Exporter
Home Assistant developer documentation https://developers.home-assistant.io/
Home Assistant 2026.9 device registry deprecations https://developers.home-assistant.io/docs/device_registry_index/

TimeTree Calendar

🏠 Documentation home


🟢 Current — 2.0.x (branch ha2026_09_dev, HA 2026.9+)


🗄️ Archived — 1.1.3 (branch main)


🔗 Links

Clone this wiki locally