Addresses "the list on every page isn't good": adopt the approved mockup's
card design (icon well, name, sub, live state chips, footer toggle).
- New <x-device-card>: per-device card with online dot, cloud badge, entity
chips and an inline switch/light toggle (active cards highlighted accent).
- TogglesEntities trait → dashboard, devices index, rooms show all drive the
same toggle (H1, audited); card grids replace the divide-y rows.
- Dashboard + devices grouped into responsive card grids by room.
- Windows page: sensor cards showing closed/open/tilted prominently, low-battery
chip, open sensors highlighted (screenshot-verified).
- Rooms index already card-based; left as-is.
Suite 51 green, 12/12 tabs clean, zero console errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses "I have to configure MQTT per device; HA just works":
- Shared device account `shelly` (one credential every Shelly uses) with a
SCOPED ACL (status/events/online/rpc for any prefix; cannot touch homeos/,
ring/ or $SYS). gen-passwd seeds it from MQTT_SHELLY_PASSWORD.
- Auto-onboarding: IngestShellyMessage creates the device on the first
recognizable component (sys/wifi/cloud noise ignored), so pointing a Shelly
at the broker is all it takes — no manual "Zuweisen". Partial unique index on
config->>'mqtt_prefix' + race-safe create (merge-dedup migration).
- Settings → Geräte-MQTT card: server, username, reveal/copy password + steps
(config/homeos.php, MQTT_DEVICE_HOST). Discovery "Zuweisen" now upserts by
prefix (names/rooms an already-onboarded device, no duplicate) and no longer
forces per-device creds.
- Per-device credentials kept as opt-in hardening: a "generate" button on the
device page (pattern %u ACL retained).
Live-verified: publishing as `shelly` to a new prefix auto-creates the device
with switch+power state; a sys topic creates nothing. Suite 46 green, 12/12
tabs clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second half of the Ring integration (handoff §12):
- RingTopics + RingNormalizer (H3): parse ring/<loc>/<cat>/<id>/<entity>/state,
map ding/motion/contact/lock/battery; bridge status topic drives the addon
connection state. Defensive — unknown topics are ignored, never junk devices.
- IngestRingMessage (H4, single ingest path): auto-creates cloud-flagged Ring
devices on first sight, monotonic race-safe state upsert, broadcasts live.
Gated on the addon being installed (cached) so a still-running bridge can't
recreate devices after uninstall.
- Shared AppliesDeviceState trait: the monotonic upsert now lives once, used by
both Shelly and Ring ingest (MqttTest guards the Shelly path).
- Listener subscribes ring/#. ring-mqtt container (opt-in `addons` compose
profile) as least-privileged `ring` MQTT user (ACL: ring/# only); gen-passwd
seeds the account. "Cloud" badge on Ring devices in list + detail.
11 Ring tests (topic parse, normalizer, bridge status, auto-create, install
gate, unknown-topic guard, out-of-order). Suite 40 green; 12/12 tabs clean.
Live-verified: real MQTT ring/.../ding + info publish → cloud device created
with ding + battery state. (Real Ring OAuth login runs in the ring-mqtt sidecar.)
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>
- AssignDevice derives the Shelly MQTT prefix from the mDNS service instance name
(the device id) instead of the slugified topic identifier (which carries the
_shelly._tcp suffix); non-Shelly devices get no prefix.
- IngestDiscoveryMessage broadcasts DeviceDiscovered only on first creation, not on
every retained/periodic re-publish.
- The generic Confirm modal can target its event at the opening component (->to),
so a shared event name can't be caught by an unrelated component.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sidebar drawer is now opaque (.sidebar-tint solid), so on mobile the bright
content no longer bleeds through and it stays readable.
- Devices index reworked for ~50 devices: live search (name/model/vendor),
room + online/offline filters (URL-persisted), grouped by room with counts —
scannable instead of one long list.
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>
- 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>