# Wall-E Session Input Queue

## Problem

Wall-E session input currently has two incompatible behaviors:

- The composer stays editable while Wall-E is generating, so users can draft the
  next prompt.
- The transport rejects a second `walle-message` while a turn is active, so
  pressing Enter during generation either does nothing useful or leaves the user
  with a draft that is not part of the session lifecycle.

The product contract should be explicit: a prompt submitted while Wall-E is busy
is queued for the same session and dispatched only when the session is ready for
another normal user turn.

## Architecture

CTM reuses the existing persisted `queueEngine` and `prompt_queues` table. The
queue remains server-owned; desktop and phone clients only render a projection of
that state.

```text
Wall-E composer / Queue Builder
        |
        v
POST /api/queues { append: true, autoStart: true }
        |
        v
queueEngine
  - persists queue items in prompt_queues
  - holds pending items while a target is not ready
  - claims one item at a time as sending
        |
        v
Wall-E readiness gate
  - blocks while session.abortController exists
  - blocks when the latest assistant turn has pending external actions and the
    queued item is not an approval
        |
        v
handleWalleMessage()
  - appends the user turn to the Wall-E transcript
  - calls /api/wall-e/chat?stream=1
  - wakes the queue when the turn finishes
```

## State Contract

Queue item statuses:

- `pending`: durable item waiting to be dispatched.
- `sending`: exactly one item claimed by the dispatcher.
- `sent`: the Wall-E turn completed and the queue may advance.
- `skipped`: user skipped the item.

Queue status:

- `running`: queue is active. It may be dispatching or waiting on readiness.
- `paused`: queue will not dispatch until resumed.
- `done`: no remaining pending work.

Readiness metadata is intentionally separate from persisted queue contents:

- `waitingCode`
- `waitingReason`
- `waitingMessage`

These fields explain why a running queue has not dispatched yet. They are
derived from live session state and rebroadcast over WebSocket; they do not need
to be durable.

## UX Contract

When the user presses Enter in a Wall-E composer:

- If Wall-E is idle, send the prompt immediately.
- If Wall-E is generating, append one durable queue item and clear the composer
  only after the server accepts the queue.
- If a queue exists, append instead of replacing.
- Show a compact "Queued next" card above the composer with the next pending
  prompt and cancel/open-queue actions.
- Keep the Stop button scoped to the current Wall-E turn. Stopping does not
  delete queued prompts.

## Safety

The queue must never send a queued normal prompt into an approval boundary. If
the latest assistant turn contains pending external actions, a queued prompt is
held until the user sends an approval/denial or cancels the queue. This prevents
a follow-up question from being interpreted as consent.

## Tests

Required coverage:

- Queue engine waits when a readiness gate returns false, then dispatches on
  wake.
- Appending to a running async queue preserves item order.
- Wall-E composer Enter while generating creates a queue item instead of a
  second `walle-message`.
- The queued prompt card renders and clears when queue state is done/deleted.
- Dev instance browser validation uses real keyboard events against a non-primary
  CTM port.
