Skip to content

feat(lite): hand existing installs over to agliteterm - #193

Merged
yeroo merged 1 commit into
mainfrom
feat/lite-handover-to-agliteterm
Aug 18, 2026
Merged

feat(lite): hand existing installs over to agliteterm#193
yeroo merged 1 commit into
mainfrom
feat/lite-handover-to-agliteterm

Conversation

@yeroo

@yeroo yeroo commented Aug 18, 2026

Copy link
Copy Markdown
Owner

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-setup asset, 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.

  • The feed and the asset name both move. Retargeting the URL alone would have downloaded a agwinterm-lite-setup asset that happened to sit in the same release. The test seeds exactly that decoy, listed first, and asserts it is ignored.
  • The prompt says what is happening — titled "agwinterm lite is now agliteterm", and explaining what the user actually needs to know: the profile comes across on first run, the old build stays installed so they can go back, and --pipe agwinterm-lite keeps 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.
  • The relaunch starts the successor. This is the part that would have silently broken it: the apply helper takes -Exe from GetModuleFileNameW, 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 -Exe when 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:

Start-Process $Exe -ArgumentList '--pipe', $Instance

with a comment claiming the separate array element protects 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 mya 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 with CommandLineToArgvW, which strips them again.

Verification

lite/test/handover.ps121 checks — drives the built exe against a local feed through the seams the updater already has (AGWINTERM_UPDATE_API takes a path, updFetch reads local files, AGWINTERM_VERSION_OVERRIDE sets 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:

PASS  the successor release is offered      PASS  the installer ran
PASS  the prompt names the rename           PASS  the successor is what gets relaunched
PASS  it says this is not a routine update  PASS  this build is NOT relaunched
PASS  it promises the profile comes across  PASS  a spaced instance name survives
PASS  it says the old build stays           PASS  a missing successor falls back to this build
PASS  it still reports the verification     PASS  and says so in the update log
PASS  the agliteterm asset was downloaded   PASS  an unreachable feed does not stop lite
PASS  the lite-setup decoy was ignored      PASS  and does not nag on startup
PASS  declining leaves lite running         PASS  an explicit check reports the failure
PASS  the apply helper was written          PASS  nothing was downloaded

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 to EnumWindows; and copying only *.exe into the sandbox install produced a startup error box that the dialog check happily mistook for the update prompt.

Sequencing

  1. this PR merges, and 0.17.4 ships as a normal agwinterm release — the lite asset is still built and published exactly as today
  2. feat(agliteterm): rename from agwinterm-lite, adopting the 0.17.x profile #192 (rename) merges, and agliteterm publishes 0.17.5 from its own repo
  3. only then does agwinterm stop publishing a lite asset

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

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
yeroo merged commit 5dcaef5 into main Aug 18, 2026
1 check passed
@yeroo
yeroo deleted the feat/lite-handover-to-agliteterm branch August 18, 2026 11:39
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>
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