Skip to content

2.12.1 — a website, and Restore All no longer ends early when one step throws - #23

Merged
Anton-Babaskin merged 4 commits into
mainfrom
claude/windows-11-rewrite-j1tyfw
Sep 20, 2026
Merged

Anton-Babaskin merged 4 commits into
mainfrom
claude/windows-11-rewrite-j1tyfw

Conversation

@Anton-Babaskin

Copy link
Copy Markdown
Owner

What this changes

A landing page at anton-babaskin.github.io/SysTuneX,
in English and Russian, with the download button in the first screen. And the rollback path no
longer gives up halfway when one restorer throws.

Why

Restore All could end early and say nothing useful. Restorers run in order, so an exception in
one took every later one with it — and the RollbackReport, which exists specifically to say which
changes came back and which did not, was never returned at all. The user would be told the rollback
failed, with nothing saying which half went through. A machine left mid-rollback is the worst state
this application can put one in.

Nothing is known to throw today; that was checked rather than assumed. UnblockTelemetryHostsAsync
catches and returns an OperationResult, including the Defender refusal the release notes already
document, and RegistryValueComparer.Materialize uses TryParse throughout and returns null rather
than throwing on a journal entry it cannot read. But the restorers reach bcdedit, netsh, the
registry, the task scheduler and PowerShell, and "everything down there reports rather than
throws"
is not a property the rollback can rely on. A throw is now what a refusal already was:
counted against that restorer, named in the user's language, and not the end of the rollback.
Cancellation still propagates — the user left the page, which is not a restorer failing.

The settings page can no longer enforce two of its own rules. Splitting it into a page and five
sections in 2.12.0 turned one loading flag into six independent checks. A handler that does not
consult it fires while the page is filling its controls in and writes the settings file several
times over, applying each value as though it had just been chosen. A section the page never loads
shows its defaults and then writes those over the real settings. Nothing is broken today — all
fifteen handlers do consult the flag, in three different spellings — and that variety is exactly why
a check is worth having.

How it was verified

  • dotnet build SysTuneX.sln -c Release — no warnings
  • dotnet test tests/SysTuneX.Core.Tests — 461 green, up from 456
  • dotnet test tests/SysTuneX.App.Tests — Windows only; CI runs it
  • The website was rendered and looked at, at 1440 and 390 wide, in both languages

Each behavioural claim was checked by breaking the code and confirming the right test failed:

Broken deliberately Caught by
A restorer throws mid-rollback A_restorer_that_throws_does_not_stop_the_ones_after_it — the later restorer still ran
Cancellation treated as a restorer failure Cancellation_is_not_recorded_as_a_failure
One settings handler with its guard removed named with its file
A section dropped from OnEnterAsync named on its own

Two real defects in the website, found by rendering it rather than trusting it:

  1. The whole middle of the page was invisible. Every section was opacity:0 waiting for a
    scroll observer, so anything that stopped the script — a blocked request, an old browser, a
    crawler that does not run JavaScript — left a hero, a footer and nothing between them. For a page
    whose entire job is being found in search, that is the worst possible failure. The reveal is an
    enhancement now: content is visible on its own and only hides once scripting is confirmed.
  2. The page scrolled sideways on a phone — 605px of it in a 390px window, because the hero's
    gradient field is deliberately wider than the viewport. It clips now, with overflow-x:clip
    rather than hidden so the sticky header survives, and the field scales down under 700px.

Also checked: no console errors, no missing local references, and both JSON-LD blocks parse.

Risk and rollback

  • Writes to the registry: no new writes; the catalogue is unchanged.
  • Changes service configuration: no.
  • Reversible from the change journal: yes, and more reliably than before — that is the point of
    the change.
  • The website is static files under docs/. It ships nothing inside the executable and cannot
    affect a running install.

Release

  • release.version bumped to v2.12.1, and CHANGELOG.md has a ## v2.12.1 section
  • All four READMEs link the website
  • Every new user-visible string exists in Strings.resx, Strings.ru.resx and
    Strings.uk.resx — the rollback failure message is translated in all three

Why the number moved. release.version was still v2.12.0, which shipped on 18 September.
Merging on that number would not have failed — the workflow sees the existing tag and quietly
replaces its assets with --clobber. That is the problem: a real behaviour change would have gone
out under release notes that do not mention it, silently changing the binary and its checksum
underneath anyone who had already downloaded and verified it. RELEASING.md names that trade
directly, so the number moves instead.

