About hub-creator
hub-creator is a skill for authoring new Datex Studio hub configurations on a branch. It guides the creation and validation of hubs with filters, tabs, mounted components, toolbar actions, and flows, including checks for reference contracts.
Key features
- 01Create new hub configurations
- 02Set up filters, tabs, and mounted components
- 03Configure toolbar buttons and flows
- 04Check component reference contracts
- 05Validate and push hub configurations
Use cases
- 01Build a filtered hub with grid tabs
- 02Add toolbar actions that open forms
- 03Create a hub with access-gated behavior
- 04Validate component bindings before pushing
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ datex/ skills --skill hub-creatorAfter installing, type/hub-creatorin Claude Code to confirm it loaded.
Bundled filesevals/evals.jsonreferences/hubs.md
Details
From the project READMEHub 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 —
configurationTypeIdtable and TypeScript-expression encoding rules - ../datex-studio-conventions/naming-conventions.md —
_hubsuffix, 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-gatheringskill — invoked to produce a requirements brief if one doesn't already exist in the conversation contextgrid-creator/selector-creator/form-creator/editor-creatorskills — invoked when a tab grid, filter-backing selector, or dialog form/editor referenced by the hub doesn't exist yet on the branchcomponent-wiring-checkskill — invoked to auditconfigParameters↔ targetinParamscontracts 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-gatheringor 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-gatheringskill 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.jsonfile shape. - Filters. Each filter field's
configId/moduleIdmust resolve to a real selector (*-selector.json). If the selector doesn't exist on the branch yet, invokeselector-creatorfirst. Every selectorconfigParametergets an entry in the filter'sdropdownConfig.configParameters(usevalue: ""for ones that don't apply). - Tabs. Every tab declares a
contentConfigpointing at a grid (or other component) on the branch. If the target grid doesn't exist yet, invokegrid-creatorfirst. The tab'sconfigParametersarray must mirror the target'sinParamsexactly — see the Phase 3 dead-wiring check. varsarray. Every$hub.vars.<id>written in hub flow code must be declared at the top-levelvarsarray. 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, anyvalue/tooltip/placeholder/labelfollows 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
clickFlowConfigpointing 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-gatedon_init, and shared-local-flow patterns. -
Dead-wiring trap audit. The single most common hub bug: a hub filter binds into a tab's
configParametersfor 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
configParametersmust be named in the mounted grid'sinParams— and every inParam the grid declares must have an entry in the tab'sconfigParameters(unused entries usevalue: ""orvalue: null).When a hub filter exists to scope a specific tab's grid (e.g. a
projectsfilter scoping a rules grid byproject_ids, or acontractfilter scoping a documents grid bycontract_id), audit the mounted grid's top-levelinParamsin 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-checkto 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↔ gridinParamscontract holds). - Toolbar buttons fire their click flows; dialog openers return;
$hub.refresh()propagates state. - Role-gated tabs hide for unauthorized users (access-gated
on_initflow 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,referenceNameends_hub— plus the universal checks (../datex-studio-conventions/universal-checklist.md). - Tabs carry full
configParameterscontracts (mirrors the mounted grid'sinParamsexactly — 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-levelvars). - 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.