feat: Phase 4 domains (cover/scene/sensor) + deep-review hardening - #3
Open
gentslava wants to merge 11 commits into
Open
feat: Phase 4 domains (cover/scene/sensor) + deep-review hardening#3gentslava wants to merge 11 commits into
gentslava wants to merge 11 commits into
Conversation
Add three HA domains to the companion mapper, following the light/switch/lock
pattern:
- cover: state-dependent open/close/stop (+ all three as secondary)
- scene: single turn_on ("Activate"); state masked to a stable "scene" token
(HA's raw state is a last-activated timestamp)
- sensor: read-only — no primary action, unit_of_measurement folded into
state ("21.5 °C")
EntityCard.primary becomes optional (additive, NO protocol v bump): senders
that always include it stay valid, readers must tolerate its absence so a
read-only sensor renders no primary tile.
Update docs (p2p-protocol, ha-integration-notes) and EntityMapper tests
(cover/scene/sensor cases; sensor is now mapped, not filtered out).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…plain text
watch-lite + watch-arkts mirror the companion's new domains:
- cover: state-dependent open/close/stop
- scene: single Activate
- sensor: read-only — no primary (EntityCard.primary now optional)
Also fixes a latent arkts bug: EntityDetails rendered the primary label via
$r() (a resource id), which never resolved the companion's plain-text labels
("Open", "Activate", …). Now rendered as plain Text, which also fixes the
existing light/switch/lock primary tile on real devices. Mock labels switched
to plain text to match.
Adds cover/scene watch-lite icons (128px, monochrome) and mock entries on
both watches. All three apps build: companion tests green, watch-lite and
watch-arkts assembleHap successful.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- K2: @volatile on WearEngineP2pService.device — it's written from HMS callbacks and read from send coroutines; without a barrier sendRaw could read a stale null and silently drop a reply. - B3: getStates parses element-by-element — one malformed HA state object no longer fails the entire sync (partial data > all-or-nothing). - B4: unavailable/unknown entities get no primary/secondary action, per ha-integration-notes (uses the previously-unused HaState.isAvailable). - B5: OkHttp callTimeout(7s) so a large /api/states stays within the watch's 8s P2P budget. - B6: parseIncoming validates protocol v — a mismatched version returns an explicit error ACK (UnsupportedVersion) instead of being mis-parsed as v1. Adds tests for unavailable-no-actions and version rejection. All green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ew K3-K5) - K3: new HaBridgeService (foreground, START_STICKY) owns the P2P transport instead of MainActivity, so it survives Activity recreation (rotation / theme) and keeps the watch reachable while backgrounded. stop() now runs on service destroy — no leaked scope / duplicate receivers. - K4: the bridge starts after first-time "Connect & test" succeeds, not only if config was already present at onCreate — fixes the first-run case where the watch couldn't reach HA until an app restart. - K5: WearEngineP2pService re-resolves the watch and re-registers the receiver with a 5s retry — handles the watch connecting late, dropping, or reconnecting on a new Device (was a one-shot registration before). Manifest: foreground-service + connected-device + notifications permissions, service declaration (foregroundServiceType=connectedDevice). Builds clean; on-device verification pending (adb device dropped). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- T2: extract HaService interface; HaClient implements it and HaBridge depends on it — the bridge is now unit-testable without HTTP, and a future WebSocket client can replace HaClient without touching the bridge. - T1: add HaBridgeTest (fake HaService) covering the orchestration node: sync success/failure, call-service pass-through + error, unsupported version, unrecognized message — all previously untested. - T4: companion UI entityGlyph/entityAccent now cover cover/scene/sensor (were "?" / inactive after the domains were added). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…w K6,B8-B10,V3) - K6: wearEngineRemoteAppNameList -> ru.gentslava.homeassistant.companion (was the com.yourcompany.ha.bridge placeholder). It must match the bundle in setRemoteApp or Wear Engine rejects P2P and the app silently drops to Mock. - B9: onIncoming wraps JSON.parse in try/catch — a malformed payload from the companion no longer throws out of the Wear Engine receiver callback. - B10: refreshEntity now catches errors into lastError (was try/finally only, so a P2P timeout produced an unhandled rejection and no offline banner). - B8: Services.tryUpgradeToReal() + store.sync calls it — if the app fell back to Mock at startup, it reconnects the real P2P repo once the phone is reachable instead of being stuck on fake data forever. - V3: runAction refreshes the entity after callAction so the details screen reflects the new state (Mock updated in place but the UI never refreshed). All compile: assembleHap successful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… B7,K7,C1) - B7: the entity screen now renders the companion-provided primary+secondary actions (per the P2P contract — the watch shouldn't re-derive HA semantics) and sends each action's own domain/service/data on tap. The local per-domain table is kept only as a mock/offline fallback. navEntity carries the actions so they survive a back-and-return. - K7: add the 4 missing Settings icons (connection/server/debug/info, 128px monochrome) — the Settings screen referenced non-existent files. - C1: dead MockHomeAssistantRepository.js (used Promise, banned on lite) was removed in the previous commit — it was superseded by mockItems() + haRepository. watch-lite assembleHap successful. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ct backoff On-device the foreground service crashed at startForeground: FGS type connectedDevice on Android 14+ (targetSDK 35) additionally requires a BT/network runtime permission. Switched to dataSync (semantically: syncing HA state <-> watch) which needs only the normal FOREGROUND_SERVICE_DATA_SYNC. Also: the reconnect loop polled getBondedDevices every fixed 5s forever while the watch was away (CPU wakeups / battery). Now exponential backoff 5s..60s, reset on connect. HA itself is never polled — the companion calls HA only on a watch request (SYNC/CALL_SERVICE), so there are no background HA requests. Verified on device: no crash, service starts, bridge reaches requestPermission (code=8 Scope unauthorized — pending Wear Engine approval, as before). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The monorepo exists to protect the shared P2P contract, but nothing ran on push. The companion is the only app with a headless suite, so guard it: gradle test + assembleDebug on every push/PR touching apps/phone-android or the protocol doc. Path-filtered per ADR-0006. The watch apps have no CI-runnable build (DevEco/JerryScript, device-only). Builds without agconnect-services.json (AGC plugin stays commented out). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…review B11) The docs described an earlier state and actively misled agents: - companion was "not written / separate repo" — it's implemented in apps/phone-android/ (README, architecture.md). - setRemoteApp / PEER_FINGERPRINT described as "commented out / placeholder" — they're configured with the real companion bundle + debug-cert fingerprint (p2p-protocol.md, ADR-0003, watch-arkts AGENTS.md, CLAUDE.md, DEVELOPMENT.md). - supported domains listed as 3 — now 6 (light/switch/lock/cover/scene/sensor), and the "ask first before adding domains" gate updated (root AGENTS.md, architecture.md, spec). - AGP 8.7 -> 8.13.2 (phone AGENTS.md). Remaining real placeholder noted accurately: module.json5 client_id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nused imports - watch-lite TD7: debugMode defaults to false — a fresh install with no companion now shows the honest "Offline — open the phone app" instead of fake mock data. - watch-lite M2: index onShow retries loadEntities when the list is empty (offline first load), so returning to the screen auto-recovers instead of requiring a manual pull-down with no on-screen affordance. - watch-arkts: drop unused ArcListAttribute/ArcListItemAttribute imports (Index/EntityDetails/Settings) — Code Linter warnings. Both watch apps assembleHap successfully. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
gentslava
force-pushed
the
feat/real-ha-integration
branch
from
June 15, 2026 06:55
862d1c1 to
b9eb8ba
Compare
gentslava
force-pushed
the
feat/phase4-domains
branch
from
June 15, 2026 06:55
bf0c878 to
ab09439
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on top of #2 (real-HA MVP). Two things: adds three HA domains, then a full pass of fixes from a deep code review.
Phase 4 — new domains
cover/scene/sensoracross all three apps, following the light/switch/lock pattern:EntityCard.primarymade optional (additive, novbump), unit folded intostateAlso fixes a latent arkts bug:
EntityDetailsrendered the primary label via$r()(resource id), which never resolved the companion's plain-text labels — now plainText.Deep-review hardening
A 5-agent review (companion / watch-lite / watch-arkts / security / cross-platform) drove these:
Repository hygiene
pullwill fail).Lifecycle (companion)
HaBridgeService), survives Activity recreation / background; watch reconnect with exponential backoff (5s→60s); bridge starts after first Connect. Verified on device.connectedDevicecrashed on Android 14 (needs a BT/network runtime perm) → switched todataSync.@Volatileon the shareddevice(send-path race).Error handling
getStates(one bad entity no longer fails the whole sync) · no actions forunavailable/unknown· OkHttpcallTimeoutwithin the 8s budget · protocol-version guard ·onIncomingtry/catch (arkts) ·refreshEntityerror capture.Contract
primary/secondaryactions instead of re-deriving HA semantics locally.Quality
test+assembleDebug, path-filtered).HaServiceinterface +HaBridgeunit tests (the central orchestration node was untested).Promisefile removed,debugModeoff by default, offline auto-retry.Verification
./gradlew testgreen (incl. new HaBridge/EntityMapper/version cases).assembleHapsuccessful.Deliberately out of scope
networkSecurityConfig— Android config can't express a CIDR for arbitrary local HA IPs.🤖 Generated with Claude Code