diff --git a/static/connections.html b/static/connections.html
index ef9561a..f3873d3 100644
--- a/static/connections.html
+++ b/static/connections.html
@@ -118,6 +118,12 @@
.entries-table td:last-child { text-align: right; white-space: nowrap; }
.entries-table .actions-cell { white-space: nowrap; text-align: right; }
+ /* Long names wrap inside their cell instead of widening the table past
+ the page: a dotted FQDN like db01.prod.example.internal has no
+ natural break point, so at any width a long enough one overran it
+ whatever the padding (#252). Buttons stay on one line. */
+ .entries-table td { overflow-wrap: anywhere; }
+ .entries-table td.connect-cell, .entries-table td.actions-cell { overflow-wrap: normal; }
.entries-table .actions-cell .btn-small { margin-left: var(--s-1); }
/* Bulk selection (admins). */
diff --git a/static/recordings.html b/static/recordings.html
index 64366c3..afa7471 100644
--- a/static/recordings.html
+++ b/static/recordings.html
@@ -102,6 +102,12 @@
#player-time { color: var(--text-muted); font-size: var(--fz-sm); white-space: nowrap; }
.btn-delete { color: var(--primary); }
.btn-delete:hover { color: var(--primary-hover); }
+ /* Long names, emails and folder paths wrap inside their cell instead
+ of widening the table past the page: a dotted FQDN has no natural
+ break point (#252). The action buttons stay on one line. */
+ #recording-list td { overflow-wrap: anywhere; }
+ #recording-list td:last-child { overflow-wrap: normal; }
+
/* Narrow desktops (a 1710px MacBook display): the 18px table with
20px cell padding ran past the right edge. Same treatment as the
connections table: 16px cells, tighter padding, 14px headers, and
diff --git a/static/reports.html b/static/reports.html
index fe7e78c..9a8eabe 100644
--- a/static/reports.html
+++ b/static/reports.html
@@ -48,6 +48,12 @@
font-size: var(--fz-sm);
}
.panel th { background: transparent; padding-top: 0; }
+ /* Long names, emails and folder paths wrap inside their cell instead
+ of widening the table past the page: a dotted FQDN has no natural
+ break point (#252). The action buttons stay on one line. */
+ #history-table td { overflow-wrap: anywhere; }
+ #history-table td:last-child { overflow-wrap: normal; }
+
/* Narrow desktops (a 1710px MacBook display): the 18px table with
20px cell padding ran past the right edge. Same treatment as the
connections table: 16px cells, tighter padding, 14px headers, and
diff --git a/tests/e2e/README.md b/tests/e2e/README.md
index 7b44a3a..0744087 100644
--- a/tests/e2e/README.md
+++ b/tests/e2e/README.md
@@ -23,18 +23,7 @@ One spec per change, named after the branch that introduced it:
| spec | pins |
|---|---|
-| `tables-1710` | #252: no horizontal overflow, admin table fits at 1710px |
-| `connections-sidebar` | #254: independent sidebar scroll, selected row marked and kept in view |
-| `connections-folder-filter` | #254: live filter box, ARIA tree, arrow keys, `f` shortcut |
-| `connections-app-shell` | #254: clipped body, two scroll panes, folder change scrolls the pane not the page |
-| `role-nav` | one nav on every page, gated by `data-role-level` |
-| `sessions-adhoc-modal` | ad-hoc form opens as a modal |
-| `facet-filters` | shared facets narrow lists, persist, combine with search |
-| `folder-display-name` | display name over the key, modal field round-trips |
-| `themed-dialogs` | `rgConfirm`/`rgAlert` replace native dialogs |
-| `global-search` | `/` opens the overlay on every page, folder view untouched |
-| `search-under-modals` | a page modal covers the search box |
-| `zero-click-fullscreen` | #257: same-tab iframe sized from the measured viewport |
+| `tables-1710` | #252: no horizontal overflow at 1710px on Connections, Recordings and Reports, with short and with long unbreakable names |
The fixture suppresses the first-visit onboarding tour (`options.onboarding`
re-enables it) and defaults to the admin role; pass `role` to test gating.
diff --git a/tests/e2e/specs/tables-1710.spec.mjs b/tests/e2e/specs/tables-1710.spec.mjs
index 689239f..ee56412 100644
--- a/tests/e2e/specs/tables-1710.spec.mjs
+++ b/tests/e2e/specs/tables-1710.spec.mjs
@@ -2,7 +2,7 @@
// sideways. Each list page must fit its table inside the viewport at 1710px
// with nothing cut off, and the page must not scroll horizontally.
import { test, expect } from '@playwright/test';
-import { installApi, settle } from '../lib/api.mjs';
+import { installApi, settle, folder, entry } from '../lib/api.mjs';
test.use({ viewport: { width: 1710, height: 1112 } });
@@ -54,3 +54,75 @@ test('reports: the session history table fits at 1710px', async ({ page }) => {
await tableFits(page, '#history-table');
await noHorizontalOverflow(page);
});
+
+// The fixture above is short enough to fit even without #252's changes, so
+// these use the data that actually overran the page: dotted FQDNs, service
+// accounts, long emails and folder paths. None of them has a natural break
+// point, so they are what the wrapping has to cope with at any font width.
+const LONG_HOST = 'sqlclusternode03.prod.melbourne.acmeholdings.internal';
+const LONG_USER = 'firstname.lastname@subsidiary.acmeholdings.com.au';
+
+function wideFolder() {
+ return folder('wide', {
+ entries: [
+ entry('sqlclusternode03', 'rdp', {
+ hostname: LONG_HOST,
+ username: 'svcbackupoperator',
+ fields: { jump_hosts: [{ hostname: 'bastion01.dmz.acmeholdings.net' }] }
+ }),
+ entry('fileserverreplica02', 'ssh', {
+ hostname: 'fileserverreplica02.dr.sydney.acmeholdings.internal',
+ username: 'ansibleautomation'
+ })
+ ]
+ });
+}
+
+test('connections: long hostnames and a jump host still fit at 1710px', async ({ page }) => {
+ await installApi(page, { role: 'admin', folders: [wideFolder()] });
+ await page.goto('/connections.html');
+ await settle(page);
+ await page.locator('.folder-row').filter({ hasText: /wide/ }).first().click();
+ await expect(page.locator('.entries-table tbody tr').first()).toBeVisible();
+ await tableFits(page, '.entries-table');
+ // The last admin action must be on screen, not just the table box.
+ const del = await page.locator('[data-delete-entry]').first().boundingBox();
+ expect(del.x + del.width).toBeLessThanOrEqual(page.viewportSize().width);
+ await noHorizontalOverflow(page);
+});
+
+test('recordings: long entry names, users and folders still fit at 1710px', async ({ page }) => {
+ await installApi(page, {
+ recordings: [{
+ name: `rdp-${LONG_HOST}-20261011-020000.guac`, modified: '2026-10-11T02:30:00Z',
+ size_bytes: 9_000_000, session_type: 'rdp', user: LONG_USER,
+ folder: 'shared/customers/acmeholdings/production',
+ address_book_entry: 'sqlclusternode03', entry_display_name: LONG_HOST
+ }]
+ });
+ await page.goto('/recordings.html');
+ await settle(page);
+ await expect(page.locator('#recording-list tbody tr').first()).toBeVisible();
+ await tableFits(page, '#recording-list');
+ await noHorizontalOverflow(page);
+});
+
+test('reports: long hostnames and users still fit at 1710px', async ({ page }) => {
+ await installApi(page, {
+ routes: {
+ 'GET /api/reports/sessions': {
+ total: 1, limit: 100, offset: 0,
+ sessions: [{
+ session_id: '1', session_type: 'rdp', hostname: LONG_HOST, username: 'svcbackupoperator',
+ created_by: LONG_USER, status: 'Ended', started_at: '2026-10-11T02:00:00Z',
+ ended_at: '2026-10-11T02:30:00Z', duration_secs: 1800, entry_name: LONG_HOST
+ }]
+ }
+ }
+ });
+ await page.goto('/reports.html');
+ await settle(page);
+ await expect(page.locator('#history-body tr').first()).toBeVisible();
+ await tableFits(page, '#history-table');
+ await noHorizontalOverflow(page);
+});