425 lines
24 KiB
Markdown
425 lines
24 KiB
Markdown
# handoff.md — HomeOS
|
||
|
||
> **Status: FINAL (2026-07-17).** This is the authoritative handoff — supersedes any earlier
|
||
> draft or chat summary.
|
||
>
|
||
> **Handoff for the Claude Code session on the server.** This document is the source of truth
|
||
> to build the project from scratch on the target VM. Read fully before the first command.
|
||
> Starting state of the VM: see §6 (user `nexxo` + sudo, Docker installed, otherwise empty).
|
||
> Companion file: `design-mockup.html` (interactive design reference — open in a browser;
|
||
> contains all three views: Dashboard, access gate, mobile/PWA layout).
|
||
>
|
||
> Meta-docs are English; **UI copy is German** (localized DE+EN, see rules). Conventions and
|
||
> STRICT RULES are carried over from the user's Clusev project and are **non-negotiable**.
|
||
|
||
---
|
||
|
||
## 1. Product
|
||
|
||
**HomeOS** *(working title — rename is an open decision, see §12)* — a **self-built smart-home
|
||
control plane**. Explicitly NOT Home Assistant and not a wrapper around it: own data model, own
|
||
integrations, own UI. Single household, self-hosted, LAN-first.
|
||
|
||
**Core capabilities (full vision):**
|
||
- Automatic **device discovery** on the network (mDNS/SSDP/ARP) with an "assign new device" flow
|
||
- **Device control** (switches, lights, plugs, power metering; later TVs, covers, …)
|
||
- **Rooms** and **persons**: every device/entity assignable to a room and optionally a person
|
||
- **Presence detection** (home/away per person) via UniFi + later geofencing, score-based fusion
|
||
- **Window/door contact sensors** with status + automations
|
||
- **Automations** (trigger → condition → action)
|
||
- Live dashboard (Reverb) + later **PWA** on the phone
|
||
- Later: settings area protected by **WebAuthn/passkeys** (deferred, see §11)
|
||
|
||
**v1 scope (build this first — do NOT boil the ocean):**
|
||
1. Foundation: Docker stack, auth, design system, device/entity/room model, MQTT ingest,
|
||
Reverb live updates, audit/command log
|
||
2. **Shelly integration** (the user's actual hardware today — see §4)
|
||
3. Discovery sidecar + "Neue Geräte" assignment flow
|
||
4. Presence via **UniFi API** (the user's actual network — see §5)
|
||
5. Automations engine (minimal TCA: state-change + time triggers, few actions)
|
||
|
||
**Deferred (keep OUT of v1):** face recognition entirely (later, maybe), WebAuthn gate,
|
||
TV/Chromecast control, Matter, Zigbee (until hardware decision), Ring bridge, native mobile app
|
||
(PWA later covers it), energy history charts beyond a simple sparkline.
|
||
|
||
---
|
||
|
||
## 2. Tech stack (hard requirements)
|
||
|
||
| Layer | Choice |
|
||
|---|---|
|
||
| Framework | **Laravel 13** (current major; user's newer projects run 13 — confirm once, see §12) |
|
||
| UI | **Livewire v3** — class-based, **NOT Volt** |
|
||
| CSS | **Tailwind v4** (CSS-first, `@theme` in `app.css`, no `tailwind.config.js`) |
|
||
| Build | **Vite** |
|
||
| Modals | **wire-elements/modal** |
|
||
| Realtime | **Laravel Reverb** + Echo — **every channel PRIVATE** |
|
||
| Queue/Cache | **Redis** (+ Horizon) |
|
||
| DB | **PostgreSQL 17 + TimescaleDB** recommended (telemetry hypertables, compression, retention). Fallback if user prefers stack consistency with Clusev: MariaDB + monthly partitioning + prune job. **Open decision §12 — ask before migrating.** |
|
||
| MQTT | **Eclipse Mosquitto 2** (auth + per-client ACLs from day one) |
|
||
| MQTT client | **php-mqtt/laravel-client** (^1.8) — subscriber runs as its own Artisan daemon container |
|
||
| Discovery | **Python sidecar** container (`network_mode: host`): python-zeroconf (mDNS), async-upnp-client (SSDP), arp-scan — reports via MQTT. PHP cannot do multicast listening from the bridge network. |
|
||
| Charts | JS lib as Alpine island (uPlot or ApexCharts) — v1 needs only sparklines |
|
||
| Icons | **Lucide**, inline SVG via `x-icon` Blade component — no emoji anywhere |
|
||
| Fonts | **Plus Jakarta Sans** (UI) · **IBM Plex Mono** (values, MACs, IPs, timestamps) — **self-hosted woff2 only** (R14) |
|
||
|
||
Composer: `livewire/livewire wire-elements/modal laravel/reverb laravel/horizon php-mqtt/laravel-client`
|
||
NPM: `tailwindcss @tailwindcss/vite laravel-echo pusher-js` (+ chart lib)
|
||
|
||
---
|
||
|
||
## 3. Architecture
|
||
|
||
**MQTT-first hybrid.** Mosquitto is the central bus. Everything that speaks MQTT natively
|
||
(Shelly Gen2+, later Zigbee2MQTT, Tasmota, WLED, ring-mqtt) connects directly. Devices without
|
||
MQTT get adapters later (HTTP drivers in PHP, or sidecars for TV/Cast/Matter). The UI never
|
||
touches protocol topics directly — **always through the DB mapping** (protocol-agnostic).
|
||
|
||
```
|
||
devices ──MQTT──► mosquitto ──► mqtt-listener (artisan daemon)
|
||
│ parse + dispatch only (NEVER heavy work in the loop)
|
||
▼
|
||
Events / Horizon jobs ──► DB upsert (device_states)
|
||
│
|
||
▼
|
||
Reverb (private channels) ──► Livewire UI (Echo)
|
||
|
||
UI action ──► Livewire ──► DeviceCommand job ──► driver (MqttDriver | HttpDriver) ──► device
|
||
└── every command written to command log (audit)
|
||
```
|
||
|
||
**Data model (core tables):**
|
||
- `users`, `rooms`, `persons` (person ↔ user optional link)
|
||
- `devices` — identity: uuid (route key, R11), name, vendor, model, protocol, connection config
|
||
(JSON: topic prefix / IP / auth ref), `room_id`, `person_id` nullable, first_seen/last_seen,
|
||
approval state (`discovered` | `active` | `ignored`)
|
||
- `entities` — one capability per row: type (`switch`, `light`, `power`, `contact`, `temperature`,
|
||
`humidity`, `motion`, …), capabilities JSON, `device_id`
|
||
- `device_states` — CURRENT state only: `entity_id` unique, state JSON, updated_at (pure upserts,
|
||
stays small and fast)
|
||
- `telemetry` — history (TimescaleDB hypertable; numeric value column + state JSON). Compression
|
||
+ retention policy **from day one** (a 30 s sensor ≈ 1 M rows/year)
|
||
- `commands` — audit: who/what switched which entity when (user | automation | physical),
|
||
payload, result. Unverzichtbar for debugging "warum ging das Licht an?"
|
||
- `automations` — name, enabled, trigger_type + trigger_config JSON, conditions JSON,
|
||
actions JSON, `cooldown_seconds`, `last_triggered_at` (cooldown from day one — motion sensors
|
||
without debounce flood the queue)
|
||
- `presence_signals` + computed person state (see §5)
|
||
- `discovery_findings` — raw sidecar reports before a device is approved
|
||
|
||
**Driver contract:** `app/Support/Drivers/DeviceDriver` interface (`turnOn`, `turnOff`,
|
||
`setState`, `capabilities`). v1 implements `ShellyMqttDriver`. Everything else is a later driver
|
||
behind the same contract — this is what keeps the system "für alle Produkte brauchbar".
|
||
|
||
**Broadcasting:** private channels only — `rooms.{uuid}`, `devices.{uuid}`, `presence`,
|
||
`discovery`. Authorize in `routes/channels.php`. State changes broadcast with
|
||
`ShouldBroadcastNow` (or a dedicated high-priority queue) — a slow job in the default queue must
|
||
never delay a light toggle in the UI.
|
||
|
||
**Long-runners:** `mqtt-listener`, `reverb`, `horizon`, `scheduler` are separate containers off
|
||
the same PHP image. MQTT loop rule: parse + dispatch only; reconnect with exponential backoff;
|
||
graceful SIGTERM shutdown via pcntl.
|
||
|
||
---
|
||
|
||
## 4. Hardware reality (drives the build order)
|
||
|
||
**Today the user owns: Shelly devices only, plus one Ring product. No Zigbee, no Hue.**
|
||
|
||
**Shelly (v1 target):**
|
||
- Gen2/Gen3 speak JSON-RPC over **MQTT** (enable MQTT in device settings → point at Mosquitto),
|
||
plus HTTP `POST /rpc` and WebSocket. Status arrives on `<device>/status/...`, notifications via
|
||
`rpc_ntf`; commands via RPC (`Switch.Set`, `Light.Set`).
|
||
- Discovery: Shellys announce via mDNS (`_shelly._tcp`) and expose `GET /shelly` (identity JSON:
|
||
model, gen, mac) — the sidecar uses both to classify.
|
||
- Power metering (PM devices) → `power` entity → dashboard energy tile.
|
||
- Docs: shelly-api-docs.shelly.cloud
|
||
|
||
**Window/door contacts (near future, still Shelly-native):** **Shelly BLU Door/Window**
|
||
(Bluetooth) received by any Shelly Gen3 device or a Shelly BLU Gateway acting as BT gateway —
|
||
events surface over the same MQTT path. This avoids buying into Zigbee just for contacts.
|
||
Zigbee (Zigbee2MQTT + LAN coordinator SLZB-06, never USB passthrough into the Proxmox VM)
|
||
remains the documented expansion path if/when the user buys Zigbee hardware.
|
||
|
||
**Ring (deferred, optional):** no official local API. Established bridge: **ring-mqtt**
|
||
(tsightler/ring-mqtt, Docker) — cloud-based, token auth, surfaces motion/ding/contact/camera
|
||
events on MQTT. Fits the bus as-is. Cloud dependency must be labeled in the UI (device badge
|
||
"Cloud"). Do not build a custom Ring client.
|
||
|
||
**Later expansion paths (documented, not built):** Hue via local CLIP v2 (HTTPS, HTTP driver in
|
||
PHP — existing PHP Hue packages are all deprecated v1 API, don't use them), TVs (LG SSAP /
|
||
Samsung Tizen websockets) + Chromecast + **Matter** (matter.js + @matter/mqtt) via a Node
|
||
sidecar, Tasmota/WLED native MQTT. The entity/driver model above must never assume Shelly.
|
||
|
||
---
|
||
|
||
## 5. Network topology & presence
|
||
|
||
**Actual network:** Fritz!Box (internet, NAT) → **UniFi Dream Machine** (own NAT — double NAT,
|
||
works fine, accepted) → LAN/WLAN with all devices and phones.
|
||
|
||
Consequences:
|
||
- **Presence source of truth = UniFi**, NOT Fritz!Box (phones associate with UniFi WLAN).
|
||
Package: **art-of-wifi/unifi-api-client** (`list_clients()` → MAC, IP, AP, last_seen). UDM =
|
||
UniFi OS console (port 443); create a dedicated read-only local user or API key for HomeOS.
|
||
Fritz!Box TR-064 is irrelevant here (it only sees the UDM as one client).
|
||
- Presence engine: "home" immediately on association; "away" only after **5–10 min debounce**
|
||
(`last_seen_at` + scheduler sweep). iPhone WLAN sleep causes false-aways otherwise. Never hang
|
||
a security automation on a single raw signal.
|
||
- MAC randomization: private MAC is stable per SSID — onboarding flow assigns "this client =
|
||
person X" from the live UniFi client list instead of asking for hardware MACs.
|
||
- Later additions: OwnTracks geofencing (MQTT, fastest clean "left home" signal), score-based
|
||
fusion of UniFi + GPS (+ BLE if ever). v1 = UniFi only.
|
||
- **Remote access:** double NAT makes port forwarding annoying (two chained forwards) — use
|
||
**Tailscale** (or WireGuard on the UDM) instead. No open ports. This is also the interim
|
||
answer for "PWA von unterwegs" until a public domain + TLS is set up.
|
||
- **WebAuthn (deferred)** will require HTTPS + a real domain (RP ID cannot be an IP; domain
|
||
change invalidates all passkeys) — plan `home.<domain>` + Let's Encrypt DNS-01 via Caddy when
|
||
that phase starts.
|
||
- Proxmox: VM NIC on vmbr0, **same L2/VLAN as the smart-home devices**, otherwise
|
||
mDNS/SSDP discovery is blind. If IoT ever moves to its own VLAN → mDNS reflector required.
|
||
|
||
---
|
||
|
||
## 6. Docker topology (dev = prod parity, R8 — host has only Docker)
|
||
|
||
**Starting point on the target VM (already prepared — this is where you begin):**
|
||
- User **`nexxo`** exists and has **sudo**. Work as this user.
|
||
- **Docker is already installed.** Verify with `docker compose version` as one of the first
|
||
commands; if the compose plugin is missing, report it.
|
||
- The VM is otherwise **empty**: no PHP, no Composer, no Node on the host — hence R8, every
|
||
project command runs in containers (bootstrap Laravel via a throwaway container).
|
||
- Project root: **`/home/nexxo/homeos`** (own git repo — does not exist yet, create it during
|
||
bootstrap; feature branches, not `main`). Read `HOST_UID`/`HOST_GID` from `id nexxo` into
|
||
`.env` — do not hardcode a UID.
|
||
|
||
Services in `docker-compose.yml`:
|
||
|
||
| Service | Role |
|
||
|---|---|
|
||
| `app` | php-fpm + nginx + Vite (dev) via supervisor — Laravel + Livewire |
|
||
| `horizon` | queue workers (same image, `php artisan horizon`) |
|
||
| `scheduler` | `php artisan schedule:work` (same image) — presence sweeps, retention, polling fallbacks |
|
||
| `reverb` | `php artisan reverb:start` (same image) |
|
||
| `mqtt-listener` | `php artisan mqtt:listen` (same image, `restart: always`) |
|
||
| `mosquitto` | eclipse-mosquitto:2 — password auth + ACL per client (laravel, sidecar, shelly, later z2m/ring) |
|
||
| `db` | timescale/timescaledb:latest-pg17 (or MariaDB — §12) |
|
||
| `redis` | redis:7-alpine |
|
||
| `discovery` | Python sidecar, **`network_mode: host`** + `cap_add: [NET_RAW]` |
|
||
|
||
Port planning (host-mode container shares the VM's ports — plan collisions explicitly):
|
||
app **:80**, Vite **:5173**, Reverb internal 8080 → host **:6001**, Mosquitto **:1883**
|
||
(LAN only), DB bound to 127.0.0.1. All ports env-driven, nothing hardcoded.
|
||
|
||
Ops non-negotiables:
|
||
- **Backups:** Proxmox vzdump for the VM + DB dump job; later `coordinator_backup.json` when
|
||
Zigbee exists. Mosquitto persistence volume.
|
||
- **Timezone:** app logic Europe/Berlin, storage UTC; VM NTP-synced (DST double/zero-fire bug
|
||
in time triggers otherwise).
|
||
- **Reverb behind proxy:** websocket upgrade headers + long `proxy_read_timeout`, or sockets
|
||
drop every 60 s.
|
||
- **Graceful degradation:** server down must never make the home unusable — physical switches
|
||
keep working because Shellys switch locally (detached/relay mode kept functional).
|
||
|
||
---
|
||
|
||
## 7. Folder map (R6 — follow exactly)
|
||
|
||
```
|
||
app/
|
||
Livewire/
|
||
Dashboard.php
|
||
Rooms/{Index.php, Show.php}
|
||
Devices/{Index.php, Show.php}
|
||
Discovery/Index.php
|
||
Persons/Index.php
|
||
Automations/{Index.php, Edit.php}
|
||
Settings/Index.php
|
||
Modals/ # wire-elements/modal (ConfirmDelete, AssignDevice, …)
|
||
Concerns/
|
||
Models/ # Device, Entity, DeviceState, Room, Person, Automation, Command, DiscoveryFinding
|
||
Support/
|
||
Mqtt/ # topic mapping, payload normalizers per vendor
|
||
Drivers/ # DeviceDriver contract + ShellyMqttDriver (+ later drivers)
|
||
Services/ # DeviceCommandService, PresenceEngine, AutomationEngine, DiscoveryService, UnifiClient
|
||
Events/ # DeviceStateChanged, DeviceDiscovered, PresenceChanged (ShouldBroadcastNow)
|
||
Jobs/
|
||
Console/Commands/MqttListenCommand.php
|
||
resources/
|
||
views/livewire/… # mirrors app/Livewire, kebab-case
|
||
views/components/ # x-panel, x-kpi, x-status-pill, x-status-dot, x-badge, x-icon,
|
||
# x-room-card, x-device-chip, x-toggle, sidebar, topbar
|
||
views/layouts/app.blade.php
|
||
css/app.css # @import "tailwindcss"; @theme { tokens §8 }
|
||
fonts/ # plus-jakarta-sans*.woff2, ibm-plex-mono*.woff2 (Vite-bundled)
|
||
js/app.js # Echo bootstrap + Alpine chart islands
|
||
lang/{de,en}/ # common, dashboard, rooms, devices, discovery, persons, automations, settings, modals
|
||
routes/web.php # full-page Livewire routes, English paths (R13)
|
||
routes/channels.php # private-channel auth
|
||
sidecar/ # Python discovery sidecar (own Dockerfile)
|
||
docker/ docker-compose.yml docker-compose.prod.yml
|
||
handoff.md rules.md CLAUDE.md design-mockup.html
|
||
```
|
||
|
||
---
|
||
|
||
## 8. Design system — "Enterprise Ops, dark OLED"
|
||
|
||
Reference: **`design-mockup.html`** (open it — it is the approved direction: UniFi/Grafana-class
|
||
ops console, NOT a consumer app). Port these tokens verbatim into `@theme` (R3); markup uses
|
||
token utilities only.
|
||
|
||
```css
|
||
@theme {
|
||
/* surfaces */
|
||
--color-base: #080D18; /* page bg, blue-biased OLED black */
|
||
--color-surface: #0D1424; /* cards */
|
||
--color-raised: #121C31; /* nested surfaces, chips */
|
||
--color-inset: #18233D; /* icon wells, kbd */
|
||
/* text ramp */
|
||
--color-ink: #EAF0FB;
|
||
--color-ink-2: #93A1BD;
|
||
--color-ink-3: #5E6C8A;
|
||
/* brand */
|
||
--color-accent: #4FC1FF; /* cyan — interaction, active nav, toggles; tints via /10 /25 */
|
||
/* status triad — reserved, never decorative */
|
||
--color-online: #34D399;
|
||
--color-warning: #FBBF24;
|
||
--color-offline: #F87171;
|
||
/* hairlines */
|
||
--color-line: #1C2846;
|
||
--color-line-soft: #141E36;
|
||
/* fonts */
|
||
--font-sans: "Plus Jakarta Sans", ui-sans-serif, system-ui;
|
||
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
|
||
}
|
||
```
|
||
|
||
Rules of the look: status via colored dot/pill + text (never color alone, never emoji);
|
||
**every numeric value, MAC, IP, timestamp in `font-mono` with `tabular-nums`**; hairline borders
|
||
instead of shadows; accent used sparingly (one primary action per view); subtle pulse on the
|
||
live indicator; `prefers-reduced-motion` respected. Responsive 375/768/1280 (R7): sidebar →
|
||
drawer, KPI grid 5→3→2→1, right rail stacks below.
|
||
|
||
---
|
||
|
||
## 9. STRICT RULES (carried over — compact form)
|
||
|
||
> The user's Clusev `rules.md` applies 1:1 to this project (full examples live there).
|
||
> Copy this list into `rules.md` in the repo at bootstrap. On any conflict: **STOP and ask.**
|
||
|
||
- **R1** Pages = full-page class-based Livewire components mapped directly in `routes/web.php`. No page controllers.
|
||
- **R2** Livewire class + separate Blade view. **Volt forbidden** (never `make:volt`).
|
||
- **R3** All colors/design tokens in `@theme` (`app.css`); markup uses token utilities only — no raw hex/rgb.
|
||
- **R4** No inline `style=` — single exception: progress-bar `width`.
|
||
- **R5** Destructive/confirm actions via **wire-elements/modal** — never `confirm()`/Alpine popups.
|
||
- **R6** Files exactly per folder map (§7).
|
||
- **R7** Responsive verified at 375/768/1280; touch targets ≥ 44 px.
|
||
- **R8** Every PHP/Composer/Node/artisan command **inside the container** — host has only Docker.
|
||
- **R9** UI copy German, terse/operational, **no emoji**; status via color/dots/pills; technical tokens stay native.
|
||
- **R10** Reuse `@theme` tokens + Blade component kit — no ad-hoc colors/widgets.
|
||
- **R11** URL-exposed records addressed by **UUID**, never integer PK (`getRouteKeyName(): 'uuid'`).
|
||
- **R12** Every touched page verified in a real browser: **HTTP 200, zero console errors, zero failed requests**; loaded state of lazy pages, not the skeleton; inspect rendered DOM for leaked `@`/`{{ }}`/`group.key` literals. Green `Livewire::test` ≠ done.
|
||
- **R13** Route paths + names **English** (`/devices`, not `/geraete`); German only in visible labels.
|
||
- **R14** Fonts **self-hosted** (`resources/fonts/*.woff2`, relative `url('../fonts/…')` + `@font-face`) — no Google Fonts/CDN link or import.
|
||
- **R15** **Codex review** (`/codex:review`) after every change — no errors, no security findings, else fix and re-run. Task not done until clean.
|
||
- **R16** Every UI string localized `lang/{de,en}/<group>.php`, identical keys both languages, `__('group.key')` — no hard-coded copy anywhere.
|
||
- **R17** Blade: block `@php … @endphp` only (no inline `@php(...)`); never the literal directive tokens in comments/text.
|
||
- **Secrets:** `.env*`, tokens, keys never staged; check `git status` before every commit.
|
||
Git repo does not exist yet — create it at bootstrap (feature branches, not `main`).
|
||
|
||
**HomeOS-specific additions:**
|
||
- **H1** UI/Livewire never publishes to protocol topics directly — every command goes through
|
||
`DeviceCommandService` → driver, and every command is written to the `commands` audit table.
|
||
- **H2** MQTT subscriber callbacks: parse + dispatch only. No DB aggregation, no HTTP calls in
|
||
the loop.
|
||
- **H3** Vendor specifics (topics, payload shapes) live only in `Support/Mqtt` + `Support/Drivers`.
|
||
Models, Livewire and views stay protocol-agnostic.
|
||
- **H4** Telemetry writes go through one ingest path that enforces retention/compression policies.
|
||
- **H5** Automations always carry `cooldown_seconds`; the engine has a **dry-run mode** (log
|
||
instead of switch) for development — never develop automations against the live home.
|
||
|
||
---
|
||
|
||
## 10. Claude Code plugin setup (server session — do this FIRST, before Phase 1)
|
||
|
||
The server session must mirror the user's local Claude Code plugin set and **use** these
|
||
plugins during the build. At session start:
|
||
|
||
1. **Add the external marketplaces**, then install:
|
||
|
||
| Plugin | Marketplace / source | Install |
|
||
|---|---|---|
|
||
| superpowers | `claude-plugins-official` (built-in) | `/plugin install superpowers@claude-plugins-official` |
|
||
| frontend-design | `claude-plugins-official` | `/plugin install frontend-design@claude-plugins-official` |
|
||
| code-review | `claude-plugins-official` | `/plugin install code-review@claude-plugins-official` |
|
||
| context7 | `claude-plugins-official` | `/plugin install context7@claude-plugins-official` |
|
||
| codex | `/plugin marketplace add openai/codex-plugin-cc` | `/plugin install codex@openai-codex` |
|
||
| claude-mem | `/plugin marketplace add thedotmack/claude-mem` | `/plugin install claude-mem@thedotmack` |
|
||
| ui-ux-pro-max | `/plugin marketplace add nextlevelbuilder/ui-ux-pro-max-skill` | `/plugin install ui-ux-pro-max@ui-ux-pro-max-skill` |
|
||
| caveman | `/plugin marketplace add JuliusBrussee/caveman` | `/plugin install caveman@caveman` |
|
||
|
||
2. **Use them, don't just install them:** superpowers workflows (brainstorming before creative
|
||
work, TDD, systematic debugging, writing/executing plans), frontend-design + ui-ux-pro-max
|
||
for every UI task (§8 design system), code-review + **codex for R15** (mandatory review
|
||
gate), claude-mem for cross-session memory, context7 for current library docs
|
||
(Laravel/Livewire/Tailwind APIs — don't answer from training data), caveman optional.
|
||
3. **Anything missing → install it.** If an install fails, a plugin needs a runtime it can't
|
||
find, or **any tool requires a login/authentication** (Codex CLI login, API keys, tokens,
|
||
Gitea credentials, UniFi access) → **stop and report to the user**; never skip silently or
|
||
work around it. Because R15 depends on Codex, verify `/codex:setup` during bootstrap, not
|
||
when the first review is due.
|
||
4. Scope note: plugin runtimes (node/python for claude-mem, ui-ux-pro-max) belong to the Claude
|
||
Code session on the host — R8 (everything in containers) applies to **project** tooling, not
|
||
to session tooling.
|
||
|
||
---
|
||
|
||
## 11. Deferred decisions already researched (do not re-litigate, just don't build yet)
|
||
|
||
- **Face recognition:** postponed entirely. When it returns: **WebAuthn/passkeys**
|
||
(spatie/laravel-passkeys or asbiin/laravel-webauthn) is the security gate — browser-based
|
||
DIY liveness is not securable (virtual cameras, replay, deepfakes). Camera face recognition
|
||
only ever as comfort/personalization layer, never as the lock.
|
||
- **Zigbee:** if bought, Zigbee2MQTT + **SLZB-06 LAN coordinator** (PoE), never USB passthrough
|
||
into the Proxmox VM. Zigbee channel chosen against WLAN channels BEFORE first pairing.
|
||
- **Matter/TVs/Cast:** Node/Python sidecars speaking MQTT — never native PHP implementations.
|
||
- **Mobile:** PWA first (manifest + service worker + Web Push via
|
||
laravel-notification-channels/webpush; iOS supports Web Push for installed PWAs). Expo/React
|
||
Native only if a concrete PWA limit hurts. NativePHP rejected (runs a second Laravel on the
|
||
phone — wrong model).
|
||
|
||
---
|
||
|
||
## 12. Open decisions — ask the user before committing
|
||
|
||
1. **DB:** PostgreSQL+TimescaleDB (recommended for telemetry) vs MariaDB (Clusev consistency)?
|
||
2. **Laravel version:** 13 assumed (current, Clusev precedent) — original statement said 12.
|
||
3. **Project name:** "HomeOS" is a working title.
|
||
4. **Git remote:** Gitea like Clusev (`git.bave.dev`)? Repo must be created at bootstrap.
|
||
5. **UniFi auth:** local read-only user vs API key on the UDM — needs the user to create it.
|
||
6. **Which Ring product** the user owns (relevant only when the ring-mqtt phase starts).
|
||
|
||
---
|
||
|
||
## 13. Build order (each phase ends browser-verified, R12 + R15; §10 plugin setup comes first)
|
||
|
||
1. **Bootstrap** — compose stack up, Laravel installed in-container, git repo + `rules.md` +
|
||
`CLAUDE.md` created, tokens + fonts + layout shell (sidebar/topbar per mockup), auth + login.
|
||
2. **Domain core** — migrations for §3 tables, models, seeders with fake devices; dashboard
|
||
renders mock state (rooms, KPI row, live feed) — mock first, real hardware after.
|
||
3. **MQTT ingest** — mosquitto + mqtt-listener daemon; Shelly state flows into
|
||
`device_states`; Reverb pushes to the dashboard; toggle switches a real Shelly via
|
||
`ShellyMqttDriver`; commands audited.
|
||
4. **Discovery** — Python sidecar (mDNS/SSDP/ARP → MQTT), `discovery_findings`, "Neue Geräte"
|
||
panel with assign/ignore flow (per mockup).
|
||
5. **Presence** — UnifiClient poller (scheduler), person↔client mapping UI, debounce sweep,
|
||
presence panel + avatars live.
|
||
6. **Automations** — TCA engine (state_change + time triggers; actions: switch entity, notify),
|
||
cooldown, dry-run mode, audit integration.
|
||
7. **Polish + PWA prep** — empty states, `wire:init` loading states, breakpoints pass,
|
||
manifest groundwork.
|
||
|
||
Definition of done per phase: R12 browser proof (200, console clean, all 3 breakpoints) +
|
||
R15 Codex clean + commit on a feature branch.
|