Skip to content

feat(dis-console-ui): fleet console web UI over the dis-console API - #3868

Merged
sduranc merged 3 commits into
mainfrom
feat/dis-console-ui-releases
Jul 31, 2026
Merged

feat(dis-console-ui): fleet console web UI over the dis-console API#3868
sduranc merged 3 commits into
mainfrom
feat/dis-console-ui-releases

Conversation

@sduranc

@sduranc sduranc commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Adds dis-console-ui: a read-only web console over the dis-console fleet API. React 19 + Vite + TypeScript + Designsystemet, built and served with Bun (a small BFF serves the static build and proxies /api same-origin).

What it shows

  • Releases per app and environment, built from the fleet API's status-event history: one row per release, stage chips per environment, git commit links (direct for git revisions, via source origin annotations for OCI digests, via the owning Kustomization for Helm chart versions), and per-release container images in a drawer.
  • Syncroot pages: everything a syncroot deploys (appliedBy closure), workloads with cross-environment image drift, a pan/zoom dependency map, releases scoped to the syncroot, ApplicationIdentities, and Grafana links.
  • DIS resources by product family with Azure Portal deep links.
  • Provenance everywhere: any opened resource names its applier, controller owner, and DIS parent — clickable.

Model

An app is every HelmRelease, every Kustomization that is not an azapi root, and every workload applied directly by a root (release identity = primary container image tag). HelmReleases and workloads applied by an app fold into its row.

Modes

Mock data bundled for standalone demos; the mode is a runtime decision — the BFF serves /config.js from its environment, live whenever DIS_CONSOLE_API is set. One image serves all modes.

CI + deploy

  • Lint/typecheck/vitest (109 tests)/build + Kind e2e (dis-console-ui-lint-test.yml)
  • Image scan/release to ghcr via the reusable workflow on dis-console-ui-v* tags
  • deploy/admin: Deployment + ClusterIP Service on admin-test (no ingress; reach it via kubectl -n product-dis port-forward svc/dis-console-ui 8080:80). Image rides latest until the first release exists; a follow-up pins the version.

Rollout

Merge → release-please cuts dis-console-ui + the image builds → dispatch the admin syncroot publish → port-forward.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added the DIS Console UI with fleet overview, deployments, syncroots, releases, workloads, databases, identities, APIM, and Vault views.
    • Added live API and mock-data modes with tenant and environment scoping.
    • Added resource details, artifact and release dialogs, deployment matrices, status indicators, searchable tables, deployment maps, Azure Portal links, and observability links.
    • Added containerized deployment support for local development and Kubernetes environments.
  • Tests

    • Added automated type-checking, linting, unit tests, builds, smoke tests, and end-to-end validation.
  • Documentation

    • Added setup, configuration, development, and deployment guidance.

@sduranc
sduranc requested a review from a team as a code owner July 31, 2026 07:08
@sduranc
sduranc force-pushed the feat/dis-console-ui-releases branch 2 times, most recently from aeaa10e to 9d68610 Compare July 31, 2026 07:15
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@sduranc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 20 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 58c61dac-f810-4cf0-96b6-8f5edfb47df7

📥 Commits

Reviewing files that changed from the base of the PR and between 9d68610 and f0f5246.

