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 typedthis.$datasources.<ref>stub, but the preview is served mocked data —dxs ng data generateseedsmocks/harness-mocks.jsonand you fill it with representative values (from a realdxsquery). 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 withschema-explorerfirst) 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 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 havedxscommands. Plainhead/grepover 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/upsertrejection 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
dxscommand 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 @Inputs 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 @Outputs ($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 — Branch/connection selection (shared across skills). Never assume a branch ID.
- references/custom-angular-components.md — Authoritative CAC reference: the two author regions, the light-harness / preview model, the injected
$-context, mock data,manifest.jsonshape, prerequisites, timings, and gotchas - ../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 — the platform
$-globals the component's constructor receives ($datasources,$flows,$shell,$utils,$settings, …) - ../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 — reference-name / display-name rules (the name you pass to
dxs ng createis PascalCase; the reference name is camelCase) - ../datasource-creator/references/datasources.md — when the component reads real data via
$datasources/$flows
Dependencies
requirements-gatheringskill — 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-explorerskill — invoked when the component reads Footprint data through$datasources, to confirm the entities/fields exist before wiring them.datasource-creatorskill — 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):
- 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_modulesin its runtime stage precisely so the API can run codegen on demand — socreate/pullwork 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.) - Authenticated —
dxs auth statusshows a signed-in identity. - agent-browser (headless screenshots) — installed from the unscoped npm package:
npm install -g agent-browserthenagent-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
- Follow ../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.
- 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.
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 (mirrorsdxs 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:
//#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/ (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.
Phase 4: Preview loop (screenshot-driven)
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
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
- Branch confirmed with the user (never assumed); prerequisites up (API reachable, auth,
agent-browser). - Only the two regions +
.html+.scsswere edited — the wrapper class line,@Component, constructor, and//#regionsentinels are untouched. - No raw
HttpClient/ direct backend calls — data comes throughthis.$datasources/this.$flows; UI actions through$shell/$flows(see calling-conventions). - Mocks seeded (
dxs ng data generate) and filled with representative values if the component reads$datasources/$flows, so the preview renders real-looking data. manifest.jsonIO changes are followed bypreview --refresh -b <branch>(new@Input/@Outputneeds codegen re-wiring). A referenced datasource must already exist on the branch (create it withdatasource-creatorfirst, ideally before materializing) and is read with typed$datasourcesaccess — a missing datasource is a compile error to fix, never something to cast around; sample data stays inmocks/. See the datasource note in Phase 3.- The preview PNG matches the target — the loop converged, and no compile/template errors remain in the render.
displayName≤ 100 chars — it becomes the configtitle/description(there is no separate manifestdescriptionfield).- Verified after push —
dxs ng list -b <branch>shows it; optionallydxs ng pull … -d ./roundtripround-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-oksentinel (it pairs withnode_modules—previewreinstalls unless both are present). Everything authored stays resumable at ~16 MB; the nextpreviewre-runsnpm 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> --forcedoes the whole thing (stops the running dev server itself, deletes, re-materializes); a residual locked-file failure surfaces as a structuredDXS-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 pullre-materializes the two regions +.html/.scssfaithfully, butmocks/harness-mocks.json, captured target/notes files, andmanifest.datasources/componentRefsdo 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). |
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 @Outputs 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.