feat: self-update banner and restart - #42
Merged
Merged
Conversation
keeptui watches its own releases and offers to update from inside the TUI. - `version.SelfLatest` — a release-only pass to `stanlyzoolo/keeptui` with its own `ReleaseCheckedAt` freshness stamp, so it never marks a tracked keeptui's repo card fresh-but-blank. A 404 is recorded in the new `ReleaseMissing` flag instead of wiping the release tuple the card shares; all three release writers maintain the flag. - A non-modal banner in the hints bar: `keeptui v0.5.0 available — [U] update [X] dismiss`. `[X]` collapses it into a compact cell next to the API gauge. Session-only, never written to disk. - The update runs through the existing detect/confirm/stream pipeline. The self log owns panel `[3]` regardless of the selection — keeptui may not be tracked — via the single `showsUpdateLog()` predicate at all six former call sites. - After a successful update `[U] restart` re-execs the new binary (`syscall.Exec` on unix, an honest hint on Windows). Path resolution follows argv0 semantics and prefers `LookPath` for a bare name, so Linux's symlink-resolved `/proc/self/exe` cannot loop the restart into the pre-upgrade binary. The feature is off entirely on working-copy builds: `selfCheckEnabled` rejects `dev`, Go pseudo-versions and `+dirty`, so `go build .` neither fetches nor shows anything. Also: `[u]` on a tracked keeptui row is now the same self-update as `[U]`, both update keys report when they are blocked instead of doing nothing, a late detect result no longer opens a confirm under an open editor, and CI cross-compiles for windows/darwin so the build-tagged files are checked.
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.
keeptui watches its own releases and offers to update from inside the TUI. Plan:
docs/plans/completed/20260725-self-update-restart.md.What it does
stanlyzoolo/keeptui, cached 24h. Works out of the box for an untracked keeptui, which is the main case.keeptui v0.5.0 available — [U] update [X] dismissreplaces the hints.[X]collapses it into a compact cell next to the API gauge, so the update stays reachable all session. Dismiss is session-only.updater.Detect→modeConfirmUpdate→ streaming log. The self log owns panel[3]regardless of the selection (keeptui may not be tracked).[U] restartre-execs the new binary viasyscall.Exec(unix). Windows degrades to a hint.Off entirely on working-copy builds:
selfCheckEnabledrejectsdev, Go pseudo-versions and+dirty, sogo build .neither fetches nor renders anything.Design notes worth reviewing
ReleaseMissingis the part with the widest blast radius. The release tuple (Latest/Body/HtmlUrl/PublishedAt) belongs to the shared repo card, so a release-only pass must not wipe it on a 404 — one drafted release would strip a tracked keeptui'slatest:line, date,↑, changelog body and release URL, withCheckedAtdeliberately not advanced so nothing could recover. The 404 is recorded in a newCacheEntry.ReleaseMissingflag instead, maintained by all three release writers (SelfLatest,getRepoData,getChangelog) through oneapplyReleaseOutcomehelper.getChangelog's write is load-bearing: with a freshCheckedAtand an emptyBodyit is the only pass that reaches/releases/latest.Behavior changes for non-keeptui tools (three, all in the update pipeline):
[u]while an update runs now reportsanother update is runninginstead of doing nothing.selectedMeta()— the target comes fromupdateTarget, so a selection that moved during detection cannot retarget the plan.[u]on a tracked keeptui row is now a self-update, same as[U]. The plan originally kept them separate; that left a banner advertising an update the card already showed as installed, with[U] restartreachable only by running the whole update again.Out of the plan's stated scope: the
internal/versioncache changes above, a testability split ofrunTUIintonewRootModel/restartIfRequested(mutation showed both wiring lines could be deleted with the suite still green), a CI cross-compile step for windows/darwin, and a test-fixture consolidation ininternal/versionthat also fixed a pre-existing leak — one httptest server was not resetting the token, so it sent a realGITHUB_TOKENto a local server.internal/updater,internal/launcher,internal/procand themeta.yamlschema are untouched.Testing
go build,go vet,go test -race ./...(10/10 packages),golangci-lint run(0 issues), cross build+vet forGOOS=windows/GOOS=linux.Not covered by tests, needs a manual pass before release:
syscall.Execis never called by any test. Specifically Linux withgo install, where/proc/self/exeis symlink-resolved and the argv0/LookPathordering is what stops a "restart → same banner" loop.restart_windows.gohas never executed anywhere; it is only cross-compiled.Known caveat
updater.Detectresolves the target withLookPath(name)while the restart resolves argv0-first. Run./keeptuiwith a different keeptui on PATH and the update hits the PATH one while the restart returns the local binary. Documented in README/CLAUDE.md rather than fixed — the fix belongs ininternal/updater, well outside this branch.