Skip to content

fix: rename Debloater & Ads to Preinstalled Apps, move New App Alerts to Apps - #2341

Merged
laurentiu021 merged 1 commit into
mainfrom
fix/preinstalled-apps-and-app-alerts-group
Sep 17, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
fix/preinstalled-apps-and-app-alerts-group

Conversation

@laurentiu021

Copy link
Copy Markdown
Owner

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:

  • DebloaterService grepped for Registry / SetValue / SubscribedContent → nothing.
  • The only 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 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 / debloater so 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, 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"
. 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 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. Two things corroborate it:

  • The tab's own subtitle already pointed there: "removing a program is Apps → Uninstaller".
  • 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 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:2785 records it). So the rename to "App Installation Alerts" would now be a regression — 23
characters 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. That
test exists for exactly this decision shape (File Lock #1521, Bandwidth Monitor #1514, Notification Blocker
#1522) and builds the tab graph once — three extra MainWindowViewModel constructions once took the CI
integration run from 6 minutes to 22, with leaked winget_*_hang.log artifacts.

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_ListsExactlyItsGroupsTabs re-derives every | Group | Tabs | row from
the 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+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. Its own failure message is what
surfaced this.

Red ritual

Mutation Result
Large Files dropped from the Storage row RED — the real pre-existing drift
New App Alerts left listed under Monitor RED
the table row keeps the old tab name RED
a group row is invented RED — "has a row for Widgets, which is not a nav group"
the table header reworded away RED via the floor
the tab is renamed in source and the README is not RED — the other direction
the group-header pattern matches nothing RED via the floor, only 0 nav groups
New App Alerts moves back to Monitor RED
it stays in Apps but loses the adjacency to Uninstaller RED

9/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 and
screenshot-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

  • Two stale screenshot alt texts: "App Installation Alerts" and "Debloater & Ads" — neither name exists in the
    app any more.
  • The App Alerts screenshot moved to the Apps gallery group, so the gallery matches the sidebar.
  • Apps' sidebar subtitle: "Install, update, remove and catch new installs".
  • The affected-tab dropdown in all three issue templates, entry moved next to Uninstaller.

Verification

  • dotnet build -c Release on all four projects: 0 errors, 0 warnings.
  • Unit suite twice: 5773 / 5773 (5772 before, +1 guard).
  • MainWindowViewModelTests: 40 / 40.
  • dotnet format --verify-no-changes clean on all four.
  • Version gate replicated locally: FileVersion / AssemblyVersion 1.109.2.0, CHANGELOG head 1.109.2
    dated 2026-09-17 from date -u.

Visual confirmation of the renamed header and the moved sidebar entry is deferred to the secondary
workstation, per the main-workstation policy.

… 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
@laurentiu021

Copy link
Copy Markdown
Owner Author

Fixup after the first CI run — and the guard it needed

The UI job caught one more call site the rename had to reach: 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. It surfaced only in the continue-on-error UI job — which reports success at the check level while the log says failed: 1.

Row fixed. But the interesting part is why nothing blocking caught it.

Two guards sit right next to this and neither could see it

Guard What it checks
EverySidebarTab_HasASmokeRow each tab has a row
EveryUiTextAssertion_QuotesCopyTheAppActuallyShips UI waits quote real copy

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 below confirms.

EverySmokeRowHeader_IsTextItsOwnTabRenders

Population-driven rather than heuristic: 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. That is the 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, 6 mutations, all red

The old guard was run alongside two of them, to prove this is not a duplicate:

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 format clean.
  • Unit suite twice: 5774 / 5774 (5773 before, +1 guard).

@laurentiu021
laurentiu021 force-pushed the fix/preinstalled-apps-and-app-alerts-group branch from b05ee7e to cabc126 Compare September 17, 2026 12:20
@laurentiu021
laurentiu021 merged commit d03b65c into main Sep 17, 2026
6 checks passed
@laurentiu021
laurentiu021 deleted the fix/preinstalled-apps-and-app-alerts-group branch September 17, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant