Merge commit '537c7ae9a50025efbf7963cdf7e544f42cbf0f7e'

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-05 18:35:14 +01:00
13 changed files with 216 additions and 19 deletions
@@ -7085,3 +7085,16 @@ Patrol-origin fixes are waiting for approval. Loading, failed-load, no-runs,
and truncated-history states carry distinct copy, and a failed load never
renders zero counts as if the week were quiet. Browser proof covers the desktop
and narrow Activity tab in `frontend-modern/browser-verification.json`.
### Phone Docker update labels preserve page-owned scrolling
Below the 33.999rem table-container boundary, Docker update cells reduce inline
padding and wrap existing badge/button content within the allocated cell. The
rule is scoped to `docker-container-update-cell`; it must not restore a nested
horizontal scrollport or change app-shell touch handling. Phone platform wrappers
retain `overflow: clip`. Labels remain text, not icon-only substitutes.
App.architecture.test.ts protects the CSS scope and scroll ownership;
96-navigation-socket-recovery.spec.ts checks real 390px text ranges and the
non-scrolling wrapper. Browser receipts do not establish physical Android touch
behaviour or every asynchronous update state.
@@ -5030,3 +5030,17 @@ organisation URL state reset. The global no-op store reports false.
A failed admission HTTP refresh is not an empty estate either: retain the last
valid facet on request failure. A successful response still replaces it,
including all-false admission; first-load failure remains unresolved.
### Existing Docker update and expanded identity text remain readable
DockerContainersTable marks its update cell as the phone reflow boundary for both
read-only status and governed UpdateButton content. The boundary must preserve
full action names, review semantics and native agent/container identity; it must
not hide update labels to satisfy width constraints. DockerNativeTables.test.ts
checks that the governed update action remains inside that boundary.
ResourceDetailDrawer headings wrap long display names rather than ellipsising
them. Overview rows may still truncate identity; keyboard expansion exposes the
full heading. This shared drawer rule applies across resource types. The 390px
browser qualification checks heading fit and keyboard expansion; no data model,
authorisation, action dispatch or resource admission contract changes.
+27 -17
View File
@@ -1,37 +1,47 @@
{
"version": 1,
"base_sha": "2356e4300022a9e0502b97030b84fc902ca15281",
"verified_at": "2026-09-05T15:02:51Z",
"base_sha": "5beeaf4cbe32603bf4d8dcb07069957a659aa1a4",
"verified_at": "2026-09-05T17:22:19Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/components/Settings/settingsPanelRegistryContext.tsx",
"frontend-modern/src/types/config.ts"
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx",
"frontend-modern/src/features/docker/DockerContainersTable.tsx",
"frontend-modern/src/index.css"
],
"content_sha256": {
"frontend-modern/src/components/Settings/settingsPanelRegistryContext.tsx": "a4c53fd983db3390e43771e484ef7337c72a0ada6e6c28bcc311b02e88cbde19",
"frontend-modern/src/types/config.ts": "07c9961a8ca87998b6d6e4f398770efd11bfd4b089ce25f18e93e5bafffd9c8d"
"frontend-modern/src/components/Infrastructure/ResourceDetailDrawer.tsx": "0c81c819d7a4fc604df3e202181815b1fcab3b7e3c28198c8a4f7a4d5e441dd3",
"frontend-modern/src/features/docker/DockerContainersTable.tsx": "555c0653223509adafbde423558ef9e89672d68c096e50f6f4772c42088b8286",
"frontend-modern/src/index.css": "db660b38b158594f6e09f179db69e76f85afed11f0dc863fb35619720445312d"
},
"routes": [
"/settings/organization/sharing"
"/docker",
"/proxmox",
"/settings",
"/alerts"
],
"viewports": [
{
"width": 1280,
"height": 720
},
{
"width": 390,
"height": 844
},
{
"width": 1100,
"height": 900
},
{
"width": 1440,
"height": 900
}
],
"states": [
"Source-built isolated e2e_runtime with mock inventory, Chromium desktop context at default zoom; narrow run changes scenario 6 viewport only, not device emulation.",
"Cookie-session organisation switch preserves currentUsername while security detail drops from privileged to authenticated; both owner memberships match. Pending incoming/outgoing shares render; incoming Accept succeeds with persisted accepted status and viewer role.",
"Pending and accepted screenshots inspected at both widths. Wide table rows and narrow scrolled layout are clipped; this is functional identity/acceptance proof, not a clean visual-layout claim. No installed customer, release, reconnect/admission or automatic interruption-cleanup qualification."
"Isolated source-built local backend with synthetic inventory; desktop Chromium at default zoom, not physical Android or touch emulation.",
"Six populated-navigation recovery cases passed: normal and failed admission at each width. Phone update text-range, single-line Current, non-scrolling wrapper and expanded heading checks passed at 390px normal admission.",
"Viewed retained final phone screenshots: Current and Update fully visible; notifications-worker wraps in expanded drawer. No blanket accessibility, 320px, every asynchronous action-state or installed-release claim."
],
"interactions": [
"pulse-heavy-run -- bash tests/integration/scripts/run-tests.sh multi-tenant: 6 passed, 1 expected disabled-feature skip.",
"PULSE_E2E_SHARING_NARROW=1 pulse-heavy-run -- bash tests/integration/scripts/run-tests.sh multi-tenant: 6 passed, 1 expected disabled-feature skip.",
"Scenario 6 creates two organisations and pending shares, reloads scoped cookie session, asserts owner identity, clicks Accept, verifies Active/Remove and accepted API role, switches to default and deletes fixtures. Scenario 7 cookie-session RBAC scope checks pass."
"pulse-heavy-run -- env PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MOCK_MODE=true PULSE_E2E_NAVIGATION_RECOVERY=1 PULSE_E2E_TABLE_ACCESS=1 PULSE_E2E_LOCAL_BACKEND_PORT=18765 npm --prefix tests/integration test -- tests/96-navigation-socket-recovery.spec.ts --project=chromium: six passed; managed backend stopped.",
"Interrupted and restored WebSocket connections; preserved populated navigation through successful and failed admission; exercised mobile More, Settings, platform switching and Alerts.",
"At 390px, horizontal wheel and ArrowRight leave wrapper unscrolled; Enter expands container detail, whose heading is scrolled into view and photographed. Receipts: tests/qualification/navigation-reconnect/2026-09-05-phone-layout/.",
"Receipt reconstructed during governance repair from the retained final run and exact unchanged runtime bytes. Only contracts and tests changed after that run."
]
}
@@ -986,3 +986,18 @@ describe('mobile bottom navigation clearance', () => {
expect(offenders).toEqual([]);
});
});
describe('Docker phone update reflow boundary', () => {
it('scopes label wrapping to narrow Docker cells without restoring a nested scrollport', () => {
const phoneStyles = appStylesSource.slice(appStylesSource.indexOf('/* Phone update states'));
expect(phoneStyles).toContain('@container (max-width: 33.999rem)');
expect(phoneStyles).toMatch(/\.docker-container-update-cell\s*\{\s*padding-inline: 0.125rem;/);
expect(phoneStyles).toContain('max-width: 100%');
expect(phoneStyles).toContain('white-space: normal');
expect(phoneStyles).toContain('overflow-wrap: anywhere');
expect(phoneStyles).toContain('flex-wrap: wrap');
expect(phoneStyles).not.toMatch(/overflow(?:-x|-y)?:\s*(auto|scroll)/);
expect(appStylesSource).toMatch(/\.table-scroll-shell\.table-scroll-shell-phone-page\s*\{\s*overflow: clip;/);
});
});
@@ -84,7 +84,7 @@ const DrawerContent: Component<ResourceDetailDrawerProps> = (props) => {
/>
<h2
id={headingId()}
class="text-sm font-semibold text-base-content truncate m-0"
class="text-sm font-semibold text-base-content min-w-0 [overflow-wrap:anywhere] m-0"
title={drawer.displayName()}
>
{drawer.displayName()}
@@ -632,7 +632,7 @@ export const DockerContainersTable: Component<DockerContainersTableProps> = (pro
);
case 'updates':
return (
<TableCell class={`${getPlatformTableCellClassForKind(column.kind)} text-base-content`}>
<TableCell class={`${getPlatformTableCellClassForKind(column.kind)} text-base-content docker-container-update-cell`}>
<Show
when={action}
fallback={
@@ -872,6 +872,9 @@ describe('Docker native tables', () => {
// One click plans the governed action; the review dialog is the
// confirmation surface, so no second in-row confirm click exists.
const updateButton = screen.getByRole('button', { name: /click to review and update/i });
// The phone reflow boundary must include the governed action, not just read-only badges.
expect(updateButton.closest('td')).toHaveClass('docker-container-update-cell');
expect(updateButton).toHaveTextContent('Update');
fireEvent.click(updateButton);
await waitFor(() =>
+30
View File
@@ -1432,3 +1432,33 @@ body,
transition: none;
}
}
/* Phone update states must remain readable inside the existing, non-scrolling
* Docker table. Wrap the badge rather than clipping it or hiding its label. */
@container (max-width: 33.999rem) {
.docker-container-update-cell {
padding-inline: 0.125rem;
}
.docker-container-update-cell > :is(span, div),
.docker-container-update-cell button {
max-width: 100%;
white-space: normal;
overflow-wrap: anywhere;
overflow: visible;
text-overflow: clip;
}
.docker-container-update-cell > span,
.docker-container-update-cell button {
flex-wrap: wrap;
justify-content: center;
gap: 0.125rem;
padding-inline: 0.125rem;
font-size: 10px;
}
.docker-container-update-cell svg {
flex-shrink: 0;
}
}
@@ -137,6 +137,37 @@ for (const admissionFailure of [false, true]) {
}));
await row.scrollIntoViewIfNeeded();
const initial = await measure();
// Text ranges catch clipped glyphs even when the wrapper itself has no
// scrollable overflow. Check every rendered update state, not one row.
await expect(table.locator('.docker-container-update-cell').filter({ hasText: 'Current' }).first()).toBeVisible();
const clippedUpdateText = await table.locator('.docker-container-update-cell').evaluateAll(cells =>
cells.flatMap(cell => {
const bounds = cell.getBoundingClientRect();
const walker = document.createTreeWalker(cell, NodeFilter.SHOW_TEXT);
const clipped: string[] = [];
while (walker.nextNode()) {
if (!walker.currentNode.textContent?.trim()) continue;
const range = document.createRange();
range.selectNodeContents(walker.currentNode);
if (Array.from(range.getClientRects()).some(rect =>
rect.left < bounds.left - 1 || rect.right > bounds.right + 1)) {
clipped.push(walker.currentNode.textContent!);
}
}
return clipped;
}),
);
expect(clippedUpdateText).toEqual([]);
const currentLabel = table.locator('.docker-container-update-cell span').filter({ hasText: /^Current$/ }).last();
expect(await currentLabel.evaluate(el => {
const range = document.createRange();
range.selectNodeContents(el);
return range.getClientRects().length;
})).toBe(1);
expect(initial.overflowX).toBe('clip');
expect(initial.scrollWidth).toBe(initial.width);
await row.hover();
await page.mouse.wheel(2000, 0);
await page.waitForTimeout(350);
@@ -156,6 +187,13 @@ for (const admissionFailure of [false, true]) {
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
const detailId = await toggle.getAttribute('aria-controls');
const detailText = detailId ? await page.locator(`[id="${detailId}"]`).innerText() : null;
const heading = page.locator(`[id="${detailId}"] h2`).first();
await expect(heading).toContainText('notification');
expect(await heading.evaluate(el => el.scrollWidth <= el.clientWidth &&
getComputedStyle(el).textOverflow !== 'ellipsis')).toBe(true);
await heading.scrollIntoViewIfNeeded();
await capture('table-full-detail-heading');
await testInfo.attach('narrow-table-access', {
body: JSON.stringify({ initial, pointer, keyboard, accessibleRow, expanded, detailText }, null, 2),
contentType: 'application/json',
@@ -0,0 +1,54 @@
# Docker phone update-cell correction — 5 September 2026
Mainline base: `5beeaf4cbe32603bf4d8dcb07069957a659aa1a4`.
## Decision and scope
The preceding access receipt demonstrates clipped update badges at 390×844,
with neither wheel nor keyboard horizontal input recovering them. Viewed its
retained screenshot again. Fresh external reading of W3C's Reflow guidance
(https://www.w3.org/WAI/WCAG22/Understanding/reflow.html, September 5) confirms
that a table exception does not justify losing cell information. This is not
a claim of WCAG conformance or new operator demand.
Read the current pulse-pro FEATURE_REQUESTS.md from the supplied team's
20260905T170527Z-pro-customer checkout. The shipped resizable-columns entry
explicitly keeps phones responsive; it does not authorise extending desktop
manual scrolling to phones. No new surface or named product bet is introduced.
The existing Docker update cell now reduces phone padding and wraps badge/action
content rather than clipping it. Labels, accessible names, handlers and review
requirements are unchanged. Detail headings wrap long identities instead of
ellipsising them (shared drawer, not only Docker). No overflow-container or
touch-handler change; the Android page-owned scrolling rule remains intact.
## Validation
- Focused Vitest: DockerNativeTables and ResourceDetailDrawer.docker-container:
38 tests passed.
- Initial real-backend Chromium run: 390px reconnect and new text-bounds checks
passed. Screenshot inspection nevertheless found avoidable mid-word breaks;
cell padding was then reduced and a single-line Current-label assertion added.
- Final run: all six Chromium cases passed (390, 1100, 1440px; normal and
failed admission). Managed backend PID 2415019 stopped afterwards. Browser assertions inspect text ranges in all
rendered Docker update cells, assert clip/non-scrollable wrapper geometry,
preserve Enter expansion, and verify a non-ellipsised, fitting detail heading.
The populated synthetic fixture must include a visible Current status.
Reproduce from repository root (locked packages and pinned Chromium installed):
```sh
pulse-heavy-run -- env PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MOCK_MODE=true PULSE_E2E_NAVIGATION_RECOVERY=1 PULSE_E2E_TABLE_ACCESS=1 PULSE_E2E_LOCAL_BACKEND_PORT=18765 npm --prefix tests/integration test -- tests/96-navigation-socket-recovery.spec.ts --project=chromium
```
No full repository suite, physical Android touch, screen-reader, installed
release or every asynchronous action-state qualification. Full names remain
available by expanding the existing row; overview names remain truncated.
Review and integration are still required; nothing was published or deployed.
Final screenshots were viewed: Current and Update fit without mid-word breaks
at 390px, and notifications-worker wraps fully in the expanded heading.
Screenshots contain only synthetic local inventory. The final run rebuilt the
embedded frontend and core backend through pulse-heavy-run. Wider cases protect
navigation recovery; phone text-bound assertions run in the 390px normal-admission
case. The fixture does not guarantee every update error/progress state.
@@ -0,0 +1,20 @@
Running 6 tests using 1 worker
(node:2415548) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
(node:2415548) Warning: The 'NO_COLOR' env is ignored due to the 'FORCE_COLOR' env being set.
(Use `node --trace-warnings ...` to show where the warning was created)
✓ 1 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 1440px (admission failure: false) (8.3s)
✓ 2 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 1100px (admission failure: false) (9.8s)
✓ 3 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 390px (admission failure: false) (16.2s)
✓ 4 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 1440px (admission failure: true) (6.5s)
✓ 5 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 1100px (admission failure: true) (7.1s)
✓ 6 [chromium] tests/96-navigation-socket-recovery.spec.ts:79:5 populated navigation survives socket loss at 390px (admission failure: true) (13.9s)
6 passed (1.0m)
To open last HTML report run:
npx playwright show-report
[integration] Stopped managed local backend
Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB