Skip to content

EDM-4809: Catalog now has improved Backend support - #757

Merged
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-4809-catalog-robust-api
Aug 5, 2026
Merged

EDM-4809: Catalog now has improved Backend support#757
celdrake merged 5 commits into
flightctl:mainfrom
celdrake:EDM-4809-catalog-robust-api

Conversation

@celdrake

@celdrake celdrake commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Deployment of catalog items is no longer controlled by defining labels in a fleet/device, but rather by updating the spec to include a catalogItemRef for the specific catalog item.

Summary

  • Updated shared catalog deployment flows to use explicit catalogItemRef values instead of fleet or device labels.
  • Added catalog reference resolution, caching, context providers, validation, lookup hooks, and catalog-aware UI components.
  • Updated OS, application, volume, install, edit, fleet, and device workflows to preserve catalog references and generate targeted JSON patches.
  • Improved catalog deletion with deployment-use checks, retry support, and per-item failure reporting.
  • Added catalog-aware display for installed software, system images, versions, channels, artifacts, and unresolved references.
  • Added shared volume conversion utilities and image-or-catalog reference form controls.
  • Exported catalog reference and deployment types from libs/types/ and libs/types/alpha/.
  • Updated shared documentation links from version 1.1 to 1.2.

Area impact

  • libs/ui-components/: Major shared UI and data-model changes. This affects catalog management, device configuration, fleet configuration, image promotion, and deployment workflows.
  • libs/types/: Added public catalog reference and deployment types.
  • libs/i18n/: No changes reported.
  • libs/cypress/: No changes reported.
  • apps/standalone/: No direct changes reported.
  • apps/ocp-plugin/: No direct changes reported.
  • proxy/: No changes reported.
  • packaging/: No changes reported.
  • .github/workflows/: No changes reported.

Cross-cutting implications

Shared UI changes can affect both standalone and OCP plugin consumers. OS, application, and volume specifications now retain explicit catalog references. Label-based deployment behavior is removed.

Unresolved catalog references remain visible and deletable. Editing is disabled for unresolved references. External documentation links are restricted to HTTP(S) URLs before opening in a new tab.

The changes do not affect the Go authentication proxy, container builds, E2E tests, or CI configuration.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

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

Catalog handling now uses typed catalog-item references instead of metadata labels across catalog management, device and fleet forms, install/edit workflows, dynamic volumes, and image promotion. Shared lookup, resolution, validation, patch, display, and volume conversion utilities were added.

Changes

Catalog reference foundation

Layer / File(s) Summary
Shared catalog contracts and lookup
libs/types/*, libs/ui-components/src/utils/catalog.ts, libs/ui-components/src/utils/volumes.ts, libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts
Public catalog-reference types, lookup hooks, reference extraction, resolution, patch generation, artifact handling, and volume conversion utilities were added.
Catalog context and resolution
libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx, libs/ui-components/src/components/Catalog/specCatalogItems.ts, libs/ui-components/src/components/Catalog/useResolvedCatalogRef.ts
Catalog references are deduplicated, fetched, cached, and exposed through context and resolution helpers.

Catalog management

Layer / File(s) Summary
Catalog item management and deletion
libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx, libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx, libs/ui-components/src/components/Catalog/useCatalogItems.ts
Management actions use centralized modal state, deployment checks, managed-resource restrictions, safe external links, partial deletion handling, retry support, and in-use detection.
Catalog presentation and installed software
libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx, libs/ui-components/src/components/Catalog/InstalledSoftware.tsx, libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
Catalog titles, broken references, version updates, deprecation indicators, and item actions use resolved catalog data.

Install and edit workflows

Layer / File(s) Summary
Reference-based install flow
libs/ui-components/src/components/Catalog/InstallWizard/*
OS installation compares catalog references directly, generates conditional patches, tracks unchanged specifications, preserves catalog-linked volumes, and reports no-op completion states.
Reference-based edit flow
libs/ui-components/src/components/Catalog/EditWizard/*
Edit workflows resolve route catalog items, preserve existing application configuration, generate non-empty patches, and pass unchanged-state information to the success page.

Device, fleet, and form migration

Layer / File(s) Summary
Unified image specification model
libs/ui-components/src/types/deviceSpec.ts, libs/ui-components/src/components/form/validations.ts, libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx
Application and OS forms now support either OCI images or catalog references. Validation and field rendering handle both forms.
Device and fleet integration
libs/ui-components/src/components/Device/*, libs/ui-components/src/components/Fleet/*
Device and fleet routes provide catalog context. OS displays, creation forms, edit forms, and catalog-managed application controls consume unified image specifications.
Dynamic volume handling
libs/ui-components/src/components/DynamicForm/*
Volume selections persist catalog references, clear references when users edit image values, and use volume-specific form context state.

Promotion and supporting updates

Layer / File(s) Summary
Image promotion lookup and normalization
libs/ui-components/src/components/ImageBuilds/*, libs/ui-components/src/components/ImagePromotion/*
Promotion workflows use the shared multi-item lookup hook and normalize optional promotion fields.
Shared UI updates
libs/ui-components/src/components/form/ApplicationPortMappingField.tsx, libs/ui-components/src/components/common/*, libs/ui-components/src/components/Events/useEvents.ts, libs/ui-components/src/hooks/useAppLinks.ts
Form layouts, label rendering, event translations, and documentation links were updated.

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

Possibly related PRs

Suggested labels: ui-components, api-types, i18n

Suggested reviewers: rawagner

🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is related to the catalog deployment changes but is broad and does not identify the catalogItemRef-based deployment support.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
No-Hardcoded-Secrets ✅ Passed The PR diff adds no credential assignments, credential-bearing URLs, private keys, provider-token signatures, or base64 secrets; the existing Kubernetes Secret fixture is unchanged and contains no...
No-Weak-Crypto ✅ Passed Full branch review found no MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB, custom crypto, or secret/token comparisons; structural crypto searches and sensitive-path checks were empty.
No-Injection-Vectors ✅ Passed No eval/exec, dangerouslySetInnerHTML, unsafe yaml.load, or shell command injection vectors detected. js-yaml uses safe schema by default (v4.3.0); Kubernetes resource names follow DNS subdomain va...
Container-Privileges ✅ Passed The PR adds no container or Kubernetes manifests and no listed privilege flags; its catalog runAs: root preserves the documented existing application default.
No-Sensitive-Data-In-Logs ✅ Passed The catalog lookup warning logs only validated catalog and item identifiers; it does not log passwords, tokens, API keys, PII, session IDs, hostnames, responses, or error contents.
Resource-Leaks ✅ Passed The pull request changes no Go files under proxy/; the diff contains only TypeScript/TSX and locale files, so it adds no checked resource or goroutine leaks.
Unchecked-Errors ✅ Passed The PR changes no Go files under proxy; git diff from the PR base to HEAD contains no proxy/*.go paths, so this check is not applicable.
Ai-Attribution ✅ Passed The five pull request commits use the acceptable Made-with: Cursor trailer, and no AI-related Co-authored-by trailer appears.
Generated-Files-Not-Hand-Edited ✅ Passed No hand edits are evident: changed model files retain the OpenAPI generator header, and translation changes match updated source keys and valid parser output.
I18n-Compliance ✅ Passed All user-visible strings in .tsx files are properly wrapped in t() translation functions, and no t() calls use variables as keys. Extensive sampling of new and modified files confirms i18n complian...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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: 28

Caution

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

⚠️ Outside diff range comments (2)
libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx (1)

65-78: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle failed parent catalog resolution before initializing the form.

useCatalogItemsLookup sets isLoading to false after a failed request. This component then calls getInitialValues(undefined) and does not show the lookup error. A user can receive an uninitialized promotion form instead of an error for the parent promotion.

Read error from the lookup hook. When creating from a parent promotion, block initialization and show an error if the catalog item cannot resolve.

🤖 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 `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`
around lines 65 - 78, Update the ImagePromotionModal lookup flow to destructure
the catalog lookup error alongside catalogItemLoading. For parentPromotion
creation, handle a failed catalog item resolution before computing initialValues
by displaying the lookup error and preventing form initialization, while
preserving the existing loading state and normal resolved-item behavior.
libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx (1)

73-91: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Resolve the fleet-owned OS reference in the catalog lookup.

For a non-package-mode device, ownerFleet.spec.template.spec.os.catalogItemRef can be absent from device.spec. DeviceDetailsPage does not request that ID. useResolvedCatalogRef then uses the existing context and skips its local lookup. useSystemImage has no desired URI, so DeviceOs reports a mismatch even when the fleet-selected image is running.

Include the effective fleet OS ID in the lookup, or make useResolvedCatalogRef fall back on a context miss. Add a fleet-owned device test where the catalog reference exists only in the fleet template.

🤖 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
`@libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx`
around lines 73 - 91, Update the catalog-reference resolution used by the
non-package-mode flow around osSpec and DeviceRunningOsImage so a catalogItemRef
defined only in ownerFleet.spec.template.spec.os is resolved even when it is
absent from device.spec. Pass the effective fleet OS reference into the existing
lookup path or add the equivalent fallback in useResolvedCatalogRef, ensuring
useSystemImage receives the desired URI and DeviceOs does not report a false
mismatch. Add coverage for a fleet-owned device whose catalog reference exists
only in the fleet template.
🤖 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
`@libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx`:
- Around line 127-130: Add explicit type annotation Record<string, string> to
the reduce accumulator object (the {} initializer) and rename the reduce
callback parameter from artifact to artifactType to avoid shadowing the outer
artifact parameter. This improves type safety and clarifies the distinction
between the iteration variable and the outer scope.

In `@libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx`:
- Around line 164-174: Update CatalogItemPanelLink to parse and validate the
provided link’s protocol, allowing only http: and https: URLs; render disallowed
values as plain text rather than an anchor while preserving the existing N/A
behavior for missing links. Keep valid links using the current Button
configuration.

In `@libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx`:
- Around line 46-59: Replace the description line’s Title element in
CatalogItemTitle with Content, matching the existing versionLine rendering,
while preserving the description conditional and displayed text.
- Line 1: Update the React import in CatalogItemTitle.tsx to use the namespace
import style, matching CatalogItemDetails.tsx and InstalledSoftwareItem.tsx and
the repository convention.

In `@libs/ui-components/src/components/Catalog/CatalogPage.tsx`:
- Around line 449-454: Update the onDeleteSuccess handler in CatalogPage so
catalogFilter.setCatalogs removes only the deleted catalog’s id via its updater
form, preserving all other selected catalogs. Use the catalog being deleted as
the removal target, then keep the existing refetchCatalogs and refetch calls
unchanged.

In `@libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx`:
- Around line 127-143: Update loadCatalogItems in DeleteCatalogModal to paginate
catalog item requests: set the API limit, append each page’s mapped items to the
accumulated collection, and use each response’s metadata.continue token to fetch
subsequent pages until it is absent. Preserve the existing success and error
state handling while ensuring catalogItems contains results from every page.

In `@libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx`:
- Around line 3-5: The imports in EditAppWizard.tsx and InstallWizard/utils.ts
mix runtime and type-only symbols. Update EditAppWizard.tsx imports for
FormikErrors, WizardStepType, RJSFValidationError, and AppUpdateFormik, and
update utils.ts imports for CatalogItem, FormikHelpers, and
DynamicFormConfigFormik to use import type or inline type specifiers while
leaving runtime imports unchanged.

In `@libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx`:
- Line 2: Separate type-only imports from runtime imports in EditOsWizard.tsx
(line 2), moving WizardStepType to import type; in InstallOsWizard.tsx (line 6),
import Device, Fleet, ImageOrCatalogItemRefSpec, and PatchRequest as types; in
AppConfigStep.tsx (lines 3-4), import RJSFValidationError and FormikErrors as
types; in ReviewStep.tsx (line 20), import InstallAppFormik and InstallOsFormik
as types; and in SelectTargetStep.tsx (line 1), import Device, Fleet, and
ImageOrCatalogItemRefSpec as types. Leave runtime imports unchanged.

In `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx`:
- Around line 130-135: Update the empty-patch branch in the EditWizard update
flow to call setIsSpecUnchanged(true), since no PATCH request occurs when
allPatches is empty; keep setIsSpecUnchanged(false) after a non-empty patch is
successfully sent, and apply the same correction to the corresponding second
branch.
- Line 212: Update the active breadcrumb in EditWizard to use one t()
translation key for the complete catalogDisplayName/appName format, including
the parentheses, so translators can reorder both values; preserve the existing
omission of appName when it is unavailable.
- Around line 106-127: Validate values.channel against
catalogItemVersion.channels in EditOsWizard’s onUpdate callback before calling
buildCatalogItemRef; reject the update when the channel is not allowlisted,
while preserving the existing patch flow for valid channels.

In `@libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx`:
- Line 9: Update the import from useSpecCatalogItems so ResolvedCatalogItemData
is marked as a type-only import, matching SpecAppCatalogItem and
SpecOsCatalogItem, since it is only used in type positions within
InstalledSoftwareItem.

In `@libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx`:
- Around line 187-191: Update the validation around catalogItemVersion in
InstallOsWizard so it also verifies that values.channel exists in
catalogItemVersion.channels before creating the patch. Reject missing or
mismatched version/channel pairs through the existing setError path, while
preserving the current behavior for valid selections.
- Line 181: Replace the type-assertion-based selection in the InstallOsWizard
flow with an explicit discriminated-union branch on values.target, reading
values.device only for the device variant and values.fleet only for the fleet
variant; preserve the resulting selectedResource behavior.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx`:
- Around line 39-48: Update isOsUnchanged to compare osRef.channel with the
target catalog item's channel as part of the complete catalog reference, and
ensure both call sites pass the corresponding channel value. Preserve the no-op
result only when item, catalog, version, and channel all match.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx`:
- Around line 341-345: Synchronize the no-op state with current specifications:
in SelectTargetStep.tsx, update onTargetSelected and its dependencies so
isSpecUnchanged is recomputed when the selected target, target type, version, or
channel changes; in InstallOsWizard.tsx, set the final no-op result from
allPatches.length === 0 after the fresh resource read.

In `@libs/ui-components/src/components/Catalog/useCatalogItems.ts`:
- Around line 126-133: Update useItemIsInUse to expose the deployment request’s
loading and error state and return false unless the request has completed
successfully with a confirmed empty deployment list; keep Delete disabled during
initial loading and failures. Build the deployments endpoint using the same
encoding approach as catalogItemEndpoint for both catalogItem.metadata.catalog
and catalogItem.metadata.name.
- Around line 57-58: Update the nameFilter handling in useCatalogItems so it
trims the value before the guard and selector construction, treating
whitespace-only input as empty. Escape backslashes, commas, and whitespace
according to flightctl field-selector rules (or validate against an explicit
allow-list), while preserving ! and = as valid RHS characters, then use the
sanitized value in metadata.name contains.

In `@libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts`:
- Around line 142-148: Stabilize the shared catalog hook results by memoizing
the returned value in
libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts lines 142-148
with dependencies getItem, isLoading, and error; also memoize the os/apps
derivation and returned object in
libs/ui-components/src/components/Catalog/useSpecCatalogItems.ts lines 48-97
using spec, lookup.getItem, lookup.isLoading, and lookup.error so the apps array
identity remains stable.
- Around line 109-139: Update the async catalog lookup around fetchGet and its
cleanup to create an AbortController, pass its signal to each fetchGet request,
and call abort() when the effect cleanup runs. In the rejected-results handling,
skip console.warn and firstError assignment for expected abort failures when the
controller signal is aborted, while preserving existing handling for other
errors.

In
`@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`:
- Around line 769-799: Update the OS patch-building logic to return no patch
whenever formOsSpec.catalogItemRef is set, before the image-change handling can
generate a remove patch. Preserve the existing currentOsSpec.catalogItemRef
guard and image patch behavior for non-catalog form specifications.
- Line 65: The FullAppVolume type is imported as a value import when it should
be imported as a type-only import since it is only used for type annotations and
not as a runtime value. Update the import statement to separate FullAppVolume
into a type-only import while keeping formVolumesToApi and toFormVolumes as
regular value imports from the volumes utility module.

In
`@libs/ui-components/src/components/Device/EditDeviceWizard/SystemImageDescriptionGroup.tsx`:
- Around line 26-47: Translate both user-visible fallback messages using
hardcoded translation keys. In
libs/ui-components/src/components/Device/EditDeviceWizard/SystemImageDescriptionGroup.tsx
lines 26-47, obtain t in SystemImageDisplay and replace the '-' fallback with a
static t() key. In
libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewApplications.tsx
lines 9-16, replace the constructed template literal with a hardcoded
interpolation key passed to t(), preserving the image value through the
interpolation variable.

In `@libs/ui-components/src/components/DynamicForm/DynamicForm.tsx`:
- Line 24: Update the imports in
libs/ui-components/src/components/DynamicForm/DynamicForm.tsx at lines 24-24 and
libs/ui-components/src/components/form/validations.ts at lines 6-6: import
VolumeCatalogSelection and ImageOrCatalogItemRefSpec as type-only symbols, while
retaining AppType and ImagePullPolicy as value imports.

In `@libs/ui-components/src/components/form/validations.ts`:
- Around line 784-798: The imageSpecSchema validation test must handle an
undefined val when imageRequired is true. In imageSpecSchema, use optional
access for catalogItemRef and image so the missing value reaches
ociImageSchema(...).required(requiredMessage) and returns requiredMessage rather
than throwing; add coverage for the missing required imageSpec case.

In
`@libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts`:
- Around line 38-44: Fix the skips initialization in the existing-item promotion
utility so the fallback is applied before calling split. Update the skips
expression around values.existingItem.skips to safely produce an empty array
when unset, while preserving trimming and filtering for provided skip values.

In `@libs/ui-components/src/components/ImagePromotion/ImagePromotionForm.tsx`:
- Around line 132-140: The catalog change flow around useCatalogItems must
prevent stale items from remaining selectable during the debounced refresh.
Consume its isUpdating result, clear or disable existing-item selection while
updating, and reset the dependent existingItem values whenever values.catalog
changes before ExistingItemForm can use the previous catalog’s items.

In `@libs/ui-components/src/types/deviceSpec.ts`:
- Around line 22-23: Update the imports in deviceSpec.ts to use a type-only
import for ApplicationVolumeForm, and re-export ApplicationVolumeForm from this
public subpath so existing consumers remain compatible.

---

Outside diff comments:
In
`@libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx`:
- Around line 73-91: Update the catalog-reference resolution used by the
non-package-mode flow around osSpec and DeviceRunningOsImage so a catalogItemRef
defined only in ownerFleet.spec.template.spec.os is resolved even when it is
absent from device.spec. Pass the effective fleet OS reference into the existing
lookup path or add the equivalent fallback in useResolvedCatalogRef, ensuring
useSystemImage receives the desired URI and DeviceOs does not report a false
mismatch. Add coverage for a fleet-owned device whose catalog reference exists
only in the fleet template.

In `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`:
- Around line 65-78: Update the ImagePromotionModal lookup flow to destructure
the catalog lookup error alongside catalogItemLoading. For parentPromotion
creation, handle a failed catalog item resolution before computing initialValues
by displaying the lookup error and preventing form initialization, while
preserving the existing loading state and normal resolved-item behavior.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 64b29bae-2160-451f-809b-a9b556a0c9c1

📥 Commits

Reviewing files that changed from the base of the PR and between abcc302 and 4abebc8.

⛔ Files ignored due to path filters (15)
  • libs/i18n/locales/en/translation.json is excluded by !libs/i18n/locales/en/translation.json
  • libs/types/alpha/models/CatalogItemDeployment.ts is excluded by !libs/types/alpha/models/**
  • libs/types/alpha/models/CatalogItemDeploymentList.ts is excluded by !libs/types/alpha/models/**
  • libs/types/models/CatalogItemRefApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/CatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ComposeApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ContainerApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/DeviceOsSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/Event.ts is excluded by !libs/types/models/**
  • libs/types/models/HelmApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageOrCatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageVolumeSource.ts is excluded by !libs/types/models/**
  • libs/types/models/QuadletApplication.ts is excluded by !libs/types/models/**
📒 Files selected for processing (78)
  • libs/types/alpha/index.ts
  • libs/types/index.ts
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/VersionStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/utils.ts
  • libs/ui-components/src/components/Catalog/CatalogItemCard.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx
  • libs/ui-components/src/components/Catalog/CatalogPage.tsx
  • libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/steps/UpdateStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/UpdateSuccessPage.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/AppConfigStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/types.ts
  • libs/ui-components/src/components/Catalog/InstallWizard/utils.ts
  • libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
  • libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
  • libs/ui-components/src/components/Catalog/ResourceCatalog/ResourceCatalogPage.tsx
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/Catalog/useCatalogItems.ts
  • libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts
  • libs/ui-components/src/components/Catalog/useResolvedCatalogRef.ts
  • libs/ui-components/src/components/Catalog/useSpecCatalogItems.ts
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsCatalog.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceOs.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/SystemImageDescriptionGroup.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/DeviceTemplateStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewApplications.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewDeviceStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/useSystemImage.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/utils.ts
  • libs/ui-components/src/components/DynamicForm/DynamicForm.tsx
  • libs/ui-components/src/components/DynamicForm/FieldTemplate.tsx
  • libs/ui-components/src/components/DynamicForm/VolumeImageField.tsx
  • libs/ui-components/src/components/Events/useEvents.ts
  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/utils.ts
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsCatalog.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsContent.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx
  • libs/ui-components/src/components/Fleet/FleetRow.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/NewVersionImageBuildWizard.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionForm.tsx
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx
  • libs/ui-components/src/components/common/LabelsView.tsx
  • libs/ui-components/src/components/form/ApplicationPortMappingField.tsx
  • libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx
  • libs/ui-components/src/components/form/LabelsField.tsx
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/hooks/useAppLinks.ts
  • libs/ui-components/src/types/deviceSpec.ts
  • libs/ui-components/src/utils/catalog.ts
  • libs/ui-components/src/utils/volumes.ts
💤 Files with no reviewable changes (5)
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsCatalog.tsx
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/form/LabelsField.tsx

Comment thread libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx
Comment thread libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx Outdated
Comment thread libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
Comment thread libs/ui-components/src/components/Catalog/CatalogPage.tsx
Comment thread libs/ui-components/src/components/DynamicForm/DynamicForm.tsx Outdated
Comment thread libs/ui-components/src/components/form/validations.ts
Comment thread libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts Outdated
Comment thread libs/ui-components/src/types/deviceSpec.ts Outdated
@celdrake
celdrake force-pushed the EDM-4809-catalog-robust-api branch from 97883c6 to 3eb9c83 Compare August 4, 2026 12:53

@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: 2

Caution

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

⚠️ Outside diff range comments (3)
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx (1)

220-225: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Derive no-op status from the final specification comparison.

The success pages must report whether the submitted specification changed. Do not keep a stale selection-time value or mark an empty patch as changed.

  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx#L220-L225: set the success state from allPatches.length === 0 after the fresh resource read.
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx#L177-L182: set isSpecUnchanged to true when allPatches is empty.
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx#L340-L358: recompute or defer isSpecUnchanged when the target, version, or channel changes.
🤖 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 `@libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx`
around lines 220 - 225, Derive no-op success status from the final patch
comparison: in
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
lines 220-225, set the success state from allPatches.length === 0 after the
fresh resource read; in
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx lines
177-182, set isSpecUnchanged to true when allPatches is empty; and in
libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
lines 340-358, recompute or defer isSpecUnchanged whenever the target, version,
or channel changes.
libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts (2)

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

Use type-only imports for the new type symbols.

CatalogItemRefApplicationProviderSpec, ImageOrCatalogItemRefSpec, and ImageVolumeSource are used only in type positions. Mark them with type or move them to an import type declaration. (raw.githubusercontent.com)

Proposed import fix
-  CatalogItemRefApplicationProviderSpec,
+  type CatalogItemRefApplicationProviderSpec,
...
-  ImageOrCatalogItemRefSpec,
+  type ImageOrCatalogItemRefSpec,
-  ImageVolumeSource,
+  type ImageVolumeSource,
#!/bin/bash
set -euo pipefail
sed -n '1,30p' libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts

As per path instructions: **/*.{ts,tsx} requires types used only for checking to be imported with import type.

Also applies to: 20-22

🤖 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 `@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`
at line 9, Update the imports in deviceSpecUtils.ts so
CatalogItemRefApplicationProviderSpec, ImageOrCatalogItemRefSpec, and
ImageVolumeSource are imported via import type or marked as type-only, while
leaving any runtime imports unchanged.

Sources: Path instructions, MCP tools


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

Preserve application-type filtering when serializing volumes.

If a volume form retains mountPath after a single-container application changes to Compose or Quadlet, formVolumesToApi(app.volumes) emits the unsupported mount field. Filter volume fields by application type in both toApiComposeApp and toApiQuadletApp. Keep volume fields initialized for every application type, but include only supported fields in the API payload. Add a regression test for this application-type change.

🤖 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 `@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`
around lines 535 - 555, Update toApiComposeApp and toApiQuadletApp to serialize
volumes through application-type filtering, preventing retained mountPath values
from producing unsupported mount fields while preserving initialization of
volume fields for all types. Add a regression test covering a single-container
application changed to Compose or Quadlet and verifying only supported volume
fields reach the API payload.

Sources: Learnings, MCP tools

♻️ Duplicate comments (2)
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx (2)

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

Translate the complete active breadcrumb label.

Line 212 leaves the literal (${appName}) outside t(). Use one hardcoded translation key for the complete catalog/app format so translators can reorder both values.

