Skip to content

Commit f61267e

Browse files
josephweckerClaude-42d677c5
andcommitted
Merge session/42d677c5-chat-debug-panel: Debug panel with ScrollListener
Implemented chat command debug panel with principled Pinax abstractions. FEATURES: - --debug-panel flag for chat command (default: true) - Fixed 65-column right panel, flexible content area - SemanticLogger integration via SidePanelAppender - ANSI-aware text wrapping (Pinax::Text) - Panel redraws on scroll and input resize ARCHITECTURE: - ScrollListener protocol for decoupled scroll events - Layout::Composed emits events, SidePanel implements protocol - Clean separation keeps Pinax gem-extractable NEW FILES: - lib/pinax/layout/scroll_listener.rb - lib/pinax/text.rb (wrap, truncate, visible_length, strip_ansi) - lib/autopax/cli/side_panel_appender.rb TESTS: 926 examples, 0 failures Session: 42d677c5-6fc9-4bf4-ba21-f3a1e67ebcbd Branch: session/42d677c5-chat-debug-panel Commits: 17 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude-42d677c5 <claude@v2.io>
2 parents 140ee79 + cd10dda commit f61267e

11 files changed

Lines changed: 748 additions & 118 deletions

File tree

HANDOFF.md

Lines changed: 33 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,62 @@
11
# Handoff Notes for Next Agent
22

33
**Last Updated:** 2025-12-19
4-
**Current State:** Side panel implementation COMPLETE - ready for review/merge
4+
**Current State:** Debug panel integration COMPLETE and merged
55

66
---
77

8-
## Completed: Pinax Side Panel Implementation
8+
## Recently Completed: Chat Command Debug Panel (Session 42d677c5)
99

10-
**Worktree:** `/Users/josephwecker-v2/src/autopax-worktrees/42d677c5-pinax-side-panel`
11-
**Branch:** `session/42d677c5-pinax-side-panel`
10+
### Summary
1211

13-
### What Was Built
12+
Implemented a debug panel for the chat command that shows telemetry (API timing,
13+
token counts, tool execution) in a right-side panel. The panel uses a principled
14+
ScrollListener protocol for proper abstraction.
1415

15-
A fixed right-side debug panel for terminal UIs that preserves native scrollback.
16+
### Key Features
1617

17-
**Pattern: Redraw-After-Scroll**
18-
```
19-
Before scroll: Clear top N rows of panel area (just spaces)
20-
Scroll: Native DECSTBM scroll (spaces go into scrollback - clean!)
21-
After scroll: Redraw the panel
22-
```
23-
24-
Tested and working in Ghostty, Kitty, and iTerm.
25-
26-
### New Files
27-
28-
- `lib/pinax/side_panel.rb` - Main SidePanel class (buffer, config, public API)
29-
- `lib/pinax/side_panel/rendering.rb` - Extracted rendering methods
30-
31-
### Changed Files
18+
- `--debug-panel` flag (default: true, `--no-debug-panel` to disable)
19+
- Fixed 65-column panel on right side (content area flexes)
20+
- SemanticLogger integration routes all log output to panel
21+
- ANSI-aware text wrapping via `Pinax::Text`
22+
- Panel redraws properly on scroll and input resize
3223

33-
- `lib/pinax/layout/composed.rb` - Added side_panel integration
34-
- `lib/pinax/layout/region_builder.rb` - Uses content_width for history region
35-
- `spec/pinax/side_panel_spec.rb` - 22 specs for SidePanel
36-
- `spec/pinax/layout/composed_spec.rb` - 11 new specs for side panel integration
24+
### Architecture: ScrollListener Protocol
3725

38-
### Usage
26+
The side panel uses a clean observer pattern for scroll events:
3927

