@cxtms/cx-schema 1.9.259 → 1.9.260
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.
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workflowValidator.d.ts +7 -0
- package/dist/workflowValidator.d.ts.map +1 -1
- package/dist/workflowValidator.js +54 -0
- package/dist/workflowValidator.js.map +1 -1
- package/package.json +1 -1
- package/schemas/actions/sound.json +4 -3
- package/schemas/actions/vibrate.json +6 -4
- package/schemas/components/README.md +1 -0
- package/schemas/components/avatar.json +117 -1
- package/schemas/components/infoLine.json +82 -1
- package/schemas/components/planner.json +369 -1
- package/schemas/components/progressBar.json +115 -1
- package/schemas/fields/attachment.json +35 -2
- package/schemas/workflows/agent/agent.json +98 -0
- package/schemas/workflows/tasks/attachment.json +58 -2
- package/schemas/workflows/workflow.json +14 -19
- package/skills/cxtms-developer/ref-entity-notification.md +2 -2
- package/skills/cxtms-developer/ref-entity-shared.md +6 -2
- package/skills/cxtms-module-builder/SKILL.md +83 -5
- package/skills/cxtms-module-builder/ref-components-data.md +1 -1
- package/skills/cxtms-module-builder/ref-components-display.md +98 -15
- package/skills/cxtms-module-builder/ref-components-forms.md +27 -1
- package/skills/cxtms-module-builder/ref-components-interactive.md +3 -3
- package/skills/cxtms-module-builder/ref-components-layout.md +1 -1
- package/skills/cxtms-workflow-builder/SKILL.md +3 -1
- package/skills/cxtms-workflow-builder/ref-agent.md +283 -0
- package/skills/cxtms-workflow-builder/ref-communication.md +37 -10
- package/templates/workflow-agent.yaml +48 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"title": "Attachment Tasks",
|
|
4
|
-
"description": "Attachment entity CRUD operations",
|
|
4
|
+
"description": "Attachment entity CRUD operations and entity links",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"properties": {
|
|
7
7
|
"task": {
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"enum": [
|
|
10
10
|
"Attachment/Create@1",
|
|
11
11
|
"Attachment/Update@1",
|
|
12
|
+
"Attachment/Link@1",
|
|
13
|
+
"Attachment/Unlink@1",
|
|
12
14
|
"Attachment/Delete@1",
|
|
13
15
|
"Attachment/Get@1",
|
|
14
16
|
"Attachment/Thumbnail",
|
|
@@ -48,13 +50,67 @@
|
|
|
48
50
|
"type": "string",
|
|
49
51
|
"description": "Attachment ID"
|
|
50
52
|
},
|
|
53
|
+
"attachment": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "Attachment values (Attachment/Create@1, Attachment/Update@1)",
|
|
56
|
+
"properties": {
|
|
57
|
+
"fileName": {
|
|
58
|
+
"type": "string"
|
|
59
|
+
},
|
|
60
|
+
"attachmentType": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Picture, OtherDocument, Avatar, CustomerDocument"
|
|
63
|
+
},
|
|
64
|
+
"description": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
},
|
|
67
|
+
"parentType": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "Primary parent type: Order, Contact, AccountingTransaction, EquipmentType, Job, Commodity, Route, TrackingEvent"
|
|
70
|
+
},
|
|
71
|
+
"parentId": {
|
|
72
|
+
"type": "string",
|
|
73
|
+
"description": "Primary parent ID"
|
|
74
|
+
},
|
|
75
|
+
"links": {
|
|
76
|
+
"type": "array",
|
|
77
|
+
"description": "Additional entity links on create (Attachment/Create@1)",
|
|
78
|
+
"items": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"entityType": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"description": "Linked entity type: Order, Contact, Job, TrackingEvent"
|
|
84
|
+
},
|
|
85
|
+
"entityId": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "Linked entity ID (int id; uuid for Job)"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"required": ["entityType", "entityId"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"additionalProperties": true
|
|
95
|
+
},
|
|
96
|
+
"fileData": {
|
|
97
|
+
"description": "File content: base64 string, byte array or stream (Attachment/Create@1)"
|
|
98
|
+
},
|
|
99
|
+
"fileUrl": {
|
|
100
|
+
"type": "string",
|
|
101
|
+
"description": "URL to download the file from (Attachment/Create@1)"
|
|
102
|
+
},
|
|
51
103
|
"entityName": {
|
|
52
104
|
"type": "string",
|
|
53
105
|
"description": "Parent entity name"
|
|
54
106
|
},
|
|
107
|
+
"entityType": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "Entity type to link/unlink: Order, Contact, Job, TrackingEvent (Attachment/Link@1, Attachment/Unlink@1)"
|
|
110
|
+
},
|
|
55
111
|
"entityId": {
|
|
56
112
|
"type": "string",
|
|
57
|
-
"description": "
|
|
113
|
+
"description": "Entity ID to link/unlink: int id, uuid for Job (Attachment/Link@1, Attachment/Unlink@1)"
|
|
58
114
|
},
|
|
59
115
|
"fileName": {
|
|
60
116
|
"type": "string",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
},
|
|
68
68
|
"workflowType": {
|
|
69
69
|
"type": "string",
|
|
70
|
-
"enum": ["Document", "Quote", "Flow", "Webhook", "PublicApi", "McpTool", "McpResource", "McpPrompt"],
|
|
71
|
-
"description": "Workflow type,
|
|
70
|
+
"enum": ["Process", "Document", "Quote", "Flow", "Webhook", "PublicApi", "McpTool", "McpResource", "McpPrompt", "Agent", "EmailTemplate", "RulesTariff"],
|
|
71
|
+
"description": "Workflow type: Process (standard workflow), Document (PDF/Excel generation), Quote, Flow (declarative state machine), Webhook (HTTP endpoint), PublicApi (REST API endpoint), McpTool, McpResource, McpPrompt (organization-scoped MCP tools, resources and prompts), Agent (LLM agent), EmailTemplate or RulesTariff. Omit for standard process workflows."
|
|
72
72
|
},
|
|
73
73
|
"runAs": {
|
|
74
74
|
"type": "string",
|
|
@@ -279,6 +279,7 @@
|
|
|
279
279
|
},
|
|
280
280
|
"additionalProperties": false
|
|
281
281
|
},
|
|
282
|
+
"agent": { "$ref": "agent/agent.json" },
|
|
282
283
|
"entity": {
|
|
283
284
|
"$ref": "flow/entity.json",
|
|
284
285
|
"description": "Entity configuration for Flow workflows"
|
|
@@ -308,24 +309,18 @@
|
|
|
308
309
|
"required": ["workflow"],
|
|
309
310
|
"allOf": [
|
|
310
311
|
{
|
|
311
|
-
"if": {
|
|
312
|
-
|
|
313
|
-
"workflow": {
|
|
314
|
-
"properties": {
|
|
315
|
-
"workflowType": { "const": "Flow" }
|
|
316
|
-
},
|
|
317
|
-
"required": ["workflowType"]
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
},
|
|
321
|
-
"then": {
|
|
322
|
-
"required": ["workflow", "entity"],
|
|
323
|
-
"properties": {
|
|
324
|
-
"activities": false
|
|
325
|
-
}
|
|
326
|
-
},
|
|
312
|
+
"if": { "properties": { "workflow": { "properties": { "workflowType": { "const": "Flow" } }, "required": ["workflowType"] } } },
|
|
313
|
+
"then": { "required": ["workflow", "entity"], "properties": { "activities": false } },
|
|
327
314
|
"else": {
|
|
328
|
-
"
|
|
315
|
+
"if": { "properties": { "workflow": { "properties": { "workflowType": { "const": "Agent" } }, "required": ["workflowType"] } } },
|
|
316
|
+
"then": {
|
|
317
|
+
"required": ["workflow", "agent"],
|
|
318
|
+
"properties": {
|
|
319
|
+
"activities": false,
|
|
320
|
+
"workflow": { "properties": { "executionMode": { "const": "Sync" } }, "required": ["executionMode"] }
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
"else": { "required": ["workflow", "activities"] }
|
|
329
324
|
}
|
|
330
325
|
},
|
|
331
326
|
{
|
|
@@ -13,8 +13,8 @@ Real-time notification system. Notifications are org-scoped with per-user read t
|
|
|
13
13
|
| `type` | `NotificationType` | System=0, OrderUpdate=1, TaskAssignment=2, Alert=3, Info=4 |
|
|
14
14
|
| `priority` | `NotificationPriority` | Low=0, Normal=1, High=2, Urgent=3 |
|
|
15
15
|
| `targetUserId` | `string?` | If set, targets one user; if null, broadcasts to all active org users |
|
|
16
|
-
| `entityType` | `string?` | Linked entity type (e.g. "Order", "Job") |
|
|
17
|
-
| `entityId` | `
|
|
16
|
+
| `entityType` | `string?` | Linked entity type (e.g. "Order", "Job", "AgentSession") |
|
|
17
|
+
| `entityId` | `string?` | Linked entity id (varchar(64)). Most entity types still store an integer PK as text; `entityType: "AgentSession"` carries the session's GUID (`AgentSessionId.ToString()`) instead. |
|
|
18
18
|
| `expiresAt` | `DateTime?` | Optional expiration |
|
|
19
19
|
| `created` / `lastModified` | `DateTime` | Audit fields (from `AuditableEntity`) |
|
|
20
20
|
| `createdBy` / `lastModifiedBy` | `string` | Audit fields |
|
|
@@ -77,7 +77,7 @@ Tagging system for orders, commodities, inventory items.
|
|
|
77
77
|
|
|
78
78
|
## Attachment
|
|
79
79
|
|
|
80
|
-
File attachments linked to
|
|
80
|
+
File attachments. `parentType`/`parentId` is the primary parent; an attachment can also be linked to more Orders, Contacts, Jobs and TrackingEvents (link tables).
|
|
81
81
|
|
|
82
82
|
| Field | Type | Notes |
|
|
83
83
|
|-------|------|-------|
|
|
@@ -91,7 +91,7 @@ File attachments linked to orders, contacts, jobs, etc.
|
|
|
91
91
|
| `description` | `string?` | |
|
|
92
92
|
| `attachmentType` | `AttachmentType` enum | Picture=1, OtherDocument, Avatar, CustomerDocument |
|
|
93
93
|
| `parentId` | `string?` | Polymorphic FK (entity ID as string) |
|
|
94
|
-
| `parentType` | `AttachmentParentType` enum | None=0, Order=1, Contact=2, AccountingTransaction=3, EquipmentType=4, Job=5, Commodity=6 |
|
|
94
|
+
| `parentType` | `AttachmentParentType` enum | None=0, Order=1, Contact=2, AccountingTransaction=3, EquipmentType=4, Job=5, Commodity=6, Route=7, TrackingEvent=8 |
|
|
95
95
|
| `status` | `AttachmentStatus` enum | Active=0, PendingUpload=1, UploadFailed=2 |
|
|
96
96
|
| `category` | `AttachmentCategory` enum | General=0, FieldValue=1 |
|
|
97
97
|
| `organizationId` | `int` | |
|
|
@@ -103,6 +103,10 @@ File attachments linked to orders, contacts, jobs, etc.
|
|
|
103
103
|
- `presignedFileUri`, `presignedPreviewUri`, `presignedThumbnailUri` — signed URLs
|
|
104
104
|
- `getPresignedUri(expiresInDays, uriType)` — custom resolver
|
|
105
105
|
- `getParentOrder` — resolve parent Order
|
|
106
|
+
- `links { entityType entityId }` — all links, including the primary one
|
|
107
|
+
- Filter by link: `attachments(filter: "orderLinks.orderId:123")` — also `contactLinks.contactId`, `jobLinks.jobId`, `trackingEventLinks.trackingEventId`
|
|
108
|
+
- `job { getJobAttachments }`, `trackingEvent { getTrackingEventAttachments }` — attachments linked to the entity
|
|
109
|
+
- Mutations: `linkAttachment` / `unlinkAttachment` (`attachmentId`, `entityType`, `entityId`); `createAttachment` `values.links: [{ entityType, entityId }]`. Workflows: `Attachment/Link@1`, `Attachment/Unlink@1`
|
|
106
110
|
|
|
107
111
|
---
|
|
108
112
|
|
|
@@ -315,8 +315,9 @@ onClick:
|
|
|
315
315
|
- consoleLog: { message: "debug info" }
|
|
316
316
|
- openBarcodeScanner: { onScan: [...] }
|
|
317
317
|
- resetDirtyState: {}
|
|
318
|
-
- sound: success
|
|
319
|
-
- vibrate:
|
|
318
|
+
- sound: success # audible cue: success|error|warning|scan (or { type, volume: 0-1 })
|
|
319
|
+
- vibrate: success # haptic: success|error|warning|light|medium|heavy
|
|
320
|
+
- vibrate: { pattern: [100, 50, 100] } # custom ms pattern (web Vibration API convention) or { duration: 200 }
|
|
320
321
|
```
|
|
321
322
|
|
|
322
323
|
## Common Patterns
|
|
@@ -334,10 +335,87 @@ value: "{{ fieldName }}" # Simple variable
|
|
|
334
335
|
value: "{{ format date L }}" # Format helper
|
|
335
336
|
value: "{{ number quantity }}" # Type cast
|
|
336
337
|
value: "{{ sessionStorage isDevMode }}" # Current-tab sessionStorage
|
|
337
|
-
|
|
338
|
-
|
|
338
|
+
isHidden: "{{ !canEdit }}" # Negation — no eval needed
|
|
339
|
+
value: "{{ eval items.map(x => x.id) }}" # JavaScript eval — last resort
|
|
339
340
|
```
|
|
340
341
|
|
|
342
|
+
**Prefer built-in template functions over `eval`.** The engine ships named functions for
|
|
343
|
+
the common cases — use them first. Reach for `eval` only when nothing below fits
|
|
344
|
+
(array `.map()`/`.filter()`, arithmetic, template literals, object building, ternaries).
|
|
345
|
+
|
|
346
|
+
| Function | Example | Notes |
|
|
347
|
+
|----------|---------|-------|
|
|
348
|
+
| `number` / `number!` | `{{ number quantity }}` | Cast to number; `number!` returns `0` for null |
|
|
349
|
+
| `string` / `boolean` | `{{ boolean isActive }}` | Cast; `boolean` treats string `'true'` as true |
|
|
350
|
+
| `nullIfEmpty` | `{{ nullIfEmpty notes }}` | `''`, `0`, undefined → null |
|
|
351
|
+
| `luceneString` | `{{ luceneString search }}` | Escape for Lucene filter strings |
|
|
352
|
+
| `isEqual` | `{{ isEqual status 'Pending' }}` | Deep equality; numeric strings coerced |
|
|
353
|
+
| `isTrue` | `{{ isTrue customValues.flag }}` | `true`, `'true'`, or truthy |
|
|
354
|
+
| `isNullOrEmpty` | `{{ isNullOrEmpty orderNumber }}` | null, undefined, `''`, or empty array |
|
|
355
|
+
| `any` | `{{ any selectedItems }}` | Array/string has at least one element |
|
|
356
|
+
| `moreThan` / `lessThan` | `{{ moreThan totalAmount 100 }}` | Numbers by value; arrays/strings by length |
|
|
357
|
+
| `startsWith` / `endsWith` | `{{ startsWith trackingNumber 'TRK' }}` | String prefix/suffix check |
|
|
358
|
+
| `includes` / `contains` | `{{ includes description 'rush' }}` | Substring check (aliases) |
|
|
359
|
+
| `trim` | `{{ trim name }}` | Trim whitespace |
|
|
360
|
+
| `round` | `{{ round totalAmount 2 }}` | `toFixed(n)` returned as number |
|
|
361
|
+
| `encodeURIComponent` | `{{ encodeURIComponent returnUrl }}` | URL-encode |
|
|
362
|
+
| `format` | `{{ format createdDate L }}` | moment format (`L`, `MM/DD/YYYY`, `fromNow`, …) |
|
|
363
|
+
| `formatTz` | `{{ formatTz pickupDate 'MM/DD h:mm A' 'America/New_York' }}` | Format UTC value in a timezone |
|
|
364
|
+
| `dateDiff` | `{{ dateDiff dueDate startDate }}` | Whole days, first minus second |
|
|
365
|
+
| `daysBetween` | `{{ daysBetween pickupDate deliveryDate }}` | Absolute day difference |
|
|
366
|
+
| `daysUntil` / `daysAgo` | `{{ daysUntil dueDate }}` | Days between now and the date |
|
|
367
|
+
| `isDateBefore` / `isDateAfter` | `{{ isDateBefore pickupDate deliveryDate }}` | Date comparison |
|
|
368
|
+
| `hasPermission` | `{{ hasPermission Orders/Update }}` | Current user permission check |
|
|
369
|
+
| `fromConfig` | `{{ fromConfig apps.myFeature key }}` | Organization config lookup |
|
|
370
|
+
| `localStorage` / `sessionStorage` | `{{ localStorage recentSearch }}` | Browser storage read |
|
|
371
|
+
| `parse` | `{{ parse configs.labelTemplate }}` | Re-parse a template stored in a variable |
|
|
372
|
+
|
|
373
|
+
Combinators — also no `eval` needed:
|
|
374
|
+
- `!` / `!!` prefix negates / booleanizes any path or function: `{{ !isEqual status 'Pending' }}`, `{{ !!order.notes }}`
|
|
375
|
+
- `||` / `&&` combine paths and comparisons: `{{ orderId || quoteId }}`, `{{ !pickupForm.contactAddressId || !pickupForm.pickupDate }}`
|
|
376
|
+
- `?.` optional chaining inside paths: `{{ order?.customer?.name }}`
|
|
377
|
+
- Quoted arguments are string literals; parenthesized arguments evaluate as nested expressions
|
|
378
|
+
|
|
379
|
+
```yaml
|
|
380
|
+
# ❌ eval where a built-in exists
|
|
381
|
+
disabled: "{{ eval status !== 'Pending' }}"
|
|
382
|
+
isHidden: "{{ eval selectedItems.length === 0 }}"
|
|
383
|
+
# ✅ built-in equivalents
|
|
384
|
+
disabled: "{{ !isEqual status 'Pending' }}"
|
|
385
|
+
isHidden: "{{ !any selectedItems }}"
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**When `eval` is genuinely needed, keep it human-readable.** Inline form is only for
|
|
389
|
+
trivial expressions. Anything longer (ternaries, method chains, or ~60+ characters)
|
|
390
|
+
MUST be written as a multiline YAML block scalar:
|
|
391
|
+
|
|
392
|
+
```yaml
|
|
393
|
+
# Multiline eval — block scalar, one clause per line
|
|
394
|
+
color: >-
|
|
395
|
+
{{ eval
|
|
396
|
+
item.status === 'delayed' ? '#e53e3e'
|
|
397
|
+
: item.priority === 'high' ? '#ffa726'
|
|
398
|
+
: '#4ecdc4'
|
|
399
|
+
}}
|
|
400
|
+
label: >-
|
|
401
|
+
{{ eval
|
|
402
|
+
attachments.items
|
|
403
|
+
.map(x => x.fileName)
|
|
404
|
+
.join(', ')
|
|
405
|
+
}}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Multiline eval rules:
|
|
409
|
+
- **Always use `>-` (folded, strip)** — never `>`, `|`, or `|+`. Those keep a trailing
|
|
410
|
+
newline, so the value no longer ends with `}}` and the engine silently falls back to
|
|
411
|
+
string interpolation instead of evaluating the expression.
|
|
412
|
+
- The block must contain exactly one `{{ eval ... }}` and nothing else — surrounding text
|
|
413
|
+
or a second `{{ }}` also switches the engine to string interpolation.
|
|
414
|
+
- Block scalars need no YAML quoting or escaping — use natural JavaScript quotes
|
|
415
|
+
(`'` or `"`) inside the expression.
|
|
416
|
+
- Put `{{ eval` on the first line, one clause per indented line, and `}}` on its own
|
|
417
|
+
closing line.
|
|
418
|
+
|
|
341
419
|
### Permissions
|
|
342
420
|
```yaml
|
|
343
421
|
permission: "ModuleName/Read" # Single string (PascalCase/Action)
|
|
@@ -474,7 +552,7 @@ npx cxtms app release -m "Add warehouse locations module" --org 42
|
|
|
474
552
|
- Component names: Module/Component pattern (e.g., `WarehouseLocations/List`)
|
|
475
553
|
- Route paths: kebab-case (e.g., `/warehouse-locations`)
|
|
476
554
|
- Permission names: PascalCase with slashes (e.g., `WarehouseLocations/Read`, `System/Contacts/Update`)
|
|
477
|
-
4. **Template expressions** use `{{ expression }}` syntax (double curly braces)
|
|
555
|
+
4. **Template expressions** use `{{ expression }}` syntax (double curly braces); prefer built-in functions (`isEqual`, `any`, `isNullOrEmpty`, `format`, …) over `eval`, and write non-trivial `eval` expressions as multiline block scalars (`>-`) — see Template expressions
|
|
478
556
|
5. **Include filePath** property pointing to the YAML file location
|
|
479
557
|
6. **Set proper entityKind** when defining entities (Order, Contact, OrderEntity, AccountingTransaction, Calendar, CalendarEvent, Other)
|
|
480
558
|
7. **DataGrid options** requires ALL properties: query, rootEntityName, entityKeys, navigationType, enableDynamicGrid, enableViews, enableSearch, enablePagination, enableColumns, enableFilter, defaultView, onRowClick
|
|
@@ -314,7 +314,7 @@ props:
|
|
|
314
314
|
- navigate: "orders/{{ item.id }}"
|
|
315
315
|
- label: "Delete"
|
|
316
316
|
icon: trash
|
|
317
|
-
disabled: "{{
|
|
317
|
+
disabled: "{{ isEqual item.status 'Completed' }}"
|
|
318
318
|
onClick:
|
|
319
319
|
- confirm: { title: { en-US: "Delete?" } }
|
|
320
320
|
onClick:
|
|
@@ -477,21 +477,6 @@ props:
|
|
|
477
477
|
|
|
478
478
|
Compact theme-aware badge. Prefer semantic variants for new modules; the dot is opt-in.
|
|
479
479
|
|
|
480
|
-
## avatar, infoLine, and progressBar
|
|
481
|
-
|
|
482
|
-
Use these template-aware display components for compact entity cards and planner headers.
|
|
483
|
-
|
|
484
|
-
```yaml
|
|
485
|
-
- component: avatar
|
|
486
|
-
props: { name: '{{ row.record.name }}', colorSeed: '{{ row.record.id }}', size: 40 }
|
|
487
|
-
- component: infoLine
|
|
488
|
-
props: { icon: tabler-phone, value: '{{ row.record.phone }}', href: 'tel:{{ row.record.phone }}' }
|
|
489
|
-
- component: progressBar
|
|
490
|
-
props: { items: '{{ row.items }}', completedPath: status, completedValue: Completed }
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
`avatar` derives initials from names/email and uses `src` as an optional image. `infoLine` renders nothing for an empty value and only links allowlisted URL schemes. `progressBar` accepts direct `value`/`max` or derives counts from `items`; choose `count`/`percent` and `linear`/`circular`/`segmented` display modes. On mobile, `activeColor` and `trackColor` customize segmented progress; `tooltip` is exposed to assistive technology rather than hover UI.
|
|
494
|
-
|
|
495
480
|
**Props:**
|
|
496
481
|
| Prop | Type | Description |
|
|
497
482
|
|------|------|-------------|
|
|
@@ -534,6 +519,104 @@ props:
|
|
|
534
519
|
|
|
535
520
|
---
|
|
536
521
|
|
|
522
|
+
## avatar
|
|
523
|
+
|
|
524
|
+
Template-aware display component for compact entity cards and planner headers. Circular avatar with automatic two-letter initials. Sources tried in priority order: `src` image → `firstName` + `lastName` → `name` → `email` → placeholder user icon. Color is picked deterministically from the theme palette by hashing `colorSeed`.
|
|
525
|
+
|
|
526
|
+
**Props (all template-parsed):**
|
|
527
|
+
| Prop | Type | Description |
|
|
528
|
+
|------|------|-------------|
|
|
529
|
+
| `src` | `string` | Image URL; falls back to initials when empty or broken |
|
|
530
|
+
| `name` | `string` | Full name: `Andre Kovac` → AK, `Kovac, Andre` → AK, `Madonna` → MA |
|
|
531
|
+
| `firstName` / `lastName` | `string` | Explicit name parts (organization user form); take priority over `name` |
|
|
532
|
+
| `email` | `string` | Last text fallback: local part, digits dropped (`andre.kovac@…` → AK) |
|
|
533
|
+
| `colorSeed` | `string\|number` | Stable color seed — use an id (`contactId`, user id), not the name |
|
|
534
|
+
| `color` | `string` | Pin a theme color: `primary` \| `secondary` \| `error` \| `warning` \| `info` \| `success` |
|
|
535
|
+
| `bgcolor` / `textColor` | `string` | Explicit CSS color overrides |
|
|
536
|
+
| `size` | `number` | Diameter in px, default 40 |
|
|
537
|
+
| `variant` | `string` | `circular` (default) \| `rounded` \| `square` |
|
|
538
|
+
| `skin` | `string` | `light` (default, tinted bg) \| `filled` \| `light-static` |
|
|
539
|
+
| `tooltip` | `string\|localized` | Tooltip on hover |
|
|
540
|
+
| `onClick` | `Action[]` | Actions on click; when present the avatar is clickable |
|
|
541
|
+
|
|
542
|
+
```yaml
|
|
543
|
+
component: avatar
|
|
544
|
+
name: driverLaneAvatar
|
|
545
|
+
props:
|
|
546
|
+
name: '{{ row.record.name }}'
|
|
547
|
+
colorSeed: '{{ row.record.contactId }}'
|
|
548
|
+
size: 40
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
## infoLine
|
|
554
|
+
|
|
555
|
+
Compact "icon + value" line for entity cards (phone, email, truck, container…). Abstract: YAML assembles the value string, the component only renders it. **An empty value renders nothing**, so optional lines can be declared unconditionally.
|
|
556
|
+
|
|
557
|
+
**Props (all template-parsed):**
|
|
558
|
+
| Prop | Type | Description |
|
|
559
|
+
|------|------|-------------|
|
|
560
|
+
| `icon` | `string` | Icon class: `tabler-phone`, `tabler-mail`, `tabler-truck`, `tabler-container`, FA names |
|
|
561
|
+
| `iconColor` | `string` | Theme path or CSS color, default `text.secondary` |
|
|
562
|
+
| `value` | `string` | The text; assemble separators in YAML: `'{{ n }} • {{ size }}'`. Empty → not rendered |
|
|
563
|
+
| `href` | `string` | Renders the value as a link: `tel:{{ … }}`, `mailto:{{ … }}`, or a URL; only allowlisted URL schemes are linked |
|
|
564
|
+
| `onClick` | `Action[]` | Actions on click (alternative to href) |
|
|
565
|
+
| `truncate` | `boolean` | Ellipsis on overflow; tooltip defaults to the full value |
|
|
566
|
+
| `tooltip` | `string\|localized` | Explicit tooltip, overrides the truncate default |
|
|
567
|
+
| `variant` | `string` | `default` \| `chip` — bordered rounded container |
|
|
568
|
+
|
|
569
|
+
```yaml
|
|
570
|
+
- component: infoLine
|
|
571
|
+
name: driverPhone
|
|
572
|
+
props:
|
|
573
|
+
icon: tabler-phone
|
|
574
|
+
value: '{{ row.record.phoneNumber }}'
|
|
575
|
+
href: 'tel:{{ row.record.phoneNumber }}'
|
|
576
|
+
- component: infoLine
|
|
577
|
+
name: containerChip
|
|
578
|
+
props:
|
|
579
|
+
icon: tabler-container
|
|
580
|
+
value: '{{ container.number }} • {{ container.size }}'
|
|
581
|
+
variant: chip
|
|
582
|
+
truncate: true
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## progressBar
|
|
588
|
+
|
|
589
|
+
Abstract progress bar. Feed it `value`/`max` directly, or an `items` array with a declarative completed predicate — what the numbers mean (moves, stops, hours…) is the module's business. **max 0 or missing → nothing renders**, so it can be declared unconditionally.
|
|
590
|
+
|
|
591
|
+
**Props (all template-parsed):**
|
|
592
|
+
| Prop | Type | Description |
|
|
593
|
+
|------|------|-------------|
|
|
594
|
+
| `value` | `number\|string` | Completed amount; overrides the derived count |
|
|
595
|
+
| `max` | `number\|string` | Total; defaults to `items.length` when `items` is set |
|
|
596
|
+
| `items` | `string` | Single-chunk template → array, e.g. `'{{ row.items }}'` |
|
|
597
|
+
| `completedPath` | `string` | Dot path inside each item, e.g. `orderMove.orderMoveStatus.statusStage` |
|
|
598
|
+
| `completedValue` | `string` | Match at the path counts as completed; omitted → any truthy value |
|
|
599
|
+
| `label` | `string\|localized` | Caption on the left, e.g. `Moves` |
|
|
600
|
+
| `showValue` | `boolean` | Value caption (`2/5`), default true |
|
|
601
|
+
| `valueFormat` | `string` | `count` (default) \| `percent` |
|
|
602
|
+
| `variant` | `string` | `linear` (default) \| `circular` \| `segmented` |
|
|
603
|
+
| `height` / `size` | `number` | Linear thickness (6) / circular diameter (40) |
|
|
604
|
+
| `color` | `string` | Theme name or CSS color; default primary → success at 100% |
|
|
605
|
+
| `activeColor` / `trackColor` | `string` | Mobile: completed-segment color / track or incomplete-segment color for the segmented variant |
|
|
606
|
+
| `tooltip` | `string\|localized` | Tooltip on hover (on mobile it is exposed to assistive technology rather than hover UI) |
|
|
607
|
+
|
|
608
|
+
```yaml
|
|
609
|
+
- component: progressBar
|
|
610
|
+
name: laneMovesProgress
|
|
611
|
+
props:
|
|
612
|
+
label: Moves
|
|
613
|
+
items: '{{ row.items }}'
|
|
614
|
+
completedPath: orderMove.orderMoveStatus.statusStage
|
|
615
|
+
completedValue: Completed
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
537
620
|
## icon
|
|
538
621
|
|
|
539
622
|
Icon renderer. Supports FontAwesome, Tabler, and Feather icons.
|
|
@@ -448,6 +448,32 @@ still control the displayed label and stored value.
|
|
|
448
448
|
fields: ["name", "address_components", "formatted_address", "geometry", "place_id"]
|
|
449
449
|
```
|
|
450
450
|
|
|
451
|
+
**Attachment options (under `options`):**
|
|
452
|
+
| Prop | Type | Description |
|
|
453
|
+
|------|------|-------------|
|
|
454
|
+
| `parentId` / `parentType` | `string` | Primary parent of the uploaded attachment (`parentType` defaults to `Order`) |
|
|
455
|
+
| `links` | `{entityType, entityId}[]` or template | More entities to link the same upload to: `Order`, `Contact`, `Job`, `TrackingEvent`. Entries whose `entityType` or `entityId` resolves empty are skipped |
|
|
456
|
+
| `category` | `string` | Attachment category (web) |
|
|
457
|
+
| `allowMultiple` / `allowCamera` / `clearAfterUpload` | `boolean` | Upload behavior |
|
|
458
|
+
| `maxSize` / `allowedExtensions` | `number` / `string[]` | File limits |
|
|
459
|
+
| `onUploaded` | actions | Runs after each upload; `attachment` is in scope |
|
|
460
|
+
|
|
461
|
+
```yaml
|
|
462
|
+
# One upload linked to the order (parent) and a tracking event
|
|
463
|
+
- component: field
|
|
464
|
+
name: eventPhoto
|
|
465
|
+
props:
|
|
466
|
+
type: attachment
|
|
467
|
+
options:
|
|
468
|
+
parentType: Order
|
|
469
|
+
parentId: "{{ orderId }}"
|
|
470
|
+
links:
|
|
471
|
+
- entityType: TrackingEvent
|
|
472
|
+
entityId: "{{ trackingEventId }}"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
`Attachments/ListAttachments` (cx-app-core) takes the same `links` prop next to `parentId`/`parentType` and passes it to its upload field; its grid still lists by `parentId`/`parentType`.
|
|
476
|
+
|
|
451
477
|
**Events:**
|
|
452
478
|
| Event | Description |
|
|
453
479
|
|-------|-------------|
|
|
@@ -567,7 +593,7 @@ still control the displayed label and stored value.
|
|
|
567
593
|
type: textarea
|
|
568
594
|
label: { en-US: "Notes" }
|
|
569
595
|
rows: 4
|
|
570
|
-
disabled: "{{
|
|
596
|
+
disabled: "{{ !canEdit }}"
|
|
571
597
|
```
|
|
572
598
|
|
|
573
599
|
---
|
|
@@ -83,7 +83,7 @@ MUI Button with icon, label, loading state, and action dispatch.
|
|
|
83
83
|
icon: check-circle
|
|
84
84
|
options:
|
|
85
85
|
variant: success
|
|
86
|
-
disabled: "{{
|
|
86
|
+
disabled: "{{ !isEqual status 'Pending' }}"
|
|
87
87
|
onClick:
|
|
88
88
|
- mutation:
|
|
89
89
|
command: "mutation($id: Int!) { approveOrder(id: $id) { success } }"
|
|
@@ -131,7 +131,7 @@ props:
|
|
|
131
131
|
- dialog:
|
|
132
132
|
component: Module/ImportDialog
|
|
133
133
|
- label: { en-US: "Archive All" }
|
|
134
|
-
disabled: "{{
|
|
134
|
+
disabled: "{{ !any selectedItems }}"
|
|
135
135
|
onClick:
|
|
136
136
|
- confirm: { title: { en-US: "Archive?" }, message: { en-US: "Archive selected items?" } }
|
|
137
137
|
```
|
|
@@ -216,7 +216,7 @@ Programmatic navigation — renders nothing (or spinner with delay).
|
|
|
216
216
|
component: redirect
|
|
217
217
|
name: createRedirect
|
|
218
218
|
props:
|
|
219
|
-
condition: "{{
|
|
219
|
+
condition: "{{ !id }}"
|
|
220
220
|
path: "/orders/create"
|
|
221
221
|
|
|
222
222
|
# External redirect with delay
|
|
@@ -46,6 +46,7 @@ npx cxtms create workflow <name> --template <template>
|
|
|
46
46
|
| `webhook` | HTTP endpoint for external callers |
|
|
47
47
|
| `public-api` | REST API endpoint with OpenAPI docs |
|
|
48
48
|
| `mcp-tool` | Synchronous tool exposed through the organization MCP server |
|
|
49
|
+
| `agent` | AI agent that calls other workflows as tools (workflowType: Agent) |
|
|
49
50
|
|
|
50
51
|
### Step 2: Read the generated file
|
|
51
52
|
|
|
@@ -105,7 +106,7 @@ workflow:
|
|
|
105
106
|
isActive: true
|
|
106
107
|
enableAudit: true
|
|
107
108
|
filePath: "workflows/<name>.yaml"
|
|
108
|
-
workflowType: Document | Quote | Flow | Webhook | PublicApi | McpTool | McpResource | McpPrompt # omit for standard process workflows
|
|
109
|
+
workflowType: Document | Quote | Flow | Webhook | PublicApi | McpTool | McpResource | McpPrompt | Agent | EmailTemplate | RulesTariff # omit for standard process workflows
|
|
109
110
|
runAs: "system" # Optional elevated permissions
|
|
110
111
|
tags: ["tag1", "tag2"]
|
|
111
112
|
concurrency: # Optional
|
|
@@ -342,6 +343,7 @@ Implicit variable: `iteration` (zero-based).
|
|
|
342
343
|
| Template Expressions & Value Directives | `!cat skills/cxtms-workflow-builder/ref-expressions-template.md` |
|
|
343
344
|
| NCalc Expressions & Functions | `!cat skills/cxtms-workflow-builder/ref-expressions-ncalc.md` |
|
|
344
345
|
| Flow Workflows (state machines) | `!cat skills/cxtms-workflow-builder/ref-flow.md` |
|
|
346
|
+
| Agent Workflows (LLM agents) | `!cat skills/cxtms-workflow-builder/ref-agent.md` |
|
|
345
347
|
|
|
346
348
|
## Dynamic Schema Access (load on demand)
|
|
347
349
|
|