# Custom Angular Component Creator

> Author or edit a Datex Studio Custom Angular Component (CAC, configurationTypeId=36) on a branch via the `dxs ng` command family — a screenshot-driven create → edit-regions → preview → push loop for bespoke Angular UI (charts, dashboards, custom widgets).

- Skill: `datex/custom-angular-component-creator` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add datex/custom-angular-component-creator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/datex/custom-angular-component-creator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: datex (https://skillmd.com/u/datex)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/datex/custom-angular-component-creator

---


# Custom Angular Component Creator

Author or edit a Datex Studio **Custom Angular Component** (CAC, `configurationTypeId: 36`) on a branch. A CAC is an **author-written standalone Angular component** — you write the actual `component.ts` / `.html` / `.scss`, not a declarative JSON config. It runs inside the generated app with the real platform context (`$datasources`, `$flows`, `$shell`, …) injected, so it's the escape hatch for UI the declarative components (grid / form / hub / selector / editor) can't express: bespoke charts, dashboards, visualizations, custom layouts.

A CAC can also **embed an existing app configuration in its template** — declare it as a component reference (selectors first; the contract is generic over component kind) and codegen imports the generated component into the CAC's scope, so a real generated control (e.g. a selector dropdown) renders inside your custom UI.

Clues for embedding: a selector generates in **two variants — `_single` and `_multi`** — so a referenced selector gives you both a single- and a multi-select control to choose from. An embedded control exposes the *configuration's own* properties as component inputs/outputs (e.g. a selector surfaces each of its datasource parameters as an input named by the parameter id, plus its selected value and a display-text output). To find the exact tag and the inputs/outputs a referenced config exposes, **read its generated component in the materialized harness** — the `@Component` selector is the tag to use, and the `@Input`/`@Output` members are what you bind — rather than guessing them.

**This skill uses a different CLI surface from every other creator.** There is no `dxs configuration upsert customangularcomponent` hand-authoring path here — CAC authoring goes through the **`dxs ng`** command family, which materializes a **light harness** (the one component + real typed context + stub services, generated by the server for the branch), lets you edit the two author regions locally, and renders a **screenshot** each iteration. Nothing is created in Studio until `dxs ng push`.

## Data pattern (fixed default — never ask)

Whenever a CAC displays data, that data comes from a **real, typed datasource** — and *how* it is wired is **not a question to put to the user**. Do not ask whether to use real data, sample data, an `@Input`, or how to source it: the pattern is fixed for every component.

- **Authoring loop (CLI / `dxs ng preview`):** the component reads through the typed `this.$datasources.<ref>` stub, but the preview is served **mocked data** — `dxs ng data generate` seeds `mocks/harness-mocks.json` and you fill it with representative values (from a real `dxs` query). The authoring preview **never hits real data**.
- **After `push` (generated Studio app):** the *same* typed `$datasources.<ref>` calls hit the **real datasource / real data** in the running app. The component always depends on the datasource at runtime.
- **Datasource missing on the branch?** Create one that matches the real schema with `datasource-creator` (explore with `schema-explorer` first) and upsert it **before** materializing the harness, so the harness types the stub. Never fall back to embedded sample rows in the body or a cast (`$datasources as any`).

In one line: **real typed datasource + mocked data for authoring, real data after push** — applied to every CAC without asking. Sample data lives only in `mocks/harness-mocks.json` (transient, never pushed); the body stays clean with real empty/loading/error states. See [Phase 3](#phase-3-author-the-two-regions) for the mechanics.

## CLI-first — no workarounds (hard rule)

The `dxs` CLI is the **only sanctioned surface** for this skill — every read and write of
Studio state, and every step of the authoring loop (`create`/`pull`, `data generate`,
`preview`, `push`), goes through `dxs`. When the CLI falls short, **report the gap as a CLI
problem to fix**, don't route around it — a workaround that "gets the task done" hides a
defect every later session will hit again.

- **Derive facts from `dxs`, not side channels.** Field shapes, config contents, schema,
  branch state all have `dxs` commands. Plain `head`/`grep` over command output is fine;
  scripted parsing of config JSON (`jq`/`python`) or reading raw platform artifacts is not.
- **Derive server contracts from CLI-observable evidence.** A `validate`/`upsert` rejection
  names the expected server type, and an existing valid config on the branch
  (`dxs configuration get`) shows the accepted shape — that pair is sufficient. Do **not**
  open the platform source repositories to reverse-engineer contracts.
- **A needed workaround is a bug report, not a technique.** If the task seems to require
  killing stray processes, scripting around a CLI output format, driving a tool the CLI
  already wraps (e.g. screenshotting outside `dxs ng preview`), or hand-editing generated
  JSON — stop, tell the user what the CLI gap/bug is, and agree on next steps.
- **Hangs and orphaned processes are reportable defects.** A `dxs` command that blocks the
  terminal, leaves children holding file locks, or leaks temp state is a CLI lifecycle bug.
  Record the exact symptom chain (command, error code, leftover process/file) so it gets
  fixed; unblocking kills are done *after* reporting, never as an unremarked routine.

## One CAC per screen (hard rule)

A design/screen is authored as **one** CAC — never decomposed into multiple CACs
composed together. Composition happens through embedded platform components
(component references: selectors first) and `$shell` dialog openers, not CAC-in-CAC.
If a design feels too big for one component, trim the design, not the component count.

## Using a CAC as tab content

A CAC can be a tab's content in a **hub, editor, card, or dashboard** — the same slot that hosts
grids and forms. In the container's tab designer, pick **Custom Angular Component** as the tab
content type, reference the pushed CAC, and bind its `@Input`s through the tab's parameters (the
CAC's `inParams`/`outParams` are what the tab wires to). The platform generates a tab-content
contract onto every CAC, which **reserves three member names** — `refresh`, `$refreshEvent`, and
`outParamsChange`. Never declare an outParam or `@Output` with any of these names, and never
redeclare the two generated `@Output`s (`$refreshEvent`, `outParamsChange`) in the body — a
collision is a duplicate-member compile error. The one exception is a body **`refresh()`
method**, the sanctioned override: to re-load the CAC when the container refreshes, implement
`refresh()` in the body region and it replaces the generated default (which re-runs
`ngOnInit()`).

## References

- [../datex-studio-shared/branch-setup.md](../datex-studio-shared/branch-setup.md) — Branch/connection selection (shared across skills). **Never assume a branch ID.**
- [references/custom-angular-components.md](references/custom-angular-components.md) — Authoritative CAC reference: the two author regions, the light-harness / preview model, the injected `$`-context, mock data, `manifest.json` shape, prerequisites, timings, and gotchas
- [../datex-studio-shared/design-system/README.md](../datex-studio-shared/design-system/README.md) — **Datex design system** (Fluent 2): theme tokens, the real `datex-*` component class names, patterns, and compiled-CSS traps. A CAC gets **no** styling for free — read this and apply it so hand-authored UI looks native.
- [../datex-studio-runtime/runtime-globals.md](../datex-studio-runtime/runtime-globals.md) — the platform `$`-globals the component's constructor receives (`$datasources`, `$flows`, `$shell`, `$utils`, `$settings`, …)
- [../datex-studio-runtime/calling-conventions.md](../datex-studio-runtime/calling-conventions.md) — UI-tier calling rules for code the component runs (call flows/datasources, not raw HTTP)
- [../datex-studio-conventions/naming-conventions.md](../datex-studio-conventions/naming-conventions.md) — reference-name / display-name rules (the name you pass to `dxs ng create` is PascalCase; the reference name is camelCase)
- [../datasource-creator/references/datasources.md](../datasource-creator/references/datasources.md) — when the component reads real data via `$datasources` / `$flows`

## Dependencies

- **`requirements-gathering`** skill — invoked to produce a brief (or capture the **target screenshot**) if one isn't already in the conversation context. A CAC's whole point is bespoke UI, so a concrete visual target is worth more here than for any other component.
- **`schema-explorer`** skill — invoked when the component reads Footprint data through `$datasources`, to confirm the entities/fields exist before wiring them.
- **`datasource-creator`** skill — invoked when the component needs a datasource/flow that doesn't exist yet on the branch (author it first, then reference it from the component and mock it for preview).

## Prerequisites (one-time, host-provided)

The `dxs ng` loop needs a reachable Datex API — confirm these before starting (see [references/custom-angular-components.md → Prerequisites](references/custom-angular-components.md#prerequisites)):

1. **A reachable Datex API for the branch you're targeting.** The harness endpoints run codegen **server-side and are not environment-gated** — the deployed image installs Node and `codegen/node_modules` in its *runtime* stage precisely so the API can run codegen on demand — so `create` / `pull` work against whichever Datex Application API the CLI is configured for, dev, qa or prod alike.
   *(Not to be confused with app **publish**, which genuinely is dev-gated — outside Development it is delegated to an Azure DevOps pipeline. That gate does not apply to the harness endpoints.)*
2. **Authenticated** — `dxs auth status` shows a signed-in identity.
3. **agent-browser** (headless screenshots) — installed from the **unscoped** npm package: `npm install -g agent-browser` then `agent-browser install`. (It is **not** `@anthropic-ai/agent-browser` — that name 404s.)

## CLI Lifecycle — the `dxs ng` family

| Command | Online? | Touches Studio? | What it does |
|---|---|---|---|
| `dxs ng create <Name> -b <branch> [-d <dir>]` | yes | **no** | Build a starter config → server generates the **light harness** for the branch → materialize `<name>/angularapp/…` + `manifest.json` + `mocks/` locally. Nothing is created in Studio. |
| `dxs ng pull <name> -b <branch> [-d <dir>] [--force]` | yes | no | Same materialization for an **existing** CAC (the edit-existing entry point). An existing folder is a structured `DXS-NG-048` error; `--force` deletes the whole folder (stopping a running preview server itself first) and re-materializes from server truth. |
| `dxs ng data generate <folder> -b <branch>` | yes | no | Seed `<folder>/mocks/harness-mocks.json` with typed placeholders for the `$datasources`/`$flows` the component uses. |
| `dxs ng preview <folder> [-o out.png] [--refresh] [--clean]` | **no** (local) | no | Serve the harness locally and screenshot the component → `<folder>/render.png`. `--refresh -b <branch>` re-fetches the harness after a manifest/IO change; `--clean` resets a stuck server **and** tears down the agent-browser session (daemon, Chrome tree, stale state files). |
| `dxs ng push <folder> -b <branch>` | yes | **yes** | Extract the two regions + `manifest.json` → upsert type-36. **First push creates the component in Studio**; the server validates on upsert (hard gate). |
| `dxs ng stop <folder>` | **no** (local) | no | Stop the folder's warm preview dev server + its agent-browser session (identity-checked lock kill, lock removed). Idempotent (`stopped: false` when nothing runs) — the disposal step when you're done; never read the lock or `taskkill` by hand. |
| `dxs ng list -b <branch>` | yes | no | List the branch's type-36 components. |

The `<Name>` you pass to `create` is **PascalCase** (`OutboundCommandCenter`); the reference name becomes camelCase (`outboundCommandCenter`), the selector is kebab-case `app-outbound-command-center` (auto-derived — what `create` reports and `manifest.json` stores), and the files are `app.outboundCommandCenter.component.{ts,html,scss}`.

## Workflow

```
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch selection (NEVER assume a branch ID)
Confirm prerequisites (API reachable, auth, agent-browser)
        |
[requirements brief / TARGET SCREENSHOT in context?]
  +-----+-----+
  |            |
 YES          NO -> invoke `requirements-gathering` (capture the visual target)
  +-----+------+
        |
[Phase 2: Materialize the harness]
dxs ng create <Name> -b <branch>        (new component)
   -- or --
dxs ng pull <name> -b <branch>          (edit an existing one)
        |
[Phase 3: Author the two regions]
Edit <name>/angularapp/src/app/app.<ref>.component.ts:
   __COMPONENT_TYPES__  region -> imports / interfaces / types
   __COMPONENT_BODY__   region -> fields / methods / getters / lifecycle
Edit app.<ref>.component.html and .scss.
NEVER touch the wrapper class line, the @Component decorator, the
constructor, or the //#region ... //#endregion sentinels.
        |
[real data?] -> dxs ng data generate <folder> -b <branch>   (seed mocks)
        |
[Phase 4: Preview loop (screenshot-driven)]
dxs ng preview <folder>        -> writes <folder>/render.png
Read render.png. Compare to the target. Edit regions. Re-preview.
Repeat until it matches. (First preview ~15-46s cold; each later ~10s.)
        |
[Phase 5: Push + verify]
dxs ng push <folder> -b <branch>     (creates in Studio; server validates)
dxs ng list -b <branch>              (confirm it landed)
dxs ng pull <name> -b <branch> -d ./roundtrip   (optional round-trip check)
```

## Phase Details

### Phase 1: Setup + Requirements

1. Follow [../datex-studio-shared/branch-setup.md](../datex-studio-shared/branch-setup.md) for branch selection. **Never assume or reuse a branch ID** — ask the user to confirm, even if one appeared earlier in the session. Confirm the prerequisites above.
2. Check for a **requirements brief** in context. For a CAC, the highest-value input is a **concrete visual target** — a target screenshot or a precise description of the layout, data, and interactions. If none exists, invoke `requirements-gathering`. A target screenshot turns Phase 4 into an objective converge-to-target loop.

### Phase 2: Materialize the harness

Run `dxs ng create <Name> -b <branch>` (new) or `dxs ng pull <name> -b <branch>` (existing). Pass `-d components` so the working copy lands under a shared `components/<Name>/` directory (the convention for CAC working folders) instead of the current directory. This asks the server to generate the light harness for the branch and materializes it locally:

```
<name>/
  angularapp/…       # the runnable light harness (one component + real typed context + stub services)
  manifest.json      # identity + IO (inputs/outputs) + datasource/flow refs + displayModes
  mocks/             # fixtures the harness $datasources/$flows read during preview
```

Nothing is created in Studio at this step. See [references/custom-angular-components.md → The light harness](references/custom-angular-components.md#the-light-harness).

**Editing an existing component (`pull`).** `pull` is the edit-existing entry point, and it round-trips your two author regions + `.html`/`.scss` faithfully from Studio. On an existing folder it raises a structured **`DXS-NG-048`** error naming the two sanctioned outs — **pick the sync direction explicitly**:

- **Keep my local work, refresh the harness** → `dxs ng preview <folder> --refresh -b <branch>` — the only region-preserving sync (keeps authored regions/template/styles, `manifest.json`, `mocks/`, `node_modules`).
- **Discard local, take server truth** → `dxs ng pull <name> -b <branch> -d <dir> --force` — deletes the **whole** folder (mirrors `dxs report download --force`) and re-materializes. The CLI stops a still-running preview dev server itself via the identity-checked serve lock — never kill processes by hand — and refuses (`DXS-NG-049`) if the target doesn't look like a CAC working copy.

Three things never round-trip through the server, so after a `--force` pull restore them: **(1)** `manifest.datasources` comes back empty (the persisted config stores only identity + `inParams`/`outParams`) — re-add entries as objects `{ "name": "<ref>", "ref": "<ref>" }`, never bare strings (strings fail manifest validation on the next `dxs ng` command); **(2)** `mocks/` comes back empty — re-seed with `dxs ng data generate` + fill (or restore your prior file); **(3)** `node_modules` is gone — the next `preview` re-runs `npm install` (minutes). The first `preview` after re-adding manifest entries reports `refetched: true` (the manifest edit changes the IO signature, triggering an auto-rematerialize) — expected, not a bug.

### Phase 3: Author the two regions

You edit **only** the two author regions inside `<name>/angularapp/src/app/app.<ref>.component.ts`, plus the sibling `.html` and `.scss`:

```ts
//#region __COMPONENT_TYPES__
//   imports, interfaces, type declarations  (spliced verbatim on push)
//#endregion __COMPONENT_TYPES__

// ...generated @Component + class wrapper + constructor (DO NOT EDIT)...

  //#region __COMPONENT_BODY__
  //   fields, methods, getters, lifecycle body  (spliced verbatim on push)
  //#endregion __COMPONENT_BODY__
```

**Never** change the wrapper `class` line, the `@Component` decorator, the constructor, or the `//#region … //#endregion` sentinel lines — `push` extracts your work from **between** the sentinels, so damaging them breaks extraction. The constructor already injects the real context (`$datasources`, `$flows`, `$shell`, `$utils`, `$settings`, `$reports`, `$localization`, `$operations`, `$userSettings`, `$frontendFlows`) with real branch types, so body code uses `this.$datasources.…` etc. — never raw `HttpClient`. `SharedModule` is imported, so Angular directives (`*ngFor`, `*ngIf`, `[ngClass]`, `[style.*]`) and Material/AG-Grid/ApexCharts are available in the template.

**Style to the Datex design system** — a CAC gets **no** styling for free, so it looks foreign unless you apply it. The non-negotiables: **mirror, don't invent** (copy the closest existing component's markup + classes); **use `var(--…)` theme tokens, never a hard-coded hex** (a hex breaks dark mode and the token the filled-control system pivots on); **compose the real `datex-*` class names** (`datex-button primary`, `field-container`, `grid-table-*`, `card datex-card`, `widget-container`, …) rather than bespoke CSS; **Fluent icons only on `<i>`**; sentence case, one primary button on screen. For the token variables, the full component class list, and the compiled-CSS traps, read [../datex-studio-shared/design-system/](../datex-studio-shared/design-system/README.md) (start with `02-tokens`, `03-components`, `06-traps`).

If the component reads real data, run `dxs ng data generate <folder> -b <branch>` to seed `mocks/harness-mocks.json`, then fill in realistic values so the preview renders with representative data. Invoke `schema-explorer` first if you're unsure the datasource/entity exists; invoke `datasource-creator` if it needs to be authored.

A manifest **IO** change (new `@Input`/`@Output`) needs codegen wiring — edit `manifest.json`, then re-preview with `--refresh -b <branch>`; the regenerated harness types `this.inParams.<name>` and emits through `this.<output>`. Region/template/style edits stay fully local (no `--refresh`).

**Lifecycle hooks go in the body.** Implement `ngOnInit` / `ngOnChanges` / `ngOnDestroy` directly in `__COMPONENT_BODY__` (e.g. `async ngOnInit()` that loads data). Codegen emits its own stub only for the hooks you *don't* implement, so a **refetch** reconciles them — but a plain `preview` with no IO change won't refetch, leaving your hook duplicating the still-present generated stub (a compile error). So add the hook together with the IO change that triggers a refetch, or run `preview --refresh -b <branch>` once after adding it. For in-component interactive state that isn't an `@Input` (e.g. a mode toggle), keep a plain field seeded from the input in `ngOnInit` — no manifest change, so the preview stays warm.

**Datasources — create first, reference with typed access, no fixtures.** A datasource is a real branch config: author it with `datasource-creator` (`dxs datasource generate` → `validate` → `dxs configuration upsert datasource`) **before** you materialize the harness, so the generated harness types the `this.$datasources.<ref>` stub. `manifest.datasources` only *declares* the dependency — it never creates the datasource. Read it with **typed** access (`await this.$datasources.<ref>.getList({…})`), never a cast. If the datasource is missing, that typed reference is a **compile error** — and that fail-fast is exactly what you want: fix the wiring (create/upsert it, then re-materialize), do **not** cast around it (`$datasources as any`) or fall back to embedded sample data. Added the datasource *after* materializing and the stub hasn't appeared? Re-materialize fresh (`dxs ng pull` / `preview --refresh -b <branch>`) and confirm it's listed in `angularapp/src/app/app.datasource.index.ts` before relying on it. Sample/preview data belongs **only** in `mocks/harness-mocks.json` (transient, never pushed) — never in the component body, which is spliced verbatim into the Studio config. **To learn the datasource's field shape for your row mapping, use the CLI — never hand-parse the config JSON:** since you upsert before materializing anyway, read the authoritative shape with `dxs report datasource-fields <ref> -b <branch>` (result type + params + flat field paths + collections), and validate the local file with `dxs datasource validate <file> -b <branch>` (it exits **1** when it finds errors — that is validation reporting findings, not a broken CLI: read `validation_errors`, fix, re-run); don't `jq`/`python` fields out of `outParams`/`queryOptionsObjectTypeDef`, and remember `this.$datasources.<ref>` is already typed after `dxs ng create`. See [references/custom-angular-components.md → Reading real data](references/custom-angular-components.md#reading-real-data-via-datasources).

### Phase 4: Preview loop (screenshot-driven)

```bash
dxs ng preview <folder>          # -> <folder>/render.png
```

Read `render.png`, compare it to the target, edit the regions/template/styles, and re-run `preview`. This is the core loop and where an agent earns its keep: **read the PNG, diff it against the target screenshot, adjust, repeat** until it matches. Type/template errors surface here too (the harness compiles the real component). Timings (local): first `preview` after `create` is a one-time `npm install` (minutes) + a ~15-46s compile; every `preview` after that is ~10s, and an edit hot-reloads in ~4s. The cold-compile wait is handled internally (no timeout knob); use `--clean` if the server or browser session wedges — it resets both, including stale agent-browser session state from crashed runs.

`preview` captures the component's **default** rendered state — it doesn't click. For a mode/variant switched by in-component UI (rather than an `@Input`), temporarily set that default (or drive it from an `@Input`/mock) to screenshot each variant. For genuine interaction states (click-to-select, toggles, panels), drive `agent-browser` against the served harness instead: read the port from `<folder>/.dxs-serve.lock`, then `agent-browser open http://127.0.0.1:<port>` → `wait '<css>'` → `click '<css>'` → `screenshot out.png` — this verifies the real handler wiring, not a simulated default.

### Phase 5: Push + verify

```bash
dxs ng push <folder> -b <branch>     # first push CREATES it in Studio; server validates on upsert
dxs ng list -b <branch>              # confirm it appears
dxs ng pull <name> -b <branch> -d ./roundtrip   # optional: confirm the regions round-trip
dxs ng stop <folder>                 # done for now? dispose of the warm dev server + browser session
```

`push` is the only command that writes to Studio. The server runs the configuration validator on upsert (a hard gate — it recognizes CAC configs); a component that references a datasource that doesn't exist on the branch, or otherwise fails validation, is rejected. Fix what it reports and push again. `push` also fails **locally** (before any network call) if a region sentinel is damaged — that's the "don't touch the sentinels" rule biting.

**Datasource connection preflight (`DXS-NG-047`).** Before upserting, `push` verifies every own-branch datasource the CAC uses — the union of `manifest.datasources` and the unqualified `$datasources.<ref>` calls in the component body (so a stale manifest can't blind it) — exists on the branch **and** names an API connection setting the branch actually defines. This matters because the harness mocks never touch a connection: preview renders green even when Studio would flag *"Missing API Connection setting `<name>`"* on the datasource, and a green push must not imply the real codegen build works. On `DXS-NG-047`: check `dxs source branch settings <branch>`; regenerate the datasource against **this** branch with `dxs datasource generate` (it auto-resolves the setting); if the app has no API connection at all, wire one in Studio first (the CLI never creates connections). `--skip-connection-check` bypasses the gate for deliberate mock-only development on an app with no connections yet. Package datasources (`Module/ref`, `$datasources.<Module>.<ref>`) are exempt — their settings resolve via the reference remap.

## Pre-Flight Checklist

1. **Branch confirmed with the user** (never assumed); prerequisites up (API reachable, auth, `agent-browser`).
2. **Only the two regions + `.html` + `.scss` were edited** — the wrapper class line, `@Component`, constructor, and `//#region` sentinels are untouched.
3. **No raw `HttpClient` / direct backend calls** — data comes through `this.$datasources` / `this.$flows`; UI actions through `$shell` / `$flows` (see calling-conventions).
4. **Mocks seeded** (`dxs ng data generate`) and filled with representative values if the component reads `$datasources`/`$flows`, so the preview renders real-looking data.
5. **`manifest.json` IO changes are followed by `preview --refresh -b <branch>`** (new `@Input`/`@Output` needs codegen re-wiring). A referenced datasource must already exist on the branch (create it with `datasource-creator` first, ideally before materializing) and is read with **typed** `$datasources` access — a missing datasource is a compile error to fix, never something to cast around; sample data stays in `mocks/`. See the datasource note in Phase 3.
6. **The preview PNG matches the target** — the loop converged, and no compile/template errors remain in the render.
7. **`displayName` ≤ 100 chars** — it becomes the config `title`/`description` (there is no separate manifest `description` field).
8. **Verified after push** — `dxs ng list -b <branch>` shows it; optionally `dxs ng pull … -d ./roundtrip` round-trips the regions.

## Cleanup — working copies are heavy

A materialized working folder weighs ~700 MB, ~85% of it `angularapp/node_modules`. The folder is not the deliverable — after `push`, the config in Studio is.

**After a successful `push` (verified via `dxs ng list -b <branch>`), ask the user** whether to clean up — never silently. First stop the warm dev server and its agent-browser session with **`dxs ng stop <folder>`** — the identity-checked teardown (PID + image verified, never a blind kill), including the browser session/daemon and the lock file; idempotent, so call it unconditionally. Never read `.dxs-serve.lock` or `taskkill` by hand — on CLIs that predate `stop`, that manual dance was the workaround, not the method. (`dxs ng preview <folder> --clean` is the OTHER lifecycle verb: a **reset, not a stop** — same teardown, then a fresh server; use it for a wedged preview, `stop` for disposal.) Then offer the two tiers below. Reclaim disk in two tiers:

- **Prune (default between sessions):** delete `<folder>/angularapp/node_modules` (~600 MB), `<folder>/angularapp/.angular` (build cache, ~100 MB), and the `<folder>/.dxs-install-ok` sentinel (it pairs with `node_modules` — `preview` reinstalls unless both are present). Everything authored stays resumable at ~16 MB; the next `preview` re-runs `npm install` (minutes) and a cold compile. Zero data-loss risk.
- **Full delete (done with the component):** only after confirming the config is in Studio (`dxs ng list -b <branch>`). If the goal is a fresh working copy, don't delete by hand at all — `dxs ng pull <name> -b <branch> -d <dir> --force` does the whole thing (stops the running dev server itself, deletes, re-materializes); a residual locked-file failure surfaces as a structured `DXS-NG-049`. Deleting *without* re-pulling (final disposal): `dxs ng stop <folder>` first, then delete the folder. Rare residual case: a previously **crashed** server can leave orphaned children (`node.exe`, `esbuild.exe`) the lock no longer tracks, so if the delete still fails with locked files, sweep processes whose command line references the folder — and report it as a CLI lifecycle bug. `dxs ng pull` re-materializes the two regions + `.html`/`.scss` faithfully, but `mocks/harness-mocks.json`, captured target/notes files, and `manifest.datasources`/`componentRefs` do **not** round-trip — re-seed/re-add them after a re-pull.

**Never full-delete an unpushed working copy** — the authored regions exist nowhere else.

## Common Mistakes

| Mistake | Fix |
|---|---|
| Asking the user how the component should get its data (real vs sample, `@Input` vs datasource) | Don't ask — the data pattern is fixed. Real typed datasource + `mocks/` data for the authoring preview; real data after `push`. See [Data pattern (fixed default — never ask)](#data-pattern-fixed-default--never-ask). |
| Hand-authoring a JSON body and `dxs configuration upsert customangularcomponent` | CACs are authored through `dxs ng` (regions + harness + preview), not a JSON round-trip. Use `dxs ng create` / `preview` / `push`. |
| Editing outside the two regions (touching the wrapper class, `@Component`, constructor, or the `//#region` sentinels) | `push` extracts your code from **between** the sentinels. Edits elsewhere are lost on push or break extraction. Keep to `__COMPONENT_TYPES__`, `__COMPONENT_BODY__`, `.html`, `.scss`. |
| Installing `@anthropic-ai/agent-browser` (404) | The package is the **unscoped** `agent-browser`: `npm i -g agent-browser && agent-browser install`. |
| `preview` reports `ng serve did not become ready` (`DXS-NG-042`) | Usually the first cold compile running long — extend with `DXS_NG_SERVE_TIMEOUT` (seconds), re-run, `--clean` to reset a stuck server. On CLI ≤0.4.13 a **relative folder argument** also caused this (the dev server died instantly on a mis-resolved script path; fixed since — pass an absolute path as the workaround there). If it persists on a current CLI, it IS a real compile problem — inspect the ng build. |
| `preview` fails with `DXS-NG-053` (`agent-browser 'wait' timed out`) — assuming the component is broken | A screenshot timeout ≠ a render failure: the component usually compiled + served fine (contrast `DXS-NG-042` `ng serve did not become ready`, which is the compile failure). A warm serve can also be stale. Re-run with `--clean`, then verify the real render by opening the `.dxs-serve.lock` port with `agent-browser` and checking `app-<ref>` has content — don't chase phantom code bugs. |
| Expecting to preview/open OTHER components (or `$shell` dialogs to them) | The harness makes only YOUR candidate a real component; `$shell.open<X>Dialog(...)` to other components are compilable stubs that won't open in preview. `$datasources`/`$flows` (real branch) and your own UI are fully live. |
| Expecting the component to appear in Studio after `create` / `preview` | `create`/`pull`/`data generate`/`preview` are all transient. Only `push` writes to Studio (first push creates). |
| Reaching for `HttpClient` or `fetch` in the body | Use the injected `$datasources` / `$flows`; they're real, typed, and mockable for preview. Raw HTTP won't have the branch's auth/context. |
| Component uses a datasource but preview renders empty | Run `dxs ng data generate` and fill `mocks/harness-mocks.json` — the harness `$datasources`/`$flows` read those fixtures during preview (no backend). |
| Added an `@Input`/`@Output` but `preview` doesn't reflect it | IO changes need codegen re-wiring: update `manifest.json`, then `dxs ng preview <folder> --refresh -b <branch>`. |
| Expecting `manifest.datasources` to create or wire a datasource | It does neither. Create the datasource first with `datasource-creator` (`dxs datasource generate`/`validate`/`configuration upsert datasource`), ideally **before** materializing so the harness types the `$datasources.<ref>` stub; `manifest.datasources` only declares the dependency. |
| Casting around a missing datasource (`$datasources as any`) or fixture-fallback | Anti-pattern: it ships fake data to Studio and hides the wiring problem. A missing datasource *should* be a compile error — fix it by creating/upserting the datasource and re-materializing (`pull`/`--refresh`), then use **typed** `this.$datasources.<ref>`. |
| Embedding sample/fixture rows (or "replaced by the real datasource later" comments) in the body | The body is spliced verbatim into the pushed type-36 config, so fixtures + WIP comments become production. Keep the body clean — typed datasource read + real empty/loading/error states; sample data lives only in `mocks/harness-mocks.json`. |
| Hand-parsing the datasource config JSON (`jq`/`python`, or eyeballing `outParams`/`queryOptionsObjectTypeDef`) to learn its field shape | Use the CLI. Upsert first (required before materializing anyway), then `dxs report datasource-fields <ref> -b <branch>` for the field list; `dxs datasource validate`/`context <file> -b <branch>` for the local file. After `dxs ng create`, `this.$datasources.<ref>` is typed — you rarely need a hand-written row type. Never script the JSON. |
| `--refresh` render looks clean, but a later `preview` shows a compile error | A `--refresh` restart can screenshot **before** the recompile error overlay renders. Confirm with a second plain `preview`, and grep the harness `app.datasource.index.ts` to see which datasources are actually wired. |
| `push` rejected by the server | It ran the validator (hard gate). Fix what it reports (commonly a datasource ref missing on the branch — invoke `schema-explorer`/`datasource-creator`) and push again. |
| `push` fails with `DXS-NG-047` (datasource connection preflight) | The CAC uses a datasource that's missing on the branch or whose `apiSettingName` names an API connection setting the branch doesn't define — Studio would flag it even though preview rendered fine (mocks never touch connections). Regenerate the datasource against **this** branch (`dxs datasource generate` auto-resolves the setting), or wire a connection in Studio first; `--skip-connection-check` only for deliberate mock-only development. See Phase 5. |
| Trusting a green `preview` as proof the datasource wiring works in Studio | Preview data comes from `mocks/` — the harness never touches an API connection, so broken `apiSettingName` wiring is invisible until Studio (or the `push` preflight) flags it. The `DXS-NG-047` gate on `push` is the honest check; don't bypass it casually. |
| `push` fails locally before any network call | A region sentinel was damaged. Restore the `//#region __COMPONENT_TYPES__/__COMPONENT_BODY__` markers exactly, or re-`pull` and re-apply your edits. |
| `pull` came back with an empty `mocks/` and no `manifest.datasources` (editing an existing CAC) | Expected — three things never round-trip: `manifest.datasources`, `mocks/`, `node_modules`. Re-add `datasources` (objects, not strings), re-seed `mocks/` (`data generate` + fill), let `preview` reinstall. The two regions + `.html`/`.scss` round-trip fine. See Phase 2. |
| Hand-deleting the working folder (or killing dev-server processes) to get past `pull`'s existing-folder error | Use the flags, not manual cleanup: `preview --refresh` keeps your local work; `pull --force` discards it — and stops the running dev server itself (identity-checked lock kill). A residual locked-file failure is a structured `DXS-NG-049`, not a raw WinError. |
| Reading `.dxs-serve.lock` and `taskkill`-ing the dev server by hand when done previewing | That was the pre-`stop` workaround. `dxs ng stop <folder>` is the disposal verb: identity-checked tree-kill + agent-browser session teardown + lock removal, idempotent (`stopped: false` when nothing runs). The full lifecycle is CLI-owned: `pull --force` disposes on replace, `preview --clean` resets, `stop` disposes. |
| Writing `manifest.datasources` entries as bare ref strings | Entries are `ManifestDatasourceRef` objects: `{ "name": "<ref>", "ref": "<ref>" }`. Bare strings fail manifest validation on the next `dxs ng` command (`data generate`, `preview`, `push`). |
| `push` fails with `DXS-API-TIMEOUT` on `.../config/<ref>/lock` (~600s) | Not necessarily a hang or stale lock: a dev running the API **under a debugger** may be paused on a breakpoint on the lock/upsert path, or the token lapsed mid-call. Continue past the breakpoint (or re-`dxs auth login`), then retry `push`. |
| `dxs ng data generate` leaves a `$frontendFlows` ref or an embedded componentRef selector's datasource with no mock key | The generator doesn't seed every key: `$frontendFlows` refs aren't seeded at all, and it silently skips a componentRef selector's backing datasource when branch resolution fails during generation (no error). After generating, open `mocks/harness-mocks.json` and add any missing keys by hand. |
| Seeding a mock value for a `$frontendFlows` ref and expecting the preview to use it | `$frontendFlows` run as real, computed client-side code in the harness — never mocked. The preview always shows the flow's actual computed result; a mock entry for a frontendFlow key is inert. |
| A full-page/dashboard CAC's `render.png` looks cut off partway down | The platform shell forces `html, body { overflow: hidden }`, so `preview`'s screenshot is capped to one viewport (commonly ~569px) — it can't see past your own inner `overflow-y: auto` container no matter how tall the content is. This is a platform-shell constraint, not a bug in your layout. To verify content beyond the first viewport, drive `agent-browser` directly against the harness's served port with a taller viewport instead of relying on `render.png` alone. |
| Using `dxs source explore configs`/`trace` to confirm a CAC exists or to trace its references | Neither command indexes type-36 components — there's no CAC/customangularcomponent category in their type filters or output. Verify presence with `dxs ng list -b <branch>`, and trace declared datasource/flow/componentRefs via `dxs configuration get customangularcomponent <id> -b <branch>` or by reading the manifest/harness source directly. |
| Declaring an outParam or `@Output` named `refresh` / `$refreshEvent` / `outParamsChange`, or redeclaring the two generated `@Output`s in the body | Reserved — the platform generates these on every CAC (the tab-content contract that lets a CAC be a hub/editor/card/dashboard tab); a collision is a duplicate-member compile error. The exception is a body `refresh()` method — the sanctioned override; it replaces the generated default (which re-runs `ngOnInit`). |

**A CAC's value is bespoke visuals — treat the preview screenshot as the acceptance test, and converge it to the target before pushing.**

