From 67104c830d39d2b798a45122d7df1dcad2844067 Mon Sep 17 00:00:00 2001
From: "pulse-triage[bot]"
<249995291+pulse-triage[bot]@users.noreply.github.com>
Date: Mon, 12 Oct 2026 14:21:42 +0100
Subject: [PATCH] Show guest-read safety explanations without hover
The existing Workloads drawer puts the meaning of reported availability and cached metadata only in a title. Show that fixed explanation directly beneath the read state so phone and keyboard users can see that it does not establish thaw, writes or a current connection. Deferred states retain their existing prominent warning rather than repeating it in the row. Add visible-text and same-guest transition controls, with source-bound desktop and phone browser proof.
Contract-Neutral: Rendering existing guest-read explanations visibly; no state selection, collector, metric, permission or guest-command contract change.
Change-source: pulse-maintainer
---
.../119fba64dc909865.json | 39 +++++++++++++++++++
.../Workloads/GuestDrawerOverview.tsx | 30 +++++++++++---
.../GuestDrawerOverview.agent-state.test.tsx | 34 ++++++++++++++++
3 files changed, 98 insertions(+), 5 deletions(-)
create mode 100644 frontend-modern/browser-verification/119fba64dc909865.json
diff --git a/frontend-modern/browser-verification/119fba64dc909865.json b/frontend-modern/browser-verification/119fba64dc909865.json
new file mode 100644
index 0000000000..454807796b
--- /dev/null
+++ b/frontend-modern/browser-verification/119fba64dc909865.json
@@ -0,0 +1,39 @@
+{
+ "version": 1,
+ "base_sha": "020a7be6bf2a2923c291395a8409ed79ddcde336",
+ "verified_at": "2026-10-12T13:19:44.508Z",
+ "result": "passed",
+ "changed_paths": [
+ "frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx"
+ ],
+ "content_sha256": {
+ "frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx": "af9cd697e786db4ead5a3ea02b923c4d95bbfbd32c22457db11c9ee0a1f74371"
+ },
+ "routes": [
+ "/proxmox (production GuestDrawer in a local synthetic read-state fixture)"
+ ],
+ "viewports": [
+ {
+ "width": 1440,
+ "height": 900
+ },
+ {
+ "width": 390,
+ "height": 844
+ }
+ ],
+ "states": [
+ "Available, expected-unreachable, not-running, disabled, unrecognised and cached-version-only: original fixed explanations are visible without hover; unknown raw values are not rendered.",
+ "Backup lock and timed-out read retain the original prominent safety warning/link without duplicate row text; available state returns on the same guest after clearance. Clearance is not native thaw/recovery.",
+ "LXC and vSphere remain without a QEMU read-state row.",
+ "22 source-bound desktop Chromium 141.0.7390.37 / phone WebKit 26.0 checks on Playwright 1.56.1; four final captures inspected with native view_image. Source imports: 164 verified loaded files.",
+ "Explicit empty GET /api/resources and GET /api/alerts/active fixtures only; no API writes or external service. Loopback WebSocket proxy ECONNREFUSED remains in raw browser log; no streaming/native acceptance.",
+ "Prior clients 1/1/1 retained: navigation readiness failures and the zero-read fixture oracle failure are not passes. Final client 0, browsers/contexts/server closed."
+ ],
+ "interactions": [
+ "Changed provider-owned state for the same canonical guest without remount, checking visible state/detail, geometry and precautions.",
+ "Focused the production collapse button and pressed Enter, then reopened the fixture drawer and read the current explanation.",
+ "Changed fixture platform to LXC/vSphere and confirmed no misleading QEMU explanation."
+ ],
+ "evidence": "Lane outcome RECEIPTS.md links complete results/scripts/captures/native observations and retains adverse attempts; not a real guest, current-source backend or released proof."
+}
diff --git a/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx b/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx
index 95469bb425..319748c473 100644
--- a/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx
+++ b/frontend-modern/src/components/Workloads/GuestDrawerOverview.tsx
@@ -79,6 +79,30 @@ export function GuestDrawerOverview(props: GuestDrawerOverviewProps) {
ageClass: props.backupPresentation?.ageClass,
});
const guestReadPresentation = () => getGuestDrawerGuestReadPresentation(props.guest);
+ const guestReadRow = () => {
+ const read = guestReadPresentation();
+ if (!read) return null;
+ return makeDetailRow('Guest-agent reads', read.label, {
+ title: read.detail,
+ tone: read.tone,
+ wrap: true,
+ // A title alone is not available to touch users. Deferred reads already
+ // have a visible safety notice above the drawer; do not repeat it here.
+ layout: read.precaution ? undefined : 'stacked',
+ valueContent: (
+ <>
+
+ {read.label}
+
+
+
+ {read.detail}
+
+
+ >
+ ),
+ });
+ };
const memoryReading = () => getGuestDrawerMemoryReading(props.guest);
// A linked Pulse agent past its reporting lease leaves its last report on
// the guest, so its RAID, network and filesystem rows read as last known.
@@ -104,11 +128,7 @@ export function GuestDrawerOverview(props: GuestDrawerOverviewProps) {
: undefined,
wrap: true,
}),
- makeDetailRow('Guest-agent reads', guestReadPresentation()?.label, {
- title: guestReadPresentation()?.detail,
- tone: guestReadPresentation()?.tone,
- wrap: true,
- }),
+ guestReadRow(),
makeDetailRow('Memory reading', memoryReading()?.summary, {
wrap: true,
}),
diff --git a/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
index 669b746599..21011baffc 100644
--- a/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
+++ b/frontend-modern/src/components/Workloads/__tests__/GuestDrawerOverview.agent-state.test.tsx
@@ -63,6 +63,40 @@ const readRow = () => screen.getByText('Guest-agent reads').closest('tr')!;
afterEach(cleanup);
describe('guest agent coverage requires current state, not cached metadata', () => {
+ it.each([
+ 'available',
+ 'expected-unreachable',
+ 'not-running',
+ 'disabled',
+ 'future-state-private-detail',
+ '',
+ ])('makes the %s explanation visible without hovering', (state) => {
+ const value = guest({ guestAgentStatus: state, agentVersion: '9.2' });
+ render(() => );
+ const presentation = getGuestDrawerGuestReadPresentation(value)!;
+ expect(within(readRow()).getByText(presentation.detail)).toBeVisible();
+ expect(readRow()).not.toHaveTextContent('future-state-private-detail');
+ expect(readRow().querySelector('[tabindex]')).toBeNull();
+ });
+
+ it('updates the visible explanation without repeating the deferred safety notice', () => {
+ const [value, setValue] = createSignal(guest({ guestAgentStatus: 'available' }));
+ render(() => );
+ const availableDetail = getGuestDrawerGuestReadPresentation(value())!.detail;
+ expect(within(readRow()).getByText(availableDetail)).toBeVisible();
+ setValue({ ...value(), lock: 'backup', diskStatusReason: 'prev-vm-locked' });
+ expect(within(readRow()).getByText('Deferred')).toBeVisible();
+ expect(within(readRow()).queryByText(availableDetail)).not.toBeInTheDocument();
+ expect(
+ within(readRow()).queryByText(getGuestDrawerGuestReadPresentation(value())!.detail),
+ ).not.toBeInTheDocument();
+ setValue({ ...value(), lock: '', diskStatusReason: '', guestAgentStatus: 'not-running' });
+ expect(
+ within(readRow()).getByText(getGuestDrawerGuestReadPresentation(value())!.detail),
+ ).toBeVisible();
+ expect(within(readRow()).queryByText(availableDetail)).not.toBeInTheDocument();
+ });
+
it.each([
['deferred', 'Deferred'],
['expected-unreachable', 'Unreachable'],