@cxtms/cx-schema 1.9.67 → 1.9.241

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/cli.js +43 -9
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
  4. package/schemas/actions/all.json +5 -1
  5. package/schemas/actions/reload.json +17 -0
  6. package/schemas/actions/setSessionStorage.json +38 -0
  7. package/schemas/actions/sound.json +39 -0
  8. package/schemas/actions/vibrate.json +48 -0
  9. package/schemas/components/avatar.json +1 -0
  10. package/schemas/components/badge.json +79 -0
  11. package/schemas/components/barcodeScanner.json +28 -0
  12. package/schemas/components/camera.json +177 -0
  13. package/schemas/components/card.json +78 -0
  14. package/schemas/components/dashboard-widget.json +39 -0
  15. package/schemas/components/dashboard.json +46 -0
  16. package/schemas/components/dataGrid.json +15 -0
  17. package/schemas/components/field-collection.json +86 -2
  18. package/schemas/components/field.json +39 -4
  19. package/schemas/components/form.json +4 -0
  20. package/schemas/components/index.json +17 -1
  21. package/schemas/components/infoLine.json +1 -0
  22. package/schemas/components/map.json +167 -0
  23. package/schemas/components/module.json +32 -0
  24. package/schemas/components/planner.json +1 -0
  25. package/schemas/components/progressBar.json +1 -0
  26. package/schemas/components/timeline.json +216 -74
  27. package/schemas/components/timelineGrid.json +5 -1
  28. package/schemas/fields/autocomplete-googleplaces.json +2 -2
  29. package/schemas/fields/datetime.json +2 -2
  30. package/schemas/fields/index.json +4 -0
  31. package/schemas/fields/number-select.json +88 -0
  32. package/schemas/fields/rangedatetime.json +10 -2
  33. package/schemas/fields/select-async.json +2 -2
  34. package/schemas/fields/toggle.json +91 -0
  35. package/schemas/schema.graphql +11016 -12171
  36. package/schemas/schemas.json +131 -4
  37. package/schemas/workflows/flow/entity.json +3 -1
  38. package/schemas/workflows/input.json +1 -1
  39. package/schemas/workflows/output.json +1 -1
  40. package/schemas/workflows/tasks/all.json +9 -0
  41. package/schemas/workflows/tasks/commodity.json +8 -0
  42. package/schemas/workflows/tasks/contact-address.json +25 -0
  43. package/schemas/workflows/tasks/dispatch-routes.json +64 -0
  44. package/schemas/workflows/tasks/edi.json +15 -0
  45. package/schemas/workflows/tasks/export.json +21 -0
  46. package/schemas/workflows/tasks/httpRequest.json +4 -0
  47. package/schemas/workflows/tasks/import.json +5 -0
  48. package/schemas/workflows/tasks/order-move.json +38 -0
  49. package/schemas/workflows/tasks/order.json +6 -0
  50. package/schemas/workflows/tasks/organization-config.json +101 -0
  51. package/schemas/workflows/tasks/tracking-event.json +11 -3
  52. package/schemas/workflows/tasks/workflow-execute.json +4 -0
  53. package/schemas/workflows/variable.json +2 -2
  54. package/schemas/workflows/workflow.json +39 -2
  55. package/skills/cxtms-developer/SKILL.md +5 -2
  56. package/skills/cxtms-developer/ref-cli-auth.md +2 -0
  57. package/skills/cxtms-developer/ref-entity-commodity.md +41 -5
  58. package/skills/cxtms-developer/ref-entity-contact.md +46 -0
  59. package/skills/cxtms-developer/ref-entity-dispatch-routing.md +195 -0
  60. package/skills/cxtms-developer/ref-entity-geography.md +26 -1
  61. package/skills/cxtms-developer/ref-entity-job.md +6 -2
  62. package/skills/cxtms-developer/ref-entity-order-sub.md +1 -1
  63. package/skills/cxtms-developer/ref-entity-order.md +36 -3
  64. package/skills/cxtms-developer/ref-entity-organization.md +4 -2
  65. package/skills/cxtms-developer/ref-entity-shared.md +79 -3
  66. package/skills/cxtms-developer/ref-graphql-query.md +246 -4
  67. package/skills/cxtms-module-builder/SKILL.md +33 -6
  68. package/skills/cxtms-module-builder/ref-components-data.md +85 -0
  69. package/skills/cxtms-module-builder/ref-components-display.md +282 -8
  70. package/skills/cxtms-module-builder/ref-components-forms.md +386 -21
  71. package/skills/cxtms-module-builder/ref-components-interactive.md +2 -0
  72. package/skills/cxtms-module-builder/ref-components-layout.md +288 -29
  73. package/skills/cxtms-module-builder/ref-components-specialized.md +168 -20
  74. package/skills/cxtms-workflow-builder/SKILL.md +45 -6
  75. package/skills/cxtms-workflow-builder/ref-entity.md +100 -4
  76. package/skills/cxtms-workflow-builder/ref-expressions-ncalc.md +35 -2
  77. package/skills/cxtms-workflow-builder/ref-expressions-template.md +15 -1
  78. package/skills/cxtms-workflow-builder/ref-flow.md +6 -1
  79. package/skills/cxtms-workflow-builder/ref-query.md +16 -0
  80. package/skills/cxtms-workflow-builder/ref-utilities.md +32 -2
  81. package/templates/workflow-public-api.yaml +1 -0
@@ -18,8 +18,9 @@ Core tenant entity. Each organization represents a company/tenant in the system.
18
18
  | `faxNumber` | `string?` | |
19
19
  | `uniqueId` | `Guid` | Immutable external identifier |
20
20
  | `slug` | `string?` | URL-friendly identifier for Public API routes (lowercase, hyphenated) |
21
+ | `organizationType` | `OrganizationType` | `Regular` (0) or platform-managed `SystemAdministration` (1) |
21
22
  | `isDeleted` | `bool` | Soft delete |
22
- | `customValues` | `Dictionary?` | jsonb, merged on update |
23
+ | `customValues` | `Dictionary?` | jsonb, merged on update; incoming keys overwrite existing keys while unrelated keys are preserved |
23
24
  | `created` / `lastModified` | `DateTime` | Audit fields (from `AuditableEntity`) |
24
25
  | `createdBy` / `lastModifiedBy` | `string` | Audit fields |
25
26
 
@@ -37,5 +38,6 @@ Used by `PublicApiOrganizationResolver` to resolve org by slug or GUID in Public
37
38
 
38
39
  - `ChangeSlug(string?)` — trims and lowercases
39
40
  - `ChangeCompanyName(string)`, `ChangeAddressLine(string?)`, etc.
40
- - `ChangeCustomValues(Dictionary?)` — merges into existing values
41
+ - `ChangeCustomValues(Dictionary?)` — initializes `CustomValues` when needed, merges incoming values into the existing dictionary, and overwrites matching keys without dropping unrelated keys
41
42
  - `DeleteOrganization()` — sets `IsDeleted = true`
43
+ - `ChangeOrganizationType(OrganizationType)` — platform-only; ignored by dynamic update mutations
@@ -1,15 +1,37 @@
1
1
  # Shared Entity Reference
2
2
 
3
3
  ## Contents
4
+ - EntityField
4
5
  - Tag
5
6
  - Attachment
6
7
  - Division
7
8
  - EquipmentType
9
+ - Equipment
10
+ - EquipmentStatus
8
11
  - PackageType
9
12
  - NoteThread
10
13
  - Note
11
14
 
12
- Tag, Attachment, Division, EquipmentType, PackageType, Note/NoteThread.
15
+ EntityField, Tag, Attachment, Division, EquipmentType, Equipment, EquipmentStatus, PackageType, Note/NoteThread.
16
+
17
+ ## EntityField
18
+
19
+ App-module field metadata used by grids, forms, filters, and entity extensions.
20
+
21
+ | Field | Type | Notes |
22
+ |-------|------|-------|
23
+ | `entityFieldId` | `int` | PK |
24
+ | `organizationId` | `int` | |
25
+ | `entityTypeId` | `int` | FK to EntityType |
26
+ | `name` | `string?` | Field name / path |
27
+ | `fieldDefinition` | `Dictionary` | Original module field definition |
28
+ | `isCustomField` | `bool` | True for custom field definitions |
29
+ | `isInactive` | `bool` | Inactive fields are excluded when resolving fields for an `entityName` |
30
+ | `priority` | `int` | Override precedence; if multiple active module fields have the same entity/name, highest priority wins |
31
+
32
+ **GraphQL:** `entityFields(organizationId, entityName, filter, search, orderBy)` exposes `priority`. Supplying `entityName` applies inactive filtering and priority-based duplicate resolution.
33
+
34
+ ---
13
35
 
14
36
  ## Tag
15
37
 
@@ -103,6 +125,54 @@ No customValues. Linked to carriers via `CarrierEquipment` join.
103
125
 
104
126
  ---
105
127
 
128
+ ## Equipment
129
+
130
+ Physical equipment units (trailers, containers, trucks) tracked within an organization.
131
+
132
+ | Field | Type | Notes |
133
+ |-------|------|-------|
134
+ | `equipmentId` | `int` | PK |
135
+ | `organizationId` | `int` | |
136
+ | `unitNumber` | `string` | Required; unique per org |
137
+ | `equipmentTypeId` | `int` | FK to EquipmentType |
138
+ | `equipmentStatusId` | `int` | FK to EquipmentStatus |
139
+ | `notes` | `string?` | |
140
+ | `customValues` | `Dictionary` | jsonb |
141
+
142
+ **Navigation:** `organization`, `equipmentType` (EquipmentType), `equipmentStatus` (EquipmentStatus)
143
+
144
+ **Constraints:** `(organizationId, unitNumber)` unique index.
145
+
146
+ ---
147
+
148
+ ## EquipmentStatus
149
+
150
+ User-defined statuses for equipment lifecycle management.
151
+
152
+ | Field | Type | Notes |
153
+ |-------|------|-------|
154
+ | `equipmentStatusId` | `int` | PK |
155
+ | `organizationId` | `int` | |
156
+ | `statusName` | `string` | Required |
157
+ | `statusDescription` | `string?` | |
158
+ | `statusStage` | `EquipmentStatusStage` enum | Required |
159
+ | `priority` | `int` | Required; controls display/sort order |
160
+
161
+ **Navigation:** `organization`
162
+
163
+ No customValues.
164
+
165
+ ### EquipmentStatusStage Enum
166
+
167
+ | Value | Int |
168
+ |-------|-----|
169
+ | `Available` | 1 |
170
+ | `InUse` | 2 |
171
+ | `Maintenance` | 3 |
172
+ | `OutOfService` | 4 |
173
+
174
+ ---
175
+
106
176
  ## PackageType
107
177
 
108
178
  | Field | Type | Notes |
@@ -172,5 +242,11 @@ Encrypted key-value store for sensitive configuration (API keys, tokens, credent
172
242
  **Table:** `secrets` (snake_case columns)
173
243
  **Provider:** `PostgresSecretManager` (default) or `AzureKeyVault` via `SecretManager:Provider` config.
174
244
 
175
- **GraphQL mutations:** `setSecret(organizationId, secretName, secretValue)`, `deleteSecret(organizationId, secretName)`.
176
- **Org scoping:** Commands validate user org membership; qualified name `org/{orgId}/{name}` is built by the command handler.
245
+ **GraphQL mutations:** org-scoped secrets use input-wrapper mutations: `setSecret(input: { organizationId, secretName, secretValue }) { setSecretResult { secretName } }` and `deleteSecret(input: { organizationId, secretName }) { boolean }`.
246
+ **Org scoping:** Commands validate user org membership; qualified name `org/{orgId}/{name}` is built by the command handler. Module `secret` fields must pass the current organization ID in the mutation input.
247
+
248
+ ---
249
+
250
+ ## EntityKind enum
251
+
252
+ Known entity-kind values include `Order`, `Contact`, `OrderEntity`, `AccountingTransaction`, `Calendar`, `CalendarEvent`, `CalendarAvailabilityBlock`, `Other`, and `AuditChangeEntry`. Use `AuditChangeEntry` for audit-history result rows/change-log entries rather than a primary business entity.
@@ -28,7 +28,24 @@ orders(
28
28
  Entity-specific extras:
29
29
  - `orders` — `includeDraft: Boolean` (default false)
30
30
 
31
- Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, and others.
31
+ Available root queries: `orders`, `contacts`, `commodities`, `accountingTransactions`, `jobs`, `entityFields`, and others.
32
+
33
+ `entityFields` returns field metadata. When `entityName` is supplied, the API filters out inactive fields and duplicate field names resolve to the active definition with the highest `priority`.
34
+
35
+ `getEntityFieldsLastModified(organizationId, entityName, filter)` returns the newest matching field timestamp (or `null`) for cheap saved-view reconciliation without fetching every definition. Entity-name matching is case-insensitive.
36
+
37
+ Order quick search (`orders.search` and `orderGroupBy.search`) matches order number, bill-to, carriers, tracking numbers, order entities, commodity text/tracking numbers, and InventoryItem values linked through order commodities or child/container commodities. Inventory item matches include `sku`, `productName`, `description`, `modelNumber`, and JSON `customValues` such as color or size.
38
+
39
+ ## App Module Metadata Visibility
40
+
41
+ GraphQL metadata queries hide rows attached to soft-deleted app modules:
42
+
43
+ - `appComponents` / `appComponent` require the parent `AppModule.IsDeleted != true`
44
+ - `appRoutes` and public route queries require the parent module to be active
45
+ - `appPermissions` require the parent module to be active
46
+ - `entityFields` exclude fields whose `EntityType.AppModule` is deleted
47
+
48
+ Do not add extra client-side `isDeleted` filters for these metadata screens unless the UI also needs to expose deleted-module diagnostics.
32
49
 
33
50
  ## Filter Syntax (Lucene Query)
34
51
 
@@ -82,11 +99,61 @@ filter: "customValues.fieldName:\"exact match\""
82
99
  filter: "customValues.fieldName:NULL"
83
100
  ```
84
101
 
102
+ ### CustomValue join expressions
103
+
104
+ When a custom value stores a foreign key to a supported entity, use `customValues.key->entity.property` to filter by a property on the joined row. Supported join aliases include `contact`, `order`, `modeOfTransportation`, `country`, `terminal`, `contactAddress`, `port`, and `vessel`. Vessel keys are integer `VesselId` values.
105
+
106
+ ```
107
+ filter: "customValues.carrierId->contact.name:Acme*"
108
+ filter: "customValues.terminalId->terminal.name:Chicago*"
109
+ filter: "customValues.portId->port.name:Los Angeles*"
110
+ filter: "customValues.vesselId->vessel.name:Ever*"
111
+ filter: "customValues.deliveryLocationId->contactAddress.name:Warehouse*"
112
+ filter: "NOT customValues.returnLocationId->contactAddress.contactAddressId:NULL"
113
+ ```
114
+
115
+ For list-valued scalar columns, a negated match includes rows where the list column is null; a positive match requires a non-null list. For example, `NOT tags:Priority` matches null tag lists as well as lists that do not contain `Priority`.
116
+
85
117
  ### Filtered collections (bracket notation)
86
118
  ```
87
119
  filter: "children[category:CatA].name:test"
120
+ filter: "contactAddresses[addressType:Billing|Shipping].contactAddressId:[1 TO *]"
121
+ filter: "dispatchRouteStopOrders.dispatchRouteStop.dispatchRouteStopStatus.statusStage:Completed"
122
+ ```
123
+
124
+ Inside `[path:value]`, pipe-separated values are OR-combined after trimming and normal type conversion. Use this for exact multi-value bracket filters in both `filter` and `orderBy`, e.g. `children[category:CatA|CatB].name` or `lastTrackingEvent[eventDefinition.eventName:Departed|Delivered].eventDate`.
125
+
126
+ ## Mutations
127
+
128
+ ### `importContacts`
129
+
130
+ Bulk import contacts from an uploaded CSV, JSON, or XLSX file.
131
+
132
+ ```graphql
133
+ mutation ImportContacts($input: ImportContactsInput!) {
134
+ importContacts(input: $input) {
135
+ added
136
+ updated
137
+ errors
138
+ }
139
+ }
88
140
  ```
89
141
 
142
+ `ImportContactsInput` fields:
143
+
144
+ | Field | Type | Notes |
145
+ |-------|------|-------|
146
+ | `organizationId` | `Int!` | Tenant scope |
147
+ | `fileUploadUrl` | `String!` | Uploaded file URL/path |
148
+ | `contactType` | `ContactType` | Optional default for new rows; row-level `ContactType` wins |
149
+ | `columnMappings` | `MapOfString` | Map internal field paths to inbound column headers |
150
+
151
+ Behavior notes:
152
+ - Matches existing contacts by `ContactId` first.
153
+ - Ignores primary key fields during patching.
154
+ - Skips nulls and empty strings on updates so blank cells do not clear existing values.
155
+ - Resolves nested `division.divisionName` / `Division.DivisionName` to `DivisionId` when possible.
156
+
90
157
  ## OrderBy Syntax
91
158
 
92
159
  ```
@@ -97,25 +164,93 @@ orderBy: "customValues.fieldName" # Custom field sort
97
164
  orderBy: "orderNumber~ToInt32" # Type conversion during sort
98
165
  ```
99
166
 
100
- ### `lastTrackingEvent` synthetic sort path (Order / Commodity)
167
+ Join expressions are also valid in `orderBy`, including terminal and contact-address custom-value references:
101
168
 
102
- Sort the result list by the winning tracking event of each order or commodity. The "winner" is selected using `COALESCE(EventDate, Created) DESC/ASC, TrackingEventId DESC/ASC` — identical to the DataLoader logic — so SQL-level ordering and per-row resolution are always consistent.
169
+ ```
170
+ orderBy: "customValues.terminalId->terminal.name"
171
+ orderBy: "-customValues.returnLocationId->terminal.name"
172
+ orderBy: "customValues.deliveryLocationId->contactAddress.name"
173
+ orderBy: "customValues.portId->port.name"
174
+ orderBy: "customValues.vesselId->vessel.name"
175
+ orderBy: "orderSummary.totalPieces"
176
+ ```
177
+
178
+ ### Tracking-event synthetic sort paths (Order / Commodity)
179
+
180
+ Sort the result list by the winning tracking event of each order or commodity. Use `lastTrackingEvent` for the newest matching event and `firstTrackingEvent` for the oldest matching event. Winners are selected using `COALESCE(EventDate, Created)` plus `TrackingEventId` as a tie-breaker, so SQL-level ordering and per-row resolution stay consistent.
103
181
 
104
182
  ```
105
183
  orderBy: "-lastTrackingEvent.eventDate" # Latest event first (DESC)
106
- orderBy: "lastTrackingEvent.eventDate" # Earliest event first (ASC)
184
+ orderBy: "lastTrackingEvent.eventDate" # Newest-event winner, sorted ASC
185
+ orderBy: "firstTrackingEvent.eventDate" # Oldest-event winner, sorted ASC
107
186
  ```
108
187
 
109
188
  Filter to a specific event type using bracket notation before sorting:
110
189
 
111
190
  ```
112
191
  orderBy: "-lastTrackingEvent[eventDefinition.eventName:Departed].eventDate"
192
+ orderBy: "-lastTrackingEvent[eventDefinition.eventName:Departed|Delivered].eventDate"
193
+ orderBy: "firstTrackingEvent[eventDefinition.eventName:Yard Scan|Archive Yard Scan].eventDate"
113
194
  ```
114
195
 
115
196
  - The bracket predicate (`[path:value]`) filters the `TrackingEvents` collection before the winner is picked.
197
+ - Pipe-separated bracket values are OR-combined, so the winner can be selected from several event definitions.
116
198
  - Only `.eventDate` is supported as the sub-path. The expression resolves to `COALESCE(winner.EventDate, winner.Created)`, so null `EventDate` values fall back to `Created`.
117
199
  - Works on both `orders` and `commodities` top-level queries.
118
200
 
201
+ ### Commodity `getContact` resolver
202
+
203
+ Commodities expose `getContact(idPropertyName: String!)` to resolve a contact ID stored in `customValues`. The lookup is scoped to the commodity organization and returns `null` when the custom value is missing or not a valid integer contact ID.
204
+
205
+ ```graphql
206
+ {
207
+ commodities(organizationId: 1, take: 1) {
208
+ items {
209
+ commodityId
210
+ getContact(idPropertyName: "shipperContactId") {
211
+ contactId
212
+ name
213
+ }
214
+ }
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Commodity related dispatch routes
220
+
221
+ Commodities expose `getRelatedDispatchRoutes(filter, orderBy)` for dispatch routes linked through non-draft orders attached to the commodity or any descendant commodity.
222
+
223
+ ```graphql
224
+ commodities(organizationId: 1, filter: "commodityId:5001") {
225
+ items {
226
+ commodityId
227
+ getRelatedDispatchRoutes(orderBy: "routeDate") {
228
+ dispatchRouteId
229
+ name
230
+ routeDate
231
+ }
232
+ }
233
+ }
234
+ ```
235
+
236
+ ## Order Custom-Value Resolvers
237
+
238
+ Orders expose `getContactAddress(idPropertyName: String!)` to resolve a contact-address id stored in `customValues` into a `contactAddress` object. The lookup is organization-scoped and returns null/empty when the custom-value key is missing.
239
+
240
+ ```graphql
241
+ orders(organizationId: 1, take: 1) {
242
+ items {
243
+ orderId
244
+ getContactAddress(idPropertyName: "pickupContactAddressId") {
245
+ contactAddressId
246
+ name
247
+ addressLine
248
+ cityName
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
119
254
  ## Pagination
120
255
 
121
256
  ```graphql
@@ -197,6 +332,21 @@ Arguments: `organizationId: Int!`, `filter: String`, `search: String`, `take: In
197
332
 
198
333
  Filter fields: `entityName`, `primaryKey`, `userId`, `state`
199
334
 
335
+ ### Root-level: `auditEntityHistory` grid query filters
336
+
337
+ The paged audit-history query accepts DataGrid/Lucene filter strings:
338
+
339
+ - `entityName:"Order*"` — normalized to the entity path segment; quotes, trailing `*`, and trailing `~` are stripped.
340
+ - `primaryKey:"123"` — narrows the S3 path to one entity key.
341
+ - `timestamp:["2026-05-01T00:00:00Z" TO "NOW"]` — filters parsed audit file timestamps. ISO timestamps and date-math bounds (`NOW-7DAYS`, `NOW/DAY+1DAY`) are supported.
342
+ - `user.fullName:"Jane*"` — resolves users by full name, first/last name, username, or email and filters by persisted `userId`.
343
+
344
+ Sorting supports `timestamp` and `-timestamp`; descending timestamp is the default.
345
+
346
+ ### Entity fields lookup
347
+
348
+ `entityFields(organizationId, entityName, filter, search, orderBy)` matches `entityName` with case-insensitive SQL `ILIKE`, so exact names and `ILIKE` patterns are both accepted. `search` matches field names by substring.
349
+
200
350
  ### Root-level: `auditChangeSummaries` query
201
351
 
202
352
  High-level summary of changes (grouped by change event):
@@ -283,6 +433,75 @@ High-level summary of changes (grouped by change event):
283
433
  | `userName` | `String` |
284
434
  | `email` | `String` |
285
435
 
436
+ ## Workflow Execution Queries
437
+
438
+ ### `executeWorkflow` idempotency
439
+
440
+ The `executeWorkflow` mutation accepts an optional `executionId` GUID. Use one stable ID per retryable business operation, especially offline or mobile sync actions. Replays with the same organization, workflow, and `executionId` return the stored result instead of executing the workflow again; using the same ID for another workflow or organization is rejected.
441
+
442
+ ### `workflowExecutions` — paginated execution history
443
+
444
+ Returns paginated `WorkflowExecutionLog` records. Supports Lucene filter scoping, free-text search, and sorting.
445
+
446
+ ```graphql
447
+ {
448
+ workflowExecutions(
449
+ organizationId: 1
450
+ filter: "workflowId:abc123 AND status:Failed"
451
+ search: "user@example.com"
452
+ orderBy: "-executedAt"
453
+ take: 50
454
+ skip: 0
455
+ ) {
456
+ items {
457
+ executionId
458
+ workflowId
459
+ status
460
+ executedAt
461
+ userId
462
+ }
463
+ totalCount
464
+ pageInfo { hasNextPage hasPreviousPage }
465
+ }
466
+ }
467
+ ```
468
+
469
+ **Arguments:**
470
+
471
+ | Argument | Type | Notes |
472
+ |----------|------|-------|
473
+ | `organizationId` | `Int!` | Required |
474
+ | `filter` | `String` | Lucene query syntax — scope by any field |
475
+ | `search` | `String` | Free-text: matches `userId`, `executionId`, `workflowId` |
476
+ | `orderBy` | `String` | Default: `-executedAt` (newest first) |
477
+ | `take` / `skip` | `Int` | Pagination |
478
+
479
+ **`filter` examples:**
480
+ ```
481
+ filter: "workflowId:abc123"
482
+ filter: "status:Failed"
483
+ filter: "userId:user@example.com AND status:Completed"
484
+ ```
485
+
486
+ **`search` behavior:** case-insensitive match across `userId`, `executionId` (UUID string), and `workflowId` (UUID string).
487
+
488
+ ### `workflowExecution` — single execution by ID
489
+
490
+ ```graphql
491
+ {
492
+ workflowExecution(
493
+ organizationId: 1
494
+ executionId: "00000000-0000-0000-0000-000000000000"
495
+ ) {
496
+ executionId
497
+ workflowId
498
+ status
499
+ executedAt
500
+ userId
501
+ }
502
+ }
503
+ ```
504
+
286
505
  ## Discovering Fields
287
506
 
288
507
  **Always discover field names before building a query.** Do not guess field names — use the tools below.
@@ -326,3 +545,26 @@ GraphQL error messages reveal valid field/type names:
326
545
 
327
546
  - `npx cxtms schema <name>` — shows workflow/module JSON schema fields (not GraphQL)
328
547
  - Entity reference files (`ref-entity-*.md`) — document common fields, computed properties, and enums
548
+
549
+
550
+ ## Contact Nested Address Resolver
551
+
552
+ Contacts expose `getContactAddresses(filter, orderBy)` for loading only the addresses needed by a selection set. Prefer this resolver over raw `contactAddresses` when the UI/workflow needs server-side filtering or deterministic sorting.
553
+
554
+ ```graphql
555
+ query {
556
+ contacts(organizationId: 1, filter: "contactId:123") {
557
+ items {
558
+ contactId
559
+ name
560
+ getContactAddresses(filter: "addressType:Shipping", orderBy: "cityName") {
561
+ contactAddressId
562
+ addressType
563
+ cityName
564
+ stateCode
565
+ countryCode
566
+ }
567
+ }
568
+ }
569
+ }
570
+ ```
@@ -172,10 +172,10 @@ Read the relevant category ref file when building specific component types:
172
172
  |----------|-----------|------|
173
173
  | **Layout & Structure** | `layout`, `row`, `col`, `header`, `tabs`, `toolbar`, `card`, `line`, `slot` | `skills/cxtms-module-builder/ref-components-layout.md` |
174
174
  | **Forms & Input** | `form`, `field`, `field-collection`, `barcodeScanner` | `skills/cxtms-module-builder/ref-components-forms.md` |
175
- | **Data Display** | `dataGrid`, `text`, `markup`, `badge`, `icon`, `image`, `photo`, `summary`, `diff`, `viewer`, `embed` | `skills/cxtms-module-builder/ref-components-display.md` |
175
+ | **Data Display** | `dataGrid`, `text`, `markup`, `badge`, `avatar`, `infoLine`, `progressBar`, `icon`, `image`, `photo`, `summary`, `diff`, `viewer`, `embed` | `skills/cxtms-module-builder/ref-components-display.md` |
176
176
  | **Interactive & Nav** | `button`, `dropdown`, `menuButton`, `link`, `redirect`, `navbar`, `navbarItem`, `navbarLink`, `navDropdown` | `skills/cxtms-module-builder/ref-components-interactive.md` |
177
177
  | **Data & Collections** | `collection`, `list`, `listItem`, `datasource`, `script` | `skills/cxtms-module-builder/ref-components-data.md` |
178
- | **Specialized** | `calendar`, `notes`, `dashboard`, `dashboard-widget`, `widget`, `timeline`, `timeline-grid`, `oauth2` | `skills/cxtms-module-builder/ref-components-specialized.md` |
178
+ | **Specialized** | `calendar`, `notes`, `dashboard`, `dashboard-widget`, `widget`, `timeline`, `timeline-grid`, `planner`, `oauth2` | `skills/cxtms-module-builder/ref-components-specialized.md` |
179
179
 
180
180
  ### Templates
181
181
 
@@ -224,9 +224,10 @@ entities:
224
224
  fieldType: text | number | date | boolean | ...
225
225
  displayName: { en-US: "..." }
226
226
  isCustomField: false
227
+ priority: 0 # Optional override precedence; highest active duplicate field wins
227
228
  props:
228
229
  allowOrderBy: true
229
- allowFilter: true
230
+ allowFilter: true # Boolean or template expression, e.g. "{{ canFilterField }}"
230
231
  filter: # Optional filter selector
231
232
  component: "Contacts/Select"
232
233
  props:
@@ -276,6 +277,7 @@ onClick:
276
277
  - navigateBack: { fallback: "/home" } # Go back in history
277
278
  - navigateBackOrClose: { fallback: "/home" } # Go back or close dialog
278
279
  - refresh: "componentName" # Refresh a component
280
+ - reload: "formName" # Refetch and replace a form's initial values
279
281
  - notification: { message: { en-US: "Saved!" }, type: success } # success|error|warning|info
280
282
  - confirm: { title: { en-US: "Delete?" }, message: { en-US: "Are you sure?" } }
281
283
  - mutation:
@@ -290,6 +292,7 @@ onClick:
290
292
  onError: [...]
291
293
  - setFields: { "fieldName": "{{ value }}" } # Set form field values
292
294
  - setStore: { "key": "{{ value }}" } # Set store values
295
+ - setSessionStorage: { key: "isDevMode", value: "true" } # Set tab-scoped sessionStorage
293
296
  - validateForm: {} # Trigger form validation
294
297
  - dialog:
295
298
  name: "dialogName"
@@ -312,6 +315,8 @@ onClick:
312
315
  - consoleLog: { message: "debug info" }
313
316
  - openBarcodeScanner: { onScan: [...] }
314
317
  - resetDirtyState: {}
318
+ - sound: success # success|error|warning|scan
319
+ - vibrate: light # success|error|warning|light|medium|heavy
315
320
  ```
316
321
 
317
322
  ## Common Patterns
@@ -328,6 +333,7 @@ displayName:
328
333
  value: "{{ fieldName }}" # Simple variable
329
334
  value: "{{ format date L }}" # Format helper
330
335
  value: "{{ number quantity }}" # Type cast
336
+ value: "{{ sessionStorage isDevMode }}" # Current-tab sessionStorage
331
337
  value: "{{ eval items.length > 0 }}" # JavaScript expression
332
338
  isHidden: "{{ eval !canEdit }}" # Conditional visibility
333
339
  ```
@@ -400,7 +406,7 @@ Reusable select components (e.g., `Countries/Select`, `Ports/Select`) follow thi
400
406
  entity(...) { entityId name }
401
407
  }
402
408
  variables: { entityId: "{{number entityId}}" }
403
- onEditClick: # Edit action on selected item
409
+ onEditClick: # Edit action on selected item (also supported on text fields)
404
410
  - dialog:
405
411
  component: { layout: { component: layout, children: [{ component: Entity/UpdateEntity }] } }
406
412
  ```
@@ -473,5 +479,26 @@ npx cxtms app release -m "Add warehouse locations module" --org 42
473
479
  6. **Set proper entityKind** when defining entities (Order, Contact, OrderEntity, AccountingTransaction, Calendar, CalendarEvent, Other)
474
480
  7. **DataGrid options** requires ALL properties: query, rootEntityName, entityKeys, navigationType, enableDynamicGrid, enableViews, enableSearch, enablePagination, enableColumns, enableFilter, defaultView, onRowClick
475
481
  8. **Form component** requires `validationSchema` in props
476
- 9. **Do not change `appModuleId` or `filePath`** set correctly by CLI scaffold
477
- 10. **Always validate** the final YAML: `npx cxtms <file.yaml>`
482
+ 9. **Adaptive (mobile/tablet/desktop) design by default** — every form and layout must work on phones (`xs`), tablets (`sm`/`md`), and desktops (`lg+`):
483
+ - Wrap form fields in a `layout` child with `itemDefaults.size: { xs: 12, md: 6 }` (1 col on phone, 2 col on tablet+)
484
+ - Long fields (`textarea`, `quill`, `attachment`, multi-line `notes`) → `size: { xs: 12 }` (always full width)
485
+ - Cap visible columns: max 2 on `sm`, 3 on `md`, 4 on `lg`
486
+ - Group large forms into `card` sections that reflow with `size: { xs: 12, lg: 6 }`
487
+ - **Use modern MUI Grid v2 spacing**: `columnSpacing` + `rowSpacing` (both accept responsive maps `{ xs, sm, md, lg, xl }`) per https://mui.com/material-ui/react-grid/#row-and-column-spacing. Use `spacing` only when both axes should match.
488
+ - For details, see `skills/cxtms-module-builder/ref-components-layout.md` ("Adaptive / Responsive Design") and `skills/cxtms-module-builder/ref-components-forms.md` ("Mobile-Friendly Form Layout")
489
+ 10. **Do NOT use these legacy / forbidden layout patterns:**
490
+ - **No `cols: N`** on `layout` or `form` — non-responsive, ignored on mobile. Use `itemDefaults: { size: { xs: 12, md: <12/N> } }` instead.
491
+ - **No `row` component** in new code — use `layout` with responsive `columns` and `itemDefaults.size`. Existing `row` usages can stay.
492
+ - **No CSS class names (`className`) for layout** — neither on `layout`, `row`, nor `col`. Layouts must control sizing and spacing through `size`, `columns`, `spacing`/`rowSpacing`/`columnSpacing`, and `sx` / `containerSx` / `itemDefaults.sx`. Bootstrap-style classes (`col-md-6`, `row`, `container-fluid`) do not affect MUI Grid and have no effect.
493
+ - **No bare `spacing: <number>` for adaptive layouts** — use a breakpoint map (`spacing: { xs: 1, md: 3 }`) or split into `columnSpacing`/`rowSpacing`.
494
+ 11. **Do not change `appModuleId` or `filePath`** — set correctly by CLI scaffold
495
+ 12. **Always validate** the final YAML: `npx cxtms <file.yaml>`
496
+
497
+
498
+ ## Reactive page titles
499
+
500
+ Route `props.title` and root component `displayName` may be template expressions. App screens parse them against the app module store and update the page header/browser title when store values change. Unauthorized-route labels intentionally use route name/path instead of parsing store-backed title templates.
501
+
502
+ ## Screen route variables
503
+
504
+ Matched route parameters are raw strings and are available on the component's first render, including when a cached component definition mounts. Query-string values that look numeric are converted to numbers. When names collide, the matched route parameter wins over the query string. Screen variables also include `organizationId`, `locale`, and `currentUser`.
@@ -7,6 +7,91 @@
7
7
  - Datasource component
8
8
  - Script component
9
9
 
10
+ ## dataGrid export identity keys
11
+
12
+ Saved views reconcile automatically with newer entity-field schemas before their first data load. Removed fields and filters are dropped, nested reference columns are resolved, and YAML-declared computed columns are preserved.
13
+
14
+ `rowStyles.conditions` accepts a semantic `variant` or template-resolved hex `color` for theme-aware row and hover tints. Matching conditions apply in order; the last value wins.
15
+
16
+ DataGrid table views support `includeEntityKeysInExport` on each view. It defaults to `true` and forces entity key/primary key fields into exports so rows can be matched by ID on re-import. Set it to `false` only when exports must omit internal IDs.
17
+
18
+ On phone layouts, DataGrid pagination already reduces inline padding and the page-size select width below the MUI `sm` breakpoint. When placing grids in dialogs or cards, avoid adding extra horizontal padding around the grid unless it is required by the surrounding design.
19
+
20
+ ```yaml
21
+ component: dataGrid
22
+ name: contactsGrid
23
+ props:
24
+ options:
25
+ query: contacts
26
+ rootEntityName: Contact
27
+ entityKeys: [contactId]
28
+ views:
29
+ - name: allContacts
30
+ includeEntityKeysInExport: true
31
+ columns:
32
+ - name: name
33
+ - name: emailAddress
34
+ ```
35
+
36
+
37
+ ## dataGrid static items inherit parent variables
38
+
39
+ When `options.items` is a template expression, the grid parses it with local variables, store values, and parent component variables. Nested grids can reference parent-scope values such as collection items or datasource results:
40
+
41
+ ```yaml
42
+ component: dataGrid
43
+ name: commodityLines
44
+ props:
45
+ options:
46
+ items: "{{ order.commodities }}"
47
+ entityKeys: [commodityId]
48
+ ```
49
+
50
+ Use `options.items` for already-loaded arrays; use `options.query` for server-side search/sort/pagination.
51
+
52
+ ---
53
+
54
+ ## dataGrid conditional filter columns
55
+
56
+ DataGrid column `props.allowFilter` accepts either a boolean or a template expression. The filter column picker evaluates the expression with the grid's current variables before deciding whether the column can be added as a filter.
57
+
58
+ ```yaml
59
+ component: dataGrid
60
+ name: ordersGrid
61
+ props:
62
+ views:
63
+ - name: operations
64
+ columns:
65
+ - name: orderStatusName
66
+ props:
67
+ allowFilter: "{{ currentUser.canFilterStatus }}"
68
+ ```
69
+
70
+ Dynamic entity fields use the same rule through `fieldDefinition.props.allowFilter`.
71
+
72
+ ---
73
+
74
+ ## dataGrid sticky column state
75
+
76
+ DataGrid table headers let users pin columns left or right. The active view stores pinned column names under `stickyColumns.left[]` and `stickyColumns.right[]`; left-pinned columns render first, normal columns render in their original order, and right-pinned columns render last.
77
+
78
+ ```yaml
79
+ component: dataGrid
80
+ name: ordersGrid
81
+ props:
82
+ views:
83
+ - name: operations
84
+ stickyColumns:
85
+ left: [orderNumber]
86
+ right: [orderStatusName]
87
+ columns:
88
+ - name: orderNumber
89
+ - name: customerName
90
+ - name: orderStatusName
91
+ ```
92
+
93
+ ---
94
+
10
95
  ## collection
11
96
 
12
97
  Iterates over data items and renders children as templates. Supports drag-and-drop reordering.