mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
Merge commit '537c7ae9a50025efbf7963cdf7e544f42cbf0f7e'
Change-source: pulse-maintainer
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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(() =>
|
||||
|
||||
@@ -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
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
Reference in New Issue
Block a user