Proposed fix
-<BreadcrumbItem isActive>{`${catalogDisplayName}${appName ? ` (${appName})` : ''}`}</BreadcrumbItem>
+<BreadcrumbItem isActive>
+  {appName
+    ? t('{{catalogName}} ({{appName}})', { catalogName: catalogDisplayName, appName })
+    : catalogDisplayName}
+</BreadcrumbItem>

As per coding guidelines and path instructions, user-visible strings in .tsx files must use t() with hardcoded translation keys.

🤖 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 `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx` at line
212, Update the active BreadcrumbItem label to pass the complete catalog-and-app
format through t() using one hardcoded translation key, including the
conditional app name value, so translators can reorder both values. Preserve the
catalog-only label when appName is absent and use the existing translation
mechanism.

Sources: Coding guidelines, Path instructions


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

Validate channel at every catalog-reference write boundary.

Both flows persist values.channel without proving that the selected catalog version declares it.

  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx#L120-L129: validate values.channel against catalogItemVersion.channels before calling buildCatalogItemRef.
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx#L188-L191: reject the version/channel pair unless catalogItemVersion.channels.includes(values.channel) is true.
🤖 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 `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx` around
lines 120 - 129, Validate values.channel against catalogItemVersion.channels
before each catalog-reference write: in
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx lines
120-129, reject the pair before buildCatalogItemRef; in
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
lines 188-191, reject unless
catalogItemVersion.channels.includes(values.channel) is true.
🤖 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 `@libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx`:
- Line 3: Update the import in EditAppWizard.tsx so Wizard and WizardStep are
imported as runtime values, while only WizardStepType remains in the type-only
import. Preserve the existing JSX usage of the two React components.

In `@libs/ui-components/src/types/deviceSpec.ts`:
- Line 23: The ApplicationVolumeForm type is imported from ../utils/volumes in
the deviceSpec module but is no longer exported locally, breaking the public API
for consumers of the types/deviceSpec subpath. Add a re-export statement for
ApplicationVolumeForm to preserve access for existing consumers while keeping
the internal import intact.

---

Outside diff comments:
In `@libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx`:
- Around line 220-225: Derive no-op success status from the final patch
comparison: in
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
lines 220-225, set the success state from allPatches.length === 0 after the
fresh resource read; in
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx lines
177-182, set isSpecUnchanged to true when allPatches is empty; and in
libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
lines 340-358, recompute or defer isSpecUnchanged whenever the target, version,
or channel changes.

In
`@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`:
- Line 9: Update the imports in deviceSpecUtils.ts so
CatalogItemRefApplicationProviderSpec, ImageOrCatalogItemRefSpec, and
ImageVolumeSource are imported via import type or marked as type-only, while
leaving any runtime imports unchanged.
- Around line 535-555: Update toApiComposeApp and toApiQuadletApp to serialize
volumes through application-type filtering, preventing retained mountPath values
from producing unsupported mount fields while preserving initialization of
volume fields for all types. Add a regression test covering a single-container
application changed to Compose or Quadlet and verifying only supported volume
fields reach the API payload.

---

Duplicate comments:
In `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx`:
- Line 212: Update the active BreadcrumbItem label to pass the complete
catalog-and-app format through t() using one hardcoded translation key,
including the conditional app name value, so translators can reorder both
values. Preserve the catalog-only label when appName is absent and use the
existing translation mechanism.
- Around line 120-129: Validate values.channel against
catalogItemVersion.channels before each catalog-reference write: in
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx lines
120-129, reject the pair before buildCatalogItemRef; in
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
lines 188-191, reject unless
catalogItemVersion.channels.includes(values.channel) is true.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 721f2444-232f-4852-9fee-379053532213

📥 Commits

Reviewing files that changed from the base of the PR and between 4abebc8 and 97883c6.

📒 Files selected for processing (14)
  • libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/utils.ts
  • libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/DeviceTemplateStep.tsx
  • libs/ui-components/src/components/DynamicForm/DynamicForm.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/types/deviceSpec.ts

Comment thread libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx Outdated
Comment thread libs/ui-components/src/types/deviceSpec.ts

@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

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

⚠️ Outside diff range comments (3)
libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts (1)

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

Handle catalog-reference OS transitions before building image patches.

DeviceSpecConfigFormValues.osSpec now accepts catalogItemRef in libs/ui-components/src/types/deviceSpec.ts at Line 259. getFormOsSpecPatches skips only a catalog-backed current specification and then reads formOsSpec.image.

When the current OS uses an image and the form selects a catalog reference, formOsSpec.image is undefined and the function emits a remove patch. This can delete the existing OS configuration. When the current OS uses a catalog reference and the form selects an image, the early return leaves the old catalog reference unchanged.

Serialize catalog-reference add/replace patches, or reject both unsupported transitions before the image-only logic. Add regression tests for both directions.

🤖 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 `@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`
around lines 749 - 801, Update getFormOsSpecPatches to handle transitions
between image and catalogItemRef before the image-only patch logic: either
serialize the appropriate catalogItemRef add/replace patches or reject both
unsupported directions consistently, preventing image removal or stale catalog
references. Preserve existing image-only behavior and add regression coverage
for image-to-catalog and catalog-to-image transitions.
libs/ui-components/src/components/DynamicForm/DynamicForm.tsx (1)

24-32: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve the AssetSelection export or document the breaking API change. @flightctl/ui-components is publishable and exposes source paths through wildcard exports.

