ui: single-line avatar (no smear), friendly errors, decode-body retryable - #52
Merged
Conversation
Three things from the visual review:
## Single-line avatar, repositioned
User preferred the simpler one-line faces:
Idle (o o) ↔ (- -) blink
Thinking (o .) ↔ (. o) eyes shift
Speaking (o o) ↔ (o O) mouth opens
Reading [@ @] scan-eye brackets
Writing (>_<) ↔ (-_-) concentration
Bash [$_$] prompt brackets
Alert (O_O) wide eyes (yellow)
Error (x_x) ko'd (red)
Done (^_^) happy (accent)
Avatar now paints a single row on the *input* row, horizontally
centered between col 0 and the input prompt's start column:
Before (3 rows above input, smeared when chat scrolled):
,-, ,-, ← chat content
[@ @] >>>>> [@ @] ← stale avatar fossil
\_/ \_/ ← from ScrollUp
[@ @] ← current avatar
▌▌ _
After (1 row on input line, centered in margin, never scrolled):
[@ @] ▌▌ _
`AVATAR_H` is gone — single row, `AVATAR_W = 5`. The avatar lives
on `input_top` which sits *below* the chat scroll region, so
`ensure_room`'s `ScrollUp(1)` doesn't drag it into scrollback. The
ghost-avatar fossils from the screenshot are gone.
`draw_avatar` wipes the entire left margin (`cols 0..indent`) before
painting so a face from a previous state can't leave artifacts
when the indent recomputes on resize.
## Friendly error messages
`error: CompletionError: ProviderError: Http client error: error
decoding response body` was unhelpful at the chat boundary —
opaque enough to leave the user wondering what to do.
New `recovery::user_facing_error(msg, attempts)` produces a
3-line message: a plain-English headline that names what failed,
a `↳ hint:` that points at the recovery path, and a `↳ cause:`
that preserves the raw error for debugging:
lost the response stream from the provider (truncated or malformed body)
↳ hint: usually transient — retry. If it persists the provider
may be having issues or returning non-JSON (HTML error
pages, plaintext)
↳ cause: CompletionError: ProviderError: Http client error: ...
Headlines per `ErrorKind`:
Auth → "authentication failed talking to the LLM provider"
hint points at the API key env var
RateLimit → "provider rate-limited the request"
hint suggests waiting / /model
ContextLength → "conversation exceeds the model's context window"
hint points at /compress
Network → "network error reaching the LLM provider"
(with a special variant for decode-body errors)
Other → "the LLM provider returned an error we didn't recognize"
hint suggests /model
Wired into every runner.rs error-surfacing site (auth, ctx-length,
tool-side-effects, retries-exhausted) — they all now route through
`user_facing_error`.
## "error decoding response body" is now Network-class
Reqwest decode failures mid-stream (very common with streaming
providers) were classified as `Other` and surfaced immediately
without retrying. Now classified as `Network` so the existing
retry policy kicks in. The cause is almost always transient
(connection blip, truncated chunked response, provider hiccup);
single-attempt failure was a frequent papercut.
## Test plan
- [x] 4 new tests:
- `test_classify_network` extended with the rig decode error
+ "decode error: EOF"
- `user_facing_error_includes_cause` (headline + hint + raw)
- `user_facing_error_classifies_auth` (mentions API key)
- `user_facing_error_classifies_context_length` (mentions /compress)
- [x] `cargo test --features plugin` -> 604 pass, 0 fail.
- [x] Both build profiles -> 0 warnings.
- [ ] Eyeball: trigger a streaming hiccup, verify the chat shows
a friendly multi-line error and the request is retried;
verify only one avatar visible at a time when scrolling.
allen-munsch
pushed a commit
to allen-munsch/dirge
that referenced
this pull request
Jun 3, 2026
Three things from the visual review:
## Single-line avatar, repositioned
User preferred the simpler one-line faces:
Idle (o o) ↔ (- -) blink
Thinking (o .) ↔ (. o) eyes shift
Speaking (o o) ↔ (o O) mouth opens
Reading [@ @] scan-eye brackets
Writing (>_<) ↔ (-_-) concentration
Bash [$_$] prompt brackets
Alert (O_O) wide eyes (yellow)
Error (x_x) ko'd (red)
Done (^_^) happy (accent)
Avatar now paints a single row on the *input* row, horizontally
centered between col 0 and the input prompt's start column:
Before (3 rows above input, smeared when chat scrolled):
,-, ,-, ← chat content
[@ @] >>>>> [@ @] ← stale avatar fossil
\_/ \_/ ← from ScrollUp
[@ @] ← current avatar
▌▌ _
After (1 row on input line, centered in margin, never scrolled):
[@ @] ▌▌ _
`AVATAR_H` is gone — single row, `AVATAR_W = 5`. The avatar lives
on `input_top` which sits *below* the chat scroll region, so
`ensure_room`'s `ScrollUp(1)` doesn't drag it into scrollback. The
ghost-avatar fossils from the screenshot are gone.
`draw_avatar` wipes the entire left margin (`cols 0..indent`) before
painting so a face from a previous state can't leave artifacts
when the indent recomputes on resize.
## Friendly error messages
`error: CompletionError: ProviderError: Http client error: error
decoding response body` was unhelpful at the chat boundary —
opaque enough to leave the user wondering what to do.
New `recovery::user_facing_error(msg, attempts)` produces a
3-line message: a plain-English headline that names what failed,
a `↳ hint:` that points at the recovery path, and a `↳ cause:`
that preserves the raw error for debugging:
lost the response stream from the provider (truncated or malformed body)
↳ hint: usually transient — retry. If it persists the provider
may be having issues or returning non-JSON (HTML error
pages, plaintext)
↳ cause: CompletionError: ProviderError: Http client error: ...
Headlines per `ErrorKind`:
Auth → "authentication failed talking to the LLM provider"
hint points at the API key env var
RateLimit → "provider rate-limited the request"
hint suggests waiting / /model
ContextLength → "conversation exceeds the model's context window"
hint points at /compress
Network → "network error reaching the LLM provider"
(with a special variant for decode-body errors)
Other → "the LLM provider returned an error we didn't recognize"
hint suggests /model
Wired into every runner.rs error-surfacing site (auth, ctx-length,
tool-side-effects, retries-exhausted) — they all now route through
`user_facing_error`.
## "error decoding response body" is now Network-class
Reqwest decode failures mid-stream (very common with streaming
providers) were classified as `Other` and surfaced immediately
without retrying. Now classified as `Network` so the existing
retry policy kicks in. The cause is almost always transient
(connection blip, truncated chunked response, provider hiccup);
single-attempt failure was a frequent papercut.
## Test plan
- [x] 4 new tests:
- `test_classify_network` extended with the rig decode error
+ "decode error: EOF"
- `user_facing_error_includes_cause` (headline + hint + raw)
- `user_facing_error_classifies_auth` (mentions API key)
- `user_facing_error_classifies_context_length` (mentions /compress)
- [x] `cargo test --features plugin` -> 604 pass, 0 fail.
- [x] Both build profiles -> 0 warnings.
- [ ] Eyeball: trigger a streaming hiccup, verify the chat shows
a friendly multi-line error and the request is retried;
verify only one avatar visible at a time when scrolling.
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.
Three fixes: avatar collapsed to a single row on the input line (no more fossils in scrollback from
ScrollUp), centered in the left margin between col 0 and the input prompt. Errors now show a 3-line headline + hint + raw cause instead of a stack of nested error types.error decoding response body(a common transient mid-stream failure) is now classified as Network and retried. 604 tests pass.