From b5c9970e28f78e5262dc5b32b3e090fba1e352aa Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Sun, 6 Sep 2026 15:26:31 +0100 Subject: [PATCH] fix: extend transcript hover surfaces --- web/src/structured.test.ts | 2 +- web/src/structuredRender.test.ts | 8 +++--- web/src/structuredRender.ts | 10 +++----- web/src/styles.css | 44 ++++++++++++++++++++------------ 4 files changed, 36 insertions(+), 28 deletions(-) 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] {