pages: wrap long names so the tables fit at any font width (#252)

The 1710px spec from the fork passed against main as well as against the
change it was meant to pin: its fixture names are short enough to fit
either way under Linux's monospace font, and the overrun on the reporter's
Mac came from wider glyphs and longer real names. With realistic data
(dotted FQDNs, service accounts, long emails and folder paths, which have
no natural break point) main overran by 370px, and the tighter padding on
its own still overran Connections and Recordings.

Table cells on Connections, Recordings and Reports now wrap long text
inside the cell (overflow-wrap: anywhere), with the Connect and action
buttons kept on one line. The spec gains a long-data test per page: all
three fail on main, two fail with the padding change alone, all pass now.
The e2e README lists only the specs that exist.
This commit is contained in:
Dave Kempe
2026-10-13 09:47:00 +11:00
parent c934065a84
commit e4fb8ee552
5 changed files with 92 additions and 13 deletions
+6
View File
@@ -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). */
+6
View File
@@ -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
+6
View File
@@ -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
+1 -12
View File
@@ -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.
+73 -1
View File
@@ -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);
});