Audit pass against the live mock backend (PULSE_MOCK_MODE=true) revealed
three platform sub-tabs that could never populate from canonical
resources today, even with a mature fixture environment:
- Docker > Swarm services: no `docker-service` resource is emitted by
the unified resource adapter at the /api/resources boundary.
- Kubernetes > Services: no `k8s-service` resource type is defined in
internal/unifiedresources; only k8s-cluster, k8s-node, pod, and
k8s-deployment are projected.
- TrueNAS > Hosts: TrueNAS connections produce storage, datasets,
physical disks, and app-containers but no top-level `agent` row
tagged with the `truenas` platform.
Per the platform-pages goal ("if a platform lacks enough canonical data
model support to make a useful page even after fixture work, do not
invent speculative UI — report the data-model gap and skip or gate
that surface deliberately"), the three sub-tabs are removed from the
page navigation and the resource queries are tightened to drop the
unused types. TrueNAS now defaults to /truenas/storage as the first
canonical operator entry point.
Kubernetes Nodes now also includes agent rows whose `sources` array
reports `kubernetes`, because the backend registry merges Pulse-Agent-
linked Kubernetes nodes onto the linked agent row. The merged agents
are the canonical projection of K8s nodes when an agent is installed
on them; treating them as Nodes makes the tab populate against mock
mode and live agent fleets alike.
Browser verification (Playwright, chromium, against live mock-mode
Pulse dev runtime):
- 8 tests, all pass. Every declared platform sub-tab now asserts at
least one canonical row populates under mock mode:
- docker: /docker/overview (2 hosts), /docker/containers
- kubernetes: /kubernetes/overview (1 cluster),
/kubernetes/nodes (3 merged agents), /kubernetes/pods (10),
/kubernetes/deployments (4)
- truenas: /truenas/storage (9), /truenas/apps (5)
- vmware: /vmware/overview (4 ESXi hosts), /vmware/vms (8),
/vmware/storage (datastores)
Targeted tests:
- dockerPageModel, kubernetesPageModel, truenasPageModel suites
updated and passing.
- resourceLinks.test.ts asserts the new TrueNAS default sub-tab.
Contracts updated:
- unified-resources.md Extension Points: platform default sub-tab must
land on a canonical surface that actually populates.
- storage-recovery.md Shared Boundaries: same; calls out TrueNAS
defaulting to /truenas/storage today.
Remaining canonical data-model gaps (intentionally not surfaced as
empty platform sub-tabs in the UI):
- internal/unifiedresources does not emit `docker-service` resources
even though mock.generateDockerServicesAndTasks() generates them in
the StateSnapshot; revisit when adding a docker-service projection.
- internal/unifiedresources does not define ResourceTypeK8sService;
revisit when k8s-service rows are added to the canonical model.
- internal/unifiedresources does not project a top-level TrueNAS
system as an `agent` row; revisit when the TrueNAS adapter promotes
the connection target into a canonical infrastructure row.
The unified resource projection returned by /api/resources leaves
`platformType` empty on several canonical resource types (storage,
agent, pbs, app-container, vm, k8s-deployment, pod, etc.) under the
mock fixture path and parts of the live backend. Platform-first pages
were filtering on `resource.platformType` directly, so Docker /
Kubernetes / TrueNAS / vSphere pages rendered empty under mock mode
even though the resources existed and were tagged with the right
`sources` array (['docker'], ['kubernetes'], ['truenas'], ['vmware']).
Introduce `resolveResourcePlatformType(resource)` in
`frontend-modern/src/utils/sourcePlatforms.ts` as the canonical reader
for "what platform family does this unified resource belong to". It
prefers `resource.platformType` when present and falls back to the
resource's `sources` array via the existing
`resolvePlatformTypeFromSources` normalization, so client-side family
grouping behaves identically against mock fixtures and live backends.
Each platform page model now buckets resources through that helper:
- dockerPageModel.ts
- kubernetesPageModel.ts
- truenasPageModel.ts
- vmwarePageModel.ts
Browser verification (Playwright, chromium, against live mock-mode
Pulse dev runtime):
- 4 no-data tests (stubbed empty /api/resources): all 4 pages render
sub-tab chrome and surface empty state.
- 4 populated tests (live mock backend): each platform asserts at
least one canonical row renders on its data-bearing sub-tabs:
- docker: /docker/overview (Hosts), /docker/containers
- kubernetes: /kubernetes/pods, /kubernetes/deployments
- truenas: /truenas/storage
- vmware: /vmware/vms, /vmware/storage
All 8 tests pass.
Verification artifacts staged:
- sourcePlatforms.test.ts extended with resolveResourcePlatformType
cases (19 tests pass).
- 68-platform-pages-shell.spec.ts extended with populated-state
assertions per platform.
Contracts updated:
- unified-resources.md Shared Boundaries: resolveResourcePlatformType
is the canonical reader for unified-resource platform family.
- frontend-primitives.md Extension Points: same.
Remaining mock fixture gaps (intentionally not asserted populated in
this commit; tracked for fixture extension):
- docker/services: default mock fixtures do not expose docker-service
resources at the /api/resources boundary.
- kubernetes/overview, kubernetes/nodes, kubernetes/services:
KubernetesClusters are generated but k8s-cluster, k8s-node, and
k8s-service resource projections are not surfaced.
- truenas/overview, truenas/apps: no TrueNAS agent or TrueNAS-scoped
app-container resources in default fixtures.
- vmware/overview: no VMware ESXi-host agent resources in default
fixtures.
Complete the platform-first navigation shift started in cdaeb3b84.
Primary navigation now enumerates exactly the supported platform
families (Proxmox, Docker, Kubernetes, TrueNAS, vSphere) and removes
Infrastructure, Workloads, Storage, and Recovery as equal top-level
tabs. Their tables continue to render inside each platform page via
the embedded tableOnly canonical surfaces; their routes remain wired
in App.tsx for deep-link compatibility, but they are not duplicated
in primary nav.
First-run UX decision: all supported platform tabs are alwaysShow:true
so first-run operators can discover what Pulse monitors. Unconnected
platforms render in a disabled tone (enabled/live derive from
canonical resource presence in state.resources), and the empty-state
inside the platform page itself surfaces the setup affordance. This
favors discoverability over hiding-until-connected, and is the
canonical decision recorded in the contract delta below.
Routing follow-up:
- routePreload.ts adds ROUTE_PRELOADERS entries for Docker, Kubernetes,
TrueNAS, and vSphere so platform navigation stays warm on first paint.
- mobileNavBarModel.ts MOBILE_NAV_PLATFORM_PRIORITY mirrors the new
platform-first primary set; legacy entries are removed.
Contracts updated:
- cloud-paid.md: records the platform-first primary-nav decision and
the alwaysShow + presence-derived enabled/live contract; explicitly
forbids reintroducing infra/workloads/storage/recovery as equal
primary tabs without a governed contract decision.
- frontend-primitives.md: PlatformTab and MOBILE_NAV_PLATFORM_PRIORITY
must mirror the supported-platform set; legacy entries intentionally
absent.
- performance-and-scalability.md: every supported platform must be in
the app-shell route preload registry; presentation-only platforms
must not be registered.
- ai-runtime.md: demotion does not affect Patrol or Assistant
addressability; platform pages must not replicate Patrol findings,
Assistant prompts, or AI launcher affordances inside their chrome.
Verification:
- App.architecture.test.ts extended to assert the platform-first
AppLayout structure (no infrastructure/workloads tabs, no
buildStorageRecoveryTabSpecs call) and the new routePreload entries.
- MobileNavBar.test.tsx extended to assert the platform-first
MOBILE_NAV_PLATFORM_PRIORITY ordering and the absence of legacy IDs.
- 68-platform-pages-shell.spec.ts (Playwright, chromium) re-run
post-demotion: all 4 platform pages still render with sub-tab chrome
against the live Pulse dev runtime in empty-resource state, proving
the first-run / no-data nav stays discoverable.
- vitest sweep across src/features, src/__tests__,
src/routing/__tests__ (351 tests) green. Pre-existing
PageControls.guardrails failure is unrelated (verified by stashing).
Unrelated dirty files (other-agent work in internal/ai/, status.json,
ai-runtime.md further edits) intentionally left untouched.
Introduce a shared platform-page primitive and four new top-level family
pages that mirror the v5-style Proxmox surface: chrome only, embedding
the canonical WorkloadsSurface, StorageSurface, RecoverySurface, and
UnifiedResourceTable in tableOnly/embedded mode with forced platform or
source filters. No dashboard cards, no fake data, no bespoke per-family
tables.
Pages added:
- /docker Hosts / Containers / Swarm services
- /kubernetes Clusters / Nodes / Pods / Deployments / Services
- /truenas Hosts / Storage / Apps
- /vmware Hosts / VMs / Storage (vSphere, first-lab-ready)
Top-level navigation entries are gated on platform presence in
state.resources, so empty platforms stay hidden by default; Proxmox
remains alwaysShow. Infrastructure, Workloads, Storage, and Recovery
remain available unchanged. Routing, navigation tab IDs, and the
document-title map are extended to match. Route preload and mobile-nav
priority changes are deliberately deferred to a follow-up commit to
avoid wider entanglement with parallel-agent edits to those shared
shell files.
Contracts extended for the new platform-page boundary:
- cloud-paid.md
- unified-resources.md
- storage-recovery.md
- ai-runtime.md
- frontend-primitives.md
Tests:
- dockerPageModel, kubernetesPageModel, truenasPageModel, vmwarePageModel
vitest suites (12 new tests).
- resourceLinks.test.ts extended for the new platform path builders.
- App.architecture.test.ts extended for the new lazy imports and routes.
- 68-platform-pages-shell.spec.ts Playwright smoke covering all four
pages and every sub-tab link against the live Pulse dev runtime.
- App.architecture, proxmox model, and Workloads suites continue to pass.
Skipped (canonical model not ready):
- Unraid as a top-level page: an agentHostProfile, no platform
projections; already surfaces through the Pulse-managed Hosts / Storage
views.
- Synology DSM, Microsoft Hyper-V, AWS, Azure, GCP:
governanceState=presentation-only, no canonical projections.
- add a forced run action to pulse_discovery for known resources
- make discovery progress describe model-backed evidence analysis rather than a live Assistant chat
- keep shared select hydration stable for persisted discovery intervals