Codex R15 on the Ring bridge flagged two issues:
- [P1] Concurrent first-sight messages (retained info + motion on bridge
startup) could create duplicate devices — no uniqueness on ring_id. Added a
partial unique index on (config->>'ring_id'); resolveDevice now creates and,
on the unique violation, re-fetches the winner (race-safe).
- [P2] The install-gate cache was never invalidated, so ingest was dropped for
up to 15s after install and devices kept being created for up to 15s after
uninstall. AddonService now forgets the shared cache key on install/uninstall.
+3 tests (unique-index guard, repeated-messages-reuse-device, cache
invalidation). Suite 43 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First half of the Ring integration (handoff §12: ring-mqtt, cloud token auth):
- Addons registry (static catalogue) + Addon model with encrypted config, so
the Ring refresh token / credentials are never stored in plaintext.
- AddonService: install / uninstall (wipes secrets) / saveConnection (blank
password keeps the stored one) / markStatus (from the bridge over MQTT).
- Addons page (new sidebar tab) with per-addon install, Cloud badge, live
status pill, and a Ring connect modal capturing email/password/2FA — we
never call Ring ourselves; creds are handed to the ring-mqtt sidecar.
- Uninstall behind a wire-elements confirm (R5). Fixed the confirm target:
Livewire names App\Livewire\Addons\Index as "addons" (Index suffix dropped),
and only $wire.$dispatch (not Alpine $dispatch) reaches the modal listener.
- DE/EN localization (R16); doorbell/package/cloud icons.
7 AddonTest cases (registry, install, encryption-at-rest, blank-password
keep, uninstall wipe, status guard). Suite 33 green; 12/12 tabs clean; full
install→connect→connecting→uninstall UI flow browser-verified, 0 console errors.
Backend bridge (ring-mqtt container) + Ring MQTT normalizer land next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex R15 flagged two P1 correctness bugs in AutomationEngine:
- Conditions were never evaluated — a trigger→condition→action rule ran its
actions unconditionally. Now every stored condition must match current
entity state (AND) before actions run; a false condition does NOT touch the
cooldown clock, so the rule stays armed for its next legitimate trigger.
- Cooldown was read-check-then-save, so concurrent queue workers on a burst of
state changes could all pass the check and each fire. Now claimed with a
single conditional UPDATE — exactly one worker wins the race (H5).
+3 tests (condition true/false, cooldown-not-armed-on-failed-condition).
AutomationTest 9 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New "Steuerung" page for tablet control:
- Large touch tiles for every light/switch — tap toggles (through
DeviceCommandService), highlighted when on, live via Echo.
- Drag-to-reorder via SortableJS (handle per tile); order persisted to
entities.panel_sort.
- Lights get a colour/brightness modal → Light.Set (brightness + rgb) on the
Shelly, with preset swatches + a custom colour picker. Driver contract gains
setLight(); command service audits it (H1).
Nav check 11/11 tabs clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Assigning a Shelly now issues a real broker credential so the physical device can
connect (onboarding was incomplete before):
- MqttCredentialProvisioner writes a mosquitto-compatible PBKDF2-SHA512 ($7$)
password line (in PHP) for username = the device's topic prefix, and touches a
reload trigger. A small wrapper in the mosquitto container (docker/mosquitto/
config/entrypoint.sh) SIGHUPs mosquitto so it re-reads the passwd live — no
restart. Verified: a provisioned device authenticates and publishes to its own
prefix (bound by the pattern %u ACL).
- The credential is shown once on the device page after assignment (enter it into
the Shelly). passwd is app-owned + world-readable so the web request can write it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- UnifiClient service (art-of-wifi/unifi-api-client) talks to the local UDM with a
read-only account (self-signed cert, verify_ssl off). Verified live: logged in
and read 32 active clients.
- presence:poll command (scheduled every minute, withoutOverlapping): "home"
immediately on association, "away" only after an 8-min debounce so iPhone WLAN
sleep can't cause false-aways. Broadcasts PresenceChanged on the presence channel.
- Persons page: "Person hinzufügen" modal picks the representing device straight
from the live UniFi client list (falls back to manual MAC entry if UniFi is down);
the sweep then tracks that person's presence. mac + last_seen_home_at on persons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real bidirectional MQTT so devices are live, not mock (handoff §13.3):
- Mosquitto 2 broker (auth + per-client ACLs for laravel/shelly/sidecar from day
one); passwd generated by docker/mosquitto/gen-passwd.sh (gitignored).
- mqtt-listener daemon: subscribes `+/status/#`, parse + dispatch only (H2),
exponential reconnect backoff, graceful SIGTERM. php-mqtt/laravel-client.
- Ingest path (H4): IngestShellyMessage resolves device by mqtt_prefix, upserts
device_states, refreshes last_seen, broadcasts DeviceStateChanged
(ShouldBroadcastNow) on the private `home` channel.
- Control path (H1): DeviceDriver contract + ShellyMqttDriver (command topic +
Shelly.Reboot RPC) behind DeviceCommandService, which audits every command to
the new `commands` table. Device detail toggles + restart route through it;
flash reflects the real result.
- Live UI: dashboard + device pages listen via Echo (#[On('echo-private:home,
.DeviceStateChanged')]) and re-render instantly.
- Vendor specifics isolated in Support/Mqtt + Support/Drivers (H3).
Verified end-to-end in a real browser: publishing an MQTT status turned a light
"An" on the dashboard in 3.0s with no reload, 0 console errors. R12 30/30;
15 feature tests green (incl. ingest + command audit). README/bootstrap document
the broker passwd step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Document that production must set APP_ENV=production + APP_DEBUG=false (else
exceptions leak and the demo household seeds); note it in .env.example too.
- HomeStatus::warnings now iterates devices → entities using the already-loaded
device, instead of $entity->device, removing an N+1 on dashboard render.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HomeStatus aggregated from Room, so a device with room_id = null (a supported
state, e.g. after picking "no room") vanished from dashboard totals, KPIs and
warnings. Aggregate from all devices instead; the dashboard groups them by room
with a trailing "Ohne Raum" group so nothing disappears. Added a feature test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Warnings no longer clutter the dashboard: a compact "Warnungen (n)" button in
the topbar opens a themed wire-elements/modal (R5) listing all messages. Dark
modal wrapper overridden; HomeStatus service shared by dashboard + modal.
- Device detail page (/devices/{uuid}, UUID route key): edit name, room and
active state; view info + live capabilities; Neustart (with confirm modal) and
"Update prüfen" as mock commands (Phase 3 routes them through the real driver).
Devices index (/devices) added; "Geräte" nav activated; dashboard device rows
and index link to the detail. Generic Confirm modal + x-detail component.
- Mock devices no longer rot: online is now "active and (no last_seen or seen
<10min)", so the demo stays healthy; the one offline device keeps a stale
timestamp. Full DE/EN i18n for devices + modal copy.
Verified: R12 30/30 in headless Chromium (0 console errors, 0 failed requests);
10 feature tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworks the dashboard into a home-control view per user feedback (it is not a
server dashboard):
- Dashboard now shows home data — rooms with live device state (lights on/off,
power draw, window/door contacts, battery %), a home summary (devices online,
lights on, open contacts, low batteries), and a Warnings panel that only lists
what needs attention (offline device, open window, low battery, degraded host
service). No server internals on the dashboard.
- Server/service health (DB, Redis, Reverb, Horizon) + version info moved to a
dedicated "Host & Dienste" page (new nav item under System); dashboard surfaces
a host problem only as a warning that links there.
- New domain slice (handoff §3, mock-first §13.2): rooms/devices/entities/
device_states migrations + models (UUID route keys, R11) + DemoHomeSeeder with
Shelly-like devices incl. deliberate faults. Extracted SystemHealth service.
- Sidebar decluttered further; new x-entity-state component; full DE/EN i18n.
- Fixed R15 findings: .env.example now ships matching non-empty dev defaults for
DB_PASSWORD and Reverb keys so a fresh `cp .env.example .env` boots cleanly.
Verified: R12 21/21 in headless Chromium (0 console errors, 0 failed requests,
breakpoints 375/768/1280); 10 feature tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>