Skip to content

Import the service-resources wire types from the server - #965

Merged
selfcontained merged 2 commits into
mainfrom
debt/service-resources-wire-types
Aug 16, 2026
Merged

Import the service-resources wire types from the server#965
selfcontained merged 2 commits into
mainfrom
debt/service-resources-wire-types

Conversation

@selfcontained

@selfcontained selfcontained commented Aug 16, 2026

Copy link
Copy Markdown
Owner

What

apps/web/src/hooks/use-service-resources.ts was ~180 lines of which nearly all were type declarations hand-mirroring the server's observability type tree. Those declarations are gone; web now import types them directly from the server modules that own them.

Type Now sourced from
ResourceSample, SubsystemResourceSample, ServiceResourcesResponse apps/server/src/observability/service-resources.ts
SubsystemSnapshot apps/server/src/observability/subsystem-tracker.ts

ResourceHealthState stays web-side — it is a UI concept (the set of states the dashboard renders a badge for), not a wire type — but it is now derived from both server unions it actually spans:

export type ResourceHealthState =
  | SubsystemHealthState
  | ServiceResourcesResponse["overall"]["state"];

stateBadgeVariant/stateLabel are called with both subsystem.state and overall.state, so both sources belong in the union. Same member set as the deleted literal union — verified with a throwaway mutual-assignability assertion, not by eye.

One file. Every consumer keeps importing from @/hooks/use-service-resources, so no other file changed.

Why this is tech debt

This is the largest single duplication the 2026-08-14 audit's duplicate-block scan found (23 duplicated 7-line windows — the biggest file pair in the repo). It follows the established pattern from #856/#863: web needs only types here, and esbuild erases type-only imports, so nothing reaches the bundle.

Drift found: none

Every prior wire-type consolidation turned up silent divergence, so I diffed the two trees field by field before deleting the web copy. This pair had not drifted — all 13 workloads fields, all 6 agents fields, and both inlined sub-objects matched the server's WorkloadSnapshot / AgentProcessSnapshot exactly, in the same order. The one asymmetry was SubsystemSnapshot.state, typed as the 7-member ResourceHealthState on web versus the 6-member SubsystemHealthState on the server — web was over-broad by "unavailable", a state a subsystem can never actually report. That narrowing is the only type-level behavior change here, and it is a tightening.

Feasibility probes (the three that have burned prior runs)

  1. Types only, no values — web imports nothing runtime from these modules; finalize:web builds clean, so the import is fully erased.
  2. Leaf-nessservice-resources.ts pulls only node:os, node:perf_hooks, pg types, shared/lib/run-command.js and subsystem-tracker.js. No service-layer graph, no AgentManager. (use-jobs.ts already precedents a type-only import of a pg-importing module.)
  3. generated/apps/server/src/generated/ is absent in this tree (it only exists after prepare:runtime-assets, which check:web does not run), and check:web passes anyway. That is the Alias web release/assisted-update wire types to the server definitions #863 failure mode probed in its strongest form.

Deliberately not touched

  • service-resources-format.ts:3 formatBytes — the known name collision with shared/lib/format-bytes.ts. Genuinely different (different signature, a B tier, different rounding). Still a rename candidate, not a merge candidate; left alone.
  • AgentProcessSnapshot stays unexported on the server. Web reaches those fields structurally through ServiceResourcesResponse, so exporting it would be churn with no consumer.
  • The other six wire-type pairs in the cluster (incl. the three-way reviews copy) — one per run, per the standing scope rule.

Validation

pnpm run check · pnpm run finalize:web · pnpm run test:e2e (180 passed, incl. settings.spec.ts:148 "shows live service resources and expands subsystem details", which covers this dashboard directly) · apps/web vitest (912 passed).

Closing content-grep on serverHeapBytes, gitRefreshesInFlight, expectedCadenceMs and the statusReason union returns only the single server declaration plus use sites — no second declaration survives anywhere in apps, scripts, e2e, bin or plugins.

Next run

Backlog item: the remaining server↔web wire-type pairs, next largest being the reviews type copy that includes an inline third copy at shared/mcp/server.ts:448.

🤖 Generated with Claude Code

selfcontained and others added 2 commits August 16, 2026 03:04
apps/web/src/hooks/use-service-resources.ts hand-mirrored the server's
observability type tree: ResourceSample, SubsystemResourceSample,
SubsystemSnapshot and ServiceResourcesResponse were all maintained as a
second copy. The web copies now `import type` directly from
observability/service-resources.ts and observability/subsystem-tracker.ts;
esbuild erases type-only imports, so nothing reaches the bundle.

ResourceHealthState stays web-side but is now derived as
`SubsystemHealthState | "unavailable"` instead of restating six of the
server's seven members.

Every importer keeps its existing `@/hooks/use-service-resources` path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review feedback: `| "unavailable"` hardcoded today's difference between the
subsystem and overall unions instead of tracking its source. If the server
adds an overall-only state, the badge formatters would reject it and the
wire-type coupling would not catch the drift.

Same member set today, verified by a mutual-assignability assertion against
the previous literal union.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit d5672cd into main Aug 16, 2026
1 check passed
@selfcontained
selfcontained deleted the debt/service-resources-wire-types branch August 16, 2026 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant