No way to type: the compose surface the design specifies was never built #53

Closed
opened 2026-07-30 15:14:59 +00:00 by aiko · 1 comment
Owner

There is no way to type to her. The receiver chrome has no text input at all — the only <Input> elements in the entire client are onboarding's address field (onboarding.tsx:349) and the settings sheet (settings-sheet.tsx:435). Both mic keys are the only way in, and on desktop both are currently dead (#35, #51).

Split out of #15, which holds the full failure-screen taxonomy and stays out of v1. This is the part of #15 that v1 needs, and it is separable because the wire beneath it is already proven.

The wire already works — only the UI is missing

  • The go-between handles it. websocket_handler.py:72 maps "text-input" to _handle_conversation_trigger.
  • The client already sends it. src/renderer/src/services/pairing.ts:75:
    export const pairingProbe = () => ({ type: 'text-input', text: PAIRING_PROBE_TEXT });
    
  • It was exercised end to end on 2026-07-30. Onboarding's probe crossed client → go-between → adapter → Hermes and came back answered:
    17:05:15 inbound message: platform=denpa user=059ca39b-… msg='きこえてる?'
    17:05:25 response ready: time=10.4s api_calls=1 response=22 chars
    17:05:25 [Denpa] Sending response (22 chars) to 059ca39b-…
    

So every layer under the input box is working. This issue is the box.

What the design specifies

The compose surfacedocs/design/denpa-receiver/Denpa Receiver.dc.html:1016 (palCompose) and :1185 (deskCompose), with the field at :1018:

<textarea placeholder="長い話はここに。⏎ で送信、⇧⏎ で改行。">

Send on ⏎, newline on ⇧⏎, drawn for both the pal and the desktop window.

The mic-denied fallbackREADME.md:189, the 集音 onboarding step offers マイクを許可 and 文字だけで話す. dc.html:797 words it: 「声を聞かせて。マイクを使えないなら、文字だけでも大丈夫。」

The rule that makes it v1README.md:358:

6d matters most: it's the only fault where she still works. Losing the mic must degrade to text, not to a dead end — it explicitly confirms 文字入力 · TTS · 自発送信 は有効 while 常時受信 · 押して送信 are stopped.

Walking onboarding on the desktop app on 2026-07-30 produced exactly the dead end that sentence forbids: onboarding's 集音 step offered no 文字だけで話す, and in the finished chrome both hands-free (key-row.tsx:62) and ptt (:86) raise a VAD permission dialog with nothing else to try.

Scope

In:

  • The compose surface in the receiver chrome, per palCompose / deskCompose, sending { type: 'text-input', text }.
  • sends, ⇧⏎ newlines.
  • 文字だけで話す on the 集音 onboarding step, so a device that cannot hold the mic still finishes onboarding.
  • The capability line 文字入力 · TTS · 自発送信 は有効 where the design shows it, so what still works is stated rather than implied.

Out — stays in #15:

  • The 6d failure screen itself (耳を塞がれてる, the flatlined 無音 — 権限なし meter, 設定を開く).
  • Every other failure screen and the WAITING treatment.

Acceptance

  • A turn can be sent by typing, on the desktop window and on Android, with the mic never touched.
  • sends and ⇧⏎ inserts a newline.
  • Onboarding's 集音 step can be completed by choosing text instead of granting the mic.
  • Verified by typing a message on a real device and getting her answer back, not by a test that asserts the message shape.
  • #15 — the failure screens this was split from; 6d stays there
  • #35 — Rust audio; this issue is what makes the product usable before it lands
  • #51 — the desktop PTT hotkey, the other dead mic route
  • #40 — enrolment; onboarding's 集音 step is touched by both
There is no way to type to her. The receiver chrome has no text input at all — the only `<Input>` elements in the entire client are onboarding's address field (`onboarding.tsx:349`) and the settings sheet (`settings-sheet.tsx:435`). Both mic keys are the only way in, and on desktop both are currently dead (`#35`, `#51`). Split out of `#15`, which holds the full failure-screen taxonomy and stays out of v1. **This is the part of `#15` that v1 needs**, and it is separable because the wire beneath it is already proven. ## The wire already works — only the UI is missing - **The go-between handles it.** `websocket_handler.py:72` maps `"text-input"` to `_handle_conversation_trigger`. - **The client already sends it.** `src/renderer/src/services/pairing.ts:75`: ```ts export const pairingProbe = () => ({ type: 'text-input', text: PAIRING_PROBE_TEXT }); ``` - **It was exercised end to end on 2026-07-30.** Onboarding's probe crossed client → go-between → adapter → Hermes and came back answered: ``` 17:05:15 inbound message: platform=denpa user=059ca39b-… msg='きこえてる?' 17:05:25 response ready: time=10.4s api_calls=1 response=22 chars 17:05:25 [Denpa] Sending response (22 chars) to 059ca39b-… ``` So every layer under the input box is working. This issue is the box. ## What the design specifies **The compose surface** — `docs/design/denpa-receiver/Denpa Receiver.dc.html:1016` (`palCompose`) and `:1185` (`deskCompose`), with the field at `:1018`: ``` <textarea placeholder="長い話はここに。⏎ で送信、⇧⏎ で改行。"> ``` Send on ⏎, newline on ⇧⏎, drawn for both the pal and the desktop window. **The mic-denied fallback** — `README.md:189`, the 集音 onboarding step offers `マイクを許可` **and** `文字だけで話す`. `dc.html:797` words it: 「声を聞かせて。マイクを使えないなら、文字だけでも大丈夫。」 **The rule that makes it v1** — `README.md:358`: > **6d matters most: it's the only fault where she still works.** Losing the mic must degrade to text, not to a dead end — it explicitly confirms `文字入力 · TTS · 自発送信 は有効` while `常時受信 · 押して送信` are stopped. Walking onboarding on the desktop app on 2026-07-30 produced exactly the dead end that sentence forbids: onboarding's 集音 step offered no `文字だけで話す`, and in the finished chrome both `hands-free` (`key-row.tsx:62`) and `ptt` (`:86`) raise a VAD permission dialog with nothing else to try. ## Scope In: - The compose surface in the receiver chrome, per `palCompose` / `deskCompose`, sending `{ type: 'text-input', text }`. - `⏎` sends, `⇧⏎` newlines. - `文字だけで話す` on the 集音 onboarding step, so a device that cannot hold the mic still finishes onboarding. - The capability line `文字入力 · TTS · 自発送信 は有効` where the design shows it, so what still works is stated rather than implied. Out — stays in `#15`: - The 6d failure screen itself (`耳を塞がれてる`, the flatlined `無音 — 権限なし` meter, `設定を開く`). - Every other failure screen and the WAITING treatment. ## Acceptance - A turn can be sent by typing, on the desktop window and on Android, with the mic never touched. - `⏎` sends and `⇧⏎` inserts a newline. - Onboarding's 集音 step can be completed by choosing text instead of granting the mic. - Verified by **typing a message on a real device and getting her answer back**, not by a test that asserts the message shape. ## Related - `#15` — the failure screens this was split from; 6d stays there - `#35` — Rust audio; this issue is what makes the product usable before it lands - `#51` — the desktop PTT hotkey, the other dead mic route - `#40` — enrolment; onboarding's 集音 step is touched by both
aiko closed this issue 2026-07-30 18:01:22 +00:00
Author
Owner

Landed as c1328ca, merged c82517c on main.

What changed

The chrome has a 文 key and a compose panel. chrome-spec.ts gains COMPOSE/COMPOSE_KEY_WIDTH; key-row.tsx:71 draws the key at the leading edge, so it exists in the dock on 1a and in the wide band on 1c without a second component. The panel it opens is the one the pal already had — components/pal/pal-composer-panel.tsx, now taking a width so the chrome's bands can stretch it. dock.tsx:65 and transmission-band.tsx:150 render it above their key row.

micHeld does not reach the 文 key. An ask holds the two mic keys (key-row.tsx); 文 is the one way in that never touches the microphone, so it stays live.

The send is the existing one. use-receiver-chrome.ts:168 pulls useTextInput and mirrors each keystroke into it, then calls handleSend() — the same path the pal, the pet-mode subtitle and the footer use. No second text-input frame, so her reply to a typed message is not drawn as 自発.

⏎ / ⇧⏎ come from pal-composer.ts's composerKeyAction, unchanged and already tested — including leaving ⏎ to the IME while a candidate window is open.

