As a user, I want consistent blank line spacing in terminal output, so that the interface feels polished and predictable.
SYNOPSIS
Audit and standardize blank lines between all terminal output sections.
DESCRIPTION
Currently, the number of blank lines between sections varies depending on the order of events during a session. Sometimes there are 2 blank lines, sometimes 1, leading to an inconsistent visual experience.
This story involves:
- Auditing all places that write to the terminal
- Establishing spacing rules (e.g., 1 blank line between sections)
- Ensuring consistent application of those rules
DESIGN
Root cause: Spacing is caller-side (each method decides its own prefix/suffix spacing) instead of boundary-aware (spacing happens at transitions).
Scenarios causing inconsistency:
- Dots running →
stop_dotsadds newline +markdownadds 2 newlines = 3 lines - No dots →
markdownadds 2 newlines = 2 lines headerreturns\n...+sayadds newline = double spacing
Solution: One flag (@at_line_start), one method (gap).
gap is idempotent: “ensure we’re at a blank line”. Call it anywhere between
sections. If already at line start, it’s a no-op. If mid-content, it adds one newline.
Changes:
- Terminal tracks cursor state via
@at_line_start gapmethod:newline unless @at_line_startmarkdownusesgapinstead ofnewline(n: 2)headerdrops its\nprefix (caller usesgap)
Trade-offs:
- Simplicity ✓ - 1 flag, 1 method, 3 file changes
- No plugin changes needed - existing
say/printcalls just work - Idempotent - safe to call
gapmultiple times
SEE ALSO
- lib/elelem/terminal.rb - Primary output methods (say, print, markdown, newline)
- lib/elelem/agent.rb - REPL loop and turn processing with terminal calls
- lib/elelem/toolbox.rb - header() method prepends \n to output
- lib/elelem/plugins/read.rb - after hook uses terminal.say and display_file
- lib/elelem/plugins/write.rb - after hook uses terminal.say and display_file
- lib/elelem/plugins/execute.rb - streaming print and after hook
- lib/elelem/plugins/tools.rb - markdown output for tool listings
- lib/elelem/plugins/context.rb - multi-line output for context display
- lib/elelem/plugins/builtins.rb - /clear and /help command output
- lib/elelem/plugins/provider.rb - provider switching messages
Tasks
Terminal (lib/elelem/terminal.rb)
- Add
@at_line_start = truein initialize - Add
gapmethod:newline unless @at_line_start(idempotent blank line) - Update
sayto set@at_line_start = trueafter output - Update
printto set@at_line_start = false(mid-line content) - Update
newlineto set@at_line_start = true - Update
stop_dots- already calls newline, will inherit correct state - Update
markdown- replacenewline(n: 2)withgap
Toolbox (lib/elelem/toolbox.rb)
- Update
header- remove leading\nfrom return string
Agent (lib/elelem/agent.rb)
- Add
terminal.gapbeforeterminal.say toolbox.header(...)in process method
Testing
- Add spec for
gapidempotence: calling twice produces one blank line - Visual audit: conversation with tool calls
- Visual audit: multi-tool execution
- Visual audit: streaming execute output
Acceptance Criteria
- Single blank line between distinct output sections
- No double blank lines appear in any scenario
- No missing blank lines between sections
- Spacing is consistent regardless of event order
- Visual audit of common workflows passes
Demo Notes
Verified: 2026-02-04 Status: ACCEPTED
Verification Run: 2026-02-04 (Final)
All 83 tests pass (0 failures).
Code Review Fixes Applied:
newlinenow respectsquiet?modeprintandsayonly set@at_line_startwhen actually outputting- Test spec uses
expectinstead ofallowfor dots thread kill - Replaced
@quietwithquiet?predicate throughout - Tests refactored to test behavior, not implementation (no
instance_variable_get)
Manual Verification via ./bin/run:
- Library loads without errors ✓
- Spacing between sections is exactly 1 line ✓
- No double blank lines in output ✓
gapis idempotent (multiple calls = single newline) ✓
Previous: Verification Run 2026-02-04
All 75 tests pass (0 failures).
Automated Tests Verified:
gapidempotence: calling multiple times produces single blank line ✓@at_line_startstate tracking acrosssay,print,newline✓gapstops dots before adding newline ✓
Scenarios Verified:
| Scenario | Expected | Result |
|---|---|---|
| Dots → tool header | 1 blank line | ✓ |
| Tool → tool | 1 blank line | ✓ |
| Tool → markdown | 1 blank line | ✓ |
Remaining: Visual audit of streaming execute output (requires manual LLM testing)
Implementation Summary
Added @at_line_start flag and gap method to Terminal class:
gapis idempotent: stops dots, then adds newline only if not at line start- All output methods (
say,print,newline) update@at_line_start markdownusesgapinstead of hardcodednewline(n: 2)headerin Toolbox no longer prepends\n- Agent calls
terminal.gapbefore tool headers
Tested Scenarios
-
Dots → tool header:
.prints, gap stops dots + newline, header prints- Result: Single blank line after dots ✓
-
Tool header → tool header: gap adds single newline between headers
- Result: Consistent single blank line ✓
-
Tool header → markdown: gap inside markdown is no-op (already at line start)
- Result: No extra blank lines ✓
-
Idempotence: Calling gap multiple times produces only one newline
- Result: Safe to call gap anywhere ✓
Files Changed
lib/elelem/terminal.rb- Added@at_line_start,gap, updated output methodslib/elelem/toolbox.rb- Removed\nprefix fromheaderlib/elelem/agent.rb- Addedterminal.gapbefore tool headersspec/elelem/terminal_spec.rb- Added tests for gap behavior
Edge Case Fixed During Demo
Initial implementation missed that gap should stop dots if running. When dots
thread prints directly to stdout, @at_line_start stays true but cursor is
mid-line. Fixed by having gap call stop_dots before checking state.