Skip to content

frontend: globalSearch: Improve useLocalStorageState and fix sync bugs - #5936

Open
vishnukothakapu wants to merge 1 commit into
kubernetes-sigs:mainfrom
vishnukothakapu:fix/use-local-storage-state-stale-closure-v2
Open

frontend: globalSearch: Improve useLocalStorageState and fix sync bugs#5936
vishnukothakapu wants to merge 1 commit into
kubernetes-sigs:mainfrom
vishnukothakapu:fix/use-local-storage-state-stale-closure-v2

Conversation

@vishnukothakapu

@vishnukothakapu vishnukothakapu commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

While the original stale-closure bug was resolved in main via useSyncExternalStore (commit 676ef52), that refactor unfortunately missed some critical features of standard local storage hooks. Since I've spent two months iterating on this PR, I decided to pivot this branch to add those missing features, drastically improving the hook and fixing a real bug!

The Improvements

1. Cross-Tab Synchronization (Missing in main)
The current implementation in main only synchronizes state within the same window because it doesn't listen to the browser's storage event. I added a global window.addEventListener('storage') listener. Now, if a user has Headlamp open in two tabs and changes a setting (like log severity), all tabs immediately update in sync.

2. Direct-Value Setters (Missing in main)
Standard React useState accepts both updater functions and direct values. The implementation in main forced developers to use updater functions (e.g., setZoomMode(() => '100%')). I updated the signature to accept T | ((oldValue: T) => T) for a much better developer experience.

3. Fixed LogsButton Synchronization Bug
Previously, LogsButton.tsx updated headlamp.logs.severityFilter directly via localStorage.setItem. Because it bypassed the hook, other components (like Pod Details) did not re-render when it changed. I refactored LogsButton to fully consume the useLocalStorageState hook instead of raw local storage, instantly fixing this cross-component sync bug!

Steps to Test

  • Cross-tab sync: Open Headlamp in two tabs. Change the Log Severity filter in one tab, and observe it automatically update in the other tab.
  • LogsButton sync: Open the Logs Viewer from a Pod Details view. Change the severity filter, and observe that the Pod Details background view immediately receives the updated state thanks to the new robust hook synchronization.

@k8s-ci-robot k8s-ci-robot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Jun 7, 2026
@k8s-ci-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: vishnukothakapu
Once this PR has been reviewed and has the lgtm label, please assign ashu8912 for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Jun 7, 2026
@vishnukothakapu
vishnukothakapu force-pushed the fix/use-local-storage-state-stale-closure-v2 branch 3 times, most recently from 85c6b95 to 429b362 Compare June 7, 2026 05:52
@illume
illume requested a review from Copilot June 7, 2026 08:32

@illume illume left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR.

There are snapshot mismatches in CI. You can fix them locally by running cd frontend && npm run test -- -u.

How to update snapshots

Run cd frontend && npm run test -- -u to regenerate all snapshots. Review the diff to make sure the visual changes are intentional, then commit the updated snapshot files.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a stale-closure bug in useLocalStorageState that could cause cross-component updates triggered via useLocalStorageState.update() to apply using an outdated setter/state snapshot, leading to missed or incorrect synchronization across mounted components.

Changes:

  • Makes the hook’s set function stable across renders via useCallback([key]).
  • Switches to the functional setState(oldState => ...) form to avoid closing over a stale state value.
  • Fixes the useEffect dependency list by depending on set (removing the prior exhaustive-deps suppression).

Notes (review constraints):

  • CI/check status and PR commit history (merge commits / commit-message coherence) were not available in the provided context.

Comment thread frontend/src/components/globalSearch/useLocalStorageState.tsx Outdated

@illume illume left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution.

There are some open Copilot review comments — could you take a look at them? Please mark each one as resolved once you've addressed it.

@vishnukothakapu
vishnukothakapu force-pushed the fix/use-local-storage-state-stale-closure-v2 branch from 429b362 to bc24145 Compare June 7, 2026 16:35
@k8s-ci-robot k8s-ci-robot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Jun 7, 2026
@vishnukothakapu
vishnukothakapu force-pushed the fix/use-local-storage-state-stale-closure-v2 branch from bc24145 to 866d3c8 Compare June 7, 2026 16:45
@k8s-ci-robot k8s-ci-robot added size/S Denotes a PR that changes 10-29 lines, ignoring generated files. and removed needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Jun 7, 2026
@vishnukothakapu
vishnukothakapu force-pushed the fix/use-local-storage-state-stale-closure-v2 branch 2 times, most recently from 5d5713c to 07887c4 Compare June 7, 2026 20:10
@vishnukothakapu
vishnukothakapu requested a review from illume June 7, 2026 20:26
@vishnukothakapu
vishnukothakapu force-pushed the fix/use-local-storage-state-stale-closure-v2 branch from 07887c4 to ec81afd Compare June 8, 2026 20:16
@illume
illume requested a review from Copilot June 9, 2026 12:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread frontend/src/components/globalSearch/useLocalStorageState.tsx Outdated
@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels Jun 9, 2026

@illume illume left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution.

it looks like there's a merge-main commit in this PR — could you rebase onto main instead?

Why this matters

Merge commits from main make the PR history harder to review. Please rebase your branch on top of the latest main instead, then update the PR with the rebased commits.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread frontend/src/components/globalSearch/useLocalStorageState.tsx Outdated
Comment thread frontend/src/components/globalSearch/useLocalStorageState.test.tsx Outdated

@illume illume left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this.

Would you mind addressing the open Copilot review comments? Please mark each comment as resolved after addressing it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:52

  • This test exercises the JSON-parse failure path, which logs via console.warn, but it doesn't stub console.warn. Stubbing it avoids noisy test output and aligns with other tests that treat expected warnings as part of the assertion surface.
  it('returns the default value when the stored JSON is malformed', () => {
    localStorage.setItem(TEST_KEY, 'not-valid-json{{{');
    const { result } = renderHook(() => useLocalStorageState(TEST_KEY, 0));
    expect(result.current[0]).toBe(0);
  });

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:46

  • This test intentionally triggers the hook's console.warn path, but it doesn't stub console.warn. This can make unit test output noisy and inconsistent with other tests in the repo that silence/inspect expected warnings. Consider stubbing console.warn within the test (and restoring it) when exercising expected error paths.

This issue also appears on line 48 of the same file.

  it('returns the default value when localStorage.getItem throws', () => {
    const spy = vi.spyOn(localStorage, 'getItem').mockImplementation(() => {
      throw new Error('Storage disabled');
    });
    try {

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:123

  • This test intentionally triggers useLocalStorageState.update's console.error path, but it doesn't stub console.error, which can produce noisy CI output. Consider stubbing console.error and optionally asserting it was called.
  it('does not notify listeners if useLocalStorageState.update fails to persist', () => {
    const spy = vi.spyOn(localStorage, 'setItem').mockImplementation(() => {
      throw new Error('Storage write disabled');
    });

Comment thread frontend/src/components/globalSearch/useLocalStorageState.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

frontend/src/components/globalSearch/useLocalStorageState.tsx:92

  • Same issue here: setState(newValue) will invoke newValue if it’s a function (React treats it as an updater). Use setState(() => newValue) so cross-component updates always set the value literally.
      setState(newValue);

frontend/src/components/globalSearch/useLocalStorageState.tsx:80

  • setState(newValue) will treat a function newValue as a state-updater callback, which can cause unintended behavior if T (or a mistaken update() call) ever passes a function value. Use the functional form to always set the value literally.

This issue also appears on line 92 of the same file.

      setState(newValue);

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f4d65256-7a75-438d-b2c4-fe52a81a688d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The pull request adds pod and workload diagnostics, CRD validation, plugin resource relations, StatefulSet creation, local-storage synchronization, and supporting UI, documentation, testing, and localization updates.

Changes

Diagnostics and resource UI

Layer / File(s) Summary
Diagnostics and event flow
frontend/src/components/diagnostics/*, frontend/src/components/common/ObjectEventList.tsx, frontend/src/components/common/Resource/Resource.tsx, frontend/src/components/pod/Details.tsx, frontend/src/components/workload/Details.test.tsx
Adds diagnostic analysis for pods and workloads. Event and owned-pod data now flow into diagnostic sections.
StatefulSet creation and local storage synchronization
frontend/src/components/statefulset/*, frontend/src/components/common/Resource/CreateButton.tsx, frontend/src/components/globalSearch/useLocalStorageState.*
Adds StatefulSet creation fields and stories. Updates local-storage persistence, listener synchronization, failure handling, and tests.

CRD resilience and resource relations

Layer / File(s) Summary
CRD validation and safe rendering
frontend/src/lib/k8s/crd*.ts, frontend/src/components/crd/*, frontend/src/components/Sidebar/useSidebarItems.tsx
Validates CRD specifications, supports nullable class creation, and handles incomplete CRDs in lists, details, sidebar entries, and resource maps.
Plugin relation registration and graph labels
frontend/src/plugin/*, plugins/headlamp-plugin/src/index.ts, plugins/examples/customizing-map/src/index.tsx, frontend/src/components/resourceMap/*
Adds validated plugin relation registration. Stores relations in graph state. Generates relation-specific edge IDs and optional edge labels.

Supporting application and documentation updates

Layer / File(s) Summary
Application, documentation, localization, and validation support
.github/scripts/*, app/electron/tray.ts, backend/pkg/clusterinventory/*, docs/development/*, frontend/src/i18n/locales/*, frontend/src/components/App/icons.ts
Updates release instructions, dynamic tray labels, fuzz-test input limits, telemetry and relation documentation, diagnostics translations, StatefulSet translations, and an offline icon.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ResourceDetails
  participant useObjectEvents
  participant OwnedPodsSection
  participant Diagnostics
  ResourceDetails->>useObjectEvents: fetch object events
  ResourceDetails->>OwnedPodsSection: request owned pods
  useObjectEvents-->>Diagnostics: provide events
  OwnedPodsSection-->>Diagnostics: provide pods and errors
  Diagnostics-->>ResourceDetails: render findings and log actions
Loading
sequenceDiagram
  participant Plugin
  participant registerResourceRelationProvider
  participant ReduxGraphState
  participant GraphSources
  Plugin->>registerResourceRelationProvider: submit relation
  registerResourceRelationProvider->>ReduxGraphState: store validated relation
  GraphSources->>ReduxGraphState: read relations
  GraphSources-->>GraphSources: create labeled relation edges
Loading

Possibly related PRs

Suggested reviewers: joaquimrocha, illume, sniok

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes many unrelated changes, including diagnostics, CRD handling, StatefulSet forms, telemetry documentation, and resource-map features. Remove unrelated changes or split them into separate pull requests; retain only the useLocalStorageState fix and its tests.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the stale-closure fix in useLocalStorageState and its cross-component listener.
Description check ✅ Passed The description includes the required summary, issue, changes, test steps, and reviewer notes; screenshots are not applicable.
Linked Issues check ✅ Passed The hook now tracks current state and synchronizes mounted subscribers reliably, addressing issue [#5934].
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread frontend/src/components/globalSearch/useLocalStorageState.tsx Outdated
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

frontend/src/components/globalSearch/useLocalStorageState.tsx:127

  • Object.assign(useLocalStorageStateBase, { update() { ... } }) can cause TypeScript to lose or degrade the generic call signature for consumers (depending on TS/version/config). To keep the public API strongly typed, add an explicit type annotation for the exported value (e.g., an intersection of typeof useLocalStorageStateBase with an { update(...) } shape) when assigning the Object.assign result.
export const useLocalStorageState = Object.assign(useLocalStorageStateBase, {
  /**
   * Update the value in local storage and notify all `useLocalStorageState` hooks.
   *
   * @param key - local storage key
   * @param value - local storage value
   */
  update(key: string, value: any): void {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (e) {
      console.error(`Failed to save "${key}" to localStorage`, e);
      return;
    }
    updateListeners[key]?.forEach(fn => fn(value));
  },
});

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:34

  • The suite no longer clears localStorage in a beforeEach, so tests can become order-dependent and leak state between cases within this file (e.g., a test writing TEST_KEY can affect later tests expecting no entry). Add a beforeEach(() => localStorage.clear()) (or at least clear TEST_KEY) in this describe block to ensure isolation and stability.
describe('useLocalStorageState', () => {
  it('returns the default value when localStorage has no entry', () => {
    const { result } = renderHook(() => useLocalStorageState(TEST_KEY, 0));
    expect(result.current[0]).toBe(0);
  });

  it('returns the persisted value when localStorage already has an entry', () => {
    localStorage.setItem(TEST_KEY, JSON.stringify(42));
    const { result } = renderHook(() => useLocalStorageState(TEST_KEY, 0));
    expect(result.current[0]).toBe(42);
  });

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/components/crd/List.tsx (1)

53-65: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Partial spec guarding in the CRD views. Both files now treat spec as possibly undefined for spec.names, but they still dereference other spec fields directly. A CRD delivered without spec therefore still breaks these screens, so the stated fix is incomplete.

  • frontend/src/components/crd/List.tsx#L53-L65: apply optional handling to the Group column at Line 87 and the Scope column at Line 92.
  • frontend/src/components/crd/Details.tsx#L69-L87: apply optional handling to item.spec.group at Line 44, item.spec.version at Line 48, item.spec.scope at Line 52, item.spec.subresources at Lines 56-57, and pass item.spec?.versions ?? [] at Line 116.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/List.tsx` around lines 53 - 65, Complete the
partial spec guarding in frontend/src/components/crd/List.tsx lines 53-65 by
applying optional handling to the Group and Scope column accessors. In
frontend/src/components/crd/Details.tsx lines 69-87, guard item.spec.group,
item.spec.version, item.spec.scope, and item.spec.subresources, and pass an
empty array when item.spec.versions is unavailable. Preserve existing rendering
behavior when spec is present.
frontend/src/components/resourceMap/sources/definitions/relations.tsx (1)

90-114: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Include the relation ID in indexed owner-edge IDs.

The generic relation path appends relation.id to each edge ID. Both indexed owner-relation builders still emit only from.id + '-' + to.id. A matching edge from another relation can then be removed during edge deduplication.

  • frontend/src/components/resourceMap/sources/definitions/relations.tsx#L90-L114: derive the owner relation ID once and append it when buildEdgesWithIndex creates an edge.
  • frontend/src/components/resourceMap/sources/definitions/relations.tsx#L118-L156: derive the reversed-owner relation ID once and append it when buildEdgesWithIndex creates an edge.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/resourceMap/sources/definitions/relations.tsx` around
lines 90 - 114, Update makeOwnerRelation in
frontend/src/components/resourceMap/sources/definitions/relations.tsx lines
90-114 to derive the owner relation ID once and append it to indexed edge IDs
created by buildEdgesWithIndex. Apply the same change to the reversed-owner
relation builder at lines 118-156, ensuring both builders include their relation
ID alongside from.id and to.id.
🧹 Nitpick comments (11)
frontend/src/components/statefulset/CreateStatefulSetForm.tsx (1)

55-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use _.cloneDeep for draft cloning.

The shared form and useSelectorPodTemplate clone drafts with _.cloneDeep. structuredClone throws DataCloneError for non-cloneable values, such as functions or class instances. Use _.cloneDeep here for consistency and for safety with unexpected draft content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.tsx` at line 55,
Replace structuredClone in the draft update flow with the shared _.cloneDeep
utility, keeping the existing resource cloning behavior and update logic
unchanged.
frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx (1)

117-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a story that exercises the OnDelete cleanup effect.

OnDeleteStrategy.args provides updateStrategy.type: 'OnDelete' with no rollingUpdate, so the cleanup effect in CreateStatefulSetForm.tsx Lines 53-59 never runs. Add a story with updateStrategy: { type: 'OnDelete', rollingUpdate: { partition: 3 } } to cover the removal path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx` around
lines 117 - 139, Update the StatefulSet story using the OnDelete strategy to
include a rollingUpdate object with partition set to 3, so
CreateStatefulSetForm’s cleanup effect executes the partition-removal path.
Preserve the existing OnDelete resource configuration and story purpose.
frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Empty.stories.storyshot (1)

341-404: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

The Partition field appears without a selected update strategy.

In the Empty story, spec.updateStrategy.type is undefined. The condition at CreateStatefulSetForm.tsx Line 83 tests only !== 'OnDelete', so the form renders Partition for an unset strategy. Consider rendering Partition only when the type equals RollingUpdate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Empty.stories.storyshot`
around lines 341 - 404, Update the conditional rendering around the Partition
field in CreateStatefulSetForm so it renders only when spec.updateStrategy.type
equals "RollingUpdate"; keep it hidden for undefined and "OnDelete" strategies.
frontend/src/components/crd/CustomResourceInstancesList.test.tsx (1)

189-213: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the all-unusable empty state.

This test covers the mixed case (one unusable CRD, one usable CRD). The new branch in CustomResourceInstancesList.tsx at Lines 204-208 renders No CustomResourceDefinitions with usable specs were found. when every CRD is unusable. That branch has no test. Add a case with only unusable CRDs and assert the message instead of a loader.

💚 Proposed additional test
+  it('shows the empty state when every CRD has an unusable spec (`#4824`)', () => {
+    const incompleteCrd = {
+      cluster: 'test-cluster',
+      metadata: { name: 'incomplete.example.com', namespace: 'default' },
+      jsonData: { status: { acceptedNames: { categories: [] } } },
+      makeCRClassOrNull: () => null,
+    } as unknown as ReturnType<typeof makeMockCrd>;
+
+    setOuterCrdsList([incompleteCrd]);
+
+    renderList();
+
+    expect(
+      screen.getByText('No CustomResourceDefinitions with usable specs were found.')
+    ).toBeInTheDocument();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/CustomResourceInstancesList.test.tsx` around
lines 189 - 213, Add a test alongside the existing makeCRClassOrNull null case
in CustomResourceInstancesList.test.tsx using only CRDs whose makeCRClassOrNull
returns null. Render the list and assert that “No CustomResourceDefinitions with
usable specs were found.” is displayed, while the loading indicator is absent.
frontend/src/components/crd/CustomResourceInstancesList.tsx (1)

38-48: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Confirm the remount key protects the per-entry hook calls.

CrInstancesView calls crdClass.useList(...) once per entry at Line 43. The hook count therefore depends on classified.length. The design relies on remountKey changing whenever membership changes, so React never renders a different hook count in the same component instance.

One case needs confirmation: two different CRDs that produce the same crdSortKey. sortKey returns ${cluster}/${uid || name}. If metadata.uid is absent for one CRD and another CRD's name equals that uid value in the same cluster, the keys collide and the fingerprint stays stable while the count changes. This is unlikely in practice. If you want the guarantee to be structural instead of probabilistic, derive key from classified.length as well.

♻️ Optional hardening
-  const remountKey = useMemo(() => fingerprint(classified.map(it => it.crdSortKey)), [classified]);
+  const remountKey = useMemo(
+    () => `${classified.length}|${fingerprint(classified.map(it => it.crdSortKey))}`,
+    [classified]
+  );

Also applies to: 169-184

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/CustomResourceInstancesList.tsx` around lines 38
- 48, Harden the remount key used by CrInstancesView so it changes whenever the
number of classified entries changes, preventing a different count of per-entry
crdClass.useList hooks from rendering in the same component instance. Include
classified.length in the key/fingerprint derivation alongside the existing CRD
identity values, preserving the current remount behavior for membership changes.
frontend/src/components/crd/CustomResourceDetails.test.tsx (1)

73-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the real Loader component in this behavior test.

Loader is a local implementation. The test can assert that the real loading title is absent. The mock makes the assertion depend on the mock output instead of the rendered application behavior.

Proposed change
-vi.mock('../common/Loader', () => ({
-  default: ({ title }: { title: string }) => <div data-testid="mock-loader">{title}</div>,
-}));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/CustomResourceDetails.test.tsx` around lines 73 -
75, Remove the vi.mock for Loader from CustomResourceDetails tests and render
the real Loader component so the behavior assertion verifies the actual loading
title is absent from the application output.

Source: Coding guidelines

frontend/src/components/diagnostics/Diagnostics.tsx (1)

407-418: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the misplaced JSDoc blocks to the functions they describe.

Two doc comments sit above the wrong function:

  • Lines 407-411 describe getPodDiagnostics but precede diagnosisHint.
  • Lines 811-814 describe PodDiagnosticsSection but precede getFailingContainerName.

Move each block directly above its function. Both affected functions are exported, so editors show the wrong description.

Also applies to: 811-820

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/diagnostics/Diagnostics.tsx` around lines 407 - 418,
Move the JSDoc describing aggregated pod findings so it directly precedes the
exported getPodDiagnostics function, and move the JSDoc describing the
diagnostics section so it directly precedes the exported PodDiagnosticsSection
function. Remove both misplaced comment blocks from above diagnosisHint and
getFailingContainerName without changing their text or function behavior.
frontend/src/components/diagnostics/Diagnostics.stories.tsx (1)

46-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add pod stories for the loading and error states.

WorkloadDiagnosticsSection has WorkloadLoading and WorkloadPodsError stories. PodDiagnosticsSection has only healthy and failing stories. Add a story that exercises the log action (onViewLogs with a failing non-default container) and one that covers an empty/unavailable events result.

As per coding guidelines: "Add Storybook stories with error and loading states for new React components."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/diagnostics/Diagnostics.stories.tsx` around lines 46
- 60, Extend the PodTemplate stories with loading and unavailable-events cases
for PodDiagnosticsSection, matching the existing WorkloadDiagnosticsSection
state coverage. Add a story that provides a failing pod with a non-default
container and exercises the onViewLogs action, plus a story representing an
empty or unavailable events result; reuse existing pod/event fixtures and story
conventions.

Source: Coding guidelines

frontend/src/components/workload/Details.test.tsx (1)

32-48: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert event propagation in this wiring test.

The mocked DetailsGrid supplies context.events, but the assertions only check pods and errors. A regression that drops context.events from WorkloadDiagnosticsSection would pass. Add a non-empty event fixture and assert that it reaches lastDiagnosticsProps().events.

As per coding guidelines, test files should prefer real implementations and integration tests, and mock dependencies only when necessary.

Also applies to: 162-180

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/workload/Details.test.tsx` around lines 32 - 48,
Update the DetailsGrid wiring test and its fixtures to include a non-empty
events collection in the mocked context, then assert that
lastDiagnosticsProps().events matches that fixture. Preserve the existing pods
and errors assertions, and keep the mock limited to the necessary DetailsGrid
boundary.

Source: Coding guidelines

frontend/src/components/pod/Details.test.tsx (1)

91-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the diagnostics integration observable in tests.

This mock replaces PodDiagnosticsSection with a no-op. The PodDetails tests cannot detect a broken events or onViewLogs prop, or verify container-specific log launching. Capture these props and assert them, or add a focused test that renders the real component. Keep the mock only if real event fetching makes it necessary.

As per coding guidelines, test files should prefer real implementations and integration tests, and mock dependencies only when necessary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/pod/Details.test.tsx` around lines 91 - 94, Remove
the no-op PodDiagnosticsSection mock in the PodDetails tests unless real event
fetching requires it, preferring the real diagnostics integration. If mocking
remains necessary, capture the events and onViewLogs props and add assertions or
a focused test covering their propagation and container-specific log launching.

Source: Coding guidelines

frontend/src/components/common/index.test.ts (1)

73-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Scope internalExports to the source module.

internalExports.includes(key) ignores useObjectEvents wherever key is evaluated. If the export check covers multiple source modules, a future same-named export can bypass validation. Scope the exception to ObjectEventList, or use a per-module allowlist.

Also applies to: 109-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/common/index.test.ts` around lines 73 - 74, Scope the
useObjectEvents exception in internalExports to the ObjectEventList source
module instead of applying it to every key evaluated by the export check. Update
the validation logic around internalExports so same-named exports from other
modules remain subject to normal validation, using a per-module allowlist if
needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/generate-release-issue-body.js:
- Line 72: Update the release issue body generated by the relevant template in
generate-release-issue-body.js to remove the literal ellipsis from the static
server command’s code span, using ./frontend/build as the path while preserving
any continuation punctuation outside the command.

In `@docs/development/plugins/functionality/index.md`:
- Around line 245-265: Update both relation predicates in the plugin
documentation to scope matches by namespace as well as metadata.name, ensuring
Deployment, Secret, and custom-source resources from different namespaces cannot
be related accidentally. Keep the existing name-matching behavior and add
corresponding metadata.namespace comparisons for each endpoint.

In `@docs/development/telemetry.md`:
- Around line 131-161: Update the Prometheus scrape target in
kubernetes-headlamp-monitoring.yaml to use the Headlamp Service port 80, such as
headlamp.kube-system.svc.cluster.local:80 or headlamp:80, and remove the stale
:4466 target. Keep the existing /metrics scrape path and metrics configuration
unchanged.

In `@frontend/src/components/crd/crInstancesKey.ts`:
- Around line 71-83: Update frontend/src/components/crd/crInstancesKey.ts lines
71-83 in the fingerprint/sortKey serialization path to frame every key entry
with its length in both the joined and hashed representations, removing reliance
on an unescaped separator for entry boundaries. Update lines 101-103 to
serialize cluster and id as an unambiguous tuple, such as
JSON.stringify([cluster, id]). Add regression tests covering embedded separators
and delimiter-containing cluster or identifier values.

In `@frontend/src/components/crd/CustomResourceInstancesList.tsx`:
- Around line 200-210: Add translation entries for every new translation|...
string used by CustomResourceInstancesList, CustomResourceList, and
CustomResourceDetails to the locale files under the existing locale structure.
Reuse the exact keys and provide localized labels consistent with each locale’s
conventions so the UI does not display raw key strings.

In `@frontend/src/components/crd/Details.tsx`:
- Line 69: Guard every remaining direct spec access in the Details render path,
including group, version, scope, subresources, and versions, by consistently
handling an absent item.spec. Ensure the SimpleTable at the versions usage
receives a safe fallback collection when versions is undefined, while preserving
the existing fallback behavior for kind and name.

In `@frontend/src/components/diagnostics/Diagnostics.test.ts`:
- Around line 150-152: Update the duplicate-hint assertion in the Diagnostics
test to check for the emitted id `pod-scheduling-event` rather than
`pod-scheduling-condition`, so it verifies that getPendingHints does not produce
the duplicate scheduling hint.

In `@frontend/src/components/pod/Details.tsx`:
- Around line 894-905: Update the diagnostics integration around
PodDiagnosticsSection so its failing-container log action uses the same
AuthVisible get permission gate for the pod log subresource as the header log
action. Pass an authorization-aware onViewLogs callback or apply the gate inside
PodDiagnosticsSection, ensuring users without permission cannot see or open
PodLogViewer while authorized behavior remains unchanged.

In `@frontend/src/components/resourceMap/sources/definitions/relations.test.tsx`:
- Around line 243-249: Add a test fixture in the relations test setup where the
CRD’s makeCRClassOrNull returns null, then invoke useGetAllRelations and assert
it does not throw and produces no CRD owner relation. Keep the existing
complete-CRD coverage unchanged and target the resilience path through
useGetCRToOwnerRelations.

In `@frontend/src/components/resourceMap/sources/definitions/relations.tsx`:
- Around line 404-420: The plugin relation handling must contain indexed
edge-builder failures: in
frontend/src/components/resourceMap/sources/definitions/relations.tsx lines
404-420, wrap plugin buildEdgesWithIndex invocation in try/catch and return an
empty edge list on errors; in frontend/src/plugin/registry.tsx lines 1271-1325,
validate that a defined buildEdgesWithIndex value is a function and reject
registrations that provide any other type.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx`:
- Around line 84-115: The Filled story in
frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx:84-115
must include a valid serviceName under Filled.args.spec so the required form
field is populated and the story remains valid. Regenerate
frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Filled.stories.storyshot:288-336
so the Service Name input reflects the new value.

In `@frontend/src/i18n/locales/hi/translation.json`:
- Line 568: Update the CRD empty-state translation consistently: change the
CustomResourceList translation call to the new key and add localized entries for
it in frontend/src/i18n/locales/hi/translation.json:568,
it/translation.json:573, ja/translation.json:563, ko/translation.json:563,
pt/translation.json:573, ru/translation.json:578, ta/translation.json:568,
ur/translation.json:568, and zh-tw/translation.json:563, then remove the old key
only after every consumer and locale entry has been migrated.

---

Outside diff comments:
In `@frontend/src/components/crd/List.tsx`:
- Around line 53-65: Complete the partial spec guarding in
frontend/src/components/crd/List.tsx lines 53-65 by applying optional handling
to the Group and Scope column accessors. In
frontend/src/components/crd/Details.tsx lines 69-87, guard item.spec.group,
item.spec.version, item.spec.scope, and item.spec.subresources, and pass an
empty array when item.spec.versions is unavailable. Preserve existing rendering
behavior when spec is present.

In `@frontend/src/components/resourceMap/sources/definitions/relations.tsx`:
- Around line 90-114: Update makeOwnerRelation in
frontend/src/components/resourceMap/sources/definitions/relations.tsx lines
90-114 to derive the owner relation ID once and append it to indexed edge IDs
created by buildEdgesWithIndex. Apply the same change to the reversed-owner
relation builder at lines 118-156, ensuring both builders include their relation
ID alongside from.id and to.id.

---

Nitpick comments:
In `@frontend/src/components/common/index.test.ts`:
- Around line 73-74: Scope the useObjectEvents exception in internalExports to
the ObjectEventList source module instead of applying it to every key evaluated
by the export check. Update the validation logic around internalExports so
same-named exports from other modules remain subject to normal validation, using
a per-module allowlist if needed.

In `@frontend/src/components/crd/CustomResourceDetails.test.tsx`:
- Around line 73-75: Remove the vi.mock for Loader from CustomResourceDetails
tests and render the real Loader component so the behavior assertion verifies
the actual loading title is absent from the application output.

In `@frontend/src/components/crd/CustomResourceInstancesList.test.tsx`:
- Around line 189-213: Add a test alongside the existing makeCRClassOrNull null
case in CustomResourceInstancesList.test.tsx using only CRDs whose
makeCRClassOrNull returns null. Render the list and assert that “No
CustomResourceDefinitions with usable specs were found.” is displayed, while the
loading indicator is absent.

In `@frontend/src/components/crd/CustomResourceInstancesList.tsx`:
- Around line 38-48: Harden the remount key used by CrInstancesView so it
changes whenever the number of classified entries changes, preventing a
different count of per-entry crdClass.useList hooks from rendering in the same
component instance. Include classified.length in the key/fingerprint derivation
alongside the existing CRD identity values, preserving the current remount
behavior for membership changes.

In `@frontend/src/components/diagnostics/Diagnostics.stories.tsx`:
- Around line 46-60: Extend the PodTemplate stories with loading and
unavailable-events cases for PodDiagnosticsSection, matching the existing
WorkloadDiagnosticsSection state coverage. Add a story that provides a failing
pod with a non-default container and exercises the onViewLogs action, plus a
story representing an empty or unavailable events result; reuse existing
pod/event fixtures and story conventions.

In `@frontend/src/components/diagnostics/Diagnostics.tsx`:
- Around line 407-418: Move the JSDoc describing aggregated pod findings so it
directly precedes the exported getPodDiagnostics function, and move the JSDoc
describing the diagnostics section so it directly precedes the exported
PodDiagnosticsSection function. Remove both misplaced comment blocks from above
diagnosisHint and getFailingContainerName without changing their text or
function behavior.

In `@frontend/src/components/pod/Details.test.tsx`:
- Around line 91-94: Remove the no-op PodDiagnosticsSection mock in the
PodDetails tests unless real event fetching requires it, preferring the real
diagnostics integration. If mocking remains necessary, capture the events and
onViewLogs props and add assertions or a focused test covering their propagation
and container-specific log launching.

In
`@frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Empty.stories.storyshot`:
- Around line 341-404: Update the conditional rendering around the Partition
field in CreateStatefulSetForm so it renders only when spec.updateStrategy.type
equals "RollingUpdate"; keep it hidden for undefined and "OnDelete" strategies.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx`:
- Around line 117-139: Update the StatefulSet story using the OnDelete strategy
to include a rollingUpdate object with partition set to 3, so
CreateStatefulSetForm’s cleanup effect executes the partition-removal path.
Preserve the existing OnDelete resource configuration and story purpose.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.tsx`:
- Line 55: Replace structuredClone in the draft update flow with the shared
_.cloneDeep utility, keeping the existing resource cloning behavior and update
logic unchanged.

In `@frontend/src/components/workload/Details.test.tsx`:
- Around line 32-48: Update the DetailsGrid wiring test and its fixtures to
include a non-empty events collection in the mocked context, then assert that
lastDiagnosticsProps().events matches that fixture. Preserve the existing pods
and errors assertions, and keep the mock limited to the necessary DetailsGrid
boundary.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4786ef9e-b906-499e-b2af-80cd78143e98

📥 Commits

Reviewing files that changed from the base of the PR and between fb30e36 and 95acce7.

⛔ Files ignored due to path filters (1)
  • docs/development/plugins/images/resource-relation-provider.png is excluded by !**/*.png
📒 Files selected for processing (90)
  • .github/scripts/generate-release-issue-body.js
  • app/electron/tray.ts
  • backend/pkg/clusterinventory/clusterinventory_fuzz_test.go
  • docs/development/backend.md
  • docs/development/index.md
  • docs/development/plugins/functionality/index.md
  • docs/development/telemetry.md
  • frontend/src/components/App/icons.ts
  • frontend/src/components/Sidebar/useSidebarItems.tsx
  • frontend/src/components/common/ObjectEventList.tsx
  • frontend/src/components/common/Resource/CreateButton.tsx
  • frontend/src/components/common/Resource/Resource.tsx
  • frontend/src/components/common/Resource/index.tsx
  • frontend/src/components/common/index.test.ts
  • frontend/src/components/common/index.ts
  • frontend/src/components/crd/CustomResourceDetails.test.tsx
  • frontend/src/components/crd/CustomResourceDetails.tsx
  • frontend/src/components/crd/CustomResourceInstancesList.test.tsx
  • frontend/src/components/crd/CustomResourceInstancesList.tsx
  • frontend/src/components/crd/CustomResourceList.tsx
  • frontend/src/components/crd/Details.tsx
  • frontend/src/components/crd/List.tsx
  • frontend/src/components/crd/crInstancesKey.test.ts
  • frontend/src/components/crd/crInstancesKey.ts
  • frontend/src/components/diagnostics/Diagnostics.stories.tsx
  • frontend/src/components/diagnostics/Diagnostics.test.ts
  • frontend/src/components/diagnostics/Diagnostics.tsx
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.PodHealthy.stories.storyshot
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.PodWithIssues.stories.storyshot
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.WorkloadHealthy.stories.storyshot
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.WorkloadLoading.stories.storyshot
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.WorkloadPodsError.stories.storyshot
  • frontend/src/components/diagnostics/__snapshots__/Diagnostics.WorkloadWithIssues.stories.storyshot
  • frontend/src/components/diagnostics/storyHelper.ts
  • frontend/src/components/globalSearch/useLocalStorageState.test.tsx
  • frontend/src/components/globalSearch/useLocalStorageState.tsx
  • frontend/src/components/pod/Details.test.tsx
  • frontend/src/components/pod/Details.tsx
  • frontend/src/components/pod/__snapshots__/PodDetails.DebugDisabled.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.Error.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.Initializing.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.LivenessFailed.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.PullBackOff.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.Running.stories.storyshot
  • frontend/src/components/pod/__snapshots__/PodDetails.Successful.stories.storyshot
  • frontend/src/components/resourceMap/edges/GraphEdgeComponent.tsx
  • frontend/src/components/resourceMap/graph/graphModel.tsx
  • frontend/src/components/resourceMap/graphViewSlice.test.tsx
  • frontend/src/components/resourceMap/graphViewSlice.tsx
  • frontend/src/components/resourceMap/sources/GraphSources.test.tsx
  • frontend/src/components/resourceMap/sources/GraphSources.tsx
  • frontend/src/components/resourceMap/sources/definitions/relationIds.ts
  • frontend/src/components/resourceMap/sources/definitions/relations.test.tsx
  • frontend/src/components/resourceMap/sources/definitions/relations.tsx
  • frontend/src/components/resourceMap/sources/definitions/sources.test.tsx
  • frontend/src/components/resourceMap/sources/definitions/sources.tsx
  • frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx
  • frontend/src/components/statefulset/CreateStatefulSetForm.tsx
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Default.stories.storyshot
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Empty.stories.storyshot
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Filled.stories.storyshot
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.OnDeleteStrategy.stories.storyshot
  • frontend/src/components/workload/Details.test.tsx
  • frontend/src/components/workload/Details.tsx
  • frontend/src/i18n/locales/ar/translation.json
  • frontend/src/i18n/locales/bn/translation.json
  • frontend/src/i18n/locales/de/translation.json
  • frontend/src/i18n/locales/en/translation.json
  • frontend/src/i18n/locales/es/translation.json
  • frontend/src/i18n/locales/fr/translation.json
  • frontend/src/i18n/locales/he/translation.json
  • frontend/src/i18n/locales/hi/translation.json
  • frontend/src/i18n/locales/it/translation.json
  • frontend/src/i18n/locales/ja/translation.json
  • frontend/src/i18n/locales/ko/translation.json
  • frontend/src/i18n/locales/pt/translation.json
  • frontend/src/i18n/locales/ru/translation.json
  • frontend/src/i18n/locales/ta/translation.json
  • frontend/src/i18n/locales/ur/translation.json
  • frontend/src/i18n/locales/zh-tw/translation.json
  • frontend/src/i18n/locales/zh/translation.json
  • frontend/src/lib/k8s/KubeObject.test.ts
  • frontend/src/lib/k8s/crd.test.ts
  • frontend/src/lib/k8s/crd.ts
  • frontend/src/lib/k8s/crdSpec.ts
  • frontend/src/plugin/__snapshots__/pluginLib.snapshot
  • frontend/src/plugin/registry.test.ts
  • frontend/src/plugin/registry.tsx
  • plugins/examples/customizing-map/src/index.tsx
  • plugins/headlamp-plugin/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/globalSearch/useLocalStorageState.tsx
  • frontend/src/components/globalSearch/useLocalStorageState.test.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

🛑 Comments failed to post (12)
.github/scripts/generate-release-issue-body.js (1)

72-72: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the literal ellipsis from the command path.

The code span contains ./frontend/build.... A release tester who copies the command passes an invalid or unintended path. Use ./frontend/build, or place the ellipsis outside the code span.

Proposed documentation fix
-  - Run the app in static server mode (`./backend/headlamp-server -html-static-dir ./frontend/build...`)
+  - Run the app in static server mode (`./backend/headlamp-server -html-static-dir ./frontend/build`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  - Run the app in static server mode (\`./backend/headlamp-server -html-static-dir ./frontend/build\`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/generate-release-issue-body.js at line 72, Update the
release issue body generated by the relevant template in
generate-release-issue-body.js to remove the literal ellipsis from the static
server command’s code span, using ./frontend/build as the path while preserving
any continuation punctuation outside the command.
docs/development/plugins/functionality/index.md (1)

245-265: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope relation predicates to the namespace.

The examples match names only. metadata.name is not unique for namespaced Deployments and Secrets. The predicate can connect unrelated objects with the same names in different namespaces. Compare metadata.namespace too, or use stable resource IDs.

Proposed fix for the Deployment and Secret example
     return (
       from.kubeObject?.jsonData.metadata.name === "my-deployment" &&
-      to.kubeObject?.jsonData.metadata.name === "my-secret"
+      to.kubeObject?.jsonData.metadata.name === "my-secret" &&
+      from.kubeObject?.jsonData.metadata.namespace ===
+        to.kubeObject?.jsonData.metadata.namespace
     );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/development/plugins/functionality/index.md` around lines 245 - 265,
Update both relation predicates in the plugin documentation to scope matches by
namespace as well as metadata.name, ensuring Deployment, Secret, and
custom-source resources from different namespaces cannot be related
accidentally. Keep the existing name-matching behavior and add corresponding
metadata.namespace comparisons for each endpoint.
docs/development/telemetry.md (1)

131-161: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a -e yaml -e yml 'kubernetes-headlamp|prometheus' .
rg -n -C 4 'headlamp.*(:80|:4466)|targetPort|/metrics' \
  --glob '*.yaml' --glob '*.yml' .

Repository: kubernetes-sigs/headlamp

Length of output: 41890


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- manifests namespace/header ---\n'
sed -n '1,60p' kubernetes-headlamp.yaml kubernetes-headlamp-monitoring.yaml 2>/dev/null || true

printf '\n--- Prometheus scrape target section ---\n'
sed -n '140,160p' kubernetes-headlamp-monitoring.yaml 2>/dev/null || true

printf '\n--- Service selectors in manifests ---\n'
sed -n '1,20p' kubernetes-headlamp.yaml 2>/dev/null || true

printf '\n--- Deployments / metrics listener config ---\n'
rg -n -C 2 'kind: Deployment|HEADLAMP_CONFIG_|listen|port: 4466|4466' kubernetes-headlamp.yaml kubernetes-headlamp-monitoring.yaml 2>/dev/null || true

Repository: kubernetes-sigs/headlamp

Length of output: 253


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- manifests namespace/header ---'
sed -n '1,60p' kubernetes-headlamp.yaml
sed -n '1,60p' kubernetes-headlamp-monitoring.yaml

printf '%s\n' ''
printf '%s\n' '--- Prometheus scrape target section ---'
sed -n '140,160p' kubernetes-headlamp-monitoring.yaml

printf '%s\n' ''
printf '%s\n' '--- Service selectors in manifests ---'
sed -n '1,20p' kubernetes-headlamp.yaml

printf '%s\n' ''
printf '%s\n' '--- Deployments / metrics listener config ---'
rg -n -C 2 'kind: Deployment|HEADLAMP_CONFIG_|listen|port: 4466|4466' kubernetes-headlamp.yaml kubernetes-headlamp-monitoring.yaml

Repository: kubernetes-sigs/headlamp

Length of output: 6938


🌐 Web query:

Headlamp source HEADLAMP_CONFIG_METRICS_ENABLED metrics endpoint

💡 Result:

The HEADLAMP_CONFIG_METRICS_ENABLED environment variable is a configuration flag used in Headlamp to control the exposure of an internal Prometheus metrics endpoint for the Headlamp backend [1][2]. When this variable is set to true, Headlamp exposes metrics in Prometheus format at the /metrics endpoint [1][3][2]. Key details regarding this configuration include: Integration: Enabling this endpoint allows monitoring systems (like Prometheus) to scrape metrics from the Headlamp backend [2][4]. This is commonly achieved in Kubernetes environments by using a ServiceMonitor or similar discovery mechanism to target the metrics port (typically 9090) on the Headlamp service [5][2][6]. Configuration: It is set as an environment variable in the Headlamp deployment [5][6]. For example: - name: HEADLAMP_CONFIG_METRICS_ENABLED value: "true" Distinction from Resource Metrics: It is important to distinguish this backend metrics endpoint from the cluster resource metrics (CPU/memory usage for pods and nodes) displayed within the Headlamp UI [7]. Headlamp's UI resource metrics rely on the Kubernetes metrics-server being installed in the cluster, whereas HEADLAMP_CONFIG_METRICS_ENABLED specifically enables telemetry about the Headlamp application itself [1][7]. Do not confuse this with the Prometheus plugin, which is a separate extension that allows Headlamp to query and visualize data from an existing Prometheus instance within the cluster [8].

Citations:


🌐 Web query:

Headlamp kubernetes-headlamp.yaml HEADLAMP_CONFIG_METRICS_ENABLED metrics endpoint port

💡 Result:

When enabling metrics in Headlamp by setting the HEADLAMP_CONFIG_METRICS_ENABLED environment variable to true, the metrics are exposed on port 9090 [1][2][3]. In the official Headlamp example kubernetes-headlamp.yaml file, the container is explicitly configured with two ports: 1. Port 4466, which serves the primary Headlamp application [1][3]. 2. Port 9090, which is named metrics and corresponds to the Prometheus metrics endpoint [1][3]. The application code further confirms this behavior, as setting HEADLAMP_CONFIG_METRICS_ENABLED to true registers a handler on the /metrics endpoint [2][4].

Citations:


Correct the Headlamp metrics scrape target in kubernetes-headlamp-monitoring.yaml.

HEADLAMP_CONFIG_METRICS_ENABLED routes /metrics over the main HTTP port 4466 in these manifests, not the HEADLAMP_CONFIG_METRICS_PORT path. Keep Prometheus scraping headlamp.kube-system.svc.cluster.local:80 or headlamp:80, and remove the stale :4466 target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/development/telemetry.md` around lines 131 - 161, Update the Prometheus
scrape target in kubernetes-headlamp-monitoring.yaml to use the Headlamp Service
port 80, such as headlamp.kube-system.svc.cluster.local:80 or headlamp:80, and
remove the stale :4466 target. Keep the existing /metrics scrape path and
metrics configuration unchanged.
frontend/src/components/crd/crInstancesKey.ts (1)

71-83: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use unambiguous serialization for CRD identity keys.

Both paths concatenate strings with delimiters that the typed inputs can contain. For example, fingerprint(['a\x1fb', 'c']) and fingerprint(['a', 'b\x1fc']) produce the same joined and hashed stream. A sortKey() collision can also make a changed CRD set retain the same remount key. This can preserve hook state for the wrong CRD entry.

  • frontend/src/components/crd/crInstancesKey.ts#L71-L83: Frame every entry with its length in both the join and hash paths. Do not use an unescaped separator as entry framing.
  • frontend/src/components/crd/crInstancesKey.ts#L101-L103: Serialize cluster and id as an unambiguous tuple, such as JSON.stringify([cluster, id]).
  • Add regression tests with embedded separators and delimiter-containing cluster or identifier values.
📍 Affects 1 file
  • frontend/src/components/crd/crInstancesKey.ts#L71-L83 (this comment)
  • frontend/src/components/crd/crInstancesKey.ts#L101-L103
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/crInstancesKey.ts` around lines 71 - 83, Update
frontend/src/components/crd/crInstancesKey.ts lines 71-83 in the
fingerprint/sortKey serialization path to frame every key entry with its length
in both the joined and hashed representations, removing reliance on an unescaped
separator for entry boundaries. Update lines 101-103 to serialize cluster and id
as an unambiguous tuple, such as JSON.stringify([cluster, id]). Add regression
tests covering embedded separators and delimiter-containing cluster or
identifier values.
frontend/src/components/crd/CustomResourceInstancesList.tsx (1)

200-210: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the new i18n keys are registered in the locale files.
fd -g 'translation.json' frontend/src/i18n/locales | while IFS= read -r f; do
  echo "== $f"
  rg -n 'usable specs|incomplete spec' "$f" || echo "  (missing)"
done

Repository: kubernetes-sigs/headlamp

Length of output: 1202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
fd -a 'CustomResource(InstancesList|List|Details)\.tsx$|translation\.json$' frontend/src | sed 's#^\./##'

echo
echo "== i18n usages in PR files =="
for f in \
  frontend/src/components/crd/CustomResourceInstancesList.tsx \
  frontend/src/components/crd/CustomResourceList.tsx \
  frontend/src/components/crd/CustomResourceDetails.tsx
do
  if [ -f "$f" ]; then
    echo "-- $f --"
    rg -n "t\\('translation\\|" "$f" || true
  fi
done

echo
echo "== translation key definitions across translated locale files =="
python3 - <<'PY'
import json
from pathlib import Path

for root in sorted(Path('frontend/src/i18n/locales').glob('*')):
    path = root / 'translation.json'
    if not path.exists():
        continue
    try:
        data = json.loads(path.read_text())
    except Exception as e:
        print(f"== {path} parse error: {e}")
        continue
    needles = [
        'No CustomResourceDefinitions with usable specs were found.',
        'This CustomResourceDefinition has an incomplete spec.',
    ]
    hits = [k for k in data if k in needles]
    print(f"== {path} ==")
    if hits:
        print(json.dumps({"translation_keys": {k: data[k] for k in hits}}, ensure_ascii=False, indent=2))
    else:
        print("(missing)")
PY

Repository: kubernetes-sigs/headlamp

Length of output: 4066


Add the new translation keys to the locale files.

CustomResourceInstancesList.tsx, CustomResourceList.tsx, and CustomResourceDetails.tsx use translation|... strings, but the locale files under frontend/src/i18n/locales do not define these keys. Add the missing entries, or UI text will render the key string instead of the localized label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/CustomResourceInstancesList.tsx` around lines 200
- 210, Add translation entries for every new translation|... string used by
CustomResourceInstancesList, CustomResourceList, and CustomResourceDetails to
the locale files under the existing locale structure. Reuse the exact keys and
provide localized labels consistent with each locale’s conventions so the UI
does not display raw key strings.
frontend/src/components/crd/Details.tsx (1)

69-69: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the remaining spec reads in this file.

Lines 69 and 87 now treat item.spec as possibly undefined. Other reads in the same render path still dereference it directly:

  • Line 44: item.spec.group
  • Line 48: item.spec.version
  • Line 52: item.spec.scope
  • Lines 56-57: item.spec.subresources
  • Line 116: data={item.spec.versions}

If spec is absent, this component still throws, so the new optional chaining does not prevent the crash. Line 116 also breaks when versions is absent, because SimpleTable receives undefined. Apply the same optional handling to those reads.

🛡️ Proposed fix
-            value: item.spec.group,
+            value: item.spec?.group,
-            value: item.spec.version,
+            value: item.spec?.version,
-            value: item.spec.scope,
+            value: item.spec?.scope,
-            value: item.spec.subresources && Object.keys(item.spec.subresources).join(' & '),
-            hide: !item.spec.subresources,
+            value: item.spec?.subresources && Object.keys(item.spec.subresources).join(' & '),
+            hide: !item.spec?.subresources,
-                  data={item.spec.versions}
+                  data={item.spec?.versions ?? []}

Also applies to: 87-87

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/crd/Details.tsx` at line 69, Guard every remaining
direct spec access in the Details render path, including group, version, scope,
subresources, and versions, by consistently handling an absent item.spec. Ensure
the SimpleTable at the versions usage receives a safe fallback collection when
versions is undefined, while preserving the existing fallback behavior for kind
and name.
frontend/src/components/diagnostics/Diagnostics.test.ts (1)

150-152: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert on the id that the implementation can emit.

getPendingHints emits the id pod-scheduling-event (Diagnostics.tsx line 326). No code path emits pod-scheduling-condition, so this assertion passes unconditionally and does not detect a duplicate scheduling hint.

💚 Proposed fix
     // The failing PodScheduled condition must not also produce a duplicate
     // scheduling hint from getPendingHints.
-    expect(diagnostics.some(item => item.id === 'pod-scheduling-condition')).toBe(false);
+    expect(diagnostics.some(item => item.id === 'pod-scheduling-event')).toBe(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    // The failing PodScheduled condition must not also produce a duplicate
    // scheduling hint from getPendingHints.
    expect(diagnostics.some(item => item.id === 'pod-scheduling-event')).toBe(false);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/diagnostics/Diagnostics.test.ts` around lines 150 -
152, Update the duplicate-hint assertion in the Diagnostics test to check for
the emitted id `pod-scheduling-event` rather than `pod-scheduling-condition`, so
it verifies that getPendingHints does not produce the duplicate scheduling hint.
frontend/src/components/pod/Details.tsx (1)

894-905: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Apply the existing log permission gate to diagnostics.

The header log action uses AuthVisible for get on the log subresource. This callback is passed to PodDiagnosticsSection for every pod, so a user without that permission can still see the failing-container log button and open PodLogViewer. The backend should still reject the request, so this is not an authorization bypass. It is a broken unauthorized-user flow. Pass an authorization-aware callback or gate the action inside PodDiagnosticsSection.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/pod/Details.tsx` around lines 894 - 905, Update the
diagnostics integration around PodDiagnosticsSection so its failing-container
log action uses the same AuthVisible get permission gate for the pod log
subresource as the header log action. Pass an authorization-aware onViewLogs
callback or apply the gate inside PodDiagnosticsSection, ensuring users without
permission cannot see or open PodLogViewer while authorized behavior remains
unchanged.
frontend/src/components/resourceMap/sources/definitions/relations.test.tsx (1)

243-249: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add coverage for an incomplete CRD.

Add a fixture where makeCRClassOrNull() returns null. Assert that useGetAllRelations() does not throw and does not add a CRD owner relation. This covers the new resilience path in useGetCRToOwnerRelations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/resourceMap/sources/definitions/relations.test.tsx`
around lines 243 - 249, Add a test fixture in the relations test setup where the
CRD’s makeCRClassOrNull returns null, then invoke useGetAllRelations and assert
it does not throw and produces no CRD owner relation. Keep the existing
complete-CRD coverage unchanged and target the resilience path through
useGetCRToOwnerRelations.

Source: Coding guidelines

frontend/src/components/resourceMap/sources/definitions/relations.tsx (1)

404-420: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Contain plugin indexed edge-builder failures.

safePluginRelations catches errors from predicate, but GraphSourceManager invokes buildEdgesWithIndex directly. A plugin can register a throwing edge builder, or a truthy non-function value, and crash Resource Map rendering.

  • frontend/src/components/resourceMap/sources/definitions/relations.tsx#L404-L420: wrap plugin buildEdgesWithIndex in try/catch and return an empty edge list on failure.
  • frontend/src/plugin/registry.tsx#L1271-L1325: reject buildEdgesWithIndex when it is defined but not a function.
📍 Affects 2 files
  • frontend/src/components/resourceMap/sources/definitions/relations.tsx#L404-L420 (this comment)
  • frontend/src/plugin/registry.tsx#L1271-L1325
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/resourceMap/sources/definitions/relations.tsx` around
lines 404 - 420, The plugin relation handling must contain indexed edge-builder
failures: in
frontend/src/components/resourceMap/sources/definitions/relations.tsx lines
404-420, wrap plugin buildEdgesWithIndex invocation in try/catch and return an
empty edge list on errors; in frontend/src/plugin/registry.tsx lines 1271-1325,
validate that a defined buildEdgesWithIndex value is a function and reject
registrations that provide any other type.
frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx (1)

84-115: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The Filled story omits the required spec.serviceName. The form marks spec.serviceName as required, so the story renders an empty required field and reports valid === false, which contradicts its stated purpose.

  • frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx#L84-L115: add serviceName to Filled.args.spec.
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Filled.stories.storyshot#L288-L336: regenerate this snapshot so the Service Name input holds the new value.
📍 Affects 2 files
  • frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx#L84-L115 (this comment)
  • frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Filled.stories.storyshot#L288-L336
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx` around
lines 84 - 115, The Filled story in
frontend/src/components/statefulset/CreateStatefulSetForm.stories.tsx:84-115
must include a valid serviceName under Filled.args.spec so the required form
field is populated and the story remains valid. Regenerate
frontend/src/components/statefulset/__snapshots__/CreateStatefulSetForm.Filled.stories.storyshot:288-336
so the Service Name input reflects the new value.
frontend/src/i18n/locales/hi/translation.json (1)

568-568: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

old_key='No custom resources found'
new_key='No CustomResourceDefinitions with usable specs were found.'

printf '%s\n' "Old-key usages outside locale files:"
rg -n -F "$old_key" frontend --glob '!frontend/src/i18n/locales/**' || true

printf '%s\n' "New-key usages outside locale files:"
rg -n -F "$new_key" frontend --glob '!frontend/src/i18n/locales/**' || true

Repository: kubernetes-sigs/headlamp

Length of output: 363


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' "Relevant source around CustomResourceList:"
sed -n '150,190p' frontend/src/components/crd/CustomResourceList.tsx

printf '%s\n' "Locale entries for both keys:"
for f in frontend/src/i18n/locales/*/translation.json; do
  echo "--- $f"
  rg -n 'No custom.resources.found|No CustomResourceDefinitions with usable specs were found' "$f" || true
done

Repository: kubernetes-sigs/headlamp

Length of output: 3286


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Extract translation keys referenced and present in each affected locale outside locale files.
python3 - <<'PY'
from pathlib import Path
import json, re

root = Path("frontend/src/i18n/locales")
files = sorted(root.glob("*/translation.json"))
patterns = [
    r"translation\|\s*(['\"])([^\1]+?)\1",
    r"i18next\.t\(\s*(['\"])translation\|([^\1]+?)\1",
]

for file in files:
    data = json.loads(file.read_text())
    text = file.read_text()
    missing = []
    for pat in patterns:
        for m in re.finditer(pat, text):
            key = m.group(2)
            if key in data and key.startswith("No CustomResourceDefinitions"):
                print(f"{file}:{key}:present={key in data}")
                break
        else:
            continue
        break
    else:
        # No translation| No Custom..., report missing if old-like key in data as context.
        keys = set(data)
        if "translation|No CustomResourceDefinitions with usable specs were found." not in keys:
            print(f"{file}:no-new-key:missing=True")
PY

Repository: kubernetes-sigs/headlamp

Length of output: 1355


Update the current CRD translation call before replacing the key.

frontend/src/components/crd/CustomResourceList.tsx:175 still uses translation|No custom resources found as the only app-side consumer, and the new key is not present in the locale files. Either keep the existing key with localized values, or update the source call plus every locale entry with the new key before removing the old one.

📍 Affects 9 files
  • frontend/src/i18n/locales/hi/translation.json#L568-L568 (this comment)
  • frontend/src/i18n/locales/it/translation.json#L573-L573
  • frontend/src/i18n/locales/ja/translation.json#L563-L563
  • frontend/src/i18n/locales/ko/translation.json#L563-L563
  • frontend/src/i18n/locales/pt/translation.json#L573-L573
  • frontend/src/i18n/locales/ru/translation.json#L578-L578
  • frontend/src/i18n/locales/ta/translation.json#L568-L568
  • frontend/src/i18n/locales/ur/translation.json#L568-L568
  • frontend/src/i18n/locales/zh-tw/translation.json#L563-L563
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/i18n/locales/hi/translation.json` at line 568, Update the CRD
empty-state translation consistently: change the CustomResourceList translation
call to the new key and add localized entries for it in
frontend/src/i18n/locales/hi/translation.json:568, it/translation.json:573,
ja/translation.json:563, ko/translation.json:563, pt/translation.json:573,
ru/translation.json:578, ta/translation.json:568, ur/translation.json:568, and
zh-tw/translation.json:563, then remove the old key only after every consumer
and locale entry has been migrated.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.tsx:115

  • The PR description includes an auto-generated “Summary by CodeRabbit” section listing unrelated features (pods/workload diagnostics, StatefulSet creation support, telemetry docs, etc.) that are not part of this diff. This makes the PR description misleading for reviewers/release notes; please remove or replace that section so it reflects only this hook fix.
export const useLocalStorageState = Object.assign(useLocalStorageStateBase, {
  /**
   * Update the value in local storage and notify all `useLocalStorageState` hooks.
   *
   * @param key - local storage key

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:106

  • The cross-component regression claimed by the PR is not demonstrated here: in the pre-change implementation the listener called set(() => newValue), so its updater ignored the captured state and this test would also produce 42 after the forced rerender. The actual failing test is the consecutive functional-setter case above, while the Global Search caller also benefits from update() now persisting when no subscriber is mounted. Please align the title/linked-issue explanation with those concrete bugs, or add a listener-path test that fails against the old implementation before claiming the stale listener is fixed.
  it('propagates updates via useLocalStorageState.update across hook instances', () => {
    const { result: a } = renderHook(() => useLocalStorageState(TEST_KEY, 0));
    const { result: b } = renderHook(() => useLocalStorageState(TEST_KEY, 0));

    // Force a re-render of one hook instance before update() fires.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.tsx:139

  • The current diff no longer implements the stale-closure fix described in the PR: the base hook already uses a shared StorageEntry with useSyncExternalStore, and production callers now use the synchronized setter. This change instead reintroduces a new .update public member with no non-test call sites, while the PR body still claims stateRef/useEffect changes and “no public API” change. Please either retitle and rewrite the PR to justify restoring this API, or remove the now-obsolete addition.
export const useLocalStorageState = Object.assign(useLocalStorageStateBase, {

@kubernetes-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: vishnukothakapu
Once this PR has been reviewed and has the lgtm label, please ask for approval from illume. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@vishnukothakapu

vishnukothakapu commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Hi @illume ,

I wanted to leave a quick note explaining the recent force-push and rewrite of this PR's description.

As you might have noticed, the original stale-closure bug that this PR was opened to fix 2 months ago was recently resolved in main (via the migration to useSyncExternalStore in commit 676ef52e7).

Since I had put a lot of work and gone through many review cycles on this PR over the last two months, I didn't want all that effort to go to waste! Instead of closing this, I pivoted the PR. I kept the .update() utility method we developed here, and I put it to work fixing a genuine cross-component synchronization bug I found in LogsButton.tsx (it was previously using raw localStorage.setItem, which bypassed the hook and broke synchronization).

The PR is now fully updated, tested, and ready for review under its new purpose!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Suppressed comments (3)

frontend/src/components/common/Resource/LogsButton.tsx:574

  • This only broadcasts changes made by LogsButton; its selectedSeverities is still a useState initialized once from storage, so an update from another tab or hook never updates an already-open LogsButton. Cross-tab severity synchronization therefore remains one-way. Back this state with useLocalStorageState (while preserving validation) or subscribe it to external updates.
              setSelectedSeverities(value);
              useLocalStorageState.update('headlamp.logs.severityFilter', value);

frontend/src/components/globalSearch/useLocalStorageState.tsx:36

  • StorageEvent.newValue is null for removeItem, and key is null for clear; both cases are ignored here, so mounted hooks keep stale values after another tab resets storage. Please retain each entry's default value and notify affected subscribers with that default for removals and clears.
    if (event.key === null) {
      return;
    }
    const entry = storageEntries.get(event.key);
    if (entry && event.newValue !== null) {

frontend/src/components/globalSearch/useLocalStorageState.test.tsx:283

  • These tests call .update() directly, so they do not cover the production regression in LogsButton; reverting that component to raw localStorage.setItem would leave every added test passing. Add a LogsButton regression test that changes the severity control and verifies a hook consumer receives the update.
    it('propagates updates via useLocalStorageState.update across hook instances', () => {
      const { result: a } = renderHook(() => useLocalStorageState(storageKey, 0));
      const { result: b } = renderHook(() => useLocalStorageState(storageKey, 0));

      act(() => {

@illume illume left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for these changes.

Can you please have a look at the git commits to see if they meet the contribution guidelines? We use a Linux kernel style of git commits. See the contributing guide for general context, and please see previous git commits with git log for examples.

Commits that need attention
  • Address Copilot review feedback — Missing area: description prefix — e.g. frontend: HomeButton: Fix so it navigates to home or backend: config: Add enable-dynamic-clusters flag.
Commit guidelines
  • Use atomic commits focused on a single change.
  • Use the title format <area>: <Description of changes> — description must start with a capital letter.
  • Keep the title under 72 characters (soft requirement).
  • Explain the intention and why the change is needed.
  • Make commit titles meaningful and describe what changed.
  • Do not add code that a later commit rewrites; squash or reorder commits instead.
  • Do not include Fixes #NN in commit messages.

Good examples:

  • frontend: HomeButton: Fix so it navigates to home
  • backend: config: Add enable-dynamic-clusters flag

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (3)

frontend/src/components/common/Resource/LogsButton.tsx:117

  • The second commit, Address Copilot review feedback, significantly rewrites the first commit's LogsButton integration and adds omitted remove/clear handling, while its title does not describe those changes. Please squash it into the first commit or rewrite the commits into coherent <area>: <description> units so the PR history presents the final design directly.
  const [storedSeverities, setStoredSeverities] = useLocalStorageState<LogSeverity[]>(
    'headlamp.logs.severityFilter',
    [...ALL_SEVERITIES]
  );

frontend/src/components/common/Resource/LogsButton.tsx:569

  • The PR description says LogsButton was refactored to use the new useLocalStorageState.update() utility, but this path now uses the normal hook setter, and the utility has no production caller. Please align the description and scope with the final implementation—either remove the unused public utility and its dedicated tests, or document the separate use case that requires it.
              setStoredSeverities(value);

frontend/src/components/globalSearch/useLocalStorageState.tsx:35

  • The global storage listener also receives sessionStorage events. Without checking event.storageArea, a same-named session-storage update can overwrite this hook's local-storage state, and sessionStorage.clear() (key === null) resets every active entry. Ignore events whose storage area is not localStorage, and cover that case in the synchronization tests.
  window.addEventListener('storage', (event: StorageEvent) => {
    if (event.key === null) {
      // localStorage.clear() was called. Reset all entries to their default values.
      for (const entry of storageEntries.values()) {

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.tsx:180

  • The PR says LogsButton was refactored to use this new .update() API, but the component actually uses the hook setter, and repository search finds no caller of .update(). This leaves an untested public API that is unnecessary for the synchronization fix. Please either remove this export and correct the description, or integrate and test it where it is genuinely needed.
export const useLocalStorageState = Object.assign(useLocalStorageStateBase, {
  /**
   * Update the value in local storage and notify all `useLocalStorageState` hooks.
   *
   * @param key - local storage key

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.tsx:45

  • After useSyncExternalStore replays a subscription (as React StrictMode does), the existing cleanup removes this hook's entry from storageEntries, but the next subscribe only re-adds the listener to the captured entry and never restores the map entry. This lookup then returns undefined, so mounted hooks under StrictMode—such as useGraphViewport inside GraphView's StrictMode boundary—will not receive the new cross-tab events. Please make subscribe restore the map entry (and make cleanup delete it only when the map still points to that same entry), with a StrictMode regression test.
    const entry = storageEntries.get(event.key);

@vishnukothakapu

vishnukothakapu commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

@illume Just an update: the latest Copilot reviews have generated no new comments. The PR is fully up-to-date and ready for your review whenever you have the time!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

frontend/src/components/globalSearch/useLocalStorageState.tsx:49

  • Please add a regression test for the new removeItem path. The cross-tab tests cover updates and clear(), but not a matching storage event with newValue === null, so the default-value fallback in this branch can regress unnoticed.
      if (event.newValue === null) {
        // localStorage.removeItem() was called for this key.
        entry.value = JSON.parse(entry.serializedDefaultValue);

This commit brings several necessary improvements to useLocalStorageState
and uses them to fix a bug in LogsButton.

1. Added cross-tab synchronization: The hook now listens to the browser's
'storage' event, ensuring that changes made in one tab immediately update
the state in all other tabs, including correctly falling back to default
values on removeItem or clear.

2. Added direct-value setters: The setState function now accepts both
updater functions and direct values (T | (oldValue: T) => T) to improve DX.

3. Fixed LogsButton sync bug: Refactored LogsButton to fully use the
useLocalStorageState hook instead of raw localStorage, adding robust two-way
sync for the log severity filter so that it stays perfectly in sync across
the entire application and multiple tabs.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

.github/workflows/app.yml:87

  • This installs the app dependencies twice: npm ci --force runs here, then make app-build invokes the new npm ci command in Makefile:86. Because npm ci removes the existing node_modules before reinstalling, the first install is discarded for every Windows matrix job. Let app-build perform the single clean install.
        cd app
        npm ci --force
        cd ..
        make app-build

Makefile:86

  • This app build-policy change is unrelated to the local-storage/log synchronization described by the PR, and the accompanying workflow modification touches a manual-review-only area without any rationale in the description. Please revert these two app-build changes or move them to a focused PR that explains and validates the intended CI change.
	cd app && npm ci && node ./scripts/setup-plugins.js && npm run build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. e2e-tests End to end tests frontend Issues related to the frontend kind/bug Categorizes issue or PR as related to a bug. search To do with searching. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants