Skip to content

feat: Devices app chips, close/terminate, undo, and card options - #44

Merged
hoobio merged 20 commits into
mainfrom
feature-26-05-31
May 31, 2026
Merged

hoobio merged 20 commits into
mainfrom
feature-26-05-31

Conversation

@hoobio

@hoobio hoobio commented May 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

Devices-page work accumulated on this branch since grouping landed on main: per-device app-chip indicators, close/terminate from a chip, card-height/divider options, Ctrl+Z undo for structural actions, and a reworked card reflow animation.

Changes

  • App-chip indicators per device card: live peak underbars, drag-to-reroute, and a context menu (hide app, close, terminate).
  • Close / terminate an app from a chip: graceful WM_CLOSE or force-kill, acting on every process the app spawns (a browser's renderer/audio children), elevation-aware.
  • Card-height and divider options in Settings: Match-row / Balanced / Dynamic heights, and opt-in section dividers.
  • "Always show pinned apps" so a rule-pinned app keeps its chip while silent.
  • Ctrl+Z undo extended to chip hide, block/member reorder, and group create / join / leave / delete.
  • Animation: chips glide on re-sort (rank-based, not collection `Move`); the card reflow now always animates - cards slide when an apps row appears, a device connects/disconnects, the window resizes, a card is shown/hidden, or a group changes - attached after each element's arrange so first placement stays instant and scrolling stays crisp.
  • Reflow fix: expanding one card's rules no longer shrinks a shorter no-rules sibling (the row baseline holds the expanded card's collapsed height).
  • In-window toast service for action feedback, plus the WinAppSDK #6071 self-contained notification-resource workaround.
  • Navigation pane expand/collapse persisted across relaunches.
  • Dependencies refreshed to current stable.
  • Docs: in-progress feature specs under `docs/features/` (device persistence, Devices block-assembly rewrite) for follow-up work.

Testing

  • Manual testing on a Windows 11 host; the relaunched build was exercised for routing, chip placement, close/terminate, undo, and the reflow/scroll animations
  • Latest log shows the expected `Applied rule ...` / `Skip Set ...` / `Skip re-apply ...` lines
  • Unit tests added/updated (UI/interaction changes; none added)
  • Existing tests pass (Core.Tests is scaffolding only)

