About selector-creator
selector-creator is a skill for creating and editing Datex Studio dropdown and autocomplete selectors on a branch. It guides datasource wiring, validation, and safe configuration round-trips.
Key features
- 01Create and edit selector configurations
- 02Wire selectors to datasources
- 03Configure full-text search
- 04Validate before pushing
Use cases
- 01Build an entity-backed dropdown
- 02Create an enum option list
- 03Add autocomplete to a form or filter
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ datex/ skills --skill selector-creatorAfter installing, type/selector-creatorin Claude Code to confirm it loaded.
Bundled filesevals/evals.jsonreferences/selectors.md
Details
From the project READMESelector Creator
Author or modify a Datex Studio selector (configurationTypeId=7) on a branch — the platform's dropdown / autocomplete control. Selectors define an option list backed by a datasource, a display-label expression over each option, and a built-in full-text search box that forwards typed input into the backing datasource. They mount inside form fields, hub filter dropdowns, and grid filter cells; they don't render standalone.
References
- ../datex-studio-shared/branch-setup.md — Branch/connection selection (shared across skills)
- references/selectors.md — Authoritative selector authoring reference: component-variant rule, file shape, datasource wiring, full-text search three-piece contract, getList ↔ getByKeys parity
- ../datex-studio-conventions/file-format.md —
configurationTypeIdtable and TypeScript-expression encoding rules - ../datex-studio-conventions/naming-conventions.md —
_ddsuffix convention, filename stem matching, display-name rule - ../datex-studio-runtime/runtime-globals.md — platform-injected globals available in selector code (
$selector,$option,$flows,$utils, ...) - ../datex-studio-runtime/calling-conventions.md — UI-tier calling rules (call functions, never actions)
- ../datasource-creator/references/datasources.md — datasource taxonomy (component variant
-datasource.jsonvs FPDS-footprintDatasource.json) - ../datasource-creator/references/flow-datasources.md — flow-type datasource shape (enum dropdowns, computed option sets)
- ../datasource-creator/references/odata-datasources.md — OData-type datasource shape (entity-backed dropdowns)
- ../component-wiring-check/references/component-wiring.md — caller-contract rules, moduleId rule
- ../form-creator/references/forms.md — typical mounting site (form fields)
- ../hub-creator/references/hubs.md — typical mounting site (hub filter dropdowns)
Dependencies
requirements-gatheringskill — invoked to produce a requirements brief if one doesn't already exist in the conversation contextdatasource-creatorskill — invoked when the selector needs a new backing datasource (any selector that doesn't already have a-datasource.jsonto wrap). Reminder: the backing must be a-datasource.json, never a-footprintDatasource.jsonschema-explorerskill — invoked before authoring an OData-backed selector to confirm the entity, the display-label field, and any filter fields exist in the Footprint schemaform-creator/hub-creatorskills — invoked when the selector's host (form field or hub filter dropdown) needs itsconfigParameterscontract set up to mount the selectorcomponent-wiring-checkskill — invoked to auditconfigParameters↔ targetinParamscontracts on both the selector's host (outer) and the selector's backing datasource (inner) before push, especially thefull_text_searchwiringtype-definition-creatorskill — invoked when the selector's option entity shape needs a new or extended interface (rare; most selectors mirror an existing datasource'soutParams)
CLI Lifecycle
Selector authoring goes through dxs configuration — the generic CRUD primitive over every platform configuration type. There is no dxs selector subcommand and no field-level patching; you build (or fetch + extract) the whole JSON body, edit it, and push the whole thing back. The type identifier in the CLI is selector (lowercase, matches ConfigurationEndpoints.normalize_type output), mapping to configurationTypeId: 7.
Create a new selector:
# 1. Build body.json from scratch (see references/selectors.md → Datasource-Backed Selector)
# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate selector -b <branchId> -D body.json
# 3. Create
dxs configuration upsert selector -b <branchId> -D body.json
Edit an existing selector:
# 1. Fetch — note the envelope wrapper
dxs configuration get selector <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 selector -b <branchId> -D body.json
# 5. Push
dxs configuration upsert selector -b <branchId> -D body.json
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.
Selectors are leaner than grids or forms — the body is mostly datasourceConfig (with configId, moduleId, datasourceKeyDef, configParameters, configOutParameters), a text display-label expression, a string top, and the component-identity envelope. The full-text-search wiring spans the selector and its backing datasource — round-trip discipline (fetch → jq-extract → edit → validate → push) applies to both.
Workflow
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?]
+-----+-----+
| |
YES NO -> invoke `requirements-gathering`
| |
+-----+------+
|
[Phase 2: Decide selector shape + backing source]
Consult references/selectors.md:
- datasource-backed dropdown (most common) — wraps an existing or new
`-datasource.json` (flow-type or OData-type query inside)
- autocomplete — same shape; the dropdown's built-in full-text search
box plus the three-piece contract makes it an autocomplete
- enum dropdown — flow-type backing iterating $types.<Package>.<enum>
with formatKey helpers, getList/getByKeys parity required
Backing-datasource source:
- existing `-datasource.json` -> wrap it; verify keyDef/outParams; flow-type
backing must be the keyed-collection shape (getListFlow + getByKeysFlow
populated, non-empty keyDef) — getList-only cannot resolve selected labels
- existing `-footprintDatasource.json` (FPDS) -> NOT ALLOWED as backing;
wrap the FPDS in a flow-type `-datasource.json` that calls through,
or invoke `datasource-creator` for a fresh one
- no datasource yet -> invoke `datasource-creator`; for OData-backed
options invoke `schema-explorer` first to confirm the entity
|
[Phase 3: Author selector body]
Build body.json:
- File shape (configurationTypeId=7, *-selector.json suffix,
referenceName typically ends _dd, snake_case matches filename stem)
- Hard component-variant rule — datasourceConfig.configId points to a
`-datasource.json` (configurationTypeId=6), NEVER a
`-footprintDatasource.json` (configurationTypeId=19); query type
inside the backing is unconstrained (flows or oDataQuery)
- Key wiring — datasourceKeyDef mirrors the datasource's keyDef;
configId + moduleId resolve to the backing; moduleId is the
DATASOURCE's package, not the selector's caller
- configOutParameters mirrors the datasource's outParams shape with
full property-descriptor boilerplate on every objectTypeDef entry
- text — display-label expression on $option.entity.<DisplayField>
- top — STRING ("25"), not a number
- Sort by display label — when authoring the backing datasource for
this selector, sort by the field referenced in text (OData orderBys
or in-code sort for flow-type)
- Full-text-search three-piece contract (when backing exists solely
for this selector): (1) backing declares full_text_search inParam
(string, required: false); (2) backing applies conditional
contains-filter on the display-label field gated on
$utils.isDefined; (3) selector wires $selector.fullTextSearch into
the datasource's full_text_search via configParameters
- getList ↔ getByKeys parity — if getListFlow applies a Key transform
(formatKey, etc.), getByKeysFlow MUST apply the identical transform
(duplicate the helper definitions inside both code bodies — flows
don't share scope)
- No $types.<Package>.e_<enum> in vars/inParams/outParams — primitives
only at the param layer; cast at usage in flow code
- Sibling *Config keys explicitly null
- Invoke `schema-explorer` for OData entity / display-label / filter
field validation before authoring OData-backed selectors
- Invoke `component-wiring-check` to audit BOTH the outer host
contract (form field / hub filter / grid filter cell) AND the inner
selector → datasource contract (every declared backing inParam,
including full_text_search, needs an explicit configParameters
entry)
|
[Phase 4: Validate + push]
dxs configuration validate selector -b <branchId> -D body.json
|
+----+----+
| |
CREATE MODIFY-EXISTING
| |
| use the corrected round-trip
| (get -O envelope -> jq .json -> body)
| |
+----+----+
|
v
dxs configuration upsert selector -b <branchId> -D body.json
(upsert creates or updates by referenceName — one command for both)
|
[Phase 5: Verify in Studio (optional)]
Open the selector through its normal mount (form field / hub filter);
confirm option population, alphabetized order by display label,
full-text-search filtering live as you type, saved-selection re-hydration
shows the same display label getList showed (getByKeys parity)
|
[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 should establish what the dropdown represents (an entity, an enum, a computed list), the display label users see, the underlying Key/Value the saved selection persists, where the selector mounts (form field / hub filter / grid filter cell), and whether autocomplete behavior is required.
- No brief — invoke the
requirements-gatheringskill first. Getting the display label, the saved Key, and the mount site right up front avoids re-authoringtext,datasourceKeyDef, and the host'sconfigParameters.
Phase 2: Decide selector shape + backing source
Consult references/selectors.md before authoring. Two decisions feed in.
Selector shape. Three common shapes:
- Datasource-backed dropdown — the canonical shape. Wraps an existing or new
-datasource.jsonand renders itsoutParamsas options. Display label viatext: "$option.entity.<DisplayField>". - Autocomplete — same shape as the datasource-backed dropdown; what makes it an autocomplete is the dropdown's built-in full-text search box plus the three-piece contract (see references/selectors.md → Full-Text Search Wiring) so typed characters filter the option set live.
- Enum dropdown — a flow-type backing datasource iterates
$types.<Package>.<enum>and emits{ Key, Value }rows;formatKey/capitalizehelpers shape the display label. Requires getList ↔ getByKeys parity (see references/selectors.md → getList ↔ getByKeys Key Transformation Parity).
Backing source. Where does the option set come from?
- Existing
-datasource.json— wrap it. VerifykeyDefandoutParamsshape; the selector'sdatasourceKeyDefandconfigOutParametersmust mirror them. The branch's server-side usage gate blocks publish if the backing datasource doesn't implementgetListandgetByKeys— the same keyed-collection shape this section requires. - Existing
-footprintDatasource.json(FPDS) — not allowed as the selector's backing. Hard platform rule. Wrap the FPDS in a flow-type-datasource.jsonthat calls through, or invokedatasource-creatorfor a fresh datasource that returns the same shape. - No datasource yet — invoke
datasource-creator. For an OData-backed datasource, invokeschema-explorerfirst to confirm the entity, the display-label field, and any filter fields exist in the Footprint schema.
Phase 3: Author selector body
Build body.json from the skeleton in references/selectors.md → Datasource-Backed Selector. Key points:
-
File basics. Per the Pre-Flight Checklist below + ../datex-studio-conventions/universal-checklist.md; see references/selectors.md → Datasource-Backed Selector for the
-selector.jsonfile shape. The_dddropdown indicator onreferenceNameis the selector-specific naming cue. -
The component-variant hard rule.
datasourceConfig.configId+moduleIdmust resolve to a-datasource.json(configurationTypeId: 6), never a-footprintDatasource.json(FPDS,configurationTypeId: 19). This is a hard platform rule with no exceptions. The query type inside the backing is unconstrained — flow-type (type: "flows", used for enum dropdowns and computed lists) or OData-type (type: "oDataQuery", used when options come from a database entity). Put plainly: selectors care about the component variant, not the query type. If an FPDS already exposes the data you need, wrap it in a flow-type-datasource.jsonthat calls through — don't point the selector at the FPDS directly. See references/selectors.md → Backing Datasource Variant — Hard Rule. -
Key wiring.
datasourceConfig.configId+moduleIdidentify the backing datasource;moduleIdis the datasource's package, not the selector's caller.datasourceConfig.datasourceKeyDefmirrors the datasource'skeyDef(same id, same type) — drift here breaks saved-selection re-hydration throughgetByKeys.datasourceConfig.configParameterswires selector inputs (e.g.$selector.fullTextSearch) into datasource inParams.datasourceConfig.configOutParametersmirrors the datasource'soutParamsshape — use the sameobjectTypeDefentries with full parameter-descriptor boilerplate. See references/selectors.md → Key Points. -
textis the display-label expression. Typically"$option.entity.<DisplayField>"(e.g."$option.entity.Name","$option.entity.Key"). It runs once per option as the dropdown renders the list. For enum dropdowns whose backing emits{ Key, Value }rows shaped byformatKey, the canonical display is"$option.entity.Key". -
topis a string, not a number."25"controls how many items the dropdown shows. JSON-numeric25fails import — the platform's parameter slot expects the TS-expression string literal. -
Sort by display label. When you're authoring the backing datasource specifically for this selector, sort results by the field referenced in
text. For OData-type backings, add anorderBysentry on the field; for flow-type backings, sort the array in code before returning. Atask_status_ddselector displayingNameshould have its datasource order byNameascending — the user sees alphabetized options immediately, not insertion-order or whatever the database returned. See references/selectors.md → Key Points. -
Full-text-search three-piece contract. Applies when the backing datasource exists specifically to back this selector. Standalone datasources used by other flows/actions are not subject. Three coupled requirements — missing any one makes the dropdown's built-in search feel broken:
- Backing datasource declares
full_text_searchas an inParam — exactly that id,type: "string",required: false. - Backing datasource applies a conditional contains-filter on the display-label field (the field referenced by
text), gated on$utils.isDefined($datasource.inParams.full_text_search)so empty input disables the filter. OData-type: the canonicalcontains(<LabelField>, ${$utils.odata.formatString(...)})shape withhasCondition: trueand the condition expression. Flow-type: guard insidegetListFlowcode withif ($utils.isDefined($flow.inParams.full_text_search)) { rows = rows.filter(r => r.<LabelField>.toLowerCase().includes((... as string).toLowerCase())); }. - Selector wires
$selector.fullTextSearchinto the datasource'sfull_text_searchinput via adatasourceConfig.configParametersentry whosevalueis"$selector.fullTextSearch".
Combined with
top: "25"and "sort by display label", this is the baseline autocomplete behavior. Skipping pieces makes the dropdown feel unresponsive as users type. See references/selectors.md → Full-Text Search Wiring. - Backing datasource declares
-
getListFlow↔getByKeysFlowKey transformation parity. The platform callsgetListFlowto populate the dropdown when the user opens it, andgetByKeysFlowto re-hydrate the displayable record for a previously-saved Value. IfgetListFlowapplies a Key transformation (e.g.formatKeyturningParentOfTarget→Parent of target, or any other label massaging),getByKeysFlowmust apply the identical transformation — otherwise the saved selection round-trips throughgetByKeys, returns the raw enum id, and the dropdown control showsParentOfTargetwhile editing andParent of targetafter reload. TheformatKey/capitalizehelper functions live as nested function declarations inside each flow'scodebody — duplicate the same definitions in both flows; the platform compiles each flow independently and they don't share scope. OData-type backings rarely hit this (theirgetByKeysis auto-generated fromkeyDefand selects raw columns), but if the OData query options include computed columns or column-aliasing, the projection used at list-time must match the projection used at key-lookup time. See references/selectors.md → getList ↔ getByKeys Key Transformation Parity. -
No custom-enum FQNs in
vars/inParams/outParams. Selectors can't resolve$types.<Package>.e_<enum>in param declarations — declare those fields as primitive (stringfor string-valued enums,numberfor numeric) and cast at usage inside flow code. This bites enum-backed selectors most because the backing'soutParams.result.objectTypeDef[Value]is the place authors most want to type as the enum. -
Sibling
*Configkeys are explicitly null.customOptionsConfig,actionConfig,onInitFlowConfig, and all the other inactive sub-configs stay explicitlynullon a standard datasource-backed selector. Match this convention for structural diffing — existing selector components keep all sibling slots present and null. -
Calling-tier compliance. Selector code (
onInitFlowConfig, anyflows[]entries) calls functions via$flows.<Package>.<fn>; the function wraps any CRUD action call as$apis.<Package>.FootprintApi.extendedActions.<action_name>({...}). No direct action calls from selector code. See ../datex-studio-runtime/calling-conventions.md. -
Host contract audit. Every host that mounts this selector (form field, hub filter dropdown, grid filter cell) declares a full
configParameterscontract — everyinParamthe selector declares gets an entry on the host, including unused ones withvalue: null. Invokecomponent-wiring-checkto audit reference contracts on both sides — the outer host → selector contract AND the inner selector → backing-datasource contract (every backing inParam the selector reads, includingfull_text_search, needs an explicitconfigParametersentry on the selector with no auto-wiring). See ../component-wiring-check/references/component-wiring.md.
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 selector -b <branchId> -D body.json
# For a new selector
dxs configuration upsert selector -b <branchId> -D body.json
# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get selector <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert selector -b <branchId> -D body.json
Validation surfaces missing required fields, malformed parameter-descriptor shapes, undefined flow-id references, and reference errors before push. It does not catch the component-variant rule (a selector pointed at a -footprintDatasource.json will pass schema validation and fail at upload time or runtime), the unsorted-options bug, the half-wired full-text-search contract, or getList ↔ getByKeys Key transformation drift — those are behavioral and only surface at upload-time validation or runtime in the dropdown. Walk the pre-flight checklist embedded below before push.
Phase 5: Verify in Studio (optional)
Open the selector through its normal mount (a form field, a hub filter dropdown, a grid filter cell) and confirm:
- Options populate from the backing datasource — not empty, not raw enum ids when
formatKeyshould have run, not duplicated. - Options arrive alphabetized by the display-label field (the field referenced by
text), confirming the backing'sorderBys/ in-code sort landed. - Typing into the search box filters the option set live — confirming all three pieces of the full-text-search contract are wired (datasource declares the inParam, applies the conditional filter, selector wires
$selector.fullTextSearchthroughconfigParameters). - Save a selection, close the form/dialog, reopen — the saved Value re-hydrates and renders with the same display label
getListFlowwould have shown (confirminggetByKeysFlowKey parity). - The mount's
moduleIdresolves the selector — noReferenced configuration <name> does not existerrors in the import log.
If the running app isn't available, re-fetch the config (using the corrected jq .json extract pattern) and diff against body.json to confirm the push landed.
Pre-Flight Checklist
Before push, walk this pre-flight checklist. The fast version:
- Component variant. Backing is a
-datasource.json(configurationTypeId: 6), not a-footprintDatasource.json(configurationTypeId: 19). Query type inside can be either flows or oDataQuery. - File basics. Suffix
-selector.json;configurationTypeId: 7;referenceNametypically ends_dd— plus the universal checks (../datex-studio-conventions/universal-checklist.md). - Datasource wiring contract.
configId+moduleIdresolve to a real datasource (moduleId= datasource's package).datasourceKeyDefmirrors the datasource'skeyDef.configOutParametersmirrorsoutParamswith full param-descriptor boilerplate. - Display expression.
textreferences a real field on$option.entity.<field>. topis a string ("25"), not a number.- Datasource sorts by the display-label field (OData
orderBysor flow-code sort) — alphabetized options out of the box. - Full-text-search fully wired when the backing datasource exists solely for this selector. Three pieces: backing declares
full_text_searchinParam (string,required: false); backing applies a conditional contains-filter on the display-label field gated on$utils.isDefined; selector wires$selector.fullTextSearchinto the backing'sfull_text_searchviaconfigParameters. getListFlow↔getByKeysFlowKey transformation parity. IfgetListFlowapplies a Key transform (formatKey, etc.),getByKeysFlowapplies the identical transform. Helper functions (formatKey,capitalize) duplicated inside bothcodebodies — flows compile independently.- No
$types.<Package>.e_<enum>invars/inParams/outParams— primitives only at the param layer; cast at usage inside flow code. - Sibling
*Configkeys explicitly null —customOptionsConfig,actionConfig,onInitFlowConfig, etc., staynullon a standard datasource-backed selector. - Calling-tier compliance — selector code calls functions via
$flows.<Package>.<fn>; functions wrap CRUD actions via$apis.<Package>.FootprintApi.extendedActions.<action_name>({...}); no direct action calls from the selector. - Host carries a full
configParameterscontract — every inParam the selector declares has an entry on the host (form field / hub filter / grid filter cell); unused ones usevalue: null. Audit viacomponent-wiring-check. - Inner datasource contract. The selector's own
datasourceConfig.configParameterscovers every inParam declared on the backing datasource — includingfull_text_search(no auto-wiring; needs an explicit entry withvalue: "$selector.fullTextSearch").
Common Mistakes
| Mistake | Fix |
|---|---|
Selector pointed at a -footprintDatasource.json (FPDS, configurationTypeId: 19) | Hard platform rule: backing must be a -datasource.json (configurationTypeId: 6). If an FPDS has the data, wrap it in a flow-type -datasource.json that calls through; don't point the selector at the FPDS. |
| Confusing component variant with query type — assuming "OData dropdown means FPDS backing" | Selectors care about the component variant, not the query type. OData-backed dropdowns are perfectly fine — but the OData query lives inside a -datasource.json (type: "oDataQuery"), not an FPDS. |
top: 25 (JSON number) instead of top: "25" (TS-expression string) | The slot expects a string literal. Numeric 25 fails import. Always quote. |
text: "$option.entity.<field>" where <field> doesn't exist on the datasource's outParams | text runs against $option.entity — the option row shape comes from the datasource's outParams[result].objectTypeDef. Pick a field that's actually present; or add the field to the datasource. |
| Backing datasource returns options in insertion order; users complain dropdown is "random" | Add orderBys (OData) or sort in code (flow-type) on the display-label field. Users expect alphabetized options. |
fullTextSearch mounts but typed characters don't filter the option set | Three-piece contract is half-wired. Confirm: (1) backing declares full_text_search inParam; (2) backing applies the conditional filter on the display-label field; (3) selector's datasourceConfig.configParameters has the full_text_search entry with value: "$selector.fullTextSearch". No auto-wiring on any piece. |
getListFlow uses formatKey(key) for the Key projection but getByKeysFlow returns the raw camelCase enum id | Saved selection round-trips and renders raw while the dropdown options show the formatted version. Duplicate the identical formatKey expression (and helper function definitions) inside getByKeysFlow.code — flows compile independently and don't share scope. |
$types.<Package>.e_<enum> referenced in the selector's vars / inParams / outParams | UI components can't resolve custom-enum FQNs at the param layer. Declare as primitive (string for string-valued enums, number for numeric) and cast at usage inside flow code. |
datasourceKeyDef diverges from the backing's keyDef (different id or different type) | Saved-selection re-hydration through getByKeysFlow fails or returns the wrong record. The two keyDefs must mirror exactly. |
configOutParameters strips the descriptor boilerplate down to just {id, type} | The platform expects the full property-descriptor shape (required, description, oneOf, fromBaseConfiguration, objectTypeDef, objectType, isCollection, isSecured, isConstant, constantValue) on every entry. Use the same shape the backing's outParams carries. |
moduleId on the selector's datasourceConfig set to the selector's package instead of the datasource's | Cross-component reference rule — moduleId is always the target's package. The datasource lives where it's registered. See ../component-wiring-check/references/component-wiring.md. |
Selector's own vars / inParams / outParams fields written in flow code without top-level declaration | Import error: Property 'vars' does not exist on type 'ISelector'. Declare every var / inParam / outParam in the corresponding top-level array with the standard property-descriptor shape. |
Sibling *Config slot dropped (e.g. only datasourceConfig populated, customOptionsConfig / actionConfig / onInitFlowConfig missing entirely) | Existing selectors keep all sibling slots present and null for structural diffing. Match the convention. |
| Selector code calls an action directly | UI-tier rule: selector calls functions only. The function wraps the action via $apis.<Package>.FootprintApi.extendedActions.<action_name>({...}). |
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. |
description exceeds 100 chars | SQL column limit — push will fail validation. Tighten. |
referenceName doesn't end in _dd (when the selector is a dropdown indicator) or doesn't match filename stem | Convention drift breaks lookups and downstream callers expecting the _dd suffix. Snake_case, _dd suffix where applicable, filename stem matches. |
After your edit, invoke post-edit-verification to surface description/JSON/schema violations. For a final review, invoke component-validator.