After merging, GitHub Pages needs turning on once: Settings → Pages → Deploy from a branch →
main / /docs
. Until then the README links point at a 404.


Generated by Claude Code

Splitting the settings page into a page and five sections turned one loading
flag into six independent checks, and neither half of what that flag does is
enforced by anything any more.

A handler that does not consult it fires while the page is filling its controls
in, because assigning a property raises the same notification a click does.
Opening the page then writes the settings file several times over and applies
each value as though it had just been chosen - re-registering the hotkey,
rewriting the schedule, toggling the tray icon. A section the page never loads
is the mirror image: it shows its defaults instead of what is stored, then
writes those defaults over the real settings the moment anything on it is
touched.

Nothing is broken today. All fifteen handlers do consult the flag - in three
different spellings, because the page owns the field and a section reads it
through the interface, and the schedule's three one-line handlers hand the
decision to Apply(). That variety is the reason a check is worth having: it is
held by convention across six files, and the sixth section is where convention
runs out.

So the check follows the flag rather than matching a name, and follows one step
of delegation for the schedule's shape. Verified by making each failure real:
one handler with its guard removed is named with its file, and a section
dropped from the page's OnEnterAsync is named on its own.
Restorers run in order, so an exception in one took every later one with it -
and the report built to make a silently skipped change visible was never
returned at all. The user would be told the rollback failed, with nothing
saying which half of it went through. A machine left mid-rollback is the worst
state this application can put one in.

No restorer is known to throw today; that was checked rather than assumed.
Each one's underlying service answers with an OperationResult, including the
hosts file, which Defender can refuse, and the registry value parser returns
null on anything it cannot read rather than throwing. But they reach bcdedit,
netsh, the registry, the task scheduler and PowerShell, and "everything down
there reports rather than throws" is not a property this class can rely on.

So a throw becomes what a refusal already is: counted against that restorer,
named in its errors in the user's language, and not the end of the rollback.
Cancellation still propagates - the user left the page, which is not a
restorer failing.
A GitHub release page is where you go once you have already decided. This is
the page for someone who has not: what it is, what it costs you, and a
download button in the first screen.

Built as static files under docs/, so GitHub Pages serves it from the same
repository the code lives in - no build step, no framework, no third-party
host, 232 KB for the whole site. It uses the application's own brand colours,
and the hero carries a live frame-time strip because that is what the thing
being downloaded is for.

The SEO is the part that decides whether any of it is seen: per-language
title, description and canonical, hreflang between the two, Open Graph and
Twitter cards on the real screenshot, a sitemap, and JSON-LD for both
SoftwareApplication and FAQPage - the second is what puts the answers
themselves into search results. The version and file size are read from the
GitHub API at load, with the markup's values as the fallback, so the page
cannot go stale behind a release.

Two real defects, found by rendering the page and looking at it rather than
trusting that it was fine:

The whole middle of the page was invisible. Every section was opacity:0
waiting for a scroll observer, so anything that stopped the script - a blocked
request, an old browser, a crawler that does not run JavaScript - left a page
with a hero, a footer and nothing between them. The reveal is an enhancement
now: the content is visible on its own and only hides when scripting is
confirmed present.

The page scrolled sideways on a phone, 605px of it in a 390px window, because
the hero's gradient field is deliberately wider than the viewport. It clips
now, with overflow-x:clip rather than hidden so the sticky header survives,
and the field is scaled down under 700px instead of merely being cut off.

Checked at 1440 and 390 wide in both languages: no overflow, no console
errors, no missing references, and both JSON-LD blocks parse.
The version was still v2.12.0, which shipped on 18 September. Merging on that
number would not have failed - the workflow sees the existing tag and quietly
replaces its assets - and that is the problem. The rollback fix in this branch
is a real behaviour change, and it would have gone out under release notes
that do not mention it, silently changing the binary and its checksum
underneath anyone who had already downloaded and verified it.

RELEASING.md names that trade: a published release that changes underneath
people is worse than a version gap. So the number moves instead.

Patch rather than minor: the application gains a fix and nothing else. The
website is an addition, but it is not shipped inside the executable.
@Anton-Babaskin
Anton-Babaskin merged commit 8dcdac5 into main Sep 20, 2026
6 checks passed
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