#29754·argo-cd

Refactor of the ApplicationSet UI coupling

Author: agaudreaultCreated Sep 16, 2026Updated Sep 17, 2026
Labelsenhancementtriage/pendingtype:tech-debtcomponent:application-setscomponent:ui

Summary

When the ApplicationSet UI was added, the applications files were used and if conditions where added to handle the ApplicationSet use case. This has become a limitation to introduce new features to the appset UI. While many components must be reused like the resource tree and list views, they are currently not re-usable as shared components.

Motivation

Ensure the ApplicationSet UI can grow without exponentially increasing the complexity of the UI components

Proposal

Since this is a big refactor with multiple file move and partial extraction, this cannot be done in one big-bang refactor PR. This would simply have too much risk of merge conflicts without a major code freeze. Instead, most of the existing components can be extracted on by one, to eventually be able to rework highly coupled components such as the application ResourceDetail.

Structure

  • shared/components/resource/* — per-kind resource building blocks (a node/resource kind and how it's displayed).
  • shared/components/resource-helpers/* + shared/components/app-utils — kind-agnostic logic/helpers.
  • shared/components/views/* — the list/details/tree view scaffolding shared by the Application and ApplicationSet screens.
  • applications/ and applicationsets/ — only genuinely kind-specific code.

Phase 0 — Scaffolding (must come first)

  • applicationsets/ module + route split. Create the applicationsets/ folder and ApplicationSetsContainer; register the /applicationsets lazy route in app.tsx. Carve the appset entry point out of the applications list/details so there's a place to receive extracted code.
  • shared/components/app-utils.tsx. Extract the kind-agnostic status primitives + type guards + appset domain logic: ComparisonStatusIcon, HealthStatusIcon, isApp, appInstanceName, appQualifiedName, getAppSetHealthStatus, getAppSetConditionCategory, isAppNode, isAppSetNode, getApplicationParentRef. Depended on by almost everything below.

Phase 1 — Leaf primitives (Tier 0)

  • resource-iconshared/components/resource/.
  • resource-labelshared/components/resource/.
  • resources.ts (kind/status maps) → shared/components/resource/.
  • resource-customizations.tsshared/components/resource/.
  • application-urls.tsxshared/components/resource/.
  • view-type-switchershared/components/ (only needs shared context/services).
  • filter widget (Filter/FiltersGroup + scss) → shared/components/filter/. Prerequisite for every filter and for resource-list/resource-filter.

Phase 2 — Helper modules (Tier 1)

  • resource-helpers — extract kind-agnostic helpers from utils.tsx (nodeKey, getAppUrl, getAppListLink, PodHealthIcon, getPodStateReason, formatCreationTimestamp, resourceStatusToResourceNode, handlePageVisibility, getApplicationLinkURLFromNode, userMsgsList, source helpers…) into shared/components/resource-helpers/; re-export from utils.tsx. Breaks the utils ↔ tree cycle; keystone for Phase 3–4.
  • list-search — consolidate the duplicated createMatcher (applications + resources) into shared/components/list-search.ts (+ merged test).
  • filters modulelabel-selector, selectors (createMetadataSelector), favorite (favoriteKey/isFavorite/toggleFavorite), FavoriteFilter, LabelsFiltershared/components/filters/; re-export favorites through utils.tsx.

Phase 3 — Mid resource components (Tier 2)

  • application-resource-sortshared/components/resource/.
  • application-resource-filter (+ test) → shared/components/resource/ (needs filter widget + resource-helpers).
  • application-resource-list (+ scss) → shared/components/resource/.
  • resource-highlight-pulse.scssshared/components/resource/ (shared scss partial imported by list + tree; move before/with the tree).

Phase 4 — Heavy resource components (Tier 3)

  • application-resource-tree/shared/components/resource/.
  • application-pod-view/shared/components/resource/.
  • application-node-info/shared/components/resource/.
  • application-resources-diff/shared/components/resource/.

Phase 5 — Shared view scaffolding (shared/components/views/)

  • shared/components/views/details/tree-filter.tsNodeInfo, SelectNode, getTreeFilter, filterTreeNode, wildcard/escape helpers.
  • shared/components/views/tree/load-application-info.tsloadApplicationInfo.
  • shared/components/views/tree/resource-tree-toolbar.tsx — graph options panel (zoom/expand/group/name direction).
  • shared/components/views/tree/application-details-filters.tsx — filter portal wrapper.
  • shared/components/views/list/use-items-per-container.ts — list layout hook shared by both list views.

Phase 6 — Per-kind split (this is the actual refactor of the most entangled components)

  • ApplicationDetails → app-only. Drop objectListKind, collapse every isApplication branch to its app side, remove appset imports; compose the Phase 5 scaffolding.
  • ApplicationSetDetails — new thin appset view composing the same scaffolding (no branching).
  • App-dropdown splitApplicationsDetailsAppDropdown app-only (hardcode argo-icon-application); duplicate as ApplicationSetsDetailsAppDropdown (hardcode argo-icon-applicationset) + co-located scss.
  • Filter splitApplicationsFilter app-only; new applicationsets-filter reusing FavoriteFilter/LabelsFilter.
  • Status bar splitAppsStatusBar app-only; AppSetsStatusBarapplicationsets/.
  • Conditions splitapplicationset-conditions (+ co-located scss).
  • Status panel splitappset-status-panel (+ co-located scss).
  • Per-kind list rows/tiles/summaryappset-tile, appset-table-row, application-sets-summary in applicationsets/, importing icons/helpers from shared (not applications/).

Phase 7 — Resource details (the complex tail, last)

  • App resource actions — extract deleteApplication (+ isChildApplication) into shared resource-helpers/resource-actions.ts; re-export from utils.tsx.
  • appset-generated-app-details — trimmed appset view for generated-app + root-appset nodes only (SUMMARY/EVENTS), reusing shared header/tree/diff.
  • Invert AppSetResourceNodePreview — remove the applications → applicationsets import from ResourceDetails via an injected node-preview seam.
  • Shared ResourceDetailsHeadershared/components/views/details (icon/label/health/comparison/links/actions).
  • Shared ResourceTabs shellshared/components/views/details (Tabs + extension wiring + manifest/YAML/EVENTS), with app-only tabs (ApplicationParameters, ApplicationSummary) injected.
  • applications/ResourceDetails → shared shell. Collapse into a thin composition over shared header + tabs + injected preview; repoint resources/resources-details-panel; optionally retire the appset duplicate onto the shared shell.

All these items are the result of a coupling analysis and are subject to changes as the implementation progresses.