Files
clacky-ai--openclacky/docs/ui2-architecture.md
T
wehub-resource-sync 9f2e1cd2af
Ruby / Ruby 4.0.5 (push) Failing after 5m13s
Ruby / Ruby 3.3.4 (push) Failing after 5m43s
Ruby / Ruby 2.6.8 (push) Failing after 16m2s
chore: import upstream snapshot with attribution
2026-07-13 12:33:07 +08:00

3.5 KiB

UI2 Architecture

Core Principle

Strict Layering: UI layer must NOT directly access Agent layer. Use callbacks for communication.

Component Hierarchy

UIController (single external interface)
  ├── LayoutManager (layout coordination)
  │   ├── OutputArea (output display)
  │   ├── InputArea (user input)
  │   ├── TodoArea (task list)
  │   └── InlineInput (confirmation prompt)
  └── Callbacks (external communication)
      ├── on_input -> CLI handles input
      ├── on_interrupt -> CLI handles interruption
      └── on_mode_toggle -> CLI handles mode change

Data Flow

Agent → UI: One-way calls

# Agent calls UI to display
@ui&.show_tool_call(...)
@ui&.append_output(...)
@ui&.show_token_usage(...)

UI → Agent: Via callbacks

# ❌ Wrong: UI directly calls Agent
agent.run(input)

# ✅ Correct: Via callback
ui_controller.on_input { |input| agent.run(input) }

Common Mistakes

Directly accessing Agent in UI components

# Bad example
def toggle_mode
  @agent.config.mode = "auto_approve"  # ❌ Violates separation
end

Notify via callback

# Good example
def toggle_mode
  @mode_toggle_callback&.call("auto_approve")  # ✅ Proper separation
end

Using puts for logging

puts "Debug info"  # ❌ Breaks UI rendering

Use UIController.log

ui_controller.log("Debug info")  # ✅ Displays in output area
ui_controller.log("Warning", level: :warning)
ui_controller.log("Error", level: :error)

Logging System

Use ui_controller.log(message, level: :info) to display debug information in the output area without breaking rendering.

Available log levels:

  • :debug - Gray dimmed text
  • :info - Normal text with info symbol
  • :warning - Yellow warning text
  • :error - Red error text

Example:

# In UIController or components with access to UIController
@ui_controller.log("Tool execution started", level: :debug)
@ui_controller.log("Cache hit", level: :info)
@ui_controller.log("Retry attempt 3/10", level: :warning)
@ui_controller.log("Network failed", level: :error)

Rendering Flow

Fixed Areas

  • InputArea: Fixed at bottom (hidden when InlineInput is active via paused?)
  • TodoArea: Fixed above InputArea

Scrolling Area

  • OutputArea: Natural scrolling, all content appended here

Thread Safety

  • All rendering protected by @render_mutex
  • Never call render methods outside LayoutManager

Key Methods

Display Methods (Agent → UI)

  • append_output(content) - Add content to output area
  • update_sessionbar(tasks:, cost:) - Update session bar
  • show_token_usage(token_data) - Display token statistics
  • show_tool_call(name, args) - Display tool execution
  • request_confirmation(message) - Blocking user confirmation

Callback Registration (CLI sets these)

  • on_input { |text, images| ... } - Handle user input
  • on_interrupt { |input_was_empty:| ... } - Handle Ctrl+C
  • on_mode_toggle { |new_mode| ... } - Handle Shift+Tab

Logging (Use instead of puts)

  • log(message, level: :info) - Display debug/info in output

Best Practices

  1. Never bypass UIController - All UI updates go through UIController
  2. Use callbacks for upward communication - UI notifies CLI/Agent via callbacks
  3. Log via UIController - Never use puts or print directly
  4. Check paused state - Don't render InputArea when InlineInput is active
  5. Trust the render flow - Let LayoutManager handle rendering coordination