Skip to content

feat(desktop): HA value-setting slider for #442 Slice 1 - #461

Merged
badbread merged 1 commit into
mainfrom
feat/ha-value-slider-desktop
Aug 2, 2026
Merged

feat(desktop): HA value-setting slider for #442 Slice 1#461
badbread merged 1 commit into
mainfrom
feat/ha-value-slider-desktop

Conversation

@badbread

@badbread badbread commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Part of #442

Desktop client (Flutter) implementation of #442 Slice 1's HA value-setting controls (dimmable light brightness, cover position, fan speed), consuming the backend contract frozen in #460 (still open, not yet merged into main — the contract's shape was confirmed directly against that branch's services/api/src/ha.rs).

  • lib/api/ha_models.dart: new HaControlDescriptor (action/kind/value/min/max/step/unit), parsed defensively off HaEntityState.control — any missing/malformed field yields null rather than throwing, so an older server (or an odd entity) never breaks the whole /ha/states decode.
  • lib/api/ha_api.dart: haAction() gains an optional value param, sent only when non-null.
  • lib/ui/ha_overlay/ha_actions.dart: haValueActionLabel (row caption), haValueActionNeedsConfirm (cover/lock parity with the discrete button confirms), haFormatControlValue (kind-agnostic display, e.g. "62%").
  • lib/ui/ha_overlay/ha_state_card.dart: a value row (Slider + caption) below the existing button row, shown only when the host passes a control descriptor. Bounds/step/unit come entirely from the descriptor — never hardcoded — so Slice 2's climate.set_temperature can reuse the same widget. Commits on release with exactly one POST per drag gesture (never while dragging), never optimistically flips the displayed value (same settle-window/in-flight-spinner convention as the button row), and confirms first (with the target value in the prompt, e.g. "Set Garage Door to 20%?") for cover.set_position or a require_confirm link.
  • lib/ui/ha_overlay/ha_overlay_layer.dart: gates the slider like the button row (actuators capability + actuator role + the link's allowed_actions), wires it to POST /ha/action, and adds a right-click (secondary-tap) gesture on any controllable badge to open the detail card. This was needed because a controllable "simple" domain (light/fan) already direct-fires its primary action on left-click and never reaches the card (issue HA controls: single-click actuates directly; hover shows state; card only for multi-action devices (#187 follow-up) #428) — so a dimmable light had no gesture to reach its slider. Left-click behavior is unchanged.
  • lib/ui/overlay_editor/overlay_editor_layer.dart: adds an optional onSecondaryTapItem callback to the shared view-mode gesture layer (used only by the HA overlay; the PTZ panel's usage is edit-mode only and unaffected).

Backend, Android, and iOS were not touched.

Verify

  • Manual review: null-safety, every referenced symbol exists, imports correct, brace/paren balance checked.
  • Caught and fixed two num.clamp() return-type pitfalls (clamp() returns num even when called on an int/double, so Slider's divisions/value need an explicit .toInt()/.toDouble()).
  • flutter analyzeBLOCKED: winbuild (10.1.10.90), the only Windows Flutter box, is offline (SSH connection timed out). dev1/dev2/macmini were also checked and have no Flutter toolchain. Did not build/run on this Windows workstation per policy.

Test plan

Part of #442 Slice 1 (epic #445). Consumes the frozen backend contract
from PR #460: an optional `control` capability descriptor on each
`GET /ha/states` entry (action/kind/value/min/max/step/unit, parsed
defensively so an older server behaves exactly as today), and an
optional numeric `value` on `POST /cameras/:id/ha/action`.

The badge detail card gains a value row (a `Slider` plus a caption)
driven entirely by the descriptor's own bounds/step/unit, never
hardcoded, so Slice 2's temperature control can reuse it unchanged.
It commits on release with exactly one POST per drag gesture and
never optimistically flips the displayed value, matching the existing
button row's settle-window convention; `cover.set_position` (like the
discrete cover actions) and any require_confirm link confirm first,
with the target value in the prompt.

Left-click still direct-fires a controllable simple domain's primary
action, which never reached the card before, so a dimmable light/fan
had no way to its slider. Adds a right-click (secondary-tap) gesture,
plumbed through `OverlayEditorLayer`'s new optional
`onSecondaryTapItem`, that opens the detail card for any controllable
badge regardless of domain, leaving left-click untouched.

Flutter analyze: BLOCKED. winbuild (10.1.10.90) is offline
(connection timed out); dev1/dev2/macmini have no Flutter toolchain.
Changes were reviewed manually instead (types, null-safety, every
referenced symbol, num.clamp() return-type pitfalls).

Signed-off-by: badbread <badbread@users.noreply.github.com>
@badbread
badbread merged commit 9f44bdf into main Aug 2, 2026
6 checks passed
@badbread
badbread deleted the feat/ha-value-slider-desktop branch August 2, 2026 05:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant