Skip to content

Give Lite a switch for the startup update check (#2413) - #2415

Merged
erikdarlingdata merged 2 commits into
devfrom
feat/2413-update-check-switch
Aug 21, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
feat/2413-update-check-switch

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Aug 21, 2026 •

Copy link
Copy Markdown
Owner

Fixes #2413.

Five seconds after every launch, MainWindow.CheckForUpdatesOnStartupAsync calls github.com looking for a new version. It is gated on App.CheckForUpdatesOnStartup, which defaults to true and was drawn in no window, so the only supported way to refuse an unsolicited outbound call from a DBA workstation was to hand-edit check_for_updates_on_startup into settings.json. This adds the checkbox, and that is the whole change.

The issue said the key "appears in no sample file", and that turns out to be stronger than it reads: Lite/config/settings.json is the only thing resembling a sample, and it is CopyToOutputDirectory=Never and not in ConfigSeeder's seed list, so it ships nowhere and seeds nothing. It also still carries "theme": "light" where the loader reads color_theme. I left it alone rather than adding a key to a file nothing reads, but it is worth someone deciding whether it should be deleted or made real.

The default is unchanged. Leaving it on with a visible switch beside it is defensible and is what the issue scoped. Whether it should stay on is a product question about how users learn a release exists, and I did not want to answer it as a side effect of drawing a checkbox — say the word and I will file it separately.

Where it lives, and why not in SaveAlertSettings

The checkbox sits directly under "Minimize to system tray", which puts it above AlertsEnabledCheckBox — the master switch where Lite's alert block actually begins. Note that in Lite there is no "General" section: "Minimize to tray" is the first child of the Notifications border, sitting above that gate precisely because it is not an alert setting. The update announcement is itself a tray balloon (AnnounceUpdate → _trayService.ShowNotification), so the two tray-behavior switches reading together at the top of that panel is the placement that made sense.

Persistence deliberately does not go through SaveAlertSettings, even though MinimizeToTray next to it does. Being in the alert block would mean two wrong things: the control would gray out in UpdateAlertControlStates with the "Enable notifications" master switch, claiming a dependency the update check does not have, and RestoreAlertDefaultsButton_Click would stomp it. So it takes the shape the Dashboard Defaults knobs already use — LoadCheckForUpdates seeded from the static in the constructor, SaveCheckForUpdates called from the Save button, and one App.WriteSetting merge into settings.json.

The key name is unchanged, so anyone who already hand-edited it keeps their setting, and WriteSetting parses the existing document and mutates it rather than rewriting, so nothing else in the file moves.

The drift guard

AlertSettingsControlWiringTests.LiteSaveAlertSettings_PersistsEveryStaticItAssigns asserts every App.X = inside SaveAlertSettings has a matching root[...] = App.X in the same body. Putting the assignment in its own method leaves it outside that fact's scope, which is the correct answer rather than a dodge — the guard's subject is the alert block. The sibling EveryLoadedThresholdBox_IsAlsoGatedAndReset matches \bAlert\w*Box\b and explicitly drops anything ending in CheckBox, so it never reaches this control either.

Lite.Tests targets net10.0-windows and cannot run on macOS, so I reimplemented both facts in Python against the patched source — the same brace-matching method-body extraction and the same regexes — plus assertions on the new wiring itself. Both existing facts pass unchanged for Lite and for Darling (22 and 28 loaded threshold boxes, none ungated, none unreset; 56 assigned statics, all persisted), CheckForUpdatesOnStartup is confirmed absent from SaveAlertSettings's body, the control is confirmed absent from both the gate and the reset methods, and the XAML control is confirmed to sit between MinimizeToTrayCheckBox and AlertsEnabledCheckBox. CI is still the arbiter for the real run.

I did not add a new test. The risk this change carries — loaded but never saved — is shared by all five existing single-value Save* methods and none of them has a guard, so a pin for mine alone would be arbitrary; a general "every Load* in the constructor has a Save* in the Save button" check would need an exception list for LoadServerScheduleSummary and a special case for SaveMcpSettingsAsync, which is the hand-maintained-list smell. Happy to take it as a follow-up if you want it.

Darling

No equivalent, confirmed. The Darling Viewer's only Velopack use is in AboutWindow, on demand when the user opens it; there is no startup check and no setting to match wording with. Lite's AboutWindow does the same on-demand check, and I left it unconditional on purpose — opening About is asking, and this setting governs the call the user did not ask for. The tooltip says so, so a user turning the switch off is told where the deliberate check still lives; see the comment below for why it names the sidebar button rather than the "Help > About" the rest of the product promises.

Verification

Built Lite/PerformanceMonitorLite.csproj and Lite.Tests/Lite.Tests.csproj on macOS with EnableWindowsTargeting, both clean. The first attempt failed on MC3000 because the XAML comment contained --, which XML forbids; reworded. global.json was restored and is not in the diff.

erikdarlingdata and others added 2 commits August 21, 2026 13:15
Five seconds after every launch, MainWindow.CheckForUpdatesOnStartupAsync
calls github.com looking for a new version. It is gated on
App.CheckForUpdatesOnStartup, which defaults to true and was drawn in no
window, so the only supported way to refuse an unsolicited outbound call
from a DBA workstation was to hand-edit check_for_updates_on_startup into
settings.json -- a key that appears in no documentation and, as it turns
out, in no sample file either, because Lite/config/settings.json is
CopyToOutputDirectory=Never and is not in ConfigSeeder's seed list, so it
ships nowhere and seeds nothing. The population running this product skews
toward locked-down networks where that call is somebody's business, and
several of those users cannot reach github.com at all, so it has been
failing silently for them with nothing to explain why the update banner
never appears. The discoverability side cuts the same way: a user who WANTS
the notifications had no way to confirm they were on.

The checkbox is the whole change. The default stays true, which is
defensible with a visible switch beside it; whether it should stay true is
a product question about how users learn a release exists, and it belongs
in its own issue rather than being decided here as a side effect.

The key name is unchanged, so anyone who already hand-edited it keeps their
setting, and the writer merges into the parsed document rather than
rewriting it, so nothing else in the file moves.

It is deliberately not wired through SaveAlertSettings, even though it sits
beside Minimize to tray, which is. This is application behavior rather than
alerting, and being in the alert block would mean two wrong things: it
would gray out with the "Enable notifications" master switch, claiming a
dependency the update check does not have, and Restore Defaults would stomp
it. So it takes the shape the Dashboard Defaults knobs already use -- a
LoadCheckForUpdates seeded from the static, a SaveCheckForUpdates called
from the Save button, and one App.WriteSetting merge into settings.json.
That also puts it outside the scope of
LiteSaveAlertSettings_PersistsEveryStaticItAssigns, which is the correct
answer rather than a dodge: that guard's subject is the alert block, and
the Alert*Box gate/reset guard beside it never reaches a checkbox at all.

AboutWindow's check stays unconditional. Opening Help > About is asking,
and the setting governs the call the user did not ask for.

Fixes #2413

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lite has no Help menu. About is a sidebar button beside Settings, and the
"Help > About" the title-bar suffix and the tray balloon both promise has
never been a real path. My tooltip repeated it, which would have shipped a
brand new wrong string in the one place a user turning the check OFF looks
to find out where the deliberate check still lives.

The three pre-existing strings in MainWindow are left alone here and filed
separately rather than folded in -- they are a different lane's one-line
change and this diff is meant to stay a Settings-window diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@erikdarlingdata

Copy link
Copy Markdown
Owner Author

Follow-up commit f351c3e: my tooltip originally said the About check still runs from "Help > About". Lite has no Help menu — MainWindow.xaml has no menu bar at all, and About is a sidebar button directly under Settings (line 327). Reworded to "the sidebar's About button" so the new string is correct.

The three pre-existing strings that make the same claim (MainWindow.xaml.cs 353, 370, and the tray balloon at 391) are left alone here and filed as #2416 rather than folded in, so this stays a Settings-window diff. That one is worth a look on its own: the balloon at 391 exists precisely to stop people re-running Setup.exe, and sending them to look for a menu that isn't there is how they end up doing it anyway.

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Reviewed. Small, well-scoped change — verified against the code:

  • App.CheckForUpdatesOnStartup is read live inside CheckForUpdatesOnStartupAsync after the 5s Task.Delay, so a setting change made in Settings before the delay elapses is honored correctly (no stale-read risk).
  • The new checkbox is correctly excluded from UpdateAlertControlStates (no gating on "Enable notifications") and from RestoreAlertDefaultsButton_Click (Restore Defaults doesn't touch it) — matches the PR description's claim.
  • LoadCheckForUpdates/SaveCheckForUpdates follow the exact same shape as the neighboring LoadTimeDisplayMode/SaveTimeDisplayMode single-value pattern, including reuse of the shared WriteSetting helper (read/merge/write, swallow+log on error) — consistent with existing style in this file.
  • Darling parity: confirmed by grep — Darling's only Velopack/update-check usage is in AboutWindow, on-demand, with no startup check and no corresponding setting. The PR's "no equivalent" claim holds; nothing to add there.
  • Tooltip wording ("the sidebar's About button") matches the actual UI — AboutButton_Click is a sidebar button, not a Help menu item, since Lite has no Help menu.

No correctness, parity, or security issues found. This isn't T-SQL so the collector-query style rules (OPTION(RECOMPILE), etc.) don't apply here.

@erikdarlingdata
erikdarlingdata merged commit 3814f95 into dev Aug 21, 2026
7 checks passed
MisterZeus pushed a commit to MisterZeus/PerformanceMonitor that referenced this pull request Aug 24, 2026
…ingdata#2418)

Lite/config/settings.json shipped nowhere, seeded nothing and was read by
nothing, and it had drifted further than the report said. Four of its
eight keys are read by no code anywhere in the repo -- not just "theme",
which the loader has always spelled color_theme, but also
hot_data_retention_days, archive_retention_days and
start_collection_on_launch, none of which appear in a single .cs file.
Half the template was fiction, and the four keys that were real are read
by three different loaders, only one of which the file's position beside
collection_schedule.json would suggest.

Of the two options, deleting it is the cheaper answer and it is the wrong
one, because it does not address what the file was for. Lite reads 88
keys out of settings.json and four of them have no control in the
Settings window at all: analysis_timeout_seconds, and
check_for_updates_on_startup until erikdarlingdata#2415 lands, plus the two legacy
plaintext webhook URLs erikdarlingdata#1506 migrates. So "the Settings window is the
discovery path" is true for 84 keys and false for the ones a user is most
likely to need help with, and deleting the file leaves the only route to
them being to read App.xaml.cs. Darling already carries the honest
version of this in darling.sample.json, and erikdarlingdata#2389 was decided on exactly
that asymmetry: the sample described the store-vs-file split correctly
while the runtime said nothing about it.

So the file is now a real commented reference to all 88 keys, with each
one's default, its clamp range where the loader clamps on read, and its
legal values where the loader validates. It is renamed to
settings.sample.json and switched to PreserveNewest. Both halves matter.
The old name was the live file's name, sitting among two siblings that
ARE seeded, which is what made it look like a seed rather than a
document; and shipping it beside the exe is the difference between a
reference for people reading GitHub and one for people editing an
install. It stays out of ConfigSeeder's list deliberately: it is
documentation to read, not a file to drop in, and its header says so,
because Lite's own loader rejects comments and rewrites the whole
document on every Save.

The test is the part that makes this worth keeping rather than a
better-looking version of the same trap. It derives the key list by
regex from the shipped App.xaml.cs and Mcp\McpSettings.cs, copied beside
the test binary, and fails in both directions: a key read but not
documented, and a key documented but read by nothing. Each TryGetProperty
is attributed to the nearest *.json literal above it, so a loader added
later for servers.json or collection_schedule.json is excluded by
construction instead of needing an exemption. Both exemption sets are
therefore EMPTY, and the comments on them say what would justify adding
one. A fourth test pins that the extraction still finds the loaders at
all, because the regex is the whole test and a restructured loader would
otherwise make the symmetry checks pass on an empty set.

The dead "theme" key is discussed in a sample COMMENT rather than carried
as a key, so the warning reaches anyone who used the old template without
the sample having to claim a key nothing reads.

Verified by compiling the real test file into a net10.0 console harness
with an xUnit shim -- the shipped test source, not a transcription --
since Lite.Tests is net10.0-windows and cannot run on macOS. It passes on
this branch and fails on dev's file, naming all four dead keys and all 84
undocumented ones. Two single-key mutations confirm it is not merely a
bulk-mismatch detector: adding one undocumented key to the loader is
caught by name, and renaming color_theme to theme in the sample -- this
issue's original defect, reproduced -- fails both directions at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@erikdarlingdata
erikdarlingdata deleted the feat/2413-update-check-switch branch September 12, 2026 20: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

Development

Successfully merging this pull request may close these issues.

1 participant