turbo-editors/turbo-gopublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-go.git
git clone ssh://git@rickub.com/turbo-editors/turbo-go.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

history.md · 517 lines · 110.5 KBmarkdown Blame HistoryRaw
📦 Turbo Go 3d7798b k33g 9h ago1# History
2
3*Append only. One dated entry per session. Never rewrite or delete an entry, including your own from an earlier turn.*
4
5## 2026-08-30 — Turbo C-style Go editor, built from an empty repository
6
7- **Goal**: "the same editor as Turbo C but written in Go and made for Go programming — so Go syntax colouring, and LSP support for completion while editing. It must also be possible to apply themes to the editor." Delivered in full, in one session, at the user's request to go all the way through without stopping at the approval checkpoints.
8
9- **Changes**: the whole project. Eight packages under `internal/` (`buffer`, `theme`, `syntax`, `ui`, `editor`, `lsp`, `app`) plus `main.go`, a `Makefile`, three embedded themes, 13 documentation pages in each of two languages, a drawio dependency diagram, per-package `README.md`s, and this `.memory/`.
10
11- **Decisions**:
12 - **tcell over tview and bubbletea**, with a hand-written widget framework (~1500 lines). tview has no text editor and the wrong look; bubbletea's whole-view re-render suits forms rather than a full-screen editor with stacked windows and an exact cursor cell. Chosen by the user from three options.
13 - **TOML for themes**, chosen by the user over JSON and YAML. Costs one dependency (`BurntSushi/toml`), buys comments in a file people edit by hand.
14 - **gopls detected, never bundled**, chosen by the user. `PATH`, then `GOBIN`, then `GOPATH/bin`; absence is reported on the status bar with the command that fixes it.
15 - **LSP client written by hand** rather than taking `go.lsp.dev/jsonrpc2` — about 300 lines, and it keeps the total dependency count at two.
16 - **Colouring by `go/scanner`** rather than a hand-written lexer or a highlighting library: exactly as right as the compiler, and no table to update when the language changes. The cost — only Go is coloured — was accepted deliberately.
17 - **Steps 6 and 7 of the plan were swapped**: `lsp` was built before `app`, because `app` depends on it and the original order had it the other way round.
18 - **`kits/**` excluded from qlty**, with the user's explicit agreement and a comment recording exactly what it hides. The two findings are real defects in the quality skill's own `quality_report.py` and belong to the kit, not here.
19
20- **Bugs found and fixed while building**:
21 - `MoveWordLeft` stepped left before scanning, so it skipped a word when the cursor sat just after one.
22 - `theme.LoadFile` restarted the inheritance depth at zero, so a two-theme `inherits` loop blew the stack instead of being reported.
23 - `closeBoxLabel = "[■]"` was measured with `len()` — 5 bytes for 3 columns — so clicks two cells past the close box closed the window.
24 - `InputLine` swallowed `Alt`-letter, which stopped a dialog's buttons from ever seeing their own shortcuts.
25 - The status bar drew its hints and its right-aligned text over each other.
26 - `buffer.New()` claimed an empty buffer ends with a newline, so every new file gained a stray `"\n"`.
27 - **`ui.Dialog` grabbed the arrow keys for its focus ring before the focused control saw them**, which made the theme picker, the window list and the file browser unusable from the keyboard. Found while writing the tutorial, which is the second time this session that documenting something exposed a defect in it.
28
29- **Tests**: written alongside each step, never after. Every package green. Widgets and the editor are driven through `tcell.SimulationScreen`; the LSP client is driven against a fake server in the same process over `net.Pipe`, and — when gopls is installed — against the real one, which skips itself otherwise. Command: `make test`.
30
31- **Quality**: gate **PASS**. Four runs: smells 12 → 4 → 2 → 0, complexity 878 → 802, lint errors and warnings 0 throughout. The refactoring was real — a table-driven token classifier, `actions.go` split into three cohesive files, `buffer.IsWordRune` shared instead of duplicated in `editor`.
32
33- **Docs**: `docs/` with a language selector, and `en/` + `fr/` each holding one tutorial, five how-to guides, four reference pages and three explanations. Every link checked to resolve and to stay inside its own language. Package dependency diagram at `docs/diagrams/packages.drawio`, verified edge by edge against `go list -deps`. Root `README.md` rewritten from its one-line placeholder.
34
35- **Not done**: never run in a real terminal; only Linux/arm64 exercised; no CI; diagnostics stored but only surfaced on the status bar.
36
37## 2026-08-30 (later) — two defects found by running it for real
38
39- **Goal**: the user ran the editor in an actual terminal and reported two things: the cursor is invisible under `turbo-dark`, and completion produces nothing after typing `fmt.`.
40
41- **Changes**:
42 - `internal/app/app.go``StartLanguageServer` now posts a `languageReady` interrupt when the server finishes starting, and `announceOpenDocuments` re-sends `didOpen` for every window already open, from the main goroutine.
43 - `internal/app/language.go``Language` holds a `*lsp.Client` alongside the `*lsp.Server`, so a client can be attached without a process. That is what makes the editor's side of the conversation testable.
44 - `internal/app/complete.go` — the popup is anchored with `View.CursorScreenPosition()` instead of an anchor of its own that forgot the gutter and the horizontal scroll.
45 - `internal/editor/view.go` — the cursor cell is painted in a new `editor.cursor` theme key; `View` embeds `ui.FocusBox`.
46 - `internal/ui/window.go``SetActive` propagates the focus to a content widget that can hold it.
47 - `internal/ui/dialog.go``HandleKey` restated as an ordered list of handlers, which the quality gate required after the arrow-ring fix added a branch.
48 - Three theme files, the theme reference and the theme how-to, in both languages.
49
50- **Decisions**:
51 - **The cursor is painted by the editor, not left to the terminal.** A terminal draws its cursor in the user's colour, which owes nothing to the theme. The colours are a distinct pair rather than a reversal, so a terminal that draws its cursor by inverting the cell cannot invert it back into invisibility.
52 - **Announce, rather than reorder `main`.** Starting gopls before opening the files would have fixed this one case and left the general one — a server that becomes ready at any later moment — still broken.
53
54- **Root cause of the completion failure**: `main` opens the files named on the command line and *then* starts the language server, so `DidOpen` at that moment reached nothing. gopls was never told the document was open; the `didChange` sent on every keystroke afterwards therefore referred to a document it did not have, and it answered completions from the stale on-disk text instead. Typing `fmt.` produced "No completions here".
55
56- **Tests**: an end-to-end test in `internal/app` replays the command's exact start-up order against a real gopls and completes text that exists **only in the buffer**. The first version of this test pre-wrote `strings.` into the fixture and passed with the fix removed — gopls answered it from disk. That version proved nothing; it was rewritten to type the text, and then it failed with the fix removed and passed with it, which is what a regression test is for. Also added: a fake language server in `internal/app` for testing what the editor says; focus-propagation tests; cursor-contrast tests across every theme.
57
58- **Quality**: gate back to **PASS** after restating `Dialog.HandleKey`. Run 6: 0 errors, 0 warnings, 0 smells.
59
60- **Lesson worth keeping**: both defects were invisible to a suite that never leaves memory. The simulation screen exercises the drawing code but not the terminal; a fixture on disk exercises the protocol but not what the editor actually said. Neither gap was obvious until someone ran the program.
61
62## 2026-08-30 (third) — the completion fix was still droppable, and an empty list said nothing
63
64- **Goal**: the user reported that the top menu had stopped working and that completion still produced nothing. They run the editor inside tmux, screen or an IDE terminal.
65
66- **The menu**: could not be reproduced. The real binary was driven under a pty (`script`, `TERM=xterm-256color`) and F10, Alt-F, Alt-E, a mouse click on the bar, and Down+Enter on an item all worked, with and without gopls. `internal/ui/menu.go` had not been touched since the previous session. The user confirmed it works again — most likely a stale binary.
67
68- **Changes**:
69 - `internal/app/app.go` — the re-announcement no longer rides on a posted event. `announceOpenDocuments` is checked on every turn of the event loop and is idempotent. `tcell.PostEvent` **drops** events when its queue is full, and start-up is precisely when gopls floods it with diagnostics, so the previous session's fix could silently fail to arrive. The `languageReady` event type is gone.
70 - `internal/app/complete.go` — an empty list now names the reason: `No completions — this file does not compile: <first error>`.
71 - `internal/app/actions_view.go``Run ▸ Language server status` reports the server path, the workspace root, the current file, whether the server has been told about it, and the first error it reported.
72 - `internal/app/language.go` — records the server path, the root, and which documents have been announced; `Report()` and `Knows()` expose it.
73 - Both `enable-completion` how-to guides gained a section on the two ways completion looks broken when it is not.
74
75- **Root cause of the user's remaining symptom, found and reproduced**: they had created `hello.go` in the turbo-go repository root — the working tree is mounted from their machine, so the file was visible here. It declares `package main` and `func main()` alongside the project's own `main.go`. The package therefore does not compile, and a probe against real gopls in an identical two-`main` module returned **0 completions with no error at all**. That is not an editor defect; the editor's failing was to shrug at it, which is what the new message fixes.
76
77- **A file of the user's was deleted earlier by mistake.** An empty `hello.go` appeared at the repository root and was removed as a stray artefact of local experimentation. It was almost certainly the user's first attempt at the same reproduction. It was empty, so nothing was lost, but the working tree is shared and files appearing in it are not to be assumed to be one's own.
78
79- **Tests**: the announcement is now tested without any event being delivered, and for being made exactly once however often it is checked. Added tests for the empty-completion message and for the status report naming the file, the reason, and an untitled window. The end-to-end gopls test still fails with the fix removed and passes with it.
80
81- **Quality**: gate PASS, run 7. 0 errors, 0 warnings, 0 smells.
82
83## 2026-08-30 (fourth) — an installer, so the editor can be used on real projects
84
85- **Goal**: the user asked for a script that builds and installs the editor onto their PATH, so they can try it on an actual Go project.
86
87- **Changes**: `scripts/install.sh`, `make install` / `make uninstall`, `install_test.go`, and the install how-to and CLI reference in both languages. The README's "getting started" now leads with `make install`.
88
89- **Decisions**:
90 - **Default destination is `$GOBIN`, then `$GOPATH/bin`** — where `go install` would put it, and therefore the directory a Go developer most likely already has on PATH. `--prefix` overrides.
91 - **Build to a temporary file, then copy.** A failed build must never replace a working installation; there is a test for exactly that, because a stray `.go` file in package main is precisely what the user's own scratch file did to this repository an hour earlier.
92 - **The required Go version is read from `go.mod`**, not hardcoded, so the check cannot drift from the build.
93 - **It reports rather than assumes**: the Go version, where the binary went, whether that directory is on PATH (with the exact line to add, for the user's own shell), and whether gopls is installed. `--with-gopls` installs the server too.
94
95- **Tests**: ten, in `install_test.go`. They run the script for real into a temporary prefix and check the binary works, that the PATH warning appears and says how to fix it, that `--uninstall` removes what was installed and does not fail when there is nothing, that bad options are refused, that it runs from any working directory, and that a deliberately broken build leaves the previous installation byte-for-byte untouched. They skip under `-short`, on Windows, and without bash.
96
97- **Note**: the user removed their own `hello.go` from the repository root, so `go build ./...` compiles again and the root package's tests run.
98
99## 2026-08-30 (fifth) — windows follow the terminal
100
101- **Goal**: the user asked that the main window be resizable when the terminal changes size.
102
103- **Root cause**: `Desktop.SetBounds` only ever called `ClampInto`, which **moves** a window and never resizes it. A window that filled an 80-column terminal kept its 78 columns in a 120-column one.
104
105- **Changes**:
106 - `internal/ui/window.go` — a Turbo Vision-style `Grow` mode. `GrowBoth` is the default for a document window: the right and bottom edges move by the same delta the desktop's did, the top-left corner stays put, and the result is then held to the desktop's own size.
107 - `internal/ui/desktop.go``SetBounds` compares against its previous rectangle and takes the windows with it. It is inert when nothing changed, which matters because it runs on every turn of the event loop.
108 - `internal/ui/dialog.go``MoveTo` and `CenterIn`, which take a dialog's controls with it. Controls are placed in screen coordinates at build time, so moving the frame alone would have left them behind.
109 - `internal/app/app.go` — a resize re-centres every open dialog and dismisses the completion popup, which is anchored to a cursor that has just moved.
110
111- **Decisions**: grow modes rather than **proportional scaling**. Scaling moves windows the user placed deliberately, and rounding makes it lossy — shrink then grow and nothing is where it was.
112
113- **Tests**: eleven new ones. Growing, shrinking, a cascaded window keeping its offset, a `GrowNone` window being moved but not resized, the desktop's size winning over the minimum on a terminal too small to hold one, the minimum being respected when the desktop can hold it, and `SetBounds` being inert when nothing changed. At the app level: the window keeps its margins from the terminal's far edges, the editor shows more lines afterwards, dialogs are re-centred with their controls, and the popup is dismissed.
114
115- **Verified live in a real pty.** The editor was run under `script`, its pty resized with `stty` — which sends a genuine SIGWINCH — and the window's bottom border measured **76 → 96 → 46** cells as the terminal went 80 → 100 → 50. This is the first time the project has been checked against an actual terminal resize.
116
117- **Quality**: gate PASS.
118
119## 2026-08-30 (sixth) — the cursor, properly this time
120
121- **Goal**: the user reported that the cursor is still invisible under `turbo-dark`, after the earlier fix.
122
123- **Why the earlier fix was not enough**: it painted the cursor's cell in the theme's colours, but a terminal draws its own cursor **over** the cell, in whatever colour the user configured for some other palette. On a dark theme that is very often a dark block covering the amber cell underneath. Painting can never win against something drawn on top of it.
124
125- **Changes**:
126 - `internal/app/app.go``applyCursorStyle` calls `SetCursorStyle(tcell.CursorStyleSteadyBlock, cursorColor(theme))` whenever the theme changes. tcell turns that into `ESC[2 q` and `ESC]12;<colour>`, so the theme now decides the terminal's own cursor. The painted cell stays as a fallback for terminals that support neither.
127 - `internal/theme/themes/turbo-dark.toml` — the current line was `#262626` against a `#1c1c1c` page: ten channel values, which is no highlight at all. Now `#303030`.
128 - `internal/theme/themes/borland-light.toml` — same defect, `#f4f4f4` on `#ffffff`, eleven values. Now `#e8e8e8`.
129
130- **Verified on the wire**: run under a pty, the editor emits `ESC[2 q` and `ESC]12;#ffd787` for turbo-dark, `#00ffff` for turbo-classic and `#af5f00` for borland-light. tcell emits `ESC]112` and `ESC[0 q` on exit, so the user's terminal is left as it was found.
131
132- **Tests**: contrast is now **measured**, not assumed. `channelDistance` gives the largest per-channel difference between two colours, and every theme must keep the cursor at least 64 from its line and the line at least 16 from the page. Confirmed non-vacuous by restoring the old `#262626` and watching the test fail with "the current line is 10 from the page, want at least 16". Also: `cursorColor` returns the cursor style's background, and changing the theme changes it.
133
134- **Lesson**: "it looks fine to me" is not a measurement. Two of the three shipped themes had a current-line highlight nobody could see, and no test could tell.
135
136## 2026-08-31 — the release build staged nothing, then built for five platforms
137
138- **Goal**: first, that `03-build-releases.sh` copy `./bin/turbo-go` into the release directory correctly; then that it cross-compile for darwin/arm64, linux/amd64, linux/arm64 and both Windows architectures.
139
140- **Root cause of the original failure**: three faults stacked. `VERSION` was never defined anywhere — `release.env` sets only `TAG`, `ABOUT`, `OWNER`, `REPO` — so `TURBO` evaluated to `turbo-go-`. `make build` writes `bin/turbo-go`, not a file of that name in the root, so the `mv` had nothing to move and `set -e` killed the script. And `mv` would have taken the binary out of `bin/`, breaking `make run` and any local install.
141
142- **Changes**: a `PLATFORMS` array drives the builds, the checksum file and the README table, so adding a target is one line. `VERSION` is derived from `TAG` (`${TAG#v}`). Assets are named `turbo-go-<version>-<goos>-<goarch>`, with `.exe` on Windows — with five downloads the platform has to be in the name. Cross-compiles run with `CGO_ENABLED=0`, which is safe because tcell and toml are both pure Go, and `-trimpath`, which keeps the build machine's paths out of a binary that goes to strangers. The host build still runs first, as the fastest way to find a compile error and the only binary this machine can run to check the version against `TAG` — which is what `release.env`'s own comment always said the script should do. `SHA256SUMS` covers every platform in one file, since that is what `sha256sum -c` reads.
143
144- **Verified by running it**: five binaries staged; `go version -m` reports the right `GOOS`/`GOARCH` for each and the magic bytes are Mach-O, ELF and PE as they should be; `sha256sum -c` passes on all five; the host binary runs; no `/home/agent` path survives `-trimpath`; `TAG=v9.9.9` is refused against a binary reporting `0.1.0`; and adding a sixth platform propagated to the build, the README table and the checksums before being reverted.
145
146- **Reported, not changed** (out of the scope asked for, twice): `04-release.upload-binaries.sh` globs `*.vsix`, left over from the VS Code extension template these scripts came from. It uploads `SHA256SUMS` and none of the five binaries the checksums are *for*. Replacing `"${RELEASES_DIR}"/*.vsix` with `"${RELEASES_DIR}"/turbo-go-*` would fix it. The stale `.vsix` and `package.json` comments in `04` and `release.env` are from the same template.
147
148## 2026-08-31 (later) — the upload script, adapted to a Go release
149
150- **Goal**: the user asked that `04-release.upload-binaries.sh` be fixed and adapted, after two rounds of flagging that it uploaded nothing.
151
152- **Root cause**: the loop globbed `"${RELEASES_DIR}"/*.vsix`, left over from the VS Code extension template these scripts came from. It attached `SHA256SUMS` and none of the five binaries the checksums were *for*.
153
154- **A supposition that was wrong, and checked before acting**: the upload used `--data-binary` with `application/octet-stream`, and Gitea's documented parameter for this endpoint is a `multipart/form-data` file field. Reading Codeberg's own swagger showed the endpoint `consumes` **both**, so the existing mechanism was correct and was left alone. Nearly rewrote something that was not broken.
155
156- **Changes**: the glob now picks up `turbo-go-*` and then `SHA256SUMS`, in that order, so an interrupted run never leaves checksums on a release with nothing to check. `jq` reads the release id and the attached assets instead of `grep -o '"id":[0-9]*' | head -1`. HTTP statuses are told apart: 401/403 says the token was refused, 404 says to run `02` — previously a bad token was reported as a missing release, which sends you to fix the wrong thing. Assets already attached are listed up front and replaced only with permission, so a run that failed halfway can simply be repeated. And `--dry-run` resolves the release and prints exactly what would be sent, without sending it.
157
158- **A bug the dry run found in itself**: `read -p` returns non-zero at end of input, so under `set -e` the script died at the README prompt when stdin was not a terminal. Both prompts now go through a `confirm` helper that answers no by itself when there is no terminal — a publishing script must not take silence for consent, nor die on the end of its input.
159
160- **Verified against the live API**, read-only: the release id resolves (11905813), the six assets are listed in order, an unknown option is refused, missing artefacts are reported, a deliberately invalid token gives "Codeberg refused the token (HTTP 401)", and a tag with no release gives the 404 message. `release.env` and `turbo-go.token.env` were restored after each. **Not verified**: the POST and DELETE themselves, because running them would publish artefacts on the user's behalf. That is theirs to run.
161
162## 2026-08-31 — Terminal windows (ticket 0007)
163
164- **Goal**: "je voudrais avoir la possibilité de créer des fenêtres qui soient des terminaux (pour lancer des commandes shell)", then "vas au bout du bout, puis crée le ticket pour la version pour Windows". Options chosen up front: a **real terminal (pty + VT emulator)** rather than captured command output; **several terminals**, each starting in the active file's directory; **Linux and macOS first**, with Windows showing a clear "not supported yet" message.
165- **Changes**: new `internal/terminal` package (16 files) — `Session` over `/dev/ptmx` with build-tagged `pty_linux.go` / `pty_darwin.go` / `pty_other.go`, a `Parser` state machine over CSI/OSC/ESC, a `Screen` with scrollback and an alternate-screen aside, `Encode` for keys, and a `View` widget. Wired into `app` through a new `terminals.go`, plus edits to `app.go` (the `terminals` map, `keyLayers()`, title refresh, `F8`), `menus.go` (`Window ▸ New terminal`) and `actions_file.go` (`editorViewOf`, `closeWindow`, `Quit`). Two new theme keys, `terminal.text` and `terminal.cursor`, in `keys.go` and all three theme files.
166- **Decisions**: a **real pseudo-terminal**, because a captured pipe loses colour, paging, `isatty` and `Ctrl-C`, and nothing interactive works at all — that is most of what a terminal is for. The **emulator is hand-written and partial** rather than a third dependency: what a shell, `go test`, `git`, `less`, `htop` and `vim` need is a bounded list, about six hundred lines, and a general-purpose library brings character sets, mouse protocols and sixel that would all need keeping alive. The **key routing was inverted for a focused terminal** — it outranks the editor's global shortcuts, keeping only the function keys, `Alt-X` and `Alt-0``Alt-9` — because a shell and an editor both want `Ctrl-C`, `Ctrl-W` and `Ctrl-F`, and without the reserved handful there is no way out of a full-screen program. Rejected: reserving fewer keys (no escape from `vim`), reserving more (readline becomes unusable), and asking for confirmation when closing a terminal (it holds a process, not unsaved work).
167- **Tests**: 12 new tests in `internal/app/terminals_test.go` plus the package's own suites; `internal/terminal` at 95.7 %, `internal/app` up from ~71 % to 82.8 %. Run with `make test`, or `go test ./... -race`. Two test defects were found and fixed rather than accepted: a test that matched the pty's own echo of the command line instead of the shell's output, and a flaky drawing test racing the shell's startup — which, once made deterministic, turned out to have been sampling the cursor cell rather than the text.
168- **Quality**: PASS. Four `return-statements` smells appeared (`handleKey`, `applySGR`, `applyAttribute`, `extendedColor`) and were refactored away by turning three switch-tables into actual tables and the routing chain into a list of layers. 0 errors, 0 warnings, 0 smells, complexity 1023.
169- **Docs**: three new pages per language — `how-to/use-a-terminal.md`, `reference/terminal.md`, `explanation/terminal-windows.md` — and updates to both `README.md` indexes, `reference/keyboard.md`, `reference/menus.md`, `reference/themes.md`, `how-to/write-a-theme.md` and `explanation/architecture.md`. New `internal/terminal/README.md`; updates to `internal/app/README.md` (whose file table was also stale) and `internal/theme/README.md`. `docs/diagrams/packages.drawio` gained the `terminal` and `golang.org/x/sys/unix` nodes and five edges, and was then checked against `go list` programmatically — it matches edge for edge.
170- **Also**: created ticket `0015` for the Windows/ConPTY port. Ticket `0007` was left `open` — closing it is the user's call. Nothing was committed.
171
172## 2026-08-31 — Project settings, autosave and TOML colouring (ticket 0002)
173
174- **Goal**: "enregistrer les paramètres du projet dans un dossier `.turbo-go` dans un fichier `settings.toml`" — the theme, a statement that files are saved automatically, the autosave implementation itself, TOML syntax colouring, loading the file if it exists, and a menu entry that creates a pre-initialised one. Then "va au bout du bout". Options chosen up front: autosave **after a pause in typing**; the theme written back **only when the file already exists**; the file looked for in **the working directory only**, with `-theme` winning over it; and **nothing beyond theme and autosave** in the file for now.
175- **Changes**: new `internal/settings` package (`settings.go`, `create.go`, `rewrite.go`, `README.md`, tests). `internal/syntax` gained a `Language` dimension — `Highlight(lang, src)`, `LanguageOf(path)`, `NewCache(Language)`, `SetLanguage` replacing `SupportsPath`/`SetEnabled` — plus `toml.go`, a hand-written TOML scanner. `internal/app` gained `autosave.go` and `project.go`, an `autosave` and `settingsPath` field, an injectable clock, `saveDueDocuments` in the `Run` loop, `UseSettings`, and two Options menu items. `main.go` reads the file and resolves the theme. `internal/editor` updated for the new `syntax` API.
176- **Decisions**: the settings directory is **not searched for upwards** — a module has a real boundary, "the project" does not, and a walk makes a file three directories up change your colours silently. `.turbo-go/` is **created only from the menu**, never as a side effect of picking a theme, because that would put a directory into someone's repository for trying a colour; this is also what makes the write-back rule one sentence. `SetTheme` **rewrites one key in place** rather than re-encoding, since the file exists to be hand-edited and is mostly comments — losing them on a first theme change would be a silent deletion of someone's writing. Autosave waits for a **pause in typing**: a fixed interval writes mid-edit, and on-focus-change leaves disk an hour behind screen. Rejected along the way: per-window autosave deadlines (unobservable gain, more state), and new `syntax.toml*` theme keys (every existing user theme would have stopped colouring TOML). `ui.Menu` has no nested submenus, so the two entries are flat under Options rather than the submenu asked for — said so rather than building nesting nobody requested.
177- **Tests**: 20 in `internal/settings`, 24 for the TOML scanner, 18 for autosave (with an injected clock, so nothing waits), 10 for the project-settings menu items, 7 in `main`. Run with `make test`, or `go test ./... -race`. Also verified end to end against the real binary in a pty: the theme coming from `settings.toml`, `-theme` overriding it, and autosave writing a file **from the idle timer alone** with the process killed before any quit path could run — plus a control run with no settings file, which left the file untouched.
178- **Two defects found in my own new code**: the TOML scanner patched `spans[len-1].Start` after an emit that had dropped an empty span, corrupting the *previous* span (a test caught it); and the first colouring test asserted through a live shell, which is the class of trap already recorded from the terminal session.
179- **Quality**: PASS. Four smells appeared (`writeFile` and `tomlWordClass` many-returns, two complex binary expressions) and were refactored away by splitting functions and naming the character sets. 0 errors, 0 warnings, 0 smells, complexity 1150.
180- **Docs**: three new pages per language — `how-to/configure-a-project.md`, `reference/project-settings.md`, `explanation/project-settings.md` — and updates to both indexes, `reference/cli.md`, `reference/menus.md`, `explanation/architecture.md` and `explanation/colouring-and-completion.md`. New `internal/settings/README.md`; `internal/syntax/README.md` and `internal/app/README.md` brought back in sync. `docs/diagrams/packages.drawio` gained `settings` and three edges, and was re-checked against `go list` — it matches edge for edge.
181- **Context**: the terminal work from earlier the same day was merged to `main` by the user as PR #1 during this session; this work sits on `feature/project-settings`, uncommitted.
182
183## 2026-08-31 — Window frame boxes: [x] to close, [■] to maximise
184
185- **Goal**: "ce bouton `[■]` pour le moment sert a fermer la fenetre, il faudrait le changer par `[x]` et ajouter un bouton sur la droite `[■]` pour maximiser la fenetre — il faudra mettre le readme a jour, ainsi que la doc". Then "va au bout du bout". One option chosen up front: the maximise box **toggles**, and its **symbol changes with the state** (`[■]``[▬]`).
186- **Changes**: `internal/ui/window.go``closeBoxLabel` is now `[x]`; new `maximizeBoxLabel`/`restoreBoxLabel`/`boxWidth`/`maximizeOffset`/`numberOffset`; `OnMaximize func()`; `Maximized()`, `Maximize(area)`, `Restore()`; `followDesktop` and `place` split out of the resize path; the window number moved from `W-4` to `W-6` and the title's reserved margin from 10 to 11 columns. `internal/ui/desktop.go``Maximize` became **`ToggleMaximize`**, `Add` wires `OnMaximize`, `Tile`/`Cascade` now use `place`. `internal/app/actions_view.go``MaximizeWindow` toggles.
187- **Decisions**: the box **shows its action, not the window's state** — a fixed symbol is ambiguous exactly when it matters, since you can see the window is large but not what pressing the box would do. `Desktop.Add` wires `OnMaximize` rather than `app`, because the desktop is the only thing that knows the area to fill; a window not on a desktop draws **no box** rather than a dead one. `Desktop.Maximize` was **renamed** rather than left in place: keeping the name for a toggle would be a lie, and there was one caller. **Window ▸ Maximise toggles too** — a menu and a button disagreeing about "maximise" is a bug people report. Rejected: a one-way maximise (the second press does nothing visible), and a fixed `[■]` in both states.
188- **Two holes found and closed on the way**: a maximised window kept a **stale restore rectangle** across a terminal resize, so shrinking the terminal then restoring would put the window partly off screen (`followDesktop` now carries it); and `Tile`/`Cascade` left a window calling itself maximised, so its box offered to restore to a rectangle that no longer meant anything (`place` clears it).
189- **Tests**: 14 new in `internal/ui/maximize_test.go`, including a property test over widths 16…60 × four title lengths. `internal/ui` at 93.0 %. Run with `make test`, or `go test ./... -race`.
190- **A test that was worthless until it was fixed**: the margin test asserted only that the close box, number and maximise box were intact after drawing — which is true whatever the margin, because `drawNumber` runs *after* `drawTitleBar` and simply repaints over the title. Verified by putting the wrong margin back: the test passed. It now asserts on the cell **beside** each piece of furniture, and with the wrong margin it fails on `"…" sits against the number`.
191- **Verified end to end** by rendering the real binary through the project's own VT emulator (`internal/terminal`): the frame reads `╔═[x]═ main.go ═1═[■]╗`, Window ▸ Maximise fills the terminal and flips the box to `[▬]`, and a second use restores the previous size and symbol.
192- **Quality**: PASS first time. 0 errors, 0 warnings, 0 smells, complexity 1156.
193- **Docs**: `internal/ui/README.md` (API table, Turbo Vision details, the new methods) and the root `README.md`'s ASCII screenshot. In both languages: `reference/keyboard.md` (three mouse rows), `reference/menus.md` (Maximise is a toggle), `how-to/use-a-terminal.md` (which told people to click `[■]` to close), and a new section in `explanation/design-decisions.md`. No package was added or rewired, so `docs/diagrams/packages.drawio` is unchanged — re-checked against `go list` and still matching.
194- **Context**: project settings were merged to `main` as PR #2 before this session; this work sits on `feature/windows-buttons`, uncommitted.
195
196## 2026-08-31 — Bug fix: OK did nothing in the Open dialog
197
198- **Goal**: user report — "quand on ouvre un fichier, dans la popup le bouton OK ne semble pas fonctionner (click souris ou focus et entree)".
199- **Diagnosis**: `FileDialog` never wired `ListBox.OnSelect`. Highlighting a file therefore never reached the **Name** field, `Path()` returned `""`, and `confirm()` — which only closed when the path was non-empty — did nothing at all. Both routes the user tried went through `confirm()`, which is why both appeared dead. `OnSelect` itself was fine: implemented, fired by `setSelected`, and covered by its own test in `internal/ui`. It simply had no caller.
200- **Changes**: `internal/app/dialogs.go``f.list.OnSelect = f.showSelection`; new `showSelection`, which writes the highlighted entry's name into the field and clears it for the parent entry; `confirm()` falls back to `choose()` when the field is empty, so OK is never dead; `parentEntry` extracted, replacing two copies of `".." + string(filepath.Separator)`.
201- **Decisions**: the field **mirrors the highlight** rather than OK reading the list behind the user's back — a fallback nobody can see would let Save As write to a filename that was never shown. `../` clears the field instead of putting `..` in a box labelled "Name:", and OK then falls back to the highlight, which browses up. Construction is safe without a special case: `SetItems` calls `setSelected(0)`, which does not fire when the selection is already 0, so Save As keeps the name it opened with.
202- **Tests**: 11 new in `internal/app/dialogs_test.go`, three of them end-to-end through the app — clicking **OK** with the mouse, `Tab` then `Enter`, and `Alt-O`. All eight behavioural ones were confirmed to fail against the original code before the fix went in. `internal/app` 84.3 % → 84.7 %.
203- **A wrong diagnosis I had to correct mid-session**: the mouse-click test kept failing after the fix and I reported a second bug. It was my own test — `strings.Index` on a drawn row returns a **byte** offset, and a row full of `░` and `║` at three bytes each put the click about thirty columns right of the button. The helper now counts runes. There was only ever one bug.
204- **Verified end to end** by driving the real binary through a pty and rendering it with the project's own VT emulator: `F3`, `↓`, `↓` fills the Name field with the highlighted entry, and `Tab` `Enter` opens it in a second window.
205- **Quality**: PASS. 0 errors, 0 warnings, 0 smells, complexity 1160.
206- **Docs**: a new "The Open and Save As box" subsection in `reference/keyboard.md` in both languages, and a paragraph in `internal/app/README.md`. No package added or rewired; the drawio diagram is unchanged and re-verified against `go list` (27 edges each side).
207
208## 2026-08-31 — Project tree window (ticket 0003)
209
210- **Goal**: "une fenêtre qui affiche un treeview du projet en cours avec possibilté de sélectionner un fichier et l'ouvrir", then "va au bout du bout". Options chosen up front: rooted at the **working directory** (the `.turbo-go` rule, not the `go.mod` walk); **hide `.git` only**; an **ordinary window**, not a docked panel; refreshed by a **key and after a save**, with no filesystem watching.
211- **Changes**: new `internal/filetree` package — `tree.go` (the model: `Node`, `Tree`, lazy expansion, sorted listing, `Refresh`), `view.go` / `view_draw.go` / `view_events.go` (the `ui.Widget`), `README.md`, tests. Four `tree.*` keys in `internal/theme/keys.go`, all three shipped themes and the completeness test. New `internal/app/tree.go`, plus `treeWindow`/`treeView` fields, the `F9` shortcut, `Window ▸ Project tree`, a branch in `closeWindow`, and `refreshTree()` on both save paths.
212- **Decisions**: a **window, not a panel** — a docked strip would mean `Desktop` growing reserved edges that `fitInto`, the grow modes, maximise, tile and cascade all have to respect, which is a change to the foundation of the interface for one widget; as a window it got F6, Alt-digits, `[x]`, `[■]` and Tile for free and `ui` did not change at all. **One tree at a time**, because the root is fixed at start-up and a second view would have nothing to distinguish it. **`.git` hidden and nothing else** — copying the Open dialog's hide-every-dot-entry rule would have made `.turbo-go/settings.toml` unreachable from the editor's own file browser. **No filesystem watching**: `fsnotify` would be a third dependency for a feature whose failure mode is a stale line, so the editor refreshes at the moments it can be sure of. Rejected: rooting at the `go.mod` (unpredictable in a monorepo, and the root would depend on a file three directories away), and respecting `.gitignore` (wants a pattern engine that is a feature in itself).
213- **A theme decision forced by measurement**: the tree was going to borrow `list.*` and needed keys of its own instead. `list.selected` is coloured against a *dialog* — turbo-classic makes it white on navy while `window.body` is navy — so a tree in a window would have highlighted its selected row in the colour underneath it. Checked before writing the keys, not guessed; a test now holds every shipped theme to 64 channel values between `tree.text` and `tree.selected`, and it was confirmed to fail when `tree.selected` is set back to navy.
214- **Tests**: 41 in `internal/filetree` (94.7 %) and 10 in `internal/app`; `internal/app` 84.7 % → 84.6 % on a larger base. Run with `make test`, or `go test ./... -race`.
215- **Verified end to end** by rendering the real binary through the project's own VT emulator: `F9` lists the project with `.git` hidden and `.turbo-go`/`.gitignore` shown, directories first; three `→` presses nest two levels with the right markers; `Enter` opens `internal/app/app.go` into a third window with its content.
216- **Quality**: PASS first time. 0 errors, 0 warnings, 0 smells, complexity 1228.
217- **Docs**: three new pages per language — `how-to/browse-a-project.md`, `reference/project-tree.md`, `explanation/project-tree.md` — and updates to both indexes, `reference/keyboard.md`, `reference/menus.md`, `reference/themes.md`, `how-to/write-a-theme.md` and `explanation/architecture.md`. New `internal/filetree/README.md`; `internal/app/README.md` and `internal/theme/README.md` brought back in sync. `docs/diagrams/packages.drawio` gained `filetree` and four edges, re-checked against `go list` — 31 edges each side.
218
219## 2026-08-31 — Markdown, JavaScript, HTML and shell colouring (tickets 0009, 0013, 0014)
220
221- **Goal**: "ajoute le support des syntaxes markdown, javascript, html et bash", then "va au bout du bout". Options chosen up front: **five new classes** (heading, tag, attribute, emphasis, link) rather than reusing the twelve; **readable depth** rather than the hard tail; a Markdown fence **not** coloured in its announced language; recognition by **extension plus shebang**.
222- **Changes**: five `Class` values and five `KeySyntax*` keys, set in all three shipped themes and in the completeness test. New `internal/syntax/scanner.go` — a shared `lineScanner` plus `scanLines`, `takeQuoted`, the block-comment helpers and the rune predicates — and the **TOML scanner ported onto it**. New `markdown.go`, `markdown_inline.go`, `javascript.go`, `html.go`, `bash.go`. `LanguageOf(path)` became `LanguageOf(path, firstLine)` with shebang detection; the two call sites in `internal/editor` pass `buf.Line(0)`.
223- **Decisions**: **no general engine** — no pattern language, no grammar format; each scanner is ordinary Go sharing only a line, a position and the spans so far, so adding a language means writing one rather than learning a notation. **A scanner guesses nothing**: JavaScript regex literals, shell heredocs, JavaScript inside `<script>` and the language of a Markdown fence are all absent, each because recognising it needs more than one line holds and a wrong guess is louder than no guess. **Five new classes** because a heading is not a keyword and a tag is not one either; the cost — third-party themes falling back to `default` — was accepted and is documented. The shared scanner **touched working TOML code**, which was flagged before starting; its 24 tests were the net and stayed green throughout.
224- **Two defects in my own new code**, both caught by tests: `finishTemplate` coloured nothing because a helper had already run the position to the end of the line and `emit` drops empty spans — the same trap the TOML scanner produced once before, in a different shape; and the shell scanner split `-eu` into an operator and a word, so every option in every script was arithmetic.
225- **One defect no test could catch**: `syntax.link` was lime in `turbo-classic`, the exact colour of `syntax.string`, so a Markdown link and an inline `code` span were indistinguishable. Found by rendering the real editor through the project's own VT emulator and reading back the foreground colour of every run.
226- **A weakness found in an existing test**: the theme completeness test only constrains `turbo-classic`, because the other two inherit from it and inheritance is resolved at parse time. Deleting a key from a child passes; from the base, it fails. Recorded rather than changed.
227- **Tests**: 180 in `internal/syntax` (96.0 %), covering each language's constructs, its multi-line carries, its stated omissions, and that every span stays inside its line. Run with `make test`, or `go test ./... -race`.
228- **Verified end to end** by rendering the real binary through the project's own VT emulator on one file per language, reading back the foreground of each run: Markdown headings, emphasis, code and links; JavaScript keywords, template literals, builtins and hex numbers; HTML tags, attributes, entities and comments; shell builtins, options, and expansions inside double-quoted strings.
229- **Quality**: PASS after one round. Four smells appeared (`Highlight` and `Language.String` many-returns, `markdown.go` file complexity, an `html.go` binary expression) and were refactored away with two dispatch tables, a file split and a named character set. 0 errors, 0 warnings, 0 smells, complexity 1389.
230- **Docs**: a new `reference/languages.md` in both languages, giving each scanner's exact boundary; rewritten "the other five languages" and a new "five classes Go has nothing to say about" section in `explanation/colouring-and-completion.md`; updates to both indexes, `reference/themes.md` and `how-to/write-a-theme.md`. `internal/syntax/README.md` rewritten for the new surface. No package added, so `docs/diagrams/packages.drawio` is unchanged and still matches `go list` (31 edges each side).
231
232## 2026-08-31 — Snippets, and one level of submenus in the menu bar (ticket 0006)
233
234- **Goal**: "un système de snippets qui seraient dans un fichier toml dans ./turbo-go, on aura un menu principal Snippets dont les sous menus seront construits à partir du contenu de snippets.toml — le snippet sélectionné est copié au niveau du fichier ouvert à l'endroit du curseur", then mid-work: "tu genereras un fichier de snippets par defaut si ils n'existent pas a partir du menu", then "va au bout du bout". Options chosen up front: `.turbo-go/snippets.toml` **plus a user-level file**; **real nested submenus**, not flat items; a **flat `[[snippet]]` list** with a `group` field and a `languages` filter; insertion **re-indented** to the cursor's column.
235- **Changes**: `ui.MenuItem.Items` and `ui.Menu.OnOpen`, with the submenu's state, geometry, drawing, keyboard and mouse; `menu.go` split into `menu.go` / `menu_draw.go` / `menu_events.go` / `submenu.go`. New `internal/snippets` package (`snippets.go`, `create.go`, `README.md`, tests). `editor.InsertSnippet` plus `indentContinuationLines` and `leadingWhitespace`. New `internal/app/snippets.go` and the `Snippets` menu on the bar.
236- **Decisions**: **one level of nesting**, because the format is groups → snippets and a general depth would mean replacing the bar's two indices with a path in the widget every dialog depends on. **`Menu.OnOpen`** rather than rebuilding the bar each loop turn: the contents depend on a file that changes and on the front window, so there is no start-up moment at which they exist, and OnOpen runs at exactly the moment they are about to be seen. **Two files, project wins on a clash**, mirroring how `-theme` beats a project setting which beats the built-in default. **Re-indented insertion**, because an `if err != nil` is inserted inside something by definition and a feature whose output needs fixing every time saves nobody anything. **An unreadable file is a greyed line in the menu**, not silence, because silence looks exactly like having no snippets and sends you to create a file you already have. Rejected: flat items with greyed group captions (thirty snippets give a menu taller than the terminal), and placeholders/tab stops (a second feature with its own state).
237- **Two defects found by driving the real binary**, neither of which any test caught: **`Alt-S` opened Search, not Snippets** — both labels claimed S and the bar answers the first match, so the new menu was unreachable from the keyboard; the label is now `S~n~ippets` and `TestNoTwoMenusShareAHotKey` holds the line. And a submenu **wider than the terminal** could not be made to fit by flipping it left, so the width is capped and long labels are clipped.
238- **Tests**: 19 for submenus in `internal/ui` (93.3 %), 19 in `internal/snippets` (83.8 %), 12 for insertion in `internal/editor` (96.2 %), 16 in `internal/app` (84.8 %). Run with `make test`, or `go test ./... -race`.
239- **Verified end to end** by rendering the real binary through the project's own VT emulator: `Alt-N` opens the menu, `Enter` on **Create snippets file** writes and opens `.turbo-go/snippets.toml`, `Alt-N` then `→` opens the **Go** submenu (which flipped to the *left* for want of room), and `Enter` on **table test** inserted a four-line snippet correctly indented on the tab of the line it landed on.
240- **Quality**: PASS after two rounds. One smell — `menu.go` file complexity, 46 before this work and 69 after — was fixed by splitting the file the way `terminal` and `filetree` are already split, first pulling out `submenu.go` (69 → 56, still over) and then `menu_draw.go` and `menu_events.go`. 0 errors, 0 warnings, 0 smells, complexity 1480.
241- **Docs**: three new pages per language — `how-to/use-snippets.md`, `reference/snippets.md`, `explanation/snippets.md` — plus a Snippets section in `reference/menus.md`, submenu keys in `reference/keyboard.md`, and updates to both indexes and `explanation/architecture.md`. New `internal/snippets/README.md`; `internal/ui/README.md` and `internal/editor/README.md` brought back in sync. `docs/diagrams/packages.drawio` gained `snippets` and three edges — including `app → syntax`, which the programmatic check against `go list` caught and I had missed.
242
243## 2026-08-31 — The Go menu: format, lint, build, test, run (ticket 0017, partly)
244
245- **Goal**: "ajouter les commandes go qui permettent de lancer un formatage, le lint, le build, le lancement de tests, le run", then "va au bout du bout". Ticket 0017 asked for more than the message — a regenerable TOML file of commands — and the user chose that: a `tools.toml` **initialised with the five**. Other options chosen up front: output **in a terminal window**; the commands `gofmt -l -w .`, `go vet ./...`, `go build ./...`, `go test ./...`, `go run .` over the whole module; a **new `Go` menu** on `Alt-G`.
246- **Changes**: `terminal.Options.Args` to run one command rather than a shell, and `terminal.ViewOptions` carrying `Name`/`OnChange`/`OnExit`. New `internal/tools` (`tools.go`, `create.go`, `README.md`, tests) and `internal/projectfile` (`projectfile.go`, `README.md`, tests). `Buffer.Reload` plus `ErrModified`. New `internal/app/gotools.go` with the `Go` menu, `RunTool`, and `reloadAfterTools`; `createProjectFile` extracted in `project.go`. `settings`, `snippets` and `tools` all now write through `projectfile`.
247- **Decisions**: the five commands are **the starter file's contents, not code**`go vet` is the default only because it ships with the toolchain, and a project with a `Makefile` wants `make check`; changing one is editing a file. **No user-level tools file** (unlike snippets): a global one would offer `go build` in a Rust repository. **A terminal window, not a captured pane**, because a pipe costs `go test`'s colours, `go build`'s paging, `go run`'s keyboard and `Ctrl-C`. **Files a command rewrote are re-read, unless modified**: `Format` rewrites the file in front, and without this the next `F2` writes the unformatted version back over gofmt's work — but a modified buffer is left alone and named, because the edit and the formatter genuinely disagree and the editor is not in a position to decide. Rejected: hardwiring five items (wrong within a week), and splitting an argv instead of `sh -c` (would mean inventing quoting rules for a hand-written string).
248- **A data race that predated this work and that this work exposed.** `terminal.NewView` starts the reading goroutine, and both callers then assigned `OnChange`/`OnExit`. `-race` never caught it across the whole terminal and snippets features, because a shell takes longer to produce its first output than an assignment takes to run; `sh -c "echo x"` closed that window and the detector fired immediately. Fixed structurally with `ViewOptions` rather than with a mutex, so the race is impossible rather than guarded.
249- **A second defect found by driving the binary**: a finished command's window could not be closed with `Ctrl-W`. The view consumed every key and wrote it to the dead shell, where the write failed silently and the key was consumed anyway — the mouse was the only way out. A finished view now takes only the scrolling keys.
250- **Tests**: 4 for `Args`/`Name` and 3 for `Exited` in `internal/terminal` (95.9 %), 12 in `internal/tools` (93.1 %), 7 in `internal/projectfile` (75.0 %), 8 for `Buffer.Reload` (95.8 %), 10 in `internal/app` (85.0 %). Run with `make test`, or `go test ./... -race`.
251- **Quality**: PASS after one round. Four smells, all real duplication: `CreateTools`/`CreateSnippets` were the same dance (extracted as `createProjectFile`, which `CreateProjectSettings` now uses too), and the atomic TOML write existed in **three** copies (extracted as `internal/projectfile`; `internal/buffer`'s was left alone because it preserves an existing file's mode, which is a different operation). Complexity went **down**, 1528 → 1513. 0 errors, 0 warnings, 0 smells.
252- **Verified end to end** by rendering the real binary through the project's own VT emulator: `Alt-G` on a project with no tools file offers only `Create tools file`; `Enter` writes and opens it; `Alt-G` then shows the five; `T` runs `go test ./...` in a window titled with the command; and `F` on a deliberately misformatted file ran `gofmt -l -w .`, after which `Ctrl-W` closed the command window and the editor showed the **reformatted** file — the reload working.
253- **Docs**: three new pages per language — `how-to/run-go-commands.md`, `reference/go-tools.md`, `explanation/go-tools.md` — plus a Go section in `reference/menus.md`, `Alt-G` in `reference/keyboard.md`, a "after the program has gone" section in `reference/terminal.md`, and updates to both indexes and `explanation/architecture.md`. New `internal/tools/README.md` and `internal/projectfile/README.md`; `internal/terminal`, `internal/buffer` and `internal/app` READMEs brought back in sync. `docs/diagrams/packages.drawio` gained `tools` and `projectfile` with five edges, re-checked against `go list` (39 edges each side).
254
255## 2026-08-31 — Go tools: configurable output, and a popup by default
256
257- **Goal**: "finalement je préfère pour les tools go que la sortie ne soit pas dans un terminal mais dans une popup / pour les autres tools il faudra prévoir de définir le type de sortie: popup, terminal, editeur". A revision of the uncommitted work from earlier the same session, not a layer on it. Options chosen up front: the popup **opens immediately and fills in** (modal, Escape stops the command); the **exit code always in the title** with `(no output)` for a silent success; `editor` means **an ordinary editable window**; and `Run` stays `terminal` in the starter file while the other four are `popup`.
258- **Changes**: `tools.Output` with `OutputPopup`/`OutputTerminal`/`OutputEditor`, `Tool.Where()`, validation that refuses an unknown value, and the starter file naming `output` on all five. New `internal/tools/run.go``Start`, `Run`, `Lines`, `Done`, `Dropped`, `Stop` — plus build-tagged `group_unix.go`/`group_other.go`. `NewOutputDialog` in `internal/app/dialogs.go`. `internal/app/gotools.go` reworked into `runInTerminal` / `runCaptured`, with `toolRun`, `refreshRunningTool`, `finishRun` and `openOutputInEditor`. `App.tick` extracted from the `Run` loop.
259- **Decisions**: **three destinations because none is right for everything** — a terminal for interactive or long commands, a popup for run-read-dismiss, an editor window for output to work through. The popup opens **immediately** because one appearing three seconds later swallows whatever was being typed then; it is modal, which is a real cost on a slow build, named in the docs, and answered by `output = "terminal"` on that tool. An **unknown `output` is refused, not corrected**: `"termnial"` falling back silently would look as though it worked. The **exit code is always in the title** because `go build ./...` succeeding is silent and a blank dialog cannot be told from one that never started.
260- **A defect found by a flaky test, then reproduced deliberately**: `Stop()` killed only the shell, and a grandchild inheriting the output pipe left the reading goroutine blocked until it ended — 20 seconds in the suite, and for `go test ./...` it would be every test binary spawned. Fixed by killing the **process group**, with `cmd.WaitDelay` as the backstop. Verified: 11 ms after the fix, never before it.
261- **Two vacuous tests caught and fixed**, both the same shape — the test driving the thing under test. `waitForLoopTurn` called `a.reloadAfterTools()` directly, so the test passed with that step deleted from the event loop; `App.tick` was extracted and the helper now takes a whole loop turn. And the process-group test killed the shell before it had forked; it now waits for the child to print. Both were confirmed by breaking the code they cover.
262- **Tests**: 26 in `internal/tools` (95.0 %), 11 more in `internal/app` (85.7 %). Run with `make test`, or `go test ./... -race`.
263- **Verified end to end** through the project's own VT emulator: `go build ./... — ok` with `(no output)`; `go vet ./... — exit 1` showing `main.go:6:2: unreachable code`; `gofmt -l -w . — ok` listing the reformatted file.
264- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1543.
265- **Docs**: the three `go-tools` pages written earlier in the session were **revised**, not appended to — they described a terminal as the only destination. Plus the Go line in `reference/menus.md` in both languages, and `internal/tools/README.md` and `internal/app/README.md` brought back in sync. No package added, so the diagram is unchanged and still matches `go list` (39 edges each side).
266
267## 2026-08-31 — Fix: a reinstall produced a binary that would not run on macOS
268
269- **Goal**: user report — `scripts/install.sh` printing `✗ the installed binary does not run` after a clean build, on macOS (`/Users/k33g/go/bin`).
270- **Diagnosis**: the installer used `cp "$STAGING/$BINARY" "$TARGET"`, which opens the destination with `O_TRUNC` and writes in place — **the inode is reused**. macOS caches a binary's code signature against its inode, so new bytes in the old inode leave the cached signature describing something else and the kernel refuses to execute the result. It fails on *reinstall*, not on a first install, which matches a user who had been running the editor all day. Cross-compiling and `go vet` for `darwin/arm64` and `darwin/amd64` were both clean, ruling out the code.
271- **Changes**: `scripts/install.sh` now copies to `.turbo-go.incoming.$$` **inside `$prefix`** and `mv -f`s it over the target, so the name gets a fresh inode and the install is atomic besides; the `EXIT` trap cleans the temporary. And the verification captures the binary's own stderr and prints it: `the installed binary does not run` on its own tells nobody anything they can act on.
272- **Decisions**: the temporary must live in `$prefix` rather than in `$STAGING`, because a rename only works within one filesystem — the same reasoning `internal/buffer` and `internal/projectfile` already follow. Rejected: `install -m 0755` (does an in-place write on some platforms, so it would not fix it), and `rm` then `cp` (leaves a window with no binary on the PATH).
273- **Tests**: 3 added to `install_test.go` — the reinstall gives the file a **new inode**, the reinstalled binary runs, and the installer surfaces the binary's own error. The first two were confirmed failing against the `cp` version before the fix. `install_test.go` is now 13 tests.
274- **Verified end to end** on Linux: inode 529596 → 529598 across a reinstall of a binary that had been executed in between, no temporary left in the prefix, and the error path shown to carry the system's message.
275- **Not verified on macOS**, and said so plainly to the user: this sandbox is Linux, so the diagnosis rests on the symptom matching a known failure mode rather than on a reproduction. The improved error message is what makes a wrong diagnosis recoverable.
276- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1543 — unchanged, the fix is in a shell script.
277- **Docs**: a new "When something goes wrong" section in `how-to/install.md` in both languages, covering the three failures the installer can report, and the script's own header comment brought back in line with what it does.
278
279## 2026-08-31 — Terminal output that never appeared, and tools in menus of their own
280
281- **Goal**: two user reports in one message. `output = "terminal"` with `command = "echo 'TADA'"` showed only the title and no output; and "je voudrais pouvoir faire la différence entre les tools go et d'autres tools qui iraient dans un menu tools". Options chosen up front for the second: a **free-form `menu` key** on the tool (absent → Go, arbitrary names, menus in the order their first tool appears), in the **single** `tools.toml`, with the stated cost that hot keys for created menus must be assigned without clashing with the nine fixed ones.
282- **Changes (the bug)**: `internal/terminal/screen_resize.go``rowsToDrop(previous, height, cursorRow)` replaces `max(len(previous)-height, 0)`.
283- **Changes (the feature)**: `tools.Tool.Menu` with `MenuName()`, `List.In(menu)` and `List.MenuNames()`; `DefaultMenu`; the starter file documents the key. `ui.MenuBar.SetMenus`. New `internal/app/toolmenus.go``allMenus`, `toolMenus`, `toolMenu`, `takenHotKeys`, `hotKeyLabel`, `fileStamp`, `stampOf`, `toolsFileStamp`, `refreshToolMenus` — with `refreshToolMenus` added to `App.tick` and `App.toolsStamp` to the struct. `commandItems` took a menu name. `buildMenus` now delegates the order to `allMenus`.
284- **The bug, diagnosed**: a terminal window is created at the default 80×24 and the first `layout()` resizes it to its frame (about 76×20). `Screen.Resize` dropped `previous-height` rows **from the top** into scrollback, so a single line of output at row 0 went with them while blank rows stayed below. Whether the output arrived before or after that resize decided whether it showed — which is exactly the intermittence reported. It now drops only as many rows as the cursor actually needs. The two tests were written first and confirmed failing; the pre-existing `TestShrinkingKeepsTheNewestLinesAndRemembersTheRest` still passes unchanged.
285- **Decisions**: a **free-form name, not a fixed second `Tools` menu** — a Tools menu holding `docker compose up`, `psql` and a deploy script is as undifferentiated as a Go menu holding them, and rejecting the fixed menu also rejects the second file (`menus.toml`) that would have to agree with the first about which tools exist. **`Go` stays fixed** on the bar: it holds `Create tools file`, which has to be reachable in a project that has no tools file — the very project that needs it. **Hot keys are assigned, not read**: the file's author cannot know which letters are free, and a clash is silent (the bar answers the first match; the second menu draws normally and never opens) — the `Snippets`/`Search` bug from earlier in this project, made permanent. Tildes in a name are honoured **only when the letter is free**; refusing the file instead would break a working tools file the day a release adds a menu. **A `stat` per loop turn, not a parse**: `Menu.OnOpen` cannot cover a menu that does not exist yet, and parsing on every keystroke is work done for nothing.
286- **Tests**: 7 in `internal/tools`, 2 in `internal/ui`, 16 in `internal/app` (new `toolmenus_test.go`), 2 in `internal/terminal`. Run with `make test`, or `go test ./... -race`.
287- **Two test expectations were wrong, and the code was right** — worth recording because both are the mechanism working: `Format` gets `For~m~at`, not `F~o~rmat`, because `o` is Options'; and a menu written `T~o~ols` loses its `o` for the same reason, so the case was retested with `Doc~k~er`.
288- **Verified end to end** by rendering the real binary through the project's own VT emulator: the bar reads `… Snippets Go Tools Docker Help` in file order; `Alt-T` drops down Echo and Date with no `Create tools file`; `Alt-T Enter` opens a terminal window titled `echo 'TADA'` **showing TADA**; `Alt-D Enter` gives the popup `echo docker — ok` showing `docker`; and `Alt-G` still holds Build and `Create tools file`.
289- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1543 → 1567.
290- **Docs**: `reference/go-tools.md` gained the `menu` key, a Go-menu section and a "Menus a tool asks for" section with the hot-key rules; `how-to/run-go-commands.md` gained "Put a tool in a menu of its own" and two variants; `explanation/go-tools.md` gained three sections (why a tool names its menu, why the hot key is not the file's, why the bar is rebuilt from a stat); `reference/menus.md` gained "Project menus"; `reference/keyboard.md` a note — all in **both languages**. `internal/tools`, `internal/app` and `internal/ui` READMEs and the root README brought back in sync. No package added, so the diagram is unchanged and still matches `go list`.
291- **Then, on request**: `demo/.turbo-go/tools.toml` brought up to date — **regenerated from the `template` constant** in `internal/tools/create.go` rather than hand-edited, so it cannot drift from the generator again, with the user's `~E~cho` tool appended under `menu = "Tools"`. Its header had still been describing a terminal as the only output destination, two revisions after that stopped being true.
292- **Merged by the user** as PR #7 (`88a4c38`), `feature/go-format-lint` into `main`, who also closed tickets 0004 and 0017.
293
294## 2026-08-31 — The version in the About box comes from the build
295
296- **Goal**: "je voudrais que lorsque l'on fait une release, la version apparaisse dans la fenêtre about" — ticket 0010. The stated request was already half true: About *did* show a version. It showed `0.1.0` from `const Version` in `internal/app`, on a checkout fourteen commits past `v0.1.0`. Said so up front and treated the real goal as making the number true. Options chosen: **ldflags plus BuildInfo with a fallback**; a non-release build shows `git describe` output; About also carries the **commit** and the **build date** (not the Go version); **no `make release` target** — stamping only.
297- **Changes**: new `internal/version``Info{Number, Commit, Built}`, `Current`, `String`, `BuiltAt`, and a `resolve` split out so every case is testable, plus `isPseudoVersion`. `app.Version` deleted; `Name` kept. `aboutText(info, themeName)` extracted as a pure function in `actions_view.go`. `main.go` prints `version.Current()`. `Makefile` gained `VERSION`/`COMMIT`/`BUILT`/`LDFLAGS`, a stamped `build`, and a `version` target. `scripts/install.sh` stamps the same way, with a path for a checkout git cannot describe.
298- **Two discoveries that changed the design mid-way, both from running the thing rather than reasoning about it.** Go 1.26 does **not** report `(devel)` for a plain `go build .` in a checkout: it reports a **pseudo-version**, `0.1.1-0.20260831165958-88a4c3859bf3+dirty`. Unreadable in a dialog, and its `0.1.1` is a patch release that does not exist — so pseudo-versions are recognised and reported as `devel`. Then the first recogniser was wrong: the character before the timestamp is a **dot**, not a dash, whenever a base tag precedes the commit (`-0.` / `-pre.0.`). Caught by writing the three forms into a test and watching three of four fail.
299- **Decisions**: `unknown` rather than a fallback constant, because a plausible-looking version nobody set is the exact defect being removed. `vcs.time` deliberately unused — it is the commit's timestamp, so "Built" would be false on every binary. About **omits a line whose fact is empty**; `go install …@v0.2.0` records a version and no VCS information at all. `resolve` takes its four inputs as arguments because a test binary cannot be built into having linker stamps.
300- **Tests**: 17 in `internal/version` (98.3 %), 3 for `aboutText` in `internal/app` (86.3 %), 3 in `install_test.go` (now 16). The existing About test only counted modals; it was left in place and joined by tests that read the text. Run with `make test`, or `go test ./... -race`.
301- **Verified end to end** through the project's own VT emulator, in both states: a build stamped `v0.2.0` shows `Turbo Go 0.2.0` with `Commit: 88a4c38` and `Built: 2026-08-31 18:04 UTC`; an unstamped build shows `Turbo Go devel-dirty` with the commit line and **no** `Built:` line and no gap where it would have been. `scripts/install.sh` reports `Turbo Go 0.1.0-14-g88a4c38-dirty (88a4c38, built …) → /tmp/tgbin/turbo-go`.
302- **Quality**: PASS after one round. One real smell — a four-term boolean in `isPseudoVersion` — fixed by splitting out `hasPseudoTail` and `withoutBuildMetadata`, which reads better than what the linter complained about. 0 errors, 0 warnings, 0 smells, complexity 1567 → 1592.
303- **Docs**: two new pages per language — `reference/versioning.md` and `how-to/make-a-release.md` — plus a section in `explanation/design-decisions.md`, the `-version` row in `reference/cli.md`, the About row in `reference/menus.md`, a note in `how-to/install.md`, and both indexes. New `internal/version/README.md`; `internal/app/README.md` and the root README brought back in sync. `docs/diagrams/packages.drawio` gained the `version` node with two edges, re-checked against `go list`: 41 drawn = 29 internal + 12 third-party, none missing, none stale.
304
305## 2026-08-31 — Fix: the release script, broken by removing the version constant
306
307- **Goal**: user report — `./03-build-releases.sh` failing with `❌ v0.2.0 does not match the binary, which reports 2026-08-31T18:59:13Z)`. Their own release tooling, broken by the version work merged as PR #8 earlier the same day.
308- **Two defects, and the one they saw was the smaller.** The script read the version with `awk '{print $NF}'`, which took the last field of `Turbo Go 0.2.0 (7f8b36a, built 2026-08-31T18:59:13Z)` — a timestamp. But the cross-compile loop called `go build -trimpath` with **no `-ldflags` at all**, so all five downloadable binaries would have reported `devel` while the release page announced v0.2.0. Before the constant was removed it travelled into cross-builds; afterwards nothing did, and only the host binary was stamped — so nothing but a hand check would ever have caught it. Reproduced both before changing anything.
309- **Changes**: `Makefile` gained an `ldflags` target that prints `$(LDFLAGS)`, so the stamp is defined once. `03-build-releases.sh` reads it, passes it to every cross-compile, and runs the staged binary for the host platform before declaring the release built. Its version check was replaced by three plain ones — `git describe --tags --exact-match` equals `TAG`, `git diff --quiet HEAD` is clean, and `grep -F` finds the version in the binary — none of which parses prose. The stale hint "Update Version in internal/app/app.go" named a constant that no longer exists and is gone.
310- **Decisions**: the script asks **git** rather than the binary wherever it can, because `-version` is written for a person and has already changed shape once. `grep -F` rather than a field, for the one thing only the binary knows. A **dirty-tree check** was added, not asked for: `git describe --dirty` would otherwise stamp `v0.2.0-dirty` into binaries staged in a directory named `v0.2.0`, which is the same class of mismatch the script exists to prevent. Rejected: adding a machine-readable `-version-number` flag — new public surface, two languages of documentation, for a problem `grep -F` solves.
311- **Tests**: new `release_test.go`, 5 tests — the cross-compile carries `-ldflags`, the flags come from the Makefile and are not respelt, both git checks are present, no field is read out of `-version`, and `make ldflags` really does stamp a binary that then reports what `git describe` says. The first failed before the fix.
312- **Verified end to end** in a throwaway clone, so no tag was created in the user's repository: a full run stages five binaries and reports `✅ turbo-go-0.2.0-linux-arm64 reports 0.2.0`; and each guard was made to fire — untagged HEAD, HEAD tagged `v0.9.9` against `TAG=v0.2.0`, and a dirty tree — each with an actionable message.
313- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1592 — unchanged; the fix is in a Makefile and a shell script.
314- **Docs**: `reference/versioning.md` gained `make ldflags` and a note that `-version` is not a machine interface; `how-to/make-a-release.md` gained the numbered-scripts section and the three checks — both languages. `internal/version/README.md` and `.memory/summary.md` record why a cross-compile is the case that hides this.
315
316## 2026-08-31 — Fix: 01-release.tag.sh pushed a stale tag in silence
317
318- **Goal**: user report — `./03-build-releases.sh` refusing with `❌ HEAD carries no tag, so nothing built here can report v0.2.0`, then "fixe moi ca". The refusal was correct; the message was not, and the cause was in a different script.
319- **Diagnosis**: `01-release.tag.sh` had **no `set -e`**. Their first run tagged `v0.2.0` at `7f8b36a`. The second run's `git add . && git commit` created `78ea819`, then `git tag -a v0.2.0` failed with "already exists" — ignored — and the `git push origin "${TAG}"` after it pushed the **old** tag. `git describe` then read `v0.2.0-1-g78ea819` and `03` refused. So the release builder was reporting a fault three steps upstream of itself.
320- **Changes**: `01-release.tag.sh` gained `set -euo pipefail`; a `tagExists` check covering **both** the local ref and `git ls-remote origin` (a tag deleted locally after a failed attempt still exists on the remote, and a fresh one at another commit is then rejected); a guard so that having nothing to commit is not a failure, which `set -e` would otherwise have made one; and the tag now goes on **after** the push, so a rejected push leaves no stray tag. `03-build-releases.sh` gained a three-way diagnosis — HEAD tagged something else, the tag exists but HEAD has moved N commits past it, or no such tag — each naming the fix.
321- **Decisions**: the remote is consulted for the tag as well as the local ref, because the state the user was actually in (local tag deleted, remote tag possibly still there) is invisible locally. No commit SHA is printed for a remote tag: `ls-remote` returns the *tag object* for an annotated tag, and printing it as the commit sends the reader after a SHA that does not exist. **`02-release.publish.sh` was left alone and reported instead** — it has the same missing `set -e`, but its `read -r -d '' DATA` idiom always exits non-zero, so adding one naively would kill the script at its first line; and it POSTs to Codeberg, which is not mine to change unasked.
322- **Tests**: 5 more in `release_test.go` (now 10) — `01` stops on failure, checks both refs, survives an empty commit, tags only after pushing, and `03` can say how far HEAD is past the tag.
323- **Verified end to end** in throwaway clones, one with a local bare remote, so no tag was created in the user's repository: `03` was made to print each of its three diagnoses, and `01` was run on the happy path (tagged HEAD, pushed), then again to see it refuse a tag now present only on origin.
324- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1592 — unchanged; the fix is in two shell scripts.
325- **Docs**: a "When the scripts refuse" table in `how-to/make-a-release.md`, both languages, one row per message with its cause and its fix.
326
327## 2026-08-31 — Simplify: a release build stamps the tag, and stops checking
328
329- **Goal**: user, after the release builder refused a third time — "fais quelque chose de plus simple, tu build comme avant avec le tag de release". A correction of my own two previous turns, not a new feature.
330- **What was wrong with my design**: I had `03-build-releases.sh` stamp `make ldflags` (derived from `git describe`) and then *verify* that it agreed with `TAG` — HEAD tagged exactly, tree clean, binary reporting the version. Three gates, each defensible on its own, and together they blocked a release for conditions that were not actually errors. `git describe` answers "where is HEAD", which is a different question from "what release is this", so the gates existed only to reconcile an answer I should not have been asking for.
331- **Changes**: `03-build-releases.sh` now stamps `TAG` directly — `make ldflags VERSION="${TAG}"` and `make build VERSION="${TAG}"` — and the three gates are gone. The Makefile needed nothing: a command-line `VERSION=` already overrides the `:=` default. The one check kept is the staged binary for this machine reporting the version, which proves the artefact rather than the intent.
332- **Decisions**: the release **is** `${TAG}`, so the binaries say `${TAG}`; the whole class of "describe disagrees with TAG" stops existing rather than being detected. Building no longer requires the tag to exist — only `02-release.publish.sh` does, which is the step that genuinely needs it. `01-release.tag.sh`'s guards were **kept**: they are about not pushing the wrong tag, and they block no build.
333- **Tests**: two removed with the behaviour they covered — deliberately, at the user's decision, not to make anything pass — and two added: the script stamps `VERSION="${TAG}"` for both the host build and the cross-compiles, and `make ldflags VERSION=v9.9.9` really does produce a binary reporting 9.9.9. `release_test.go` is 9 tests.
334- **Verified end to end** in a throwaway clone with **no tag at all and a dirty tree** — the situation that had been refused — five binaries staged and `✅ turbo-go-0.2.0-linux-arm64 reports 0.2.0`.
335- **Quality**: PASS. 0 errors, 0 warnings, 0 smells, complexity 1592 — unchanged.
336- **Docs**: the "When the scripts refuse" table removed along with the refusals; `how-to/make-a-release.md` and `reference/versioning.md` rewritten around the override, both languages.
337- **Lesson worth keeping**: three turns were spent adding checks to reconcile two sources of truth, when the fix was to have one. The user saw it before I did.
338
339## 2026-08-31 — Three themes: cappuccino, cobalt, monochrome
340
341- **Goal**: `/methodical-dev` — "ajouter un theme cappucino, un theme cobalt, un theme monochrome" (ticket 0012). Options chosen up front: cappuccino **dark** (espresso, not cream); monochrome **pure grey**, no phosphor tint; cobalt **faithful to the recognised palette**; and yes to closing the silent-inheritance trap.
342- **Changes**: three new files under `internal/theme/themes/``cappuccino.toml`, `cobalt.toml`, `monochrome.toml` — each stating all 67 style keys. No Go code changed: themes are embedded by `//go:embed themes/*.toml`, so adding one is adding a file. Three new tests.
343- **A measurement changed the design of a test I had already promised.** The plan was "every colour legible on its own background". Probing the six themes first showed the weakest pairs are all *deliberately* faint furniture — scrollbar trough at 20, desktop, shadow, inactive frame, disabled entry, line-number gutter, between 20 and 70 in every theme including the two oldest. A blanket rule would have flagged six correct keys. Narrowed to the keys whose job is to be read, the measured floor is **80** (turbo-dark's `syntax.comment`), so the threshold is **64**: a quarter of the range, 16 below the present, a guard against regression rather than a description of today.
344- **Decisions**: shipped themes state their palette in full, **user themes may still inherit** — the rule is about what the project is answerable for, not about how a theme should be written, and the how-to still recommends inheriting (now with the advice to inherit from a theme whose ground matches yours). Monochrome distinguishes syntax by weight and slant rather than hue, which is what makes it useful on a projector and to a reader who cannot separate the red from the green. Cobalt's accents were left as loud as the palette is known for rather than muted into house style: a theme called Cobalt that is not that blue is a different theme with a borrowed name.
345- **Tests**: `TestEveryEmbeddedThemeSetsEveryKeyItself` in `internal/theme`; `TestEveryThemeKeepsItsTextReadable` and `TestEveryThemeTellsAdjacentSyntaxClassesApart` in `internal/editor`, beside the two cursor rules that were already there. **All three were falsified before being trusted** — a deleted `syntax.tag`, a `#2a2a2a` comment, and a cobalt link set to the string green, each producing the expected failure. The five existing theme tests now run over six themes.
346- **Verified end to end** through the project's own VT emulator: each new theme renders the editor intact, and the real Theme dialog was opened to confirm the list is six entries alphabetically — which **broke the tutorial**, whose "press ↓ to move to turbo-dark" had become five presses. Fixed in both languages with the list written out.
347- **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1592 — unchanged, the themes are data.
348- **Docs**: a "themes that ship" table in `reference/themes.md`, the embedded list, a new step 5 "check it stays readable" and the inherit-from-a-similar-ground advice in `how-to/write-a-theme.md`, the tutorial's theme step, `internal/theme/README.md`, and the root README — both languages throughout. The root README's docs index was also brought back in sync: it had been missing `make-a-release` and `versioning` since the previous session. No package added, so the diagram is unchanged and still parses.
349
350## 2026-09-01 — Migrated onto turbo-core, and a second editor exists
351
352- **Goal**: ticket 0001 in the `turbo-editors` parent — extract the code shared with a future Turbo Rust into a versioned library, and build that second editor. Options chosen by the user before implementation: the library holds `app` too, so an editor is a command, a profile and a scanner; the language scanner lives in its own editor; the editors depend on the library with `require` plus a committed `replace`; per-editor configuration directories rather than a shared `.turbo/`; the Rust toolchain menu is `Rus~t~` on Alt-T.
353- **Changes here**: `internal/*` deleted — fourteen packages moved to `codeberg.org/turbo-editors/turbo-core` and made public. New `internal/golang`: the profile, the Go scanner (recovered from `internal/syntax/scan.go` and ported onto the library's exported `Class`, `LineIndex` and `Register`), and the three starter templates. `main.go` rewritten around `golang.Profile()`; `moduleRoot`/`projectRoot` replaced by `app.ProjectRoot`. `Makefile` and `scripts/install.sh` stamp `turbo-core/version` instead of `internal/version`.
354- **Nothing about the editor's behaviour changed.** The menus, the keys, the themes, the file formats and the environment variables are what they were. `TURBO_GO_THEME_DIR` still works, deliberately: it is derived from the profile's slug precisely so a released name is preserved.
355- **Decisions**: the Go scanner stays here rather than in the library, so that "what does this editor register?" is the first question about a new editor — a `.rs` file therefore opens as plain text here. `golang.Register()` is called from `main` explicitly, not from an `init`, so the fact is a line somebody can read. The `replace` is committed rather than hidden in a gitignored `go.work`, so it is visible in the diff and so three repositories side by side build with nothing published.
356- **Tests**: the whole existing suite passes unchanged. Twelve Go-scanner tests came back here from the library, along with the three templates' content tests and both real-gopls tests — the ones that are about *Go*, and that the library has no language server of its own to run. New `internal/golang/editor_test.go` builds a whole Turbo Go on a simulated terminal through the library's public API and checks that Register was called, that the profile reached the menu bar, and that a `.go` file comes out coloured; a bug where `main` forgot to register Go would pass every test in turbo-core.
357- **One pre-existing test was fragile and is fixed.** `TestTheMakefileHandsOutTheFlagsThatStampABuild` asserted the stamped binary did not say `devel`, which is only true in a checkout that has tags. It now compares against `make version`, so it holds in a fresh clone too — and the comparison strips the leading `v`, because `internal/version` does.
358- **Quality**: PASS. 0 errors, 0 warnings, 0 smells, complexity 1592 → 37. The fall is the code moving, not anything being simplified; turbo-core carries 1584 of it.
359- **Docs**: `explanation/architecture.md` rewritten in both languages around the split, with a table of what moved where; `colouring-and-completion.md` updated to say which scanners are shared; every `internal/version` path corrected in `reference/versioning.md`, `how-to/make-a-release.md` and `how-to/run-the-tests.md`; the root README given a "Where the code is" section; `docs/diagrams/packages.drawio` regenerated from `go list` and verified against it edge for edge.
360- **A pre-existing documentation defect was found by driving the real binary**, and fixed: the tutorial said "press ↓ five times to reach turbo-dark" when the Theme dialog has always opened *on the current theme*, so it was one press. Both languages now say one, and say why.
361- **Two themes were added** at the user's request during the same session — `catppuccin-frappe` and `catppuccin-latte`, in turbo-core — which is what made the tutorial's arrow count worth checking rather than merely updating.
362
363## 2026-09-01 — Tool parameters, from turbo-core
364
365- **Goal**: part of the same request as turbo-core's entry of this date — a tool whose command needs a value must be able to ask for it. The feature is the library's; what changed here is the starter file people are given.
366- **Changes**: `internal/golang/templates.go` — the tools template's comments now teach `{{label}}` and `{{label...}}`, with an example for THISgolang and the warning about single braces. `install_test.go` — one test asserted before checking whether it was in a git checkout at all, so it failed in a tree with no `.git` where `unknown` is the correct answer.
367- **Decisions**: the examples go in the **comments**, not as a sixth tool. The five starter commands are what a project runs before it commits; `go mod init` is a different kind of thing, and adding it would change what `Create tools file` gives everybody in order to demonstrate a syntax.
368- **Tests**: 2 in `internal/golang/templates_test.go` — the created file teaches the syntax, and none of the five starter commands accidentally became parameterised by the prose around them.
369- **Quality**: PASS. 0/0/0, complexity 37 — unchanged; the change is comments and a test.
370- **Docs**: a section in `reference/go-tools.md`, one in `how-to/run-go-commands.md` and one in `explanation/go-tools.md`, both languages.
371
372## 2026-09-01 — Released as v0.2.2
373
374- **Goal**: the user committed and released everything and asked for the record to be brought up to date. This entry is what was verified, not what was intended.
375- **Verified from the repository and the Codeberg API**: **v0.2.2** at `d64410c`, which is exactly HEAD, with a release page. Working tree clean, on `main`. The tag is the fourth for this editor and the first since it moved onto the library.
376- **The dependency is the published library**: `require codeberg.org/turbo-editors/turbo-core v0.1.0` with no active `replace`, and a `go.sum` whose checksum matches sum.golang.org. A clean clone now builds without turbo-core beside it, which is what the whole extraction was for.
377- **One wart, left alone deliberately**: the old replace block is commented out rather than deleted, and its comment still says "drop it once the version above is tagged and published" — which is done. It sits inside a released commit, so it was written down rather than changed.
378- **Nothing was built or changed in this entry** — no code, no tests, no docs. The suite and the gate were last measured at the previous entry and are unchanged.
379
380## 2026-09-01 — Dockerfile, compose, YAML and XML colouring (ticket 8)
381
382- **Goal**: ticket 8 — "add syntax for Dockerfile, compose file, yaml, xml". The scanners themselves belong in turbo-core; this repository's part was to use them and to say so.
383- **Changes**: `internal/golang/templates.go` — the snippets template's `languages` comment now lists the nine names this editor knows. `go.mod` requires `turbo-core v0.2.0`. Documentation: the YAML, XML and Dockerfile sections in `docs/{en,fr}/reference/languages.md` with the recognition and class tables brought up to date, and the language counts corrected in the architecture and colouring explanations, both READMEs, and the snippets references.
384- **Decisions**: none taken here — the three that matter (a compose file is just YAML, XML gets its own scanner for CDATA's sake, `Filenames` matches the stem) were taken in turbo-core and are recorded there.
385- **Tests**: `TestTheCreatedSnippetsFileListsEveryLanguageTheEditorKnows` iterates `syntax.Registered()` rather than a hardcoded list, so the template cannot fall behind the registry again. Falsified by removing a name from the template.
386- **A stale claim found while sweeping**: the reference said themes were "the three shipped themes" when eight ship, and turbo-rust's English snippets reference listed `go` where it meant `rust`. Both fixed.
387- **Quality**: PASS, 0 errors / 0 warnings / 0 smells, complexity unchanged.
388- **Verified in a real pty**: a `Dockerfile`, a `compose.yaml` and a `pom.xml` opened in the built binary and coloured, with a CDATA section's contents arriving as a string rather than as markup.
389- **Blocked on a release**: this branch does not build until turbo-core v0.2.0 is tagged and published.
390
391## 2026-09-01 — The build checks the version it stamped
392
393- **Goal**: the user asked that the build verify it really embeds the right version number.
394- **Changes**: new `scripts/check-version.sh`, called by `make build` after linking, by `scripts/install.sh` on the staged binary **before** the install, and by `03-build-releases.sh` on the one asset this machine can run. The release script's own `grep -qF` check was replaced by it.
395- **The failure it catches**: a linker stamp is a string, and a wrong one is not an error. `-X` naming a symbol that does not exist links happily and stamps nothing; the binary then reports whatever Go build info says — `devel`, on a binary attached to a release. Reproduced by hand: `make build LDFLAGS="-X '….version.stampX=v9.9.9'"` linked cleanly and reported `0.2.2+dirty`, and now fails the build.
396- **Decisions**: the version comparison is an **equality**, not a search — `0.2.0` is a substring of `10.2.0` and of a commit hash that contains it, and a stamp that is nearly right is the case worth catching. The check runs **before** the install, so a binary that cannot name itself never replaces one that can. With no version to expect — a build outside a git checkout — the only claim left is that the number is not `unknown`.
397- **Tests**: 8 in a new `version_check_test.go`, driving the script against binaries built for the purpose. Three were falsified: the wiring in the Makefile, the ordering in the installer, and the substring case.
398- **Verified for real**: `make build`, `scripts/install.sh --prefix $(mktemp -d)`, and a deliberately misspelt `-X`.
399- **Docs**: a "Checked at build time" section in `docs/{en,fr}/reference/versioning.md`.
400- **Quality**: PASS 0/0/0, complexity unchanged.
401
402## 2026-09-01 — Tickets 9 to 14: autosave on in a created settings file
403
404- **Goal**: tickets 9–14. Only ticket 9 is editor-side; the other five are turbo-core's and reach Turbo Go through the library.
405- **Changes**: `internal/golang/templates.go` — the settings template now writes `autosave = true`, with the reason in the comment above it. `.gitignore` gained `go.work`.
406- **Decision**: the template, **not** `settings.Default()`. A project that has created a settings file has said what it wants, and the file is the visible, editable place to say otherwise. Turning the library default on would mean the editor writing to disk in any directory it is started in, which is a different and much larger claim; the user was asked and chose the narrower one.
407- **Tests**: `TestTheCreatedSettingsFileTurnsAutosaveOn` loads the created file rather than grepping it, and `TestAProjectWithNoSettingsFileStillDoesNotAutosave` holds the other half of the decision. The first was falsified by putting `false` back.
408- **Docs**: the settings reference gained a "When a change takes effect" section; the menus reference now states the enabled condition of all six create/open items; the tools and snippets references gained their `Open …` rows and lost "a project that already has one is opened unchanged"; `configure-a-project.md` was rewritten around autosave already being on; `run-the-tests.md` gained a section on testing against an unreleased turbo-core with `go work`. EN and FR throughout.
409- **Quality**: PASS 0/0/0, complexity unchanged.
410- **Verified in a real pty**: all six menu items flipping between available and greyed, and the created settings file holding `autosave = true`.
411- **Note**: this branch builds and passes against the published `turbo-core v0.2.0`. The other five tickets only become visible once turbo-core v0.3.0 is released and the `require` here is bumped.
412
413## 2026-09-02 — Code navigation: documentation only
414
415- **Goal**: the Code menu and the eight questions it puts to the language server. All the code is turbo-core's; Turbo Go changes only by describing it.
416- **Changes**: a new `docs/{en,fr}/how-to/ask-about-code.md`; the **Code** section in the menus reference, with Describe symbol and Go to definition removed from Run and Search; `Shift-F12` and `Ctrl-T` in the keyboard reference; a "Nine questions, one connection" section in the colouring-and-completion explanation. EN and FR throughout.
417- **Decision**: a **separate** guide rather than an extension of `navigate-code.md`. That page answers "how do I get to the piece of code I am looking for" — searching, line numbers, windows. This one answers "what does this name mean" — a different need, so a different page, with the old one linking to it.
418- **Docs traps met**: the new guide was first written *over* `navigate-code.md` and had to be restored from git. And the two moved menu items had to be deleted from Run and Search in **four** files, not two — the French tables are separate text.
419- **Quality**: PASS 0/0/0, complexity unchanged.
420- **Note**: this branch builds against the published `turbo-core v0.3.0`. Nothing here needs v0.4.0 to compile; the menu it documents appears once that is released and the `require` is bumped.
421- **Follow-up the same day**: the user asked whether the LSP features were documented for users. They were — `how-to/ask-about-code.md`, EN and FR, both editors — but the neighbouring `enable-completion.md` still had a "what else the server gives you" section listing three keys and no mention of the Code menu, Problems, or the gutter marks. Fixed in all four files. That is the "adapting is not substituting" trap from the `turbo-new-editor` skill, met on a page I had not thought to re-read: **a new feature makes its neighbours stale, and the neighbours are where a user already is.**
422
423## 2026-09-02 — Ticket 19: better code editing, documentation only
424
425- **Goal**: ticket 19 — double-click to select a word, insert line, delete line. All the code is turbo-core's; this repository documents it.
426- **Changes**: the keyboard and menus references in EN and FR, and a "Select and edit whole lines" section in `how-to/navigate-code.md`.
427- **The one thing to notice**: **redo is `Ctrl-R` now, not `Ctrl-Y`**`Ctrl-Y` deletes a line, as it did in Turbo C. That is a key changing under people who had learnt it, so it is stated in the menus reference rather than only in the table of keys.
428- **Quality**: PASS 0/0/0, complexity unchanged.
429
430## 2026-09-02 — Starter templates moved out of the source into embedded files
431
432- **Goal**: the user asked for the three starter templates to live in three files in `internal/golang/` and be embedded into the binary, instead of Go constants in `templates.go`. Extended to both editors at their choice.
433- **Changes**: `settings.toml.tmpl`, `snippets.toml.tmpl` and `tools.toml.tmpl` beside the code; `templates.go` reduced to three `//go:embed` declarations. `profile.Templates` is unchanged — it takes strings, and an embedded variable is one, so turbo-core needed nothing.
434- **Decisions**: **`.tmpl`, not `.toml`**, put to the user with the measurement behind it — `settings.toml.tmpl` holds `theme = %q`, which `tomllib` rejects, so naming it `settings.toml` would be a claim it cannot meet: a linter would reject it and the editor would colour it as TOML and draw it as broken. The snippets and tools templates *are* valid TOML (their verbs sit in comments), but all three take the suffix so the set is consistent. **The user accepted that the editor will not colour `.tmpl` files.**
435- **Method**: the constants were **evaluated, not cut out of the source** — each is a concatenation of a raw string with a quoted one, because a raw string cannot contain the backtick in `\`turbo-go -list-themes\``. A throwaway test wrote the three files from the constants themselves, then was deleted.
436- **A guard added for a risk this refactoring created**: the format verbs no longer sit next to the `profile.Templates` contract that documents them, so three tests now count the verbs per file, check none is empty, and fill each template asserting no `%!` marker comes out — Go writes `%!q(MISSING)` into the output rather than failing, so a wrong count produces a starter file that is written, opened, and wrong. All three falsified.
437- **A verification that went stale under me.** I compared the six new files against HEAD byte for byte and they matched — and then `turbo-go/internal/golang/snippets.toml.tmpl` was overwritten with the contents of the playground's own `bin/.turbo-go/snippets.toml`, which a test caught. I could not attribute the overwrite. Restored from HEAD's evaluated constants and re-verified **after** the last step rather than in the middle. The lesson is the ordering: verify at the end, not when convenient.
438- **Quality**: PASS 0/0/0 in both, complexity unchanged.
439- **Docs**: turbo-core's `how-to/write-the-starter-files.md` gained a section on keeping them in files, in EN and FR; both architecture explanations list the new files; the `turbo-new-editor` skill's step 3 now prescribes this shape.
440
441## 2026-09-03 — Family count corrected, and three stale documentation claims found by a pty run
442
443- **Documentation only; no code changed.** `turbo-python` joined the family, so `docs/{en,fr}/explanation/architecture.md`'s "both editors use them unchanged" and "a change to a menu now affects both editors at once" became false. Changed to "every editor".
444- **Three claims were stale because the library grew a Code menu, and nothing noticed.** Driving this editor's own binary in a pty gives the bar as `File Edit Search Run Code Options Window Snippets Go Help`. The tutorial listed `File Edit Search Run Options Window Help` — missing Code, Snippets **and this editor's own Go menu** — and told the reader to press `→` **four** times to reach Options, which has been five since the Code menu shipped. `reference/menus.md`'s opening sentence omitted Code as well. Fixed in EN and FR.
445- **The lesson, and it is not this editor's alone**: a library that grows a menu makes every editor's tutorial wrong in a way no test sees, because a tutorial is prose about a screen. The counts are worth re-reading off a terminal after any change to the bar.
446- **This repository's suite was already red at `HEAD`** — four tests in `internal/golang/templates_test.go` still assert a five-tool starter file that deliberately grew to eight. Verified pre-existing by stashing and re-running; not caused here and not fixed here. Detail in the handoff.
447- Not committed.
448
449## 2026-09-09 (later) — the theme list gained three entries
450
451- **Goal**: none of its own. turbo-core gained `monochrome-light`, `darcula` and `intellij-light`, and renamed `monochrome` to `monochrome-dark`; this repository's documentation had to follow. Eleven themes ship now.
452- **Changes**: `docs/{en,fr}/reference/themes.md` — the embedded list, three new table rows, and a new "a name a theme used to answer to" section saying `monochrome` still loads; `docs/{en,fr}/how-to/write-a-theme.md` — the inherit-from advice and the shipped-theme count; `README.md`'s themes bullet. No code change.
453- **History was left alone**: "comments were the dimmest colour in six of the eight shipped themes" in `write-a-theme.md` is a true sentence about when that rule was written.
454- **Not yet true of the binary.** This repository pins turbo-core v0.4.2, which ships eight themes. The documentation is ahead until turbo-core is tagged and the `go.mod` here is bumped — see turbo-core's handoff of the same date.
455
456## 2026-09-15 — ACP agent windows, and four stale tests fixed on the way in
457
458- **Goal**: the user asked for Agent Client Protocol support — an agent window with a typing area and a rendered conversation, code coloured, several agents configured in TOML in `acp.toml`, one window per agent. The library owns the window, the menu and the event loop, so the feature itself went into turbo-core; see that repository's history for the same date.
459- **What is in *this* repository**: `internal/golang/acp.toml.tmpl`, embedded beside the other three starter files and wired into `profile.Templates.Agents`. That is all the code. Plus six documentation pages (EN + FR: a how-to, a reference, an explanation) and their index entries.
460- **Step zero was fixing a suite that had been red at `HEAD` since 2026-09-03.** Four assertions in `templates_test.go` still described a five-tool starter file that had deliberately grown to eight. The template was right and the tests had drifted, exactly as that handoff predicted. `…HoldsTheFiveGoCommands` now names all eight and fails on a ninth it does not know about; `TestRunIsTheOneToolInATerminal` became `TestEachToolGoesWhereItsOwnOutputBelongs` with the real map; the tabs test targets `main` rather than an `if err != nil` snippet that no longer exists; and `…StillLoadsWithItsPlaceholderExamples` was inverted — two starter commands now take a value on purpose, and what it checks is that the braces in the file's *comments* did not become tools. All four were falsified before being accepted.
461- **Decisions**: the starter file's example agent is `docker agent`, because that is what a Go developer is most likely to already have; the template takes two blanks (the project directory, and the user-level path a comment names) and a test fills it and asserts no `%!` marker comes out, since Go writes `%!s(MISSING)` into the output rather than failing.
462- **Tests**: 4 new in `internal/golang`, 4 corrected. `make test` green — for the first time since 2026-09-03.
463- **Quality**: PASS 0/0/0, complexity 37, unchanged.
464- **Docs**: `docs/{en,fr}/how-to/talk-to-an-agent.md`, `reference/acp.md`, `explanation/agent-windows.md`, both `README.md` indexes. The drawio diagram was checked against `go list` and needed no change — this repository's import graph did not move.
465- **The documentation was written before the code, at the user's request**, with a status banner on every page saying so. Two of its claims were false by the time the code existed (`syntax.error` is not a class — `diagnostic.error` is the key; and the output cap is per entry rather than 10 000 lines), and the refusal messages it quoted were not the ones the loader emits. All corrected against the running code before the banners came off.
466- Not committed.
467
468## 2026-09-15 (later) — the spinner and copying, documented
469
470- **Documentation only in this repository**; the code is turbo-core's. The user asked for a spinner beside *thinking* and for a way to copy text out of a conversation, and both landed in the library.
471- **Changes**: `docs/{en,fr}/how-to/talk-to-an-agent.md` gained "Take something out of the conversation" — the key table, what `Ctrl-C` copies with nothing selected, and the two clipboards. `reference/acp.md` gained per-pane key tables, a Copying section, the `editor.selection` row and a paragraph on the spinner. `explanation/agent-windows.md` gained three sections: why copying goes to two clipboards, why copying with nothing selected takes a whole block, and why the spinner is drawn from the clock.
472- **Verified against the running binary**, not written from the code: the spinner was captured turning through ten distinct frames in a pty, and the copy was checked by base64-decoding the OSC 52 payload off the wire — which is how the editor's own defect (the speaker's label copied with the code) was found.
473- **Quality**: PASS 0/0/0, unchanged.
474- Not committed.
475
476## 2026-09-15 (night) — slash commands and `@` mentions, documented
477
478- **Documentation and the starter file only in this repository**; the code is turbo-core's (see its `.memory/` of the same date). The user asked for the ACP changes that let an agent's commands be discovered the way Zed discovers them, then for `@` as a file selector.
479- **Changes**: `docs/{en,fr}/reference/acp.md` — seven key rows for the list, a **Commands and mentions** section, the `session/prompt` and `available_commands_update` rows, the Limits bullet; `docs/{en,fr}/how-to/talk-to-an-agent.md` — "Use the agent's own commands" and "Point the agent at a file"; `docs/{en,fr}/explanation/agent-windows.md` — the "left out" bullet narrowed to images, two sections appended; the embedded `acp.toml.tmpl` — two key lines. Applied by one script across the five editors with an exactly-once anchor check.
480- **Tests**: `go test ./...` green (the template change is a comment; the tests that read the created file still pass).
481- **Ahead of the binary**: this repository pins turbo-core v0.7.0, which has none of this. The pages are true once turbo-core is tagged and the pin moved.
482- Not committed.
483- **Later the same night**: `.turbo-go/acp.toml` gained the user's `mini-me (llama.cpp)` agent (`mm -acp`, `AGENT_CONFIG` env). Loads as two agents; not opened here, `mm` lives on the user's Mac.
484
485## 2026-09-16 — the trace variable and a troubleshooting bullet, documented
486
487- turbo-core gained `TURBO_ACP_TRACE=<file>` and an "update this editor could not read" line in Agent status, because the user saw no `/` commands from their own agent and nothing on screen could say why. Documented here EN + FR: a bullet in the how-to's Variants, a section in `reference/acp.md`. Docs only; not committed.
488
489## 2026-09-17 — documentation: terminal windows and tools on Windows
490
491- **Asked**: nothing of this repository directly. turbo-core gained pseudo-console (ConPTY) terminal windows and a per-platform tools shell (cmd.exe on Windows); the pages here that said "Linux and macOS" or `/bin/sh -c` went false the moment that landed.
492- **Changes** (EN + FR): `README.md` (terminal windows: Linux, macOS and Windows), `docs/*/reference/terminal.md` (shell row, controlling-terminal row, platform table, error row), `docs/*/explanation/terminal-windows.md` (the Windows section rewritten: a pseudo-console and why it is a file of its own, built and vetted but not yet run), `docs/*/how-to/use-a-terminal.md` (`%COMSPEC%`, the five things to try first on Windows), `docs/*/reference/go-tools.md` (shell row, cmd.exe's globs and `;`, error row), `docs/*/explanation/go-tools.md` (`cmd.exe /S /C`). Applied by one script across the six editors, one anchor per file.
493- **Not changed**: code, `go.mod`. **The documentation is ahead of the binary** until turbo-core is tagged (v0.9.0) and re-pinned here; the feature has never been run on Windows by anyone.
494- **Tests**: none affected — documentation only.
495
496## 2026-09-18 — the "Untitled window has no LSP" fix: docs variant added, fix is turbo-core's
497
498- **Origin**: the defect was reported against this editor — "first launch: no LSP; save, quit, relaunch: works", the window having started Untitled — and diagnosed then fixed in turbo-core, where the save path lives.
499- **Asked**: propagate to every editor the fix made in turbo-core the same day — saving now announces a document the server does not know (a window that started Untitled gets LSP from its first save), and Save As under a new name closes the old document. See turbo-core's `.memory/history.md` of 2026-09-18 for the defect and the fix.
500- **Changes here**: `docs/{en,fr}/how-to/enable-completion.md` gain one variant — completion in a window that started without a name works from its first save, no relaunch needed. No code in this repository is involved.
501- **Quality**: gate not re-run — a Markdown-only change; the gate measures the Go code.
502- **Docs ahead of the binary**: the behaviour arrives only when turbo-core is tagged and this editor re-pinned. Not committed.
503
504## 2026-09-19 — moved to Rickub: turbo-core v1.0.0 re-pinned, releases published by a workflow
505
506- **Asked**: turbo-core had been moved to `rickub.com` and published as v1.0.0 with a Release workflow; do the same migration here — add the GitHub Action, update `01-release.tag.sh` and if need be `02`, drop `04` which the workflow makes unnecessary, and keep `03-build-releases.sh` runnable by hand.
507- **Changes, module**: `codeberg.org/turbo-editors``rickub.com/turbo-editors` in `go.mod`, every `.go` file, `Makefile` (`VERSION_PKG`), `scripts/install.sh`, `README.md`, `docs/{en,fr}` (the two turbo-core deep links also went from Codeberg's `src/branch/main/` to `blob/main/`). `require rickub.com/turbo-editors/turbo-core v1.0.0`; `go mod tidy` with `GOWORK=off` rewrote `go.sum` from the proxy. The proxy also lists a `rickub.com/…/turbo-core v0.9.0`, but its `go.mod` still declares the Codeberg path, so v1.0.0 is the first version this module *can* require.
508- **Changes, release tooling**: `.github/workflows/release.yml` (new, modelled on turbo-core's: tag push `v*`, `contents: write`, `go test` with `TURBO_GO_RELEASING=1`, `./03-build-releases.sh "${GITHUB_REF_NAME}"`, notes from the tag message + `go install` line + docs at the tag + checksums, run artifact, `softprops/action-gh-release@v2` attaching `turbo-go-*`, `SHA256SUMS`, `README.md`). `01-release.tag.sh` rewritten on turbo-core's: requires `release.env`, validates `TAG`, runs `make check` under `TURBO_GO_RELEASING=1`, refuses a taken tag (bump, never move), refuses a `replace`, pushes the current branch (not a hardcoded `main`) before tagging, no token file. `03-build-releases.sh`: tag from `$1` with `release.env` optional and `ABOUT` defaulting to `Turbo Go ${TAG}`, tag format check, `replace` check, `rm -rf release/${TAG}` before building, README gains the `go install` line, the closing hint no longer points at 04. **`02-release.publish.sh` and `04-release.upload-binaries.sh` deleted.** `release.env` comments rewritten; `OWNER`/`REPO` dropped (nothing reads them).
509- **Tests**: `release_test.go` — the push assertion follows the new `git push origin "$(git rev-parse …)"`; new: `01` runs `make check`, refuses a `replace`, `go.mod` has none, no script reads a token and 02/04 are gone, `01` **run for real** twice against a throwaway bare remote (publishes; refuses the second time with "already exists"), `03` takes the tag from the command line and refuses `v0.o.0` (run for real, script alone in an empty dir), `03` no longer hands off to 04, and seven workflow assertions (trigger, `contents: write`, uses `./03-build-releases.sh`, attaches with `fail_on_unmatched_files`, links docs at the tag, no `secrets.`, sets `TURBO_GO_RELEASING`). The helper had to be `runOrFail`: `main.go` already owns `run`. Copy of the module for the throwaway clone leaves out `.git`, `bin`, `release` (489 MB of old binaries), `kits`, `demo`, `*.env` and `go.work*`; children run with `GOWORK=off` so the clone builds against the published library. Suite green in ~10 s with `GOWORK=off`.
510- **Verified by hand**: `GOWORK=off ./03-build-releases.sh v0.0.1-test` → five binaries, host binary reports `0.0.1-test`, `sha256sum -c SHA256SUMS` all OK, README as expected; directory removed afterwards. `01` in a hand-made throwaway clone: `make check` ran (fmt, vet, test), root commit, push, tag on the bare remote.
511- **Not done**: nothing committed or pushed — the repository has no commit yet and `origin` is unreachable from this sandbox (no SSH). The workflow has not run on Rickub: it is written against the same platform facts turbo-core's is, and turbo-core's has run. `docs/{en,fr}/reference/versioning.md` still describes `03` accurately and was left alone.
512
513## 2026-09-19 (later) — `03-build-releases.sh` renamed `02-build-releases.sh`
514
515- **Asked**: rename the build script, now that `02` and `04` are gone and the numbering had a hole.
516- **Changes**: `git mv`-equivalent rename; every reference rewritten — `01-release.tag.sh`, `.github/workflows/release.yml`, `Makefile` (the `ldflags` target's comment), `release_test.go`, `release.env`, `docs/{en,fr}/how-to/make-a-release.md`, `docs/{en,fr}/reference/versioning.md`, `.memory/summary.md`. Older history entries and handoffs keep the old name, as history does.
517- **Tests**: `GOWORK=off make check` green.