🤖 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 `@libs/ui-components/src/components/DynamicForm/DynamicForm.tsx` around lines
24 - 32, The `AssetSelection` type was previously exported from DynamicForm.tsx
as part of the public API for the publishable `@flightctl/ui-components` package,
and its removal creates a breaking change. Either restore the export of
AssetSelection alongside the existing DynamicFormContext export, or document
this breaking change in release notes or a migration guide to help downstream
consumers understand the impact and how to adapt their code.
libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts (1)

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

Omitted optional fields on existing catalog-item promotions will preserve stale values; users cannot clear them.

The optionalTrimmed function and empty-array check (lines 38–50) convert blank readme, displayName, replaces, skipRange, and skips values to undefined. The comment at line 20 states this avoids false change detection when an associated catalog item updates. However, this design choice has a consequence: if the backend treats omitted optional fields as "no change" rather than "clear," a user who edits an existing promotion and empties a field will not clear that field in the stored promotion.

Verify with the backend team whether omitted optional fields in the image-promotion API request mean:

  • "Clear the field" (acceptable—current code works as intended)
  • "Leave unchanged" (problematic—users cannot clear stale data)

If the backend interprets omitted fields as unchanged, the form must distinguish between "user cleared this" (send empty string or null) and "user left this alone" (omit the field).

🤖 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
`@libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts`
around lines 20 - 25, Verify the image-promotion API semantics for omitted
optional fields with the backend team. If omission means “leave unchanged,”
update optionalTrimmed and the empty-array handling in the promotion request
builder to distinguish untouched fields from user-cleared fields, sending the
API’s supported empty value for cleared readme, displayName, replaces,
skipRange, and skips fields while continuing to omit untouched fields.
🤖 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.

Outside diff comments:
In
`@libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts`:
- Around line 749-801: Update getFormOsSpecPatches to handle transitions between
image and catalogItemRef before the image-only patch logic: either serialize the
appropriate catalogItemRef add/replace patches or reject both unsupported
directions consistently, preventing image removal or stale catalog references.
Preserve existing image-only behavior and add regression coverage for
image-to-catalog and catalog-to-image transitions.

In `@libs/ui-components/src/components/DynamicForm/DynamicForm.tsx`:
- Around line 24-32: The `AssetSelection` type was previously exported from
DynamicForm.tsx as part of the public API for the publishable
`@flightctl/ui-components` package, and its removal creates a breaking change.
Either restore the export of AssetSelection alongside the existing
DynamicFormContext export, or document this breaking change in release notes or
a migration guide to help downstream consumers understand the impact and how to
adapt their code.

In
`@libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts`:
- Around line 20-25: Verify the image-promotion API semantics for omitted
optional fields with the backend team. If omission means “leave unchanged,”
update optionalTrimmed and the empty-array handling in the promotion request
builder to distinguish untouched fields from user-cleared fields, sending the
API’s supported empty value for cleared readme, displayName, replaces,
skipRange, and skips fields while continuing to omit untouched fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b22e02e0-2a1e-4611-ac9e-408dc5177a28

📥 Commits

Reviewing files that changed from the base of the PR and between 97883c6 and 3eb9c83.

📒 Files selected for processing (13)
  • libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/utils.ts
  • libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
  • libs/ui-components/src/components/DynamicForm/DynamicForm.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/types/deviceSpec.ts

Made-with: Cursor
@celdrake
celdrake force-pushed the EDM-4809-catalog-robust-api branch from 3eb9c83 to 2041661 Compare August 5, 2026 08:50
@coderabbitai

coderabbitai Bot commented Aug 5, 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: 10

Caution

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

⚠️ Outside diff range comments (1)
libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx (1)

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

Handle catalog lookup errors before initializing the form. The modal ignores error and falls back to defaultInitialValues, changing the target to a blank new catalog item. Although validation initially disables submission, users can fill the fallback form and create the wrong promotion. Render the lookup error and prevent form submission when the lookup fails.

🤖 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 `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`
around lines 65 - 78, Handle the catalog lookup error from useCatalogItemsLookup
before building initialValues in ImagePromotionModal: render an error state
instead of falling back to defaultInitialValues, and keep the form submission
disabled when the lookup fails. Preserve the existing loading behavior and
successful catalog-item initialization for parentPromotion.
♻️ Duplicate comments (3)
libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx (1)

342-347: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

isSpecUnchanged is captured at target selection and never reconciled with the patch result. The flag originates from a row-selection callback, so it reflects the version and channel that were active at that moment. The wizard lets the user return to the specifications step and change either value afterwards. The submit handler then computes the authoritative answer from the fresh resource read but discards it, and the success page renders the stale flag.

