fix: rename Debloater & Ads to Preinstalled Apps, move New App Alerts to Apps - #2341
Conversation
… to Apps #1515 - the name promised a capability the tab does not have. "Debloater & Ads" only ever listed and removed preinstalled Store apps. It has no ad controls at all: grepping DebloaterService for Registry/SetValue/SubscribedContent returns nothing, and the one place "ads" appears is inside an app DESCRIPTION plus Microsoft.Windows.ContentDeliveryManager - which sits in the PROTECTED prefix list, so the ad-delivery component is explicitly not removable here. The five switches that do turn ads and suggestions off are in Privacy & Telemetry (advertising ID, Start suggestions, tips, lock screen tips, Spotlight ads). So someone who wanted fewer ads opened this tab, found a list of Store apps, possibly removed something she needed, and never found the four toggles that would have helped. "Debloater" is also enthusiast jargon - the target persona does not know what bloat is, but does understand "apps that came with the laptop". Renamed to "Preinstalled Apps" (17 chars; the longest label that renders without the ellipsis today is 23). The page now carries a third line pointing at Privacy & Telemetry, and the sidebar keywords keep "debloat" and "debloater" so the old word still finds the tab. The mechanical half of the same defect: searching "ads" found this tab and did NOT find Privacy & Telemetry, whose keywords only had "advertising id". That is the discoverability bug in concrete form, so "ads, adverts, suggestions, tips, spotlight" now attach to the tab that can actually change them. #1528 - detection was filed away from its remedy. New App Alerts moved from Monitor to Apps, directly above Uninstaller. Apps is now the whole lifecycle: install, update, detect, remove. This REVERSES a placement this repo asserted, with its reason recorded - "App Alerts passively watches for new installs, it belongs with the monitoring tabs" - so the reversal is deliberate and the old argument is preserved in the test rather than deleted. The mechanism claim was true: the tab owns a live FileSystemWatcher and a 30-second registry timer. It is the ERRAND that moved. The question is "did something install itself without me asking?", and the next thing she wants is to get rid of it. The tab's own subtitle already pointed there ("removing a program is Apps -> Uninstaller"), and AppAlertService reads the SAME two registry trees as UninstallerService (Uninstall and the WOW6432Node twin), which is what makes this app-inventory work rather than resource monitoring. Two premises in #1528 had already been fixed and are noted rather than re-done: the sidebar/page mismatch it describes is gone - both say "New App Alerts" since an earlier release (CHANGELOG 2785) - so the rename to "App Installation Alerts" would now be a regression: 23 characters against 14, at the exact ellipsis limit, and it would re-create the mismatch unless the page changed too. Asserted in NavGroups_FileTabsAreGroupedByErrand_NotByMechanism as its fourth case rather than a new parallel test, because that test exists for exactly this decision shape and builds the tab graph once - three extra MainWindowViewModel constructions once took the CI integration run from 6 minutes to 22. Position is asserted, not just membership: adjacency to Uninstaller is the reason it moved, so being anywhere in Apps is not enough. New guard - the README group table had drifted twice, unnoticed. EveryReadmeGroupRow_ListsExactlyItsGroupsTabs re-derives every "Group | Tabs" row from the nav table. It found a defect that predates this work: Large Files shipped in v1.109.0 and was never added to the Storage & Files row. Moving New App Alerts would have left it listed under Monitor as well. The counts guard next door checks HOW MANY groups exist; nothing checked WHICH tabs each one claims, and that table is the first complete picture of the app a reader gets. Membership only, deliberately not order: reordering two tabs inside a group is not a defect, while naming a tab that moved or omitting one that shipped is. The one adjacency that matters is asserted on the real graph instead. One trap in the guard, documented in it: the emoji-stripping pattern was [^\p{L}]+, which matched NOTHING on the Info row, because U+2139 INFORMATION SOURCE is Unicode category Ll - a lowercase LETTER - while the other eleven prefixes are So. Keyed on ASCII letters instead, since every group label is ASCII. Red ritual, 9 mutations, all red: Large Files dropped from the Storage row ..... RED (the real drift) New App Alerts left listed under Monitor ..... RED the table row keeps the old tab name ......... RED a group row is invented ..................... RED the table header is reworded away ........... RED via the floor the tab is renamed in source, README is not . RED (the other direction) the group-header pattern matches nothing .... RED via the floor New App Alerts moves back to Monitor ........ RED it stays in Apps but loses the adjacency .... RED The sixth matters most: a guard that only reads the README would pass while the source moved underneath it. Also fixed while in here: two stale screenshot alt texts naming "App Installation Alerts" and "Debloater & Ads", the Apps sidebar subtitle, and the tab dropdown in all three issue templates. Closes #1515 Closes #1528 --- fixup, after CI --- The UI job caught one more call site the rename had to reach, and closing it properly turned out to need a second guard. AllTabsSmokeUiTests waited for the header "Debloater" on nav-debloater. My first grep was for "Debloater & Ads" and this row says only "Debloater", so I missed it - and the failure surfaced only in the continue-on-error UI job, which reports success at the check level. Two guards already sit next to this and neither could see it: EverySidebarTab_HasASmokeRow - checks each tab HAS a row EveryUiTextAssertion_QuotesCopyTheApp- - checks UI waits quote real copy ActuallyShips Neither checks the PAIR. And the second could not have, for two independent reasons - the second being the one that matters: 1. IsUserFacingSentence requires at least one space, and "Debloater" is a single word, so it fell under the scope bar. 2. Far worse: that guard matches a literal at the CALL SITE, and this table feeds its literals through [MemberData] - the call reads HasTextInCurrentTab(expectedHeader), a parameter. So NO row of the table was ever checked, at ANY length. A three-word header would have been just as invisible, which mutation M2 confirms. EverySmokeRowHeader_IsTextItsOwnTabRenders is population-driven instead: the table is an enumerated list of (nav id, header) pairs, and the nav table says which view each id opens, so every pair is compared exactly with no word-count bar to fall under. My first version of it stayed GREEN on the very defect it was written for. It compared against the view's whole renderable text, and DebloaterView.xaml still contains "Debloater" in x:Class="SysManager.Views.DebloaterView" and in its designer DataContext - so the row matched markup rather than copy. Same corpus-too-wide mistake the older guard made with /// comments, arriving from a new direction: a type name is not something a user can read. It now reads the view's Display header alone. All 59 tab views carry exactly one literal Display header and none is a binding, so the narrow corpus is also the complete one. Red ritual on the new guard, 6 mutations, all red - and the old guard was run alongside two of them to prove this is not a duplicate: the row waits for the old one-word header ... RED old guard: GREEN a THREE-word header the app does not render . RED old guard: GREEN the view stops rendering that header ........ RED the smoke-row pattern matches nothing ....... RED via floor 1 the nav-row-with-view pattern matches none .. RED via floor 2 the Display-header lookup finds nothing ..... RED via floor 3
Fixup after the first CI run — and the guard it neededThe UI job caught one more call site the rename had to reach: Row fixed. But the interesting part is why nothing blocking caught it. Two guards sit right next to this and neither could see it
Neither checks the pair. And the second could not have, for two independent reasons — the second being the one that matters:
|
| Mutation | New guard | Old guard |
|---|---|---|
| the row waits for the old one-word header | RED | GREEN — the hole |
| a three-word header the app does not render | RED | GREEN — the hole |
| the view stops rendering that header | RED | — |
| the smoke-row pattern matches nothing | RED via floor 1 | — |
| the nav-row-with-view pattern matches nothing | RED via floor 2 | — |
| the Display-header lookup finds nothing | RED via floor 3 | — |
Verification
- All four projects: 0 errors, 0 warnings;
dotnet formatclean. - Unit suite twice: 5774 / 5774 (5773 before, +1 guard).
b05ee7e to
cabc126
Compare
Closes #1515. Closes #1528.
#1515 — the name promised something the tab does not do
"Debloater & Ads" only ever listed and removed preinstalled Store apps. It has no ad controls at all:
DebloaterServicegrepped forRegistry/SetValue/SubscribedContent→ nothing.Microsoft.Windows.ContentDeliveryManager— which sits in the protected prefix list, so the ad-delivery component is explicitly not removable here.
suggestions, tips, lock-screen tips, Spotlight ads.
So someone who wanted fewer ads opened this tab, found a list of Store apps, possibly removed something she
needed, and never found the toggles that would have helped. "Debloater" is also enthusiast jargon — the target
persona does not know what bloat is, but does understand "apps that came with the laptop".
Renamed to "Preinstalled Apps" — 17 characters, against 23 for the longest label that renders without the
ellipsis today. The page gains a third line pointing at Privacy & Telemetry, and the sidebar keywords keep
debloat/debloaterso the old word still finds it.The mechanical half of the same bug
Searching "ads" found this tab and did not find Privacy & Telemetry, whose keywords only had
"advertising id". That is the discoverability defect in concrete, checkable form — so
ads, adverts, suggestions, tips, spotlightnow attach to the tab that can actually change them.#1528 — detection was filed away from its remedy
New App Alerts moved from Monitor to Apps, directly above Uninstaller. Apps is now the whole
lifecycle: install, update, detect, remove.
This reverses a placement this repo asserted, with its reason recorded — "App Alerts passively watches for
new installs, it belongs with the monitoring tabs". The reversal is deliberate and the old argument is kept in
the test rather than deleted, because the mechanism claim was true: the tab owns a live
FileSystemWatcheranda 30-second registry timer.
It is the errand that moved. The question is "did something install itself without me asking?", and the
next thing she wants is to get rid of it. Two things corroborate it:
AppAlertServicereads the same two registry trees asUninstallerService(Uninstalland theWOW6432Node twin), which is what makes this app-inventory work rather than resource monitoring.
Two of #1528's premises were already fixed — noted, not re-done
The sidebar/page mismatch it describes is gone: both say "New App Alerts" since an earlier release
(
CHANGELOG.md:2785records it). So the rename to "App Installation Alerts" would now be a regression — 23characters against 14, sitting exactly at the ellipsis limit, and it would re-create the mismatch unless the
page changed too. I left the name alone and am flagging it rather than silently doing half a decision.
Where it is asserted
As the fourth case in
NavGroups_FileTabsAreGroupedByErrand_NotByMechanism, not a new parallel test. Thattest exists for exactly this decision shape (File Lock #1521, Bandwidth Monitor #1514, Notification Blocker
#1522) and builds the tab graph once — three extra
MainWindowViewModelconstructions once took the CIintegration run from 6 minutes to 22, with leaked
winget_*_hang.logartifacts.Position is asserted, not just membership: adjacency to Uninstaller is the reason it moved, so being
anywhere in Apps is not enough.
New guard — the README group table had drifted twice, unnoticed
ArchitectureTests.EveryReadmeGroupRow_ListsExactlyItsGroupsTabsre-derives every| Group | Tabs |row fromthe nav table. It immediately found a defect that predates this work: Large Files shipped in v1.109.0 and
was never added to the Storage & Files row. Moving New App Alerts would have left it listed under Monitor too.
The counts guard next door checks how many groups exist; nothing checked which tabs each one claims — and
that table is the first complete picture of the app a reader gets.
Membership only, deliberately not order: reordering two tabs inside a group is not a defect, whereas naming a
tab that moved or omitting one that shipped is. The one adjacency that matters is asserted on the real graph.
One trap inside the guard, documented in it
The emoji-stripping pattern was
[^\p{L}]+and matched nothing on the Info row, because ℹ U+2139INFORMATION SOURCE is Unicode category
Ll— a lowercase letter — while the other eleven prefixes areSo. Keyed on ASCII letters instead, since every group label is ASCII. Its own failure message is whatsurfaced this.
Red ritual
only 0 nav groups9/9. The sixth is the one that matters: a guard reading only the README would pass while the source moved
underneath it.
One mutation first reported
SETUP-FAIL— its anchor text appears twice in README.md (group table andscreenshot-gallery summary), so the harness refused rather than mutating the wrong one. Re-run scoped to the
table row it is red. That refusal is the anchor-appears-exactly-once assertion doing its job.
Also in this diff
app any more.
Verification
dotnet build -c Releaseon all four projects: 0 errors, 0 warnings.MainWindowViewModelTests: 40 / 40.dotnet format --verify-no-changesclean on all four.FileVersion/AssemblyVersion1.109.2.0, CHANGELOG head1.109.2dated
2026-09-17fromdate -u.Visual confirmation of the renamed header and the moved sidebar entry is deferred to the secondary
workstation, per the main-workstation policy.