Repository navigation
feat(rules)!: pinned/one-shot actions, drag-and-drop, consolidated schema - #49
Merged
Merged
Conversation
…d-drop Collapse the redundant action/condition enum pairs into orthogonal fields: one DeviceMute action with a muted toggle, one WaveLinkMix action with include/exclude/exclusive membership, and one app/default-device action with an output/input direction; Device/Application conditions gain a present/missing (running/not) polarity flag. Add a "Default device is" condition driven by the existing DefaultsChanged event. Add a per-action Pinned flag: pinned actions are reconciled (external drift is reverted, today's behaviour), one-shot actions fire once on the rule's condition-activation edge and are then left editable. The applier computes activation edges per cycle and gates every apply pass (app/default/volume/mute/ Wave Link) on pinned-or-edge; reconcile passes enforce pinned only. The Devices page lock + reconcile honour Pinned too, so a one-shot never locks a slider. Rebuild the Rules editor on drag-capable ListViews: reorder conditions/actions, move actions between the Actions and Otherwise branches, and drag either onto another rule (type-enforced - a condition can't land on an action list). Cross-rule moves rebuild the row wired to the target rule and persist both rules immediately. Add per-row Duplicate for actions and conditions, and slide/fade transitions matching the Devices page.
Add coverage for the consolidated condition model (Device/Application/ DefaultDevice with the present/missing polarity flag), flow filtering, all-conditions-must-hold, app + default-device route matching with else-branch selection, and the resolver's first-match-wins plus Pinned threading.
…ion card Subscribe the Rules view-model to DefaultsChanged so a "Default device is" condition's live satisfied/status preview updates when the system default switches, not only on topology changes. Group each action's mode control (direction / membership / mute) beside the Pin toggle on one row so the editor card stays compact.
…ed specs Rewrite the AGENTS.md rule-schema section for the condition/action model (Kind + orthogonal mode fields, the Pinned reconcile-vs-one-shot flag, and the drag-reorder/commit-immediately behaviour), and point the routing-interface notes at the new action kinds. Remove the rules-drag-and-drop and pinned-rules handoff specs now that they're implemented (the drag spec described the old ItemsControl layout).
Picking a rule, condition, or action up now opens a real make-space slot at the hover point and the other rows glide into it, matching the Devices page: the source slot collapses, a margin-driven gap of the same height opens at the drop index, and every realised row carries a Composition Offset implicit so the reflow animates. The rule list moves off the built-in CanReorderItems onto the same manual drag, which also removes the built-in drag-out-removes-item behaviour - every drop now only Moves or re-inserts, never deletes, so a rule dropped outside the list just stays put. Rename the "Pin device volume" action kind to "Set device volume": the per-action Enforcement toggle already says Pinned vs One-shot, so the kind name no longer needs to imply pinning.
…hotspots
The make-space gap overwrote a container's whole Margin (to insert the gap, then
reset to zero), flattening the rule list's "0,4" inter-card spacing after a drag.
Widen the existing margin instead and restore it exactly, and stop resetting the
picked-up container's margin (only its visibility was changed).
Make each section title ("Conditions" / "Actions" / "Otherwise") and the "Rules"
page header drop targets that land the dragged row at the top of their list, and
show a dashed "drop here" placeholder when a list is empty that is itself the
index-0 target. This is what lets a condition or action be dragged into a rule
that has none yet (an empty list collapses to zero height, so it offered no drop
space before).
Drop the inline caption under each "Conditions" / "Actions" / "Otherwise" title and put the same text behind an info (i) icon beside the title, matching the page header. Trim the redundant "the rule always runs" from the empty- conditions placeholder (the Conditions tooltip already says leave-empty-to-run).
Add RuleShadowAnalyzer: for each rule, the active-branch actions whose target is already claimed by an earlier (higher-priority) enabled rule, using the same first-match-wins precedence the appliers enforce (per-app+flow, per default flow+role, per device for volume/mute; Wave Link needs the live snapshot so it's not analysed). The Rules editor flags each such action with a caution glyph and raises the rule-level warning (shown collapsed and expanded). When every live action is superseded the rule dims like a no-match rule and reads "Superseded by a higher-priority rule".
When every live action is superseded, the rule warning now reads "All actions are superseded by higher-priority rules and won't run"; with only some superseded (others still active) it keeps the singular "An action is superseded by a higher-priority rule and won't run".
…t line Drop the redundant "Action" / "Condition" kind-combo headers (the cards are already grouped under the section titles) and pull the self-labeling mode controls onto the same row as the kind: an action reads "Set device for app · Output · Pinned" and a condition reads "Device · Present · Any", with the target patterns beneath. The toggles' own on/off text (Output/Input, Mute/Unmute, Pinned/One-shot) carries the meaning, so the per-field headers are gone.
Conditions now show the same match chip as actions: a Device/Default-device condition shows the matched endpoint name, an Application condition shows the matching-app count (computed in ConditionRow.Recompute, flow-aware). Re-run the match preview as the user edits, not only on save or an external audio event: RuleRow raises PreviewInvalidated on any live content change and the view-model re-queues the (250ms-debounced) refresh, so chips, satisfied state, and shadow flags track the typed text for dirty rules too.
Add a PatternMatchMode to every pattern field (app / device / mix on actions, device / app on conditions), defaulting to Regex so existing rules are unchanged. Wildcard converts globs to a "contains" regex (* = any run, ? = one char, \* / \? = literal); Exact is a case-insensitive whole-string match (for picked items). PatternMatcher gains a mode-aware overload and RegexCache a cached wildcard compiler; the mode is threaded through the matcher, evaluator, resolver, shadow analyzer, and Wave Link applier. UI to follow.
Each pattern field (app / device / mix on actions, device / app on conditions)
gets a "Match" dropdown: Regex, Wildcard, or the field's own picker (Device / App
/ Mix) for an exact match. The field's AutoSuggestBox doubles as the free-text
input and, in pick mode, the searchable list - device candidates are full display
names ("Friendly (Hardware)"), app candidates running process names, mix
candidates Wave Link mix names, all flow-filtered. The view-models match
mode-aware, round-trip the modes, and the Devices-page rule list honours them too.
…ator - Exact-match fields now use a read-only dropdown with an in-flyout search box (PickerComboBox), replacing the editable combo that dropped focus while typing. - De-dupe picker/chip names when the friendly name already ends with the device description (AudioEndpoint.PickerName), e.g. "Comms (Elgato Virtual Audio)" not doubled. - Match chips now also match currently-disconnected devices and flag them "offline" (tooltip lists which); condition satisfaction still treats a disconnected device as absent.
hoobio
enabled auto-merge (squash)
June 1, 2026 03:01
hoobio
pushed a commit
that referenced
this pull request
Jun 1, 2026
This PR was generated automatically by [release-please](https://github.com/googleapis/release-please). --- ## [0.2.0](v0.1.9...v0.2.0) (2026-06-01) ### ⚠ BREAKING CHANGES * **rules:** pinned/one-shot actions, drag-and-drop, consolidated schema ([#49](#49)) * **devices:** rules.json schema replaced - actions and conditions now use a Kind plus orthogonal mode fields (direction / membership / muted) and a Pinned flag; the old type-per-row enum no longer deserialises. ### Features * add "always show pinned apps" setting for rule-pinned chips ([dd387a6](dd387a6)) * add appearance backdrop setting (Mica, Acrylic, Solid) ([#39](#39)) ([7f2769a](7f2769a)) * add Devices-page reset to default and new-install seeding ([baffea1](baffea1)) * card-height and card-divider options for the Devices page ([dd387a6](dd387a6)) * close and terminate an app from its chip menu, across all the ([dd387a6](dd387a6)) * device customisation panel (glyph + accent colour) ([#46](#46)) ([34956eb](34956eb)) * Devices app chips, close/terminate, undo, and card options ([#44](#44)) ([dd387a6](dd387a6)) * Devices page options menu (show title bar / show hidden / lock ([288d13a](288d13a)) * **devices:** add a Bluetooth connect/disconnect button on Bluetooth ([c6feb57](c6feb57)) * **devices:** add persisted device-card display toggles ([1ea0d9e](1ea0d9e)) * hide an app's chip on a single device from its chip context menu ([34956eb](34956eb)) * hide app chips via a context menu, manage from settings ([#41](#41)) ([8e62187](8e62187)) * per-device accents, customise dialog, and Devices page options ([#47](#47)) ([288d13a](288d13a)) * per-device app-chip indicators with drag-to-reroute on the Devices ([dd387a6](dd387a6)) * per-device deterministic accent colours, reworked customise ([288d13a](288d13a)) * persist disconnected devices and add the Show disconnected filter ([#48](#48)) ([c6feb57](c6feb57)) * persist the navigation pane expand/collapse state across ([dd387a6](dd387a6)) * **rules:** flag matched-but-disconnected devices in the match chip ([1ea0d9e](1ea0d9e)) * **rules:** per-field match modes (regex / wildcard / exact) with a ([1ea0d9e](1ea0d9e)) * **rules:** pinned/one-shot actions, drag-and-drop, consolidated schema ([#49](#49)) ([1ea0d9e](1ea0d9e)) * **rules:** warn when an action is superseded by a higher-priority rule ([1ea0d9e](1ea0d9e)) * undo app-chip hide, reorder, and group changes with Ctrl+Z ([dd387a6](dd387a6)) ### Bug Fixes * allow click-to-mute on the device icon when volume controls are ([34956eb](34956eb)) * always animate the Devices card reflow (apps row, device ([dd387a6](dd387a6)) * expanding a card's rules no longer shrinks a shorter sibling card ([dd387a6](dd387a6)) * glide app chips on re-sort by ranking, not collection Move ([dd387a6](dd387a6)) * keep the device accent tile coloured while a mute rule is active ([288d13a](288d13a)) * lay out Insights.Resource.dll for self-contained app notifications ([dd387a6](dd387a6)) * move a device directly from one group into another in one drag ([dd387a6](dd387a6)) ### Documentation * note the backdrop setting in the design-language section ([7f2769a](7f2769a)) * rewrite the rule schema reference for the consolidated model ([1ea0d9e](1ea0d9e)) * spec loopback / listening chip suppression heuristic ([#45](#45)) ([16b4aad](16b4aad)) ### Code Refactoring * **devices:** split oversized DeviceCard and HomeViewModel into ([c6feb57](c6feb57)) ### Tests * cover rule matcher, device-rule resolver, and shadow analyzer ([1ea0d9e](1ea0d9e)) * **devices:** cover device-key identity, list filter, and store re-key ([c6feb57](c6feb57)) ### Build System * update dependencies to latest stable ([dd387a6](dd387a6)) ### Continuous Integration * use the release-please PR body for pre-release notes ([288d13a](288d13a)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: release-please-hoobi[bot] <279189756+release-please-hoobi[bot]@users.noreply.github.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.
Reworks the rules engine and editor: implements the drag-and-drop and pinned-rules specs, plus the consolidation and duplicate asks. Done as a clean schema rewrite (no back-compat loader), since this is a new app.
Schema
DeviceMuteaction with a muted toggle, oneWaveLinkMixaction with Include/Exclude/Exclusive membership, and one app/default-device action with an Output/Input direction. Conditions collapse toDevice/Application/DefaultDevicewith a present/missing (running/not) polarity flag.Pinnedflag. Pinned actions reconcile (revert external drift, today's behaviour); one-shot actions fire once on the rule's condition-activation edge and are then left editable. The applier computes activation edges each cycle and gates every apply pass (app / default / volume / mute / Wave Link) on pinned-or-edge; reconcile passes enforce pinned only. The Devices page lock honoursPinned, so a one-shot never locks a slider.Default device is...condition, driven by the existingDefaultsChangedevent.Editor
Devices Page
...page options menu.Migration
The
rules.jsonschema is incompatible with the old format. My local rules were migrated in place (backups kept in the Earmark data folder). A fresh install just needs a new rules.json, or re-create rules in the editor.Validation
feat(rules): warn when an action is superseded by a higher-priority rule
feat(rules): per-field match modes (regex / wildcard / exact) with a searchable exact-match picker
feat(rules): flag matched-but-disconnected devices in the match chip
test: cover rule matcher, device-rule resolver, and shadow analyzer
docs: rewrite the rule schema reference for the consolidated model
feat(devices): add persisted device-card display toggles
BREAKING-CHANGE: rules.json schema replaced - actions and conditions now use a Kind plus orthogonal mode fields (direction / membership / muted) and a Pinned flag; the old type-per-row enum no longer deserialises.