Result: the wizard can send a real patch and still display "No action performed", or perform a genuine no-op and display "Update configuration successful". The footer label at InstallOsWizard.tsx line 95 can also read "Close" while a patch is pending.

  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx#L342-L347: recompute isSpecUnchanged when values.version or values.channel changes, not only when onTargetSelected fires. Add a React.useEffect keyed on the selected target, values.version, and values.channel.
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx#L230-L235: set the final result from allPatches.length === 0 after the fresh read, so the success page reports what actually happened. Take setFieldValue from the second argument of the Formik onSubmit handler and call setFieldValue('isSpecUnchanged', allPatches.length === 0) before setIsSuccessful(true).
🤖 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
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx`
around lines 342 - 347, The target-spec unchanged flag becomes stale after
version or channel edits. In SelectTargetStep.tsx lines 342-347, add an effect
keyed by the selected target, values.version, and values.channel to recompute
isSpecUnchanged via isSameOsSpec. In InstallOsWizard.tsx lines 230-235, obtain
setFieldValue from the Formik onSubmit second argument and set isSpecUnchanged
from allPatches.length === 0 after the fresh read, before setIsSuccessful(true).
libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx (2)

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

Move the parenthetical format into the translation key.

The parentheses around appName are user-visible text and sit outside t(). Translators cannot reorder the two names or change the punctuation.

🌐 Proposed change
-          <BreadcrumbItem isActive>{`${catalogDisplayName}${appName ? ` (${appName})` : ''}`}</BreadcrumbItem>
+          <BreadcrumbItem isActive>
+            {appName
+              ? t('{{catalogName}} ({{appName}})', { catalogName: catalogDisplayName, appName })
+              : catalogDisplayName}
+          </BreadcrumbItem>

As per coding guidelines, "Flag user-visible strings in .tsx files that are not wrapped in the t() translation function." As per path instructions, "All user-visible strings must use the t() translation function."

🤖 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 `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx` at line
212, Update the BreadcrumbItem label in EditWizard to place the full catalog/app
name format, including parentheses and spacing, inside the t() translation key;
pass catalogDisplayName and appName as interpolation values while preserving the
existing omission of parentheses when appName is absent.

Sources: Coding guidelines, Path instructions


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

Validate values.channel against the selected version before patching.

currentChannel is seeded from the channel URL search parameter at line 108. EditOsWizard validates version only. buildCatalogItemRef then copies values.channel into the persisted catalogItemRef without checking that the channel exists in catalogItemVersion.channels.

InstallOsWizard now performs this check at lines 193-202. This edit path does not, so the two OS write paths disagree. A stale or hand-edited channel parameter writes a device or fleet reference that tracks a channel which does not contain the selected version.

🐛 Proposed guard
           onUpdate={async (catalogItemVersion, values) => {
+            if (values.channel && !catalogItemVersion.channels.includes(values.channel)) {
+              throw new Error(
+                t('Invalid channel {{ channel }} for version {{ version }}', {
+                  channel: values.channel,
+                  version: catalogItemVersion.version,
+                }),
+              );
+            }
             const allPatches: PatchRequest = [];
🤖 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 `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx` around
lines 106 - 135, Validate values.channel against catalogItemVersion.channels in
the EditOsWizard onUpdate flow before constructing or submitting the patch,
matching the guard used by InstallOsWizard. Reject or surface an invalid channel
and prevent buildCatalogItemRef and patch from persisting it; keep valid-channel
updates unchanged.
🤖 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 `@libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx`:
- Line 119: Update the icon source used by CatalogItemTitle so the value from
getCatalogItemIcon is assigned to img only after validation against bundled
assets or the approved trusted origins; reject URLs and data URIs from untrusted
catalog item metadata, while preserving the existing fallback behavior for
disallowed icons.

In `@libs/ui-components/src/components/Catalog/InstalledSoftware.tsx`:
- Around line 66-94: Update SpecCatalogItemId in catalog.ts to a discriminated
union that requires appName for app entries, then remove the redundant appName
string assertions in InstalledSoftware and ResourceCatalogPage. Ensure app keys
and delete-modal resource names use the required appName while operating-system
variants continue using their formatted refs.
- Around line 45-47: Update the error branch in InstalledSoftware to import and
render getErrorMessage(error) as the Alert body, while preserving the existing
failure title and danger styling.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx`:
- Line 216: Update the volumeSelection assignment in InstallAppWizard to pass an
empty array when operating in editor mode, preventing stale selections from
reaching getCatalogApiVolumes and restoring removed YAML catalog references.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx`:
- Around line 39-55: The OS comparison logic is duplicated and disagrees on
missing channels. In
libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx#L39-L55,
delete local isOsUnchanged and import the shared predicate. In
libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx#L45-L61,
move isSameOsSpec to libs/ui-components/src/utils/catalog.ts beside
buildCatalogItemRef, export it, and import it here; preserve || '' channel
normalization as the single behavior.

In
`@libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewApplications.tsx`:
- Around line 13-14: Update the application label logic in ReviewApplications so
catalog-backed applications without a name or imageSpec.image fall back to the
formatted catalogItemRef. Preserve the existing imageSpec.image label when
available and ensure the result is not an empty string before appending the
application type.

In `@libs/ui-components/src/components/form/ApplicationPortMappingField.tsx`:
- Around line 285-378: Remove the redundant !isReadOnly conditional around
fieldContent and render the input Split content directly, eliminating the
unreachable null branch and unnecessary isDisabled={isReadOnly} props on the
port inputs. Keep the existing outer !isReadOnly guard for the input section,
and render the addedPortsContent StackItem only when addedPortsContent exists so
edit mode with no ports does not create empty gutter space.

In `@libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx`:
- Around line 33-56: Update the value selection in ImageOrCatalogRefField so
catalog-backed fields display formatCatalogItemRef(catalogRef) whenever
resolved?.imageUri is unavailable, including loading, failure, and unresolved
states; continue showing the resolved image URI when present and preserve the
existing non-catalog value behavior.

In
`@libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts`:
- Line 36: Update the new-target construction in the wizard utility to first
assign the object to a local explicitly typed as NewCatalogItemTarget, then pass
that validated local to promotionTarget. Remove the direct type assertion so
TypeScript performs structural and discriminator checks against the generated
schema.

In `@libs/ui-components/src/utils/catalog.ts`:
- Line 19: Update the TFunction import in catalog.ts to use a type-only import
because it is referenced only in type positions.

---

Outside diff comments:
In `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`:
- Around line 65-78: Handle the catalog lookup error from useCatalogItemsLookup
before building initialValues in ImagePromotionModal: render an error state
instead of falling back to defaultInitialValues, and keep the form submission
disabled when the lookup fails. Preserve the existing loading behavior and
successful catalog-item initialization for parentPromotion.

---

Duplicate comments:
In `@libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx`:
- Line 212: Update the BreadcrumbItem label in EditWizard to place the full
catalog/app name format, including parentheses and spacing, inside the t()
translation key; pass catalogDisplayName and appName as interpolation values
while preserving the existing omission of parentheses when appName is absent.
- Around line 106-135: Validate values.channel against
catalogItemVersion.channels in the EditOsWizard onUpdate flow before
constructing or submitting the patch, matching the guard used by
InstallOsWizard. Reject or surface an invalid channel and prevent
buildCatalogItemRef and patch from persisting it; keep valid-channel updates
unchanged.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx`:
- Around line 342-347: The target-spec unchanged flag becomes stale after
version or channel edits. In SelectTargetStep.tsx lines 342-347, add an effect
keyed by the selected target, values.version, and values.channel to recompute
isSpecUnchanged via isSameOsSpec. In InstallOsWizard.tsx lines 230-235, obtain
setFieldValue from the Formik onSubmit second argument and set isSpecUnchanged
from allPatches.length === 0 after the fresh read, before setIsSuccessful(true).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e3cb60f2-e61f-46c2-94db-f80ac10cad33

📥 Commits

Reviewing files that changed from the base of the PR and between abcc302 and 2041661.

