Skip to content

feat(tui): persist /model picker selection + fix release-event double-fire#162

Merged
yishuiliunian merged 3 commits into
mainfrom
worktree-sleepy-meandering-nest
May 16, 2026
Merged

feat(tui): persist /model picker selection + fix release-event double-fire#162
yishuiliunian merged 3 commits into
mainfrom
worktree-sleepy-meandering-nest

Conversation

@yishuiliunian
Copy link
Copy Markdown
Contributor

Summary

  • /model picker 中切换 model / thinking 现在会持久化到 <cwd>/.loopal/settings.local.json,下次启动自动恢复。之前只在内存生效,重启就丢。
  • 修复 input handler 未过滤 KeyEventKind::Release 的健壮性缺陷:在启用 kitty keyboard protocol 的终端(kitty / WezTerm / ghostty / iTerm2 等)上,每次按键会同时收到 Press + Release,导致 picker 的 Left/Right cycle 双触发、Char 输入 filter 翻倍。

Changes

持久化(commit 1)

  • crates/loopal-config/src/local_writer.rs (new) — update_local_settings_field(cwd, key, value),原子写入;用 serde_json::Value 而非 Settings 反序列化以保留 override-only 语义
  • crates/loopal-runtime/src/agent_loop/input_control.rsModelSwitch / ThinkingSwitch 在更新内存后 best-effort persist(失败仅 log,模式同已有的 Clear marker)
  • 5 个单测覆盖:创建文件 / 保留其他字段 / 覆盖同键 / 拒绝非 object root / 字符串字段写入

TUI 健壮性 + picker UX(commit 2)

  • crates/loopal-tui/src/input/mod.rshandle_key 入口过滤 !Press|Repeat,所有下游 handler 自动受益
  • crates/loopal-tui/src/command/model_cmd.rs — 打开 picker 时把当前 model 置顶(避免大列表中找不到)
  • crates/loopal-tui/src/views/picker.rsrender_thinking_indicatorLine::width() 替代手算估算
  • 5 个单测:Left/Right cycle + wrap + Release 防回归 + Char filter 防双触

Test plan

  • CI passes
  • 手测:进入仓库 cwd → /model 切换 thinking → 退出 → 检查 .loopal/settings.local.jsonthinking 字段 → 重启 picker 显示已记住选择

…l.json

ControlCommand::ModelSwitch and ThinkingSwitch previously updated only the
in-memory AgentConfig, so the user's picker selection was lost on restart.
Persist both fields to <cwd>/.loopal/settings.local.json (gitignored Local
layer), letting the next session load the choice via the existing config
pipeline.

Writer operates on serde_json::Value rather than Settings to preserve
override-only semantics (Settings uses #[serde(default)] everywhere, which
would inflate the file with every default value on round-trip).
Terminals that enable kitty keyboard protocol (kitty / WezTerm / ghostty /
iTerm2 with DISAMBIGUATE_ESCAPE_CODES) emit both Press and Release for every
keystroke. The model picker's Left/Right cycle handler would then advance
thinking_selected twice per physical keypress, and the Char-based filter
input would duplicate every typed character.

Drop everything that isn't a real Press/Repeat at the input::handle_key
entry so all downstream handlers stay correct without per-handler guards.

Also surface current-model-first ordering and use indicator.width() for
exact width budgeting in render_thinking_indicator.
@yishuiliunian yishuiliunian merged commit 963613d into main May 16, 2026
4 checks passed
@yishuiliunian yishuiliunian deleted the worktree-sleepy-meandering-nest branch May 16, 2026 02:29
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