Skip to content

ui: ascii avatar in bottom-left margin - #51

Merged
yogthos merged 1 commit into
mainfrom
feat/ascii-avatar
May 20, 2026
Merged

ui: ascii avatar in bottom-left margin#51
yogthos merged 1 commit into
mainfrom
feat/ascii-avatar

Conversation

@yogthos

@yogthos yogthos commented May 20, 2026

Copy link
Copy Markdown
Collaborator

A 3-row × 5-col ASCII face in the bottom-left margin reflects agent state (Thinking, Speaking, Reading, Writing, Bash, Alert, Error, Done, Idle). Animates each redraw while the agent runs. 601 tests pass.

A small 3-row × 5-col face lives in the bottom-left margin and
reflects what the agent is doing — thinking, speaking, reading,
writing, running bash, alerting, erroring, done. Sits inside the
chat band's centering indent so it never overlaps content.

## States and art

  Idle:      ,-,    ,-,
            (o o)  (- -)   ← blinks
             \_/    \_/

  Thinking:   ?      ?
            (o ·)  (· o)   ← eyes shift
             \_/    \_/

  Speaking:  ,-,    ,-,
            (o o)  (o o)
             \o/    \O/    ← mouth opens

  Reading:   ,-,    ,-,
            [@ @]  [@ @]   ← scan-eye brackets
             \_/    \../

  Writing:   ,-,    ,-,
            (>_<)  (-_-)   ← concentration / focus
             \_/    \_/

  Bash:    [___]  [___]
           [$_$]  [$ $]    ← prompt brackets
           [___]  [___]

  Alert:     !
           (O_O)
            /!\\

  Error:    ,-,
           (x_x)
            /v\\

  Done:     ,-,
           (^_^)
            \_/

## Implementation

`src/ui/avatar.rs` — `AvatarState` enum, `art(state, tick)` returns
3 lines × 5 cols of ASCII, `from_tool_name(name)` maps known tools
to read/write/bash families.

Renderer state: `avatar_state: AvatarState`, `avatar_tick: bool`.
`set_avatar_state(state)` updates from the UI loop; `avatar_tick`
flips in `draw_bottom` alongside the spinner_tick so the avatar
animates each redraw while the agent is running.

`draw_avatar(stdout, input_top)` paints at cols 0..5, rows
`input_top - 3 .. input_top`. Skipped when `content_indent < 6`
(narrow terminals where there's no margin for the avatar) or
when there's not enough vertical room above the input.

Called from `draw_bottom` after the panel paint so the avatar
sits below the chat scrollback and just above the prompt — it
stays put while content scrolls.

## State transitions

- `AgentEvent::Reasoning` -> Thinking
- `AgentEvent::Token` -> Speaking
- `AgentEvent::ToolCall { name }` -> from_tool_name(name)
- `AgentEvent::Done` -> Done
- `AgentEvent::Error` -> Error
- Permission-alert render path -> Alert

## Test plan

- [x] 2 new `ui::avatar::tests`:
  - every state x tick produces uniform 5×3 dimensions (a
    typo'd asymmetric face would visually wobble)
  - tool-name mapping covers the common families
- [x] `cargo test --features plugin` -> 601 pass, 0 fail.
- [x] Both build profiles -> 0 warnings.
- [ ] Eyeball: avatar appears in bottom-left, animates while
      the agent is thinking/streaming, switches faces per tool,
      goes back to Idle/Done on completion.
@yogthos
yogthos merged commit f67cfc3 into main May 20, 2026
1 check passed
@yogthos
yogthos deleted the feat/ascii-avatar branch May 20, 2026 22:01
allen-munsch pushed a commit to allen-munsch/dirge that referenced this pull request Jun 3, 2026
A small 3-row × 5-col face lives in the bottom-left margin and
reflects what the agent is doing — thinking, speaking, reading,
writing, running bash, alerting, erroring, done. Sits inside the
chat band's centering indent so it never overlaps content.

## States and art

  Idle:      ,-,    ,-,
            (o o)  (- -)   ← blinks
             \_/    \_/

  Thinking:   ?      ?
            (o ·)  (· o)   ← eyes shift
             \_/    \_/

  Speaking:  ,-,    ,-,
            (o o)  (o o)
             \o/    \O/    ← mouth opens

  Reading:   ,-,    ,-,
            [@ @]  [@ @]   ← scan-eye brackets
             \_/    \../

  Writing:   ,-,    ,-,
            (>_<)  (-_-)   ← concentration / focus
             \_/    \_/

  Bash:    [___]  [___]
           [$_$]  [$ $]    ← prompt brackets
           [___]  [___]

  Alert:     !
           (O_O)
            /!\\

  Error:    ,-,
           (x_x)
            /v\\

  Done:     ,-,
           (^_^)
            \_/

## Implementation

`src/ui/avatar.rs` — `AvatarState` enum, `art(state, tick)` returns
3 lines × 5 cols of ASCII, `from_tool_name(name)` maps known tools
to read/write/bash families.

Renderer state: `avatar_state: AvatarState`, `avatar_tick: bool`.
`set_avatar_state(state)` updates from the UI loop; `avatar_tick`
flips in `draw_bottom` alongside the spinner_tick so the avatar
animates each redraw while the agent is running.

`draw_avatar(stdout, input_top)` paints at cols 0..5, rows
`input_top - 3 .. input_top`. Skipped when `content_indent < 6`
(narrow terminals where there's no margin for the avatar) or
when there's not enough vertical room above the input.

Called from `draw_bottom` after the panel paint so the avatar
sits below the chat scrollback and just above the prompt — it
stays put while content scrolls.

## State transitions

- `AgentEvent::Reasoning` -> Thinking
- `AgentEvent::Token` -> Speaking
- `AgentEvent::ToolCall { name }` -> from_tool_name(name)
- `AgentEvent::Done` -> Done
- `AgentEvent::Error` -> Error
- Permission-alert render path -> Alert

## Test plan

- [x] 2 new `ui::avatar::tests`:
  - every state x tick produces uniform 5×3 dimensions (a
    typo'd asymmetric face would visually wobble)
  - tool-name mapping covers the common families
- [x] `cargo test --features plugin` -> 601 pass, 0 fail.
- [x] Both build profiles -> 0 warnings.
- [ ] Eyeball: avatar appears in bottom-left, animates while
      the agent is thinking/streaming, switches faces per tool,
      goes back to Idle/Done on completion.

Co-authored-by: Yogthos <yogthos@gmail.com>
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