Skip to content

Detail pages and logs fit at laptop widths; Pending Pods and scheduling causes read plainly - #2074

Merged
nadaverell merged 16 commits into
mainfrom
feature/shared-detail-ui
Oct 12, 2026
Merged

nadaverell merged 16 commits into
mainfrom
feature/shared-detail-ui

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Carved out of #1922. This is the shared detail-page UI from that PR: every resource kind gets it, so it is reviewed here on its own rather than inside a CloudNativePG change. Merge before #1922, whose CloudNativePG pages pass the opt-in header props carried here.

What users see

Expanded view: tabs on one line at laptop widths

At 1280px the tab strip used to wrap the actions bar onto two rows and leave Rollback or Delete on a line of their own. Tabs now drop their icons only while they don't fit, and the strip keeps the active tab in view. Scope controls wrap instead of pushing tabs out, and the tabs get a focus ring. The actions bar puts object actions (Set image, Restart, Rollback) and utility actions (compare, delete) in two groups that wrap as groups.

Before / after, Deployment at 1280×800:

Before: expanded Deployment at 1280
After: expanded Deployment at 1280

Drawer header: unchanged

#1922 puts the drawer's name on the title line beside the header buttons. That saves a line for short names, but when the labelled Investigate button shows, the name gets about 300px: a Pod name such as su-unschedulable-7d98c9cc76-4rdf7 was cut to su-unschedulable-7d98c9cc76â€Ķ. So the drawer keeps main's layout here: kind and status beside the controls, then the name on its own full-width line. The hooks #1922's pages use there still work: a host's status badge, and object actions in the actions bar.

Pending Pods say what is actually going on

  • A container that hasn't started reads Not started in neutral grey, instead of red Not Ready / unknown or amber Pending. Only a reported failure (a non-zero exit, or a waiting reason such as ImagePullBackOff or CrashLoopBackOff) is red; ContainerCreating and PodInitializing stay neutral.
  • When the metrics API answers but no container, init containers included, has run yet, Resource Usage says No usage yet: this Pod is not running instead of Metrics unavailable. Radar cannot read metrics.k8s.io. The Pod metrics history response gains metricsAPIReachable.
  • The drawer's issue row gives the scheduler's reasons in plain words on their own line, every cause kept, with the raw message in the details. Before, the row cut the cause off after a few words.

