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);