⛔ Files ignored due to path filters (3)
  • services/dis-console-ui/bun.lock is excluded by !**/*.lock
  • services/dis-console-ui/src/assets/dis-logo-color.png is excluded by !**/*.png
  • services/dis-console-ui/src/assets/dis-logo-white.png is excluded by !**/*.png
📒 Files selected for processing (93)
  • .github/workflows/dis-console-ui-lint-test.yml
  • .github/workflows/dis-console-ui-release.yml
  • .release-please-manifest.json
  • deploy/admin/base/dis-console-ui-deployment.yaml
  • deploy/admin/base/kustomization.yaml
  • release-please-config.json
  • renovate.json
  • services/dis-console-ui/.dockerignore
  • services/dis-console-ui/.env.example
  • services/dis-console-ui/.gitignore
  • services/dis-console-ui/.trivyignore
  • services/dis-console-ui/AGENTS.md
  • services/dis-console-ui/CHANGELOG.md
  • services/dis-console-ui/CLAUDE.md
  • services/dis-console-ui/Dockerfile
  • services/dis-console-ui/Makefile
  • services/dis-console-ui/README.md
  • services/dis-console-ui/config/kind/deployment.yaml
  • services/dis-console-ui/eslint.config.js
  • services/dis-console-ui/index.html
  • services/dis-console-ui/package.json
  • services/dis-console-ui/public/config.js
  • services/dis-console-ui/server/index.ts
  • services/dis-console-ui/src/App.tsx
  • services/dis-console-ui/src/api/client.ts
  • services/dis-console-ui/src/api/http.ts
  • services/dis-console-ui/src/api/index.ts
  • services/dis-console-ui/src/api/mock.fixtures.ts
  • services/dis-console-ui/src/api/mock.ts
  • services/dis-console-ui/src/api/types.ts
  • services/dis-console-ui/src/components/ArtifactDialog.tsx
  • services/dis-console-ui/src/components/ClustersTable.tsx
  • services/dis-console-ui/src/components/DeploymentMatrix.tsx
  • services/dis-console-ui/src/components/DetailDialog.tsx
  • services/dis-console-ui/src/components/DisResourcesView.tsx
  • services/dis-console-ui/src/components/DisScope.tsx
  • services/dis-console-ui/src/components/HomeView.tsx
  • services/dis-console-ui/src/components/KustomizationPage.tsx
  • services/dis-console-ui/src/components/LeftNav.tsx
  • services/dis-console-ui/src/components/MatrixSkeleton.tsx
  • services/dis-console-ui/src/components/MockBanner.tsx
  • services/dis-console-ui/src/components/ReleaseDialog.tsx
  • services/dis-console-ui/src/components/ReleasesBrowser.tsx
  • services/dis-console-ui/src/components/RouteLink.tsx
  • services/dis-console-ui/src/components/SortableTh.tsx
  • services/dis-console-ui/src/components/StageChip.tsx
  • services/dis-console-ui/src/components/StaleBanner.tsx
  • services/dis-console-ui/src/components/StatusCell.tsx
  • services/dis-console-ui/src/components/StatusTag.tsx
  • services/dis-console-ui/src/components/SyncrootMap.tsx
  • services/dis-console-ui/src/components/SyncrootsView.tsx
  • services/dis-console-ui/src/components/WorkloadsTable.tsx
  • services/dis-console-ui/src/hooks/useAppHistories.ts
  • services/dis-console-ui/src/hooks/useArtifacts.ts
  • services/dis-console-ui/src/hooks/useDialogBackClose.ts
  • services/dis-console-ui/src/hooks/useDsDialog.ts
  • services/dis-console-ui/src/hooks/useFleet.ts
  • services/dis-console-ui/src/hooks/useHashRoute.ts
  • services/dis-console-ui/src/hooks/useInventory.ts
  • services/dis-console-ui/src/hooks/useResourceDetail.ts
  • services/dis-console-ui/src/hooks/useSourceLink.ts
  • services/dis-console-ui/src/hooks/useSyncrootMap.ts
  • services/dis-console-ui/src/lib/appReleases.test.ts
  • services/dis-console-ui/src/lib/appReleases.ts
  • services/dis-console-ui/src/lib/artifacts.test.ts
  • services/dis-console-ui/src/lib/artifacts.ts
  • services/dis-console-ui/src/lib/azure.test.ts
  • services/dis-console-ui/src/lib/azure.ts
  • services/dis-console-ui/src/lib/disResources.test.ts
  • services/dis-console-ui/src/lib/disResources.ts
  • services/dis-console-ui/src/lib/flux.test.ts
  • services/dis-console-ui/src/lib/flux.ts
  • services/dis-console-ui/src/lib/mapLayout.test.ts
  • services/dis-console-ui/src/lib/mapLayout.ts
  • services/dis-console-ui/src/lib/matrix.test.ts
  • services/dis-console-ui/src/lib/matrix.ts
  • services/dis-console-ui/src/lib/releases.test.ts
  • services/dis-console-ui/src/lib/releases.ts
  • services/dis-console-ui/src/lib/route.test.ts
  • services/dis-console-ui/src/lib/route.ts
  • services/dis-console-ui/src/lib/sourceLink.test.ts
  • services/dis-console-ui/src/lib/sourceLink.ts
  • services/dis-console-ui/src/lib/statusColor.ts
  • services/dis-console-ui/src/lib/tableSort.test.ts
  • services/dis-console-ui/src/lib/tableSort.ts
  • services/dis-console-ui/src/lib/workloads.test.ts
  • services/dis-console-ui/src/lib/workloads.ts
  • services/dis-console-ui/src/main.tsx
  • services/dis-console-ui/src/styles.css
  • services/dis-console-ui/src/vite-env.d.ts
  • services/dis-console-ui/tsconfig.json
  • services/dis-console-ui/tsconfig.server.json
  • services/dis-console-ui/vite.config.ts
📝 Walkthrough

Walkthrough

Changes

The PR adds the DIS Console UI as a Bun/Vite React application. It includes mock and HTTP fleet APIs, resource and syncroot views, release and workload displays, a Bun production server, Docker and Kubernetes deployment, release configuration, and CI workflows.

DIS Console UI

Layer / File(s) Summary
Platform, runtime, and API foundation
.github/workflows/*, deploy/admin/base/*, services/dis-console-ui/{Dockerfile,Makefile,package.json,server/*,src/api/*,vite.config.ts}
Adds build and release workflows, Kubernetes resources, container support, runtime API proxying, typed fleet API clients, mock fixtures, and project tooling.
Fleet domain models and derivation utilities
services/dis-console-ui/src/lib/*
Adds Flux classification, deployment matrices, artifact and release derivation, DIS resource grouping, source links, workload processing, routing, sorting, Azure links, graph layout, and Vitest coverage.
Data loading and syncroot graph hooks
services/dis-console-ui/src/hooks/*
Adds asynchronous hooks for fleet data, artifacts, resource details, inventories, histories, source links, dialogs, routing, and syncroot graph construction.
Console views and interactive components
services/dis-console-ui/src/components/*, services/dis-console-ui/src/App.tsx
Adds navigation, fleet tables, deployment matrices, dialogs, syncroot pages, release browsing, workload tables, status controls, and the deployment map.
Application shell and presentation
services/dis-console-ui/src/main.tsx, services/dis-console-ui/src/styles.css, services/dis-console-ui/index.html
Adds the React entry point, HTML shell, runtime configuration loading, and responsive console styling.

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

Possibly related PRs

Suggested reviewers: annerisbakk

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the new dis-console-ui fleet console and its backing dis-console API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dis-console-ui-releases

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.

@sduranc
sduranc force-pushed the feat/dis-console-ui-releases branch from 9d68610 to 918afc9 Compare July 31, 2026 07:24

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 18

🧹 Nitpick comments (23)
services/dis-console-ui/src/components/DisResourcesView.tsx (1)

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

Read the Azure portal tenant from runtime configuration.

import.meta.env.VITE_AZURE_PORTAL_TENANT is inlined at build time. The same container image then carries one fixed tenant, so Portal deep links break when the image is deployed against another tenant. The runtime /config.js mechanism already carries environment-specific values; add the tenant there and fall back to the build-time value.

As per coding guidelines: "live API configuration is selected through runtime /config.js and DIS_CONSOLE_API".

🤖 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 `@services/dis-console-ui/src/components/DisResourcesView.tsx` at line 9,
Update the tenant configuration used by DisResourcesView so it first reads the
Azure portal tenant from the runtime /config.js configuration, then falls back
to import.meta.env.VITE_AZURE_PORTAL_TENANT when runtime configuration is
absent. Add the tenant to the existing runtime configuration contract and
preserve the current undefined fallback behavior.

Source: Coding guidelines

services/dis-console-ui/src/components/ReleasesBrowser.tsx (1)

27-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated firstSeenLabel formatter into src/lib. Both files define an identical pure date formatter. The shared root cause is domain formatting logic living in components, so the two copies can diverge and neither is unit tested.

  • services/dis-console-ui/src/components/ReleasesBrowser.tsx#L27-L33: delete the local firstSeenLabel and import the shared helper from src/lib.
  • services/dis-console-ui/src/components/ReleaseDialog.tsx#L22-L28: delete the local firstSeenLabel and import the same shared helper.

Add the helper to a src/lib module with unit tests covering the missing and invalid ISO cases.

As per coding guidelines: "Keep domain logic in src/lib pure and React-free, and protect the data transforms with unit tests".

🤖 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 `@services/dis-console-ui/src/components/ReleasesBrowser.tsx` around lines 27 -
33, The duplicated firstSeenLabel formatter must be centralized in a pure,
React-free src/lib helper and covered by unit tests for missing and invalid ISO
values. In services/dis-console-ui/src/components/ReleasesBrowser.tsx lines
27-33, delete the local firstSeenLabel and import the shared helper; make the
same change in services/dis-console-ui/src/components/ReleaseDialog.tsx lines
22-28. Add the shared formatter to an appropriate src/lib module while
preserving its existing output behavior.

Source: Coding guidelines

services/dis-console-ui/src/components/ClustersTable.tsx (1)

5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move relative into src/lib and cover it with unit tests.

relative is pure formatting logic with several branches and no test coverage. A src/lib module keeps it React-free and testable, and lets other views reuse it.

As per coding guidelines: "Keep domain logic in src/lib pure and React-free, and protect the data transforms with unit tests".

🤖 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 `@services/dis-console-ui/src/components/ClustersTable.tsx` around lines 5 -
16, Move the pure relative-time formatter from the ClustersTable component into
a React-free module under src/lib, then import and use it from ClustersTable
instead of the local relative function. Add unit tests covering empty or invalid
dates, seconds, minutes, hours, and days, including the relevant boundary
transitions.

Source: Coding guidelines

services/dis-console-ui/src/styles.css (1)

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

Merge the duplicate .map-node rule.

.map-node is declared at Line 623 and again at Line 647 for position: relative. Move that declaration into the first rule.

🤖 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 `@services/dis-console-ui/src/styles.css` around lines 623 - 649, Merge the
duplicate .map-node rules by moving position: relative into the initial
.map-node declaration, then remove the later standalone .map-node block while
preserving all existing styles.
services/dis-console-ui/src/components/SyncrootMap.tsx (1)

148-163: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Capture the pointer during panning.

onPointerMove and onPointerUp only fire while the pointer stays over the SVG. If the user drags past the map edge and releases there, the SVG never receives pointerup and drag.current keeps moved: true until the next pointerdown. Pointer capture keeps the gesture attached to the element.

♻️ Proposed refactor
         onPointerDown={(e) => {
+          e.currentTarget.setPointerCapture(e.pointerId);
           drag.current = { x: e.clientX, y: e.clientY, vb: view, moved: 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 `@services/dis-console-ui/src/components/SyncrootMap.tsx` around lines 148 -
163, Update the panning handlers in SyncrootMap so the SVG captures the active
pointer during onPointerDown and releases capture when the gesture ends in
onPointerUp. Use the event’s pointerId and preserve the existing drag.current
reset, ensuring pointer movement and release continue to reach the SVG after the
pointer leaves its bounds.
services/dis-console-ui/src/components/SyncrootsView.tsx (1)

58-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider runtime configuration for the Grafana and Azure portal values.

import.meta.env.VITE_* values are inlined at build time. The same container image then cannot point at a different Grafana per environment. The project already loads runtime configuration from /config.js. Reading these two values from that runtime configuration keeps one image usable in every environment.

As per path instructions for services/dis-console-ui/public/config.js: "live API configuration is selected through runtime /config.js and DIS_CONSOLE_API".

🤖 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 `@services/dis-console-ui/src/components/SyncrootsView.tsx` around lines 58 -
59, Replace the build-time import.meta.env values used for GRAFANA and
PORTAL_TENANT with the corresponding runtime configuration values loaded from
/config.js, following the existing runtime configuration pattern and naming used
for DIS_CONSOLE_API. Preserve the current trailing-slash removal for GRAFANA and
undefined fallback behavior for the portal tenant.

Source: Path instructions

services/dis-console-ui/src/components/LeftNav.tsx (1)

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

Build LeftNav hrefs with routeHash.

These anchors are page navigation, and every Section id is a valid bare-section route. Use href={routeHash({ view: id })} so the nav stays synchronized if the route hash format changes.

♻️ Proposed refactor
+import { routeHash } from '../lib/route';
                 <a
                   className="leftnav__item"
                   aria-current={active === id ? 'page' : undefined}
                   title={collapsed ? label : hint}
-                  href={`#/${id}`}
+                  href={routeHash({ view: id })}
                 >
🤖 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 `@services/dis-console-ui/src/components/LeftNav.tsx` around lines 85 - 93,
Update the navigation anchor in LeftNav to build its href with the existing
routeHash helper, passing the section id as the view value, instead of
interpolating a hash manually. Preserve the current label, active-state, and
other anchor behavior.

Source: Coding guidelines

services/dis-console-ui/src/lib/artifacts.ts (2)

43-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the status mapping instead of duplicating it.

artifactStatus repeats the mapping in statusOf (src/lib/flux.ts Lines 130-143). Two copies can drift when a new Ready value or status appears. statusOf only reads ready and suspended, so widen its parameter and delegate.

♻️ Suggested change

In src/lib/flux.ts:

-export function statusOf(r: Resource | undefined): DeployStatus {
+export function statusOf(r: Pick<Resource, 'ready' | 'suspended'> | undefined): DeployStatus {

In src/lib/artifacts.ts:

-export function artifactStatus(a: Pick<Artifact, 'ready' | 'suspended'>): DeployStatus {
-  if (a.suspended) return 'suspended';
-  switch (a.ready) {
-    case 'True':
-      return 'healthy';
-    case 'False':
-      return 'failed';
-    case 'Unknown':
-      return 'reconciling';
-    default:
-      return 'unknown';
-  }
-}
+export function artifactStatus(a: Pick<Artifact, 'ready' | 'suspended'>): DeployStatus {
+  return statusOf(a);
+}

Add statusOf to the existing ./flux import.

🤖 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 `@services/dis-console-ui/src/lib/artifacts.ts` around lines 43 - 55, Update
statusOf in flux.ts to accept an object containing only ready and suspended,
preserving its existing status mapping, then replace the duplicated logic in
artifactStatus with delegation to statusOf and add the statusOf import from
./flux.

175-205: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Repeated full rescans in the appliedBy closure. deployedBySyncroot recomputes its result with a fixed-point loop that rescans every resource of the cluster in each round, and callers invoke it once per cell or per environment. The shared root cause is the missing owner index.

  • services/dis-console-ui/src/lib/artifacts.ts#L175-L205: build a Map from the appliedBy key to resources once, then walk it as a queue seeded with the root Kustomizations; keep roots excluded and let only Kustomizations extend the frontier.
  • services/dis-console-ui/src/lib/workloads.ts#L176-L190: after the indexing fix, no change is required here; if profiling still shows cost, compute the closure once per cluster and reuse it across environments.
🤖 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 `@services/dis-console-ui/src/lib/artifacts.ts` around lines 175 - 205, Update
deployedBySyncroot in services/dis-console-ui/src/lib/artifacts.ts (lines
175-205) to build a Map from each appliedBy namespace/name key to its resources
once, then traverse matching resources with a queue seeded by root
Kustomizations; exclude the roots from the result and only let discovered
Kustomizations extend the owner frontier. No direct change is required in
services/dis-console-ui/src/lib/workloads.ts (lines 176-190); the indexing fix
addresses the issue there.
services/dis-console-ui/src/lib/mapLayout.ts (1)

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

Return pixels from the cycle guard.

place returns a y coordinate in pixels, but the cycle guard returns nextRow, a row index. The branch is unreachable today because kids filters out seen ids. If that filter changes, the mixed unit produces wrong parent centering.

♻️ Suggested change
-    if (seen.has(id)) return nextRow; // cycle guard — should not happen
+    if (seen.has(id)) return pos.get(id)?.y ?? nextRow * rowH; // cycle guard — should not happen
🤖 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 `@services/dis-console-ui/src/lib/mapLayout.ts` around lines 41 - 56, Update
the cycle-guard branch in place so it returns a y-coordinate in pixels,
consistent with the normal placement paths, rather than the row index nextRow.
Preserve the existing seen-node guard and parent-centering behavior.
services/dis-console-ui/src/lib/flux.test.ts (1)

43-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for isApp.

isApp encodes the rule that every HelmRelease is an app and a Kustomization is an app only when it is not an azapi root. buildMatrix depends on it for row selection. A regression here changes the whole matrix silently.

♻️ Suggested test
+describe('isApp', () => {
+  it('treats every HelmRelease as an app and excludes azapi roots', () => {
+    expect(isApp(res({ kind: 'HelmRelease', appliedBy: undefined }))).toBe(true);
+    expect(isApp(res({ kind: 'Kustomization', appliedBy: { name: 'root', namespace: 'flux-system' } }))).toBe(true);
+    expect(isApp(res({ kind: 'Kustomization', appliedBy: undefined }))).toBe(false);
+  });
+});

Import isApp from ./flux as well.

Based on learnings: "Keep domain logic in src/lib pure and React-free, and protect the data transforms with unit tests" and "Treat every HelmRelease as an app, and treat a Kustomization as an app only when it is not an azapi root".

🤖 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 `@services/dis-console-ui/src/lib/flux.test.ts` around lines 43 - 57, Add unit
coverage for isApp in the flux tests, importing it from ./flux alongside the
existing symbols. Verify every HelmRelease is classified as an app, while
Kustomizations are classified as apps only when they are not azapi roots;
include both positive and negative cases to protect buildMatrix row selection.

Source: Coding guidelines

services/dis-console-ui/src/lib/tableSort.ts (1)

26-38: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consider numeric collation for name-like columns.

localeCompare without options sorts db10 before db2. Resource names in the fleet often end in digits. Numeric collation gives the order users expect.

♻️ Suggested change
+const cmp = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true });
+
 export function sortRows<T extends SortableRow>(rows: T[], state: SortState): T[] {
   const sign = state.dir === 'asc' ? 1 : -1;
   return [...rows].sort((a, b) => {
     const c =
       state.col === 'status'
         ? STATUS_SEVERITY[a.status] - STATUS_SEVERITY[b.status]
-        : a[state.col].localeCompare(b[state.col]);
+        : cmp(a[state.col], b[state.col]);
     if (c !== 0) return sign * c;
     // Stable tie-break so equal values keep a deterministic order.
     return (
-      a.kind.localeCompare(b.kind) ||
-      a.namespace.localeCompare(b.namespace) ||
-      a.name.localeCompare(b.name)
+      cmp(a.kind, b.kind) || cmp(a.namespace, b.namespace) || cmp(a.name, b.name)
     );
   });
 }
🤖 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 `@services/dis-console-ui/src/lib/tableSort.ts` around lines 26 - 38, Update
the string comparisons in the table sorting comparator to use numeric collation
for name-like columns, including the primary column comparison and the
deterministic tie-break fields in the sort callback. Preserve status severity
sorting and the existing sort direction behavior while ensuring values such as
db2 sort before db10.
services/dis-console-ui/src/lib/disResources.test.ts (1)

18-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the orphan-child path.

buildDisResources promotes a child to the top level when its parent is not in the data. No test covers that branch. Add a case with a Database whose parent is missing.

As per coding guidelines: "Keep domain logic in src/lib pure and React-free, and protect the data transforms with unit tests."

🤖 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 `@services/dis-console-ui/src/lib/disResources.test.ts` around lines 18 - 47,
Extend the buildDisResources test suite with an orphan-child case: include a
Database resource whose parent is absent from the input data, then assert it is
promoted to the top-level nodes for the selected cluster. Keep the existing
grouping, filtering, and scoping tests unchanged.

Source: Coding guidelines

services/dis-console-ui/src/lib/artifacts.test.ts (1)

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

Move the syncrootSummaries test into its own describe block.

This test exercises syncrootSummaries, not deployedBySyncroot. The current placement makes the test report misleading.

🤖 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 `@services/dis-console-ui/src/lib/artifacts.test.ts` around lines 153 - 178,
Move the test titled “summarizes a syncroot like a project: namespaces, envs,
worst status” into a dedicated describe block for syncrootSummaries, separate
from the deployedBySyncroot tests. Keep the test setup and assertions unchanged.
services/dis-console-ui/src/lib/disResources.ts (1)

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

Sort orphan children with the other nodes.

Orphan children are pushed after the sort() call, so they appear in input order at the end of the group. Rows then look unordered when a parent is missing from the data. Build the orphan nodes first, then sort the full list.

♻️ Proposed refactor
-    const nodes: DisNode[] = parents
+    const parentNodes: DisNode[] = parents
       .map((p) => ({
         resource: p,
         children: children
           .filter((c) => c.parent?.kind === p.kind && c.parent?.name === p.name)
           .sort((a, b) => a.name.localeCompare(b.name)),
-      }))
-      .sort(
-        (a, b) =>
-          a.resource.kind.localeCompare(b.resource.kind) ||
-          a.resource.name.localeCompare(b.resource.name),
-      );
+      }));
 
-    const claimed = new Set(nodes.flatMap((n) => n.children.map((c) => `${c.kind}/${c.name}`)));
-    for (const c of children) {
-      if (!claimed.has(`${c.kind}/${c.name}`)) nodes.push({ resource: c, children: [] });
-    }
+    const claimed = new Set(
+      parentNodes.flatMap((n) => n.children.map((c) => `${c.kind}/${c.name}`)),
+    );
+    const orphans: DisNode[] = children
+      .filter((c) => !claimed.has(`${c.kind}/${c.name}`))
+      .map((c) => ({ resource: c, children: [] }));
+    const nodes = [...parentNodes, ...orphans].sort(
+      (a, b) =>
+        a.resource.kind.localeCompare(b.resource.kind) ||
+        a.resource.name.localeCompare(b.resource.name),
+    );
🤖 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 `@services/dis-console-ui/src/lib/disResources.ts` around lines 84 - 91, Update
the grouping logic around the nodes sort and orphan handling: create and append
all orphan child nodes before sorting the complete nodes collection. Ensure the
final nodes list, including entries added by the claimed check, is sorted using
the existing ordering before pushing the group.
services/dis-console-ui/src/hooks/useAppHistories.ts (1)

29-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Key useAppHistories on the row key and cell identifiers.

useAppHistories already runs from rows produced by the memoized matrix and from appRows, both of which are rebuilt for each relevant change. Since row is a fresh MatrixRow each time, [row] makes React treat it as a different dependency for the same row.key; key the effect on row.key plus the env|cluster|kind|namespace|name tuples of the cell resources instead.

🤖 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 `@services/dis-console-ui/src/hooks/useAppHistories.ts` around lines 29 - 62,
Update the useAppHistories useEffect dependency array to use row.key and stable
cell resource identifiers in env|cluster|kind|namespace|name form instead of the
row object. Derive those identifiers from the resource-bearing cells while
preserving the existing fetch and cancellation behavior.
.github/workflows/dis-console-ui-release.yml (1)

22-25: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

No change needed for pull-request permissions here.

This job does not run the image push paths on pull_request: the reusable workflow only pushes when github.ref is refs/tags/... or refs/heads/main. Remove id-token: write from this workflow unless OIDC issuance is required for the PR run.

🤖 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/workflows/dis-console-ui-release.yml around lines 22 - 25, Remove
the unnecessary id-token: write permission from the workflow’s permissions
block, leaving contents: read and packages: write unchanged; retain it only if
this job explicitly requires OIDC issuance during pull_request runs.
services/dis-console-ui/vite.config.ts (1)

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

Consider widening the test glob to .tsx.

The pattern matches only .ts files. A future .test.tsx file is skipped silently, with no failure to signal it.

♻️ Proposed change
   test: {
     environment: 'node',
-    include: ['src/**/*.test.ts'],
+    include: ['src/**/*.test.{ts,tsx}'],
   },
