Remove duplicate keyboard and disclosure semantics from static data-table rows across the platform surfaces. Preserve whole-row pointer convenience while leaving focus, accessible naming and expanded state on each existing native disclosure button.
The populated TrueNAS browser check also exposed an invalid combobox role on inline-only search completion, so keep it as a native textbox with aria-autocomplete.
Change-source: pulse-maintainer
Contract-Neutral: Accessibility semantics and regression coverage only; no product or API boundary changed
Any *_SPEC.md, *_PLAN.md, or *_CONTRACT.md under docs/ must now carry a
Status: line (or a ## Status section) in its header, enforced by the
pre-push lint. A spec, plan, or contract is a record of a decision, not
demand; the 2026-09-01 Home wall case showed what happens when a stale
handoff spec reads as a current signal. Subsystem contract markdown is
governed separately and is not scanned.
The commercial cancellation/reactivation e2e test plan gets the one
missing line so the existing set passes.
Offline guard for the pulseapp.io class: any domain-shaped token
containing 'pulse' in published surfaces (root markdown, docs and
mirrors, non-test frontend src and Go source) must be pulserelay.pro,
1mk.app, or a documented placeholder. Catches the invented
security@pulseapp.io / docs.pulseapp.io (10 months as the published
security contact, bouncing every disclosure) and the pulse.app
OpenRouter referer fixed in b1240c6ca. Runs in npm run lint, so the
pre-push hook enforces it.
The previous commit unintentionally flipped the mode to 100755 via a
temp-index cacheinfo; the script is invoked as 'node scripts/...'
everywhere, so keep the non-executable mode it has always had.
Vite injects modulepreload links for the entry chunk's transitive
static import graph, not just its direct imports. The posture check in
check-bundle-size.mjs allowed only direct imports, so a legitimate
future chunk-graph change (a preloaded vendor chunk statically
importing another chunk) would have false-failed the build and invited
a hasty weakening of the check. Compute the allowed preload set as the
static-import closure reachable from the entry instead; lazy route
chunks stay excluded because they are only reachable dynamically.
Re-verified: healthy build passes; injected lazy-chunk preload fails;
rebuilding with preloadDynamicChunks: true fails with 47 lazy-chunk
violations.
The deployment-installability verification policy routed
frontend-modern/vite.config.ts through the dev-runtime orchestration
proof set, all of which exercise the unbuilt hot-dev runtime. No
accepted proof could observe production build output, which is why
c4af728c0 (preload posture change) needed
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT.
Split vite.config.ts into its own frontend-build-output path policy:
the accepted set keeps every dev-runtime proof, so dev-server-facing
edits are unchanged, and adds
frontend-modern/scripts/check-bundle-size.mjs, which now also asserts
the built index.html posture the contract clause pins: modulepreload
links limited to the entry's static imports (no lazy route chunks) and
import map integrity coverage of every built JS asset. The guard test
pins the new policy's accepted set.
Verified against the built output: flipping preloadDynamicChunks to
true fails the check with 47 lazy-chunk preload violations; the
healthy build passes. Full canonical-governance chain run locally, all
exit 0.
Contract-Neutral: Customer-facing punctuation cleanup only; no API, lifecycle, policy, or subsystem behavior changed. The owning frontend copy contract and lint enforcement are updated.
Fixes#1685.
The "Last refresh" clock in the app shell was built with a hardcoded en-US
locale and hour12 true, so every reader saw a US 12 hour clock regardless of
their system settings. #1279 already fixed this once in App.tsx; the v6
rewrite that moved the logic into useAppRuntimeState.ts reinstated the
hardcoded form, and it has shipped that way since GA. The reporter spotted
the regression and named the original PR.
Swept the rest of the frontend for the same defect. One other site remained,
the alert history day group full-date label, which rendered "Thursday,
August 6, 2026" to a reader whose own rows are ordered day-month. Both now
pass undefined so the runtime resolves the viewer's locale and clock
convention.
Because this is a regression that already survived one fix, add a
canonical-shared/no-hardcoded-format-locale audit rule covering
toLocaleString, toLocaleDateString, toLocaleTimeString and the Intl
constructors, with an allowFiles escape for any call that genuinely needs a
fixed locale. Confirmed the rule fires on the exact reintroduced regression
and passes once reverted.
Verified in an en-GB browser: the footer now reads 20:52:55 rather than
8:52:55 PM, and the history day header title reads "Thursday, 6 August 2026".
Contract-Neutral: Contract deltas staged where the change actually lands: frontend-primitives.md gains the shared date/number formatting-locale rule and names the audit that enforces it, and alerts.md extends the existing timestamp paragraph to the day group label. Residual demands are inapplicable. cloud-paid.md names useAppRuntimeState.ts for hosted org-context bootstrap and licence boundaries and performance-and-scalability.md names it as an app-shell performance boundary; grepped both and neither documents date formatting, and swapping a locale argument moves neither boundary. The alerts frontend surface proof is the same fixed policy list as a0113b964 and does not name alertHistoryModel.branchcov.test.ts, which is the file that actually covers this model; editing an unrelated listed proof would be fabrication.
Preserve tenant-scoped metadata through partial URL updates and project stable URLs across runtime identities. Use a safe adjacent launch control across overview tables with desktop and mobile regression coverage.
Agent Doctor now lives at /settings/infrastructure/agent-doctor as a
full page in the settings shell instead of a max-w-lg dialog stacked
over the Infrastructure workspace, which was cramped on phones. The
per-agent card pile becomes one fleet table (agent, system, status,
reported and target versions, last seen) with per-row expansions
holding the diagnosis reasons, identity evidence, repair actions, and
the host-local update command. A lone scoped target auto-expands so
platform-page deep links land straight on the diagnosis.
Legacy ?agentDoctor=1 / ?agentUpdates=1 workspace links redirect onto
the route with their agents scope preserved, and closing the page is
now ordinary back navigation. The dialog component is renamed to
InfrastructureAgentDoctorPage; shared-template registry, the
agent-lifecycle subsystem inventory, and the frontend-primitives
settings deep-link clause follow the change.
Alerts that share a resource ancestor (e.g., ZFS pool + device within
that pool) are now grouped on the alerts overview page. The primary
alert (highest severity, most recent) is shown by default with a
'+N related' toggle that expands to reveal the related alerts.
Grouping uses resourceId nesting: alerts with 3+ path segments are
sub-components and share the group key of their parent (first 2
segments). Alerts on different subsystems remain separate.
Single-alert groups render exactly as before — no visual change when
there's nothing to group.
recoveryDatePresentation: locale-independent date assertion (en-GB
systems produce '9 Mar 2026' not 'Mar 9, 2026')
quickstartCopyContract: update 'tools' to 'capabilities' matching
docs/AI.md copy change
AIIntelligence: update stale empty-state text to full current copy
SharedPrimitives.guardrails: align guardrail + registry with
d79640c41 migration of ProxmoxBackupsTable from FilterButtonGroup to
FilterSegmentedControl
Full suite: 659 files, 6719 tests, 0 failures.