@cxtms/cx-schema 1.9.258 → 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/components/timeline.json +24 -0
- 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-module-builder/ref-components-specialized.md +3 -0
- 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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "agent/agent.json",
|
|
4
|
+
"title": "Agent section",
|
|
5
|
+
"description": "Configuration for workflowType: Agent. An LLM agent that calls other workflows as tools.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["instructions"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"description": { "type": "string", "description": "What this agent does. Shown to callers and to other agents that may invoke it." },
|
|
10
|
+
"ui": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"description": "How the AI Assistant chat shows this agent. Display-only; no runtime effect.",
|
|
13
|
+
"properties": {
|
|
14
|
+
"name": { "type": "string", "description": "Display name. Defaults to the workflow name." },
|
|
15
|
+
"shortDescription": { "type": "string", "description": "One line under the name, e.g. 'Status, ETAs, exceptions, POD'." },
|
|
16
|
+
"icon": { "type": "string", "description": "Tabler icon name without the 'tabler-' prefix, e.g. 'map-pin'. Defaults to 'robot'." },
|
|
17
|
+
"color": { "type": "string", "enum": ["primary", "secondary", "info", "success", "warning", "error"], "description": "Theme palette color for the agent's icon. Defaults to 'primary'." },
|
|
18
|
+
"prompts": { "type": "array", "items": { "type": "string" }, "maxItems": 5, "description": "Suggested prompts shown on an empty chat. At most 5." }
|
|
19
|
+
},
|
|
20
|
+
"additionalProperties": false
|
|
21
|
+
},
|
|
22
|
+
"instructions": { "type": "string", "minLength": 1, "description": "System instructions. Handlebars template over workflow variables and inputs." },
|
|
23
|
+
"model": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"fromConfig": { "type": "string", "description": "Organization config name holding provider, model, apiKey, endpoint. Default ai.default." },
|
|
27
|
+
"name": { "type": "string", "description": "Overrides the config's model name." },
|
|
28
|
+
"temperature": { "type": "number", "minimum": 0, "maximum": 2 },
|
|
29
|
+
"contextWindow": { "type": "integer", "minimum": 1, "description": "The model's context window in tokens. Needed when this agent overrides the model name via model.name: the organization config's window describes the config's model, not this one. Defaults to 256000 when neither the agent nor the resolved model configuration declares one." }
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": false
|
|
32
|
+
},
|
|
33
|
+
"session": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"type": { "type": "string", "enum": ["task", "chat"], "default": "task", "description": "task ends when set_result is called; chat is open-ended." },
|
|
37
|
+
"maxTurns": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 },
|
|
38
|
+
"timeout": { "type": "integer", "minimum": 1, "default": 300, "description": "Seconds." }
|
|
39
|
+
},
|
|
40
|
+
"additionalProperties": false
|
|
41
|
+
},
|
|
42
|
+
"result": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"description": "JSON Schema (type: object) for the set_result argument.",
|
|
45
|
+
"properties": { "type": { "const": "object" } },
|
|
46
|
+
"required": ["type"]
|
|
47
|
+
},
|
|
48
|
+
"skills": { "type": "array", "items": { "type": "string" }, "description": "Installed skill names to enable (runtime in a later release)." },
|
|
49
|
+
"tools": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"description": "Tools the agent may call: other workflows, or built-in tools. Each entry has exactly one of 'workflow' or 'builtin'.",
|
|
52
|
+
"items": {
|
|
53
|
+
"oneOf": [
|
|
54
|
+
{
|
|
55
|
+
"type": "object",
|
|
56
|
+
"required": ["workflow"],
|
|
57
|
+
"properties": {
|
|
58
|
+
"workflow": { "type": "string", "minLength": 1, "description": "Workflow name or workflowId to expose as a tool." },
|
|
59
|
+
"instructions": { "type": "string", "description": "When and how the agent should use this tool." },
|
|
60
|
+
"mode": { "type": "string", "enum": ["auto", "approval", "always"], "default": "auto", "description": "'auto' runs the tool as soon as the model calls it. 'approval' pauses a chat session until a person approves or declines the call, unless the chat is in Auto approval mode, where it runs and is recorded as auto-approved. 'always' pauses a chat session for a person in every approval mode. A task session (no person present) refuses 'approval' and 'always' calls outright and tells the agent it needs human approval." }
|
|
61
|
+
},
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": ["builtin"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"builtin": { "type": "string", "enum": ["data.query", "data.schema", "data.type"], "description": "A built-in, read-only data tool. data.query runs a GraphQL query in the agent's organization (tool data_query); data.schema lists query fields and types (data_schema); data.type describes one type (data_type)." },
|
|
69
|
+
"instructions": { "type": "string", "description": "When and how the agent should use this tool." },
|
|
70
|
+
"mode": { "type": "string", "enum": ["auto", "approval", "always"], "default": "auto", "description": "Same meaning as for workflow tools." }
|
|
71
|
+
},
|
|
72
|
+
"additionalProperties": false
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"mcp": {
|
|
78
|
+
"type": "array",
|
|
79
|
+
"items": { "type": "object", "required": ["fromConfig"], "properties": { "fromConfig": { "type": "string" } }, "additionalProperties": false },
|
|
80
|
+
"description": "Outbound MCP connections (runtime in a later release)."
|
|
81
|
+
},
|
|
82
|
+
"agents": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"items": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"agent": { "type": "string", "description": "Name of another Agent workflow in this organization." },
|
|
88
|
+
"url": { "type": "string", "format": "uri", "description": "Remote A2A agent card URL." },
|
|
89
|
+
"modes": { "type": "array", "items": { "type": "string", "enum": ["task", "chat"] } }
|
|
90
|
+
},
|
|
91
|
+
"oneOf": [{ "required": ["agent"] }, { "required": ["url"] }],
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
},
|
|
94
|
+
"description": "Allow list of agents this agent may invoke (runtime in a later release)."
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"additionalProperties": false
|
|
98
|
+
}
|
|
@@ -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
|
|
@@ -347,6 +347,7 @@ MUI Lab Timeline for chronological events or milestone-based tracking progress.
|
|
|
347
347
|
| `startDate` / `endDate` | `string` | current week | Initial date range for normal mode |
|
|
348
348
|
| `eventSources` | `EventSource[]` | — | GraphQL/static event sources |
|
|
349
349
|
| `eventTemplate` | `ComponentProps` | — | Custom event template for normal mode |
|
|
350
|
+
| `itemGroups` | `{key, accent?, background?, headerTemplate?}` | — | Mobile normal-mode grouping for consecutive events with the same non-empty key |
|
|
350
351
|
| `milestones` | `Milestone[]` | `[]` | Tracking milestones |
|
|
351
352
|
| `options.height` | `string \| number` | `400`/`auto` | Component height |
|
|
352
353
|
| `options.showTodayMarker` | `boolean` | `true` | Today marker in normal mode |
|
|
@@ -364,6 +365,8 @@ MUI Lab Timeline for chronological events or milestone-based tracking progress.
|
|
|
364
365
|
|
|
365
366
|
**Events:** `onEventClick` (data: `event`)
|
|
366
367
|
|
|
368
|
+
On mobile vertical timelines, `itemGroups` frames adjacent runs of two or more events whose templated `key` resolves equally. Runs are order-sensitive; isolated or empty keys remain ungrouped. `headerTemplate`, `accent`, and `background` resolve with `group = { key, items, size, startIndex, endIndex }` and `collection`. Grouped event templates also receive `groupPosition` and `groupOffset`. Tracking mode ignores grouping.
|
|
369
|
+
|
|
367
370
|
```yaml
|
|
368
371
|
component: timeline
|
|
369
372
|
name: orderTimeline
|