🤖 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 `@services/dis-console-ui/vite.config.ts` around lines 13 - 16, Update the test
include glob in the Vite test configuration to match both .test.ts and .test.tsx
files, ensuring TypeScript React tests are discovered alongside existing
TypeScript tests.
services/dis-console-ui/src/api/http.ts (2)

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

Reuse queryString for the summary query.

getSummary builds its query manually while queryString already handles encoding and the empty case.

♻️ Proposed change
   async getSummary(cluster?: string): Promise<Summary> {
-    const q = cluster ? `?cluster=${encodeURIComponent(cluster)}` : '';
-    return getJSON<Summary>(`/api/summary${q}`);
+    return getJSON<Summary>(`/api/summary${queryString({ cluster })}`);
   }
🤖 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 `@services/dis-console-ui/src/api/http.ts` around lines 39 - 42, Update
getSummary to build its optional cluster query through the existing queryString
helper instead of manually concatenating and encoding the query. Preserve the
/api/summary endpoint and ensure the empty-cluster case still produces no query
string.

16-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Bound every fleet request with a timeout.

fetch has no abort signal. If the backend accepts the connection but never responds, the promise never settles. The consuming hooks leave loading true, so the console shows the skeleton indefinitely with no error path.

Add AbortSignal.timeout and map the abort to a clear error message.