Checklist

  • PR title follows Conventional Commits
  • Code builds without warnings (0/0)
  • No emoji / gitmoji
  • Architecture boundary respected
  • AGENTS.md updated (the #6071 workaround + multi-agent etiquette)

feat: per-device app-chip indicators with drag-to-reroute on the Devices page

feat: close and terminate an app from its chip menu, across all the app's processes

feat: card-height and card-divider options for the Devices page

feat: add "always show pinned apps" setting for rule-pinned chips

feat: persist the navigation pane expand/collapse state across relaunches

feat: undo app-chip hide, reorder, and group changes with Ctrl+Z

fix: glide app chips on re-sort by ranking, not collection Move

fix: always animate the Devices card reflow (apps row, device add/remove, resize)

fix: expanding a card's rules no longer shrinks a shorter sibling card

fix: move a device directly from one group into another in one drag

fix: lay out Insights.Resource.dll for self-contained app notifications

build: update dependencies to latest stable

hoobio added 20 commits May 31, 2026 11:34
- Windows App SDK 1.8.260416003 -> 2.1.3 (Microsoft.Windows.SDK.BuildTools 10.0.26100.4948 -> 10.0.28000.1839)
- CommunityToolkit.WinUI.* 8.2.250402 -> 8.2.251219
- Microsoft.Extensions.* 10.0.0 -> 10.0.8
- NAudio.Wasapi 2.2.1 -> 2.3.0
- Microsoft.NET.Test.Sdk 17.13.0 -> 18.6.0, xunit.v3 1.0.1 -> 3.2.2, xunit.runner.visualstudio 3.0.2 -> 3.1.5
- replace FluentAssertions 7.1.0 with AwesomeAssertions 9.4.0 (FA v8 needs a commercial licence)
- pin Microsoft.WindowsAppSDK.Runtime 2.1.3 as the path source for the #6071 notification workaround (wired up in the follow-up commit)
WinAppSDK 2.x unpackaged self-contained builds omit
Microsoft.WindowsAppRuntime.Insights.Resource.dll, so AppNotificationManager.Register
throws 0x8007007E (MOD_NOT_FOUND) even though IsSupported() returns true
(microsoft/WindowsAppSDK#6071).

- _EarmarkLayoutInsightsResource extracts that DLL from the runtime MSIX into build and publish output
- reference Microsoft.WindowsAppSDK.Runtime with GeneratePathProperty to locate the MSIX
- gate Register() on AppNotificationManager.IsSupported() per the self-contained deployment guide
Record what _EarmarkLayoutInsightsResource is for and how to verify whether a future
WindowsAppSDK bump fixes #6071 so the workaround can be removed.
Card height (Balanced / Match row / Dynamic) controls how cards size within a row; card dividers draw Settings-style hairlines between a card's sections (default on). Both are shared through PeakMeterOptions and exposed under Settings > Devices.

SettingsPage.xaml and HomeViewModel.cs also carry minor concurrent edits from another change (a section-header margin and a doc-comment rename).
The scroll region runs full-width so its scrollbar rides the window edge (Settings-style), content inset via padding; the page title gets matching top breathing room. App chips now use Composition implicit animations (slide on re-sort, fade out on prune, instant appearance so there's no blank gap as the card grows), and the opt-in section divider lines render.

HomePage.xaml also carries a concurrent context-menu rework from another change (Delete group + critical-red styling), committed whole rather than split per the file's mixed state.
The app-chip Composition setup used an implicit opacity hide animation; on a card container recycled by the block ItemsRepeater it came back stuck at opacity 0, so a chip placed on a recycled card rendered invisible (its divider and reserved space showed, but no chip). Drop the opacity animation and keep the Offset slide only. Also remove the scroll region's bottom padding so cards scroll to the window's bottom edge instead of stopping short.
The apps row attached a Composition implicit Offset animation per chip, but
re-sorting via ObservableCollection.Move made the ItemsControl recreate the
moved chip's container - it landed fresh at its destination with no slide,
so only the chips that stayed animated. Keep the collection in arrival order
and have the WrapPanel arrange chips by a per-chip WrapOrder rank instead;
a re-sort now re-positions the same containers, so every moved chip glides.
…argin

The list's trailing space lived on the ScrollViewer padding, which reserved a dead band at the viewport bottom that clipped cards short mid-scroll. Move it onto the list itself (DevicesRepeater.Margin) so cards scroll flush to the window's bottom edge and only the last card clears it - by 28px, matching the side inset. Also centralise the page's content insets into named Thickness tokens in App.xaml (Padding/Margin can't take the x:Double Spacing tokens).

HomePage.xaml also carries a concurrent context-menu rework from another change, committed whole rather than split per the file's mixed state.
…ches

Restore NavView.IsPaneOpen from AppSettings on launch (in NavView.Loaded, so Auto display-mode init doesn't clobber it) and save it when the user toggles the pane, so the left sidebar's collapsed/expanded state survives a restart. Defaults to expanded.
- Right-click an app chip to Close it gracefully (posts WM_CLOSE to its windows); Shift+right-click reveals a red Terminate that force-kills it.
- Elevated apps are pre-detected by integrity level: when a medium Earmark can't message an elevated target, Close is disabled and relabelled "Cannot close elevated app", while Terminate stays available where the OS permits it.
- Elevated app chips show a shield badge.
- Close/terminate feedback uses a bottom-centred in-app toast; Windows notifications stay reserved for background events like rule-driven device corrections.
- A deliberate, successful close/terminate prunes the chip after a short 3s linger instead of the full configurable window.
A multi-process app (a browser's GPU / renderer / audio children all run the
same exe) only owns its main window on the browser process, which is rarely the
pid holding the audio session the chip stands for - so closing that one pid did
nothing and terminating it left the rest running. Gather every running pid that
shares the app's identity (lowercase exe path) and act on the whole set: WM_CLOSE
to any of their top-level windows, TerminateProcess on each.
Extends the Devices-page undo stack beyond hide/show and volume/mute to cover
the structural actions: hiding an app chip, reordering blocks or group members,
and creating / joining / leaving / deleting a group. Each captures a snapshot of
the layout state (block order, groups, hidden apps) before it mutates, and Ctrl+Z
restores that snapshot wholesale and resyncs - simpler than reversing each op and
uniform across all of them. Group rename is not covered.
When a card's apps row appears/disappears the cards below now slide to their new
spots instead of snapping. The prior pass armed the block Offset animation then
detached it on the next LayoutUpdated, which fired at/before the reflow arrange,
so the slide never ran. Arm it on the apps-row change and leave it attached so
back-to-back reflows glide; drop it on the ScrollViewer's ViewChanging (before a
scroll re-arranges realised blocks, so the scroll stays crisp) and re-arm on the
next change. No 'still scrolling' flag - the scroll's final ViewChanged isn't a
reliable signal and a stuck flag killed the animation for the rest of the session.
In Balanced card-height mode, expanding one card's rules made it opt out of the
row baseline, which then dropped to the shortest remaining card - so a no-rules
sibling visibly shrank the moment you expanded a neighbour. Each layout now
remembers a card's last stretch-eligible (collapsed) height and keeps counting it
toward the baseline while the card is custom-sized, so the expanded card grows on
its own and its row-mates hold their height.
The slide was disarmed on the ScrollViewer's ViewChanging, but a reflow grows the
content and itself raises ViewChanging once the list has been scrolled - so it
stripped the implicit mid-reflow and killed the slide for the rest of the session
(it only worked before the first scroll, while the content still fit). Disarm on a
short one-shot timer instead: attached for the reflow's arrange, gone before any
later scroll. Also arm on Blocks.CollectionChanged so a device connecting or
disconnecting (and showing/hiding a card, or forming/disbanding a group) slides
the surrounding cards into place instead of snapping.
The slide was gated (arm on change, disarm via timer / ViewChanging, settle flag),
which made it unreliable: maximizing the window didn't animate, and once the list
had been scrolled the reflow stopped firing at all. Drop all of it. Every realised
block now carries the implicit Offset slide, attached AFTER its arrange so it snaps
into place on first realise / recycle and animates every later move - a reflow, a
device added/removed, a window resize, a show/hide, a group change, or a drag.
Detaching on (re)prepare keeps scrolling crisp (a recycled card is repositioned
without the implicit). One mechanism, same as the app chips, no gating to misfire.
@hoobio
hoobio merged commit dd387a6 into main May 31, 2026
13 checks passed
@hoobio
hoobio deleted the feature-26-05-31 branch May 31, 2026 07:52
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>
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