Skip to content

feat: multi-namespace scope and the live rollout detail view - #45

Merged
zjy365 merged 5 commits into
mainfrom
feat/namespace-scope-and-live-rollout
Sep 17, 2026
Merged

zjy365 merged 5 commits into
mainfrom
feat/namespace-scope-and-live-rollout

Conversation

@zjy365

@zjy365 zjy365 commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Closes #31
Closes #39

Combined PR for both issues (the two features touch adjacent wiring in App.tsx / ResourceDetailView.tsx, so they ship together instead of as two conflicting PRs). Supersedes #43 and #44.

#31 — multi-namespace selection in the toolbar namespace picker

The picker becomes multi-select (Base UI Combobox multiple). The selected set is the list scope:

  • [] = All namespaces (cluster-wide snapshot, unchanged); one name = the existing single-namespace live-watch path, byte-for-byte; two or more = bounded per-namespace snapshot fan-out (limit 100 each, parallel, merged in selection order). No watch streams for multi-scope — "at most one watch per list view" and the no-global-informer boundary are untouched. Cap = 32 (renderer-only constant).
  • New pure seam lib/namespace-scope.ts (vitest-covered): toggle/cap, trigger summary (a, b +N), canonical order-insensitive scope key, merged-response helpers.
  • useNamespaces holds namespaceScope: string[] (content-equality bail-out so a context switch cannot churn array identity); useResourceList / useHelm key effects on the scope key and read the scope through a ref; Pod usage and Helm releases fan out per selected namespace; usage is keyed by namespace/name so same-named pods cannot trade rows.
  • Picker UX: click applies immediately; direct-Enter appends (IME guard kept); the All-namespaces row clears the selection; the trigger summarizes with a full-list tooltip past three names; cap rejection shows a footer message. Palette / context switch / create dialog / checked-row reset gate on the canonical scope key.

#39 — the workload detail stays live during a rollout

While an object is open, the detail keeps itself live instead of freezing at fetch time (the frozen case being every snapshot-only scope, where the table's disabled watch could never bump the selection):

  • Single-object live channel in useResourceDetail: one lazy watch with fieldSelector: metadata.name=<name> (already plumbed end-to-end), keyed on the object's identity so resourceVersion bumps adopt in place and re-get the object once — vitals, conditions and the YAML tab converge like an explicit refresh. A deleted event closes the detail. Lazy, one stream, closed on leave, never cluster-scoped.
  • Live events: the object's events re-fetch on RV bumps plus a 15s backstop; a new usePodEvents poll surfaces child pod events (Pulling / Created / Started) — one namespaced events list filtered server-side with involvedObject.kind=Pod, joined client-side to the workload's own pods. The Events tab merges both, newest first, capped at 100.
  • Rollout indicator: header chip derived from the live Progressing condition plus ready/updated/desired counters — Rollout in progress / Rolled out / Rollout stuck (pure rolloutStatus() with vitest coverage).
  • Core: two lines — Event rows project involvedObject.name; no validation caps, RPC surface, or shell changes.

Verification

  • pnpm check green (typecheck, vitest 139, build, cargo test + clippy); go -C core test -race ./... + go vet green.
  • pnpm --dir apps/desktop smoke:visual 58/58 with screenshots and overflow checks, including the new end-to-end cases: multi-select merge with per-namespace request recording (no watch), 32-cap rejection, Helm merged fan-out, pod usage across namespaces, and the live rollout (scripted snapshot → delta → get drives vitals/conditions/events without refresh; leaving the detail cancels the watch).

The picker becomes multi-select (Base UI Combobox multiple mode). The
selected set is the list scope:

- exactly one namespace keeps the existing live-watch path byte-for-byte;
- the empty selection stays the All-namespaces cluster-wide snapshot;
- two or more namespaces fan out as ordinary single-namespace snapshot
  requests (limit 100 each) merged in selection order — no watch streams,
  so "at most one watch per list view" and the no-global-informer boundary
  are untouched. The cap is 32 (renderer-only constant).

New pure seam lib/namespace-scope.ts owns toggle/cap/summary/canonical
scope-key serialization plus the merged-response helpers. useNamespaces
holds namespaceScope: string[] (bail-out on equal contents so a context
switch cannot churn array identity); useResourceList/useHelm key their
effects on the order-insensitive scope key and read the scope through a
ref. Pod usage and Helm releases fan out per selected namespace; usage
is keyed by namespace/name so same-named pods cannot trade rows. Picker
UX: click applies immediately, direct-Enter appends (IME guard kept),
the All-namespaces row clears the selection, the trigger summarizes
(a, b +N with a full-list tooltip), and the 32-cap rejection shows a
footer message. Palette/context-switch/create-dialog/reset flows pin,
replace or clear the scope as before.

Covers #31
While a detail is open, the view keeps itself live instead of freezing
at the moment the object was fetched — the frozen case being every
snapshot-only scope (All namespaces), where the table's disabled watch
could never bump the selection.

- Single-object live channel in useResourceDetail: one lazy watch with
  fieldSelector metadata.name (plumbed end-to-end already), keyed on the
  object's identity so resourceVersion bumps adopt in place instead of
  resubscribing. A bump re-gets the object once, so vitals, conditions
  and the YAML tab converge the same way an explicit refresh does; a
  deleted event closes the detail like the list-follow path. Closed on
  leave; never cluster-scoped; no informers.
- Events go live: the object's events re-fetch when its resourceVersion
  bumps (the watch's adopt) plus a 15s backstop, and a new usePodEvents
  poll surfaces the rollout's child pod events (Pulling / Created /
  Started) — one namespaced events list filtered server-side with
  involvedObject.kind=Pod and joined client-side to the workload's own
  pod names. No watch stream, no per-pod fan-out. The Events tab merges
  both halves, newest first, capped at 100.
- Rollout indicator: the identity header gains a chip derived from the
  Progressing condition plus the ready/updated/desired counters —
  "Rollout in progress / Rolled out / Rollout stuck" — so a stuck
  rollout (ProgressDeadlineExceeded) is visible at a glance.
- Core: Event rows now project involvedObject.name (the only field the
  client-side join needs); no validation or RPC surface changes.

Covers #39
Integrates both features into one branch: the multi-namespace picker's
useResourceDetail now keys on the canonical scope key (namespaceKey) and
gains the coreReady-gated single-object live channel; ResourceDetailView
keeps the scoped pods watch on namespaceScope while adding the pod-events
join and rollout chip.
…edup)

