diff --git a/web/src/structured.test.ts b/web/src/structured.test.ts
index 937d230..e497b34 100644
--- a/web/src/structured.test.ts
+++ b/web/src/structured.test.ts
@@ -725,7 +725,7 @@ describe("structured connection: reconnection/error/loading", () => {
});
const transcript = conn.el.querySelector(".structured-rows")!;
- expect(transcript.textContent).toBe("Younew");
+ expect(transcript.textContent).toBe("new");
expect(transcript.querySelector('[data-row-id="plan"]')).toBeNull();
expect(conn.el.querySelector(".structured-rail")).toBeNull();
conn.dispose();
diff --git a/web/src/structuredRender.test.ts b/web/src/structuredRender.test.ts
index 93e09e8..d0c5c9e 100644
--- a/web/src/structuredRender.test.ts
+++ b/web/src/structuredRender.test.ts
@@ -23,6 +23,7 @@ describe("buildTranscriptRow: user/agent/thought", () => {
);
expect(el.className).toContain("st-row-user");
expect(el.textContent).toContain("hi there");
+ expect(el.querySelector(".st-who")).toBeNull();
// never actual markup from user input
expect(el.querySelector("b")).toBeNull();
});
@@ -61,12 +62,11 @@ describe("buildTranscriptRow: user/agent/thought", () => {
const el = buildTranscriptRow(
row("agent_text", { content: { type: "text", text: "copy **this** exactly" } }),
);
- const body = el.querySelector(".st-body")!;
- expect(body.getAttribute("role")).toBe("button");
- body.click();
+ expect(el.getAttribute("role")).toBe("button");
+ el.click();
await Promise.resolve();
expect(writeText).toHaveBeenCalledWith("copy **this** exactly");
- body.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
+ el.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", bubbles: true }));
await Promise.resolve();
expect(writeText).toHaveBeenCalledTimes(2);
});
diff --git a/web/src/structuredRender.ts b/web/src/structuredRender.ts
index 640022c..bb12fa1 100644
--- a/web/src/structuredRender.ts
+++ b/web/src/structuredRender.ts
@@ -175,21 +175,17 @@ function buildContentRow(row: TranscriptRow, variant: "user" | "agent" | "though
const wrap = el("div", `st-row st-row-${variant}`);
const body = el("div", "st-body");
if (variant === "user") {
- const who = el("div", "st-who");
- who.textContent = "You";
- body.appendChild(who);
const line = el("div", "st-user-text");
line.textContent = text;
- makeCopyable(line, text, "message");
body.appendChild(line);
} else if (variant === "agent") {
renderProse(body, text);
- makeCopyable(body, text, "response");
} else {
body.textContent = text;
- makeCopyable(body, text, "thought");
}
wrap.appendChild(body);
+ const label = variant === "user" ? "message" : variant === "agent" ? "response" : "thought";
+ makeCopyable(wrap, text, label);
return wrap;
}
@@ -404,8 +400,8 @@ function buildPlainRow(row: TranscriptRow): HTMLElement {
const wrap = el("div", "st-row st-row-notice");
const body = el("div", "st-body");
body.textContent = `[${row.kind}]`;
- makeCopyable(body, body.textContent, "notice");
wrap.appendChild(body);
+ makeCopyable(wrap, body.textContent, "notice");
return wrap;
}
diff --git a/web/src/styles.css b/web/src/styles.css
index 211c91a..c3c3122 100644
--- a/web/src/styles.css
+++ b/web/src/styles.css
@@ -2203,10 +2203,8 @@ input.leaf-rename:focus {
}
.structured-rows {
- padding: var(--sp-3);
display: flex;
flex-direction: column;
- gap: var(--sp-3);
min-width: 0;
}
@@ -2214,6 +2212,7 @@ input.leaf-rename:focus {
.st-row {
display: flex;
gap: var(--sp-3);
+ padding: var(--sp-3);
min-width: 0;
}
@@ -2222,20 +2221,22 @@ input.leaf-rename:focus {
min-width: 0;
}
-/* user turn: the only tinted block besides the approval — an accent rail,
- never a full border, so it still reads lighter than the approval card. */
+/* User turns read as compact chat bubbles while the padded row remains the
+ full hover/copy target. */
.st-row-user {
- border-left: 2px solid var(--accent);
- background: var(--surface-2);
- padding: var(--sp-3);
+ justify-content: flex-end;
}
-.st-who {
- font-size: var(--fs-11);
- letter-spacing: 0.1em;
- text-transform: uppercase;
- color: var(--faint);
- margin-bottom: var(--sp-1);
+.st-row-user .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);
+}
+
+.st-row-user:hover {
+ background: var(--surface-2);
}
.st-user-text {
@@ -2322,6 +2323,7 @@ input.leaf-rename:focus {
/* tool rows: bordered, compact, deliberately not lifted */
.st-tool {
+ flex: 1 1 auto;
border: 1px solid var(--border);
border-radius: var(--r-2);
min-width: 0;
@@ -2346,6 +2348,11 @@ input.leaf-rename:focus {
background: var(--surface-2);
}
+.st-row-tool:hover,
+.st-row-approval:hover {
+ background: var(--surface-2);
+}
+
.st-tool-glyph {
width: 16px;
flex: 0 0 auto;
@@ -2436,6 +2443,12 @@ input.leaf-rename:focus {
border: 1px solid var(--border);
border-radius: var(--r-2);
background: color-mix(in oklab, var(--surface-2) 55%, transparent);
+ padding: var(--sp-2) var(--sp-3);
+ transition: background var(--dur-fast) var(--ease-out);
+}
+
+.st-tool-group:hover {
+ background: var(--surface-2);
}
.st-tool-group-head {
@@ -2449,7 +2462,7 @@ input.leaf-rename:focus {
display: flex;
align-items: center;
gap: var(--sp-2);
- padding: var(--sp-2) var(--sp-3);
+ padding: 0;
min-width: 0;
}
@@ -2481,10 +2494,9 @@ input.leaf-rename:focus {
}
.st-tool-group-body {
- padding: 0 var(--sp-2) var(--sp-2);
+ padding-top: var(--sp-2);
display: flex;
flex-direction: column;
- gap: var(--sp-2);
}
.st-tool-group-body[hidden] {