Failure screens, reconnect toast, and the WAITING state #15

Open
opened 2026-07-29 17:32:07 +00:00 by aiko · 5 comments
Owner

What to build

Every fault gets the same six-part structure (card 5a), varying only in whose problem it is: degraded station bar with a status word, a texture block, a headline in her voice about what she cannot do — never an error code, the two-hop chain with a one-line reason per hop, a folded 詳細 reveal holding the real error and the likely causes, and one primary action right for this fault plus a secondary that is never a dead end.

Five faults:

Fault Headline Primary action
Upstream down (5a) 上流が応答しない 再同調
Socket won't open (6a) どこにも繋がらない アドレスを直す
Auth refused 401 (6b) 鍵が合わない 鍵を入れ直す — does not auto-retry, and says so
No Live2D model (6c) 声だけになった 姿を選ぶ / 声だけで続ける
OS took the mic (6d) 耳を塞がれてる 設定を開く / 文字で話す

6d matters most — it is the only fault where she still works. It must degrade to text, explicitly confirming that 文字入力 · TTS · 自発送信 stay live while 常時受信 · 押して送信 stop.

Also in this slice: 7c reconnect toast — cyan, three seconds, no button, and it names the gap length because that is the only useful fact. No "reconnected successfully", no checkmark. If cron fired while dark it turns acid and offers 読む — she does not replay them out loud. And 7d WAITING: the meter holds the idle floor, the PTT key goes dark reading 受け取った, 割り込み dims because there is nothing yet to interrupt.

Design reference: docs/design/denpa-receiver/README.md (card ids 1a, 4a… index into Denpa Receiver.dc.html, openable in a browser).

Acceptance criteria

  • All five faults render, each keyed off real connection state, not a manual flag
  • Each names which hop broke; 詳細 holds the actual error text
  • 401 does not auto-retry and states that
  • Mic loss degrades to a working text path, not a dead end
  • Reconnect toast auto-dismisses in 3s and names the gap duration
  • Missed unprompted turns are offered to read, never replayed as audio
  • WAITING renders per 7d and auto-returns to IDLE after 2s
  • No secondary action anywhere leads to a dead end
  • Tests cover fault → screen selection for every hop-state combination

Blocked by


Reality audit

Governing rule: build to reality, Hermes leads. Not doing something is acceptable if it does not break core functionality. Breaking the design is acceptable. Building UI that does nothing is not.