Spec fixes:
- rolloutStatus: settled replica counters now complete a non-terminal
  Progressing=True (StatefulSet no longer reads "Rollout in progress"
  forever); lagging updated replicas keep it honest mid-rollout
- trigger summary overflows from the third name on ("a, b +1" per #31);
  cap message matches the spec text
- create dialog falls back to the context default namespace for All/multi
  scopes instead of a hardcoded "default" (#31 story 24)
- the single-object live watch and event polls are scoped to rollout
  workload kinds (#39): no watch ever leaves with an empty namespace
- usePodEvents keys on the empty/non-empty name transition, so a live
  pod list refresh no longer restarts the poll

Standards cleanup:
- shared namespaceScopeKey / SLOW_POLL_MS / newestEventsFirst seams
  replace the inlined copies
- useHelm unifies the single- and multi-namespace stream paths, fixing
  multi-scope load-more replacing pages and page errors wiping rows
- scopeLabel/scopeKey renames; RolloutBadge renders from one map

Refs #45
Competitor-parity interaction pass on PR #45's surfaces:

- Sidebar favorites: hover a resource row and star it; starred kinds pin
  into a Favorites group at the top of the navigation. The star is a
  sibling overlay of the row button, so the a11y tree keeps both controls
  separate; the fill uses the interactive token, not a semantic color.
- Most used: every kind open feeds a usage counter (localStorage, like
  the fold prefs); the top three unstarred kinds render in a Most used
  group. Both groups resolve entries against the full catalog so icon,
  label, and enabled state match the home rows, and carry distinct
  testids so quick-access rows never shadow the originals.
- Namespace picker: a multi selection (2+) grows an x button beside the
  trigger that clears back to All namespaces in one click — the in-popup
  All row stays as the two-click path. Single selections keep no undo
  control: one namespace is the ordinary scope, not a selection.
- Playwright: the smoke preview port is overridable via ASTER_SMOKE_PORT
  so a local service on 4173 cannot win reuseExistingServer and serve
  the wrong app to the suite.

Refs #45
@zjy365
zjy365 merged commit d4e7a4a into main Sep 17, 2026
1 check passed
@zjy365
zjy365 deleted the feat/namespace-scope-and-live-rollout branch October 8, 2026 09:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant