Skip to content

Fix 5 TUI bugs: layout, viewport, history, completion, help#15

Merged
twokidsCarl merged 1 commit intomainfrom
fix/tui-round3-bugs
Apr 8, 2026
Merged

Fix 5 TUI bugs: layout, viewport, history, completion, help#15
twokidsCarl merged 1 commit intomainfrom
fix/tui-round3-bugs

Conversation

@twokidsCarl
Copy link
Copy Markdown
Owner

@twokidsCarl twokidsCarl commented Apr 8, 2026

Summary

Test plan

  • 98 TUI tests pass (3 new tests added)
  • Manual: verify no blank line below input area
  • Manual: def abc → enter → line 1 visible
  • Manual: /help all lines aligned

🤖 Generated with Claude Code

Summary by CodeRabbit

发布说明

  • 新功能

    • 添加鼠标交互支持,包括滚动聊天窗口、拖拽分割线调整布局、点击区域快速操作。
    • 增强制表符补全功能,现支持私有方法和 REPL 定义的自动补全。
  • 改进

    • 优化聊天面板布局和间距。
    • 改进多行输入时的键盘上下导航。
  • 测试

    • 新增多行输入和私有方法补全的测试用例。

- #4: Remove extra blank line below textarea (fix chat_height calc)
- #5: Reset textarea viewport offset after height change to show line 1
- #6: Up/down arrows navigate history from first/last line of multi-line
- #7: Include private_methods in tab completion for user-defined methods
- #10: Indent all lines of multi-line system messages consistently

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

走查(Walkthrough)

此PR增强了TUI组件的交互功能:优化聊天面板布局和消息渲染,扩展输入补全以支持私有方法和用户定义,实现鼠标交互(滚动、分隔线拖动、zone点击),以及动态加载可选的bubblezone依赖。

变更(Changes)

Cohort / File(s) 摘要
聊天面板与消息渲染
lib/claw/tui/chat_panel.rb
减少水平填充(宽度-4 → 宽度-2),增加视口垂直高度,对textarea视口偏移量进行显式重新计算。扩展render_messages方法以接受可选的zone:参数,并在消息折叠时将完整文本写入msg[:folded_full]。系统消息缩进处理更新为使用gsub(/^/, " ")
输入补全
lib/claw/tui/input_handler.rb
扩展completions方法以添加来自receiver的私有实例方法(private_methods(false))和追踪的REPL定义实例变量@__claw_definitions__中的键作为补全源。
鼠标交互与Zone支持
lib/claw/tui/model.rb
添加动态加载bubblezone依赖(带native扩展symlink创建)。扩展Model类:新增attr_accessor :chat_ratio, :dragging_dividerattr_reader :zone,新增内部视图尺寸属性。实现鼠标消息处理:滚轮调整chat视口,左键点击分隔线启动拖动,拖动时动态调整比例,release停止拖动。zone点击处理支持snap/mem/tool/fold标识符解析。优化键盘导航:在textarea行边界处防止历史导航。
测试
spec/claw/tui/model_spec.rb
新增多行输入的历史导航测试和私有方法tab补全测试,覆盖up/down键在多行条目间的导航行为。
版本
lib/claw/version.rb
Claw::BUILD常量从"20260407-011"更新为"20260407-014"

可能相关的PR

预估代码审查工作量

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly references five specific bug fixes that align with the PR objectives: layout (chat_height off-by-one), viewport (textarea offset reset), history (up/down navigation), completion (private methods), and help (system message indentation).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tui-round3-bugs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
lib/claw/tui/model.rb (2)

25-25: 静态分析提示:异常捕获顺序可优化。

当前 rescue LoadError, Gem::MissingSpecError, Errno::EEXIST, Errno::EACCES 的顺序虽然在 Ruby 中不会导致实际的遮蔽问题(因为它们不是继承关系),但按照惯例,建议将更具体的异常放在前面以提高代码可读性。

此外,空的 rescue 块可以添加注释说明预期行为。

♻️ 建议改进
-  rescue LoadError, Gem::MissingSpecError, Errno::EEXIST, Errno::EACCES
-    # mouse click zones will be disabled but scroll/drag still work.
+  rescue Errno::EEXIST, Errno::EACCES => e
+    # Symlink already exists or permission denied - zones disabled
+  rescue LoadError, Gem::MissingSpecError
+    # bubblezone gem not available - zones disabled
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/tui/model.rb` at line 25, Reorder the exceptions in the rescue
clause in lib/claw/tui/model.rb to list the more specific Errno exceptions
before the others (e.g., Errno::EEXIST, Errno::EACCES, then LoadError,
Gem::MissingSpecError) to follow convention and improve readability, and add a
brief comment inside the currently empty rescue block explaining why the
exception is being ignored or how it’s handled (so the intent is clear to future
readers).

7-28: 在运行时创建符号链接到 gem 目录存在潜在风险。

这种动态修补 bubblezone 原生扩展路径的方法有几个问题:

  1. 权限问题:写入 gem 安装目录可能需要 root/管理员权限
  2. 并发风险:多个进程同时运行可能导致竞态条件
  3. 污染 gem 目录:在 gem 目录中创建文件可能影响其他使用该 gem 的应用

建议考虑更安全的替代方案,例如在用户可写目录中处理,或者将此作为安装时的设置步骤而非运行时修补。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/tui/model.rb` around lines 7 - 28, The runtime symlink creation to
the gem installation (target/source/spec/versioned_dir) is risky; instead,
detect the versioned native-extension dir (versioned_dir), and if present
prepend it to $LOAD_PATH (or require the extension directly from that dir) and
then require "bubblezone" (or "bubblezone/bubblezone"), rather than creating a
filesystem symlink; ensure this change is implemented around the existing rescue
block that handles require "bubblezone" (the begin/rescue and inner begin where
spec, versioned_dir, target, source are used), and keep the existing exception
handling for LoadError, Gem::MissingSpecError, Errno::EACCES, Errno::EEXIST
while logging or skipping mouse zones when permission/concurrency prevents using
the versioned dir.
lib/claw/tui/chat_panel.rb (1)

17-18: 通过设置更详尽的注释来说明内部变量操作的必要性。

当前代码使用 instance_variable_set 直接修改 @viewport_offset 是为了修复 TextArea 高度变化后视口偏移陈旧的问题。这是针对 Bubbles 组件的已知限制(未提供公共 API 来重置视口状态)而采取的临时措施。

建议将注释扩展为:

# Workaround: Recalculate viewport offset after height change because Bubbles::TextArea
# does not provide a public API to reset viewport state. Without this, the viewport
# position becomes stale, causing display bugs. This should be revisited if bubbles-ruby
# adds a public viewport_offset setter or similar method.
ta.instance_variable_set(:`@viewport_offset`, [ta.row - ta.height + 1, 0].max)

这样可以帮助未来的维护者理解这不是任意的 hack,而是对已知问题的必要应对。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/tui/chat_panel.rb` around lines 17 - 18, Update the inline comment
above the instance_variable_set call to clearly explain why we mutate the
internal variable: state that this is a deliberate workaround for
Bubbles::TextArea lacking a public API to reset viewport state, describe that
without resetting `@viewport_offset` the viewport becomes stale after a height
change (causing display bugs), and note this should be revisited if bubbles-ruby
adds a public viewport_offset setter or equivalent; reference the
ta.instance_variable_set(:`@viewport_offset`, [ta.row - ta.height + 1, 0].max)
line and the TextArea/ta.row and ta.height usage in the comment so future
maintainers understand it is intentional and temporary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/claw/tui/chat_panel.rb`:
- Around line 61-64: The call to Folding.fold_text in chat_panel.rb passes
unknown keyword args (zone: and fold_id:) that don't exist on Folding.fold_text
(defined as def self.fold_text(text, threshold: 10) in folding.rb), causing an
ArgumentError; fix by either removing the extra keywords from the call (change
folded = Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx) to folded
= Folding.fold_text(rendered.rstrip) and keep using folded[:folded] /
folded[:full] as before) or by updating the Folding.fold_text signature to
accept zone: and fold_id: (e.g., def self.fold_text(text, threshold: 10, zone:
nil, fold_id: nil)) and handle them inside the method; reference
Folding.fold_text, the call site in chat_panel.rb, and the zone / idx variables
when making the change.

In `@lib/claw/tui/model.rb`:
- Around line 221-230: Folding.fold_text currently only accepts (text,
threshold: 10) but callers (e.g., the code that calls
Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx)) pass keyword args
zone: and fold_id:, causing a mismatch; update the Folding.fold_text method
signature to accept those keywords (e.g., add zone: nil, fold_id: nil) and
ensure the implementation uses or safely ignores them (and maintain threshold:
default) so existing calls succeed without changing callers.

In `@lib/claw/version.rb`:
- Line 5: Update the BUILD constant in version.rb to reflect today's date and
reset the daily counter: change the BUILD constant value from "20260407-014" to
"20260408-001" (format YYYYMMDD-NNN, increment/reset per coding guideline) so
the BUILD constant matches the current change date.

---

Nitpick comments:
In `@lib/claw/tui/chat_panel.rb`:
- Around line 17-18: Update the inline comment above the instance_variable_set
call to clearly explain why we mutate the internal variable: state that this is
a deliberate workaround for Bubbles::TextArea lacking a public API to reset
viewport state, describe that without resetting `@viewport_offset` the viewport
becomes stale after a height change (causing display bugs), and note this should
be revisited if bubbles-ruby adds a public viewport_offset setter or equivalent;
reference the ta.instance_variable_set(:`@viewport_offset`, [ta.row - ta.height +
1, 0].max) line and the TextArea/ta.row and ta.height usage in the comment so
future maintainers understand it is intentional and temporary.

In `@lib/claw/tui/model.rb`:
- Line 25: Reorder the exceptions in the rescue clause in lib/claw/tui/model.rb
to list the more specific Errno exceptions before the others (e.g.,
Errno::EEXIST, Errno::EACCES, then LoadError, Gem::MissingSpecError) to follow
convention and improve readability, and add a brief comment inside the currently
empty rescue block explaining why the exception is being ignored or how it’s
handled (so the intent is clear to future readers).
- Around line 7-28: The runtime symlink creation to the gem installation
(target/source/spec/versioned_dir) is risky; instead, detect the versioned
native-extension dir (versioned_dir), and if present prepend it to $LOAD_PATH
(or require the extension directly from that dir) and then require "bubblezone"
(or "bubblezone/bubblezone"), rather than creating a filesystem symlink; ensure
this change is implemented around the existing rescue block that handles require
"bubblezone" (the begin/rescue and inner begin where spec, versioned_dir,
target, source are used), and keep the existing exception handling for
LoadError, Gem::MissingSpecError, Errno::EACCES, Errno::EEXIST while logging or
skipping mouse zones when permission/concurrency prevents using the versioned
dir.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 31f86579-8514-48bb-a4e1-fd5c62f041fd

📥 Commits

Reviewing files that changed from the base of the PR and between acaf481 and b403308.

📒 Files selected for processing (5)
  • lib/claw/tui/chat_panel.rb
  • lib/claw/tui/input_handler.rb
  • lib/claw/tui/model.rb
  • lib/claw/version.rb
  • spec/claw/tui/model_spec.rb

Comment on lines +61 to +64
folded = Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx)
if folded[:folded]
msg[:folded_full] = folded[:full]
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 验证 Folding.fold_text 方法签名
ast-grep --pattern $'def self.fold_text($$$) {
  $$$
}'

# 搜索完整的 fold_text 方法定义
rg -n "def self\.fold_text" --type ruby -A 5

Repository: twokidsCarl/ruby-claw

Length of output: 439


🏁 Script executed:

# 查看完整的 folding.rb 文件
cat lib/claw/tui/folding.rb

# 查看 chat_panel.rb 的相关行
sed -n '55,70p' lib/claw/tui/chat_panel.rb

Repository: twokidsCarl/ruby-claw

Length of output: 4104


🏁 Script executed:

# 搜索所有 fold_text 定义
rg -n "def.*fold_text" lib/claw/tui/ -A 3

# 查看 chat_panel.rb 第55-70行的完整上下文
sed -n '55,70p' lib/claw/tui/chat_panel.rb

# 确认这是否是实际存在的问题
rg -n "fold_text.*zone:" lib/claw/tui/

Repository: twokidsCarl/ruby-claw

Length of output: 1131


Folding.fold_text 方法签名不匹配 — 将导致运行时 ArgumentError

根据 lib/claw/tui/folding.rb:15Folding.fold_text 方法签名为:

def self.fold_text(text, threshold: 10)

但在 lib/claw/tui/chat_panel.rb:61 处调用时传递了 zone:fold_id: 参数,这些参数在方法定义中不存在:

folded = Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx)

会导致运行时 ArgumentError: unknown keywords: zone, fold_id。请移除这两个参数或更新 fold_text 方法签名以接受它们。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/tui/chat_panel.rb` around lines 61 - 64, The call to
Folding.fold_text in chat_panel.rb passes unknown keyword args (zone: and
fold_id:) that don't exist on Folding.fold_text (defined as def
self.fold_text(text, threshold: 10) in folding.rb), causing an ArgumentError;
fix by either removing the extra keywords from the call (change folded =
Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx) to folded =
Folding.fold_text(rendered.rstrip) and keep using folded[:folded] /
folded[:full] as before) or by updating the Folding.fold_text signature to
accept zone: and fold_id: (e.g., def self.fold_text(text, threshold: 10, zone:
nil, fold_id: nil)) and handle them inside the method; reference
Folding.fold_text, the call site in chat_panel.rb, and the zone / idx variables
when making the change.

Comment thread lib/claw/tui/model.rb
Comment on lines +221 to +230
when /\Afold_(\d+)\z/
idx = $1.to_i
if idx < @chat_history.size
m = @chat_history[idx]
if m[:folded_full]
m[:content] = m[:folded_full]
m.delete(:folded_full)
end
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 搜索 Folding 模块的完整定义
rg -n "module Folding" --type ruby -A 50

Repository: twokidsCarl/ruby-claw

Length of output: 3431


🏁 Script executed:

# Search for all calls to Folding.fold_text
rg -n "fold_text" --type ruby -B 2 -A 2

Repository: twokidsCarl/ruby-claw

Length of output: 814


🏁 Script executed:

# Check chat_panel.rb around lines 61-64
fd "chat_panel.rb" --type f | head -1 | xargs wc -l

Repository: twokidsCarl/ruby-claw

Length of output: 94


🏁 Script executed:

# Once we know file size, examine chat_panel.rb lines around 61-64
fd "chat_panel.rb" --type f | head -1 | xargs cat -n | sed -n '55,75p'

Repository: twokidsCarl/ruby-claw

Length of output: 1152


🏁 Script executed:

# Also check model.rb around lines 221-230 to see full context
fd "model.rb" --path "*/tui/*" --type f | head -1 | xargs cat -n | sed -n '215,240p'

Repository: twokidsCarl/ruby-claw

Length of output: 280


🏁 Script executed:

# Get model.rb file and show lines around 221-230
rg -n "fold_" lib/claw/tui/model.rb -B 3 -A 8 | head -50

Repository: twokidsCarl/ruby-claw

Length of output: 473


🏁 Script executed:

# Check if there are multiple fold_text method definitions
rg -n "def.*fold_text" --type ruby

Repository: twokidsCarl/ruby-claw

Length of output: 137


Folding.fold_text 方法签名与调用不匹配。

chat_panel.rb:61 调用 Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx) 传递了 zone:fold_id: 参数,但 folding.rb:15 中的方法定义只接受 textthreshold: 10。这将导致运行时错误。

需要更新 Folding.fold_text 的方法签名以支持这些参数,或者修改调用代码以移除这些参数。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/tui/model.rb` around lines 221 - 230, Folding.fold_text currently
only accepts (text, threshold: 10) but callers (e.g., the code that calls
Folding.fold_text(rendered.rstrip, zone: zone, fold_id: idx)) pass keyword args
zone: and fold_id:, causing a mismatch; update the Folding.fold_text method
signature to accept those keywords (e.g., add zone: nil, fold_id: nil) and
ensure the implementation uses or safely ignores them (and maintain threshold:
default) so existing calls succeed without changing callers.

Comment thread lib/claw/version.rb
module Claw
VERSION = "0.2.2"
BUILD = "20260407-011"
BUILD = "20260407-014"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

BUILD 日期已过时。

根据编码指南,BUILD 常量应使用 YYYYMMDD-NNN 格式,其中日期应为代码更改时的当前日期。当前 BUILD 为 20260407-014(4月7日),但今天是4月8日。应更新为 20260408-001

🔧 建议修复
-  BUILD = "20260407-014"
+  BUILD = "20260408-001"

根据编码指南:Increment the BUILD constant in lib/claw/version.rb every time you change code, using format YYYYMMDD-NNN where NNN is a sequential number starting at 001 each day

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
BUILD = "20260407-014"
BUILD = "20260408-001"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/claw/version.rb` at line 5, Update the BUILD constant in version.rb to
reflect today's date and reset the daily counter: change the BUILD constant
value from "20260407-014" to "20260408-001" (format YYYYMMDD-NNN,
increment/reset per coding guideline) so the BUILD constant matches the current
change date.

@twokidsCarl twokidsCarl merged commit 977bdb1 into main Apr 8, 2026
7 checks passed
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