Audited against the actual wire surface:

  • Hermes (hermes-agent 0.19.0, verified running): /health, /v1/models, /v1/runs, /v1/runs/{id}, /v1/runs/{id}/events, /v1/runs/{id}/stop, /v1/runs/{id}/approval
  • Run events: message.delta, tool.started, tool.completed, run.completed, run.cancelled, run.failed/run.errored, reasoning.available, approval.request (carries a per-request choices array and sets run status waiting_for_approval; the go-between discards it today — see #19)
  • Go-between → client: full-text, audio, control, error, set-model-and-conf, config-switched, config-files, background-files, history-list, history-data, new-history-created, history-deleted, user-input-transcription, backend-synth-complete, force-new-message, tool_call_status, interrupt-signal, heartbeat-ack, group-update
  • Client → go-between: text-input, mic-audio-data, mic-audio-end, raw-audio-data, ai-speak-signal, interrupt-signal, fetch-history-list, fetch-and-set-history, create-new-history, delete-history, fetch-configs, switch-config, fetch-backgrounds, audio-play-start, request-init-config, heartbeat

One of five faults degrades; the rest are real

  • 6a socket won''t open — real, fully client-side.
  • 6b auth refused — real. Still must not auto-retry.
  • 6c no model — real via set-model-and-conf / config-files.
  • 6d mic taken by the OS — real, client-side, and still the most important one: it must degrade to text.
  • 5a upstream downdegraded. With no health message the client cannot know the upstream is down before a turn. This screen becomes a post-failure state, shown when a run fails, built from the error message. Its headline and structure survive; what changes is that it can only appear after she tries and cannot answer.

Consequence to honour: the 詳細 reveal shows the real error text the go-between sent. Do not invent likely-cause lists the client cannot substantiate — if the error says only that the run failed, say that.

The reconnect toast (7c) is real: socket-derived, and the gap length is genuinely knowable. Its acid variant (missed unprompted turns) depends on #22 and should not ship before it.

7d WAITING is real and unaffected.

## What to build Every fault gets the same six-part structure (card 5a), varying only in **whose problem it is**: degraded station bar with a status word, a texture block, **a headline in her voice about what she cannot do — never an error code**, the two-hop chain with a one-line reason per hop, a folded 詳細 reveal holding the real error and the likely causes, and one primary action right for this fault plus a secondary that is **never a dead end**. Five faults: | Fault | Headline | Primary action | |---|---|---| | Upstream down (5a) | 上流が応答しない | 再同調 | | Socket won't open (6a) | どこにも繋がらない | アドレスを直す | | Auth refused 401 (6b) | 鍵が合わない | 鍵を入れ直す — **does not auto-retry, and says so** | | No Live2D model (6c) | 声だけになった | 姿を選ぶ / 声だけで続ける | | OS took the mic (6d) | 耳を塞がれてる | 設定を開く / 文字で話す | **6d matters most — it is the only fault where she still works.** It must degrade to text, explicitly confirming that 文字入力 · TTS · 自発送信 stay live while 常時受信 · 押して送信 stop. Also in this slice: **7c reconnect toast** — cyan, three seconds, no button, and it names the *gap length* because that is the only useful fact. No "reconnected successfully", no checkmark. If cron fired while dark it turns acid and offers 読む — **she does not replay them out loud**. And **7d WAITING**: the meter holds the idle floor, the PTT key goes dark reading 受け取った, 割り込み dims because there is nothing yet to interrupt. Design reference: `docs/design/denpa-receiver/README.md` (card ids `1a`, `4a`… index into `Denpa Receiver.dc.html`, openable in a browser). ## Acceptance criteria - [ ] All five faults render, each keyed off real connection state, not a manual flag - [ ] Each names which hop broke; 詳細 holds the actual error text - [ ] 401 does not auto-retry and states that - [ ] Mic loss degrades to a working text path, not a dead end - [ ] Reconnect toast auto-dismisses in 3s and names the gap duration - [ ] Missed unprompted turns are offered to read, never replayed as audio - [ ] WAITING renders per 7d and auto-returns to IDLE after 2s - [ ] No secondary action anywhere leads to a dead end - [ ] Tests cover fault → screen selection for every hop-state combination ## Blocked by - #12 - #11 --- ## Reality audit Governing rule: **build to reality, Hermes leads. Not doing something is acceptable if it does not break core functionality. Breaking the design is acceptable. Building UI that does nothing is not.** Audited against the actual wire surface: - **Hermes** (`hermes-agent 0.19.0`, verified running): `/health`, `/v1/models`, `/v1/runs`, `/v1/runs/{id}`, `/v1/runs/{id}/events`, `/v1/runs/{id}/stop`, `/v1/runs/{id}/approval` - **Run events**: `message.delta`, `tool.started`, `tool.completed`, `run.completed`, `run.cancelled`, `run.failed`/`run.errored`, `reasoning.available`, `approval.request` (carries a per-request `choices` array and sets run status `waiting_for_approval`; the go-between discards it today — see #19) - **Go-between → client**: `full-text`, `audio`, `control`, `error`, `set-model-and-conf`, `config-switched`, `config-files`, `background-files`, `history-list`, `history-data`, `new-history-created`, `history-deleted`, `user-input-transcription`, `backend-synth-complete`, `force-new-message`, `tool_call_status`, `interrupt-signal`, `heartbeat-ack`, `group-update` - **Client → go-between**: `text-input`, `mic-audio-data`, `mic-audio-end`, `raw-audio-data`, `ai-speak-signal`, `interrupt-signal`, `fetch-history-list`, `fetch-and-set-history`, `create-new-history`, `delete-history`, `fetch-configs`, `switch-config`, `fetch-backgrounds`, `audio-play-start`, `request-init-config`, `heartbeat` ### One of five faults degrades; the rest are real - **6a socket won''t open** — real, fully client-side. - **6b auth refused** — real. Still must not auto-retry. - **6c no model** — real via `set-model-and-conf` / `config-files`. - **6d mic taken by the OS** — real, client-side, and still the most important one: it must degrade to text. - **5a upstream down** — **degraded.** With no health message the client cannot know the upstream is down *before* a turn. This screen becomes a **post-failure** state, shown when a run fails, built from the `error` message. Its headline and structure survive; what changes is that it can only appear after she tries and cannot answer. Consequence to honour: the 詳細 reveal shows the real error text the go-between sent. **Do not invent likely-cause lists the client cannot substantiate** — if the error says only that the run failed, say that. The reconnect toast (7c) is real: socket-derived, and the gap length is genuinely knowable. Its acid variant (missed unprompted turns) depends on #22 and should not ship before it. 7d WAITING is real and unaffected.
Author
Owner

The acid reconnect toast's dependency changed shape

This issue notes the acid variant (missed unprompted turns) depends on #22. Still true, but what it depends on has changed twice and has now settled.

ollvt-hermes-bridge#16 chose a Hermes Platform adapter pushing to the go-between, not the go-between polling cron output. So missed-broadcast recovery is not "read the unread output files" — Hermes owns the queue, coalesces per job id (a cron that fired twelve times while the client was dark surfaces once, marked 12x), applies a staleness bound, and drains on reconnect.

For this slice that means the toast announces what is about to drain, rather than reconstructing history from a log. Which is the lighter build, and it keeps the existing criterion — "Missed unprompted turns are offered to read, never replayed as audio" — intact.

Also relevant to 6d, the fault where she still works: the claim that 自発送信 stays live is only true once the adapter lands. Until then there is no unprompted path at all, so 6d's copy should not promise one.

Unrelated correction: this issue's endpoint audit lists POST /v1/runs/{id}/approval, and it was right — 承認 has a complete data path and was wrongly filed as deferred. Now tracked as #26. Nothing in this slice changes, but the 5a/6x screens should not treat a suspended run as a failure; waiting_for_approval is alive.

## The acid reconnect toast's dependency changed shape This issue notes the acid variant (missed unprompted turns) depends on #22. Still true, but *what* it depends on has changed twice and has now settled. `ollvt-hermes-bridge#16` chose a **Hermes Platform adapter pushing** to the go-between, not the go-between polling cron output. So missed-broadcast recovery is not "read the unread output files" — Hermes owns the queue, coalesces per job id (a cron that fired twelve times while the client was dark surfaces once, marked `12x`), applies a staleness bound, and drains on reconnect. For this slice that means the toast **announces what is about to drain**, rather than reconstructing history from a log. Which is the lighter build, and it keeps the existing criterion — *"Missed unprompted turns are offered to read, never replayed as audio"* — intact. Also relevant to 6d, the fault where she still works: the claim that 自発送信 stays live is only true once the adapter lands. Until then there is no unprompted path at all, so 6d's copy should not promise one. **Unrelated correction:** this issue's endpoint audit lists `POST /v1/runs/{id}/approval`, and it was right — 承認 has a complete data path and was wrongly filed as deferred. Now tracked as #26. Nothing in this slice changes, but the 5a/6x screens should not treat a suspended run as a failure; `waiting_for_approval` is alive.
Author
Owner

Ruled out of scope for v1 on the wayfinder map, the conversation works (v1). Labelled v2; staying open rather than closed, because the scope call lives on the map and this is still real work.

v1 is: a real voice conversation — you speak, she answers in her own voice, Live2D renders, the log records it — on the Windows desktop, the OnePlus Pad and an Android phone, for one operator on hardware they already own.

**Ruled out of scope for v1** on the wayfinder map, [the conversation works (v1)](https://git.aiko.works/aiko/denpa/issues/41). Labelled `v2`; staying open rather than closed, because the scope call lives on the map and this is still real work. v1 is: a real voice conversation — you speak, she answers in her own voice, Live2D renders, the log records it — on the Windows desktop, the OnePlus Pad and an Android phone, for one operator on hardware they already own.
Author
Owner

The text path is split out into #53 and pulled into v1. This issue keeps the rest and stays v2.

Why the split: walking onboarding on the desktop app on 2026-07-30 hit exactly the dead end this issue's own text forbids —

6d matters most — it is the only fault where she still works. It must degrade to text, explicitly confirming that 文字入力 · TTS · 自発送信 stay live while 常時受信 · 押して送信 stop.

Both mic keys raise a VAD permission dialog, the desktop push-to-talk hotkey never registers (#51), and there is no text input anywhere in the receiver chrome. So the only way into the product is a microphone that does not work, and the escape hatch this issue specifies was never built.

The escape hatch is separable from the taxonomy, and far cheaper: websocket_handler.py:72 already handles text-input, pairing.ts:75 already sends it, and a real turn crossed on it today. Only the compose surface is missing.

What stays here: 6d itself — 耳を塞がれてる, the flatlined 無音 — 権限なし meter, 設定を開く — and every other failure screen, including the WAITING treatment.

**The text path is split out into [#53](https://git.aiko.works/aiko/denpa/issues/53) and pulled into v1.** This issue keeps the rest and stays `v2`. Why the split: walking onboarding on the desktop app on 2026-07-30 hit exactly the dead end this issue's own text forbids — > **6d matters most — it is the only fault where she still works.** It must degrade to text, explicitly confirming that 文字入力 · TTS · 自発送信 stay live while 常時受信 · 押して送信 stop. Both mic keys raise a VAD permission dialog, the desktop push-to-talk hotkey never registers (`#51`), and there is no text input anywhere in the receiver chrome. So the only way into the product is a microphone that does not work, and the escape hatch this issue specifies was never built. The escape hatch is separable from the taxonomy, and far cheaper: `websocket_handler.py:72` already handles `text-input`, `pairing.ts:75` already sends it, and a real turn crossed on it today. Only the compose surface is missing. **What stays here:** 6d itself — `耳を塞がれてる`, the flatlined `無音 — 権限なし` meter, `設定を開く` — and every other failure screen, including the WAITING treatment.
Author
Owner

Two more pieces split out, on the operator's call, 2026-07-30. This issue keeps the five fault screens and the reconnect toast.

  • #57the Receiver never reconnects. This issue frames it as a toast; the toast has no behaviour underneath it to announce. Hit directly today: restarting the go-between left the app on error.websocketNotOpen until it was relaunched, and it happens on every restart.
  • #58WAITING (7d), which is not a failure at all. It appears in every successful turn, depends on nothing, and is the least likely piece to be reached while bundled with a taxonomy that is out of v1.

Previously split: #53, the compose surface — 6d's 文字で話す, which turned out to be the only way to reach her without a microphone.

Two of the remaining faults have gone stale, and should not be built as written

  • 6b auth refused (401)鍵が合わない / 鍵を入れ直す describes a key the operator types. ADR-0011 removes that model: devices enrol with the go-between and present a long-lived per-device token, so an auth failure means this device is no longer enrolled, and the action is to re-enrol, not to re-enter a key. Build it with #26 / #40, or it will be a screen for a mechanism that no longer exists.
  • 6c no Live2D model姿を選ぶ assumes a model picker. #52 establishes that 姿 currently offers character-config bundles whose personas are dead, and that the picker needs rebuilding against model_dict.json. This screen's primary action does not exist yet in the form it assumes.

Both are recorded here rather than split, because they are still fault screens — they just cannot be written correctly until the mechanisms they act on are settled.

What remains genuinely ready in this issue: 6a socket won't open, 5a upstream down (already downgraded by the reality audit to a post-failure state built from the error message), and the reconnect toast itself once #57 gives it a gap length to name.

**Two more pieces split out**, on the operator's call, 2026-07-30. This issue keeps the five fault screens and the reconnect toast. - [#57](https://git.aiko.works/aiko/denpa/issues/57) — **the Receiver never reconnects.** This issue frames it as a toast; the toast has no behaviour underneath it to announce. Hit directly today: restarting the go-between left the app on `error.websocketNotOpen` until it was relaunched, and it happens on every restart. - [#58](https://git.aiko.works/aiko/denpa/issues/58) — **WAITING (7d)**, which is not a failure at all. It appears in every successful turn, depends on nothing, and is the least likely piece to be reached while bundled with a taxonomy that is out of v1. Previously split: [#53](https://git.aiko.works/aiko/denpa/issues/53), the compose surface — 6d's `文字で話す`, which turned out to be the only way to reach her without a microphone. ## Two of the remaining faults have gone stale, and should not be built as written - **6b auth refused (401)** — `鍵が合わない` / `鍵を入れ直す` describes a key the operator types. ADR-0011 removes that model: devices enrol with the go-between and present a long-lived per-device token, so an auth failure means *this device is no longer enrolled*, and the action is to re-enrol, not to re-enter a key. Build it with `#26` / `#40`, or it will be a screen for a mechanism that no longer exists. - **6c no Live2D model** — `姿を選ぶ` assumes a model picker. `#52` establishes that 姿 currently offers character-config bundles whose personas are dead, and that the picker needs rebuilding against `model_dict.json`. This screen's primary action does not exist yet in the form it assumes. Both are recorded here rather than split, because they are still fault screens — they just cannot be written correctly until the mechanisms they act on are settled. What remains genuinely ready in this issue: **6a socket won't open**, **5a upstream down** (already downgraded by the reality audit to a post-failure state built from the `error` message), and the reconnect toast itself once [#57](https://git.aiko.works/aiko/denpa/issues/57) gives it a gap length to name.
Author
Owner

Split into a v1 slice and this remainder, on the operator's call, 2026-07-30. This issue stays v2.

#59 takes 6a (socket will not open) and 5a (she could not answer) — the two faults that are buildable today and that you hit simply trying to use her. It takes them at the fidelity v1 needs: which hop broke, her words not an error code, the real error text under 詳細, one action that is not a dead end.

What stays here, and why each cannot be built correctly yet:

  • 6b auth refused鍵を入れ直す describes typing a key ADR-0011 removes. Build with #26 / #40.
  • 6c no Live2D model姿を選ぶ assumes a picker #52 establishes needs rebuilding.
  • the reconnect toast — needs #57, which is the behaviour underneath it.
  • the full six-part card treatment and the texture block, which are polish over the information.

Everything split from this issue so far: #53 compose surface, #57 reconnect, #58 WAITING, and now #59.

**Split into a v1 slice and this remainder**, on the operator's call, 2026-07-30. This issue stays `v2`. [#59](https://git.aiko.works/aiko/denpa/issues/59) takes **6a (socket will not open)** and **5a (she could not answer)** — the two faults that are buildable today and that you hit simply trying to use her. It takes them at the fidelity v1 needs: which hop broke, her words not an error code, the real error text under `詳細`, one action that is not a dead end. What stays here, and why each cannot be built correctly yet: - **6b auth refused** — `鍵を入れ直す` describes typing a key ADR-0011 removes. Build with `#26` / `#40`. - **6c no Live2D model** — `姿を選ぶ` assumes a picker `#52` establishes needs rebuilding. - **the reconnect toast** — needs `#57`, which is the behaviour underneath it. - the full six-part card treatment and the texture block, which are polish over the information. Everything split from this issue so far: [#53](https://git.aiko.works/aiko/denpa/issues/53) compose surface, [#57](https://git.aiko.works/aiko/denpa/issues/57) reconnect, [#58](https://git.aiko.works/aiko/denpa/issues/58) WAITING, and now [#59](https://git.aiko.works/aiko/denpa/issues/59).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiko/denpa#15
No description provided.