Handoff — 2026-08-31 — The version in the About box
State
Ticket 0010 is implemented and uncommitted, on main, on top of PR #7 (88a4c38). Nothing was branched: the changes are in the working tree, so git checkout -b feature/about-version at any point before committing carries them along.
internal/version is new. app.Version is gone. The Makefile, scripts/install.sh and a new make version target stamp git describe --tags --dirty, the short commit and a UTC build time through -ldflags -X. About and -version show whatever the build recorded and stay silent about the rest.
Full suite green, green under -race. Quality gate PASS — 0/0/0, complexity 1592. Docs in both languages, four READMEs, and the drawio diagram all in sync.
Also uncommitted, and not mine: .tickets/issues/0004 and 0017 at state: closed, from the previous session.
In flight
Nothing.
Next steps
- Review and commit. A commit message was proposed at the end of the session.
- Close ticket 0010 — the user's call, as always.
- The first real release is untested by definition: nothing has been tagged since
v0.1.0.make versionon a tagged commit should print the bare tag with no-N-g<hash>suffix, and that is the one assertion no test in this repository can make for you.
Open questions / blockers
None.
Watch out for
- Go 1.26 does not say
(devel). A plaingo build .in a checkout reports a pseudo-version —0.1.1-0.20260831165958-88a4c3859bf3+dirty— derived from the last tag.isPseudoVersioncatches it and reportsdevel. If a future Go changes that shape,TestEveryFormOfPseudoVersionIsRecognisedis where it will show. - The separator before a pseudo-version's timestamp is a dot, not a dash, whenever a base tag precedes the commit — the base ends in
-0.or-pre.0.. My first recogniser assumed a dash and silently matched nothing. Three of the four forms in the test caught it. vcs.timeis not a build date. It is the commit's timestamp. Anyone tempted to fill the emptyBuilt:line for unstamped builds from it will make it wrong on every binary.resolveis separate fromCurrenton purpose. A test binary cannot be built with linker stamps, so testing throughCurrentwould leave every interesting case uncovered. Add new cases toresolve, not toCurrent.unknownis load-bearing. It is what-versionprints when nothing named the build, andTestTheInstalledBinaryDoesNotReportAnUnknownVersionuses its absence to prove the installer's ldflags reached the linker. Do not make it a version number.- Everything under
.memory/handoffs/2026-08-31-tool-menus.mdstill applies — in particular the VT-emulator recipe, which verified this feature too, and the rule thatinternal/terminal/tmprender/must be deleted again before the quality gate.
Afterwards — the release script, and what removing a constant costs
The user ran their own ./03-build-releases.sh and it failed. Two defects, both mine:
- It read the version with
awk '{print $NF}', which took the build timestamp once-versiongrew a parenthetical. - The cross-compile loop had no
-ldflagsat all. All five downloadable binaries would have saiddevelwhile the release announcedv0.2.0. The host binary was stamped and correct, so nothing but a hand check would have caught it.
The second is the lesson: removing a compiled-in constant moves a cost from visible to invisible. A stale constant at least travelled into every build; a stamp only reaches the builds that ask for it. Every build path has to be found and stamped — make build, scripts/install.sh, and the five cross-compiles in 03-build-releases.sh.
Fixed by adding a make ldflags target the script reads, so the -X paths exist once. The script's version check is now three plain questions to git and one grep -F, none of which parses the -version sentence. release_test.go covers all of it.
If a sixth build path ever appears, stamp it. TestTheReleaseScriptStampsTheBinariesItShips only guards the one that exists.
And then — the tag script was the real culprit
03 refused again, and this time it was right but misdiagnosed: it said "HEAD carries no tag" when the tag existed and HEAD had merely moved one commit past it.
The cause was three steps upstream. 01-release.tag.sh had no set -e. Run twice, its git tag failed with "already exists", the failure was ignored, and the git push origin "${TAG}" on the next line pushed the old tag. Everything downstream was then working correctly on a lie.
Fixed: set -euo pipefail, a tag-exists check against the local ref and git ls-remote (the state the user was in — local tag deleted, remote tag still there — is invisible locally), nothing-to-commit tolerated, and the tag applied only after a successful push.
02-release.publish.sh still has no set -e and must not naively be given one: its read -r -d '' DATA <<-EOM always exits non-zero by design and would kill the script immediately. It also needs the curl HTTP status checked, since curl exits 0 on a 4xx. Left alone deliberately — it publishes to Codeberg and the user was not asking for it.
The remote is unreachable from this sandbox (SSH, no key), so whether v0.2.0 still exists on Codeberg could not be established. That question was handed back to the user.
Finally — the simplification, and the lesson
The user stopped me: "fais quelque chose de plus simple, tu build comme avant avec le tag de release." They were right.
I had 03 stamp git describe and then verify it agreed with TAG — three gates that each looked reasonable and together blocked a release three times running. git describe answers where is HEAD; a release builder is asking what release is this. Those are different questions, and the gates existed only to reconcile an answer I should never have been asking for.
03 now stamps TAG directly (make ldflags VERSION="${TAG}", make build VERSION="${TAG}") and the gates are gone. Building needs no tag at all; only 02 does. Do not reintroduce those checks — the mismatch they detected cannot occur once the tag is the single source.
01's guards stay: they prevent pushing the wrong tag and block no build.
The lesson, for whoever hits something like this: when you find yourself adding checks to reconcile two sources of truth, delete one of the sources instead.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|