Skip to content

feat(web): queue messages while a chat is running (outbox parity with mobile) - #26

Merged
radroid merged 1 commit into
mainfrom
t3code/web-message-queue
Jul 27, 2026
Merged

feat(web): queue messages while a chat is running (outbox parity with mobile)#26
radroid merged 1 commit into
mainfrom
t3code/web-message-queue

Conversation

@radroid

@radroid radroid commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Closes #20.

Ports the mobile thread outbox to the web app. Submitting while a thread is busy or disconnected queues the message instead of erroring; a drain sends the queue FIFO once the thread is idle and connected.

What's included

  • Composer UX — the primary action becomes Queue when a submit would enqueue (thread busy, environment disconnected, or the outbox is already non-empty); Stop is demoted to a secondary control while running. Queued messages show above the composer with inline edit/remove.
  • Drain engine (useThreadOutboxDrain) — a single global in-flight lock, one send at a time, canSettle idle-gate (survives a just-adopted turn), settings-sync before start-turn, transient-vs-deterministic retry classification with backoff.
  • Persistence — one localStorage key per queued message ("skip invalid, never fatal"); all mutations serialized through a FIFO promise chain (storage-first, then atom).
  • Reuses the shared client-runtime thread-settled predicates and environment scoping — no forked logic.

Scope (matches mobile except)

  • Image attachments are not queued (blob previews can't survive a localStorage reload).
  • Offline new-thread creation is out of scope for v1 (existing-thread queuing only).

Pre-land adversarial review — fixed here

A 4-lens adversarial review + refutation pass found 6 findings (2 medium, 4 low). Fixed:

  • [medium] Removed-environment resurrection — removing an environment now purges its queued messages (clearEnvironment, wired into EnvironmentOwnedDataCleanup), so they can't linger and auto-send if the same server is re-added. Covered by a new regression test (incl. the persisted-only "zombie" case).
  • [medium] Image-only queue submit — no longer enqueues a bootstrap prompt that claims images are attached while carrying none (which also discarded the image); it warns and keeps the composer intact.
  • [low] Warning ordering — the "images aren't queued" toast now fires only after a successful enqueue.
  • [low] Drain heartbeat — while messages are queued, the drain re-evaluates the time-based idle-gate on a cadence so a head can't stall until an unrelated re-render.

Known v1 limitation (deferred)

  • No cross-tab reconciliation — two open tabs run independent drains over the same localStorage queue (no mobile parity source; narrow scenario). Documented for a follow-up.

Verification

  • tsgo --noEmit (web): 0 errors.
  • vp test run src/outbox: 19 passed.
  • Browser E2E is manual QA (self-tested by the author).

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 35e665fb-6903-4ced-91d7-5cbbc28c6dfb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/web-message-queue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@radroid
radroid force-pushed the t3code/web-message-queue branch from 2bd9ee1 to bb89478 Compare July 27, 2026 14:54
Port the mobile "thread outbox" to the web app: submitting while a thread
is busy or disconnected enqueues the message instead of erroring, and a
drain sends the queue FIFO once the thread is idle and connected. The
composer's primary action becomes "Queue" (Stop demoted to a secondary
control) while busy; queued messages are editable/removable above the
composer and persist across reloads via localStorage.

Scope matches mobile except: image attachments are not queued (blob
previews can't survive a localStorage reload) and offline new-thread
creation is out of scope for v1.

Includes fixes from an adversarial pre-land review:
- Purge an environment's queued messages when it is removed, so they
  can't resurrect and auto-send if the same server is re-added (ports
  clearEnvironment; wires it into EnvironmentOwnedDataCleanup).
- Never queue an image-only submit (it would enqueue a prompt claiming
  images are attached while carrying none, and discard the image); warn
  and keep the composer intact instead.
- Show the "images aren't queued" warning only after a successful
  enqueue so a failed enqueue can't misinform.
- Heartbeat the drain while messages are queued so a head held back only
  by the time-based idle-gate is delivered promptly.

Known v1 limitation: no cross-tab reconciliation — two open tabs run
independent drains over the same localStorage queue.

Closes #20

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@radroid
radroid force-pushed the t3code/web-message-queue branch from bb89478 to 3cced85 Compare July 27, 2026 15:11
@radroid
radroid merged commit 72b2a16 into main Jul 27, 2026
2 of 3 checks passed
@radroid
radroid deleted the t3code/web-message-queue branch July 27, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(web): queue messages while a chat is running (outbox parity with mobile)

1 participant