turbo-editors/turbo-gopublic Fork 0
main
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.

📦 Turbo Go 3d7798b · on main · k33g · 9h ago
2026-08-31-go-tools.md · 59 lines · 6.6 KBmarkdown
Blame HistoryOpen raw

Handoff — 2026-08-31 — the Go menu, and a data race it exposed

State

Ticket 0017 is partly done and green, on branch feature/go-format-lint, uncommitted.

Alt-G opens a Go menu built from .turbo-go/tools.toml. Each entry says where its output goes — popup (the default), terminal, or editor — and files the command rewrote that have no unsaved changes are re-read afterwards. Go ▸ Create tools file writes a starter file holding the five Go commands, four popup and Run terminal.

 … Window  Snippets  Go  Help
                  ┌───────────────┐   ┌──────── go vet ./... — exit 1 ────────┐
                  │ Format        │   │  main.go:6:2: unreachable code        │
                  │ Lint          │   │                                       │
                  │ Build         │   │                [ Close ]              │
                  │ Test          │   └───────────────────────────────────────┘
                  │ Run           │
                  ├───────────────┤
                  │ Create tools… │
                  └───────────────┘
  • New internal/tools (95.0 %, both the file and the running of a command) and internal/projectfile (75.0 %).
  • terminal.Options.Args, terminal.ViewOptions, View.Exited(); Buffer.Reload with ErrModified; NewOutputDialog; App.tick.
  • Whole suite green under -race, five consecutive runs of the two most affected packages. Quality gate PASS: 0/0/0, complexity down from 1528 to 1513.
  • Docs complete in EN and FR — three new pages each, six existing pages updated each. Diagram re-checked against go list (39 edges each side).
  • Verified in a real terminal: go build ./... — ok with (no output), go vet ./... — exit 1 with its diagnostic, gofmt -l -w . — ok listing the file it rewrote, and the buffer reloading afterwards.

Earlier the same day, PR #6 merged the snippets work.

In flight

Nothing. Finished through Phase 8.

Next steps

  1. Commit and open the PR. feature/go-format-lint is the branch.
  2. Finish ticket 0017. It also asked for go mod init + touch main.go — creating a project from nothing, which is a different shape from running a command in an existing one (it needs a name, and there is no project yet). Not done, and the ticket should stay open for it.
  3. Tickets. 0002, 0003, 0006, 0007, 0009, 0013, 0014 are implemented and open; 0017 is partly implemented.

Open questions / blockers

  • A modal popup holds the whole editor while a command runs. That was chosen knowingly and is documented, with output = "terminal" as the way out for any command it annoys you on. If it turns out to annoy generally, a non-modal output window is the alternative — and it is a different feature, not a tweak.

  • output = "editor" shows a popup first, then a window. The popup is how you watch it and how you stop it; the window arrives when you close the popup. It reads oddly written down and is fine in use, but it is worth a second look with real output.

  • go mod init is not in the menu, and does not fit the current shape: every other tool runs in the project that already exists, whereas this one creates it and needs a module path typed. It wants a prompt dialog and probably its own item rather than a tools.toml entry.

  • Nothing jumps to a compile error. go build prints internal/app/app.go:42:3: … in the terminal window and you have to open the file yourself. Parsing that output and jumping would be a real win and is a feature of its own — it needs a per-language error format, and the output lives in a terminal emulator rather than in a captured buffer.

  • A menu panel still does not scroll (carried over from the snippets handoff). A tools.toml with twenty entries draws a panel taller than the terminal and the bottom is clipped.

Watch out for

  • Verify a new test by breaking the code it covers. Two tests in this session passed while proving nothing, both because the test drove the step under test: waitForLoopTurn called reloadAfterTools itself (so the test passed with that step deleted from the loop — hence App.tick), and the process-group test killed the shell before it had forked (so it passed without the fix — it now waits for the child to print). Neither was visible by reading.

  • Stopping a command must take the process group. Killing only the shell leaves a grandchild holding the output pipe, and the reading goroutine blocks until it ends — 20 seconds in the test suite, and for go test ./... it would be every test binary. processGroup() and killGroup() are build-tagged; cmd.WaitDelay is the backstop for anything that escapes.

  • A constructor that starts a goroutine must take its callbacks as parameters. terminal.NewView started the reader and callers assigned OnChange/OnExit afterwards — a data race present since the terminal feature that -race never caught, because a shell takes longer to produce its first output than an assignment takes to run. sh -c "echo x" finished immediately and the detector fired at once. ViewOptions fixes it structurally; do not add an assignable callback back.

  • A finished terminal must not swallow keys. It used to write every key to the dead shell, where the write failed silently and the key was consumed anyway, so Ctrl-W could never close the window. View.HandleKey returns false after Exited(), except for the scrolling keys. TestAFinishedTerminalStopsTakingKeys covers it.

  • Buffer.Reload must refuse over unsaved work. That restriction is the whole safety of it. TestReloadRefusesToThrowAwayUnsavedWork pins it, and TestAToolLeavesAModifiedBufferAlone pins the app end of it.

  • internal/buffer's atomic write was deliberately left out of projectfile. It preserves the mode of the file it replaces, because it is saving over something the user already had; projectfile creates a file with a fixed 0644. They look alike and are not the same operation — merging them would mean parameterising the mode and losing the different error text.

  • projectfile.Write already does MkdirAll. The three Create functions used to do it themselves; do not add it back.

  • The app tests for tools drive the event loop by hand. Run is not running, so waitForLoopTurn calls a.reloadAfterTools() itself. A test that waits for a reload without calling it will hang until its deadline.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Handoff — 2026-08-31 — the Go menu, and a data race it exposed

## State

