Hub Creator
Author a NEW Datex Studio hub configuration (configurationTypeId=2) on a branch — top-level UI containers with filters, grouping options, tabs that mount grids/editors/forms/custom Angular components, toolbar buttons, and click flows.
See also: hub-editor — modifying an EXISTING hub's toolbar/flows on a branch (this skill is for authoring NEW hubs).
References
- ../datex-studio-shared/branch-setup.md — Branch/connection selection (shared across skills)
- references/hubs.md — Authoritative hub authoring reference: file shape, runtime globals, invocation contract, common patterns, pre-flight checklist
- ../datex-studio-conventions/file-format.md —
configurationTypeId table and TypeScript-expression encoding rules
- ../datex-studio-conventions/naming-conventions.md —
_hub suffix, filename stem matching, display-name rule
- ../datex-studio-runtime/runtime-globals.md — platform-injected globals available in hub code (
$flows, $shell, $utils, ...)
- ../datex-studio-runtime/calling-conventions.md — UI-tier calling rules (call functions, never actions; dialog openers via
$shell)
- ../component-wiring-check/references/component-wiring.md — host reference contracts, vars-must-be-declared rule, moduleId rule
Dependencies
requirements-gathering skill — invoked to produce a requirements brief if one doesn't already exist in the conversation context
grid-creator / selector-creator / form-creator / editor-creator skills — invoked when a tab grid, filter-backing selector, or dialog form/editor referenced by the hub doesn't exist yet on the branch
component-wiring-check skill — invoked to audit configParameters ↔ target inParams contracts before push (the dead-wiring trap)
CLI Lifecycle
Hub authoring goes through dxs configuration — the generic CRUD primitive over every platform configuration type. There is no dxs hub subcommand and no field-level patching; you build (or fetch + extract) the whole JSON body, edit it, and push the whole thing back.
Create a new hub:
# 1. Build body.json from scratch (see references/hubs.md → Minimal Valid Skeleton)
# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate hub -b <branchId> -D body.json
# 3. Create
dxs configuration upsert hub -b <branchId> -D body.json
Edit an existing hub (only if scope creeps from "new" into "modify mid-authoring"):
# 1. Fetch — note the envelope wrapper
dxs configuration get hub <configId> -b <branchId> -O envelope.json
# 2. EXTRACT THE INNER BODY (round-trip footgun guard — see "Round-trip rule" below)
jq .json envelope.json > body.json
# 3. Edit body.json
# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate hub -b <branchId> -D body.json
# 5. Push
dxs configuration upsert hub -b <branchId> -D body.json
For purely-modify workflows (toolbar/flows changes on an existing hub), stop here and switch to the hub-editor skill — it carries the toolbar/click-flow editing patterns and the role-gating recipes.
Round-trip rule (critical)
When editing an existing config, never pipe the envelope.json directly into dxs configuration upsert — it silently destroys configuration content. The corrected sequence above (extract inner .json with jq before editing) is mandatory for any round-trip. See ../datex-studio-shared/configuration-roundtrip.md for the canonical round-trip and the underlying bug.
Workflow
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?]
+-----+-----+
| |
YES NO -> invoke `requirements-gathering`
| |
+-----+------+
|
[Phase 2: Author hub body]
Decide what the hub IS:
- which filters scope the experience
- which tabs (grids / other components) mount inside it
- role-gating, on_init access guard, hub-local flows
Consult references/hubs.md for file shape, $hub runtime,
required top-level fields, common patterns
|
[Phase 3: Wire toolbar / flows]
Toolbar buttons + click flows; dialog openers via $shell
Audit dead-wiring trap (hub filter -> tab configParameters
-> grid inParams MUST stay in sync) — invoke
`component-wiring-check` if mounted grids/selectors
need extension
|
[Phase 4: Validate + push]
dxs configuration validate hub -b <branchId> -D body.json
|
+----+----+
| |
CREATE MODIFY-EXISTING
| |
| use the corrected round-trip
| (get -O envelope -> jq .json -> body)
| |
+----+----+
|
v
dxs configuration upsert hub -b <branchId> -D body.json
(upsert creates or updates by referenceName — one command for both)
|
[Phase 5: Verify in Studio (optional)]
Reload the hub in the running app; confirm filters render,
tabs query, toolbar buttons fire
|
[invoke `post-edit-verification`; then `component-validator`]
Phase Details
Phase 1: Setup + Requirements
- Follow ../datex-studio-shared/branch-setup.md for branch and connection selection. Never assume a branch ID — ask the user to confirm.
- Check whether a requirements brief already exists in the conversation context (produced by
requirements-gathering or another calling skill).
- Brief exists — use it. The brief provides intent (what the hub is for), the scoping filters users need, the tabs/grids that should mount inside, role-gating rules, and any toolbar actions.
- No brief — invoke the
requirements-gathering skill first. Hubs are top-level entry points; getting the filter set and tab layout right up front saves rework.
Phase 2: Author hub body
Decide what the hub IS, then build body.json:
- File basics. Per the Pre-Flight Checklist below + ../datex-studio-conventions/universal-checklist.md; see references/hubs.md → File Location & Naming for the
-hub.json file shape.
- Filters. Each filter field's
configId / moduleId must resolve to a real selector (*-selector.json). If the selector doesn't exist on the branch yet, invoke selector-creator first. Every selector configParameter gets an entry in the filter's dropdownConfig.configParameters (use value: "" for ones that don't apply).
- Tabs. Every tab declares a
contentConfig pointing at a grid (or other component) on the branch. If the target grid doesn't exist yet, invoke grid-creator first. The tab's configParameters array must mirror the target's inParams exactly — see the Phase 3 dead-wiring check.
vars array. Every $hub.vars.<id> written in hub flow code must be declared at the top-level vars array. See references/hubs.md → Runtime Globals and ../component-wiring-check/references/component-wiring.md.
onInitFlowConfig. Common home for initial-filter defaulting, context loading, and the access-gated hub pattern (permission check → $hub.close() on deny). See references/hubs.md → Common Patterns.
- TypeScript-expression encoding.
hubTitle, hubDescription, any value / tooltip / placeholder / label follows the three-encoding rule — display text wrapped in backticks: "`Widget overview`". See ../datex-studio-conventions/file-format.md → Declarative String Values Are TypeScript Expressions.
Phase 3: Wire toolbar / flows
Toolbar buttons + click flows. Each toolbar button entry has a clickFlowConfig pointing at a hub-local flow or an external function. From hub code, invoke functions via $flows.<Package>.<fn> and open forms/editors via $shell.<Package>.open<referenceName>Dialog(...). Never call actions directly — wrap them in a function. See ../datex-studio-runtime/calling-conventions.md and references/hubs.md → Common Patterns for the engine-toggle, configure-options-dialog, access-gated on_init, and shared-local-flow patterns.
Dead-wiring trap audit. The single most common hub bug: a hub filter binds into a tab's configParameters for an id the mounted grid doesn't actually declare as an inParam. The binding looks wired but the grid never receives the value.
The rule: every binding in a tab's configParameters must be named in the mounted grid's inParams — and every inParam the grid declares must have an entry in the tab's configParameters (unused entries use value: "" or value: null).
When a hub filter exists to scope a specific tab's grid (e.g. a projects filter scoping a rules grid by project_ids, or a contract filter scoping a documents grid by contract_id), audit the mounted grid's top-level inParams in the same edit. If a binding is needed and the grid doesn't declare a matching inParam, extend the grid in the same edit (and the underlying datasource, since grid flows read $grid.inParams.<id> when building the datasource call).
Invoke component-wiring-check to audit the full reference contract — moduleId, configParameters mirror, vars declared — before push. See references/hubs.md → Invocation Contract and ../component-wiring-check/references/component-wiring.md.
Toolbar / tab IDs are unique within the hub. Flow code addresses them by id ($hub.toolbar.save.control..., $hub.tabs.rules.hidden = ...) — collisions silently overwrite.
Phase 4: Validate + push
# Validate the body locally against the branch. Exit 1 = validation found errors
# (read validation_errors, fix body.json, re-run) — not a broken CLI. Do not push on exit 1.
dxs configuration validate hub -b <branchId> -D body.json
# For a new hub
dxs configuration upsert hub -b <branchId> -D body.json
# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get hub <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert hub -b <branchId> -D body.json
Validation surfaces missing required fields, malformed configParameters shapes, and selector/grid reference errors before push. It does not catch the dead-wiring trap (configParameters whose id is absent from the mounted grid's inParams) — that's a contract concern between two components, audited via component-wiring-check and the Phase 3 rule above.
Phase 5: Verify in Studio (optional)
Reload the hub in the running app:
- Filters render and the dropdowns populate (selectors wired correctly).
- Tabs query when filters change (the tab
configParameters ↔ grid inParams contract holds).
- Toolbar buttons fire their click flows; dialog openers return;
$hub.refresh() propagates state.
- Role-gated tabs hide for unauthorized users (access-gated
on_init flow runs).
If the running app isn't available, re-fetch the config and diff against body.json (using the corrected jq .json extract pattern) to confirm the push landed.
Pre-Flight Checklist
Before push, walk the full checklist in references/hubs.md → Pre-Flight Checklist. The fast version:
- File basics:
configurationTypeId: 2, referenceName ends _hub — plus the universal checks (../datex-studio-conventions/universal-checklist.md).
- Tabs carry full
configParameters contracts (mirrors the mounted grid's inParams exactly — no extras, no missing entries).
- Filter bindings are contract-complete (selector references resolve; every selector configParameter has an entry).
- Vars declared (every
$hub.vars.<id> in flow code is in top-level vars).
- Calling-tier compliance (functions via
$flows.<Package>.<fn>; dialog openers via $shell.<Package>.open<referenceName>Dialog(...); no direct action calls).
- Toolbar/tab IDs are unique within the hub.
- TypeScript-expression strings wrapped correctly; dynamic tooltips go through declared
$hub.vars.<name>.
Common Mistakes
| Mistake |
Fix |
Tab configParameters binds an id the grid doesn't declare as an inParam |
Dead wiring — the value is silently dropped. Either drop the binding, or extend the grid's inParams (and underlying datasource) in the same edit. See Phase 3. |
Piping dxs configuration get -O envelope.json directly into dxs configuration upsert -D envelope.json |
Silently destroys config content. Always jq .json envelope.json > body.json before editing. See "Round-trip rule" above. |
Writing $hub.vars.<id> = ... in flow code without declaring <id> in the top-level vars array |
The var is undeclared — write fails silently or runtime error. Declare every var. |
Calling an action directly from hub code ($flows.<Package>.<action_name>(...)) |
UI-tier rule: invoke functions only; wrap actions in a function. See ../datex-studio-runtime/calling-conventions.md. |
Assigning to a filter or toolbar button's .control.tooltip from flow code |
No-op for dynamic tooltips — must bind to a declared $hub.vars.<name> instead. See references/hubs.md → Pre-Flight Checklist item 7. |
description exceeds 100 chars |
SQL column limit — push will fail validation. Tighten. |
referenceName doesn't end in _hub or doesn't match filename stem |
Import / lookup breaks. Snake_case, _hub suffix, filename stem matches. |
Tab moduleId set to the hub's package instead of the mounted grid's |
Cross-component reference rule — moduleId is always the target's package. See ../component-wiring-check/references/component-wiring.md. |
Two toolbar buttons (or two tabs) sharing the same id |
Silent overwrite — flow code can only address one of them. Make IDs unique within the hub. |
After your edit, invoke post-edit-verification to surface description/JSON/schema violations. For a final review, invoke component-validator.
1---2name: hub-creator3description: Use when authoring a NEW Datex Studio hub configuration (configurationTypeId=2) on a branch — filter-driven container with tab grids, role-gated tabs, toolbar buttons. Owns the dead-wiring trap (hub filter -> tab configParameters -> grid inParams must stay in sync), filter-driven-tab pattern, and toolbar button wiring. Triggers: "create a hub", "add a tab to xxx_hub", "add a filter", "add a toolbar button to the hub", "role-gate a hub tab", "filter-driven tabs", "tab grid ignores filter value", "hub won't open", "dead wiring between hub filter and grid". For modifying an existing hub, see hub-editor.4---56# Hub Creator78Author a NEW Datex Studio hub configuration (configurationTypeId=2) on a branch — top-level UI containers with filters, grouping options, tabs that mount grids/editors/forms/custom Angular components, toolbar buttons, and click flows.910> **See also:** `hub-editor` — modifying an EXISTING hub's toolbar/flows on a branch (this skill is for authoring NEW hubs).1112## References1314- [../datex-studio-shared/branch-setup.md](../datex-studio-shared/branch-setup.md) — Branch/connection selection (shared across skills)15- [references/hubs.md](references/hubs.md) — Authoritative hub authoring reference: file shape, runtime globals, invocation contract, common patterns, pre-flight checklist16- [../datex-studio-conventions/file-format.md](../datex-studio-conventions/file-format.md) — `configurationTypeId` table and TypeScript-expression encoding rules17- [../datex-studio-conventions/naming-conventions.md](../datex-studio-conventions/naming-conventions.md) — `_hub` suffix, filename stem matching, display-name rule18- [../datex-studio-runtime/runtime-globals.md](../datex-studio-runtime/runtime-globals.md) — platform-injected globals available in hub code (`$flows`, `$shell`, `$utils`, ...)19- [../datex-studio-runtime/calling-conventions.md](../datex-studio-runtime/calling-conventions.md) — UI-tier calling rules (call functions, never actions; dialog openers via `$shell`)20- [../component-wiring-check/references/component-wiring.md](../component-wiring-check/references/component-wiring.md) — host reference contracts, vars-must-be-declared rule, moduleId rule2122## Dependencies2324- **`requirements-gathering`** skill — invoked to produce a requirements brief if one doesn't already exist in the conversation context25- **`grid-creator`** / **`selector-creator`** / **`form-creator`** / **`editor-creator`** skills — invoked when a tab grid, filter-backing selector, or dialog form/editor referenced by the hub doesn't exist yet on the branch26- **`component-wiring-check`** skill — invoked to audit `configParameters` ↔ target `inParams` contracts before push (the dead-wiring trap)2728## CLI Lifecycle2930Hub authoring goes through `dxs configuration` — the generic CRUD primitive over every platform configuration type. There is no `dxs hub` subcommand and no field-level patching; you build (or fetch + extract) the whole JSON body, edit it, and push the whole thing back.3132**Create a new hub:**3334```bash35# 1. Build body.json from scratch (see references/hubs.md → Minimal Valid Skeleton)36# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI37dxs configuration validate hub -b <branchId> -D body.json38# 3. Create39dxs configuration upsert hub -b <branchId> -D body.json40```4142**Edit an existing hub (only if scope creeps from "new" into "modify mid-authoring"):**4344```bash45# 1. Fetch — note the envelope wrapper46dxs configuration get hub <configId> -b <branchId> -O envelope.json47# 2. EXTRACT THE INNER BODY (round-trip footgun guard — see "Round-trip rule" below)48jq .json envelope.json > body.json49# 3. Edit body.json50# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI51dxs configuration validate hub -b <branchId> -D body.json52# 5. Push53dxs configuration upsert hub -b <branchId> -D body.json54```5556For purely-modify workflows (toolbar/flows changes on an existing hub), stop here and switch to the `hub-editor` skill — it carries the toolbar/click-flow editing patterns and the role-gating recipes.5758### Round-trip rule (critical)5960When editing an existing config, **never pipe the envelope.json directly into `dxs configuration upsert`** — it silently destroys configuration content. The corrected sequence above (extract inner `.json` with `jq` before editing) is mandatory for any round-trip. See [../datex-studio-shared/configuration-roundtrip.md](../datex-studio-shared/configuration-roundtrip.md) for the canonical round-trip and the underlying bug.6162## Workflow6364```65[Phase 1: Setup + Requirements]66Follow branch-setup.md for branch/connection selection67 |68[requirements brief in context?]69 +-----+-----+70 | |71 YES NO -> invoke `requirements-gathering`72 | |73 +-----+------+74 |75[Phase 2: Author hub body]76Decide what the hub IS:77 - which filters scope the experience78 - which tabs (grids / other components) mount inside it79 - role-gating, on_init access guard, hub-local flows80Consult references/hubs.md for file shape, $hub runtime,81required top-level fields, common patterns82 |83[Phase 3: Wire toolbar / flows]84Toolbar buttons + click flows; dialog openers via $shell85Audit dead-wiring trap (hub filter -> tab configParameters86-> grid inParams MUST stay in sync) — invoke87`component-wiring-check` if mounted grids/selectors88need extension89 |90[Phase 4: Validate + push]91dxs configuration validate hub -b <branchId> -D body.json92 |93 +----+----+94 | |95 CREATE MODIFY-EXISTING96 | |97 | use the corrected round-trip98 | (get -O envelope -> jq .json -> body)99 | |100 +----+----+101 |102 v103dxs configuration upsert hub -b <branchId> -D body.json104 (upsert creates or updates by referenceName — one command for both)105 |106[Phase 5: Verify in Studio (optional)]107Reload the hub in the running app; confirm filters render,108tabs query, toolbar buttons fire109 |110[invoke `post-edit-verification`; then `component-validator`]111```112113## Phase Details114115### Phase 1: Setup + Requirements1161171. Follow [../datex-studio-shared/branch-setup.md](../datex-studio-shared/branch-setup.md) for branch and connection selection. **Never assume a branch ID** — ask the user to confirm.1182. Check whether a **requirements brief** already exists in the conversation context (produced by `requirements-gathering` or another calling skill).119 - **Brief exists** — use it. The brief provides intent (what the hub is for), the scoping filters users need, the tabs/grids that should mount inside, role-gating rules, and any toolbar actions.120 - **No brief** — invoke the `requirements-gathering` skill first. Hubs are top-level entry points; getting the filter set and tab layout right up front saves rework.121122### Phase 2: Author hub body123124Decide what the hub IS, then build `body.json`:1251261. **File basics.** Per the **Pre-Flight Checklist** below + [../datex-studio-conventions/universal-checklist.md](../datex-studio-conventions/universal-checklist.md); see [references/hubs.md → File Location & Naming](references/hubs.md#file-location--naming) for the `-hub.json` file shape.1272. **Filters.** Each filter field's `configId` / `moduleId` must resolve to a real selector (`*-selector.json`). If the selector doesn't exist on the branch yet, invoke `selector-creator` first. Every selector `configParameter` gets an entry in the filter's `dropdownConfig.configParameters` (use `value: ""` for ones that don't apply).1283. **Tabs.** Every tab declares a `contentConfig` pointing at a grid (or other component) on the branch. If the target grid doesn't exist yet, invoke `grid-creator` first. The tab's `configParameters` array must mirror the target's `inParams` exactly — see the Phase 3 dead-wiring check.1294. **`vars` array.** Every `$hub.vars.<id>` written in hub flow code must be declared at the top-level `vars` array. See [references/hubs.md → Runtime Globals](references/hubs.md#runtime-globals) and [../component-wiring-check/references/component-wiring.md](../component-wiring-check/references/component-wiring.md).1305. **`onInitFlowConfig`.** Common home for initial-filter defaulting, context loading, and the access-gated hub pattern (permission check → `$hub.close()` on deny). See [references/hubs.md → Common Patterns](references/hubs.md#common-patterns).1316. **TypeScript-expression encoding.** `hubTitle`, `hubDescription`, any `value` / `tooltip` / `placeholder` / `label` follows the three-encoding rule — display text wrapped in backticks: `` "`Widget overview`" ``. See [../datex-studio-conventions/file-format.md → Declarative String Values Are TypeScript Expressions](../datex-studio-conventions/file-format.md#declarative-string-values-are-typescript-expressions).132133### Phase 3: Wire toolbar / flows1341351. **Toolbar buttons + click flows.** Each toolbar button entry has a `clickFlowConfig` pointing at a hub-local flow or an external function. From hub code, invoke functions via `$flows.<Package>.<fn>` and open forms/editors via `$shell.<Package>.open<referenceName>Dialog(...)`. **Never call actions directly** — wrap them in a function. See [../datex-studio-runtime/calling-conventions.md](../datex-studio-runtime/calling-conventions.md) and [references/hubs.md → Common Patterns](references/hubs.md#common-patterns) for the engine-toggle, configure-options-dialog, access-gated `on_init`, and shared-local-flow patterns.1362. **Dead-wiring trap audit.** The single most common hub bug: a hub filter binds into a tab's `configParameters` for an id the mounted grid doesn't actually declare as an inParam. The binding looks wired but the grid never receives the value.137138 The rule: **every binding in a tab's `configParameters` must be named in the mounted grid's `inParams`** — and every inParam the grid declares must have an entry in the tab's `configParameters` (unused entries use `value: ""` or `value: null`).139140 When a hub filter exists to scope a specific tab's grid (e.g. a `projects` filter scoping a rules grid by `project_ids`, or a `contract` filter scoping a documents grid by `contract_id`), audit the mounted grid's top-level `inParams` in the same edit. If a binding is needed and the grid doesn't declare a matching inParam, **extend the grid in the same edit** (and the underlying datasource, since grid flows read `$grid.inParams.<id>` when building the datasource call).141142 Invoke `component-wiring-check` to audit the full reference contract — moduleId, configParameters mirror, vars declared — before push. See [references/hubs.md → Invocation Contract](references/hubs.md#invocation-contract) and [../component-wiring-check/references/component-wiring.md](../component-wiring-check/references/component-wiring.md).1431443. **Toolbar / tab IDs are unique within the hub.** Flow code addresses them by `id` (`$hub.toolbar.save.control...`, `$hub.tabs.rules.hidden = ...`) — collisions silently overwrite.145146### Phase 4: Validate + push147148```bash149# Validate the body locally against the branch. Exit 1 = validation found errors150# (read validation_errors, fix body.json, re-run) — not a broken CLI. Do not push on exit 1.151dxs configuration validate hub -b <branchId> -D body.json152153# For a new hub154dxs configuration upsert hub -b <branchId> -D body.json155156# For modify-existing (round-trip — never skip the jq extract)157dxs configuration get hub <configId> -b <branchId> -O envelope.json158jq .json envelope.json > body.json159# ... edit body.json ...160dxs configuration upsert hub -b <branchId> -D body.json161```162163Validation surfaces missing required fields, malformed `configParameters` shapes, and selector/grid reference errors before push. It does not catch the dead-wiring trap (configParameters whose `id` is absent from the mounted grid's `inParams`) — that's a contract concern between two components, audited via `component-wiring-check` and the Phase 3 rule above.164165### Phase 5: Verify in Studio (optional)166167Reload the hub in the running app:168169- Filters render and the dropdowns populate (selectors wired correctly).170- Tabs query when filters change (the tab `configParameters` ↔ grid `inParams` contract holds).171- Toolbar buttons fire their click flows; dialog openers return; `$hub.refresh()` propagates state.172- Role-gated tabs hide for unauthorized users (access-gated `on_init` flow runs).173174If the running app isn't available, re-fetch the config and diff against `body.json` (using the corrected `jq .json` extract pattern) to confirm the push landed.175176## Pre-Flight Checklist177178Before push, walk the full checklist in [references/hubs.md → Pre-Flight Checklist](references/hubs.md#pre-flight-checklist). The fast version:1791801. File basics: `configurationTypeId: 2`, `referenceName` ends `_hub` — plus the universal checks ([../datex-studio-conventions/universal-checklist.md](../datex-studio-conventions/universal-checklist.md)).1812. Tabs carry full `configParameters` contracts (mirrors the mounted grid's `inParams` exactly — no extras, no missing entries).1823. Filter bindings are contract-complete (selector references resolve; every selector configParameter has an entry).1834. Vars declared (every `$hub.vars.<id>` in flow code is in top-level `vars`).1845. Calling-tier compliance (functions via `$flows.<Package>.<fn>`; dialog openers via `$shell.<Package>.open<referenceName>Dialog(...)`; no direct action calls).1856. Toolbar/tab IDs are unique within the hub.1867. TypeScript-expression strings wrapped correctly; dynamic tooltips go through declared `$hub.vars.<name>`.187188## Common Mistakes189190| Mistake | Fix |191|---|---|192| Tab `configParameters` binds an id the grid doesn't declare as an inParam | Dead wiring — the value is silently dropped. Either drop the binding, or extend the grid's `inParams` (and underlying datasource) in the same edit. See Phase 3. |193| Piping `dxs configuration get -O envelope.json` directly into `dxs configuration upsert -D envelope.json` | Silently destroys config content. Always `jq .json envelope.json > body.json` before editing. See "Round-trip rule" above. |194| Writing `$hub.vars.<id> = ...` in flow code without declaring `<id>` in the top-level `vars` array | The var is undeclared — write fails silently or runtime error. Declare every var. |195| Calling an action directly from hub code (`$flows.<Package>.<action_name>(...)`) | UI-tier rule: invoke functions only; wrap actions in a function. See `../datex-studio-runtime/calling-conventions.md`. |196| Assigning to a filter or toolbar button's `.control.tooltip` from flow code | No-op for dynamic tooltips — must bind to a declared `$hub.vars.<name>` instead. See `references/hubs.md → Pre-Flight Checklist` item 7. |197| `description` exceeds 100 chars | SQL column limit — push will fail validation. Tighten. |198| `referenceName` doesn't end in `_hub` or doesn't match filename stem | Import / lookup breaks. Snake_case, `_hub` suffix, filename stem matches. |199| Tab `moduleId` set to the hub's package instead of the mounted grid's | Cross-component reference rule — `moduleId` is always the target's package. See `../component-wiring-check/references/component-wiring.md`. |200| Two toolbar buttons (or two tabs) sharing the same `id` | Silent overwrite — flow code can only address one of them. Make IDs unique within the hub. |201202**After your edit, invoke `post-edit-verification` to surface description/JSON/schema violations. For a final review, invoke `component-validator`.**