Before / after, unschedulable Pod with an init container (the cluster's metrics-server is healthy):

Before: Pending Pod drawer
After: Pending Pod drawer

Logs

  • At 1280px the toolbar used to squeeze its labels ("1/1 pods" and the level chips broke inside the label) and push Export and Clear off screen. Display and utility controls now wrap onto a second row as a group.
  • Export and Clear are disabled while nothing is loaded.
  • The capture time and any notice share one status line.
  • Long lines break between words before breaking inside one, a structured line keeps {N fields} whole, and a CloudNativePG PostgreSQL record shows the database's message.
  • WorkloadLogsViewer gains initialContainer, plus opt-ins emptySourceState and disableSourceControlsWithoutSource.

Before: workload logs toolbar at 1280
After: workload logs toolbar at 1280

Issue titles

Issue rows on Home and the Issues page take the reason's title when the category would name the wrong operation. Today that is CloudNativePG WAL archiving and last-backup issues, which main titles "Backup failed".

Namespace switch

No visible change on a view-filter switch: both builds send the same six requests after picking a namespace. What changes:

  • the switch waits 15s instead of 5s;
  • after a client timeout, a view-filter switch reads the server's scope and reports success if the pick was applied;
  • a cache-scoped switch (--namespace-scope) refreshes every query except the scope it just wrote, without cancelling refetches in flight.

Timeline

A Pod row is tagged by the state the change produced: unhealthy when a container just exited with an error, degraded from the moment the Pod lost readiness until it returns. Before, it waited out the standing-state grace periods. A clean container exit still reads as completion, and a Pod still starting up keeps its grace.

Smaller fixes

  • Sidebar: a filter keeps a category's matching workspace views (all of them when the term names the category or an API group). Arrow keys and Enter reach them. The heading reads Views instead of Workspace. Today only the CloudNativePG workspace has views.
  • Row menus: Escape closes the menu without also closing the drawer.
  • YAML: the Edit button uses the accent token instead of text-blue-*.
  • Logging: a namespace name is no longer used as a console format string.

Radar Hub and library consumers

All changes to @skyhook-io/k8s-ui and @skyhook-io/radar-app are additive:

  • WorkloadView gains the opt-in host props titlePrefix, inlineBadges, namespaceNote, renderStatusBadge, statusNote, hideKindBadge, subheader, tabOrder, specTab and renderHeaderActions. WorkloadExtraTab gains badge.
  • DetailShell gains subheader and wrapHeader; ResourceActionsBar gains leadingActions.
  • IssueRow and ResourceIssuesSection gain compact. LogCore.emptyMessage widens from string to ReactNode. MetricsUnavailableNotice gains noUsageYet.
  • New package exports: issueTitle, issueReasonTitle, tokenizeJson. summarizeSchedulerMessage gains an optional { plain } argument.

Version skew. Hub runs the newest UI against older Radars:

  • metricsAPIReachable is absent there, so a Pending Pod keeps the old metrics notice.
  • An issue without a decomposed cause falls back to the raw scheduler message. The plain-words summary handles that too (see the deviations).

The expanded header and tab strip change for Hub users exactly as they do in Radar.

radar-app now imports issueTitle from k8s-ui. This ships in k8s-ui 1.17.0, which the peer floor (>=1.17.0) already requires.

Deviations from #1922

Code is copied verbatim from #1922 at 01f2a97ab except:

  • The drawer header keeps main's layout (owner decision; see above). The renderStatusBadge hook, leadingActions and the non-shrinking utility buttons stay, and the drawer case in WorkloadView.spec.test.tsx now checks that the name has its own line.
  • ResourcesSidebar.tsx leaves out countLowerBound and its test. It needs facts/certaintyGlyph, which stays in CloudNativePG: incident workflows, guarded operations and guided recovery #1922.
  • status-tone.tsx carries only toneTextClass, which the logs status line uses. toneFillClass, worseTone and their exports stay in CloudNativePG: incident workflows, guarded operations and guided recovery #1922.
  • server.go carries only the metricsAPIReachable hunk. The CNPG routes and the move to internal/integration stay. server_smoke_test.go carries TestPodMetricsHistoryAPIReachability.
  • client.ts carries podMetricsQuery, metricsAPIReachable and the namespace-switch hunks. useIssues(enabled), useSubjectIssues, the memoized useRadarFeature, the new PVC usage statuses and ApplyResourceResult.uid stay; they belong to the other carve-outs or to CNPG.
  • The work order's inventory didn't list three files, but they are what makes the plain scheduling cause reach users, so this PR carries them: IssuesView.tsx and ResourceIssuesSection.tsx (with its test), and the one-line compact={!expanded} in web WorkloadView.tsx.
  • summarizeSchedulerMessage(â€Ķ, { plain: true }) also handles the scheduler phrasing in current Kubernetes. On EKS 1.34 the message reads 6 node(s) had untolerated taint(s), 8 node(s) didn't match Pod's node affinity/selector. no new claims to deallocate, preemption: â€Ķ, and CloudNativePG: incident workflows, guarded operations and guided recovery #1922's version returned 6 node(s) had untolerated taint(s); 8 node(s) didn't match Pod's node affinity/selector. no new claims to deallocate; (raw clauses and a trailing ; ). It now returns 6 nodes have taints the Pod does not tolerate; 8 nodes do not match the Pod’s node affinity or selector. The test uses that message. The phrase is removed with a plain match: CodeQL flagged the first version's \s* as polynomial on long runs of spaces.
  • Messages are split into clauses in one pass. CloudNativePG: incident workflows, guarded operations and guided recovery #1922's lookahead (/[,;](?![^\[\]{}]*[\]}])/) rescanned the rest of the message at every comma, so a 240 KB custom-scheduler message took about 7 s and blocked the drawer. The one-pass split keeps the same rule: no split where the next bracket ahead closes. A test covers a 10,000-clause message.
  • "No usage yet" also checks init containers. CloudNativePG: incident workflows, guarded operations and guided recovery #1922 looked only at regular containers, so a Pending Pod running its init container read "this Pod is not running". Test added in PodRenderer.test.tsx.
  • Timeline readiness stays degraded until it returns. In CloudNativePG: incident workflows, guarded operations and guided recovery #1922 only the row where readiness dropped was degraded. A later change while the Pod was still unready (for example, attaching a debug container) fell back to the 5-minute standing-state grace and read healthy, which ended the incident early. Readiness now counts as lost when the Ready condition went false after a running container had started; at startup it goes false first, so the startup grace stays. One case in CloudNativePG: incident workflows, guarded operations and guided recovery #1922's test described a Pod that had run for an hour and just lost readiness while calling it startup; it now uses a Pod that is actually starting. New cases cover a later unready change and a Pod first seen after losing readiness. A clean exit (code 0) also no longer counts as completion when the Pod restarts its containers (restartPolicy: Always), and a restarted container's previous run counts as proof that readiness was lost. Before, a liveness restart that exited cleanly turned the row green while the Pod was still unready.
  • WorkloadView asks a host's renderStatusBadge for a badge only once the resource has loaded. In CloudNativePG: incident workflows, guarded operations and guided recovery #1922 it was called with undefined during loading: a callback that reads fields throws, and the CloudNativePG Cluster badge read "Phase Not reported" until the object arrived. Test: waits for the resource before asking a host for its status badge.
  • The timeout reconcile applies only to view-filter switches. A --namespace-scope rescope publishes its target before the cache rebuild finishes (reinitializeNamespaceScope sets the override before InitAllSubsystems) and can still roll back, so a matching scope doesn't prove it succeeded. Test: client.namespaceSwitch.hook.test.tsx.

Left in #1922

The drawer title-line layout stays there until it adapts to long names. The rest is opt-in only or CloudNativePG-specific:

  • AreaChart (shadedRanges), CreateResourceDialog (onBack, initialMode, lockMode), DockContext / TerminalTab (shell, sessionNote), AlertBanner.action, .btn-secondary;
  • the DatabaseRole renderer dispatch, ResourcesView.renderKindView and the web wiring to web/src/integrations;
  • the facts, problems, FoldSection, RefLink, grant and ActionConfirmDialog components;
  • the stripTrailingSlashes refactor and DESIGN.md's facts section.

Testing

  • go build ./... && go test ./..., make tsc, k8s-ui tsc + vitest (245 files, 4,486 tests), web vitest (167 files, 1,998 tests), and npm run lint all pass. Lint reports 0 errors and the same 489 warnings as main.

  • Before/after on a live EKS 1.34 cluster. Main and this branch ran side by side against the same cluster at 1280×800 and 1920×1080, in light and dark. Captures:

    • a Deployment, drawer and expanded;
    • an unschedulable Pod (a fixture with an init container) and a running Pod;
    • a Service;
    • an uncurated CRD (ClickHouseInstallation);
    • workload and single-Pod logs, including a workload with no running Pod;
    • the namespace switch.

    All captures showed the "What users see" changes above. Export and Clear were disabled on the empty logs view, and the namespace switch sent identical requests on both builds.

  • Not exercised live:

    • the sidebar views filter, whose only views are on the CloudNativePG workspace page (the keyboard tests cover it);
    • the timeline health tags (Go tests);
    • the reconcile after a client timeout (client.namespaceSwitch.test.ts).

After this merges

#1922 merges main and takes main's version of every file here, including main's drawer header in WorkloadView.tsx (owner decision): it does not re-apply its title-line drawer layout. Its own changes to ResourcesSidebar.tsx (countLowerBound), status-tone.tsx, server.go, client.ts and web WorkloadView.tsx go back on top. It also picks up the scheduler-summary fix above.

â€Ķtop widths, two action groups

The drawer and expanded headers put the name, copy button and status on one line. The tab strip drops its icons only while the tabs don't fit, keeps the active tab in view and lets scope controls wrap. The actions bar splits object and utility actions into two wrapping groups. Opt-in header props for host pages come along, inert until a host passes them.
â€Ķogs loaded

At laptop widths the toolbar wraps its display and utility controls as a group instead of squeezing labels or pushing Export and Clear off screen. The capture time and any notice share one status line. Export and Clear are disabled while nothing is loaded. Long lines break between words before breaking inside one, a structured line keeps its field count whole, and a PostgreSQL record shows its own message.
â€Ķending Pod has no usage yet

A container that hasn't started is neutral instead of red Not Ready or amber Pending; only a reported failure is red. When the metrics API answers but the Pod has never run, Resource Usage says so instead of claiming Radar cannot read metrics.k8s.io. The Pod metrics history response gains metricsAPIReachable.
â€Ķe issues by reason

Drawer issue rows show the scheduler's reasons in plain words on their own line, every cause kept, with the raw message in the details. Issue rows on Home and the Issues page take the reason's title when the category would name the wrong operation.
â€Ķ after the client gave up

A view-filter switch now waits 15s instead of 5s. After a timeout the client reads the server's scope and reports success if the pick was applied. A cache-scoped switch refreshes every query except the scope it just wrote, without cancelling refetches in flight.
A Pod row is unhealthy when a container just exited with an error and degraded when the Pod just lost readiness, instead of waiting out the standing-state grace periods. A clean container exit still reads as completion.
Filtering the resources sidebar keeps the workspace views whose label matches, or all of them when the term names the category or one of its API groups. Arrow keys and Enter reach the views too. The views heading reads Views.
â€Ķhe accent token

Also stops passing a namespace name as a console format string.
While the resource loads there is no status to report; calling the host's renderer then either throws or shows a false 'not reported'.
A --namespace-scope rescope publishes its target before the cache rebuild finishes and can still roll back, so a matching scope doesn't prove it succeeded.
@nadaverell
nadaverell requested a review from hisco as a code owner October 11, 2026 23:26
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Make shared detail pages and logs fit laptop widths

âœĻ Enhancement 🐞 Bug fix 🧊 Tests 🕐 40+ Minutes

Grey Divider

AI Description

â€Ē Rework detail headers, tabs, actions, and log controls to fit laptop-width screens.
â€Ē Clarify Pending Pod status, metrics availability, scheduling causes, and issue titles.
â€Ē Improve namespace-switch recovery, timeline health labels, and sidebar view navigation.
Diagram

graph TD
  Host["Radar and Hub"] --> View["WorkloadView"] --> Shell["DetailShell"] --> Tabs["Responsive tabs"]
  View --> Actions["ResourceActionsBar"]
  View --> Issues["Issue rows"]
  View --> Logs["Logs viewer"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Retain the existing drawer title
  • ➕ Preserves full-width long names when issue actions appear.
  • ➖ Loses the shorter header for short-name resources.
2. Inline the name only when it fits
  • ➕ Keeps the compact header without squeezing long names beside actions.
  • ➖ Requires additional responsive layout behavior and testing.

Recommendation: The shared-component approach with opt-in host props is appropriate. Resolve the stated drawer-header product decision before merge: prefer fitting the name conditionally, or retain the existing drawer title until that design is ready.

Files changed (46) +1666 / -300

Enhancement (15) +446 / -179
server.goReport Pod metrics API reachability +10/-2

Report Pod metrics API reachability

â€Ē Adds metricsAPIReachable to Pod metrics history responses using collection success and current error state.

internal/server/server.go

IssuesView.tsxShow precise titles and compact scheduling causes +8/-3

Show precise titles and compact scheduling causes

â€Ē Uses reason-specific titles where appropriate. Compact unschedulable rows show a wrapping plain-language cause while retaining raw details.

packages/k8s-ui/src/components/issues/IssuesView.tsx

ResourceIssuesSection.tsxForward compact issue-row mode +3/-0

Forward compact issue-row mode

â€Ē Adds an optional compact prop and passes it to each IssueRow.

packages/k8s-ui/src/components/issues/ResourceIssuesSection.tsx

index.tsExport issue title helpers +2/-0

Export issue title helpers

â€Ē Exposes issueTitle and issueReasonTitle to library consumers.

packages/k8s-ui/src/components/issues/index.ts

LogCore.tsxWrap log controls and guard unavailable actions +25/-17

Wrap log controls and guard unavailable actions

â€Ē Groups display utilities, disables empty Export/Clear, and optionally blocks streaming without a source. Accepts React empty-state content and improves line wrapping.

packages/k8s-ui/src/components/logs/LogCore.tsx

LogToolbarSelects.tsxExplain disabled log-range controls +3/-1

Explain disabled log-range controls

â€Ē Allows callers to provide the tooltip reason when log-range selection is disabled.

packages/k8s-ui/src/components/logs/LogToolbarSelects.tsx

StructuredLogLine.tsxImprove structured log summaries +22/-30

Improve structured log summaries

â€Ē Shows verified PostgreSQL messages, keeps field counts together, and shares JSON tokenization for expanded values.

packages/k8s-ui/src/components/logs/StructuredLogLine.tsx

WorkloadLogsViewer.tsxAdd host log-source options and compact status +36/-9

Add host log-source options and compact status

â€Ē Adds initial-container selection, an optional empty-source state, and optional no-source control disabling. Combines capture time and notice into one status area.

packages/k8s-ui/src/components/logs/WorkloadLogsViewer.tsx

MetricsUnavailableNotice.tsxDistinguish no usage from metrics failure +3/-2

Distinguish no usage from metrics failure

â€Ē Adds an optional notice saying a non-running Pod has no usage yet.

packages/k8s-ui/src/components/resources/renderers/MetricsUnavailableNotice.tsx

resource-utils.tsAdd plain-language scheduler summaries +30/-4

Add plain-language scheduler summaries

â€Ē Adds optional plain mode translating recognized scheduling causes while preserving multiple and unknown clauses.

packages/k8s-ui/src/components/resources/resource-utils.ts

DetailShell.tsxMake shared tabs fit narrow widths +85/-10

Make shared tabs fit narrow widths

â€Ē Compacts tabs when needed, scrolls the active tab into view, and lets adjacent controls wrap. Adds opt-in subheader and wrapping-header slots.

packages/k8s-ui/src/components/shared/DetailShell.tsx

ResourceActionsBar.tsxSeparate object and utility actions +8/-3

Separate object and utility actions

â€Ē Adds a leading-actions slot and wrapping groups so object actions and utilities stay organized at narrower widths.

packages/k8s-ui/src/components/shared/ResourceActionsBar.tsx

status-tone.tsxProvide status-tone text classes +16/-0

Provide status-tone text classes

â€Ē Adds typed text colors used by the logs status notice.

packages/k8s-ui/src/components/ui/status-tone.tsx

WorkloadView.tsxCompose responsive headers and host extensions +194/-98

Compose responsive headers and host extensions

â€Ē Rearranges drawer identity and actions, adopts responsive expanded chrome, and adds opt-in host header, status, tab, and Spec slots. Guards resource-dependent content during loading and deletion.

packages/k8s-ui/src/components/workload/WorkloadView.tsx

WorkloadView.tsxUse compact issues in the drawer +1/-0

Use compact issues in the drawer

â€Ē Enables wrapping scheduling-cause presentation outside the expanded view.

web/src/components/workload/WorkloadView.tsx

Bug fix (11) +266 / -100
cache.goClassify recorded timeline changes +1/-1

Classify recorded timeline changes

â€Ē Passes old and new objects to change-aware timeline health classification.

internal/k8s/cache.go

timeline_health.goLabel fresh Pod failures in the timeline +40/-0

Label fresh Pod failures in the timeline

â€Ē Marks a new error exit unhealthy and lost readiness degraded without standing-state grace periods. Preserves clean completion and non-Pod classification.

internal/k8s/timeline_health.go

severity.tsName exceptional issues by reason +21/-0

Name exceptional issues by reason

â€Ē Adds titles where a category misnames an issue’s operation, with a category-label fallback.

packages/k8s-ui/src/components/issues/severity.ts

ResourcesSidebar.tsxKeep matching views in sidebar search +84/-39

Keep matching views in sidebar search

â€Ē Includes workspace destinations in filtered results and keyboard navigation. Renames their heading Views.

packages/k8s-ui/src/components/resources/ResourcesSidebar.tsx

PodRenderer.tsxPresent unstarted containers neutrally +24/-28

Present unstarted containers neutrally

â€Ē Reserves error styling for reported failures and shows Not started for unstarted containers. Shows no usage yet only for Pending Pods without samples when metrics are reachable.

packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx

EditableYamlView.tsxTheme the YAML Edit control +1/-1

Theme the YAML Edit control

â€Ē Replaces fixed blue text colors with accent tokens.

packages/k8s-ui/src/components/shared/EditableYamlView.tsx

RowActionMenu.tsxKeep Escape from closing the drawer +8/-3

Keep Escape from closing the drawer

â€Ē Consumes Escape in the open menu’s capture-phase handler before page-level dismissal.

packages/k8s-ui/src/components/ui/RowActionMenu.tsx

log-level.tsIdentify PostgreSQL records precisely +12/-8

Identify PostgreSQL records precisely

â€Ē Uses nested PostgreSQL severity only with matching logger, message, and recognized severity; shares record selection with summaries.

packages/k8s-ui/src/utils/log-level.ts

client.tsReconcile slow namespace switches safely +72/-17

Reconcile slow namespace switches safely

â€Ē Extends the view-filter timeout and verifies server scope after client timeout. Cache-scoped switches invalidate other queries without cancelling refetches; Pod metrics gain a reusable query definition and optional reachability field.

web/src/api/client.ts

HomeView.tsxShow accurate issue titles on Home +2/-2

Show accurate issue titles on Home

â€Ē Uses reason-specific titles when a category would misname the operation.

web/src/components/home/HomeView.tsx

CapabilitiesContext.tsxLog namespace names as data +1/-1

Log namespace names as data

â€Ē Passes the namespace as a separate console argument rather than embedding it in the format string.

web/src/contexts/CapabilitiesContext.tsx

Refactor (1) +71 / -15
log-format.tsShare bounded JSON string scanning +71/-15

Share bounded JSON string scanning

â€Ē Exports tokenizeJson for log rendering and highlighting; reworks string unescaping to avoid repeatedly matching long escaped input.

packages/k8s-ui/src/utils/log-format.ts

Tests (19) +883 / -6
timeline_health_test.goCover Pod change-health transitions +42/-0

Cover Pod change-health transitions

â€Ē Tests error exits, readiness loss and recovery, clean completion, and startup grace.

internal/k8s/timeline_health_test.go

server_smoke_test.goTest metrics reachability reporting +14/-0

Test metrics reachability reporting

â€Ē Checks never-successful, healthy, and currently failing collection states.

internal/server/server_smoke_test.go

ResourceIssuesSection.test.tsxTest compact scheduling issues +28/-0

Test compact scheduling issues

â€Ē Checks wrapped causes, raw expanded details, aggregated issues, and responses without a decomposed cause.

packages/k8s-ui/src/components/issues/ResourceIssuesSection.test.tsx

issues.test.tsTest reason-specific issue titles +3/-1

Test reason-specific issue titles

â€Ē Checks WAL archiving gets its own title while ordinary backup failure retains the category title.

packages/k8s-ui/src/components/issues/issues.test.ts

LogCore.theme.test.tsxTest log controls and wrapping +81/-3

Test log controls and wrapping

â€Ē Covers unavailable streaming, keyboard hints, grouped utilities, word wrapping, and Export/Clear availability.

packages/k8s-ui/src/components/logs/LogCore.theme.test.tsx

StructuredLogLine.test.tsxTest structured summaries and filtering +76/-0

Test structured summaries and filtering

â€Ē Covers PostgreSQL record detection, unrelated records, intact field counts, wrapping, safe rendering, and filter values.

packages/k8s-ui/src/components/logs/StructuredLogLine.test.tsx

WorkloadLogsViewer.test.tsxTest optional no-source log behavior +36/-0

Test optional no-source log behavior

â€Ē Checks host-provided empty states and disabled source controls while preserving default streaming behavior.

packages/k8s-ui/src/components/logs/WorkloadLogsViewer.test.tsx

useLogBuffer.test.tsProtect ordinary log severity +8/-0

Protect ordinary log severity

â€Ē Verifies unrelated nested records cannot override a workload log line’s level.

packages/k8s-ui/src/components/logs/useLogBuffer.test.ts

ResourcesSidebar.keyboard.test.tsxTest keyboard navigation through views +42/-0

Test keyboard navigation through views

â€Ē Verifies arrow keys and Enter reach workspace views and resource kinds, including view-only results.

packages/k8s-ui/src/components/resources/ResourcesSidebar.keyboard.test.tsx

ResourcesSidebar.test.tsxTest workspace view filtering +21/-2

Test workspace view filtering

â€Ē Checks category, API-group, view-label, and empty filters; updates the heading expectation to Views.

packages/k8s-ui/src/components/resources/ResourcesSidebar.test.tsx

PodRenderer.test.tsxTest Pending Pod status and metrics +31/-0

Test Pending Pod status and metrics

â€Ē Covers unstarted and waiting container tones, reported failures, readiness, and metrics fallbacks.

packages/k8s-ui/src/components/resources/renderers/PodRenderer.test.tsx

summarize-scheduler-message.test.tsTest scheduler-summary phrasing +23/-0

Test scheduler-summary phrasing

â€Ē Covers resource, affinity, taint, volume, and current scheduler messages plus unknown-clause preservation.

packages/k8s-ui/src/components/resources/summarize-scheduler-message.test.ts

DetailShell.test.tsxTest responsive tabs and action layout +80/-0

Test responsive tabs and action layout

â€Ē Exercises asynchronous badge sizing, active-tab visibility, compact spacing, action wrapping, and focus styles.

packages/k8s-ui/src/components/shared/DetailShell.test.tsx

EditableYamlView.test.tsxTest YAML Edit accent styling +7/-0

Test YAML Edit accent styling

â€Ē Checks the Edit control uses theme accents rather than fixed blue classes.

packages/k8s-ui/src/components/shared/EditableYamlView.test.tsx

WorkloadView.deleted.test.tsxTest host content after deletion +43/-0

Test host content after deletion

â€Ē Verifies live-resource host content disappears for a deleted object but remains for other errors.

packages/k8s-ui/src/components/workload/WorkloadView.deleted.test.tsx

WorkloadView.spec.test.tsxTest host customization and action placement +146/-0

Test host customization and action placement

â€Ē Covers opt-in Spec replacement, status badges, loading and deletion guards, and action grouping.

packages/k8s-ui/src/components/workload/WorkloadView.spec.test.tsx

log-format.test.tsTest JSON tokenization and escaping +41/-0

Test JSON tokenization and escaping

â€Ē Checks highlighting, unescaping, HTML safety, primitive-looking string contents, and long unterminated input.

packages/k8s-ui/src/utils/log-format.test.ts

client.namespaceSwitch.hook.test.tsxTest namespace timeout recovery +54/-0

Test namespace timeout recovery

â€Ē Checks an applied view-filter switch succeeds after timeout while a cache-scoped rescope still reports timeout.

web/src/api/client.namespaceSwitch.hook.test.tsx

client.namespaceSwitch.test.tsTest reconciliation and cache refresh +107/-0

Test reconciliation and cache refresh

â€Ē Covers scope comparison, failed reads, unchanged view-filter queries, and non-cancelling cache-scoped invalidation.

web/src/api/client.namespaceSwitch.test.ts

Comment thread packages/k8s-ui/src/components/resources/resource-utils.ts Fixed
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0) ðŸŽĻ UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Streaming controls keep a fixed green background 📘 Rule violation ⚙ Maintainability
Description
The streaming button in LogCore still uses bg-green-600 and hover:bg-green-700 instead of a
theme background token. Whenever streaming is active, the control bypasses theme background
customization and can render inconsistently across themes.
Code

packages/k8s-ui/src/components/logs/LogCore.tsx[R581-584]

+              disabled={sourceUnavailable && !isStreaming}
+              className={`flex items-center gap-1.5 px-2 py-1.5 text-xs rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed ${
           isStreaming
             ? 'bg-green-600 text-white hover:bg-green-700'
Evidence
The modified active-streaming branch contains bg-green-600 and hover:bg-green-700, while the
checklist permits only theme background tokens for modified frontend components.

Rule 3036653: Use theme background tokens instead of hardcoded utility color classes
packages/k8s-ui/src/components/logs/LogCore.tsx[581-584]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The active streaming control uses hardcoded green background utility classes, which bypasses the shared theme background tokens.
## Fix Focus Areas
- packages/k8s-ui/src/components/logs/LogCore.tsx[581-584]
## Recommended Fix
Replace `bg-green-600` and `hover:bg-green-700` with the appropriate semantic or theme background utilities, preserving the active streaming state styling without hardcoded palette colors.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Status badges bypass shared styling 📘 Rule violation ⚙ Maintainability
Description
WorkloadView renders the resource status as a span with the computed status.color class
instead of using the shared Badge component with a semantic severity or kind. Every drawer or
expanded view that supplies status reaches this branch, so status appearance remains coupled to
ad-hoc CSS class strings.
Code

packages/k8s-ui/src/components/workload/WorkloadView.tsx[R954-958]

+                {renderStatusBadge && resource && !resourceGone ? renderStatusBadge(resource) : status && (
+                  <span className={clsx('badge', status.color)}>
+                    {status.text}
+                  </span>
+                )}
Evidence
The changed workload header contains a badge-like span whose appearance comes from status.color;
the shared badge component is used elsewhere in the same PR for semantic status styling, but this
status path still uses custom markup.

Rule 3036677: Use Badge components instead of hard-coded badge color strings
packages/k8s-ui/src/components/workload/WorkloadView.tsx[954-958]
packages/k8s-ui/src/components/workload/WorkloadView.tsx[1164-1167]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The workload status badge is implemented as a raw `span` using `status.color`, bypassing the shared `Badge` component and its semantic appearance API.
## Fix Focus Areas
- packages/k8s-ui/src/components/workload/WorkloadView.tsx[954-958]
- packages/k8s-ui/src/components/workload/WorkloadView.tsx[1164-1167]
## Recommended Fix
Map the workload status to the appropriate `Badge` `severity` or `kind` value and render the status text through `Badge` in both the drawer and expanded header paths. Remove the direct `status.color` class usage from those badge elements.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Running init containers appear not running ✓ Resolved
Description
notRunning checks only regular container statuses and ignores initContainerStatuses. When a
Pending Pod has a running init container but no metrics samples yet, Resource Usage says the Pod is
not running even though its init container is shown as Running.
Code

packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx[R329-330]

+  const notRunning = data.status?.phase === 'Pending' && !containerStatuses.some((s: any) => s.state?.running)
+  const noUsageYet = notRunning && metricsHistory?.metricsAPIReachable === true && !metricsHistory.collectionError && !metricsHistory.containers?.length && !metrics?.containers?.length
Evidence
The renderer reads init and regular container statuses separately, but the new notice condition
checks only regular statuses. It explicitly labels a running init container Running while
MetricsUnavailableNotice renders the contradictory text when noUsageYet is true.

packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx[306-308]
packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx[329-330]
packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx[501-524]
packages/k8s-ui/src/components/resources/renderers/MetricsUnavailableNotice.tsx[10-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A Pending Pod with a running init container can display a notice saying the Pod is not running because `notRunning` checks only regular containers.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx[306-330]
## Recommended Fix
Include init-container running states in the `notRunning` check, and add a test covering a Pending Pod with a running init container, reachable metrics API, and no samples.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
4. Old-namespace data can persist after a rescope 🐞 Bug ≡ Correctness
Description
refreshAfterNamespaceSwitch calls invalidateQueries(..., { cancelRefetch: false }). The old code
called invalidateQueries(), which cancels a refetch already in flight and starts a new one. With
the new option, a query that was already fetching when the --namespace-scope rebuild finished is
not re-requested. Its response comes from the cache that was being torn down, gets stored as fresh,
and the resource lists and topology show the previous namespace scope until the next refetch
interval or stale time.
Code

web/src/api/client.ts[R6847-6849]

+  const notScope = (query: { queryKey: readonly unknown[] }) =>
+    query.queryKey[0] !== "namespace-scope";
+  await queryClient.invalidateQueries({ predicate: notScope }, { cancelRefetch: false });
Evidence
Old code (removed lines 6879-6881) used queryClient.invalidateQueries() with its default
cancelRefetch behaviour. The new helper adds { cancelRefetch: false }, and
useSetActiveNamespace.onSuccess (line 6936) calls it with void. The mutation only resolves after
the server's rebuild, which can take up to NAMESPACE_RESCOPE_TIMEOUT (120s). Polling queries issued
during the rebuild are the ones that will be in flight at that point, and with this option they are
kept rather than re-run against the new scope.

web/src/api/client.ts[6842-6850]
web/src/api/client.ts[6933-6938]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
After a cache-scoped namespace switch, `refreshAfterNamespaceSwitch` invalidates queries with `cancelRefetch: false`. Requests already in flight against the pre-rebuild cache are kept, and their old-scope responses are stored as fresh.
## Fix Focus Areas
- web/src/api/client.ts[6842-6850]
## Recommended Fix
Drop `{ cancelRefetch: false }` (or pass `cancelRefetch: true`) so every query except `namespace-scope` is cancelled and refetched against the new scope. Keep the predicate that excludes the `namespace-scope` key.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/k8s-ui/src/components/logs/LogCore.tsx
Comment thread packages/k8s-ui/src/components/workload/WorkloadView.tsx Outdated
Comment thread packages/k8s-ui/src/components/resources/renderers/PodRenderer.tsx Outdated
Comment thread web/src/api/client.ts
A later change while the Pod is still unready used to fall back to the standing-state grace and read healthy, ending the incident early. Readiness counts as lost when it went false after a running container had started; at startup it went false first.
The lookahead rescanned the rest of the message at every comma, so a long custom scheduler message took seconds and blocked the drawer. One backward pass keeps the same rule: no split where the next bracket ahead closes.
Sharing the title line with the drawer controls left a long name about 300px whenever Investigate showed, so a Pod name lost its distinguishing suffix. The drawer keeps main's layout: kind and status beside the controls, the name and namespace below. A host's status badge and object actions still render there.
Under restartPolicy Always a container exiting 0 is restarting, not completing, and a restarted container's previous run also proves readiness was lost rather than still to come.
@nadaverell
nadaverell merged commit 1e94aaf into main Oct 12, 2026
9 checks passed
@nadaverell
nadaverell deleted the feature/shared-detail-ui branch October 12, 2026 10:38
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.

2 participants