feat(lite): hand existing installs over to agliteterm - #193
Merged
Conversation
lite becomes its own product, agliteterm, in its own repository. This is the last agwinterm-lite: its updater points at the agliteterm release feed so existing installs discover the successor through the mechanism they already trust. Without it, agwinterm would simply stop publishing an agwinterm-lite-setup asset and every install would go quiet — the check succeeds, finds no matching asset, and reports nothing. No error, no notice, no route to the new product. - the feed and the ASSET NAME both move: an agwinterm-lite-setup asset sitting in the same release must not be what this build downloads - the prompt says what is happening — a rename, not a routine update — and what it means: the profile comes across, the old build stays installed, --pipe agwinterm-lite still works - the relaunch starts agliteterm. agliteterm installs ALONGSIDE under its own AppId, so the old exe still runs after setup: relaunching GetModuleFileNameW() would leave the user looking at the old build, concluding the update did nothing. Falls back to this exe when the successor is missing — a retryable no-op beats no terminal at all. Also fixes a bug the new check surfaced, present in shipped 0.17.x: the helper relaunched a named instance with -ArgumentList '--pipe', $Instance, and the comment claimed the separate element protected a name with a space. Start-Process joins the list with spaces and quotes nothing, so "my win" arrived as instance "my" — a different pipe AND a different state file, the "my sessions are gone" shape caused by the update itself. The quotes now belong to the value; CommandLineToArgvW strips them on the way back in. lite/test/handover.ps1 (21 checks) drives the built exe against a local feed through the existing AGWINTERM_UPDATE_API / AGWINTERM_VERSION_OVERRIDE seams, then runs the apply helper the app itself wrote, with stand-in executables, to assert which one gets relaunched. Error cases covered: successor missing, feed unreachable (no nagging, nothing downloaded, no state change). Full lite suite green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yeroo
pushed a commit
that referenced
this pull request
Aug 18, 2026
… with Ported from the handover build (#193), where a new check caught it. The helper relaunched a named instance with -ArgumentList '--pipe', $Instance, and the comment claimed the separate array element protected a name containing a space. It does not — Start-Process joins the list with spaces and quotes nothing: 1=[--pipe] 2=[my] 3=[win] as its own element 1=[--pipe] 2=["my win"] 3=[] with the quotes in the value So an instance named "my win" came back as instance "my" — a different pipe AND a different state file, i.e. sessions apparently gone, caused by the update itself. The quotes now belong to the value; the app parses its own command line with CommandLineToArgvW, which strips them. Full lite suite green (34 matrix cells plus log/diagnose/rotation/migration). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yeroo
pushed a commit
that referenced
this pull request
Aug 18, 2026
The handover (#193) and the rename meet here. Resolved so that agliteterm carries none of the handover: it IS the successor, so it updates itself in place from its own feed. Dropped the -Successor parameter and relaunch branch, updSuccessorExe(), the handover banner, and all the rename copy — the balloon, the confirm dialog and the "no agliteterm release yet" message go back to ordinary update text under the new name. Kept the instance-name quoting fix, which is a real bug in both products. Also finishes a rename this branch had left half-done: 19 user-facing strings still said "agwinterm lite" — the title bar, About box, tray tooltip, Show/About menu items, the Properties and Keyboard dialog captions, and the quick/scratch/overlay popup titles. Installing agliteterm would have shown the old name in every window. Internal class names (AgwintermLiteProps) stay as they are; they are not user-visible and renaming registered class atoms buys nothing. README's lite section follows: heading, install asset, log path (%LOCALAPPDATA%\agliteterm\ agliteterm.log), state path and the --diagnose transcript all pointed at the old locations. The agwinterm-lite mentions that remain are deliberate — the handover and the pipe alias. lite/test/handover.ps1 is dropped here: it drives a build that hands over, which this one is not. Full suite green (34 matrix cells plus log/diagnose/rotation/migration). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
yeroo
added a commit
that referenced
this pull request
Aug 18, 2026
…file (#192) * fix(lite): state-save hardening — locking, torn records, .bak rotation, name sanitising Four fixes to the save/restore path, all in the class the restore matrix exists to guard: losing saved sessions quietly. - g_userEmptied is set and read UNDER THE LOCK, with the session list it was judged from. It is written from the control-pipe thread (closeSessionAt) and read on the UI thread, and it gates both the zero-session refusal and the .bak delete — the two decisions that can cost saved sessions. - stateFileSessionCount counts only records that would actually RESTORE (>=4 tabs), not every line beginning "S\t". A record cut mid-write parses to nothing, so counting it would let the save rotate that wreckage into the .bak, over the one generation still holding the sessions, and call a good file "not empty". - Rotation keeps a generation only when the primary is worth keeping, and drops the .bak only when the user emptied the window on purpose — otherwise the next launch reads the fallback and brings back exactly the sessions they just closed. A primary that exists but cannot be read right now (AV scan, transient lock) still counts as worth keeping. - Instance names from the HKCU registry are sanitised before becoming filenames: an older build or a hand edit could hold "..\..\x", which would delete state outside the state directory. Also: saveSessionState logs when it returns on an empty path. That was the last way to get "restore doesn't work" with nothing in the log, on exactly the redirected or policy-locked profile the field reports come from. New matrix cell `inplace-fallback` covers the half of the atomic write nothing else reached: the temp file cannot be CREATED at all (policy-locked profile, DLP/AV agent blocking new files), so the save falls back to writing in place. publish-blocked locks the PRIMARY, which forces a failed publish rather than a failed create, so this path shipped untested. It must still keep both promises: a generation in the .bak, and that .bak dropped on a deliberate empty. lite suite green (30 matrix cells + log/diagnose suites). * feat(agliteterm): rename from agwinterm-lite, adopting the 0.17.x profile Task 3 of docs/plans/2026-08-17-agliteterm-product-split.md. lite becomes agliteterm: its own name, executable, installer identity, state directory, settings key, log and control pipe. A rename alone would have been data loss. Settings live in HKCU\Software\agwinterm-lite, sessions and their .bak generation in %LOCALAPPDATA%\agwinterm-lite, and the install directory doubles as the self-update channel gate — so a user upgrading would have met an empty window, indistinguishable from the failure the restore matrix exists to prevent. - Identity now lives in ONE place: kProduct / kRegKey / kInstKey, plus kLegacyProduct and kLegacyRegKey for migration to read. 30 hardcoded settings-key literals collapsed into kRegKey; state and log roots resolve through a single stateDir(legacy) helper. - migrateFromLegacy() adopts a 0.17.x profile on first run: session files, the .bak, and every value under the old key — enumerated rather than listed by name, so a key this build does not know about travels too. It COPIES, never moves: a user who rolls back must still find agwinterm-lite working. It runs only when agliteterm has no session state of its own, and says which way it went in the log. - The default instance serves BOTH control pipes, so `agwintermctl --pipe agwinterm-lite` and any script already written keep working. Gated on g_isDefaultInstance, not on g_argPipe being empty: `--pipe agliteterm` names the default instance explicitly and has to behave identically. First use of the old name is logged, which is what will tell us the alias is safe to drop. - TERM_PROGRAM becomes "agliteterm" — it names the terminal. The AGWINTERM_* session vars deliberately do NOT change: the agent skill, the status hooks and agwintermctl read them, and that compatibility is the promise the split was made under. Installer AppId is NEW ({E0ACBA4E-...}), so agliteterm installs ALONGSIDE rather than upgrading in place — which is what makes the copy-not-move migration and a rollback possible. New check lite/test/migration.ps1 (9 assertions) seeds a 0.17.x profile — sessions, workspace, .bak, font, palette — and asserts every part arrives, that an existing agliteterm profile wins instead, that the legacy copy is untouched either way, and that the old pipe name still answers. It runs against a THROWAWAY %LOCALAPPDATA%: migration is a once-ever whole-profile adoption keyed on "does agliteterm have state yet", so it cannot be exercised in a profile that already has some. Full suite green after the rename: 34 matrix cells + log/diagnose/rotation/migration. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(agliteterm): quote the instance name the update helper relaunches with Ported from the handover build (#193), where a new check caught it. The helper relaunched a named instance with -ArgumentList '--pipe', $Instance, and the comment claimed the separate array element protected a name containing a space. It does not — Start-Process joins the list with spaces and quotes nothing: 1=[--pipe] 2=[my] 3=[win] as its own element 1=[--pipe] 2=["my win"] 3=[] with the quotes in the value So an instance named "my win" came back as instance "my" — a different pipe AND a different state file, i.e. sessions apparently gone, caused by the update itself. The quotes now belong to the value; the app parses its own command line with CommandLineToArgvW, which strips them. Full lite suite green (34 matrix cells plus log/diagnose/rotation/migration). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: yeroo <pale.cloud9686@fastmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
yeroo
added a commit
that referenced
this pull request
Aug 18, 2026
Merging the rename (#192) into this repository deleted the one thing that has to ship from it: the handover build. #193 said 0.17.4 must reach users BEFORE the rename lands; merging all four PRs together collapsed that order, and the result is worse than it looks. A release from main as it stood publishes agliteterm-setup-*.exe from the AGWINTERM repo and no agwinterm-lite-setup-*.exe at all. Every existing lite install then checks the feed, matches no asset, and goes quiet — no error, no notice, no route to the successor. That is precisely the failure the handover exists to prevent, and it is silent, so nobody would report it. So lite/ here goes back to being the handover build and nothing else: pre-rename identity, updater pointed at the agliteterm feed. agliteterm.iss, the migration check and the agliteterm icon are removed — they belong to the new repository, which has them. This is also the coherent end state: agwinterm publishes the handover once, then drops lite/ entirely. Both installers -> 0.17.4. Also fixes a flake in handover.ps1 that this exposed: Wait-Dialog returned as soon as the dialog WINDOW existed, but a MessageBox creates its window before its static text child, so under load the body read empty and every "what does the prompt say" assertion failed while the title check beside them passed. It now waits for the body to be readable — and still returns a genuinely bodyless dialog rather than timing out silently. Full lite suite green: 34 restore-matrix cells, log-basics/log-restore/log-focus-font/log-rotation/ diagnose, and 21 handover checks. Co-authored-by: yeroo <pale.cloud9686@fastmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Task 4 of the product split. This is the last agwinterm-lite: old name, old AppId, old install directory, with only its updater repointed at the agliteterm feed. It branches off
main, not off the rename (#192) — the build that hands over has to be the identity users already have.Without this, every install goes quiet
The moment agwinterm stops publishing an
agwinterm-lite-setupasset, the existing updater still succeeds: it fetches the release, finds no asset whose name matches, and reports nothing. No error, no notice, no route to the successor. So the handover ships before the rename, not after.agwinterm-lite-setupasset that happened to sit in the same release. The test seeds exactly that decoy, listed first, and asserts it is ignored.--pipe agwinterm-litekeeps working. A user who reads "0.17.4 → 0.17.5" and then finds a differently-named app in their Start menu has been surprised by their terminal.-ExefromGetModuleFileNameW, and because agliteterm installs alongside under its own AppId, the old exe still runs after setup. A naive handover installs agliteterm and then relaunches agwinterm-lite — the user watches an update do nothing and concludes it failed. The helper now takes a-Successor, and falls back to-Exewhen it isn't there: relaunching the old build is a no-op they can retry, while relaunching nothing looks like the update killed their terminal.A bug this check surfaced, already shipping in 0.17.x
The helper relaunched a named instance with:
with a comment claiming the separate array element protects a name containing a space. It does not —
Start-Processjoins the list with spaces and quotes nothing:So an instance named
my wincame back as instancemy— a different pipe and a different state file. That is the "my sessions are gone" shape, caused by the update itself, on exactly the path this PR touches. The quotes are now part of the value; the app parses its own command line withCommandLineToArgvW, which strips them again.Verification
lite/test/handover.ps1— 21 checks — drives the built exe against a local feed through the seams the updater already has (AGWINTERM_UPDATE_APItakes a path,updFetchreads local files,AGWINTERM_VERSION_OVERRIDEsets the current version), then runs the apply helper the app itself wrote to disk, with stand-in executables, so the assertion is about which one gets started rather than about Inno Setup:Error cases are first-class here: feed unreachable must change nothing and nag nobody (asserted at startup, not just on an explicit check), and successor missing must still leave the user with a terminal.
Runs against a throwaway
%LOCALAPPDATA%with a real install layout, because the update path is gated on the exe living in<LOCALAPPDATA>\Programs\agwinterm-lite— and pointing a live updater at a real profile would write payloads into it. Full lite suite green: 34 matrix cells plus log/diagnose/rotation/handover.Two harness notes worth keeping, both of which produced passing-looking wrong results first:
FindWindowExW(NULL, prev, NULL, NULL)enumerates zero windows on Windows 11, so the dialog checks all "passed" as absent until they moved toEnumWindows; and copying only*.exeinto the sandbox install produced a startup error box that the dialog check happily mistook for the update prompt.Sequencing
Step 2 must not happen before an 0.17.4 is in users' hands, or the handover has nobody to hand over to.
The same helper text exists on #192, so the instance-quoting fix is ported there separately.
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com
🤖 Generated with Claude Code