﻿# Eight prompts write straight to stderr, past the printer

- **Status**: Todo
- **Kind**: Bug
- **Authors**: jp
- **Date**: 2026-09-08

Eight interactive prompts construct their own `io::stderr()` writer, or let
`inquire` pick its default, instead of taking one from `Printer`:

| Site                      | Prompt                            | Writer                         |
| ------------------------- | --------------------------------- | ------------------------------ |
| `bootstrap.rs:473`        | conflicting-root picker           | `io::stderr()`                 |
| `target.rs:994`           | "Select a conversation"           | `io::stderr()`                 |
| `target.rs:1082`          | "Select an archived conversation" | `io::stderr()`                 |
| `target.rs:1117`          | "Select archived conversations"   | `io::stderr()`                 |
| `target.rs:1153`          | "Select conversations"            | `io::stderr()`                 |
| `workspace/target.rs:462` | root picker                       | `io::stderr()`                 |
| `workspace/target.rs:482` | workspace picker                  | `io::stderr()`                 |
| `edit.rs:182`             | "Re-open the editor to fix it?"   | `.prompt()`, inquire's default |

Every other prompt goes through `Printer::prompt_writer()` or
`owned_prompt_writer()`, which is what RFD 048 asks for: prompts are a channel
the printer owns, so it can serialize them against everything else it writes.

## What bypassing costs

**No status-region suspension.** Acquiring a prompt writer erases any drawn
region and blocks redraws for the writer's lifetime (RFD 091).
A prompt that writes directly to stderr gets no such protection, so the
printer's worker can repaint a region row in the middle of a widget's
cursor-relative redraw and corrupt it.

Mostly theoretical today: `bootstrap.rs` and `workspace/target.rs` run before a
region can exist.
`target.rs`'s four conversation pickers are the exception — they run
mid-command, where a region plausibly is up.

**No prompt trace.** `PromptTrace` records `Prompt opened.` / `Prompt closed.`
with `waited_ms` on the `prompt` target, so a trace shows how much of a run was
spent waiting on the user.
These eight are invisible to it, which makes the trace quietly incomplete rather
than obviously so.

**No chrome policy.** `--quiet` closes the chrome channel by dropping writes at
the printer.
A prompt writing past it still renders, so `--quiet` is not the silence it
advertises.

**No `/dev/tty` fallback.** `prompt_writer()` prefers the controlling terminal,
so prompts survive `2>file`.
These eight land in the redirect and the user is left facing a prompt they
cannot see.

## Fix

Mechanically, `io::stderr()` becomes `printer.prompt_writer()` and `.prompt()`
becomes `.prompt_with_writer(&mut printer.prompt_writer())`.

The work is in reaching a printer at each site rather than in the substitution.
`target.rs`'s pickers have one to hand.
`bootstrap.rs` runs during pre-workspace resolution and may need it threaded in;
check before assuming it is available.

## Watch for

`prompt_writer()` returns a `PromptWriter`, not a `PrinterWriter`.
Both implement `fmt::Write` and `io::Write`, so call sites compile unchanged,
but a signature naming the concrete type has to change with them.

Each acquisition takes and releases a suspension.
A site that acquires one writer and reuses it across several prompts holds the
region down for the whole sequence, which is usually what you want; a site that
acquires per prompt lets the region reappear between them.
