diff --git a/web/src/structured.test.ts b/web/src/structured.test.ts
index fa406a3..fe288bc 100644
--- a/web/src/structured.test.ts
+++ b/web/src/structured.test.ts
@@ -824,7 +824,9 @@ describe("structured connection: turn/composer gating", () => {
expect(send.disabled).toBe(false);
expect(sendStructuredPrompt).toHaveBeenCalledTimes(1);
expect(sendStructuredPrompt).toHaveBeenCalledWith("s1", "only once");
- expect(conn.el.querySelector(".structured-queue")?.textContent).toBe("1 message queued");
+ const queued = conn.el.querySelectorAll(".st-row-queued");
+ expect(queued).toHaveLength(1);
+ expect(queued[0].textContent).toBe("queued next");
conn.dispose();
});
@@ -844,7 +846,7 @@ describe("structured connection: turn/composer gating", () => {
submit("second");
submit("third");
expect(sendStructuredPrompt).toHaveBeenCalledTimes(1);
- expect(conn.el.querySelector(".structured-queue")?.textContent).toBe("2 messages queued");
+ expect(conn.el.querySelectorAll(".st-row-queued")).toHaveLength(2);
await Promise.resolve().then(() => Promise.resolve());
ws.emit({ type: "reduced_state", state: idleState });
@@ -853,7 +855,7 @@ describe("structured connection: turn/composer gating", () => {
await Promise.resolve().then(() => Promise.resolve());
ws.emit({ type: "reduced_state", state: idleState });
expect(sendStructuredPrompt).toHaveBeenNthCalledWith(3, "s1", "third");
- expect((conn.el.querySelector(".structured-queue") as HTMLElement).hidden).toBe(true);
+ expect(conn.el.querySelectorAll(".st-row-queued")).toHaveLength(0);
conn.dispose();
});
diff --git a/web/src/structured.ts b/web/src/structured.ts
index b8985f1..8c9d7eb 100644
--- a/web/src/structured.ts
+++ b/web/src/structured.ts
@@ -117,7 +117,6 @@ export function createStructuredConnection(
-
@@ -139,7 +138,6 @@ export function createStructuredConnection(
const sendBtn = el.querySelector(".structured-send") as HTMLButtonElement;
const sendLabel = sendBtn.querySelector("span") as HTMLSpanElement;
const cancelBtn = el.querySelector(".structured-cancel") as HTMLButtonElement;
- const queueEl = el.querySelector(".structured-queue") as HTMLSpanElement;
const attachBtn = el.querySelector(".structured-attach") as HTMLButtonElement;
const fileInput = el.querySelector(".structured-file-input") as HTMLInputElement;
const attachmentEl = el.querySelector(".structured-attachments") as HTMLDivElement;
@@ -692,6 +690,22 @@ export function createStructuredConnection(
}
}
+ function renderQueuedPrompts(): void {
+ for (const old of rowsEl.querySelectorAll(".st-row-queued")) old.remove();
+ for (const queued of promptQueue) {
+ const wrap = document.createElement("div");
+ wrap.className = "st-row st-row-queued";
+ const body = document.createElement("div");
+ body.className = "st-body";
+ const line = document.createElement("div");
+ line.className = "st-user-text";
+ line.textContent = queued.text;
+ body.appendChild(line);
+ wrap.appendChild(body);
+ rowsEl.appendChild(wrap);
+ }
+ }
+
function renderComposerState(): void {
const projectedActive = state.turn.responding || state.turn.thinking;
input.disabled = false;
@@ -702,10 +716,7 @@ export function createStructuredConnection(
sendBtn.hidden = false;
sendLabel.textContent = "Send";
cancelBtn.hidden = !projectedActive;
- queueEl.hidden = promptQueue.length === 0;
- queueEl.textContent = promptQueue.length === 1
- ? "1 message queued"
- : `${promptQueue.length} messages queued`;
+ renderQueuedPrompts();
renderActivity();
}
diff --git a/web/src/styles.css b/web/src/styles.css
index dbf0132..75a6f48 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -2375,6 +2375,22 @@ input.leaf-rename:focus {
background: var(--surface-2);
}
+/* Queued messages: optimistic user bubbles, not yet sent. */
+.st-row-queued {
+ justify-content: flex-end;
+ pointer-events: none;
+}
+
+.st-row-queued .st-body {
+ flex: 0 1 auto;
+ max-width: min(86%, 72ch);
+ padding: var(--sp-2) var(--sp-3);
+ border-radius: var(--r-2);
+ background: var(--surface-3);
+ opacity: 0.45;
+ filter: blur(0.5px);
+}
+
.st-user-text {
white-space: pre-wrap;
overflow-wrap: anywhere;
@@ -3112,18 +3128,6 @@ input.leaf-rename:focus {
transition: border-color var(--dur-fast) var(--ease-out);
}
-.structured-queue {
- position: absolute;
- right: var(--sp-2);
- bottom: calc(100% + var(--sp-1));
- padding: 3px var(--sp-2);
- border: 1px solid var(--border);
- border-radius: var(--r-pill);
- background: var(--surface);
- color: var(--muted);
- font-size: var(--fs-11);
- box-shadow: var(--shadow-1);
-}
.structured-composer:focus-within {
border-top-color: var(--border);