♻️ Proposed change
+const TIMEOUT_MS = 30_000;
+
 async function getJSON<T>(path: string): Promise<T> {
-  const res = await fetch(`${BASE}${path}`, { headers: { Accept: 'application/json' } });
+  let res: Response;
+  try {
+    res = await fetch(`${BASE}${path}`, {
+      headers: { Accept: 'application/json' },
+      signal: AbortSignal.timeout(TIMEOUT_MS),
+    });
+  } catch (e) {
+    if (e instanceof Error && e.name === 'TimeoutError') {
+      throw new Error(`GET ${path} timed out after ${TIMEOUT_MS} ms`);
+    }
+    throw e;
+  }
   if (!res.ok) {
     throw new Error(`GET ${path} failed: ${res.status} ${res.statusText}`);
   }
   return (await res.json()) as T;
 }
🤖 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 `@services/dis-console-ui/src/api/http.ts` around lines 16 - 22, Update getJSON
to pass an AbortSignal.timeout to fetch so every fleet request has a finite
deadline. Catch timeout-induced aborts and throw a clear request-timeout error
while preserving the existing HTTP status error handling and successful JSON
response path.
services/dis-console-ui/server/index.ts (1)

62-65: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Send no-cache headers with the SPA document.

The fallback serves index.html without cache directives. Assets under /assets/ are immutable for one year. If a browser or proxy caches the old document, it requests asset filenames that no longer exist after a deployment.

Set Cache-Control: no-cache on the document response, and on the /index.html asset path at Line 59.

♻️ Proposed change
     return new Response(Bun.file(`${distDir}/index.html`), {
-      headers: { 'Content-Type': 'text/html' },
+      headers: { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' },
     });
🤖 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 `@services/dis-console-ui/server/index.ts` around lines 62 - 65, Update the SPA
fallback response and the `/index.html` asset response to include the header
`Cache-Control: no-cache`, while preserving their existing content type and
response behavior.
services/dis-console-ui/src/api/mock.fixtures.ts (1)

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

Guard the padding length.

'0'.repeat(40 - state.digest.length) throws RangeError if a digest longer than 40 characters is added. All current digests are 12 characters, so the fixture works today.

♻️ Proposed change
-        originRevision: `main/${state.digest}${'0'.repeat(40 - state.digest.length)}`,
+        originRevision: `main/${state.digest.padEnd(40, '0').slice(0, 40)}`,
🤖 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 `@services/dis-console-ui/src/api/mock.fixtures.ts` at line 729, Guard the
padding calculation in the fixture’s originRevision construction so
String.repeat never receives a negative count when state.digest exceeds 40
characters. Preserve the existing 40-character revision format for shorter
digests and avoid changing the digest content.
services/dis-console-ui/src/api/mock.ts (1)

50-59: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Fixture objects escape the mock client without deep cloning. Three read paths return references into the module-level RESOURCES fixture. A consumer that mutates a nested field changes the fixture for every later call, which the live HTTP client never does. getClusters and getArtifacts already use structuredClone; apply the same isolation to the remaining paths.

  • services/dis-console-ui/src/api/mock.ts#L50-L59: replace .map((r) => ({ ...r })) with .map((r) => structuredClone(r)).
  • services/dis-console-ui/src/api/mock.ts#L61-L77: clone found before spreading, for example { ...structuredClone(found), raw: rawFor(found), history: historyFor(found) }.
  • services/dis-console-ui/src/api/mock.ts#L88-L92: clone the entries array returned by inventoryFor, because each entry embeds a Resource reference taken directly from RESOURCES.
🤖 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 `@services/dis-console-ui/src/api/mock.ts` around lines 50 - 59, Deep-clone
fixture data in all three mock read paths so consumers cannot mutate
module-level RESOURCES state: in services/dis-console-ui/src/api/mock.ts lines
50-59, replace the shallow Resource mapping in getResources with
structuredClone; in lines 61-77, clone found before constructing the returned
object while preserving rawFor and historyFor; and in lines 88-92, clone the
entries returned by inventoryFor because they contain Resource references.
🤖 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 `@services/dis-console-ui/Makefile`:
- Around line 55-70: Update the install command in both dev-podman and dev-live
to use Bun’s frozen-lockfile mode. Preserve the existing container mounts,
environment variables, and dev-server commands while ensuring dependency
installation fails if package.json and the lockfile disagree.
- Around line 132-140: Update the kind-create recipe’s $(KIND) create cluster
invocation to pass $(KIND_KUBECONFIG) as the creation kubeconfig when that
variable is non-empty, while preserving the default ambient kubeconfig behavior
when it is empty; leave the existing cluster detection and export kubeconfig
flow unchanged.

In `@services/dis-console-ui/server/index.ts`:
- Around line 34-50: Update the API proxy flow in the request handler to set
init.signal to a 30-second AbortSignal.timeout, await the upstream fetch, and
wrap it in try/catch. Map abort timeouts to a 504 response and other upstream
fetch failures to a 502 response, while preserving the existing successful
response and unconfigured-backend behavior.

In `@services/dis-console-ui/src/api/mock.fixtures.ts`:
- Around line 619-621: Update the schemaVersion returned by buildClusters to
match the v5 schema represented by the fixture images data, so views gating
image or drift display receive a consistent schema version.

In `@services/dis-console-ui/src/components/HomeView.tsx`:
- Around line 20-49: Update HomeView’s useArtifacts destructuring and syncroots
panel rendering to handle the hook’s error state before the empty summaries
state. When the artifacts fetch fails, render the hook error using the
Designsystemet Paragraph component; only show “No syncroot artifacts reported.”
when loading has finished successfully with no artifacts.

In `@services/dis-console-ui/src/components/MockBanner.tsx`:
- Around line 7-8: Update the banner text in MockBanner to replace
VITE_API_BASE_URL with the supported DIS_CONSOLE_API runtime configuration,
while retaining VITE_USE_MOCK=false and accurately indicating that live API
settings come from runtime /config.js.

In `@services/dis-console-ui/src/components/SyncrootMap.tsx`:
- Around line 253-281: Restructure the node rendering around the `inner`,
`clickable`, and `onDetails` logic so details is not nested inside the node
`<button>`. Render the node as a container with sibling native `<button>`
elements for expand/collapse and details, preserving their respective handlers
and keyboard behavior; remove the `role="button"` span and add/use a
`.map-node__main` layout rule so the primary control fills the node while
`.map-node` remains the container.

In `@services/dis-console-ui/src/components/SyncrootsView.tsx`:
- Around line 184-200: Replace the environment-cell button in the syncroots view
with a RouteLink/routeHash-generated anchor targeting the same syncroot route
and key for environment e. Preserve the cell contents, styling, and aria-label
(adding RouteLink aria-label pass-through support if needed), while removing the
onClick-based navigate call so standard link interactions remain available.
- Around line 706-716: Update the aria-label construction in the orderedEnvs
mapping to use the same 'absent' fallback as the StageChip state when
rel.chips[e] is undefined, while preserving the existing environment label and
digest text.

In `@services/dis-console-ui/src/components/WorkloadsTable.tsx`:
- Around line 63-77: Move the status-to-color and tag-variant mapping from the
workload cell renderer into the `imageCellStyle` helper in
`src/lib/statusColor.ts`, accepting the status and drift flag and preserving the
existing precedence and variants. Update the relevant rendering logic in
`WorkloadsTable` to call this helper and use its returned `color` and `variant`
values.

In `@services/dis-console-ui/src/hooks/useDsDialog.ts`:
- Around line 16-38: Update the dialog synchronization effect and close
listeners in useDsDialog to follow the Designsystemet contract: remove the
el.close() path and the close event listener, and rely solely on the toggle
handler when newState is 'closed' to invoke onCloseRef.current(). Preserve
opening behavior and cleanup for the remaining toggle listener.

In `@services/dis-console-ui/src/hooks/useResourceDetail.ts`:
- Around line 21-25: Reset loading state in both lazy-fetch hooks when their
required ref is absent: in
services/dis-console-ui/src/hooks/useResourceDetail.ts lines 21-25, update the
absent-ref branch to call setLoading(false) alongside setResource(null) and
setError(null); in services/dis-console-ui/src/hooks/useInventory.ts lines
20-24, make the equivalent change alongside setInventory(null) and
setError(null).

In `@services/dis-console-ui/src/hooks/useSourceLink.ts`:
- Around line 16-45: Reset loading to false at the start of the useEffect
callback before the early returns and before starting any fetch. Keep setting it
to true only when source resolution requires the asynchronous api.getResource
request, while preserving the existing cancellation cleanup and finally
handling.

In `@services/dis-console-ui/src/lib/matrix.ts`:
- Around line 134-141: Update the status computation loop that reads each cell’s
resource and children to also include every entry in cell.conflict when
calculating the worst status and anyFailed. Ensure failed conflicting resources
affect the cell status and anyFailed exactly like the primary resource, while
preserving the existing healthy-resource behavior.

In `@services/dis-console-ui/src/lib/releases.ts`:
- Around line 63-64: Update the chip assignment in the applied branch to map
artifactStatus(cell.artifact) values of suspended to the suspended chip, while
retaining failed as failed and mapping other statuses to current.

In `@services/dis-console-ui/src/lib/route.ts`:
- Around line 22-26: Update parseRoute’s segment decoding in the parts
construction to catch URIError from decodeURIComponent and retain the original
segment when decoding fails, so malformed hashes such as `#/`% or truncated
percent sequences still produce a route without throwing during useHashRoute
rendering. Add a route.test.ts case covering parseRoute('`#/`%') and verify the
raw segment is preserved.

In `@services/dis-console-ui/src/styles.css`:
- Line 4: Update the font-family declaration in the stylesheet to remove the
redundant quotes around both Inter values while preserving the existing fallback
order and CSS variable fallback.
- Around line 921-931: Update the .sr-only visually hidden style to replace the
deprecated clip property with an equivalent clip-path declaration, preserving
the existing accessibility and layout behavior.

---

Nitpick comments:
In @.github/workflows/dis-console-ui-release.yml:
- Around line 22-25: Remove the unnecessary id-token: write permission from the
workflow’s permissions block, leaving contents: read and packages: write
unchanged; retain it only if this job explicitly requires OIDC issuance during
pull_request runs.

In `@services/dis-console-ui/server/index.ts`:
- Around line 62-65: Update the SPA fallback response and the `/index.html`
asset response to include the header `Cache-Control: no-cache`, while preserving
their existing content type and response behavior.

In `@services/dis-console-ui/src/api/http.ts`:
- Around line 39-42: Update getSummary to build its optional cluster query
through the existing queryString helper instead of manually concatenating and
encoding the query. Preserve the /api/summary endpoint and ensure the
empty-cluster case still produces no query string.
- Around line 16-22: Update getJSON to pass an AbortSignal.timeout to fetch so
every fleet request has a finite deadline. Catch timeout-induced aborts and
throw a clear request-timeout error while preserving the existing HTTP status
error handling and successful JSON response path.

In `@services/dis-console-ui/src/api/mock.fixtures.ts`:
- Line 729: Guard the padding calculation in the fixture’s originRevision
construction so String.repeat never receives a negative count when state.digest
exceeds 40 characters. Preserve the existing 40-character revision format for
shorter digests and avoid changing the digest content.

In `@services/dis-console-ui/src/api/mock.ts`:
- Around line 50-59: Deep-clone fixture data in all three mock read paths so
consumers cannot mutate module-level RESOURCES state: in
services/dis-console-ui/src/api/mock.ts lines 50-59, replace the shallow
Resource mapping in getResources with structuredClone; in lines 61-77, clone
found before constructing the returned object while preserving rawFor and
historyFor; and in lines 88-92, clone the entries returned by inventoryFor
because they contain Resource references.

In `@services/dis-console-ui/src/components/ClustersTable.tsx`:
- Around line 5-16: Move the pure relative-time formatter from the ClustersTable
component into a React-free module under src/lib, then import and use it from
ClustersTable instead of the local relative function. Add unit tests covering
empty or invalid dates, seconds, minutes, hours, and days, including the
relevant boundary transitions.

In `@services/dis-console-ui/src/components/DisResourcesView.tsx`:
- Line 9: Update the tenant configuration used by DisResourcesView so it first
reads the Azure portal tenant from the runtime /config.js configuration, then
falls back to import.meta.env.VITE_AZURE_PORTAL_TENANT when runtime
configuration is absent. Add the tenant to the existing runtime configuration
contract and preserve the current undefined fallback behavior.

In `@services/dis-console-ui/src/components/LeftNav.tsx`:
- Around line 85-93: Update the navigation anchor in LeftNav to build its href
with the existing routeHash helper, passing the section id as the view value,
instead of interpolating a hash manually. Preserve the current label,
active-state, and other anchor behavior.

In `@services/dis-console-ui/src/components/ReleasesBrowser.tsx`:
- Around line 27-33: The duplicated firstSeenLabel formatter must be centralized
in a pure, React-free src/lib helper and covered by unit tests for missing and
invalid ISO values. In
services/dis-console-ui/src/components/ReleasesBrowser.tsx lines 27-33, delete
the local firstSeenLabel and import the shared helper; make the same change in
services/dis-console-ui/src/components/ReleaseDialog.tsx lines 22-28. Add the
shared formatter to an appropriate src/lib module while preserving its existing
output behavior.

In `@services/dis-console-ui/src/components/SyncrootMap.tsx`:
- Around line 148-163: Update the panning handlers in SyncrootMap so the SVG
captures the active pointer during onPointerDown and releases capture when the
gesture ends in onPointerUp. Use the event’s pointerId and preserve the existing
drag.current reset, ensuring pointer movement and release continue to reach the
SVG after the pointer leaves its bounds.

In `@services/dis-console-ui/src/components/SyncrootsView.tsx`:
- Around line 58-59: Replace the build-time import.meta.env values used for
GRAFANA and PORTAL_TENANT with the corresponding runtime configuration values
loaded from /config.js, following the existing runtime configuration pattern and
naming used for DIS_CONSOLE_API. Preserve the current trailing-slash removal for
GRAFANA and undefined fallback behavior for the portal tenant.

In `@services/dis-console-ui/src/hooks/useAppHistories.ts`:
- Around line 29-62: Update the useAppHistories useEffect dependency array to
use row.key and stable cell resource identifiers in
env|cluster|kind|namespace|name form instead of the row object. Derive those
identifiers from the resource-bearing cells while preserving the existing fetch
and cancellation behavior.

In `@services/dis-console-ui/src/lib/artifacts.test.ts`:
- Around line 153-178: Move the test titled “summarizes a syncroot like a
project: namespaces, envs, worst status” into a dedicated describe block for
syncrootSummaries, separate from the deployedBySyncroot tests. Keep the test
setup and assertions unchanged.

In `@services/dis-console-ui/src/lib/artifacts.ts`:
- Around line 43-55: Update statusOf in flux.ts to accept an object containing
only ready and suspended, preserving its existing status mapping, then replace
the duplicated logic in artifactStatus with delegation to statusOf and add the
statusOf import from ./flux.
- Around line 175-205: Update deployedBySyncroot in
services/dis-console-ui/src/lib/artifacts.ts (lines 175-205) to build a Map from
each appliedBy namespace/name key to its resources once, then traverse matching
resources with a queue seeded by root Kustomizations; exclude the roots from the
result and only let discovered Kustomizations extend the owner frontier. No
direct change is required in services/dis-console-ui/src/lib/workloads.ts (lines
176-190); the indexing fix addresses the issue there.

In `@services/dis-console-ui/src/lib/disResources.test.ts`:
- Around line 18-47: Extend the buildDisResources test suite with an
orphan-child case: include a Database resource whose parent is absent from the
input data, then assert it is promoted to the top-level nodes for the selected
cluster. Keep the existing grouping, filtering, and scoping tests unchanged.

In `@services/dis-console-ui/src/lib/disResources.ts`:
- Around line 84-91: Update the grouping logic around the nodes sort and orphan
handling: create and append all orphan child nodes before sorting the complete
nodes collection. Ensure the final nodes list, including entries added by the
claimed check, is sorted using the existing ordering before pushing the group.

In `@services/dis-console-ui/src/lib/flux.test.ts`:
- Around line 43-57: Add unit coverage for isApp in the flux tests, importing it
from ./flux alongside the existing symbols. Verify every HelmRelease is
classified as an app, while Kustomizations are classified as apps only when they
are not azapi roots; include both positive and negative cases to protect
buildMatrix row selection.

In `@services/dis-console-ui/src/lib/mapLayout.ts`:
- Around line 41-56: Update the cycle-guard branch in place so it returns a
y-coordinate in pixels, consistent with the normal placement paths, rather than
the row index nextRow. Preserve the existing seen-node guard and
parent-centering behavior.

In `@services/dis-console-ui/src/lib/tableSort.ts`:
- Around line 26-38: Update the string comparisons in the table sorting
comparator to use numeric collation for name-like columns, including the primary
column comparison and the deterministic tie-break fields in the sort callback.
Preserve status severity sorting and the existing sort direction behavior while
ensuring values such as db2 sort before db10.

In `@services/dis-console-ui/src/styles.css`:
- Around line 623-649: Merge the duplicate .map-node rules by moving position:
relative into the initial .map-node declaration, then remove the later
standalone .map-node block while preserving all existing styles.

In `@services/dis-console-ui/vite.config.ts`:
- Around line 13-16: Update the test include glob in the Vite test configuration
to match both .test.ts and .test.tsx files, ensuring TypeScript React tests are
discovered alongside existing TypeScript tests.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b34e7d9-3cc8-49df-98e3-edc2f6c656e9

📥 Commits

Reviewing files that changed from the base of the PR and between e0fa307 and 9d68610.

⛔ Files ignored due to path filters (3)
  • services/dis-console-ui/bun.lock is excluded by !**/*.lock
  • services/dis-console-ui/src/assets/dis-logo-color.png is excluded by !**/*.png
  • services/dis-console-ui/src/assets/dis-logo-white.png is excluded by !**/*.png
📒 Files selected for processing (93)
  • .github/workflows/dis-console-ui-lint-test.yml
  • .github/workflows/dis-console-ui-release.yml
  • .release-please-manifest.json
  • deploy/admin/base/dis-console-ui-deployment.yaml
  • deploy/admin/base/kustomization.yaml
  • release-please-config.json
  • renovate.json
  • services/dis-console-ui/.dockerignore
  • services/dis-console-ui/.env.example
  • services/dis-console-ui/.gitignore
  • services/dis-console-ui/.trivyignore
  • services/dis-console-ui/AGENTS.md
  • services/dis-console-ui/CHANGELOG.md
  • services/dis-console-ui/CLAUDE.md
  • services/dis-console-ui/Dockerfile
  • services/dis-console-ui/Makefile
  • services/dis-console-ui/README.md
  • services/dis-console-ui/config/kind/deployment.yaml
  • services/dis-console-ui/eslint.config.js
  • services/dis-console-ui/index.html
  • services/dis-console-ui/package.json
  • services/dis-console-ui/public/config.js
  • services/dis-console-ui/server/index.ts
  • services/dis-console-ui/src/App.tsx
  • services/dis-console-ui/src/api/client.ts
  • services/dis-console-ui/src/api/http.ts
  • services/dis-console-ui/src/api/index.ts
  • services/dis-console-ui/src/api/mock.fixtures.ts
  • services/dis-console-ui/src/api/mock.ts
  • services/dis-console-ui/src/api/types.ts
  • services/dis-console-ui/src/components/ArtifactDialog.tsx
  • services/dis-console-ui/src/components/ClustersTable.tsx
  • services/dis-console-ui/src/components/DeploymentMatrix.tsx
  • services/dis-console-ui/src/components/DetailDialog.tsx
  • services/dis-console-ui/src/components/DisResourcesView.tsx
  • services/dis-console-ui/src/components/DisScope.tsx
  • services/dis-console-ui/src/components/HomeView.tsx
  • services/dis-console-ui/src/components/KustomizationPage.tsx
  • services/dis-console-ui/src/components/LeftNav.tsx
  • services/dis-console-ui/src/components/MatrixSkeleton.tsx
  • services/dis-console-ui/src/components/MockBanner.tsx
  • services/dis-console-ui/src/components/ReleaseDialog.tsx
  • services/dis-console-ui/src/components/ReleasesBrowser.tsx
  • services/dis-console-ui/src/components/RouteLink.tsx
  • services/dis-console-ui/src/components/SortableTh.tsx
  • services/dis-console-ui/src/components/StageChip.tsx
  • services/dis-console-ui/src/components/StaleBanner.tsx
  • services/dis-console-ui/src/components/StatusCell.tsx
  • services/dis-console-ui/src/components/StatusTag.tsx
  • services/dis-console-ui/src/components/SyncrootMap.tsx
  • services/dis-console-ui/src/components/SyncrootsView.tsx
  • services/dis-console-ui/src/components/WorkloadsTable.tsx
  • services/dis-console-ui/src/hooks/useAppHistories.ts
  • services/dis-console-ui/src/hooks/useArtifacts.ts
  • services/dis-console-ui/src/hooks/useDialogBackClose.ts
  • services/dis-console-ui/src/hooks/useDsDialog.ts
  • services/dis-console-ui/src/hooks/useFleet.ts
  • services/dis-console-ui/src/hooks/useHashRoute.ts
  • services/dis-console-ui/src/hooks/useInventory.ts
  • services/dis-console-ui/src/hooks/useResourceDetail.ts
  • services/dis-console-ui/src/hooks/useSourceLink.ts
  • services/dis-console-ui/src/hooks/useSyncrootMap.ts
  • services/dis-console-ui/src/lib/appReleases.test.ts
  • services/dis-console-ui/src/lib/appReleases.ts
  • services/dis-console-ui/src/lib/artifacts.test.ts
  • services/dis-console-ui/src/lib/artifacts.ts
  • services/dis-console-ui/src/lib/azure.test.ts
  • services/dis-console-ui/src/lib/azure.ts
  • services/dis-console-ui/src/lib/disResources.test.ts
  • services/dis-console-ui/src/lib/disResources.ts
  • services/dis-console-ui/src/lib/flux.test.ts
  • services/dis-console-ui/src/lib/flux.ts
  • services/dis-console-ui/src/lib/mapLayout.test.ts
  • services/dis-console-ui/src/lib/mapLayout.ts
  • services/dis-console-ui/src/lib/matrix.test.ts
  • services/dis-console-ui/src/lib/matrix.ts
  • services/dis-console-ui/src/lib/releases.test.ts
  • services/dis-console-ui/src/lib/releases.ts
  • services/dis-console-ui/src/lib/route.test.ts
  • services/dis-console-ui/src/lib/route.ts
  • services/dis-console-ui/src/lib/sourceLink.test.ts
  • services/dis-console-ui/src/lib/sourceLink.ts
  • services/dis-console-ui/src/lib/statusColor.ts
  • services/dis-console-ui/src/lib/tableSort.test.ts
  • services/dis-console-ui/src/lib/tableSort.ts
  • services/dis-console-ui/src/lib/workloads.test.ts
  • services/dis-console-ui/src/lib/workloads.ts
  • services/dis-console-ui/src/main.tsx
  • services/dis-console-ui/src/styles.css
  • services/dis-console-ui/src/vite-env.d.ts
  • services/dis-console-ui/tsconfig.json
  • services/dis-console-ui/tsconfig.server.json
  • services/dis-console-ui/vite.config.ts

Comment thread services/dis-console-ui/Makefile Outdated
Comment thread services/dis-console-ui/Makefile
Comment thread services/dis-console-ui/server/index.ts
Comment thread services/dis-console-ui/src/api/mock.fixtures.ts
Comment thread services/dis-console-ui/src/components/HomeView.tsx Outdated
Comment thread services/dis-console-ui/src/lib/matrix.ts
Comment thread services/dis-console-ui/src/lib/releases.ts Outdated
Comment thread services/dis-console-ui/src/lib/route.ts Outdated
Comment thread services/dis-console-ui/src/styles.css Outdated
Comment thread services/dis-console-ui/src/styles.css
sduranc added 2 commits July 31, 2026 09:38
- Run the image as USER 1000:1000 so runAsNonRoot can verify the user
  (a named user fails admission with CreateContainerConfigError)
- Time out the BFF /api proxy after 30s and map upstream failures to
  504/502 instead of crashing the request
- Honor KIND_KUBECONFIG= (ambient kubeconfig) in kind-create; install
  with --frozen-lockfile in the dev container targets
- Reset loading state on early returns in useSourceLink/useResourceDetail;
  rely on the dialog toggle event alone in useDsDialog
- Survive malformed hash routes (decodeURIComponent), fold conflict
  resources into cell status, show suspended stage chips
- Render the artifacts fetch error on Home instead of an empty state
- Use a real anchor for syncroot env cells, guard an aria-label against
  missing chip state, move the image-tag styling into statusColor.ts,
  and make the map details affordance a sibling button (nested
  interactive elements are invalid)
- Update the mock banner text and mock schemaVersion (5)
@sduranc
sduranc merged commit ad4ffa8 into main Jul 31, 2026
9 checks passed
@sduranc
sduranc deleted the feat/dis-console-ui-releases branch July 31, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant