From ee801065c2d9d90ede458553d2b4f9d9156521fa Mon Sep 17 00:00:00 2001 From: "[._.]/ Adam Eivy" Date: Wed, 2 Sep 2026 06:19:18 +0000 Subject: [PATCH] fix: wrap the machine-output pre blocks the app shell was clipping (#5675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layout's root is `overflow-x-hidden`, so a
 whose content can exceed the viewport loses its tail off the right edge with no horizontal scrollbar to recover it.

The update log and the read-only legacy script command were the last two single-line 
 openers in the client tree carrying no wrap or overflow class at all; both now match the ProcessLogLines idiom (`whitespace-pre-wrap break-all`), with the update log additionally capped at `max-h-48 overflow-y-auto` so a long log doesn't push the rest of the tab off-screen.

JobCard's two other output blocks had `whitespace-pre-wrap` but no break class, which only wraps at whitespace — a shell job's last output is full of unbroken paths and URLs that still ran past the clip edge, so it gets `break-all` too. The prompt template beside it gets `break-words` instead: it is human-authored prose, where breaking mid-word is worse than the rare long token.
---
 client/src/components/apps/tabs/UpdateTab.jsx |  2 +-
 .../components/apps/tabs/UpdateTab.test.jsx   | 29 ++++++++
 client/src/components/cos/JobCard.jsx         |  6 +-
 client/src/components/cos/JobCard.test.jsx    | 71 +++++++++++++++++++
 4 files changed, 104 insertions(+), 4 deletions(-)
 create mode 100644 client/src/components/cos/JobCard.test.jsx

diff --git a/client/src/components/apps/tabs/UpdateTab.jsx b/client/src/components/apps/tabs/UpdateTab.jsx
index 35004cefad..55d89ae27e 100644
--- a/client/src/components/apps/tabs/UpdateTab.jsx
+++ b/client/src/components/apps/tabs/UpdateTab.jsx
@@ -678,7 +678,7 @@ export default function UpdateTab() {
               )}
             
             {status.lastUpdateResult.log && (
-              
{status.lastUpdateResult.log}
+
{status.lastUpdateResult.log}
)} diff --git a/client/src/components/apps/tabs/UpdateTab.test.jsx b/client/src/components/apps/tabs/UpdateTab.test.jsx index f44a7b8b26..709692ea92 100644 --- a/client/src/components/apps/tabs/UpdateTab.test.jsx +++ b/client/src/components/apps/tabs/UpdateTab.test.jsx @@ -331,3 +331,32 @@ describe('UpdateTab — active CoS agent suppression', () => { expect(screen.getByRole('button', { name: 'Reconcile Now' })).toBeTruthy(); }); }); + +describe('UpdateTab — last update log', () => { + beforeEach(() => { + handlers.clear(); + mockCheckHealth.mockReset().mockResolvedValue({ version: '2.24.0', uptime: 120 }); + mockExecutePortosUpdate.mockReset(); + mockToast.mockClear(); + }); + + it('wraps a long single-line update log so its tail stays reachable', async () => { + // Real update logs contain long unbroken paths/URLs. The app shell is + // `overflow-x-hidden`, so an unwrapped
 clips the tail with no
+    // horizontal scrollbar to recover it — the wrap pair is the fix.
+    const log = `error: ${'no-spaces-in-this-path/'.repeat(20)}done`;
+    mockGetUpdateStatus.mockReset().mockResolvedValue({
+      currentVersion: '2.24.0',
+      lastUpdateResult: { success: false, version: '2.25.0', log },
+    });
+
+    render();
+
+    const pre = await screen.findByText(log);
+    expect(pre.tagName).toBe('PRE');
+    expect(pre.className).toContain('whitespace-pre-wrap');
+    expect(pre.className).toContain('break-all');
+    // A multi-thousand-line log must not push the rest of the tab off-screen.
+    expect(pre.className).toContain('overflow-y-auto');
+  });
+});
diff --git a/client/src/components/cos/JobCard.jsx b/client/src/components/cos/JobCard.jsx
index c3a3186ba2..7109fe98fe 100644
--- a/client/src/components/cos/JobCard.jsx
+++ b/client/src/components/cos/JobCard.jsx
@@ -530,7 +530,7 @@ export default function JobCard({
               ) : editData.type === 'script' ? (
                 
Legacy script command (read-only) -
{editData.command || 'No command'}
+
{editData.command || 'No command'}
) : (