Compare commits
2
Commits
grave-2
...
staccato-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9c9e1c647 | ||
|
|
4167b21fe9 |
@@ -824,7 +824,9 @@ describe("structured connection: turn/composer gating", () => {
|
|||||||
expect(send.disabled).toBe(false);
|
expect(send.disabled).toBe(false);
|
||||||
expect(sendStructuredPrompt).toHaveBeenCalledTimes(1);
|
expect(sendStructuredPrompt).toHaveBeenCalledTimes(1);
|
||||||
expect(sendStructuredPrompt).toHaveBeenCalledWith("s1", "only once");
|
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();
|
conn.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -844,7 +846,7 @@ describe("structured connection: turn/composer gating", () => {
|
|||||||
submit("second");
|
submit("second");
|
||||||
submit("third");
|
submit("third");
|
||||||
expect(sendStructuredPrompt).toHaveBeenCalledTimes(1);
|
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());
|
await Promise.resolve().then(() => Promise.resolve());
|
||||||
ws.emit({ type: "reduced_state", state: idleState });
|
ws.emit({ type: "reduced_state", state: idleState });
|
||||||
@@ -853,7 +855,7 @@ describe("structured connection: turn/composer gating", () => {
|
|||||||
await Promise.resolve().then(() => Promise.resolve());
|
await Promise.resolve().then(() => Promise.resolve());
|
||||||
ws.emit({ type: "reduced_state", state: idleState });
|
ws.emit({ type: "reduced_state", state: idleState });
|
||||||
expect(sendStructuredPrompt).toHaveBeenNthCalledWith(3, "s1", "third");
|
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();
|
conn.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+17
-6
@@ -117,7 +117,6 @@ export function createStructuredConnection(
|
|||||||
<svg class="structured-cancel-spinner" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="50 14" stroke-linecap="round"/></svg>
|
<svg class="structured-cancel-spinner" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" stroke-dasharray="50 14" stroke-linecap="round"/></svg>
|
||||||
<svg class="structured-cancel-stop" viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor"/></svg>
|
<svg class="structured-cancel-stop" viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<span class="structured-queue" role="status" aria-live="polite" hidden></span>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +138,6 @@ export function createStructuredConnection(
|
|||||||
const sendBtn = el.querySelector(".structured-send") as HTMLButtonElement;
|
const sendBtn = el.querySelector(".structured-send") as HTMLButtonElement;
|
||||||
const sendLabel = sendBtn.querySelector("span") as HTMLSpanElement;
|
const sendLabel = sendBtn.querySelector("span") as HTMLSpanElement;
|
||||||
const cancelBtn = el.querySelector(".structured-cancel") as HTMLButtonElement;
|
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 attachBtn = el.querySelector(".structured-attach") as HTMLButtonElement;
|
||||||
const fileInput = el.querySelector(".structured-file-input") as HTMLInputElement;
|
const fileInput = el.querySelector(".structured-file-input") as HTMLInputElement;
|
||||||
const attachmentEl = el.querySelector(".structured-attachments") as HTMLDivElement;
|
const attachmentEl = el.querySelector(".structured-attachments") as HTMLDivElement;
|
||||||
@@ -730,6 +728,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 {
|
function renderComposerState(): void {
|
||||||
const projectedActive = state.turn.responding || state.turn.thinking;
|
const projectedActive = state.turn.responding || state.turn.thinking;
|
||||||
input.disabled = false;
|
input.disabled = false;
|
||||||
@@ -740,10 +754,7 @@ export function createStructuredConnection(
|
|||||||
sendBtn.hidden = false;
|
sendBtn.hidden = false;
|
||||||
sendLabel.textContent = "Send";
|
sendLabel.textContent = "Send";
|
||||||
cancelBtn.hidden = !projectedActive;
|
cancelBtn.hidden = !projectedActive;
|
||||||
queueEl.hidden = promptQueue.length === 0;
|
renderQueuedPrompts();
|
||||||
queueEl.textContent = promptQueue.length === 1
|
|
||||||
? "1 message queued"
|
|
||||||
: `${promptQueue.length} messages queued`;
|
|
||||||
renderActivity();
|
renderActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-12
@@ -2375,6 +2375,22 @@ input.leaf-rename:focus {
|
|||||||
background: var(--surface-2);
|
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 {
|
.st-user-text {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
@@ -3121,18 +3137,6 @@ input.leaf-rename:focus {
|
|||||||
transition: border-color var(--dur-fast) var(--ease-out);
|
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 {
|
.structured-composer:focus-within {
|
||||||
border-top-color: var(--border);
|
border-top-color: var(--border);
|
||||||
|
|||||||
Reference in New Issue
Block a user