Onboarding's 集音 step. It already carried 文字だけで話す as the skip key (onboarding-spec.ts MIC_SKIP), and that was already wired to advance. Added: 6d's capability line 文字入力 · TTS · 自発送信 は有効 under the skip cost, and MIC_BODY is now the design's own wording from dc.html:797 — 「声を聞かせて。マイクを使えないなら、文字だけでも大丈夫。」 — rather than the flatter 声で話せるようにする?.

What I did not do

  • The acceptance list's last line is not met by me. "Verified by typing a message on a real device and getting her answer back" — I did not run the stack or a device. What is verified is the box, at both densities, and that it reaches the one send path; the wire under it is the one this issue records as already proven. Someone should type into the desktop window once before this is called shipped.
  • The 6d failure screen itself, the flatlined 無音 — 権限なし meter and 設定を開く stay in #15, as scoped. There is still no persisted "the OS refused the mic" state anywhere in the client — vad-context.tsx toasts and forgets — so the capability line lives on the onboarding step where a refusal is actually expressible, not on a fault screen that does not exist yet. That is the honest placement, not the design's.
  • Rotating across the 1a/1c breakpoint remounts the panel and discards an unsent draft. Whether it was open survives (held in the hook); the text does not. Same as the pal.

Gate

  • npm test: 753 passed, 49 files (baseline 742 / 48). New: components/receiver/compose.test.tsx (9) and two in onboarding.test.tsx.
  • cargo test in src-tauri: 18 passed.
  • npx tsc --noEmit: no errors in any touched file.
Landed as `c1328ca`, merged `c82517c` on `main`. ## What changed **The chrome has a 文 key and a compose panel.** `chrome-spec.ts` gains `COMPOSE`/`COMPOSE_KEY_WIDTH`; `key-row.tsx:71` draws the key at the leading edge, so it exists in the dock on 1a and in the wide band on 1c without a second component. The panel it opens is the one the pal already had — `components/pal/pal-composer-panel.tsx`, now taking a `width` so the chrome's bands can stretch it. `dock.tsx:65` and `transmission-band.tsx:150` render it above their key row. **`micHeld` does not reach the 文 key.** An ask holds the two mic keys (`key-row.tsx`); 文 is the one way in that never touches the microphone, so it stays live. **The send is the existing one.** `use-receiver-chrome.ts:168` pulls `useTextInput` and mirrors each keystroke into it, then calls `handleSend()` — the same path the pal, the pet-mode subtitle and the footer use. No second `text-input` frame, so her reply to a typed message is not drawn as 自発. **⏎ / ⇧⏎** come from `pal-composer.ts`'s `composerKeyAction`, unchanged and already tested — including leaving ⏎ to the IME while a candidate window is open. **Onboarding's 集音 step.** It already carried `文字だけで話す` as the skip key (`onboarding-spec.ts` `MIC_SKIP`), and that was already wired to advance. Added: 6d's capability line `文字入力 · TTS · 自発送信 は有効` under the skip cost, and `MIC_BODY` is now the design's own wording from `dc.html:797` — 「声を聞かせて。マイクを使えないなら、文字だけでも大丈夫。」 — rather than the flatter `声で話せるようにする?`. ## What I did **not** do - **The acceptance list's last line is not met by me.** "Verified by typing a message on a real device and getting her answer back" — I did not run the stack or a device. What is verified is the box, at both densities, and that it reaches the one send path; the wire under it is the one this issue records as already proven. **Someone should type into the desktop window once before this is called shipped.** - The 6d failure screen itself, the flatlined 無音 — 権限なし meter and 設定を開く stay in `#15`, as scoped. There is still no persisted "the OS refused the mic" state anywhere in the client — `vad-context.tsx` toasts and forgets — so the capability line lives on the onboarding step where a refusal is actually expressible, not on a fault screen that does not exist yet. That is the honest placement, not the design's. - Rotating across the 1a/1c breakpoint remounts the panel and discards an unsent draft. Whether it was *open* survives (held in the hook); the text does not. Same as the pal. ## Gate - `npm test`: **753 passed, 49 files** (baseline 742 / 48). New: `components/receiver/compose.test.tsx` (9) and two in `onboarding.test.tsx`. - `cargo test` in `src-tauri`: **18 passed**. - `npx tsc --noEmit`: no errors in any touched file.
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#53
No description provided.