Files
improvise/RELEASING.md
T
Edward Langley 926396352f fix: seat the build stamp in #120's redesigned bar, and stop it faking a ticket id
Three things the merge with #120 (ui/shell-polish) actually required, none of
which a clean auto-merge could have told us.

MOVED, not preserved. #120 left <header>'s markup alone but gave every child
explicit styling and a stated reading order -- "what this is, which document,
which view, which mode, what keys, what the peer is doing". A build identifier
is "what this is", so the span moves from after #status to beside <h1>. Keeping
it at the tail was loyalty to my own first draft: it read as metadata trailing
#status, and with #hint's `flex: 1 1 auto` fighting for the slack a 40-char
commit SHA there is the thing that crowds the bar.

STYLED, because unstyled is illegible-by-default in a design system. It takes
`var(--dim)` -- the same token #hint and #status use, so it tracks both themes
by construction rather than by a hand-picked colour -- at 0.72rem, and clamps
to 14ch. Measured under Chromium on the merged tree: a tag renders whole
(scrollWidth == clientWidth, 69px), a 40-char SHA truncates to a 13-character
prefix with an ellipsis, and `header` does not overflow in either case
(scrollWidth == clientWidth == 1280). Contrast of rgb(107,103,95) on
rgb(244,241,234) is ~5.0:1, above AA for small text. Selecting the text still
copies the whole SHA -- text-overflow is a rendering clamp, not a truncation.

NOT AN ID. `improvise-web.tar.gz` puts the token `improvise-web` in the tree,
and AGENTS.md's audit matches `improvise-[a-z0-9]{3,8}` -- so the artifact name
resolved to no ticket and read exactly like a dead id, which AGENTS.md calls
worse than one. `improvise-installer.sh` escapes only by being ten characters,
which is luck rather than a rule, so the exclusion list gains `web` and the
paragraph gains the general case.

RELEASING.md (which arrived in this same merge, from improvise-gx5d) listed
what a tag publishes and was made wrong by omission; it now names the web
capture and says plainly that it is not what Pages is serving.

Verified on the merged tree: 1631 cargo tests over 37 binaries, node --test
47 -> 50 (so #120's three new shell tests survived the merge rather than being
silently dropped), 42 Playwright tests green under Chromium, and the stamp
gate re-proved by injecting the missed substitution again -- a header rewrite
does not carry the old fault injection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
2026-09-21 23:01:08 -07:00

3.9 KiB

Releasing

A checklist, not an explanation. The reasoning is context/code/tooling.md §4; read it if a step surprises you, not before.

Its own file rather than a README.md section because README.md §Installation addresses someone installing improvise and this addresses the one person publishing it — folding them together lengthens the user-facing document for no user benefit.

The one rule

Bump the version and commit it BEFORE you tag. dist matches the tag against the root package's version and refuses when they disagree — and by the time CI tells you, the tag is already published and a published tag is not moved. Every step below exists to put that check before the irreversible one.

Cutting a release

  1. Bump the version. Cargo.toml's [package] version is the one dist announces (dist-workspace.toml says members = ["cargo:."]). Keep the crates under crates/ in step with it; gp-tree sits at its own version deliberately, and the preflight lists such crates as a note rather than an error.

  2. Commit the bump, and let Cargo.lock be part of that commit.

  3. Run the preflight, with the tag you intend to push:

    scripts/release-preflight.sh vX.Y.Z
    

    It refuses a dirty tree, refuses a tag that does not match the version, runs dist plan as a second opinion when dist is on PATH, and prints the tag commands only once it has passed. It cuts nothing itself.

  4. Tag and push, using the commands it printed:

    git tag -a vX.Y.Z -m "vX.Y.Z"
    git push gh vX.Y.Z
    

    Push the tag to gh — .github/workflows/release.yml triggers on a tag push there, matching **[0-9]+.[0-9]+.[0-9]+*. origin and gitea are mirrors and build nothing.

  5. Watch the run. A green release takes about three minutes and publishes tarballs for all four targets with their .sha256 files, sha256.sum, dist-manifest.json, source.tar.gz, improvise-installer.sh and improvise-web.tar.gz — the assembled landing page and browser app, stamped with the tag, which the page then shows in its header (improvise-oujb).

    improvise-web.tar.gz is a self-hostable copy and a rollback target, not what the published site is serving. GitHub Pages tracks main and a tag does not deploy it, so the site and the release are deliberately different code. Two things follow when the web half goes wrong. release.yml is autogenerated, so never hand-edit it: change dist-workspace.toml and re-run dist generate, which should leave the workflow byte-identical. And a pull-request run of the Release workflow skips build-global-artifacts, so a green PR says nothing about the web capture — to exercise it without cutting a tag, run dist build --artifacts=global locally.

    The failure signature is unmistakable once you know it: plan fails in about twenty seconds and the four jobs behind it skip. That is the tag not matching the version — see below. A run that gets past plan is building.

When a tag has already been pushed against the wrong version

Leave it dead and cut the next one. Do not move or re-push the tag: it is already public, and a published tag that changes meaning is worse than one that never built. Bump to the next version, commit, run the preflight, and tag that.

The worked example is v0.1.0-rc3: it was pushed while every crate still said 0.1.0-rc2, dist refused, and the four jobs behind plan skipped. No release was ever built from it.

Ask which tags exist; do not trust a document that tells you. git tag -l, and git ls-remote --tags <remote> for each remote you push to. Which tags are where is a fact with a shelf life — it changed twice while this file was being written — so this file deliberately records no inventory of it. Cleaning up a dead tag is a separate decision and is not a step in cutting a release.