Handoff — 2026-08-31 — window frame boxes
State
Done and green, on branch feature/windows-buttons, uncommitted.
The window frame now carries two boxes: [x] at the top left closes, [■] at the top right fills the desktop and then reads [▬] so the box always says what pressing it will do. Window ▸ Maximise is the same toggle, through the same Desktop.ToggleMaximize.
╔═[x]═════════════════════════════ main.go ═════════════════════════════1═[■]╗
╔═[x]══════════════════════════════ main.go ══════════════════════════════1═[▬]╗ (maximised)
internal/uiat 93.0 %; 14 new tests including a property test over widths 16…60.- Whole suite green under
-race. Quality gate PASS first time: 0 errors, 0 warnings, 0 smells, complexity 1156. - Docs updated in EN and FR — keyboard, menus, the terminal how-to, and a new section in design-decisions — plus
internal/ui/README.mdand the root README's ASCII screenshot. No package added, so the drawio diagram is unchanged and still matchesgo list. - Verified by rendering the real binary through the project's own VT emulator: both symbols, the fill, and the restore.
Project settings were merged to main as PR #2 before this session started.
Later the same day, on the same branch, a bug the user reported was also fixed: OK did nothing in the Open dialog. FileDialog never wired ListBox.OnSelect, so highlighting a file never reached the Name field and confirm() had no path to act on. The field now mirrors the highlight, and OK falls back to the highlight when the field is empty. 11 tests, three of them end-to-end through the app (mouse click on OK, Tab+Enter, Alt-O); all the behavioural ones were confirmed failing against the original code first.
In flight
Nothing. Finished through Phase 8.
Next steps
- Commit and open the PR.
feature/windows-buttonsis the branch. - Click both boxes with an actual mouse. Everything was driven through code and through the emulator; nobody has yet pressed either box by hand. That is the one check left, and mouse hit-testing on a three-cell target is exactly where an off-by-one would hide.
- Tickets. 0002 and 0007 are implemented and still
state: open; there is no ticket for either change in this session.
Unexplained, and worth knowing
One full-suite -race run failed in internal/terminal and I could not reproduce it. The summary line said FAIL internal/terminal 0.217s; the failing test's own output was not captured, and the run happened alongside gofmt and go vet in the same shell command. Since then: 22 full-suite runs, 37 runs of internal/terminal alone, and a run under deliberate parallel load — all green. The package's tests fork real shells through /dev/ptmx, so contention is a plausible cause, but that is a guess and not a diagnosis. If it appears again, capture the whole output before anything else; a summary line is not enough to work from.
Open questions / blockers
- The Open dialog starts with the focus on the Name field, not the list. So the first
↓only moves the focus and appears to do nothing, and the second moves the highlight. It is documented now, but it is still a small surprise, and it is what made the original bug report hard to reproduce on the first try. Moving the initial focus to the list would fix the surprise and would make Save As worse, where typing a name is the point — worth a decision rather than a silent change. - Otherwise none. The toggle-with-a-changing-symbol decision was the user's, up front.
Watch out for
drawNumberruns afterdrawTitleBar, so the number always wins. This makes the obvious test vacuous: asserting that the close box, the number and the maximise box are intact after drawing passes whatever the title margin is, because the number is simply repainted over whatever the title left. I wrote that test, put the wrong margin back to check it, and it passed. What actually breaks is the title, which loses a character and readsmain.g7.assertFurnitureIntactnow checks the cell beside each piece of furniture; with the margin off by one it fails on"…" sits against the number.reservedForFurnitureis5 + numberOffset, and that is arithmetic, not a constant. Five on the left for corner, frame and close box;numberOffseton the right for the number, the maximise box and the corner. If you move either offset, the margin follows automatically — andTestTheTitleNeverRunsIntoTheFurnitureis what proves it, so do not weaken it."[■]"is 5 bytes and 3 columns. The same trap that produced a real off-by-two in the close box now applies to the maximise box.boxWidthis the width in cells andTestTheCloseBoxIsThreeColumnsWidechecks all three labels against it.- A maximised window's restore rectangle has to follow the desktop.
Window.followDesktopdoes it. RevertingDesktop.SetBoundsto the plainw.SetBounds(w.fitInto(...))would look like a simplification and would put a restored window partly off screen after the terminal is shrunk;TestAMaximisedWindowFollowsATerminalThatShrinkscovers it. TileandCascademust go throughWindow.place, notSetBounds.placeclears the maximised flag. UsingSetBoundsleaves a tiled window offering to restore to a rectangle that no longer means anything.- A callback nobody wires is invisible.
ListBox.OnSelectexisted, worked, and had its own passing test ininternal/ui— andFileDialognever set it. Every test stayed green while the dialog's two controls drifted apart. When adding a callback to a widget, check that something actually assigns it. strings.Indexon a drawn screen row is a byte offset, not a column. A row is full of░and║at three bytes each, so clicking that offset lands about thirty columns too far right. This produced a confident, wrong diagnosis of "a second bug in the mouse path" before I looked.findOnScreenindialogs_test.gocounts runes; use it.Desktop.AddsetsOnMaximizeevery time, andFocusisRemove+Add, so it is reassigned on every focus change. That is harmless — the closure is equivalent — but do not add state to it that would be lost.
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 |
|