Monitor Switch + HA controls + overlay refit; merge-drop fix; full test suite (0.0.48) - #50
Merged
Merged
Conversation
Run `cargo fmt` across the backend (semantics-preserving — wrapping, trailing commas, indentation only). These modules have no behavior change; the formatting of the feature-bearing modules (main/bridge/command/display/ddc) lands with the feature commit, and the `cargo fmt --check` CI gate is added there once the whole tree is clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t, studio polish Widgets - Monitor Switch (`monitorswitch`): switch a monitor's input (HDMI/DisplayPort/…) over DDC/CI VCP 0x60. Backend ddc.rs (list_monitor_inputs / set_monitor_input — async + spawn_blocking so a slow/hung DDC read never touches the UI thread; parses the capability string for supported inputs, EnumDisplaySettingsW for resolution/refresh, masks the 0x60 reply to the low byte). Props-only meter with big touch buttons + a compact-list toggle; bespoke host; friendly sources editor (detected-input checklist + per-input rename); `displayNames` monitor-picker catalog. Pure core/monitorInputs + the Rust caps parser are unit-tested. Dev instance - `--multi` / WIDGETSACK_MULTI runs a second instance alongside the installed release: skips the single-instance lock and isolates config to <app config>/multi. A "dev" badge in the studio title bar + a "(dev)" tray-tooltip suffix mark such instances. Overlay re-fit - Overlays now re-fit on display-topology changes (monitor add/remove/move/resize — which fire no per-window scale-change event), via a polled watcher, plus a manual tray "Re-fit overlays to displays". Fixes overlays going stale after rearranging monitors. Tray - Left-click opens the studio; the menu is right-click only (show_menu_on_left_click(false)). Dropped the confusing "Edit layout" item, renamed "Open designer" -> "Open studio", added a "Start at login" toggle + a separator before Quit; tooltip shows version + "(dev)". Studio polish - NowPlaying: assembleStyles recurses into group child trees so a group-nested widget's css (the crossfade) is applied — fixes the demo widget showing two stacked covers. - Checkbox config fields: themed custom control on a single aligned row. - Add-palette: hover preview popover with a live demo render (WidgetPreview) + each widget's description in the chip tooltip. Tooling - cargo fmt --check CI gate (test.yml) + AGENTS.md; docs/widgets.md regenerated (29 widgets). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ltip de-dup - Add palette: a filter/search input narrows widgets + templates + library by name/type/description (with a "no matches" note); reordered to Widgets → Templates → Library (built-ins together, saved library last). - Hover preview now covers library defs + templates too — WidgetPreview renders any tree (a def's child / a template's tree()) through the real renderer, scaled to fit — and the popover measures its height to clamp on-screen (no bottom cutoff). - NavRail: show the native tooltip only when it expands the short label (Defs → "Widget designer", Backdrop → "Background"); the full name stays the aria-label. - Tab-switch flash: drop the rail-panel's opacity entry animation, which briefly revealed the stage behind the incoming (opaque) section panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hes, inputs Expand the Home Assistant plugin from 3 widgets to 10, all grouped under a new "Home Assistant" palette category. New prop-only control meters emit onControl → the existing '*' action → ha_call_service; service_data is built by the pure core/haControls helpers: - HaFan — on/off + speed slider + oscillate - HaInput — unified input_* helper; infers the control from the entity domain (toggle / press / select / slider / text) - HaSwitch, HaScene, HaCover (open/stop/close + position), HaLock - HaBinarySensor — device_class-aware wording (Open/Closed, Motion/Clear…) HaClimate gains A/C controls: a tap-to-cycle HVAC mode button + a fan-mode selector (set_hvac_mode / set_fan_mode). Each control widget exposes show* config toggles (mode/temp/fan, speed/oscillate, buttons/position, brightness) so sub-controls can be hidden per widget; defaults are on, so already-saved widgets are unchanged. The new builders are unit-tested (haControls.test.ts) and each new meter has a co-located component test (ha-controls.test.tsx). No backend changes — the generic ha_call_service path already covers every domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…w-transition - Add palette: major section headers (Templates / Library) get a hairline rule + breathing room — scoped to `.palette > .hd` so the property panel's own .hd headers keep their plain styling; category headers get a small top margin. - WidgetPreview: seed per-core CPU so the cpu widget previews, and show a "shows live data" placeholder for widgets that need a real backend / device / binding (now-playing, spectrum, switchers, image, iframe…) instead of a blank card. - Studio section switching now cross-fades via the View Transitions API (lib/viewTransition.ts wraps the nav swap in startViewTransition + flushSync) — the proper fix for the tab flash, feature-detected with a prefers-reduced-motion guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
appendSample now keeps each sensor's history ordered by ts_ms instead of arrival order: a sample whose ts is ≥ the last is appended (the live, O(1) case, byte-identical to before), and an earlier ts insertion-sorts into place. A parallel `historyTs` array (optional on SensorState, so existing literals/snapshots still type-check) tracks timestamps in lockstep with `history` and is capped together. This makes back-dated samples (HA history backfill) merge into the right slot rather than scrambling the series — without changing the live path, the count-based window, or `value` (still the most-recently-ingested sample). Nothing reads `historyTs` yet; sparklines keep charting `history`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an `ha_history` command that fetches REST /api/history/period for one entity over [start,end] and returns NUMERIC samples on `ha.<entity>.state` — the same id the live stream writes, so backfill + live merge into one series. Pure, tested seams: `valid_entity_id` (slug/injection guard), `iso8601_to_ms` (UTC ISO → epoch ms via days-from-civil, no chrono dep), `history_to_samples` (numeric-only, minimal_response shape). start/end are validated as real ISO before going into the URL. Frontend: each window's HA source starts a reactive backfill (ha-backfill) — when a numeric `ha.<entity>.state` sensor gains a subscriber it fetches ~24h once and ingests into THAT window's hub (no emit → no cross-window double-ingest; per-overlay correct). Order-stable ingest (prior commit) places the back-dated points correctly. A failed fetch releases its claim so a later mount retries. Pure id-helper + reactive trigger are tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HaMediaPlayer: now-playing text (title · artist) + transport (prev / play-pause / next) + a volume slider with mute, for HA-integrated media players. Prop-only meter emitting onControl → the '*' action → ha_call_service (media_play_pause / media_next_track / media_previous_track / volume_set / volume_mute); volume service_data via the pure mediaVolumeSet helper. Transport shows only while active; show* config toggles gate transport + volume. Album art deferred (entity_picture is a token-auth'd HA URL that needs a backend proxy). Builder + component tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the friction in Button macros (the HA automation/script/scene path): - domain + service inputs gain native <datalist> autocomplete of common values (light.turn_on, automation.trigger, script.turn_on, scene.turn_on, the HA control services, + the built-in media domain). - a new entity-id picker (a datalist fed the HA catalog from the Inspector) writes data.entity_id via the pure withEntityId helper, committed on blur so it doesn't remount the row mid-type; the JSON data field remains the escape hatch for extra args (brightness, temperature, …). Pure withEntityId + the editor wiring are tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ha_media_art fetches an entity_picture over REST (server-side token + insecure opt-in), stores the bytes in the shared album-art registry, and returns the http://art.localhost/<hash> URL — reusing the now-playing art scheme, so no bytes cross the JSON bridge and no CSP change is needed. HaMediaPlayerHost resolves the URL (entity_picture → ha_media_art) and renders the prop-only HaMediaPlayer with an `art` cover thumbnail (re-fetch only when the picture changes, i.e. once per track). Path is validated (host-absolute, no traversal); the token never leaks. valid_art_path seam + the art render are tested. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The button widget's whole purpose is running an action-sequence macro on press (HA services / media transport), so the palette label now says so — makes the macro entry point discoverable. Docs regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gaps Add @vitest/coverage-v8 + a `test:coverage` script and coverage config: all-files v8 coverage with principled exclusions (bootstrap, dev-only mocks, the e2e-driven Canvas organism, and thin Tauri IO adapters — invoke/listen/ window glue), so the 100% goal applies to genuinely unit-testable code; thresholds set to 100% as the enforced target. Close pure-domain gaps with real branch tests: geometry north/south min-clamp, sack container-recursion ref remap, layoutTree.monitorHasWidgets, condition >= / < / <= operators. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
prefersReducedMotion (media-query + matchMedia-absent fallback) and startViewTransition (API present + motion-allowed; API absent; reduced-motion skip) — all branches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One registration test covers home-assistant.ts (the 11-widget meta array): asserts every HA widget registers json-bound under the "Home Assistant" category, control widgets are interactive, the show* config toggles exist, the source registers, and the '*' action resolves the entity (from data or the bound ha.<entity> sensor) and calls ha_call_service. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration test: weather/sunmoon/airquality widgets register, the weather + sunmoon display toggles exist, and the source registers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration test: the assistant + transcribe widgets register, and the assistant exposes its speak (TTS) toggle. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registration tests: rss + agenda widgets register with their title/maxRows/color config; mqtt registers its source (source + settings, no widget). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercise the weather widget's sensors(config) resolver: base map + per-day weather.day.N.* expansion, clamped to 0..7 days. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
updateNode/rebuild re-applied `fn` to the matched node AND re-descended into its result. wrapLeafWith returns a fresh cell that re-contains the target (same id), so rebuild re-matched it and re-wrapped without bound — a stack overflow on every merge-drop (Canvas.tsx drag-onto-widget → group). Ids are unique, so once the match is replaced there's nothing more to find below it: stop recursing at the matched node. Surfaced by the editorOps coverage push. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Five by-group test files (place / structure / sizing / defs / bulk) cover editorOps' add / structure / sizing / float / def / token / bulk operations with behavior tests (assert the resulting tree + Patch, not internals), including the merge-drop regression that drove out the wrapLeafWith fix. Test-scoped eslint override allows non-null assertions on constructed Patch results (production code keeps the strict rule). Authored via a parallel workflow; ~150 tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
editorOps.rest.test.ts (28 tests) covers the remaining ops — splitNode grid path, addDefParam, patchGroup, setDefSize/Css, set/clear Tokens, setBackground + residual branches — taking editorOps.ts to 100% lines/functions. useEditorModel.test.ts (53 tests, via renderHook) drives the reducer through the hook: selection, the commit/op-funnel, undo/redo, history re-anchor, def-edit, and the load actions — ~99% lines. No bugs found. Authored via a parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New/extended tests at ~100%: packages.ts (plugin-package install / toggle / update / consent gates, mocked IO), and canvas hooks useDefEditor, useZoomFit, usePersistence, useThemes, useSacks, useSavedLayouts, useSplitters (renderHook + mocked Tauri/DOM, asserting real resulting state/tree). Exclude useMeasuredRects from coverage (DOM-measurement glue; its pure seam is covered in core/measureMath.test.ts). ~190 tests; full suite 1805 green, lint clean. No bugs found. Authored via a parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Component tests for LlmSettings / WeatherSettings / AgendaSettings / RssSettings / StocksSettings on the HaSettings pattern (render + mocked command adapter): config prefill, edits, validation, save→disconnect→connect ordering, and the live status badge. Weather/Agenda/Stocks → 100%, Rss 95%, Llm 83% (remainder is browser media/speech APIs happy-dom can't drive). +52 tests; suite 1857 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Component tests: DiagnosticsPanel, PluginsPanel, BackgroundPanel, SensorList, Outline, MultiInspector, DesignerListPanel (each ~100%), and the Inspector organism 53 → 86% (config-field kinds, add-palette + filter, detail tabs, widget/container/group property panels). +160 tests; suite 2017 green, lint clean. No bugs found; no panel needed excluding. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lude canvas glue MonitorSourcesEditor, MonitorSwitchHost, useTranscribe, WidgetPreview → ~100% (render/hook tests with mocked DDC/STT/LLM adapters). Exclude CpuCoresCanvas + Spectrum from coverage (canvas-2D/FFT draw glue; their pure math is covered in cpuCoresMath/sparklineMath/spectrumMath + Spectrum.test). +44 tests; suite 2061 green, lint clean. Note: MonitorSwitchHost/SourcesEditor had silently failed the 100% gate since they landed (untested + not excluded); now covered. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend tests for NowPlaying (88 → 100%: transport/seek/crossfade/grey-cue lifecycle), Agenda / Disks / Countdown / Ping (→ 100%), Cpu (100% lines; one unreachable defensive arm), and HaClimate (lower-setpoint / hvac-mode / fan-mode paths). +55 tests; suite 2116 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend/add tests for GroupFrame, FlowNode, DragSnapLayer, ThemeList, ControlsPanel, MacroEditor, registry, BoxField, TokenFields, colorHex — each to ~100% lines/functions. A few unreachable defensive arms remain (FlowNode:117, GroupFrame:93, Cpu:28, DragSnapLayer:148). +66 tests; suite 2182 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend tests for solve (→100% lines, +25), schedule/telemetry → 100%, palette/flowStyle/templates → 100% lines, plus widget, textTemplate, migration, controls, sensorActivity, plugin, pluginPackage, llm, and the formula engine + packageSandbox (incl. init-failure paths). Several unreachable defensive arms documented (not contrived). +203 tests; suite 2385 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
useKeyboard / useSensors / useLlmChat, persist, debugInfo, cssEditorExt, mcp/tools, the now-playing / llm / ha-backfill plugins + llm/source, and the Ha / NowPlaying settings residue → ~100% (a couple unreachable arms left). +72 tests; suite 2457 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…media / stores) Cover stores.ts, logs.ts, NavRail, StyleLayer, Clock, Rss, Timer/useTimer, HaInput, StickyNote, overlayPrefs, usePaneSizes, llm-studio, stocks, cssEditorLint, CssEditorImpl, weather core — and, assessed as testable rather than glue: tts, stt, Transcribe, ImageHost, VolumeHost, useControls, useAutoTheme, useBackground, useAssistant, llm-tts. Each ~100% (a few unreachable defensive arms documented). +169 tests; suite 2626 green, lint clean. No bugs found. Parallel workflow. Only useStageSize + useStudioInit assessed as exclude-worthy (ResizeObserver / listen-emit + window-manipulation glue, same class as the existing excludes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AudioSwitcherHost / HaMediaPlayerHost / StudioSettingsPanel → 100%; LlmSettings / MqttSettings / WidgetHost → 100% lines+functions (mic/stream/toast paths, sensor-binding container, dirty/save flows); Inspector def-params/floating block; useCanvasPointer, ha-exposed-store, telemetryContext, ConditionEditor, RssSettings, MonitorSwitch, plugin.ts, packages; reachable Ha* meter branches (HaSwitch/Scene/Lock/Fan/Cover/Light/BinarySensor/MediaPlayer, Bar, Text). suite 2759 green, lint clean. No bugs found. Parallel workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add core/logs.test.ts (atLeastLevel / LOG_LEVELS). Exclude the last overlay
glue from coverage (useStageSize, useStudioInit, wallpaperSampler,
plugins/index — ResizeObserver / canvas / listen-emit + window-manipulation /
barrel wiring). Replace the unattainable {100:true} gate with principled
ratchet floors (statements/lines 99, functions 96, branches 95): the residue
is unreachable defensive arms + happy-dom-undrivable visual glue. test:coverage
now passes green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The new coverage tests used the old vitest `vi.fn<[Args], Return>()` generic (invalid in vitest 2.x → 86 `tsc --noEmit` errors, though vitest passed since esbuild strips types). Converted to the function-type form `vi.fn<(...args) => Return>()` across 16 files, plus a few possibly-undefined / null narrowings and SectionId-sentinel corrections. Tighten 4 vacuous unmount tests (NowPlaying, DragSnapLayer) to assert console.error stays silent (the alive-guard is now load-bearing). Refresh the stale `updateNode` JSDoc (rebuild stops at the matched node). npm run check / lint / test:unit all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…abels Two e2e-only fixes surfaced by opening PR #50 (the branch's first CI run): - The --multi/dev-instance feature invokes `is_dev_instance` at boot; add it to the e2e devMock (returns false) so the "no unmocked commands" boot guard passes. - The add-widget palette buttons' accessible name is now "<label> — <description>" (a deliberate aria-label a11y improvement), so addWidget's exact-name match timed out and cascaded into 10 specs. Anchor the match on the label via regex and re-open the auto-collapsed add-panel. e2e 34 passed (was 24/10); check / lint / test:unit (2762) green. Test-only; no app source changed, no assertions weakened. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Summary
Large feature + hardening branch, cut as 0.0.48.
Features
ddc.rs,MonitorSwitch*,core/monitorInputs.ts).ha_media_art) and history backfill (ha_history).REFIT_OVERLAYS_EVENT,watchDisplayChanges).--multidev instance — run a 2nd instance alongside the installed release with isolated config.Fix
wrapLeafWith/rebuildrecursed unboundedly (stack overflow) on every drag-onto-widget group. Root-caused (rebuild re-descended into a replacement that re-contained the same id) and regression-tested.Tests
test:coveragegate. Pure IO/DOM/canvas glue is excluded with documented justification.Test plan
npm run check✅ ·npm run lint✅ ·npm run test:unit✅ (2762) ·npm run build(CI)cargo test/cargo clippyrun in CI (Windows).🤖 Generated with Claude Code