Skip to content

fix(cli): persist /verbose toggle across CLI sessions (#3312)#3827

Closed
NkAntony777 wants to merge 1 commit into
esengine:main-v2from
NkAntony777:fix/3312-verbose-persistence
Closed

fix(cli): persist /verbose toggle across CLI sessions (#3312)#3827
NkAntony777 wants to merge 1 commit into
esengine:main-v2from
NkAntony777:fix/3312-verbose-persistence

Conversation

@NkAntony777

Copy link
Copy Markdown
Contributor

Summary

The /verbose (and Ctrl+O) toggle previously reset to off on every CLI
launch because the in-memory showReasoning bool was never written
back to the config. This adds an optional ui.show_thinking field
(default false, fully backward-compatible) and persists every flip
off the bubbletea event loop so the next session opens with the
reasoning block in the same state.

Fixes #3312.

Changes

  • internal/config/config.go — new UIConfig.ShowThinking bool with toml:"show_thinking", defaults to false.
  • internal/config/render.goRenderTOMLForScope emits show_thinking = true only when set, with a commented-out placeholder only in RenderScopeFull so unmodified project configs stay byte-identical.
  • internal/cli/cli.gochatREPL now calls a new m.applyConfig(cfg) helper.
  • internal/cli/chat_tui.go
    • new applyConfig(cfg) method: seeds the TUI from a loaded config, using OR semantics so a persisted ShowThinking=true does not stomp the Termux native-scrollback default.
    • toggleVerboseReasoning spawns a goroutine that calls persistShowThinking(show), capturing show into a local first so the save goroutine reads a stable value (no race with the Update goroutine's write to m.showReasoning).
    • new persistShowThinking(show bool) error method: writes the preference to the user-level config via SaveTo(config.UserConfigPath()).

Design notes

Why user config, not project config. show_thinking is a personal
viewing preference; writing it to a project-local reasonix.toml that
may be git-committed would leak the user's setting to every
collaborator on the repo. The same rationale as NotificationsConfig
et al. — viewing preferences belong in ~/.config/reasonix/config.toml.
Note: a project reasonix.toml with show_thinking = false will
silently win on the next load (project > user priority in Load),
which matches the long-standing merge semantics.

Why async save. The bubbletea Update() goroutine must not block
on disk I/O. SaveTo's atomic temp+rename makes racing writes safe on
disk (last-writer-wins, never a torn file); a coalescer/debouncer is
left as a possible follow-up.

Scope of the fix. Per the issue, the --show-thinking flag in
non-interactive run mode and the desktop app's reasoning toggle
(internal/serve/, desktop/) are out of scope and explicitly
unaffected. The web/desktop frontends have their own
TextSink.showReasoning field.

Tests

go test ./internal/config/  → ok
go test ./internal/cli/     → ok

New tests (all passing):

Test Covers
TestToggleVerboseReasoningPersistsShowThinking full on→off round-trip via real disk using isolateUserConfig + LoadForEdit
TestPersistShowThinkingNoConfigIsNoop nil cfg early-return (production toggle relies on this)
TestApplyConfigRespectsShowThinking boot-time load path (the half of the bug that the round-trip test alone wouldn't catch)
TestApplyConfigKeepsTermuxVerboseOn OR semantics — Termux default-on is not stomped by an absent preference
TestApplyConfigNilIsNoop defensive nil guard on applyConfig
TestProjectRenderShowThinkingScoped RenderScopeProject emits the key only when set

The applyConfig extraction is the testability-driven refactor that
closes the gap identified in review: previously, deleting the four
applyConfig lines would still pass every existing test. The new
TestApplyConfigRespectsShowThinking covers that regression.

Manual verification

  1. Build: go build ./cmd/reasonix
  2. Launch: ./reasonix chat (or reasonix.exe on Windows)
  3. Type /verbose → notice "verbose on — thinking text will be shown"
  4. /quit
  5. Reopen: reasoning block is expanded by default
  6. Inspect ~/.config/reasonix/config.toml (or %AppData%\reasonix\config.toml on Windows):
    [ui]
    show_thinking = true
  7. Type /verbose again → notice "verbose off"
  8. Reopen → reasoning block is collapsed, and the config now shows the field with the default value (commented placeholder in the full-render path).

Out of scope

  • Debouncing rapid toggles (current behavior: 10 toggles = 10 atomic writes, all safe via temp+rename).
  • Surfacing a user-visible notice when Save fails (currently slog.Warn; the in-TUI notice fires regardless of save success — matches the project's existing preference-persistence pattern, e.g. /effort).
  • The web/desktop frontends' reasoning toggles.

The /verbose (and Ctrl+O) toggle previously reset to off on every CLI
launch because the in-memory showReasoning bool was never written back
to the config. Adds a new optional ui.show_thinking field (default
false, backward-compatible), wires it into newChatTUI's load path, and
persists the toggle off the bubbletea event loop on every flip.

The save targets the user config (config.UserConfigPath), not the
project-local reasonix.toml, because show_thinking is a personal
viewing preference — writing it to a project file that may be
git-committed would leak the user's setting to every collaborator.

The toggle captures show into a local before spawning the goroutine
and passes it as a function argument, so the Go memory model's
happens-before edge protects the save goroutine from racing the
Update goroutine's write to m.showReasoning. (Go doesn't tear bools,
but the race detector would still flag the pattern.)

Extracts applyConfig from chatREPL so the load path is unit-testable
without going through the full bubbletea bootstrap, and adds a
RenderScopeProject render test for the new field's scope-aware
emission.

Coverage:
- TestToggleVerboseReasoningPersistsShowThinking (full on/off round-trip via disk)
- TestPersistShowThinkingNoConfigIsNoop (nil cfg guard)
- TestApplyConfigRespectsShowThinking (load path opens reasoning)
- TestApplyConfigKeepsTermuxVerboseOn (OR semantics with Termux default)
- TestApplyConfigNilIsNoop (defensive nil guard)
- TestProjectRenderShowThinkingScoped (render scopes covered)
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development tui Terminal UI / CLI (internal/cli, internal/control) config Configuration & setup (internal/config) labels Jun 10, 2026
@esengine

Copy link
Copy Markdown
Owner

Thanks for the careful work here, including the clean config rendering compatibility — the diagnosis of #3312 was exactly right. In the end the /verbose persistence landed via #3919 (re-land of #3396), which bundles it with the related expand_thinking toggle under the ui.show_reasoning key, so I'm closing this one as superseded rather than carrying two config keys for the same preference. Sorry the two efforts crossed — your write-up on the bubbletea event-loop persistence timing was useful when reviewing the landed version.

@esengine esengine closed this Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config Configuration & setup (internal/config) tui Terminal UI / CLI (internal/cli, internal/control) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/verbose: toggle state not persisted across CLI sessions

2 participants