Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions familiar-workspace/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -3993,9 +3993,9 @@ code {
/* Long URLs and unbroken tokens wrap instead of widening the panel. */
overflow-wrap: anywhere;
}
/* Code blocks keep their formatting, so they cannot wrap — let them scroll
inside their own box rather than stretching the message column and taking
the whole conversation sideways with it. */
/* Code blocks wrap rather than scroll: see the "Code blocks WRAP" block
at the end of this file. The width rules below still keep a long line
from sizing the message column. */
.chat-msg-body pre {
/* width:0 makes this contribute nothing to the parent's max-content size,
so .chat-msg-body is sized by its other children instead of by the
Expand Down Expand Up @@ -6700,3 +6700,28 @@ code {
white-space: nowrap;
border: 0;
}

/* ── Code blocks WRAP, they don't scroll (2026-10-02) ────────────────────
Operator decision after a long curl in a chat code block forced horizontal
scrolling. This supersedes the earlier "let code blocks scroll inside their
own box" choice in every view that renders markdown code.

Wrapping has to reach the inner <code>, not just the <pre>: highlight.js
styles .hljs, and text inside a non-wrapping <code> ignores its parent.
Measured before the fix: CODE.hljs scrollWidth 2834 in a 378px box.
overflow-wrap:anywhere breaks unbroken tokens (URLs, base64, long flags)
that pre-wrap alone would leave running off the edge.

Placed last so it wins over the per-view rules above at equal specificity,
and over any highlight.js theme's lower-specificity .hljs rule. Guarded by
tests/e2e/flows/chat-overflow.spec.ts, which now fails if a code block
scrolls or clips instead of wrapping. */
.chat-msg-body pre,
.chat-msg-body pre code,
.chat-msg-body pre code.hljs,
.notes-editor .toastui-editor-contents pre,
.notes-editor .toastui-editor-contents pre code,
.action-run-detail pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
13 changes: 13 additions & 0 deletions familiar-workspace/static/mobile.css
Original file line number Diff line number Diff line change
Expand Up @@ -2318,3 +2318,16 @@ select.mob-sa-input {
position: absolute;
inset: -7px;
}

/* ── Code blocks WRAP, they don't scroll (2026-10-02) ────────────────────
Mirrors the same rule in app.css; see its comment. Mobile chat bubbles had
the identical bug (overflow-x:auto, no wrap) and a phone is where sideways
scrolling a code block is worst. */
.mob-msg .bubble pre,
.mob-msg .bubble pre code,
.mob-msg .bubble pre code.hljs,
.mob-note-body .toastui-editor-contents pre,
.mob-note-body .toastui-editor-contents pre code {
white-space: pre-wrap;
overflow-wrap: anywhere;
}
18 changes: 16 additions & 2 deletions tests/e2e/flows/chat-overflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,25 @@ test("the chat panel never scrolls horizontally", async ({ stack, browser, reque
return out;
});

// TABLE / PRE / the thinking trace scrolling inside themselves is by design.
// Only a TABLE (and the collapsible thinking trace) may scroll inside
// itself. Code blocks WRAP: operator decision 2026-10-02, after a long
// curl in a code block forced horizontal scrolling. PRE/CODE are no
// longer exempt, so a code block that scrolls fails here.
const bad = offenders.filter(
(o) => !/^(TABLE|PRE|CODE)\./.test(o) && !/chat-msg-thinking-body/.test(o),
(o) => !/^TABLE\./.test(o) && !/chat-msg-thinking-body/.test(o),
);
expect(bad, "unexpected horizontal overflow:\n" + offenders.join("\n")).toEqual([]);

const pre = await page.evaluate(() => {
const el = document.querySelector(".chat-msg-body pre");
if (!el) return null;
const lh = parseFloat(getComputedStyle(el).lineHeight) || 18;
return { lines: el.clientHeight / lh, clipped: el.scrollWidth > el.clientWidth + 1,
ws: getComputedStyle(el.querySelector("code") || el).whiteSpace };
});
expect(pre, "no code block rendered").not.toBeNull();
expect(pre!.clipped, "code block clips or scrolls instead of wrapping").toBe(false);
expect(pre!.lines, "400-char line should wrap onto several lines, ws=" + pre!.ws).toBeGreaterThan(3);
} finally {
await ctx.close();
}
Expand Down
Loading