Skip to content

feat(tui): todos pane open by default (+show-todo-pane config) with safe focus UX - #136

Open
Emasoft wants to merge 5 commits into
mlhher:mainfrom
Emasoft:feat/todos-pane-default-open
Open

Emasoft wants to merge 5 commits into
mlhher:mainfrom
Emasoft:feat/todos-pane-default-open

Conversation

@Emasoft

@Emasoft Emasoft commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Update 2026-09-25

New since the original description (b148100): the todos pane now also releases focus on any printable key that arrives as a multi-rune sequence. Dead-key and combining-character input (e.g. e + combining acute) is delivered as a single event carrying multiple runes, and the old len(runes)==1 check missed it — so those sequences kept holding focus. Focus now releases on the first printable non-control rune, so dead-key/combining input falls through to the input like any other key.


The todos pane now starts open — and focusing it can no longer trap your typing.

  • Open by default. config.json "show-todo-pane": false restores closed-at-start. Under 85 columns the pane stays closed (silently — the width guard exists to keep narrow terminals usable, not to nag).
  • Focus is visible. A focused pane gets a background/border change and a [focused · esc to unfocus] marker, so you can always tell where your keys are going.
  • No typing trap. While focused, the first printable non-nav key releases focus and falls through — the same keypress lands in the input. The pane can never silently swallow j/k/g.
  • Mouse escape hatch. Click inside the pane to focus it, click anywhere outside to release focus.
  • Unfocused = inert. When not focused, the pane provably consumes no keys: routing regression tests assert nav keys and plain keys land in the input.

New config key: show-todo-pane (bool, default true).

Split out of #133 (TUI changes evaluated separately, per review).

Testing

$ go test ./... -race -count=1
ok  	late/cmd/late	2.800s
?   	late/cmd/mcp-run	[no test files]
ok  	late/internal/agent	1.451s
?   	late/internal/assets	[no test files]
ok  	late/internal/client	2.346s
ok  	late/internal/common	4.156s
ok  	late/internal/config	1.641s
ok  	late/internal/executor	11.817s
ok  	late/internal/git	3.908s
ok  	late/internal/mcp	3.230s
ok  	late/internal/orchestrator	5.460s
?   	late/internal/pathutil	[no test files]
ok  	late/internal/plugin	14.308s
ok  	late/internal/session	1.962s
ok  	late/internal/skill	4.950s
ok  	late/internal/tool	4.589s
ok  	late/internal/tool/ast	5.160s
ok  	late/internal/tui	11.032s

exit 0 — all 18 packages green (15 ok, 3 without test files).

  • gofmt -l on the touched files: no output (clean)
  • go vet ./cmd/late ./internal/config ./internal/tui: clean
  • New tests: startup default + /todo toggle + silent width guard (todo_pane_test.go), focus rendering/marker/background (todo_pane_test.go), unfocused pane passes keys through, printable-key release-and-fall-through, click-in/click-out focus (todo_routing_test.go)

ResolveShowTodoPane treats an absent show-todo-pane entry (or nil config)
as open, explicit false as closed, explicit true as open. main.go resolves
it after config load and sets model.ShowTodoPane BEFORE the initial
SetSize, so updateLayout reserves the 44-col side-pane width at >= 85
columns; below the threshold the same layout path silently starts with the
pane closed (no startup toast — /todos still toasts when opened later on a
narrow terminal).
Focused pane gets an elevated background (chipBgColor) and a primary
border plus a '[focused · esc to unfocus]' title marker; unfocused pane
is unchanged.
A printable key while the pane is focused releases focus and falls
through to the input (j/k/g stay scroll keys while focused, now visible
via the focus highlight); clicking outside the pane also releases focus;
an unfocused pane provably consumes no keys.
… key text

Dead-key / combining sequences deliver multi-rune Key.Text ("e"+U+0301
in one event); the old len(runes)==1 check missed them and kept trapping
typing in the focused pane. Release on the first printable non-control
rune instead.

Review fix cherry-picked from 0a97d2f (todo-pane multi-rune portion and
todo_routing_test.go additions only).
golangci-lint v2.13.2 flags internal/tui/update.go:270:
SA6003 should range over string, not []rune(string). Ranging over a
string already yields runes; drop the redundant conversion. Behavior
identical.

Reproduced locally with golangci-lint v2.13.2; full
go test ./... -race -count=1 green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant