Problem — Layout.jsx's root is w-full max-w-full overflow-x-hidden, so anything wider than the viewport is clipped, not scrollable. Two <pre> blocks render arbitrary-length machine output with the default white-space: pre, no wrap, and no overflow-x container: a self-update log line and a legacy shell command. On any viewport narrower than the longest line the tail of the text is unreachable — there is no horizontal scrollbar to find, because the shell ate it. 32 of the 63 <pre> elements in the tree already wrap or scroll correctly, so this is a two-file drift from an established idiom, not a missing convention.
Evidence — client/src/components/apps/tabs/UpdateTab.jsx:681:
{status.lastUpdateResult.log && (
<pre className="text-xs text-gray-400 mt-2 font-mono">{status.lastUpdateResult.log}</pre>
)}
client/src/components/cos/JobCard.jsx:533:
<pre className="w-full px-3 py-2 bg-port-bg border border-port-border rounded-lg text-gray-400 text-sm font-mono">{editData.command || 'No command'}</pre>
The shell that clips them, client/src/components/Layout.jsx:1173:
<div className="h-dvh-screen print:h-auto print:min-h-screen w-full max-w-full overflow-x-hidden bg-port-bg flex">
The two correct idioms already in the tree: client/src/components/settings/LocalSetupPanel.jsx:269 (max-h-48 overflow-y-auto … whitespace-pre-wrap) and client/src/components/ui/ProcessLogLines.jsx:29 (whitespace-pre-wrap break-all). ProcessLogLines is the canonical renderer for streamed process output and is the pattern to match, since a wrapped log keeps every character reachable at 360px where a horizontal scroller does not.
Plan
- In
client/src/components/apps/tabs/UpdateTab.jsx:681, change the class to text-xs text-gray-400 mt-2 font-mono whitespace-pre-wrap break-all max-h-48 overflow-y-auto. Decision: wrap + vertical scroll rather than overflow-x-auto, matching ProcessLogLines — an update log is prose-shaped output the user reads, and a horizontal scroller inside a settings tab is worse on touch.
- In
client/src/components/cos/JobCard.jsx:533, add whitespace-pre-wrap break-all to the existing classes so a long legacy command is fully readable inside the card.
- Audit and fix the other
<pre> elements whose opening tag has no overflow, whitespace-pre-wrap or break-* class: run grep -rn '<pre' client/src --include='*.jsx' | grep -v test | grep -vE 'overflow|whitespace-pre-wrap|break-' and apply the same class pair. As of this audit only the two above plus multi-line JSX openers remain.
- Do not remove
overflow-x-hidden from Layout.jsx — it is deliberate (it stops any single wide child from giving the whole app a horizontal scrollbar). The fix belongs in the leaf.
Tests
- Extend
client/src/components/apps/tabs/UpdateTab.test.jsx with a render case supplying a 400-character single-line lastUpdateResult.log and asserting the <pre> element carries whitespace-pre-wrap. Uniquely catches removal of the wrap class, which is invisible in any test that only checks the text content.
- Add the equivalent assertion to
client/src/components/cos/JobCard.test.jsx for a long editData.command.
Acceptance criteria
grep -rn '<pre' client/src --include='*.jsx' | grep -v test | grep -vE 'overflow|whitespace-pre-wrap|break-' returns no single-line results.
- A 300-character update log line is fully readable at a 360px viewport with no horizontal page scroll.
Out of scope — Layout.jsx's overflow-x-hidden, the ProcessLogModal / ProcessLogLines implementation, and <code> spans.
Filed by a /do:better --scan-only --issues audit (2026-09-01). Category: ux · Severity: medium · Files: client/src/components/apps/tabs/UpdateTab.jsx:681, client/src/components/cos/JobCard.jsx:533, client/src/components/Layout.jsx:1173, client/src/components/ui/ProcessLogLines.jsx:29
All labels already exist in the repo; do NOT create labels. Never add planner:* labels.
Problem —
Layout.jsx's root isw-full max-w-full overflow-x-hidden, so anything wider than the viewport is clipped, not scrollable. Two<pre>blocks render arbitrary-length machine output with the defaultwhite-space: pre, no wrap, and nooverflow-xcontainer: a self-update log line and a legacy shell command. On any viewport narrower than the longest line the tail of the text is unreachable — there is no horizontal scrollbar to find, because the shell ate it. 32 of the 63<pre>elements in the tree already wrap or scroll correctly, so this is a two-file drift from an established idiom, not a missing convention.Evidence —
client/src/components/apps/tabs/UpdateTab.jsx:681:client/src/components/cos/JobCard.jsx:533:The shell that clips them,
client/src/components/Layout.jsx:1173:The two correct idioms already in the tree:
client/src/components/settings/LocalSetupPanel.jsx:269(max-h-48 overflow-y-auto … whitespace-pre-wrap) andclient/src/components/ui/ProcessLogLines.jsx:29(whitespace-pre-wrap break-all).ProcessLogLinesis the canonical renderer for streamed process output and is the pattern to match, since a wrapped log keeps every character reachable at 360px where a horizontal scroller does not.Plan
client/src/components/apps/tabs/UpdateTab.jsx:681, change the class totext-xs text-gray-400 mt-2 font-mono whitespace-pre-wrap break-all max-h-48 overflow-y-auto. Decision: wrap + vertical scroll rather thanoverflow-x-auto, matchingProcessLogLines— an update log is prose-shaped output the user reads, and a horizontal scroller inside a settings tab is worse on touch.client/src/components/cos/JobCard.jsx:533, addwhitespace-pre-wrap break-allto the existing classes so a long legacy command is fully readable inside the card.<pre>elements whose opening tag has nooverflow,whitespace-pre-wraporbreak-*class: rungrep -rn '<pre' client/src --include='*.jsx' | grep -v test | grep -vE 'overflow|whitespace-pre-wrap|break-'and apply the same class pair. As of this audit only the two above plus multi-line JSX openers remain.overflow-x-hiddenfromLayout.jsx— it is deliberate (it stops any single wide child from giving the whole app a horizontal scrollbar). The fix belongs in the leaf.Tests
client/src/components/apps/tabs/UpdateTab.test.jsxwith a render case supplying a 400-character single-linelastUpdateResult.logand asserting the<pre>element carrieswhitespace-pre-wrap. Uniquely catches removal of the wrap class, which is invisible in any test that only checks the text content.client/src/components/cos/JobCard.test.jsxfor a longeditData.command.Acceptance criteria
grep -rn '<pre' client/src --include='*.jsx' | grep -v test | grep -vE 'overflow|whitespace-pre-wrap|break-'returns no single-line results.Out of scope —
Layout.jsx'soverflow-x-hidden, theProcessLogModal/ProcessLogLinesimplementation, and<code>spans.Filed by a
/do:better --scan-only --issuesaudit (2026-09-01). Category:ux· Severity:medium· Files:client/src/components/apps/tabs/UpdateTab.jsx:681, client/src/components/cos/JobCard.jsx:533, client/src/components/Layout.jsx:1173, client/src/components/ui/ProcessLogLines.jsx:29All labels already exist in the repo; do NOT create labels. Never add
planner:*labels.