# Problem Statement: QSP-38296 — Use Smartscape title as component display name

> Re-initialised for clean regeneration on 2026-03-16 against current HEAD.

---

## Approved Requirements Snapshot

### Summary

Replace the Backstage catalog ID (e.g. `component:dt-platform/edge-service`) with the human-readable Smartscape `title` field as the displayed component name across the Overview page, Components page, and AppScorePage heading. The catalog ID continues to be used for navigation URLs, routing, and data fetching — the title is a **display-only** change. Both DQL data queries are extended with a `CATALOG_COMPONENT` Smartscape lookup to resolve the title. If a component has no title, the catalog ID is used as a fallback.

### Acceptance Criteria

- [ ] AC-1: On the **Overview page**, asset-level rows in the capability → team → asset tree display the Smartscape `title` instead of the catalog ID.
- [ ] AC-2: On the **Components page**, the "Name" column displays the Smartscape `title` instead of the catalog ID. The "Asset ID" column remains unchanged.
- [ ] AC-3: On the **AppScorePage**, the heading displays the Smartscape `title` instead of the uppercased catalog ID.
- [ ] AC-4: **Navigation** from the Components table to AppScorePage still uses the catalog ID in the URL (`/components/<catalogId>`). Routes and URL params are unchanged.
- [ ] AC-5: If a component has **no title** in Smartscape (null/undefined), the catalog ID is shown as a fallback on all affected pages.
- [ ] AC-6: **Filtering** on the Components page continues to work — the free-text search matches against all fields (including the title and the catalog ID).
- [ ] AC-7: **Filtering** on the Overview page continues to work — the capability search filter is unaffected.

### Key Decisions

| # | Decision | Rationale |
|---|---|---|
| D-1 | Add `CATALOG_COMPONENT` Smartscape `lookup` directly in both DQL queries (single-query approach) | Avoids a separate client-side join; keeps data fetching self-contained. The lookup pattern is already proven in `component-smartscape-node.ts`. |
| D-2 | Use `if(isNotNull(component.title), component.title, else: catalogId)` for fallback | Handles missing titles at the DQL level — no client-side fallback logic needed. |
| D-3 | Add `catalogId` field to `OverviewColumnData` for navigation | Decouples display name (`name` = title) from navigation identifier (`catalogId` = catalog ID). |
| D-4 | Pass title to AppScorePage via `?title=` URL query parameter | Lightweight, no extra Grail query. Survives page refresh and direct URL sharing. Falls back to URL path param `name` when `?title=` is absent. |
| D-5 | `IdentifierNode` interface unchanged | The `name` field semantics change (title instead of catalog ID) but the type stays the same. |
| D-6 | URLs and routing completely unchanged | `:name` route param continues to hold the catalog ID. |
| D-7 | Extract `mapAssetsToOverviewColumnData`, `buildComponentNavigationUrl`, `resolveDisplayName` to `componentMappingUtils.ts` | Follows the project's utils extraction rule — keeps components thin, enables unit testing. |

### Files to Create

| File | Purpose |
|---|---|
| `src/app/utils/componentMappingUtils.ts` | Pure utils: `mapAssetsToOverviewColumnData()`, `buildComponentNavigationUrl()`, `resolveDisplayName()` |
| `src/app/utils/componentMappingUtils.spec.ts` | Unit tests for all three utils functions |

### Files to Modify

| File | Change |
|---|---|
| `src/app/api/fetch-assets-for-overview.ts` | Add `CATALOG_COMPONENT` lookup to `QUERY_CAPABILITY_TEAM_RELATIONS`; use title in asset record `name` with fallback. |
| `src/app/api/fetch-all-assets-from-component-report.ts` | Add `CATALOG_COMPONENT` lookup to `QUERY_OVERVIEW_ASSETS`; add `displayName` field; update `Asset` interface. |
| `src/app/pages/Components.tsx` | Add `catalogId` to `OverviewColumnData`; use `mapAssetsToOverviewColumnData` from utils; fix navigation to use `catalogId` with `buildComponentNavigationUrl`. |
| `src/app/pages/AppScorePage.tsx` | Read title from `useSearchParams().get('title')`; fall back to URL param; use `resolveDisplayName` from utils; display in heading without `.toUpperCase()`. |
| `src/app/components/overview-aggregation-view/ComponentsTabContent.tsx` | Use `mapAssetsToOverviewColumnData` from utils instead of inline mapping. |
| `src/app/api/fetch-all-assets-from-component-report.spec.ts` | Update existing test to include `displayName`; add tests for lookup and displayName in query string. |
| `src/app/api/fetch-assets-for-overview.spec.ts` | Update existing test asset names (now titles); add tests for lookup and `component.title` fallback in query string. |
| `src/app/utils/filter.utils.spec.ts` | Add tests verifying filter works against `catalogId` and display name fields. |
| `component/mocks/grail/components-grail-response.ts` | Add `displayName` field to each mocked record. |
| `component/mocks/components-mocks.ts` | Add `catalogId` field to each `OverviewColumnData` entry. |
| `component/tests/pages/components-page.spec.tsx` | Update expected text in filter tests: first column now shows display name (title), not catalog ID. |

