| 🛟 Updated. 28d5985 k33g 11h ago | 1 | # History |
| 2 | |
| 3 | *Append only. One dated entry per session. Never rewrite or delete an entry, including your own.* |
| 4 | |
| 5 | ## 2026-09-01 — Extracted from turbo-go |
| 6 | |
| 7 | - **Goal**: ticket 0001 in `turbo-editors` — extract the code common to Turbo Go and a future Turbo Rust into a shared, versioned library. This repository is the result. Options chosen by the user up front: 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 it with `require` plus a committed `replace`; per-editor configuration directories rather than a shared one. |
| 8 | - **Changes**: fourteen packages moved from `turbo-go/internal/*` to the root of this module and made public, plus two new ones. `profile` is new and is the seam. `syntax` gained a string `Language`, a `Definition`/`Register` extension point, and an exported scanner toolkit, and lost the Go scanner. `theme` took a `userDir string` in place of reading an environment variable itself. `settings`, `snippets`, `tools` and `lsp` took a `profile.Profile`. `app` took one too, gained `ProjectRoot`, and exported `Tick`, `Handle` and `ActiveView`. `lsp.NewClient` took the editor's name. |
| 9 | - **Decisions**: a struct rather than an interface for the profile, because a third editor should be able to fill in a literal from the reference rather than read somebody else's implementation. The language an editor is *for* stays out of the library, so that "what does it register?" remains the first question about a new editor. `syntax.Register` is package-level state, following `image.RegisterFormat`, because the alternative threads a registry through every call site that asks what language a file is. `theme` takes a directory while everything else takes a profile — deliberately inconsistent, because it needs exactly one thing. |
| 10 | - **Tests**: every suite moved with its package and was reworked onto a **fictional editor called Turbo Test**, with its own slug, menu and templates. That is the point: a test in the library that expected the Go menu would be testing an editor's choices from inside the library those choices are made outside of. New: `profile` (8 tests), `app.ProjectRoot` (6), the `syntax` registry (6), and three `lsp.FindServer` tests covering the profile's search directories. Moved out to turbo-go: the Go scanner's 12 tests, the three templates' content tests, and both real-gopls tests. |
| 11 | - **Two tests were falsified on purpose** before being trusted: a deliberately unreadable `syntax.comment` in a new theme produced the expected failure, and `TestRegisteredListsWhatThisPackageColours` was rewritten to assert presence rather than an exact count after an example in the same test binary registered a language of its own and made the count order-dependent. |
| 12 | - **Verified end to end**: the whole suite green and green under `-race`; the tutorial run verbatim in a throwaway module, producing a working Zig editor whose menu bar reads `File Edit Search Run Options Window Snippets Zig Help` and whose keywords, strings and numbers come out coloured in a real pty. |
| 13 | - **Quality**: PASS, no refactoring needed. 0 errors, 0 warnings, 0 smells, complexity 1584. |
| 14 | - **Docs**: a new documentation set, 12 pages × EN + FR — a tutorial that builds an editor, five how-to guides, four reference pages and two explanations — plus a `README.md` per package and a drawio diagram generated from `go list` and checked against it edge for edge (41 = 41, nothing stale). |
| 15 | |
| 16 | ## 2026-09-01 — Catppuccin Latte and Frappé |
| 17 | |
| 18 | - **Goal**: user request, mid-session — "ajoute un theme facon catppuccin/vscode la version frappé et la version latte". |
| 19 | - **Changes**: `theme/themes/catppuccin-frappe.toml` and `theme/themes/catppuccin-latte.toml`, each stating all 67 style keys. No Go code changed: themes are embedded with `//go:embed themes/*.toml`, so adding one is adding a file. |
| 20 | - **Decisions**: the published palettes are used **unchanged** — a theme called Catppuccin that is not those exact values is a different theme with a borrowed name. Catppuccin's own style guide is followed where it has an opinion (mauve keywords, green strings, peach numbers, blue functions, yellow types, overlay comments) and the palette's surface ladder does the rest. Latte needed a different *mapping* from Frappé rather than different numbers: its accents are far more saturated, because they have to carry against paper, and using Frappé's mapping with Latte's numbers gives a legible page and an illegible menu bar. |
| 21 | - **Tests**: no new test files — the five existing theme rules now run over eight themes instead of six. The rules were **falsified** for the new themes specifically: a `#e9ebf0` comment in Latte produced `syntax.comment: text is 6 from its background, want at least 64`, and a temporary test confirmed `Available()` really returns all eight, so nothing passed vacuously. |
| 22 | - **Verified on the wire** in a real pty, reading back the true colours: Frappé draws `fn` in `#ca9ee6`, a raw string in `#a6d189`, `println!` in `#e78284`, `3u8` in `#ef9f76` and a type in `#e5c890`; Latte the same mapping in `#8839ef`, `#40a02b`, `#d20f39`, `#fe640b` and `#df8e1d`. Those are the published values exactly. |
| 23 | - **A pre-existing documentation defect surfaced and was fixed.** Both editors' tutorials said "press ↓ five times to reach turbo-dark". Driving the real binary through a pty showed the Theme dialog opens **on the current theme** — `turbo-classic`, highlighted `97;44` while every other row is `30;47` — so it was always one press, not five. Adding two themes would have made a wrong number wronger; it now says one, and says why. |
| 24 | - **Quality**: PASS. 0/0/0, complexity 1584 — unchanged, the themes are data. |
| 25 | - **Docs**: the themes table and the embedded list in both editors' `reference/themes.md`, the inherit-from-a-similar-ground advice in `how-to/write-a-theme.md`, and the theme count in this library's README, package reference, what-belongs-here page and tutorial — all in both languages. |
| 26 | |
| 27 | ## 2026-09-01 — Tool parameters, and the release tooling |
| 28 | |
| 29 | - **Goal**: two things the user asked for together — a tool whose command needs a value (`go mod init <module>`) must be able to ask for it, and turbo-core must become versionable. They asked where the parameters belonged; the answer is here, because `tools` parses the file and `app` runs the command and neither knows what language it is editing, so putting it in an editor means writing it twice. |
| 30 | - **Options chosen by the user up front**: `{{label}}` placeholders only, with no new TOML keys; shell-quoted by default with a `{{label...}}` escape hatch for verbatim; and release tooling plus a `v0.1.0` tag rather than only one or the other. |
| 31 | - **Changes (parameters)**: new `tools/placeholder.go` — `Placeholder`, `Tool.Placeholders()`, `Tool.Fill()`, `ShellQuote`, and a load-time check wired into `check`. New `app.ParametersDialog` and `app.MaxParameterFields` in `app/dialogs.go`; `RunTool` split into `askForParameters` and `runFilledTool`, with `App.toolValues` remembering per tool for the session. `runCaptured` now takes the filled command, so the popup's title shows what actually ran. |
| 32 | - **Changes (release)**: `01-release.tag.sh`, a `version` target, and `docs/*/how-to/release-the-library.md` rewritten around the script. Both editors' starter tools templates gained a comment block teaching the syntax. |
| 33 | - **Decisions**: **double braces, not single** — `awk '{print $1}'` and `find . -exec rm {} +` are ordinary tools-file entries, and a falsification run showed exactly what single braces would do to the first (`awk ''''`). **Quoted by default** because the failure mode of raw substitution is silent. **Nothing persisted to disk**: the project's directory holds what the project decided. **Refused at load, not at run**, for an unclosed `{{`. A dialog that will not fit is **refused with a message** rather than drawn with its OK button off-screen. |
| 34 | - **A recursion, found by it hanging.** The release script runs `make check`, `make check` runs the suite, and the suite contains tests that run the release script. It hung the first time it was run — and it was not a test-only hazard: a real release would have recursed identically. The script now exports `TURBO_CORE_RELEASING` before calling make, and those two tests skip themselves when they see it. |
| 35 | - **Tests**: 18 in `tools/placeholder_test.go`, 13 in `app/parameters_test.go`, 10 in a new root `release_test.go` (two of which run the script for real against a throwaway bare remote), and 2 per editor for the templates. **Six were falsified before being trusted** — single braces, no quote escaping, no load-time check, RunTool ignoring placeholders, values not remembered, and the unfilled command being run — each producing the expected failure. |
| 36 | - **A latent test bug surfaced on the way**: `TestTheInstalledBinaryDoesNotReportAnUnknownVersion` 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. Fixed in both editors. |
| 37 | - **Verified end to end in a real pty**: `Alt-G`, Enter, a box titled *Echo* with a field labelled `who`, typing `world` → the popup reads `echo hello 'world' — ok` and prints `hello world`; a `{{words...}}` tool reaches the shell as `echo -n a b c`; and running the same tool twice opens the box already holding `again`. |
| 38 | - **Verified the release end state**: a local file:// module proxy carrying turbo-core `v0.1.0` was built, both editors were switched to `require` it with the `replace` dropped, and both suites pass. So dropping the directives is known to work. |
| 39 | - **Quality**: PASS in all three. 0 errors, 0 warnings, 0 smells; turbo-core complexity 1584 → 1610, the editors unchanged at 37 and 98. |
| 40 | - **Docs**: a reference section, a how-to section and an explanation section in each editor, both languages; the `app` reference and the release how-to in turbo-core, both languages; `tools/README.md` and `app/README.md`. The diagram was re-checked against `go list` and is unchanged — no package was added. |
| 41 | |
| 42 | ## 2026-09-01 — v0.1.0 published, and the release page script |
| 43 | |
| 44 | - **Goal**: the user ran `01-release.tag.sh` with `TAG=v0.1.0`, then asked for a script that creates the release itself, and what "drop the two replace directives" meant. |
| 45 | - **Changes**: new `02-release.publish.sh`; `docs/*/how-to/release-the-library.md` gained a step for it and had its "developing across the three repositories" section rewritten, since the directives are gone; both editors' `go.mod` lost their `replace` and their `go.sum` gained the module's checksum. |
| 46 | - **Decisions**: the script builds its JSON with **jq**, which both escapes the notes properly and sidesteps the `read -r -d ''` idiom that stops the editors' own `02` from taking `set -e`. It **tells the HTTP statuses apart** — 201, 409, 401/403, 404 — because the API's raw JSON leaves the reader to guess which of four problems this is. It takes `--dry-run`, which is the only way it can be exercised here: the real call publishes on somebody's behalf. There is **no 03 or 04**: those build and attach binaries, and a library's artefact is its tag. |
| 47 | - **The release-page links are absolute and pinned to the tag.** A release page is not inside the repository tree, so a relative `docs/en/README.md` 404s, and a link to the branch rots as the branch moves. |
| 48 | - **Two defects found by running it.** The tag guard conflated "the tag is not on origin" with "I could not reach origin" and refused a perfectly good release from a machine with no ssh key — `ls-remote` exits 2 for the first and 128 for the second, and it now says which. And `TestThePublishScriptBuildsItsJSONWithJq` failed **on the script's own comment**, which mentions the idiom it avoids; it reads code lines only now. |
| 49 | - **A checksum mismatch that was mine, not the release's.** Dropping the replaces gave `SECURITY ERROR … checksum mismatch` against sum.golang.org. The proxy's `v0.1.0` pointed at exactly the tagged commit; what differed was the local module cache, poisoned by the hand-made `file://` proxy zip an earlier verification had built. Clearing that one module put it right. Worth knowing before anybody concludes a release was tampered with. |
| 50 | - **Tests**: 8 more in `release_test.go` (18 total), two of which run the publish script's dry run against a throwaway copy — including one that checks a quote in the notes survives as data. One was falsified by putting the forbidden idiom back as code. |
| 51 | - **Quality**: PASS in all three, 0/0/0. |
| 52 | |
| 53 | ## 2026-09-01 — v0.1.0 released, and the family tagged |
| 54 | |
| 55 | - **Goal**: the user committed and released everything, and asked for the record to be brought up to date. This entry is what was verified rather than what was intended. |
| 56 | - **Verified from the repositories and the Codeberg API**, not from the session: turbo-core **v0.1.0** at `f1f0375` with a release page; turbo-go **v0.2.2** at `d64410c`, exactly its HEAD; turbo-rust **v0.1.0** at `2d5dbec`, exactly its HEAD and its first release ever. All three working trees clean, all three on `main`. |
| 57 | - **turbo-core's HEAD is one commit past its tag.** `bc4a464` — the publish script, its tests and the release how-to — landed after v0.1.0 was cut. No API changed, so no consumer is affected; the v0.1.0 release page's doc links are pinned to the tag and therefore show the older how-to. Recorded rather than fixed: moving a published tag is the one thing the release script exists to refuse. |
| 58 | - **Both editors depend on the published module.** `require codeberg.org/turbo-editors/turbo-core v0.1.0`, no active `replace`, `go.sum` carrying the checksum that matches sum.golang.org. |
| 59 | - **One wart left in both editors' `go.mod`**: the old replace block is commented out rather than deleted, and its text now says "drop it once the version above is tagged and published", which is already done. It is inside a released commit, so it was left alone and written down instead. |
| 60 | - **Nothing was built or changed in this entry** — no code, no tests, no docs. The suites and the gate were last measured at the previous entry and are unchanged. |
| 61 | |
| 62 | ## 2026-09-01 — Dockerfile, compose, YAML and XML colouring (ticket 8) |
| 63 | |
| 64 | - **Goal**: ticket 8 — "add syntax for Dockerfile, compose file, yaml, xml", in three tasks: the support in turbo-core, then its use in turbo-go, then in turbo-rust. |
| 65 | - **Changes (core)**: `syntax/yaml.go`, `syntax/yaml_block.go`, `syntax/yaml_key.go`, `syntax/dockerfile.go`, `syntax/xml.go`, and their three test files. `Definition` gained a `Filenames []string` field and `LanguageOf` a name step between the extension and the shebang. Three new `Language` constants and three `Register` calls; eight languages are now built in. |
| 66 | - **Changes (editors)**: both editors' snippets template lists the nine language names the editor now knows, with a new test that iterates `syntax.Registered()` so the list cannot fall behind the registry again. |
| 67 | - **Decisions**: **a compose file is just YAML** — a dialect would be Docker's schema, kept here, going stale. **XML gets its own scanner rather than borrowing HTML's**, for CDATA: HTML colours `<not>` inside a CDATA section as a tag, which is backwards in exactly the files that have one; the carry says *which* construct is open so a `-->` inside CDATA does not close it. **`Filenames` matches the stem as well as the whole name**, so `Dockerfile` answers for `Dockerfile.dev`; an all-extension name such as `.gitignore` has an empty stem and matches nothing, which is what stops the rule becoming a wildcard. |
| 68 | - **Two defects found by reading real output, not by a test.** `image: nginx:1.27` and `url: http://x` came out in three spans each: a YAML colon is a separator only when a space or the end of the line follows it. Then `golang:1.26` split at the colon while YAML kept `nginx:1.27` whole — the Dockerfile scanner needed `:` in its word runes for the same reason. Both now have a test that walks every column of the span. |
| 69 | - **Tests**: `yaml_test.go`, `dockerfile_test.go`, `xml_test.go`, plus `TestTheEightBuiltInLanguagesAreRegistered` and `TestEveryBuiltInLanguageActuallyColoursSomething` in `highlight_test.go`; 275 assertions pass in `syntax`. One name clash fixed — `TestABareAmpersandIsLeftAlone` already existed in `html_test.go`. |
| 70 | - **Quality**: first run FAILed with 4 smells — three `qlty:boolean-logic` and `qlty:file-complexity` on `yaml.go` at 52. Fixed by extracting the rune sets to named constants (`yamlFlowRunes`, `yamlNumberRunes`, `dockerfileWordRunes`) and splitting the YAML scanner across three files. Final: PASS 0/0/0 in all three; turbo-core complexity 1610 → 1718, the editors unchanged at 37 and 98. |
| 71 | - **Verified in a real pty in both editors**: a `Dockerfile`, a `compose.yaml` and a `pom.xml` opened and coloured, with the CDATA contents arriving as green (string) rather than as markup. |
| 72 | - **Docs**: the three new sections in each editor's `reference/languages.md` in both languages, with the recognition and class tables brought up to date; the name-lookup step added to turbo-core's `how-to/add-a-language.md` and the `syntax` reference in both languages; the language counts corrected across both editors' architecture and colouring explanations, READMEs and snippets references; `syntax/README.md`. The diagram was re-checked against `go list` — no package was added, so it is unchanged. |
| 73 | - **Left for the user**: turbo-core needs a **v0.2.0** tag and release before the editors' branches build, because `Definition.Filenames` and the three new constants are new public API. Both editors' `go.mod` now `require v0.2.0` with no active `replace`. |
| 74 | |
| 75 | ## 2026-09-01 — The About box named the wrong language |
| 76 | |
| 77 | - **Goal**: the user reported that Turbo Rust's About box talks about Go. |
| 78 | - **Changes**: `app/actions_view.go` — `aboutText` takes the language and `ShowAbout` passes `a.profile.Language`. The sentence was a literal `"A Turbo C-style editor for Go,"` left behind by the extraction: correct in turbo-go, drawn unchanged by every editor built on the library. `app/README.md` and `docs/*/reference/profile.md` say so now. |
| 79 | - **Tests**: `TestTheAboutTextNamesTheLanguageTheEditorIsFor`, falsified by putting the literal back — it reproduces the reported bug exactly. |
| 80 | - **Verified in a real pty**: Turbo Rust's About box reads "A Turbo C-style editor for Rust, / written in Go."; Turbo Go's still reads "for Go". The second line is right in both — both editors *are* written in Go. |
| 81 | - **Worth noting**: turbo-rust's own documentation already showed the corrected text. The docs were right and the code was wrong, which is why nobody caught it by reading. |
| 82 | - **Quality**: PASS 0/0/0, complexity unchanged at 1718. |
| 83 | |
| 84 | ## 2026-09-01 — Tickets 9 to 14: settings applied on save, symmetric project menus, readable comments |
| 85 | |
| 86 | - **Goal**: tickets 9–14. Four of them land here; ticket 9 is editor-side. |
| 87 | - **Ticket 12 — settings did not take effect until a restart.** `UseSettings` was called once, from `main`. `app/project.go` gained `reapplySettings`, run from a new `afterSave` that `App.save` and `App.writeQuietly` now share. It matches on the path rather than on `a.settingsPath`, so creating the file counts; only autosave is re-applied; a file that no longer parses says `Saved, but not applied:` and the old values stay. The status bar now says `Applied .turbo-go/settings.toml — autosave on (2s)`, because the report was "it does not work" and an invisible fix invites it again. |
| 88 | - **Tickets 11, 13, 14 — the three project files behave alike.** `projectFile` describes one of them; `openProjectFile`, `hasProjectFile` and `not` do the rest. Six menu items: create greyed once the file exists, open greyed until it does. New public API: `OpenTools`, `OpenSnippets`, `HasProjectTools`, `HasProjectSnippets`. Both branches of `createProjectFile` stay reachable from the API, since a caller that is not a menu has no greying-out. |
| 89 | - **Ticket 10 — comments were unreadable.** Measured before touching anything: `syntax.comment` was **the worst or near-worst reading pairing in six of the eight shipped themes**, and under 4.5:1 WCAG in every one. turbo-classic 4.05, borland-light 3.91, cappuccino 3.63, cobalt 3.62, monochrome 3.57, turbo-dark 3.25. All six raised past 4.5 while staying the quietest colour in their theme. The two Catppuccin themes (2.87, 2.83) are left alone: their palettes are published, which is a decision already in force. |
| 90 | - **Decisions**: **the theme is not re-applied on save** — Options ▸ Theme is the live path and already writes back here, and a `-theme` flag is the more explicit statement for its session. **The new contrast test covers comments only** — punctuation is quieter still in several themes and stays that way, being recognised by shape rather than read. **A blanket WCAG floor was rejected on evidence**: 84 of 432 theme/key pairings are under 4.5, most of them Catppuccin's published palette, so the honest scope is the one class the ticket named. |
| 91 | - **Two measures, both kept.** `channelDistance` answers "can the eye see these are two colours"; `contrastRatio` answers "can this be read". Turbo Classic's comments cleared the first by a mile — 128 against a floor of 64 — and failed the second. That is why nothing caught this for as long as it existed. |
| 92 | - **Tests**: 7 in `app/project_test.go` for the re-apply, 5 more for the menu pairs and the open actions, 2 in `editor/view_test.go` for contrast and for the exemption list. **All falsified**: removing the single `reapplySettings` call fails exactly 7; dropping either half of a menu pair fails the pair test; restoring `gray` fails turbo-classic at 4.05; naming an unshipped theme in the exemption list fails. Two first drafts passed vacuously and were rewritten. |
| 93 | - **Verified in a real pty, both editors**: all six menu items flipping between `30;47` (available) and `90;47` (greyed); the new comment colour arriving as `38;2;143;143;143`; the created settings file holding `autosave = true`; and `Applied .turbo-go/settings.toml — autosave on (2s)` on the status bar after `F2`. |
| 94 | - **Quality**: PASS 0/0/0 in all three; turbo-core complexity 1718 → 1724, the editors unchanged at 37 and 98. |
| 95 | - **Docs**: the app reference and `app/README.md`, `theme/README.md`, `editor/README.md`, `settings/README.md`, the starter-files how-to; and a new **`how-to/test-without-publishing.md`** in EN and FR, which the release how-to now points at instead of explaining `replace` itself. `go.work` gitignored in all three. |
| 96 | - **Also fixed, found while sweeping**: turbo-rust's French tools reference said its own menu items "restent dans Go". |
| 97 | |
| 98 | ## 2026-09-02 — Code navigation: eight more LSP questions, and the problems the server already sends |
| 99 | |
| 100 | - **Goal**: after a survey of what else the language server could answer, the user chose the largest of three scopes — navigation, symbol search and diagnostics in one cycle. |
| 101 | - **Changes (`lsp`)**: `References`, `Implementation`, `TypeDefinition` sharing one `locationRequest` with `Definition`; new `symbol.go` with `DocumentSymbols`, `WorkspaceSymbols`, `Symbol` and `SymbolKind`; the matching client capabilities. |
| 102 | - **Changes (`app`)**: `code.go` (the four location questions, `chooseLocation`, `locationLabels`), `symbols.go`, `problems.go`, a **Code** menu holding all eight items, `Shift-F12` and `Ctrl-T` wired, and the `Language` façade extended with five requests plus `AllDiagnostics`. |
| 103 | - **Changes (`editor`)**: `marks.go` — `Severity`, `SetMarks`, and the gutter glyph, drawn in the three `diagnostic.*` theme keys that had been defined in every shipped theme and drawn nowhere at all. |
| 104 | - **Decisions**: **one answer jumps, several list** — `GoToDefinition` used to take `locations[0]`, so an interface with four implementations sent you to one of them at random. **`editor` does not import `lsp`**; a mark is an `editor.Severity` and `app` translates. **The mark uses the gutter's separator column**, so it costs no layout, at the accepted price that hiding line numbers hides the marks. **The two symbol shapes are told apart by `selectionRange`, not `children`** — children are optional and the mistake is silent. **A symbol is located by its name's range**, not its declaration's. **Nothing needs a selection**: the protocol takes positions, so requiring one would be an invented step. |
| 105 | - **A pre-existing defect found by the pty run, not by any test.** The Problems window listed two errors and the gutter showed none: a server publishes **absolute** URIs and a buffer opened as `turbo-go main.go` holds the **relative** path, so the two never met. The status bar had been failing the same way since long before marks existed — invisibly, because no error to show and no error findable look identical. Fixed with `diagnosticKey`; every unit test had passed because every unit test opened its file by an absolute path. |
| 106 | - **A shortcut withdrawn on evidence**: *Symbol in file* was to be `Ctrl-Shift-O`, and a terminal cannot tell that from `Ctrl-O`. It ships with no key rather than one that does nothing. |
| 107 | - **Tests**: 12 in `lsp` (four requests × three answer shapes, `includeDeclaration`, the declared capabilities), 11 in `lsp/symbol_test.go` (the three decoding decisions each have their own), 20 in `app/code_test.go`, 3 in `app/diagnostics_test.go`, 7 in `editor/marks_test.go`. **Every one falsified.** One passed vacuously — a zero severity draws NUL, which a simulated screen shows as a blank — and was rewritten to assert on `markOf` instead. |
| 108 | - **Verified against a real gopls**, installed for the purpose: `Implementations (2)` listing `French` and `English` with their line text; `References (2)`; `Symbols in file (6)` with kind tags; `Problems (2)` on a file that does not compile; and `×` marks on lines 6 and 7 drawn in `91;44;1` — turbo-classic's `diagnostic.error`. |
| 109 | - **Quality**: PASS 0/0/0 in all three; turbo-core complexity 1724 → 1802, the editors unchanged. |
| 110 | - **Docs**: `lsp/README.md`, `app/README.md`, `editor/README.md`, the app reference in EN+FR; in both editors a new `how-to/ask-about-code.md` in EN+FR, the Code menu in the menus reference, the two new keys in the keyboard reference, and a "Nine questions, one connection" section in the colouring-and-completion explanation. |
| 111 | - **A near miss worth recording**: the new how-to was first written over the existing `navigate-code.md`, which is a different guide about moving around a file. Restored from git and written under its own name, with the old page linking to it. |
| 112 | - **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.** |
| 113 | |
| 114 | ## 2026-09-02 — Ticket 19: better code editing |
| 115 | |
| 116 | - **Goal**: ticket 19 — double-click to select a word, insert line, delete line. Stacked on `feature/code-navigation` at the user's choice, so one release carries both. |
| 117 | - **Changes (`buffer`)**: `InsertLineAbove`, `DeleteLine` and `SelectWord`, all through `ReplaceRange` so each is one undo step. |
| 118 | - **Changes (`editor`)**: `click.go` — click counting against an injectable clock; `InsertLine` and `DeleteLine` on the view; `Ctrl-N`, `Ctrl-Y` and `Ctrl-R` in the key switch. |
| 119 | - **Changes (`app`)**: `InsertLine` and `DeleteLine`, and two new Edit-menu items. |
| 120 | - **Decisions**: **`Ctrl-Y` deletes, redo moves to `Ctrl-R`** — put to the user, who chose the Turbo C binding over the existing habit. `Ctrl-Shift-Z` was ruled out on the terminal's own limits, the same way `Ctrl-Shift-O` was last cycle. **Insert goes above with the cursor staying on its text**, also the user's choice and also Turbo C's. **The new line is blank**, not indented. **A double click off a word selects nothing.** **After a double click a drag extends from the word's start, by character** — not word-by-word, which was out of scope, but better than the word collapsing to one character at the first twitch. |
| 121 | - **Tests**: 12 in `buffer/line_test.go`, 10 in `editor/click_test.go`, 3 more in `editor/events_test.go`, 2 in `app`. Falsified throughout. |
| 122 | - **One falsification did not bite, and is recorded rather than dressed up**: removing the one-line case from `DeleteLine` changes nothing, because the range it would otherwise build starts on line −1 and `clamp` pulls it back to 0. The case is kept for saying what it means, and the code now explains why. |
| 123 | - **Two tests were wrong, not the code**: a column index off by one in a fixture, and a "drag after a double click" that released the button before moving — which is not a drag. Both corrected. |
| 124 | - **Verified in a real terminal**: `Ctrl-G 2`, `Ctrl-Y` twice, `Ctrl-N`, `F2` on a four-line file leaves exactly the two lines and the blank one expected; and a real SGR double click on `alpha` followed by typing `X` leaves `X beta`. |
| 125 | - **Quality**: PASS 0/0/0 in all three; turbo-core complexity 1802 → 1810. |
| 126 | - **Docs**: the keyboard and menus references in both editors and both languages — including that `Ctrl-Y` no longer redoes, which is the half a user notices; a new section in `how-to/navigate-code.md`; `buffer/README.md`, `editor/README.md`, and the app reference in EN+FR. |
| 127 | |
| 128 | ## 2026-09-03 — A third editor exists: the family count, and a sweep for hardcoded languages |
| 129 | |
| 130 | - **Goal**: register turbo-python in the family. The library itself was **not changed** — turbo-python was built against published `v0.4.0` and needed nothing new, which is the point of the seam. |
| 131 | - **Changes**: the editors list in `README.md`; the counting sentence in `docs/{en,fr}/README.md`; `profile/profile.go`'s package comment; "both editors" → "every editor" in `how-to/release-the-library.md`, `how-to/write-the-starter-files.md` and `explanation/architecture.md`, EN and FR; `how-to/test-without-publishing.md` rewritten around a four-module workspace, with a new paragraph on listing nested modules in the `use` block — the trap that reads as a broken project rather than a workspace problem. |
| 132 | - **History was left alone.** "`tools.DefaultMenu` was correct for a year and wrong the moment there were two editors" is a true sentence about the past; rewriting it to say three would make it false. Only live claims changed. |
| 133 | - **The `.go` strings were swept** for hardcoded language names, which is what a third editor is for. Every hit was a false positive (`"Go to definition"`, `"Ctrl-G Go to line"`), a doc-comment example of the seam (`// "Go", "Rust" — which a profile chooses`), or a true statement about the implementation (`"written in Go."`). **Nothing to fix**: the About box's language had already been moved into the profile after Turbo Rust exposed it, and the pty run confirms it now reads "A Turbo C-style editor for Python". |
| 134 | - **Quality**: PASS 0/0/0, complexity 1810, unchanged. Suite green. |
| 135 | - **Not committed.** The changes are in the working tree on `main` at `14aae7c`. |
| 136 | |
| 137 | ## 2026-09-09 — a fourth editor, and no library change |
| 138 | |
| 139 | - **Goal**: register turbo-moonbit in the family, and find out whether a fourth editor needed anything from the library. |
| 140 | - **Changes**: `README.md`'s editors list; the counting sentence in `docs/{en,fr}/README.md`; `profile/profile.go`'s package comment; the four-module workspace in `docs/{en,fr}/how-to/test-without-publishing.md`; the "Four editors are built on it" and extension-point entries in `.memory/summary.md`. **No code change.** |
| 141 | - **The sweep found nothing.** `grep -rnE '"[^"]*\b(Go|Rust|Python|golang|cargo|gopls|rust-analyzer|pylsp)\b[^"]*"' --include='*.go'` over the whole library: every hit was a verb (`"Go to line"`, `"F12 Go to definition"`), a doc-comment example of the seam (`profile.go`, `tools.go`, `lsp/client.go`, `app/toolchain.go`), an import path, or the true statement `"written in Go."` — which every editor in this family is. The About box that said "an editor for Go" was fixed when Turbo Rust exposed it; nothing of that shape remains. |
| 142 | - **What a fourth editor did expose** is not in this repository's code but is worth recording here, because both are the library's to decide: |
| 143 | - **`workspace/didChangeWatchedFiles` is never sent.** moon-lsp works a package's file list out once, so a `.mbt` created after the server started is never diagnosed — File ▸ New, save, and the gutter stays blank until a restart. Confirmed at the raw protocol level. Sending the notification would fix it for every editor. |
| 144 | - **`turbo-classic` draws `syntax.attribute` and `syntax.identifier` in the same plain yellow.** The other seven themes distinguish them. It makes Turbo MoonBit's labelled arguments and attributes invisible as such, and Turbo Rust's `#[derive]` equally so. |
| 145 | - **History was left alone.** "`tools.DefaultMenu` was correct for a year and wrong the moment there were two editors" is a true sentence about the past. |
| 146 | |
| 147 | ## 2026-09-09 (later) — a light monochrome, and a name that had to keep working |
| 148 | |
| 149 | - **Goal**: the user asked for a white counterpart to the `monochrome` theme. |
| 150 | - **Changes**: `theme/themes/monochrome.toml` renamed to `monochrome-dark.toml`; new `theme/themes/monochrome-light.toml`; `retiredNames` in `theme/load.go` so `monochrome` still loads; six new tests in `theme/theme_test.go`; `theme/README.md`. Nine themes ship now. |
| 151 | - **Decisions**: |
| 152 | - **Renamed to a symmetric pair rather than adding an asymmetric one**, at the user's choice — but a theme's name is what somebody wrote in a settings file their team shares, so the rename came with an alias rather than with breakage. Rejected: leaving `monochrome` as the dark one's name, which the user considered and turned down; and renaming without an alias, which the option they chose explicitly flagged as needing one. |
| 153 | - **The alias resolves after the user's directory, not before.** Every other name obeys "a file in userDir wins"; a retired name must not be the exception, or somebody's own `monochrome.toml` would silently stop being read the day the alias was added. |
| 154 | - **The alias is not listed by `Available`**, so `-list-themes` and Options ▸ Theme… show each theme once. The cost, written down where the map is: a retired name can never be reused for a different theme. |
| 155 | - **The light theme is not a mirror of the dark one.** sRGB luminance is not symmetric, so mirrored values read worse: the dark theme's `#808080` comment is 4.74:1 on its ground and 3.40:1 on paper. The class *ordering* is preserved exactly and the scale is compressed into `#626262`…`#000000`. The comment is `#626262`, 5.26:1 — `#767676` would have looked right beside the rest of the scale and reads at 3.91:1. |
| 156 | - **Tests**: six new, and the five existing readability rules picked the new theme up by themselves because they iterate `theme.Available("")`. Ten mutations run — alias removed, alias applied too early, alias pointing at nothing, the light theme copied from the dark, a failing comment contrast, a cursor that is a plain reversal, an invisible current line, two classes drawn alike, text too close to its background, a key left to inheritance — all caught. |
| 157 | - **Quality**: PASS, run #20 — 0 errors, 0 warnings, 0 smells; complexity 1810 → 1811. |
| 158 | - **Docs**: the themes reference and the theme how-to in all four editors and both languages — the embedded list, the table (a new row), the inherit-from advice, the shipped-theme count, and a new "a name a theme used to answer to" section. `theme/README.md`, the packages reference, `what-belongs-here`, and the `-list-themes` samples in four tutorials. **History was left alone**: "comments were the dimmest reading colour in six of the eight shipped themes" is a true sentence about when that rule was written, in `editor/view_test.go`, `theme/README.md` and the four editors' `write-a-theme.md`. |
| 159 | - **Verified in a pty** through Turbo MoonBit built against this working tree: `-list-themes` shows nine with `monochrome` absent; `-theme monochrome-light` draws the page at `rgb(238,238,238)` with `struct` black and bold, types at `rgb(48,48,48)` and punctuation at `rgb(88,88,88)`; `-theme monochrome` draws the dark theme unchanged. |
| 160 | - **Not released.** The editors pin turbo-core v0.4.2, which still ships eight themes, so their documentation is ahead of their binaries until this is tagged and they bump. |
| 161 | |
| 162 | ## 2026-09-09 (later still) — two JetBrains themes |
| 163 | |
| 164 | - **Goal**: the user asked for an IntelliJ-like theme, then for both of its schemes. |
| 165 | - **Changes**: `theme/themes/darcula.toml` and `theme/themes/intellij-light.toml`. Eleven themes ship now. No code change — the five readability rules and the completeness test picked both up by themselves, because they iterate `theme.Available("")`. |
| 166 | - **The colours were fetched, not remembered.** JetBrains publishes them: the editor schemes in `platform/platform-resources/src/DefaultColorSchemesManager.xml` (Darcula) and `platform/platform-resources/src/themes/Light.xml` (the modern IntelliJ Light), and Darcula's chrome in `themes/darcula.theme.json`. Every value in both files is one of theirs except the three below. |
| 167 | - **Decisions**: |
| 168 | - **"After" rather than faithful, at the user's choice.** Three values had to move: Darcula's comment `#808080` reads 3.59:1 on its own page and IntelliJ Light's `#8c8c8c` reads 3.36:1 on white, both below the 4.5:1 this project holds prose to; and Darcula's caret row `#323232` is seven channel values from `#2b2b2b`, below the sixteen the current-line rule requires. Rejected: the Catppuccin route of copying exactly and adding an exemption — the user chose readable over faithful, so `palettesWeDoNotOwn` is untouched and both themes describe themselves as "after", the way `cobalt` does. |
| 169 | - **Each lift is the smallest that clears the rule**, and the numbers are in the files: `#949494` at 4.67 (where `#909090` gives 4.44), `#767676` at 4.54 (where `#787878` gives 4.42). The caret row borrows `#3c3f41`, JetBrains' own IDE window background, rather than an invented grey. |
| 170 | - **The modern `IntelliJ Light` scheme was used, not the older `Default`.** `Default` draws classes, methods and identifiers all in plain black, which cannot satisfy the rule that adjacent syntax classes differ — it would have meant inventing three colours. `Light.xml` publishes a distinct function, type-parameter and hyperlink colour, so nothing had to be invented. |
| 171 | - **Darcula's punctuation is the keyword orange**, because `DEFAULT_COMMA` and `DEFAULT_SEMICOLON` really are `#cc7832` there. It is one of the things that makes the theme recognisable, and it survived rather than being normalised. |
| 172 | - **Tests**: none added. Eleven mutations run — including two that restore JetBrains' own comment and caret-row values — all caught, which is the evidence that the three departures are necessary rather than taste. |
| 173 | - **Quality**: PASS. **Verified in a pty** through Turbo MoonBit built against this tree: `darcula` draws `let` at `#cc7832`, strings `#6a8759`, parentheses `#cc7832`, `println` `#9876aa`, line numbers `#606366`; `intellij-light` draws `let` `#0033b3` bold, strings `#067d17` bold, `println` `#0033b3`, punctuation `#080808`. |
| 174 | - **Docs**: the themes reference and the theme how-to in all four editors and both languages — the embedded list, two new table rows, the inherit-from advice, the count; plus `theme/README.md`, the packages reference, `what-belongs-here`, the build-an-editor tutorial, and the `-list-themes` samples in four tutorials. |
| 175 | |
| 176 | ## 2026-09-12 — read-only: are any dependencies non-Go? |
| 177 | |
| 178 | - **Asked**: whether turbo-core depends on any non-Go library. |
| 179 | - **Answer, verified not remembered**: no. No `import "C"` anywhere in the tree, `CGO_ENABLED=0 go build ./...` passes, and no dependency ships C sources except `x/sys`'s `cpu_gccgo_x86.c` and `unix/gccgo_c.c`, both gccgo-only and never compiled by the `gc` toolchain. The runtime does exec external *programs* — `/bin/sh` (`tools/run.go:19`, `app/toolchain.go:35`) and the profile's LSP binary (`lsp/server.go:92`) — which is a packaging fact, not a link-time one. |
| 180 | - **Changes**: none. `summary.md` already states the dependency policy correctly (the "two direct dependencies plus one" decision); left untouched. |
| 181 | |
| 182 | ## 2026-09-14 — a fifth editor, Turbo Golo, registered in the family |
| 183 | |
| 184 | - **Asked**: nothing of this repository directly. `turbo-golo` was built beside it (`/turbo-new-editor`), pinning `v0.5.0`, and its Phase E is to make the library's live claims about the family true again. |
| 185 | - **Changes**: `README.md` (the list of editors gains Turbo Golo), `docs/{en,fr}/README.md` (five editors, five values of one type), `docs/{en,fr}/how-to/test-without-publishing.md` (the workspace line gains `./turbo-golo`; "any of the four" → five), `profile/profile.go` (the package comment counts five), `.memory/summary.md` ("Five editors are built on it", and the extension point exercised by six counting the tutorial's Zig editor). History left as it was. |
| 186 | - **Decision**: no library change. The `.go` strings were swept again for hardcoded language names against v0.5.0; every hit is a doc-comment example of the seam (`"Turbo Go"`, `"gopls"` in `profile.go`, `"Go"`/`"Rust"` in `tools.go` and `toolchain.go`), a false positive (`"Go to line"`, `"Go to definition"`), or the true `"written in Go."` in the About box. Turbo Golo needed **nothing** from the library — not even a root marker: its `RootMarkers` is `nil` and `app.ProjectRoot` answers with the file's directory, which is the behaviour the seam already had. |
| 187 | - **Tests**: `go build ./profile` and `gofmt -l profile/` after the comment change; nothing else touched. |
| 188 | |
| 189 | ## 2026-09-15 — ACP: the Agent Client Protocol, and `jsonrpc` extracted to make room for it |
| 190 | |
| 191 | - **Goal**: the user asked for ACP support in turbo-go — an agent window with an input area and a rendered transcript with syntax colouring for code, several agents configurable in TOML, one window per agent. Put to them at the start: the window, the menu and the event loop are all turbo-core's, so building it in turbo-go alone would have meant adding a vague "let an editor add a window and a menu from outside" seam to the library and using it exactly once. They chose turbo-core + the starter file in turbo-go. They also chose the largest protocol scope offered (conversation + permissions + the client filesystem), the user+project config rule, and reusing existing theme keys. |
| 192 | - **Changes, turbo-core**: new `jsonrpc` (the protocol layer, extracted from `lsp`) and new `acp` (config, framing, protocol, process, session, transcript, render, view) — 18 packages now. `lsp/conn.go` fell from 336 lines to ~50: the framing, and the names it re-exports. `app` gained `agents.go`, `agent_permissions.go`, an `~A~gent` menu on `Alt-A`, and two hooks in `tick`. `profile.Templates` gained `Agents`. |
| 193 | - **Changes, turbo-go**: `internal/golang/acp.toml.tmpl` embedded beside the other three starter files; four stale assertions in `templates_test.go` corrected first, because the suite had been red at `HEAD` since 2026-09-03 and a red suite proves nothing about anything that follows. |
| 194 | - **Decisions**: recorded in `summary.md` under Decisions in force — seventeen of them. The three that cost the most to learn: **a JSON-RPC request has to be answerable later** (a permission comes from a dialog, which belongs to the drawing goroutine), **the two id directions are separate spaces** (docker agent really does send `id: 1` against our own `id: 1`), and **every write to an agent goes off the caller's goroutine** (writing blocks until the agent reads; an agent that stops reading would otherwise freeze the editor on the very keystroke meant to escape it). |
| 195 | - **Method**: the protocol was not implemented from the specification alone. A recorded conversation with the user's own `docker agent` + llama.cpp was captured first, and the fake agent in the tests replays those exact shapes. That is what caught three things the documentation does not say: the permission request arrives *before* the `tool_call` it is about, `content` is a single object on a message chunk and an array on a tool call, and `available_commands_update` / `usage_update` exist at all. |
| 196 | - **Tests**: 13 in `jsonrpc`, 47 in `acp`, 12 in `app`, 4 in turbo-go. Both suites green, and `acp` / `jsonrpc` / `app` / `lsp` green under `-race`. The three riskiest `jsonrpc` assertions were **falsified** — broken deliberately, seen red, restored. |
| 197 | - **Quality**: turbo-core PASS after refactoring three `return-statements` smells the new code introduced (0/0/0, complexity 1811 → 2119); turbo-go PASS 0/0/0, unchanged at 37. |
| 198 | - **Docs**: EN + FR in turbo-go — `how-to/talk-to-an-agent`, `reference/acp`, `explanation/agent-windows` — plus the packages reference and architecture explanation in turbo-core, `acp/README.md`, `app/README.md`, and `docs/diagrams/packages.drawio` regenerated from `go list` and verified edge for edge (18 packages, 38 edges). |
| 199 | - **A documentation trap this cycle walked into on purpose**: the user asked for the docs *before* the code existed, so they were written as the agreed design with a status banner on every page. Two claims in them turned out false once the code was real — `syntax.error` does not exist (`diagnostic.error` does) and the output cap is per entry, not 10 000 lines — and the documented refusal messages did not match the ones the code emits. All corrected against the running code before the banners came off. **A page written ahead of the code is a design document, and must be re-read against the code line by line before it becomes documentation.** |
| 200 | - Not committed. |
| 201 | |
| 202 | ## 2026-09-15 (later) — a spinner while the agent thinks, and copying out of the conversation |
| 203 | |
| 204 | - **Goal**: the user, having used the agent windows, asked for two things — something turning beside the word *thinking*, and a way to take text out of a conversation and paste it elsewhere. |
| 205 | - **Changes**: `acp/spinner.go` (new); `acp/view_select.go` (new — the transcript's cursor, its selection, and `Copy`); `Line.Region` in the layout; selection drawing and the caret bar in `view_draw.go`; `Shift`-arrows, `Ctrl-C` / `Ctrl-Ins` and mouse dragging in `view_events.go`; `Session.pulse`; `App.copyFromAgent`. `acp/render.go` was split into `render.go`, `blocks.go` and `wrap.go` when the quality gate flagged its total complexity. |
| 206 | - **Decisions**: recorded in `summary.md`. The three that mattered: the spinner is drawn from the **clock** so it needs no state; copying goes to **both** clipboards because "use it elsewhere" usually means outside the editor entirely; and with nothing selected `Ctrl-C` copies the **region** under the cursor, because the thing somebody wants is almost always a code block and the editor already knows where it starts and ends. |
| 207 | - **A defect found by driving the real binary, not by a test**: the first version copied the speaker's label with the code block. It was caught by copying from the running editor and base64-decoding the OSC 52 payload off the pty. `TestCopyingABlockLeavesTheSpeakersLabelBehind` covers it now, and writing that test found a second defect — a `tool_call` arriving with its output already attached lost it, because only `tool_call_update` read the content field. |
| 208 | - **Tests**: 8 new in `acp`. Spinner, selection and Escape's two meanings were each **falsified** — broken deliberately, seen red, restored. |
| 209 | - **Quality**: PASS after refactoring `ruleLabel` (six returns) and splitting `render.go` (file complexity 51). 0/0/0. |
| 210 | - **Docs**: the how-to gained a section on taking text out of a conversation; the reference gained the per-pane key tables, a Copying section and the `editor.selection` row; the explanation gained three sections — the two clipboards, why copying with nothing selected takes a whole block, and why the spinner is drawn from the clock. EN + FR, then carried to the four other editors. |
| 211 | - **A hazard that cost a file.** The sandbox's `cp` corruption struck twice more, once destroying `acp/view_events.go` *and its backup* in the same command, because the backup had been made with `cp`. The file was rewritten from scratch. **Never use `cp` or a cross-filesystem `mv` to back up a file in this sandbox; use `cat`.** |
| 212 | - Not committed. |
| 213 | |
| 214 | ## 2026-09-15 (night) — slash commands and `@` file mentions in agent windows |
| 215 | |
| 216 | - **Goal**: the user runs a second agent under Zed — `mini-me`, started as `mm -acp` — that exposes commands Zed discovers, and asked for "the necessary ACP changes" so this client does the same; mid-task they added `@` as a file selector. Both are what Zed does with the same two characters. |
| 217 | - **Changes, `acp`**: `protocol.go` — `Command.Input` / `CommandInput.Hint`, `TakesInput()`, `Hint()`; `ContentBlock` grows `URI`, `Name`, `MimeType`, `Resource *EmbeddedResource`; `InitializeResult.PromptCapabilities()` reads `embeddedContext` out of the raw capabilities. `mention.go` (new) — `Mention`, `blocksFor` (text / `resource` / `resource_link`, in place of the name), `mimeOf`. `picker.go` + `picker_draw.go` (new) — `Choice`, `CommandChoices`, `FileChoices`, the word under the cursor, the keys, the click, the drawing. `session.go` — `Prompt(text, mentions...)`, `pending` queue, `embeds`, `EmbedsContext()`; its file methods moved to `session_files.go` (new) when the gate flagged the file's complexity. `view.go` — `Files` hook, `Send` collects mentions. `view_events.go` — the picker gets the key first; `Esc`'s dismissal lasts until the text changes; a click on the popup takes a line. `view_draw.go` — the rule says `· / commands · @ files` once there is something to list. |
| 218 | - **Changes, `app`**: `agent_files.go` (new) — `projectFiles` / `listProjectFiles`, wired as `view.Files`; the status dialog lists each command with its description and hint. |
| 219 | - **Decisions**: four, recorded in `summary.md` — a command is text and the picker a convenience; `Enter` completes-or-sends by whether the word is finished; a mention replaces its name with the file, embedded when the agent takes it, linked otherwise, never dropped; media types from a table of our own. Rejected: a menu of commands; sending the mention beside the text as an attachment (the agent would get the name twice); a stored popup state (recomputed from the word under the cursor every frame instead). |
| 220 | - **Tests**: 15 new in `acp` (`picker_test.go`, plus the hint and `EmbedsContext` asserted in `session_test.go`; `fakeAgent.handshakeWith` for an agent declaring less than docker agent), 1 in `app`. Green, and green under `-race`. Two guards were **falsified** — `complete()` made to always take the choice, and the mention's end-of-word check removed — each seen red on its test, then restored. |
| 221 | - **Quality**: FAIL on the first run (4 smells: seven returns in the walk, six parameters on `drawChoice`, file complexity 50 and 51 in `session.go` and `picker.go`) → refactored → **PASS 0/0/0**, complexity 2254 → 2257. |
| 222 | - **Docs**: `acp/README.md` (two new files in the table, two new "easy to get wrong"), `app/README.md`. In the five editors, EN + FR: `reference/acp.md` (seven key rows, a **Commands and mentions** section, `session/prompt` and `available_commands_update` rows, the Limits bullet), `how-to/talk-to-an-agent.md` (two sections), `explanation/agent-windows.md` (the left-out bullet corrected, two sections appended), and the starter `acp.toml.tmpl` (two key lines). Applied by one script with an anchor check that had to hit exactly once per file — it did, 5 editors × 2 languages × 3 pages. |
| 223 | - **Method note**: the user is not watching this session, so the plan-approval gate `methodical-dev` asks for was not held; the scope was taken as stated and the decisions above are the ones to review. |
| 224 | - **Not verified**: no real agent in the sandbox. See the handoff. |
| 225 | - Not committed. On `main`, not on a branch. |
| 226 | |
| 227 | ## 2026-09-16 — "I don't see the agent's / commands": a wire trace and a decode diagnostic |
| 228 | |
| 229 | - **Asked**: the user built turbo-go, pointed it at mini-me, typed `/` and saw no commands; asked whether this is handled in turbo-core. It is, and their binary has it (`go version -m bin/turbo-go` → turbo-core `(devel)` through `go.work`; the `· / commands` label string is in the binary). So either mini-me never sends `available_commands_update`, or sends one this client cannot decode — and until now the two were indistinguishable: `onNotification` dropped an undecodable update silently. |
| 230 | - **Changes**: `acp/trace.go` (new) — `TURBO_ACP_TRACE=<file>` appends every line in both directions, stamped and marked `->`/`<-`, wrapped around the child's pipes in `Start`; a file that cannot be opened means no trace. `Session.unreadableUpdate` counts an undecodable update and keeps "kind: error" in `Unreadable()`; the status dialog shows it and says how to set the trace. Tests: 2 new in `acp/trace_test.go`. |
| 231 | - **Docs**: EN + FR × 5 editors — a "commands do not appear" bullet in the how-to's Variants, a "Tracing the conversation" section in the reference. |
| 232 | - **Quality**: PASS 0/0/0, complexity 2268. |
| 233 | - **Not resolved**: the actual cause. It needs the user's trace from their Mac. Not committed. |
| 234 | |
| 235 | ## 2026-09-16 — a sixth editor, Turbo JS, registered in the family |
| 236 | |
| 237 | - **Asked**: nothing of this repository directly. `turbo-js` was built beside it (`/turbo-new-editor`), pinning `v0.8.0`, and its Phase E is to make the library's live claims about the family true again. |
| 238 | - **Changes**: `README.md` (the list of editors gains Turbo JS), `docs/{en,fr}/README.md` (six editors, six values of one type), `docs/{en,fr}/how-to/test-without-publishing.md` (the workspace line gains `./turbo-js`; "any of the five" → six), `profile/profile.go` (the package comment counts six), `.memory/summary.md` ("Six editors are built on it", the extension point exercised by seven counting the tutorial's Zig editor, and a new bullet under Not yet established). History left as it was. |
| 239 | - **Decision**: no library change. The `.go` strings were swept again for hardcoded language names against v0.8.0; every hit is a doc-comment example of the seam, a false positive (`"Go to line"`, `"Go to definition"`), or the true `"written in Go."` in the About box. Turbo JS is the **first editor to replace a built-in scanner** — it registers its own JavaScript under `syntax.LanguageJavaScript` — and the library's `Register` allowed it without a change, as its doc comment promised. |
| 240 | - **Found, not fixed (each a cycle of its own)**: `lsp/client.go` sends `languageId: "go"` in every `didOpen`, whatever the editor — a hardcoded language in a string a server reads; tsserver goes by extension so nothing broke. And the client reads `publishDiagnostics` only, so a server offering pull diagnostics (`textDocument/diagnostic`) — TypeScript 7's native `tsc --lsp --stdio` — leaves every gutter blank; measured by running turbo-js's nine end-to-end tests against it (eight pass, diagnostics never arrive). Both are in `summary.md` under Not yet established. |
| 241 | - **Tests**: `go build ./profile` and `gofmt -l profile/` after the comment change; nothing else touched. |
| 242 | |
| 243 | ## 2026-09-17 — terminal windows and tools on Windows: a pseudo-console, cmd.exe, job objects |
| 244 | |
| 245 | - **Goal**: the user asked for "le terminal pour l'ensemble des éditeurs" — Windows had no terminal windows (`pty_other.go` returned `ErrUnsupported`) and, as it turned out, no working tools menu either: `tools/run.go` and `app/toolchain.go` both ran `/bin/sh -c`. Put to the user first: the shell (cmd.exe via `%COMSPEC%`, chosen over PowerShell), that no Windows machine exists to test on (accepted: compiled and vetted, documented as unrun), the branch (`main`), the pace (run to the end). |
| 246 | - **Changes, `terminal`**: `pty.go` keeps `Session`, `Options`, `TermName`, `environment` and gains a `child` interface; `session_unix.go` (new, `linux || darwin`) holds the `/dev/ptmx` + `exec.Cmd` path that used to be in `pty.go`; `pty_windows.go` (new) creates the pipes and the pseudo-console, builds the attribute list, calls `CreateProcess`, resizes with `ResizePseudoConsole`, waits for the shell on a goroutine and closes the console so the reader sees EOF; `windows.go` (new, untagged) holds the pure parts — `windowsShell`, `environmentBlock`, `windowsCommandLine` with cmd.exe's `/S /C "…"` special case and C-runtime quoting for everything else, `programName`; `pty_other.go` now excludes windows. `pty_test.go`'s Unix tests skip on Windows explicitly; `TestShellOrDefault` moved to `session_unix_test.go`; `windows_test.go` (10 tests) runs on every platform. |
| 247 | - **Changes, `tools`**: `Shell` const → `Shell()` and `ShellArgs()` (`shell.go`), with `shell_unix.go` (`/bin/sh -c`, `Setpgid`, process-group kill), `shell_windows.go` (`%COMSPEC% /S /C`, `SysProcAttr.CmdLine` written verbatim, `CREATE_NEW_PROCESS_GROUP`, job object with `KILL_ON_JOB_CLOSE`) and `group_other.go`; `run.go` holds a `group` and releases it in `finish`. `group_unix.go` deleted (folded into `shell_unix.go`). |
| 248 | - **Changes, `app`**: `toolchain.go` drops its own `/bin/sh` constant and uses `tools.Shell()` / `tools.ShellArgs()`; `terminals.go`'s unsupported-platform message names Windows among the platforms that have terminals. |
| 249 | - **Decisions**: recorded in `summary.md` — the `child` interface, the by-hand `CreateProcess` and the `LazyProc` for `UpdateProcThreadAttribute`, the console-closing goroutine, cmd.exe's verbatim command line, job objects, `%COMSPEC%`. Rejected: PowerShell as the default (the user chose cmd.exe); `os/exec` for the pseudo-console child (no attribute support); `taskkill /T` instead of a job object; a `CommandLine` field on `terminal.Options` (Windows leaking into the API — the cmd.exe case is recognised from the program name instead). |
| 250 | - **Tests**: Linux suite green (`terminal`, `tools`, `app` also under `-race`); `GOOS=windows go vet ./...`, `GOOS=windows go build ./...` for amd64 and arm64, `GOOS=windows go test -c` for `terminal`, `tools`, `app`; `GOOS=darwin go vet ./...`. `release_test.go` fails in this sandbox for the documented `cp`-NUL reason, unrelated. **Nothing ran on Windows.** |
| 251 | - **Quality**: run #28 FAIL (six returns in `createProcess` → split into `pseudoConsoleAttributes` and `encodedArguments`), run #29 **PASS** 0/0/0, complexity 2306. |
| 252 | - **Docs**: `terminal/README.md` (platform table, the three Windows facts, the untested note), `tools/README.md` (`Shell()`/`ShellArgs()`, job objects, cmd.exe), `docs/{en,fr}/reference/packages.md` (the x/sys row). In all six editors, EN + FR: `README.md` (Linux, macOS and Windows), `reference/terminal.md` (shell, controlling terminal, platform table, error row), `explanation/terminal-windows.md` (the Windows section rewritten: a pseudo-console and why it is a file of its own, built-not-run), `how-to/use-a-terminal.md` (COMSPEC, the five things to try first on Windows), `reference/<lang>-tools.md` (shell row, cmd.exe's globs and `;`, error row), `explanation/<lang>-tools.md` (`cmd.exe /S /C`). Applied by one script asserting each anchor once per file; the four FR tools references whose error row was worded differently were fixed by hand. |
| 253 | - Not committed. Not tagged. The editors' docs are ahead of their binaries until turbo-core is tagged and re-pinned. |
| 254 | |
| 255 | ## 2026-09-18 — "first launch: no LSP" diagnosed, then fixed: saving announces what the server does not know |
| 256 | |
| 257 | - **Asked**: the user reported that on turbo-go's first launch the LSP does not work; saving, quitting and relaunching fixes it. Diagnosed first (read-only), then the user confirmed the window had started Untitled and asked for the fix — a `/methodical-dev` cycle, on `main`, on top of the still-uncommitted Windows work of 2026-09-17 (disjoint files). |
| 258 | - **Found**: a buffer that gains its path through Save As was never `didOpen`'d. `Language.DidOpen` skips path `""` (Untitled), `afterSave` sent only `didSave`, and `announceOpenDocuments` runs once (`a.announced`) — and a server ignores `didChange`/`didSave` for a document it was never told is open, so the window had no LSP until a restart. The alternative cold-gopls-cache hypothesis is in the handoff, unmeasured. |
| 259 | - **Changes**: `app/language.go` — the `documents` map is keyed by absolute path (`pathKey`, formerly `diagnosticKey`; `Knows` is now spelling-independent). `app/actions_file.go` — `announceSaved` in `afterSave` sends `didOpen` for a path the server does not know, `didSave` otherwise; `save` remembers the buffer's previous path before `SaveAs` rewrites it and `renamed` (absolute comparison) triggers `DidClose` of the old document on a genuine rename. `app/README.md` updated. |
| 260 | - **Decisions**: the announcement lives in `app`, not in `Buffer.SaveAs`, which would put the language server underneath the text type. A change of spelling of the same file is not a rename. Autosave inherits the fix through the shared `afterSave` tail and never renames, so `DidClose` lives in `save` alone. |
| 261 | - **Tests**: 5 new — `TestKnowsDoesNotDependOnTheSpellingOfThePath` (app/diagnostics_test.go) and four in the new `app/save_test.go` (Untitled announces; known document reports the write; rename closes the old document; same path under another spelling closes nothing). Each verified by breaking the code it covers. Whole suite green, `-race` green on `app`. |
| 262 | - **Quality**: run #30 **PASS** 0/0/0, complexity 2308 (+2). |
| 263 | - **Docs**: EN + FR — `reference/app.md` (a `Knows` row and a paragraph on what saving sends) and `how-to/talk-to-a-language-server.md` (a "file created inside the editor" variant). Diagram untouched: no package added or re-wired. |
| 264 | - **Not done**: not committed; not driven against a real gopls; the six editors inherit the fix only when turbo-core is tagged and re-pinned. |
| 📦 Turbo Core — canonical paths: symbolic links resolved in URIs and document keys (moon-lsp on macOS) b91316e k33g 2h ago | 265 | |
| 266 | ## 2026-09-19 — canonical paths: moon-lsp on macOS knew nothing about a file opened as /var/… |
| 267 | |
| 268 | - **Origin**: the user ran turbo-moonbit's `./01-release.tag.sh` on their Mac; `make check` failed on `TestCompletionEndToEndWithRealMoonLSP` ("No completions here", after 60 s) and `TestDiagnosticsForAFileThatDoesNotCompileWithRealMoonLSP` (nothing in 30 s), the other real-server tests passing. turbo-moonbit's memory said "never run on macOS" — this was the first time. |
| 269 | - **Diagnosed** in the sandbox with the MoonBit toolchain of 2026-09-15 installed (`moon 0.1.20260915`, `moonc v0.10.13`): the fixture and manifests are fine (`moon check` reports the expected error); the tests pass under `/tmp`; they fail identically with `TMPDIR` pointing through a symbolic link. An LSP probe against `moon-lsp --stdio` showed why: opened through a link, `publishDiagnostics` carries the *real* path's URI, and a completion asked after a `didChange` that types `text.` is never answered (40 requests) — through the real path it is answered at once. macOS's `/var/folders` is a link to `/private/var`, so every `t.TempDir()` there is the linked spelling. |
| 270 | - **Changes**: `lsp/uri.go` — `CanonicalPath` (new, exported): `Abs`, then `EvalSymlinks`, else the deepest existing directory resolved and the rest re-appended; `PathToURI` uses it. `app/language.go` — `pathKey` is `lsp.CanonicalPath` (the `path/filepath` import went). `app/fakelsp_test.go` — the fake records the params of the last `textDocument/didOpen`; `lastOpenedURI()`. Tests: `TestPathToURIResolvesSymbolicLinks`, `TestCanonicalPathOfAFileNotYetOnDiskResolvesItsDirectory` (lsp), `TestDiagnosticsPublishedUnderTheRealPathReachAFileOpenedThroughALink` (app) — all three skip where a symlink cannot be created. **Falsified**: with `app/language.go` and `lsp/uri.go` stashed, the app test fails on all three assertions. |
| 271 | - **Verified**: `make check` green (whole suite). In turbo-moonbit through `go.work` (local core), all six `…WithRealMoonLSP` tests pass with `TMPDIR` under a symlink, and the whole suite passes. |
| 272 | - **Docs**: `app/README.md` (section renamed *canonical*, a paragraph on moon-lsp and macOS), `docs/{en,fr}/reference/app.md` (`Knows` row), `lsp/README.md` (`CanonicalPath` row). |
| 273 | - **Not done**: not committed, not tagged — `release.env` is set to `TAG="v1.0.1"`; the user runs `./01-release.tag.sh`, then re-pins the editors (`go get rickub.com/turbo-editors/turbo-core@v1.0.1 && go mod tidy && GOWORK=off make check`). Not run on macOS by anyone yet; the reproduction is a Linux symlink. |
| 274 | |
| 275 | ## 2026-09-19 (later) — the canonical-path fix, second pass: three tests that compared as spelt |
| 276 | |
| 277 | - **Origin**: the user's `./01-release.tag.sh` for v1.0.1 stopped in `make check` on the Mac: `TestTheListPrefersAnOpenWindowOverTheDisk` (app), `TestDiagnosticsReachTheEditor` and `TestPathAndURIRoundTrip` (lsp). All three compared a path as spelt (`/var/folders/…`) with what now comes back canonical (`/private/var/…`); green on Linux because `/tmp` is no link. Reproduced with `TMPDIR` under a symlink. |
| 278 | - **Changes**: `app/actions_file.go` — `windowFor` compares `lsp.CanonicalPath` of both sides, not `filepath.Abs`; this is the real defect the first test caught — a location the server sends back could not find the window it was about, so the references list quoted the disk and a jump would have opened the file twice. `lsp/lsp_test.go` — the two tests now expect `CanonicalPath(path)`, with the reason in a comment. New `TestWindowForFindsAFileOpenedThroughALink` (app), **falsified** against the old `windowFor`. |
| 279 | - **Verified**: `make check` green; whole suite green with `TMPDIR` under a symlink (`TURBO_CORE_RELEASING=1` to keep the release scripts out of it). |
| 280 | - **Not done**: still not committed or tagged; `release.env` still says v1.0.1. Run `./01-release.tag.sh` again. |
| 📦 Turbo Core — a save that creates a file tells the server (workspace/didChangeWatchedFiles), so moon-lsp diagnoses a new .mbt from its first save 3561e52 k33g 1h ago | 281 | |
| 282 | ## 2026-09-19 (later) — a save that creates a file tells the server: moon-lsp never diagnosed a new .mbt |
| 283 | |
| 284 | - **Origin**: turbo-core v1.0.1 published, turbo-moonbit re-pinned; its `make check` on the Mac then failed `TestAFileCreatedAfterTheServerStartedIsNotDiagnosed` — the test pinning "moon-lsp never diagnoses a file created after it started" — because on macOS moon-lsp *does*. Here (Linux, moon 0.1.20260915) the test still passed, so the platforms disagree; the documentation it protected had in fact already been overtaken (turbo-moonbit's `enable-completion.md` has promised error marks from the first save since 2026-09-18), and `languages.md` no longer mentions it. |
| 285 | - **Diagnosed** with an LSP probe against `moon-lsp --stdio`: a new file announced by `didOpen` (and `didSave`) is never diagnosed on Linux; the moment `workspace/didChangeWatchedFiles` names it — in any order relative to the document notifications, type 1 or 2 — its diagnostics arrive. The server advertises no watcher registration, so nothing ever asked the editor to send this. turbo-moonbit's own summary had already written the fix down as turbo-core's to make. |
| 286 | - **Changes**: `lsp/protocol.go` — `FileChangeType` (`FileCreated`/`FileChanged`/`FileDeleted`), `FileEvent`, `DidChangeWatchedFilesParams`. `lsp/client.go` — `FileCreated(path)`. `app/language.go` — `FileCreated`. `app/actions_file.go` — `save` asks `fileExists` before writing; `afterSave` and `announceSaved` take `created`; the file event goes after the document is announced or saved. `app/autosave.go` — `writeQuietly` the same. `app/fakelsp_test.go` — records the last file event and the order of methods (`lastFileEvents`, `methodsSeen`). Tests: `TestSavingANewFileTellsTheServerTheFileExists`, `TestSavingAnExistingFileDoesNotClaimItWasCreated`, `TestAutosaveOfANewlyNamedFileTellsTheServerTheFileExists`; the lsp notification-order test includes the new one. The `firstIndexOf` helper: `actions_view.go` owns `indexOf`. |
| 287 | - **Verified**: `make check` green; whole suite green with `TMPDIR` under a symlink. In turbo-moonbit (through `go.work`), the rewritten `TestAFileCreatedInTheEditorIsDiagnosedFromItsFirstSaveWithRealMoonLSP` passes (plain and symlinked `TMPDIR`) and **fails against the published v1.0.1** — the falsification. A first version of that test failed for a reason of its own: its `typeText` dropped `\n`, so the four-line fixture became one `///|` doc-comment line and compiled; traced with a tee wrapper around `moon-lsp`. The helper now sends Enter for a newline. |
| 288 | - **Docs**: `app/README.md`, `docs/{en,fr}/reference/app.md`, `docs/{en,fr}/how-to/talk-to-a-language-server.md`, `lsp/README.md`. |
| 289 | - **Not done**: not committed, not tagged; `release.env` says v1.0.2. Whether moon-lsp on macOS watches the directory itself is inferred from the user's run, not measured. |