**Ticket 0017 is partly done and green**, on branch `feature/go-format-lint`, **uncommitted**.

`Alt-G` opens a **Go** menu built from `.turbo-go/tools.toml`. Each entry says where its output goes — `popup` (the default), `terminal`, or `editor` — and files the command rewrote that have no unsaved changes are re-read afterwards. **Go ▸ Create tools file** writes a starter file holding the five Go commands, four `popup` and `Run` `terminal`.

```
 … Window  Snippets  Go  Help
                  ┌───────────────┐   ┌──────── go vet ./... — exit 1 ────────┐
                  │ Format        │   │  main.go:6:2: unreachable code        │
                  │ Lint          │   │                                       │
                  │ Build         │   │                [ Close ]              │
                  │ Test          │   └───────────────────────────────────────┘
                  │ Run           │
                  ├───────────────┤
                  │ Create tools… │
                  └───────────────┘
```

- New `internal/tools` (95.0 %, both the file and the running of a command) and `internal/projectfile` (75.0 %).
- `terminal.Options.Args`, `terminal.ViewOptions`, `View.Exited()`; `Buffer.Reload` with `ErrModified`; `NewOutputDialog`; `App.tick`.
- Whole suite green under `-race`, five consecutive runs of the two most affected packages. Quality gate **PASS**: 0/0/0, complexity **down** from 1528 to 1513.
- Docs complete in EN and FR — three new pages each, six existing pages updated each. Diagram re-checked against `go list` (39 edges each side).
- **Verified in a real terminal**: `go build ./... — ok` with `(no output)`, `go vet ./... — exit 1` with its diagnostic, `gofmt -l -w . — ok` listing the file it rewrote, and the buffer reloading afterwards.

Earlier the same day, PR #6 merged the snippets work.

## In flight

Nothing. Finished through Phase 8.

## Next steps

1. **Commit and open the PR.** `feature/go-format-lint` is the branch.
2. **Finish ticket 0017.** It also asked for `go mod init` + `touch main.go` — creating a project from nothing, which is a different shape from running a command in an existing one (it needs a name, and there is no project yet). Not done, and the ticket should stay open for it.
3. **Tickets.** 0002, 0003, 0006, 0007, 0009, 0013, 0014 are implemented and open; 0017 is partly implemented.

## Open questions / blockers

- **A modal popup holds the whole editor while a command runs.** That was chosen knowingly and is documented, with `output = "terminal"` as the way out for any command it annoys you on. If it turns out to annoy generally, a non-modal output window is the alternative — and it is a different feature, not a tweak.
- **`output = "editor"` shows a popup first, then a window.** The popup is how you watch it and how you stop it; the window arrives when you close the popup. It reads oddly written down and is fine in use, but it is worth a second look with real output.

- **`go mod init` is not in the menu**, and does not fit the current shape: every other tool runs in the project that already exists, whereas this one creates it and needs a module path typed. It wants a prompt dialog and probably its own item rather than a `tools.toml` entry.
- **Nothing jumps to a compile error.** `go build` prints `internal/app/app.go:42:3: …` in the terminal window and you have to open the file yourself. Parsing that output and jumping would be a real win and is a feature of its own — it needs a per-language error format, and the output lives in a terminal emulator rather than in a captured buffer.
- **A menu panel still does not scroll** (carried over from the snippets handoff). A `tools.toml` with twenty entries draws a panel taller than the terminal and the bottom is clipped.

## Watch out for

- **Verify a new test by breaking the code it covers.** Two tests in this session passed while proving nothing, both because the test drove the step under test: `waitForLoopTurn` called `reloadAfterTools` itself (so the test passed with that step deleted from the loop — hence `App.tick`), and the process-group test killed the shell before it had forked (so it passed without the fix — it now waits for the child to print). Neither was visible by reading.
- **Stopping a command must take the process group.** Killing only the shell leaves a grandchild holding the output pipe, and the reading goroutine blocks until *it* ends — 20 seconds in the test suite, and for `go test ./...` it would be every test binary. `processGroup()` and `killGroup()` are build-tagged; `cmd.WaitDelay` is the backstop for anything that escapes.

- **A constructor that starts a goroutine must take its callbacks as parameters.** `terminal.NewView` started the reader and callers assigned `OnChange`/`OnExit` afterwards — a data race present since the terminal feature that `-race` never caught, because a shell takes longer to produce its first output than an assignment takes to run. `sh -c "echo x"` finished immediately and the detector fired at once. `ViewOptions` fixes it structurally; do not add an assignable callback back.
- **A finished terminal must not swallow keys.** It used to write every key to the dead shell, where the write failed silently and the key was consumed anyway, so `Ctrl-W` could never close the window. `View.HandleKey` returns false after `Exited()`, except for the scrolling keys. `TestAFinishedTerminalStopsTakingKeys` covers it.
- **`Buffer.Reload` must refuse over unsaved work.** That restriction is the whole safety of it. `TestReloadRefusesToThrowAwayUnsavedWork` pins it, and `TestAToolLeavesAModifiedBufferAlone` pins the app end of it.
- **`internal/buffer`'s atomic write was deliberately left out of `projectfile`.** It preserves the mode of the file it replaces, because it is saving over something the user already had; `projectfile` creates a file with a fixed `0644`. They look alike and are not the same operation — merging them would mean parameterising the mode and losing the different error text.
- **`projectfile.Write` already does `MkdirAll`.** The three `Create` functions used to do it themselves; do not add it back.
- **The app tests for tools drive the event loop by hand.** `Run` is not running, so `waitForLoopTurn` calls `a.reloadAfterTools()` itself. A test that waits for a reload without calling it will hang until its deadline.