Release Notes
Detailed history of homeCore releases. Each entry summarises what operators see — UI changes, stability fixes, configuration updates, and any flags that affect upgrade. Per-component git tags and CI state are tracked separately on the ci-glance dashboard.
Component releases follow per-component SemVer; the 0.1.x
cohort moves roughly together but not all components ship in every
patch round. The release matrix for each version below lists which
components were tagged.
v0.1.6 — 2026-07-26
Theme: One artifact per component — and devices that no plugin owns.
Two unrelated bodies of work land together, because 0.1.6 is the first tagged round since 0.1.5 in May.
Deployment and release (operator-visible)
homeCore now ships as two containers, wired together by compose:
| Image | What it is |
|---|---|
ghcr.io/homecore-io/hc-core | The REST/WebSocket API and the embedded MQTT broker. |
ghcr.io/homecore-io/hc-web | The web UI. nginx serves the app and proxies /api/v1/* to core. |
- The appliance image is retired.
homecore-appliancebaked core and every plugin into one container. Plugins install at runtime from the signed registry, so the merged image was a different product from the one being shipped. Existing appliance images stay in GHCR and keep working; none are published any more. Thehomecore-appliance-*.tar.gzrelease archive is gone for the same reason. - hc-core no longer carries a web UI. It used to bundle a Leptos WASM
build.
hc-webis the UI now, released for the first time at v0.1.6. - Repaired image tags.
hc-core:latest,:0.1.5and:0.1.4had all become unpullable — the tags existed but the amd64 child manifest each one referenced had been deleted in the 2026-05-11 GHCR cleanup incident, sodocker pullfailed with "content not found". v0.1.6 republishes a working:latest. The cleanup job now fails rather than pruning against an incomplete protected set, which is what allowed it. - amd64 only. aarch64 tarballs and images had in practice not been produced for months — the matrix entry was switched off behind a hardcoded override while the release still advertised a multi-arch toggle. The pipeline now says amd64 and means it. If you need arm64, say so; restoring it is a small change.
- The whole plugin registry is current. Ten plugins had shipped
versions well ahead of what the index served (
hc-lutronwas at 0.1.10 against a published 0.1.4). All are reconciled, andplugin.rokuis published for the first time.
Upgrade: docker compose pull && docker compose up -d. Installed plugins
live in the data directory, not the image, so they are untouched.
Devices that no plugin owns
Theme: the silence around them.
A keypad button that had been dead since May turned out to be pointing at a Hue group the Hue plugin had stopped managing back in April. The device was still listed, still accepted commands, and still reported state — the state just never changed again. Everything downstream believed it.
This round fixes that button, deletes the devices behind it, and adds the check that would have caught it in April.
The failure, for anyone who might hit it
A device can outlive its plugin's interest in it. Turn off
publish_grouped_lights in the Hue plugin and its grouped lights stop
being registered — but homeCore's registration and retained state for
them persist. Such an orphaned device then fails silently in both
directions:
- Commands vanish. Plugins subscribe to command topics per-device,
only for devices they registered. An orphan has no subscriber, so the
broker drops its commands. No error, anywhere — and the rule engine
still records the action as
fired: success. - State freezes. Its attributes stay at whatever they last were, forever, and rule conditions keep reading them as truth.
Together those wedge a toggle permanently. The affected keypad had:
Btn1 "On" — condition: group.on == false → turn on ← could never pass
Btn1 "Off" — condition: group.on == true → turn off ← always passed
The group was frozen on: true, so the "On" rule never fired once in
two months (condition_failed, 9 of 9 presses), the "Off" rule fired
every time, and its command evaporated at the broker. The button looked
broken; every layer reported success.
Added
-
GET /api/v1/devices/orphaned— finds devices homeCore holds that their owning plugin no longer manages.Detection is a count comparison, not a staleness threshold. Every management-capable plugin self-reports
device_count; if core holds more devices for that plugin than the plugin claims, the difference is exactly the orphan count. Flagging onlast_seenage is the obvious approach and is wrong — plugins that publish only on change leave healthy devices cold for months (a wall switch nobody flips is not orphaned), so an age rule cries wolf on exactly the quiet devices you care least about.Worth running once after upgrading. On the system this was found on it immediately turned up 11 more orphans in a second plugin, unrelated and unnoticed.
Fixed
hc-huenow reports a command sent to a Hue device it no longer manages — awarnplus a failedplugin_command_result— instead of silently discarding it as another plugin's device. (This covers a device pruned mid-run; a device orphaned across a restart is caught by/devices/orphanedabove, since its command never reaches the plugin at all.)hc-huewarns, rather than staying quiet, when it skips the cross-restart reconcile that retires stale devices.- Plugin SDK warns when it cannot load its published-device snapshot. That file is the only record of devices registered in earlier runs, so losing it silently disables stale-device cleanup for every plugin.
Documentation
openapi.yamlnow matches the server — 89 of 89 routes, 124 of 124 operations, up from 39. It had also stopped parsing entirely at some point (an unquoteddescription:containing": "reads as YAML mapping syntax), so nothing could consume it. Both fixed; the "adding an endpoint" checklist now includes documenting it, which is what was missing.
Upgrade notes
-
Orphans from before the SDK's device-snapshot mechanism cannot be auto-cleaned.
reconcile_devicescomputesstale = known − livefrom its persisted.published-device-ids.json; devices registered before that file existed were never written to it, soknown == liveand the stale set is empty. They are structurally invisible to the reconcile — running the plugin's cleanup stale devices action will never remove them and will log nothing when it doesn't.Delete them explicitly:
GET /api/v1/devices/orphaned # review the suspects first
DELETE /api/v1/devices/{id}Check
affected_rulesin the delete response — deletion cascades into rule files, disabling any automation that referenced the device. An empty list means nothing was disturbed.
v0.1.5 — 2026-05-07
Theme: Comfort fix + diagnostic toolkit + client UX cleanup.
A larger-than-usual patch round that closes out a cluster of operator-reported bugs and lands the WS-observability work that was deferred from 0.1.3.
Added
GET /api/v1/logs— REST log-tail companion to the WebSocket/api/v1/logs/stream. Same filter semantics (level,targetprefix), plussince=<RFC3339>for incremental polling andlimit(default 100, cap 1000). CLI-friendly:curl ... | jqno longer needs a websocket client. Same auth as the WS endpoint.GET /api/v1/ws/connections(admin-only) — live registry of every active WebSocket connection (events_stream- logs_stream) with
connection_id,endpoint,client_id,ip,user,user_agent,connected_at. Distinguishes "one looping client" from "many churning clients" during reconnect-storm investigations.
- logs_stream) with
- WS lifecycle Prometheus metrics —
homecore_ws_connects_total{endpoint}andhomecore_ws_disconnects_total{endpoint, reason}. Thereasonlabel uses the same categories as the disconnect log (see Changed). Combined with/api/v1/metricsscraping, dashboards can alert onpong_timeoutrate (network/proxy issues) independent of normalclient_closechurn. hc-thermostatoperator-triggered force-recalc — therecalculate_allplugin command now accepts{"force": true}which re-issues actuator commands unconditionally (regardless of cachedactuator_state). Recovery path for the cached-vs-physical-reality drift the comfort fix below was added to prevent in the first place.
Changed
- WS disconnect log carries a
reasonfield — seven categorised strings (client_close,socket_closed,recv_error,pong_timeout,ping_send_failed,event_send_failed,bus_closed) replace the previously-indistinguishableINFO "WebSocket client disconnected".logs/streamgot the same treatment with its own four categories. Reconnect storms are now diagnosable from logs alone. hc-thermostatsettle gate — after a plugin or appliance restart, the bridge holds command issuance until every configured sensor has reported at least once, then issues an unconditional command for the desired state. Fixes the case where the cachedactuator_state(restored from retained MQTT) matched the desired state but physical reality had drifted (power blip, manual flip, dropped Z-Wave frame) — the transition-only path never re-synced.- Version-banner is direction-aware (Leptos admin) — only fires when the server is strictly newer than the tab. The prior any-mismatch trigger fired during in-flight upgrades too, where Reload couldn't help. Banner copy also rewritten for clarity ("homeCore was upgraded — server is on v0.1.5, this tab is still on v0.1.4. Reload to pick up the new client.").
- Overview Security widget honours per-device opt-out — unchecking a contact-sensor or lock on its detail page now actually excludes it from the Security tile. Previously the default-set fallback ("all locks + contact sensors when no tags are explicit") swept unchecked devices back in. New explicit-exclude store; checkbox semantics now match operator expectations.
- Overview widget click resets Devices-page filters —
clicking a Security/Climate/etc. tile now overrides
persisted filter chips on the Devices page, instead of
intersecting with them. Previously a stored
area_filter=["kitchen"]from a prior session could produce an empty view when clicking through to a system with no Kitchen devices.
Fixed
- Cargo.lock metadata drift in 9 plugin repos — the
per-repo lockfiles' "version" line for the local crate now
matches the
Cargo.toml(was 0.1.2 lingering after the v0.1.3 ceremony due to the meta-layout workspace shadowing per-plugin builds). Cosmetic — release-pipeline binaries were already correct. /healthand/system/statusreport the binary's version, not hc-api's (HEALTH-VERSION-SOURCE-1). Core's binary now passesCARGO_PKG_VERSIONinto AppState and the handlers read it from there. Removes the bump-all-17-workspace-crates workaround that v0.1.4 needed — future "tag only what changed" releases can bump just the top-level Cargo.toml.
Release matrix
Tagged at v0.1.5 (4 repos): homeCore (core),
hc-web-leptos, hc-thermostat, homeCore-io/docker
(orchestration; triggers appliance build).
Skipped (no tag, only branch merge for cosmetic Cargo.lock
sync): 9 plugins (hc-hue, hc-yolink, hc-lutron,
hc-sonos, hc-wled, hc-isy, hc-zwave, hc-caseta,
hc-ecowitt).
Appliance image:
ghcr.io/homecore-io/homecore-appliance:0.1.5 published.
Bundles core 0.1.5 + leptos 0.1.5 + thermostat 0.1.5 +
plugins still at 0.1.3.
Upgrade notes
- Stuck thermostat from before 0.1.5? If you upgraded
from 0.1.4 with an actuator that had drifted from the
plugin's cached state (the symptom: AC outlet visibly off
while the thermostat shows
actuator_state: true), invoke the new force path:Or: flip the setpoint by ±1°F to force a transition the old way.POST /api/v1/plugins/plugin.thermostat/command
{"action":"recalculate_all", "force": true}
v0.1.4 — 2026-05-07 (hotfix)
Theme: /health version-reporting fix.
Same-day hotfix after v0.1.3. The v0.1.3 ceremony bumped only
the top-level homecore Cargo.toml, but /health and
/system/status are implemented in the hc-api sub-crate and
read its CARGO_PKG_VERSION — which was still at 0.1.2.
A v0.1.3 appliance image therefore reported version: 0.1.2
through /health, the Leptos sidebar, and the login page.
Fixed
- Bumped every workspace crate (top-level
homecore+ 16 sub-crates) to 0.1.4 in lockstep, sohc-api'sCARGO_PKG_VERSIONlines up with the binary's.hc-web-leptosrode along to keep the WASM bundle'sclient_versionin sync with the new server.
Release matrix
Tagged at v0.1.4 (3 repos): homeCore, hc-web-leptos,
homeCore-io/docker. Appliance image
ghcr.io/homecore-io/homecore-appliance:0.1.4 published.
Upgrade notes
- The proper architectural fix for this fragility ships in v0.1.5 (HEALTH-VERSION-SOURCE-1). Lockstep-bump-everything is no longer required from v0.1.5 forward.
v0.1.3 — 2026-05-06
Theme: Leptos admin reliability + automation hygiene.
The browser-side admin client got a substantial reliability pass after field reports of disconnect storms and stale state. Three of the four fixes are behavioural changes invisible until you look at the WebSocket events log; the fourth is a banner that finally tells you your tab is running old code.
Added
hc-web-leptosfirst-ever release tag. The web admin client has shipped through the appliance image since 0.1.0; this is its first standalonev*git tag, joining the per-component SemVer cohort.- Stale-WASM banner. When the operator deploys a new homeCore,
every existing browser tab keeps running the WASM it loaded
earlier (
Ctrl+Shift+Ronly reloads the active tab). The admin now compares its embeddedCARGO_PKG_VERSIONagainst/api/v1/healthon connect and on every WS reconnect; on mismatch a "new version available — Reload" banner appears at the top of the viewport. Dismissable per server-version insessionStorage. - Renovate digest-bump automation. Every base-image digest
pinned in 0.1.2 (alpine 3.23, rust 1.95-alpine3.23) now has
Renovate watching it. Weekly Monday probe; digest + patch tag
bumps auto-merge on green CI; minor/major land as PRs labelled
review-required. Twelve repos covered. - CI for
hc-web-leptos. The crate now has a.github/workflows/ci.yml(fmt + tests via the sharedhc-scripts/rust-ci.yml). It also joined the ci-glance dashboard. - Per-repo
Dockerfileforhc-caseta,hc-ecowitt,hc-thermostat. These three plugins missed the original Dockerfile copy-pass; now in line with the other seven plugins sodocker build .works from each plugin's checkout.
Changed
- WebSocket survives navigation. Routing between admin pages
(Devices → Scenes → Areas, etc.) used to tear down the
WebSocket and reopen it ~10 ms later — visible as a continuous
disconnect/connect storm in the events log and as occasional
missed state updates. The shared
WsContextis now hoisted above the router so the connection lives for the session. - Cached state self-heals on reconnect. When the WebSocket disconnects (real network blips, server restarts), the device and plugin maps now re-fetch from REST on every reconnect. Previously, state changes that happened during the disconnect window were silently lost until the next event for that device. In particular: the symptom "Sonos shows paused while the speaker is actually playing, until something nudges it" should no longer recur.
- Plugins consume
plugin_sdk_rs::types::*/plugin_sdk_rs::logging::*re-exports. Eleven plugins (hc-hue,hc-yolink,hc-lutron,hc-sonos,hc-wled,hc-isy,hc-zwave,hc-caseta,hc-ecowitt,hc-thermostat,hc-captest) dropped their directhc-types/hc-logginggit deps and now consume those surfaces through the SDK. One upstream-homeCore dependency per plugin instead of three. SDK SemVer becomes the only homeCore-side version a plugin needs to track. - Release-tag policy: tag only what changed. Starting with
0.1.3 the project no longer publishes a fixed N-tag table per
release. Components with operator-impact changes get a tag;
components whose only changes are configuration that doesn't
affect the produced binary (e.g.
.github/renovate.json) are skipped. The appliance is always retagged because it bundles whatever's latest.
Fixed
- Expired session leaves the user logged in. Before this
release, a JWT that expired mid-session was only detected on
the next API write — read-only browsing of cached state stayed
"alive" indefinitely. The admin now checks token expiry every
30 seconds and bounces to
/loginproactively. The 401-handler in the API client also reliably clears the session signal now (ause_contextlookup insidespawn_localpreviously silently failed).
Release matrix
Tagged at v0.1.3 (13 repos): homeCore (core), hc-web-leptos
(first tag), hc-hue, hc-yolink, hc-lutron, hc-sonos,
hc-wled, hc-isy, hc-zwave, hc-caseta, hc-ecowitt,
hc-thermostat, homeCore-io/docker (orchestration; triggers
appliance build).
Skipped (no tag, only branch merge): hc-captest (dev-only
repo, not in production release flow).
Appliance image:
ghcr.io/homecore-io/homecore-appliance:0.1.3 published.
Upgrade notes
- Hard-refresh at least one browser tab after pulling the new appliance — the new banner is the long-term answer, but the first tab on the new server still needs a manual reload because it loaded the old WASM before the banner code existed.
- Renovate onboarding PRs may appear once Mend Renovate
Cloud's first scan completes (24-48 h after the App was
installed). They contain default config; close them — the
canonical
renovate.jsonalready shipped onmainin each repo.
Note on Phase F (the "tag only what changed" rule)
This was the first release applying our new policy of tagging
only components whose binaries differ from the prior release
(rather than the lockstep "tag everything" approach used through
0.1.2). The initial pass tagged 12 components and skipped two
(homeCore, hc-captest) whose only changes were
.github/renovate.json. Operators reported the appliance still
showing v0.1.2 in the Leptos sidebar — because the sidebar reads
core's CARGO_PKG_VERSION which was untouched. Resolved by
amending the rule: components that ship inside the appliance
image (today: core, plus the WASM bundled into core) ride with
the appliance tag even when their own diff is binary-irrelevant.
Core was retagged at v0.1.3 and the appliance was rebuilt with
the new core image. Future releases follow the amended rule.
v0.1.2 — 2026-05-05
Theme: WebSocket reliability + version correctness + base image hardening.
A targeted patch round after a 0.1.1 deploy debugging session
exposed three orthogonal weaknesses: the server-side WS loop
mishandled some edge cases, the tooling couldn't tell which
component a heartbeat came from, and the Dockerfiles all floated
on a single alpine:3.20 tag.
Added
GET /api/v1/system/versions— BOM endpoint returning{appliance, core, built_at, plugins: {hc-*: version, ...}}. The appliance image stampsversions.jsonat build time so this endpoint always serves accurate per-component versions even when components ship at independent SemVers.- Plugin heartbeat carries
sdk_version— auto-populated from the SDK crate's ownCARGO_PKG_VERSIONat compile time. Core's state bridge reads this on first heartbeat per plugin per session and warns (does not refuse) on MAJOR/MINOR divergence fromhc-types::PROTOCOL_VERSION. - Per-tab
client_idfingerprint. The Leptos admin generates a UUID insessionStorageon first connect and includes it in every WS/SSE URL. Server logs now correlate reconnect storms to a specific tab instead ofclient_id="-".
Changed
- Server WebSocket loop refactored to
select!oversocket.recv(), a 30-second ping ticker, and the event bus — closed clients are detected within one ping interval rather than waiting for the next event broadcast. NAT/proxy idle timeouts also kept warm. - Activity page WebSocket consolidated. Pre-0.1.2, the
Activity page opened its own
/events/streamsocket in addition to the shared NavShell socket. Both cycled in lockstep, doubling the disconnect-storm signal. The page now subscribes to the sharedWsContext.latest_eventsignal. - Base images digest-pinned. Three canonical Dockerfiles in
homeCore-io/dockerplus eight per-plugin local Dockerfiles moved fromalpine:3.20(floating) toalpine:3.23@sha256:5b10f432…(3.23.4 digest-pinned). Driven by a kernel CVE in the 3.20 lineage. Rust toolchain pinned to1.95across the board. apk upgraderuns in every Dockerfile beforeapk add --no-cacheso CVE patches in named packages land even on cached layers.
Fixed
- Cargo.toml version-correctness sweep. Several plugin and
client crates had stale
version = "0.1.0"lines despite shipping inside a v0.1.1 cohort. A pre-tag CI guard now refuses to publish if any tracked Cargo.toml's version field doesn't match the tag being pushed. result_large_errboxing inhc-api— the largest variant of the auth-middleware error type was inlined in everyResult<T, _>return. Boxed it; smaller stack frames, same surface.- Duplicate
debug!disconnect log in the WS handler removed.
Release matrix
14 tags total: homeCore, hc-tui, ten plugins, and
homeCore-io/docker all at v0.1.2. hc-plugin-sdk-rs
shipped independently at v0.1.3 (its first independent
SemVer; the appliance pulls SDK by tag at plugin-build time).
Upgrade notes
- The appliance image at
:0.1.2is the first to carry/etc/homecore/versions.jsonand to exposeGET /system/versions. Earlier appliance versions had inconsistent self-reported version strings.
v0.1.1 — 2026-05-03
Theme: Timezone unification, backup/restore plumbing, plugin security hardening.
Operator-visible polish round. The most noticeable change is that every UI timestamp now renders in your configured timezone instead of UTC; the most consequential is that backups now actually include plugin configs.
Added
- System timezone propagates from
/api/v1/system/statusthrough every component. The Leptos admin renders timestamps in the configured zone; plugins receive the zone via a new retained MQTT topichomecore/system/tzand use it for log formatting. - Plugin configs included in backup archive.
POST /api/v1/system/backupnow zips upstate.redb,history.db, rules, and every plugin'sconfig.toml. Restore unzips back in place. Body limit raised to handle realistic archive sizes. - Live status text during backup + restore — the admin shows byte counters and stage transitions instead of a blocking spinner.
- Telegram channel added to
hc-notify.type = "telegram"in[[notify.channels]];channel = "all"fans out to every registered channel. TimeElapsedrule condition —type = "time_elapsed"checks ms since an attribute last changed. Per-attribute timestamp cache; dry-run useslast_seenbaseline.- README dashboard badges repointed at the new ci-glance dashboard across 12 repos.
Changed
- Solar mode ON/OFF transitions now fire the simultaneous edge correctly when the sun event lands on an exact tick boundary (previously dropped).
- Plugin MQTT log forwarding redacts secret-named fields
(
password,api_key,token, etc.) before publishing tohomecore/plugins/{id}/logs. - Plugin command admin enforcement verified end-to-end;
hc-web-leptosdisables the submit button for non-admin users. hc-ecowittLAN attack surface reduced — the HTTP receiver binds loopback by default and accepts anallowed_source_ipsallowlist when binding to a routable address.
Fixed
- Telegram
CHANGE_MEplaceholder check — homeCore now refuses to start when a notification channel is configured with the example placeholder values, instead of silently failing on first dispatch.
Release matrix
14 tags at v0.1.1. 12 GitHub Releases (canonical + latest
mirror), 12 Docker images on ghcr (:0.1.1 + :latest),
appliance image + tarball.
v0.1.0 — 2026-04-09
Initial release. Runs a house, not yet packaged for general use.
Core
- Rust kernel with
axumREST + WebSocket API and embeddedrumqttdMQTT broker. - Rule engine — triggers, conditions, actions stored as RON files on disk, hot-reloaded.
redb-backed device registry; SQLite-backed history.- Rhai sandboxed scripting for conditions and action scripts.
- Solar event triggers computed locally (no cloud).
- Multi-user auth — JWT HS256, Argon2id passwords, 7 preset roles (admin / user / read_only and four mid-tier roles).
- Pushover, email, and (later) Telegram notification channels.
Plugins (Rust SDK)
hc-hue,hc-yolink,hc-lutron,hc-sonos,hc-wled,hc-isy,hc-zwave. All on the same plugin SDK with management protocol, heartbeat, remote config, dynamic log level, and MQTT log forwarding.
Clients
hc-web-leptos— Rust+WASM single-page admin. (Retired since; the web UI is nowhc-web.)hc-tui— terminal UI built onratatui.
Distribution
- Per-component Docker images on
ghcr.io/homecore-io/. - All-in-one
homecore-applianceimage (alpine, multi-stage). - Per-component tarballs published as GitHub Releases.
Release matrix
14 repos tagged at v0.1.0; amd64-only via FORCE_FAST (multi-arch is a deliberate later run, tracked as
MULTIARCH-1).
Looking forward
Active and deferred work for the next patch release lives in the project's planning docs (not part of public docs). User-visible items will appear here once they ship.