# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Commands

```bash
npm run start          # dev server (opens app in Dynatrace Platform UI)
npm run build          # production build
npm run deploy         # deploy to Dynatrace environment
npm run typecheck      # tsc check (run after every file change)
npm run lint           # eslint check
npx eslint --fix <file>  # autofix lint (do this before manual fixes)
```

### Tests

```bash
npm run test:app                                              # unit tests (Jest)
npm run test:app -- --testPathPattern="<pattern>"            # single file
npm run test:app -- --testPathPattern="<p>" --coverage --collectCoverageFrom="<glob>"
npm run test:ct                                              # component tests (Playwright CT)
npm run test:e2e                                             # E2E tests (Playwright)
npm run test:a11y                                            # accessibility tests
npm run test:it                                              # integration tests
npm run test:dql                                             # DQL sandbox tests
```

Coverage gate: all four Jest metrics (statements, branches, functions, lines) ≥ 90%.

## Architecture

This is a Dynatrace App with two distinct runtime contexts:

### Backend (`src/api/`)
Dynatrace App Functions — run server-side on the DT platform.
- `generate-scorecards-result-event.function.ts` — triggered per component+version; queries signal events via DQL, computes a scorecard result, and ingests a BizEvent
- `scorecard-results.function.ts` — returns stored scorecard results for a given asset

Definitions that drive both backend and frontend live in `src/api/constants/`:
- `scorecard.definitions.ts` — defines scorecard types (e.g. `platform-apps-scorecard`), their matching criteria, and which signals apply
- `signal.definitions.ts` — defines all signals (id, title, category, `versionSpecific`, `blocking`)
- `defaults.constants.ts` — version regex, asset ID pattern

### Frontend (`src/app/`)
React app consuming Grail DQL data.

**Data flow:**
1. `ScorecardsDataContextProvider` (context root) fetches assets + scorecard results in parallel from Grail
2. `updateScores()` in `score-and-grade.utils.ts` merges result events into each asset; if no results exist, `addDefaultSignals()` injects a placeholder event
3. Context exposes `assets: AssetData[]` to the whole app
4. Cache: compressed (gzip + base64) assets stored in App State API with 15-minute TTL; bypass with `?noCache=true` deep link

**Key types** (all in `src/app/models/scorecards-data.model.ts`):
- `AssetData` — one component: `{ assetId, assetTitle, assetType, organisationId/Title, capabilityId/Title, teamId/Title, resultEvents }`
- `ResultEventData` — one scorecard run: `{ 'asset.id', 'asset.version', 'score.avg', 'gate.passed', signals, owners, parents }`
- `SignalData` — one signal result: `{ signalId, title, category, signalResultScore, versionSpecific, blocking }`
- `Grade` — `'A' | 'B' | 'C' | 'D' | 'F' | '-' | '/'`

**Grading thresholds** (`score-and-grade.utils.ts`):
- A > 84, B > 67, C > 50, D > 33, F ≤ 33; 0 or null → `'-'`

**Layer responsibilities:**
| Path | Responsibility |
|---|---|
| `src/app/requests/` | Grail DQL fetching + response mapping |
| `src/app/utils/` | Pure data transforms (no JSX, no fetch) |
| `src/app/hooks/` | State + side effects → props |
| `src/app/components/` | Render only |
| `src/app/pages/` | Route assembly |

### Routes
| Route | Page |
|---|---|
| `/overview` | Organisation/capability/team tree table |
| `/overview/aggregation` | Filtered, tabbed aggregation view |
| `/components` | Flat asset table |
| `/components/:name` | App Score page — single component detail |

## Important Constraints

**E2E / DQL test imports**: only import from `e2e/src/`, `dql/`, or `src/app/models/` (pure TS). Importing from `src/app/utils/` or `src/app/components/` causes `SyntaxError: Unexpected token '.'` at test runtime due to Strato CSS-in-JS.

**Strato imports**: always use subpath imports — never the package root. Check `base/knowledge/strato.md` for the import map before writing any Strato import. Wrong paths cause TS errors.

**`versionSpecific` signal handling**: signals marked `versionSpecific: true` are placed in the versioned DQL bucket; `false` goes to the versionless bucket. Exception: `app-manifest-scan` is treated as versionless when `assetVersion === 'summary'` (see `generate-scorecards-result-event.function.ts`).

**`addDefaultSignals` lookup key**: `scorecardDefinitions.find((def) => def.id === asset.assetType)` — match on `def.id`, not `def.type` (display string).

## Commits

Format: `fix: <description> <TICKET-KEY>` or `feat: <description> <TICKET-KEY>` — ticket key must be in the subject line (not just the body). NOISSUE is allowed. Husky + commitlint enforce this.