### Implementation Plan

| Step | Description | Files |
|---|---|---|
| 1 | Modify Overview DQL query with `CATALOG_COMPONENT` lookup | `fetch-assets-for-overview.ts` |
| 2 | Modify Components DQL query, add `displayName` field, update `Asset` interface | `fetch-all-assets-from-component-report.ts` |
| 3 | Create `componentMappingUtils.ts` with `mapAssetsToOverviewColumnData`, `buildComponentNavigationUrl`, `resolveDisplayName` | `componentMappingUtils.ts` |
| 4 | Update `Components.tsx`: add `catalogId` to `OverviewColumnData`, use utils for mapping and navigation | `Components.tsx` |
| 5 | Update `AppScorePage.tsx`: read `?title=` query param, use `resolveDisplayName`, display without `.toUpperCase()` | `AppScorePage.tsx` |
| 6 | Update `ComponentsTabContent.tsx` to use `mapAssetsToOverviewColumnData` from utils | `ComponentsTabContent.tsx` |
| 7 | Write/update unit tests | `componentMappingUtils.spec.ts`, `fetch-all-assets-from-component-report.spec.ts`, `fetch-assets-for-overview.spec.ts`, `filter.utils.spec.ts` |
| 8 | Update component test mocks and assertions | `components-grail-response.ts`, `components-mocks.ts`, `components-page.spec.tsx` |
| 9 | Run validation (`npm run typecheck`, `npm run lint`, `npm run test:unit`, `npm run test:ct`) | — |

---

## Current HEAD Source State (2026-03-16)

> These are the files as they exist at HEAD **before** any QSP-38296 changes are applied.
> The coding agent must read these files to understand the starting point.

### `src/app/api/fetch-all-assets-from-component-report.ts` (HEAD)

- DQL query `QUERY_OVERVIEW_ASSETS`: no `lookup`, no `displayName` field. Returns `assetId`, `assetName`, `entityOwner`, `componentType`.
- `Asset` interface: `{ assetId, assetName, entityOwner, componentType }` — no `displayName`.

### `src/app/api/fetch-assets-for-overview.ts` (HEAD)

- DQL query `QUERY_CAPABILITY_TEAM_RELATIONS`: no `CATALOG_COMPONENT` lookup. Asset record `name` is `right.assetId` (catalog ID).
- No changes to `CapabilityRelation` or `IdentifierNode` interfaces.

### `src/app/pages/Components.tsx` (HEAD)

- `OverviewColumnData` type: `{ name, assetId, owner, grade, componentType }` — no `catalogId`.
- Inline `mapToOverviewColumnData` maps `r.assetName` → `name`.
- Navigation: `<Navigate to={'/components/' + tableData[Number(activeRow)].name} />` — uses `name` (which is `assetName`).

### `src/app/pages/AppScorePage.tsx` (HEAD)

- Uses `useParams<{ name: string }>()` to get the component name.
- Heading displays `name.toUpperCase()`.
- No `useSearchParams`, no `useLocation`.

### `src/app/components/overview-aggregation-view/ComponentsTabContent.tsx` (HEAD)

- Inline `tableData` mapping: `r.assetName` → `name`, `r.assetId` → `assetId`. No `catalogId`, no `displayName`.

### `src/api/catalog/component-smartscape-node.ts` (reference only)

- Contains the `CATALOG_COMPONENT` Smartscape query pattern used for single-component lookups.
- Key pattern to reuse: `fieldsAdd assetId = concat(kind,":",namespace,"/",name)` to construct the catalog ID from Smartscape fields.
- The `title` field is available on `CATALOG_COMPONENT` nodes.

### Existing test files at HEAD

- `fetch-all-assets-from-component-report.spec.ts`: 1 test (`Returns assets`), mocked records have no `displayName`.
- `fetch-assets-for-overview.spec.ts`: 1 test (`Returns relations`), mocked asset names are catalog IDs.
- `filter.utils.spec.ts`: 3 smoke tests, no `OverviewColumnData`-shaped test data.
- `component/mocks/grail/components-grail-response.ts`: 11 mocked records, no `displayName`.
- `component/mocks/components-mocks.ts`: 3 entries, no `catalogId`.
- `component/tests/pages/components-page.spec.tsx`: filter tests expect `'jira'` (catalog ID) in first column.

---

## Implementation Log

> Append-only. Each entry is tagged with the agent and timestamped.

*(Empty — clean regeneration starting point)*
