ui: ascii avatar in bottom-left margin - #51
Merged
Merged
Conversation
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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.