4028
```ruby
41-
layout = Pinax::Layout::Composed.new
42-
layout.setup!(
43-
input_lines: 4,
44-
content_width: 80, # Wrap history at 80 cols
45-
side_panel: { header: 'Debug' } # Create panel in remaining space
46-
)
47-
48-
layout << "[user] Hello" # Auto-clears panel, scrolls, redraws
49-
layout.side_panel.log("API: 203ms") # Add log entries
29+
module Pinax::Layout::ScrollListener
30+
def before_scroll(lines) # Called before content scrolls
31+
def after_scroll # Called after content scrolled
32+
end
5033
```
5134
52-
---
35+
`Layout::Composed` emits scroll events; `SidePanel` implements the protocol.
36+
This keeps Pinax gem-extractable with no Autopax dependencies.
5337
54-
## Test Status
38+
### New Files
5539
56-
```bash
57-
./autopax dev test # 916 examples, 0 failures (32 new tests)
58-
./autopax dev format # All side_panel files pass rubocop
59-
```
40+
- `lib/pinax/layout/scroll_listener.rb` - Scroll event protocol
41+
- `lib/pinax/text.rb` - ANSI-aware text utilities (wrap, truncate, visible_length)
42+
- `lib/autopax/cli/side_panel_appender.rb` - SemanticLogger subscriber for panel
43+
44+
### Test Status
45+
46+
926 examples, 0 failures
6047
6148
---
6249
6350
## What's Next
6451
65-
1. **Optional chat command integration** - Add `--debug-panel` flag
66-
2. **Stderr routing** - Convenience method to redirect warnings to panel
52+
See OPERATA.md for current priorities. The chat command integration is a good
53+
foundation for future telemetry and debugging features.
6754
6855
---
6956
70-
## Proof of Concept Scripts (for reference)
57+
## Demo Scripts
7158
7259
```bash
73-
./bin/redraw-panel-test # Working demo of the pattern
74-
./bin/decslrm-test # DECSLRM approach (loses scrollback - rejected)
60+
./bin/side-panel-demo # Official Pinax side panel demo
61+
./bin/layout-composed-demo # Basic layout demo (no panel)
7562
```

bin/side-panel-demo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ begin
3535
layout << 'Demo complete! Press Ctrl+C to exit.'
3636
layout << 'Then scroll up to verify scrollback is clean.'
3737

38-
sleep until false
38+
loop { sleep }
3939
rescue Interrupt
4040
# Clean exit
4141
ensure
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
require 'semantic_logger'
2+
require 'paint'
3+
require 'pinax/text'
4+
5+
# SemanticLogger appender that routes log output to a Pinax::SidePanel.
6+
#
7+
# Uses the same colored format as CLI::Logger interactive mode, with text
8+
# wrapping to fit the panel width. Replaces stderr logging in TUI mode.
9+
#
10+
# ## Usage
11+
#
12+
# ```ruby
13+
# # Clear existing appenders and add panel appender
14+
# SemanticLogger.clear_appenders!
15+
# appender = CLI::SidePanelAppender.new(panel: @layout.side_panel)
16+
# SemanticLogger.add_appender(appender: appender)
17+
# ```
18+
#
19+
class CLI::SidePanelAppender < SemanticLogger::Subscriber
20+
attr_reader :panel
21+
22+
# @param panel [Pinax::SidePanel] The side panel to log to
23+
# @param level [Symbol] Minimum log level (default: :debug)
24+
def initialize(panel:, level: :debug, **args)
25+
@panel = panel
26+
super(level: level, **args)
27+
end
28+
29+
# Called by SemanticLogger for each log entry.
30+
# Formats using CLI::Logger style and wraps to panel width.
31+
#
32+
# Method name required by SemanticLogger::Subscriber interface.
33+
# rubocop:disable Naming/PredicateMethod
34+
def log(log)
35+
return false unless should_log?(log)
36+
37+
formatted = format_log(log)
38+
wrapped_lines = Pinax::Text.wrap(formatted, panel.width - 2)
39+
40+
wrapped_lines.each { |line| panel.log(line, timestamp: false) }
41+
panel.redraw!
42+
true
43+
end
44+
45+
# Flush is a no-op for panel appender (panel is always immediate).
46+
#
47+
# Method name required by SemanticLogger::Subscriber interface.
48+
def flush
49+
true
50+
end
51+
# rubocop:enable Naming/PredicateMethod
52+
53+
private
54+
55+
# Format log entry using CLI::Logger's interactive format (colored).
56+
def format_log(log)
57+
prefix = level_prefix(log.level)
58+
"#{prefix} #{log.message}#{payload_string(log)}"
59+
end
60+
61+
# Colored level prefix matching CLI::Logger interactive mode.
62+
def level_prefix(level)
63+
prefix, color = case level
64+
when :debug then ["[\uf46f debug]→", :cyan]
65+
when :info then ["[\uea74 info]→", :green]
66+
when :warn then ['[⚠ warn]→', :yellow]
67+
when :error then ['[✗ error]→', :red]
68+
when :fatal then ['[✗ FATAL]→', :red]
69+
else ["[#{level}]:", :white]
70+
end
71+
Paint[prefix, color]
72+
end
73+
74+
# Format payload key-value pairs matching CLI::Logger style.
75+
def payload_string(log)
76+
return '' unless log.payload&.any?
77+
78+
pairs = log.payload.map { |k, v| "#{k}=#{v.inspect}" }
79+
" (#{pairs.join(', ')})"
80+
end
81+
end

0 commit comments

Comments
 (0)