⛔ Files ignored due to path filters (15)
  • libs/i18n/locales/en/translation.json is excluded by !libs/i18n/locales/en/translation.json
  • libs/types/alpha/models/CatalogItemDeployment.ts is excluded by !libs/types/alpha/models/**
  • libs/types/alpha/models/CatalogItemDeploymentList.ts is excluded by !libs/types/alpha/models/**
  • libs/types/models/CatalogItemRefApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/CatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ComposeApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ContainerApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/DeviceOsSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/Event.ts is excluded by !libs/types/models/**
  • libs/types/models/HelmApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageOrCatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageVolumeSource.ts is excluded by !libs/types/models/**
  • libs/types/models/QuadletApplication.ts is excluded by !libs/types/models/**
📒 Files selected for processing (78)
  • libs/types/alpha/index.ts
  • libs/types/index.ts
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/VersionStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/utils.ts
  • libs/ui-components/src/components/Catalog/CatalogItemCard.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx
  • libs/ui-components/src/components/Catalog/CatalogPage.tsx
  • libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/steps/UpdateStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/UpdateSuccessPage.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/AppConfigStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/types.ts
  • libs/ui-components/src/components/Catalog/InstallWizard/utils.ts
  • libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
  • libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
  • libs/ui-components/src/components/Catalog/ResourceCatalog/ResourceCatalogPage.tsx
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/Catalog/specCatalogItems.ts
  • libs/ui-components/src/components/Catalog/useCatalogItems.ts
  • libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts
  • libs/ui-components/src/components/Catalog/useResolvedCatalogRef.ts
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsCatalog.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceOs.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/SystemImageDescriptionGroup.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/DeviceTemplateStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewApplications.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewDeviceStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/useSystemImage.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/utils.ts
  • libs/ui-components/src/components/DynamicForm/DynamicForm.tsx
  • libs/ui-components/src/components/DynamicForm/FieldTemplate.tsx
  • libs/ui-components/src/components/DynamicForm/VolumeImageField.tsx
  • libs/ui-components/src/components/Events/useEvents.ts
  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/utils.ts
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsCatalog.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsContent.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx
  • libs/ui-components/src/components/Fleet/FleetRow.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/NewVersionImageBuildWizard.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionForm.tsx
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx
  • libs/ui-components/src/components/common/LabelsView.tsx
  • libs/ui-components/src/components/form/ApplicationPortMappingField.tsx
  • libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx
  • libs/ui-components/src/components/form/LabelsField.tsx
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/hooks/useAppLinks.ts
  • libs/ui-components/src/types/deviceSpec.ts
  • libs/ui-components/src/utils/catalog.ts
  • libs/ui-components/src/utils/volumes.ts
💤 Files with no reviewable changes (4)
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/form/LabelsField.tsx

Comment thread libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
Comment thread libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
Comment thread libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
Comment thread libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx Outdated
Comment thread libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx
Comment thread libs/ui-components/src/utils/catalog.ts
Comment thread libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
Made-with: Cursor
@celdrake
celdrake force-pushed the EDM-4809-catalog-robust-api branch from 2041661 to 06b31a7 Compare August 5, 2026 09:16
@coderabbitai

coderabbitai Bot commented Aug 5, 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: 9

Caution

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

⚠️ Outside diff range comments (1)
libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx (1)

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

Show the catalog lookup error before initializing the form.

If the parent promotion has a catalog reference and its lookup fails, useCatalogItemsLookup clears catalogItemLoading and returns no item. Line 78 then calls getInitialValues(undefined). The modal opens with fallback values instead of preserving or reporting the failed parent reference.

Read and display the lookup error. Keep the form blocked while a requested catalog item is unresolved. Do not initialize parent-promotion values without the referenced item.

🤖 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 `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`
around lines 65 - 78, Update ImagePromotionModal around useCatalogItemsLookup,
getItem, and the parentPromotion initialization path to read and display the
catalog lookup error before initializing the form. Keep the modal blocked while
a requested catalog item is loading or unresolved, surface the lookup failure
when present, and do not call parent-promotion initial-value logic unless the
referenced catalog item exists.
♻️ Duplicate comments (4)
libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx (1)

127-143: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Paginate the catalog item request before deletion.

loadCatalogItems sends no limit and ignores metadata.continue in the response. The list endpoint returns paginated results, so only the first page is loaded. deleteCatalogItems then deletes only that page, returns true, and deleteCatalogAndItems removes the catalog while unmapped items remain on the server. The user sees success and the failure table stays empty.

useCatalogItems.ts already implements the paging pattern in this codebase. Set a limit, accumulate each page, and follow metadata.continue until it is absent.

🤖 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 `@libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx` around
lines 127 - 143, Update loadCatalogItems to paginate catalog item requests like
the existing useCatalogItems.ts pattern: set a page limit, accumulate items
across responses, and continue requesting pages using metadata.continue until
absent. Map the complete accumulated collection into catalogItems so
deleteCatalogItems processes every item before catalog deletion.
libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx (1)

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

Type the reduce accumulator and rename the shadowing callback parameter.

The {} accumulator permits an implicit any index write. The callback parameter artifact also shadows the outer values.artifacts.map((artifact, index) => ...) parameter at Line 116, which makes the block harder to read and easy to break during future edits.

♻️ Proposed fix
-                                items={artifactTypes.reduce((acc, artifact) => {
-                                  acc[artifact.type] = getArtifactLabel(t, artifact);
+                                items={artifactTypes.reduce<Record<string, string>>((acc, artifactType) => {
+                                  acc[artifactType.type] = getArtifactLabel(t, artifactType);
                                   return acc;
-                                }, {})}
+                                }, {})}
🤖 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
`@libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx`
around lines 127 - 130, Update the reduce call in TypeConfigStep so its
accumulator is explicitly typed as the string-label mapping expected by items,
eliminating the implicit-any index write. Rename the reduce callback parameter
from artifact to a distinct name, and update its references without changing the
surrounding values.artifacts mapping.
libs/ui-components/src/components/Catalog/InstalledSoftware.tsx (1)

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

Show the error detail in the failure Alert.

The Alert has a title but no body. The user sees no cause and cannot act. Sibling catalog components render getErrorMessage(error) inside the Alert.

♻️ Proposed fix
   if (error) {
-    return <Alert isInline variant="danger" title={t('Failed to load installed software')} />;
+    return (
+      <Alert isInline variant="danger" title={t('Failed to load installed software')}>
+        {getErrorMessage(error)}
+      </Alert>
+    );
   }

Add the import:

import { getErrorMessage } from '../../utils/error';
🤖 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 `@libs/ui-components/src/components/Catalog/InstalledSoftware.tsx` around lines
45 - 47, Update InstalledSoftware’s failure Alert to include the underlying
error text instead of only the title. Import and reuse getErrorMessage in
InstalledSoftware.tsx, and render its result inside the existing Alert body when
the error branch is hit so the user sees the cause, matching the pattern used by
sibling catalog components.
libs/ui-components/src/components/Catalog/CatalogPage.tsx (1)

449-454: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove only the deleted catalog from the filter.

catalogFilter.setCatalogs([]) clears every selected catalog. If a user selects catalogs A and B and deletes A, the selection for B is also lost and the item list silently widens. setCatalogs accepts an updater in this file (Lines 275-277), so a targeted removal is available.

♻️ Proposed fix
           onDeleteSuccess={() => {
+            const deletedId = catalogToDelete.metadata.name || '';
             setCatalogToDelete(undefined);
-            catalogFilter.setCatalogs([]);
+            catalogFilter.setCatalogs((catalogs) => catalogs.filter((c) => c !== deletedId));
             refetchCatalogs();
             refetch();
           }}
🤖 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 `@libs/ui-components/src/components/Catalog/CatalogPage.tsx` around lines 449 -
454, Update the onDeleteSuccess handler in CatalogPage so deleting one catalog
only removes that catalog from the current selection instead of clearing all
selections. Replace the blanket catalogFilter.setCatalogs([]) call with the
updater form already used in this file for setCatalogs, filtering out the
deleted catalog while preserving any other selected catalogs before
refetchCatalogs() and refetch() run.
🤖 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 `@libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx`:
- Around line 24-36: Remove the local toFetchIds deduplication in
CatalogItemsProvider and reuse an exported helper from catalog.ts that performs
the shared catalog-item cache-key deduplication. Update the provider’s memoized
ids calculation to call that helper, while preserving the existing extraction
and fetch behavior.

In `@libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx`:
- Line 104: Update the image alt text in CatalogItemTitle to use the t()
translation function with a hardcoded interpolation key, passing
item.metadata.name or an empty string as the name value so missing metadata does
not produce “undefined icon.”

In `@libs/ui-components/src/components/Catalog/InstalledSoftware.tsx`:
- Around line 66-93: Model SpecCatalogItemId in catalog.ts as a discriminated
union on type, requiring appName for app variants, then remove the type
assertions at InstalledSoftware.tsx lines 66-93 by using the narrowed appName
for the fragment key and resourceName, and at ResourceCatalogPage.tsx lines
44-58 by passing the narrowed appName to getRemoveAppPatches; do not add
fallback ref formatting once the type change is applied.

In `@libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx`:
- Line 3: Update the import containing ActionsColumn and IAction so IAction is
imported type-only while ActionsColumn remains a runtime import, preserving
their existing usage in the component.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx`:
- Line 80: Thread the no-op result from the empty allPatches path through
InstallAppWizardContentProps and the InstallAppWizard content flow, replacing
the hardcoded isSpecUnchanged={false} on UpdateSuccessPage with the tracked
value. Match the existing propagation pattern used by InstallOsWizard and
EditWizard so UpdateSuccessPageContent displays the no-op state when no patch is
submitted.

In `@libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx`:
- Around line 230-235: Synchronize the no-op flag with the authoritative
submit-time result: in
libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx#L230-L235,
set isSpecUnchanged from allPatches.length === 0 using the Formik helpers in
onSubmit before setIsSuccessful(true) in both branches; in
libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx#L323-L333,
recompute the flag whenever values.device, values.fleet, values.version, or
values.channel changes rather than only in the row-select handler, so the footer
and success page reflect the actual flow result.

In
`@libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx`:
- Around line 28-38: Update both isOsUpdate call sites in the ReviewStep
component to pass values.channel as the channel argument, and extend isOsUpdate
to compare the referenced channel with that value. Ensure same-item changes to
version or channel render the “Version update” alert instead of the OS
replacement alert.

In `@libs/ui-components/src/components/Catalog/useCatalogItems.ts`:
- Around line 45-55: Update isInvalidSelection in the catalog query-building
logic to account for the category constraint already produced by the preceding
category-filter handling. Append !spec.type only when the excluded type is the
sole remaining type and no category constraint was generated; otherwise preserve
the category-filtered query so combinations such as all application types plus
an excluded OS type return application items.

In `@libs/ui-components/src/components/Device/DeviceDetails/DeviceOs.tsx`:
- Around line 34-41: Replace the repeated optional property access and type
assertion with a local catalogItemRef in DeviceOs.tsx at lines 34-41; derive
hasCatalogRef from it and pass it directly to formatCatalogItemRef. Apply the
same change in SystemImageDescriptionGroup.tsx at lines 59-65: derive
isCatalogItemRef from catalogItemRef and format that narrowed value directly,
preserving the existing behavior.

---

Outside diff comments:
In `@libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx`:
- Around line 65-78: Update ImagePromotionModal around useCatalogItemsLookup,
getItem, and the parentPromotion initialization path to read and display the
catalog lookup error before initializing the form. Keep the modal blocked while
a requested catalog item is loading or unresolved, surface the lookup failure
when present, and do not call parent-promotion initial-value logic unless the
referenced catalog item exists.

---

Duplicate comments:
In
`@libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx`:
- Around line 127-130: Update the reduce call in TypeConfigStep so its
accumulator is explicitly typed as the string-label mapping expected by items,
eliminating the implicit-any index write. Rename the reduce callback parameter
from artifact to a distinct name, and update its references without changing the
surrounding values.artifacts mapping.

In `@libs/ui-components/src/components/Catalog/CatalogPage.tsx`:
- Around line 449-454: Update the onDeleteSuccess handler in CatalogPage so
deleting one catalog only removes that catalog from the current selection
instead of clearing all selections. Replace the blanket
catalogFilter.setCatalogs([]) call with the updater form already used in this
file for setCatalogs, filtering out the deleted catalog while preserving any
other selected catalogs before refetchCatalogs() and refetch() run.

In `@libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx`:
- Around line 127-143: Update loadCatalogItems to paginate catalog item requests
like the existing useCatalogItems.ts pattern: set a page limit, accumulate items
across responses, and continue requesting pages using metadata.continue until
absent. Map the complete accumulated collection into catalogItems so
deleteCatalogItems processes every item before catalog deletion.

In `@libs/ui-components/src/components/Catalog/InstalledSoftware.tsx`:
- Around line 45-47: Update InstalledSoftware’s failure Alert to include the
underlying error text instead of only the title. Import and reuse
getErrorMessage in InstalledSoftware.tsx, and render its result inside the
existing Alert body when the error branch is hit so the user sees the cause,
matching the pattern used by sibling catalog components.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 7b661e03-c021-4bee-bf29-74c87f419f5c

📥 Commits

Reviewing files that changed from the base of the PR and between abcc302 and 06b31a7.

⛔ Files ignored due to path filters (15)
  • libs/i18n/locales/en/translation.json is excluded by !libs/i18n/locales/en/translation.json
  • libs/types/alpha/models/CatalogItemDeployment.ts is excluded by !libs/types/alpha/models/**
  • libs/types/alpha/models/CatalogItemDeploymentList.ts is excluded by !libs/types/alpha/models/**
  • libs/types/models/CatalogItemRefApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/CatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ComposeApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ContainerApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/DeviceOsSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/Event.ts is excluded by !libs/types/models/**
  • libs/types/models/HelmApplication.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageApplicationProviderSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageOrCatalogItemRefSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageSpec.ts is excluded by !libs/types/models/**
  • libs/types/models/ImageVolumeSource.ts is excluded by !libs/types/models/**
  • libs/types/models/QuadletApplication.ts is excluded by !libs/types/models/**
📒 Files selected for processing (78)
  • libs/types/alpha/index.ts
  • libs/types/index.ts
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/TypeConfigStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/steps/VersionStep.tsx
  • libs/ui-components/src/components/Catalog/AddCatalogItemWizard/utils.ts
  • libs/ui-components/src/components/Catalog/CatalogItemCard.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemDetails.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
  • libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx
  • libs/ui-components/src/components/Catalog/CatalogPage.tsx
  • libs/ui-components/src/components/Catalog/DeleteCatalogModal.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditAppWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditOsWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/EditWizard.tsx
  • libs/ui-components/src/components/Catalog/EditWizard/steps/UpdateStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallAppWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallOsWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/InstallWizard.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/UpdateSuccessPage.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/AppConfigStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SelectTargetStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/steps/SpecificationsStep.tsx
  • libs/ui-components/src/components/Catalog/InstallWizard/types.ts
  • libs/ui-components/src/components/Catalog/InstallWizard/utils.ts
  • libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
  • libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
  • libs/ui-components/src/components/Catalog/ResourceCatalog/ResourceCatalogPage.tsx
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/Catalog/specCatalogItems.ts
  • libs/ui-components/src/components/Catalog/useCatalogItems.ts
  • libs/ui-components/src/components/Catalog/useCatalogItemsLookup.ts
  • libs/ui-components/src/components/Catalog/useResolvedCatalogRef.ts
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsCatalog.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsPage.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceDetailsTabContent/ConfigurationsContent.tsx
  • libs/ui-components/src/components/Device/DeviceDetails/DeviceOs.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/EditDeviceWizard.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/SystemImageDescriptionGroup.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/deviceSpecUtils.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationHelmForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationImageForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationTemplates.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVariablesForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationVolumeForm.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/DeviceTemplateStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewApplications.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ReviewDeviceStep.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/useSystemImage.ts
  • libs/ui-components/src/components/Device/EditDeviceWizard/utils.ts
  • libs/ui-components/src/components/DynamicForm/DynamicForm.tsx
  • libs/ui-components/src/components/DynamicForm/FieldTemplate.tsx
  • libs/ui-components/src/components/DynamicForm/VolumeImageField.tsx
  • libs/ui-components/src/components/Events/useEvents.ts
  • libs/ui-components/src/components/Fleet/CreateFleet/CreateFleetWizard.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/steps/ReviewStep.tsx
  • libs/ui-components/src/components/Fleet/CreateFleet/utils.ts
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsCatalog.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsContent.tsx
  • libs/ui-components/src/components/Fleet/FleetDetails/FleetDetailsPage.tsx
  • libs/ui-components/src/components/Fleet/FleetRow.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/NewVersionImageBuildWizard.tsx
  • libs/ui-components/src/components/ImageBuilds/NewVersionImageBuildWizard/utils.ts
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionForm.tsx
  • libs/ui-components/src/components/ImagePromotion/ImagePromotionModal.tsx
  • libs/ui-components/src/components/common/LabelsView.tsx
  • libs/ui-components/src/components/form/ApplicationPortMappingField.tsx
  • libs/ui-components/src/components/form/ImageOrCatalogRefField.tsx
  • libs/ui-components/src/components/form/LabelsField.tsx
  • libs/ui-components/src/components/form/validations.ts
  • libs/ui-components/src/hooks/useAppLinks.ts
  • libs/ui-components/src/types/deviceSpec.ts
  • libs/ui-components/src/utils/catalog.ts
  • libs/ui-components/src/utils/volumes.ts
💤 Files with no reviewable changes (4)
  • libs/ui-components/src/components/Catalog/const.ts
  • libs/ui-components/src/components/Catalog/utils.ts
  • libs/ui-components/src/components/form/LabelsField.tsx
  • libs/ui-components/src/components/Device/EditDeviceWizard/steps/ApplicationContainerForm.css

Comment thread libs/ui-components/src/components/Catalog/CatalogItemsContext.tsx
Comment thread libs/ui-components/src/components/Catalog/CatalogItemTitle.tsx
Comment thread libs/ui-components/src/components/Catalog/InstalledSoftware.tsx
Comment thread libs/ui-components/src/components/Catalog/InstalledSoftwareItem.tsx
Comment thread libs/ui-components/src/components/Catalog/useCatalogItems.ts
Comment thread libs/ui-components/src/components/Device/DeviceDetails/DeviceOs.tsx
@celdrake
celdrake merged commit 3372a39 into flightctl:main Aug 5, 2026
10 checks passed
@celdrake
celdrake deleted the EDM-4809-catalog-robust-api branch August 5, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants