Repository navigation
feat: persist disconnected devices and add the Show disconnected filter - #48
Merged
Merged
Conversation
…y migration Add DeviceIdentity (container|flow key, stable across driver reinstall) and DeviceKeyStore (convergent endpoint-id -> device-key re-key). Surface ContainerId + a Bluetooth flag on AudioEndpoint, read from the endpoint property store. Add AppSettings.KnownDevices + SettingsSchemaVersion. ADR records the identity model and Approach A (re-key, no alias layer).
…cted filter Remember devices across disconnect (and driver reinstall): the card set is now the union of live endpoints and persisted-absent known devices, reconciled by device key so surviving card instances are reused across a rebuild - which is what lets the block-slide animate a connect/ disconnect instead of rebuilding the list (no flash). BuildCards returns plain snapshots off the UI thread; DeviceCard.RefreshFrom applies them in place. A disconnected card renders dimmed with its controls disabled, a Disconnected pill, and a Forget device action. Rework the Devices assembly pipeline so the visibility filter is a single view-model predicate (DeviceListFilter) keyed off intrinsic card facts, not per-card toggle state. Add a session-only Show disconnected toggle beside Show hidden; define the opacity tiers (drag > disconnected > hidden-shown > normal) explicitly on the card. Distinguish genuinely-empty from filtered-empty in the empty-state copy. Block order, group membership, and per-device config are now keyed by the stable device key (migrated on load with a pre-migration backup); device-level concerns (volume/mute events, peak, app routing) stay keyed by the live endpoint id.
21 tests for DeviceIdentity (container|flow keys, reinstall stability, multi-endpoint discrimination, name fallback), DeviceListFilter (the group/hidden/disconnected truth table), and DeviceKeyStore (convergent id->key rewrite, idempotency, dedupe). Scoped CA1707 off for the test project so underscore-style fact names are allowed.
Move the icon / glyph / accent-tile / Wave Link theming + mute-lock tooltips out of DeviceCard.cs into DeviceCard.Visuals.cs (1188 -> 960 + 244). Only plain members are moved: the WinUI XAML markup compiler NREs (WMC9999) when source-generated [ObservableProperty] members are spread across partial files, so those stay in the main partial.
…ial file Move the contiguous app-indicator chip block (per-card reconcile, chip ordering, hidden-apps lookup, hide/close/terminate) out of HomeViewModel.cs into HomeViewModel.Apps.cs (2516 -> 1977 + 556). Plain methods only; the source-generated [ObservableProperty]/[RelayCommand] members stay in the main partial (XAML markup compiler constraint).
Address review findings: - Take the one-time pre-migration backup awaited in RebuildAsync before any re-key, so it captures pre-migration state (it was fire-and-forget against the live settings and could snapshot already-migrated data). - Disambiguate the no-container name-fallback device key too (append the endpoint-id tail), so two same-named virtual endpoints no longer collapse onto one key and drop a card. Covered by a test. - Purge a forgotten / pruned device's order, group, and config footprint via one shared helper, so the known-devices cap / age-out no longer leaves orphaned store entries. - Make Forget device a deliberate non-undoable action (it couldn't fully restore the row anyway).
A disconnected device no longer shows inside a group when Show disconnected is off. Group membership now overrides only the hidden filter (a connected, rules-less member still shows), not the disconnected filter, and SyncBlocks filters a group's displayed members through the same predicate (a group with no member left to show after filtering doesn't render this build).
…side a group The always-on block slide was only wired to the top-level list, so a device appearing in (or leaving) a group snapped its neighbours into place instead of sliding them. Attach the same implicit Offset animation to the group's inner member repeater via ElementPrepared, and have the drag teardown restore that baseline rather than detaching it.
Part 3 of the persistence spec. A Bluetooth render device's card shows a connect/disconnect button
top-right of the header (the name / id rows ellipsify to make room); it sends the same kernel-
streaming one-shot Windows' Quick Settings uses - KSPROPSETID_BtAudio / ONESHOT_RECONNECT|DISCONNECT
to the endpoint's BT KS filter via IKsControl. The COM/KS interop is source-generated by CsWin32
(new dep, pre-approved) so the IDeviceTopology / IConnector / IKsControl vtables come from the
Windows metadata, not hand-written declarations. Detection is the same topology walk (does the
endpoint terminate in a {2}.\?\bth filter), cached and persisted so a disconnected BT device
keeps its connect button. The call is fire-and-attempt and runs off the UI thread; the card's
connected state still settles from the device-arrival events.
Captures the running app's window to a PNG for before/after UI comparison. Forces the window foreground first (ALT-tap + AttachThreadInput) because GDI capture of a WinUI 3 window returns black unless it's the foreground window (its swapchain is otherwise on a hardware overlay plane).
…specs Both feature prompts are fully implemented on this branch (persistence Parts 1-3 + the block- assembly rewrite), so the one-off implementation specs are no longer needed.
hoobio
enabled auto-merge (squash)
May 31, 2026 15:13
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.
Implements
docs/features/device-persistence-feature.md(all three parts) anddocs/features/devices-block-assembly-rewrite.md.What changed
BuildCardsreturns plain snapshots off the UI thread;DeviceCard.RefreshFromapplies them in place), so connect/disconnect animates the block slide instead of recreating every element. The slide is now also wired to group members, so a device appearing in / leaving a group slides its neighbours.container id + flow, stable across a driver reinstall) instead of the volatile endpoint id. Migrated on first load with an awaited pre-migrationsettings.jsonbackup; the rewrite completes convergently as absent devices reappear, so the stores end up holding only device keys with no runtime resolver. ADR:docs/adr/device-persistence-and-identity.md.DeviceListFilter) keyed off intrinsic card facts, not per-card toggle state. A session-only "Show disconnected" toggle sits beside "Show hidden". The disconnected filter applies to group members too (a disconnected member hides when the toggle is off; a connected one still shows), opacity tiers (drag > disconnected > hidden-shown > normal) are explicit, and the empty state distinguishes "no devices" from "everything filtered out".KSPROPSETID_BtAudio/ONESHOT_RECONNECT|DISCONNECTto the endpoint's BT KS filter viaIKsControl). Detection is the same topology walk, cached + persisted so a disconnected BT device keeps its connect button.Verified
Earmark.Coreunit tests pass (device-key computation incl. collisions, the filter predicate, the store re-key).Notes
Microsoft.Windows.CsWin32(analyzer-only,PrivateAssets=all) so the Bluetooth COM/KS interop is generated from the Windows metadata rather than hand-maintained.DeviceCardandHomeViewModelwere over-long, so cohesive plain-member regions were split into partial files. Source-generated[ObservableProperty]/[RelayCommand]members had to stay in the main partial (the WinUI XAML markup compiler NREs when they are spread across files).feat(devices): add a Bluetooth connect/disconnect button on Bluetooth device cards
test(devices): cover device-key identity, list filter, and store re-key
refactor(devices): split oversized DeviceCard and HomeViewModel into partial files