@hailer/mcp 1.3.14 → 1.3.23
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/.claude/skills/create-and-publish-app/SKILL.md +44 -56
- package/.claude/skills/hailer-app-builder/SKILL.md +414 -970
- package/.claude/skills/hailer-app-primitives/SKILL.md +742 -0
- package/.claude/skills/hailer-apps-pictures/SKILL.md +191 -87
- package/.claude/skills/hailer-design-patterns/SKILL.md +317 -0
- package/.claude/skills/hailer-design-system/SKILL.md +202 -149
- package/.claude/skills/hailer-workflow-archetypes/SKILL.md +301 -0
- package/.claude/skills/insight-join-patterns/SKILL.md +313 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +211 -0
- package/.claude/skills/sdk-activity-patterns/SKILL.md +257 -105
- package/.claude/skills/sdk-function-fields/SKILL.md +253 -492
- package/.claude/skills/sdk-insight-calculations/SKILL.md +364 -0
- package/.claude/skills/sdk-insight-queries/SKILL.md +192 -397
- package/.claude/skills/sdk-ws-config-skill/SKILL.md +265 -720
- package/.claude/skills/tool-response-verification/SKILL.md +143 -0
- package/CLAUDE.md +50 -19
- package/dist/bot/bot.d.ts.map +1 -1
- package/dist/bot/bot.js +26 -34
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/services/helper-prompt.d.ts +1 -1
- package/dist/bot/services/helper-prompt.d.ts.map +1 -1
- package/dist/bot/services/helper-prompt.js +62 -82
- package/dist/bot/services/helper-prompt.js.map +1 -1
- package/dist/bot/services/system-prompt.js +4 -4
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/mcp/hailer-rpc.d.ts +1 -0
- package/dist/mcp/hailer-rpc.d.ts.map +1 -1
- package/dist/mcp/hailer-rpc.js +4 -0
- package/dist/mcp/hailer-rpc.js.map +1 -1
- package/dist/mcp/tools/activity.d.ts.map +1 -1
- package/dist/mcp/tools/activity.js +8 -2
- package/dist/mcp/tools/activity.js.map +1 -1
- package/dist/mcp/tools/app-core.d.ts.map +1 -1
- package/dist/mcp/tools/app-core.js +6 -3
- package/dist/mcp/tools/app-core.js.map +1 -1
- package/dist/mcp/tools/app-marketplace.d.ts.map +1 -1
- package/dist/mcp/tools/app-marketplace.js +5 -1
- package/dist/mcp/tools/app-marketplace.js.map +1 -1
- package/dist/mcp/webhook-handler.d.ts.map +1 -1
- package/dist/mcp/webhook-handler.js +27 -0
- package/dist/mcp/webhook-handler.js.map +1 -1
- package/dist/public-chat/graduate.d.ts.map +1 -1
- package/dist/public-chat/graduate.js +153 -53
- package/dist/public-chat/graduate.js.map +1 -1
- package/dist/public-chat/rate-limit.js +1 -1
- package/dist/public-chat/rate-limit.js.map +1 -1
- package/dist/public-chat/studio-prewarm.js +2 -2
- package/dist/public-chat/studio-prewarm.js.map +1 -1
- package/dist/public-chat/system-prompt.d.ts.map +1 -1
- package/dist/public-chat/system-prompt.js +41 -25
- package/dist/public-chat/system-prompt.js.map +1 -1
- package/package.json +1 -1
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -398
- package/.opencode/package-lock.json +0 -117
|
@@ -1,55 +1,49 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sdk-ws-config-skill
|
|
3
|
-
description:
|
|
4
|
-
version: 1.8.0
|
|
5
|
-
triggers: Create workflow, add field, configure phase, field visibility, phase transitions, new project, init workspace, setup SDK, create workspace
|
|
3
|
+
description: Workspace configuration for Hailer — workflows, fields, phases, keys, push/pull sequencing, and common creation patterns. Load this skill before creating or editing any workflow structure (workflows, fields, phases, teams, groups). Also use when you see errors like "can't create activities", phase-transition errors, field push slow acks, or questions about field types and phase visibility.
|
|
6
4
|
---
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
<!-- augmentation-guard: shrink-ok -->
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
mkdir my-hailer-project && cd my-hailer-project
|
|
12
|
-
npm init -y
|
|
13
|
-
npm install @hailer/sdk typescript vitest --save-dev
|
|
14
|
-
```
|
|
8
|
+
# Workspace Configuration
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
HAILER_API_KEY=your_api_key
|
|
19
|
-
HAILER_WORKSPACE_ID=your_workspace_id
|
|
20
|
-
```
|
|
10
|
+
## Push/Pull Sequencing
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
```json
|
|
24
|
-
{ "workspaceId": "your_workspace_id", "apiUrl": "https://api.hailer.com" }
|
|
25
|
-
```
|
|
12
|
+
### Decision Tree — Read First
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
npm run pull # Download workspace config into workspace/
|
|
29
|
-
git init
|
|
30
|
-
echo ".env" >> .gitignore
|
|
31
|
-
echo "node_modules" >> .gitignore
|
|
32
|
-
```
|
|
14
|
+
**Rule 1: Use granular per-layer commands, not `npm run push`**
|
|
33
15
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
└── functions/*.ts
|
|
42
|
-
```
|
|
16
|
+
| What you want to do | Command |
|
|
17
|
+
|---------------------|---------|
|
|
18
|
+
| Create a new workflow | `npm run workflows-sync:force` |
|
|
19
|
+
| Update workflow properties (name, nameFunction) | `npm run workflows-push` |
|
|
20
|
+
| Create or update fields | `npm run fields-push` |
|
|
21
|
+
| Create or update phases | `npm run phases-push:force` |
|
|
22
|
+
| Push everything at once | `npm run push:force` (only when all items already exist server-side) |
|
|
43
23
|
|
|
44
|
-
|
|
45
|
-
- `npm run pull` — Downloads workspace config into `workspace/` (editable TypeScript files: fields, phases, workflows, enums, etc.)
|
|
46
|
-
- `npm run generate` — Generates `hailer-types.ts` (a single file with workflow/phase/field enums for use in app code). These are separate commands serving different purposes.
|
|
24
|
+
**Rule 2: Pull only after creating new items (workflows, phases, fields) that need server-assigned IDs. Skip pull after update-only pushes — pull overwrites local changes.**
|
|
47
25
|
|
|
48
|
-
|
|
26
|
+
| Push situation | Pull after? | Why |
|
|
27
|
+
|----------------|:-----------:|-----|
|
|
28
|
+
| `workflows-sync:force` (new workflow) | YES | Server assigns `_id` + folder |
|
|
29
|
+
| `phases-push:force` — creating new phases | YES | Server assigns phase hex IDs |
|
|
30
|
+
| `fields-push` — creating new fields | YES | Server assigns field hex IDs |
|
|
31
|
+
| `phases-push:force` — only updating existing phases | SKIP | No new IDs; pull overwrites local edits |
|
|
32
|
+
| `fields-push` — only updating existing fields | SKIP | No new IDs |
|
|
33
|
+
| `workflows-push` (nameFunction, permissions) | SKIP | Pull strips nameFunction |
|
|
49
34
|
|
|
50
|
-
|
|
35
|
+
**Rule 3: Two-pass push for new entity + references to it.** Pass 1 = push the new entity only. Pull to get server-assigned ID into `enums.ts`. Pass 2 = add references using the freshly-pulled enum symbol.
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
**Rule 3b: New-workflow creation is a two-pass dance — the pull between passes is a wipe trap.**
|
|
38
|
+
|
|
39
|
+
The sequence:
|
|
40
|
+
1. `workflows-sync:force` creates only the workflow shell (no fields, no real phases). It reads new entries from `workflows.ts`.
|
|
41
|
+
2. You MUST `npm run pull` to fetch the server-assigned workflow `_id` and rename the folder. **That pull overwrites local with server state** — it empties the renamed dir (server shell has no fields/phases yet) AND deletes any un-pushed field additions staged on other existing workflows.
|
|
42
|
+
3. After pull: re-stage field/phase defs into the renamed dir → `phases-push:force` → `fields-push`.
|
|
43
|
+
|
|
44
|
+
**Pull-wipe prevention guard:** before running `npm run pull` after `workflows-sync`, verify the CLI printed "✨ Created: N" (N ≥ 1). If N = 0, the create was a no-op — do NOT pull. Never stage un-pushed field-definition edits on existing workflows into the same batch as a new-workflow create-then-pull. New-workflow creation must be agent-owned end-to-end (the orchestrator cannot split edit → push → pull → re-stage across agents; the renamed dir path isn't known until after pull).
|
|
45
|
+
|
|
46
|
+
---
|
|
53
47
|
|
|
54
48
|
## File Structure
|
|
55
49
|
|
|
@@ -57,7 +51,6 @@ workspace/
|
|
|
57
51
|
workspace/
|
|
58
52
|
├── workflows.ts ← Workflow registry
|
|
59
53
|
├── enums.ts ← AUTO-GENERATED (never edit)
|
|
60
|
-
├── hailer.d.ts ← AUTO-GENERATED (never edit)
|
|
61
54
|
├── teams.ts, groups.ts ← Access control
|
|
62
55
|
├── insights.ts ← SQL-like reports
|
|
63
56
|
└── [Workflow]_[id]/
|
|
@@ -76,282 +69,125 @@ workspace/
|
|
|
76
69
|
|
|
77
70
|
```typescript
|
|
78
71
|
export const workflows: WorkflowEntry[] = [
|
|
79
|
-
// NEW
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
// EXISTING workflow - has _id and folder
|
|
85
|
-
{
|
|
86
|
-
_id: "682ac815fba468d857d498f7",
|
|
87
|
-
name: "Tasks",
|
|
88
|
-
enableUnlinkedMode: false,
|
|
89
|
-
folder: "tasks_682ac815fba468d857d498f7"
|
|
90
|
-
}
|
|
72
|
+
// NEW - omit _id and folder
|
|
73
|
+
{ name: "Tasks", enableUnlinkedMode: false },
|
|
74
|
+
// EXISTING - has _id and folder
|
|
75
|
+
{ _id: "682ac815fba468d857d498f7", name: "Tasks", enableUnlinkedMode: false, folder: "tasks_682ac815fba468d857d498f7" }
|
|
91
76
|
];
|
|
92
77
|
```
|
|
93
78
|
|
|
94
|
-
|
|
95
|
-
|----------|-------------|
|
|
96
|
-
| `name` | Display name |
|
|
97
|
-
| `enableUnlinkedMode` | `true` = standalone items, `false` = must link to other activities |
|
|
98
|
-
| `_id` | Server-generated (omit for new) |
|
|
99
|
-
| `folder` | Auto-generated on pull (omit for new) |
|
|
100
|
-
|
|
101
|
-
<critical-rules>
|
|
102
|
-
## CRITICAL: Workflows Require a Default Team
|
|
79
|
+
`WorkflowEntry` schema is `{_id, name, folder, enableUnlinkedMode}` only. Do not add `key` or `description` — the server rejects them at that level. `key` is set separately via `auto_set_keys` or push path.
|
|
103
80
|
|
|
104
|
-
**
|
|
105
|
-
|
|
106
|
-
When you create a new workflow, it has NO default team. Without a default team:
|
|
107
|
-
- `mcp__hailer__create_activity` fails
|
|
108
|
-
- Apps using `hailer.activity.create()` fail
|
|
109
|
-
- You get confusing permission/validation errors
|
|
110
|
-
|
|
111
|
-
**Solution:** Always set a default team immediately after creating a workflow:
|
|
112
|
-
|
|
113
|
-
1. Create the workflow
|
|
114
|
-
2. In Hailer UI: Workflow Settings → Default Team → Select any team
|
|
115
|
-
3. Or via SDK: Add `defaultTeam: TeamIds.your_team` to workflow config
|
|
116
|
-
|
|
117
|
-
**For development/testing:** Any team works. Just pick one so activity creation functions properly.
|
|
118
|
-
</critical-rules>
|
|
119
|
-
|
|
120
|
-
---
|
|
81
|
+
**CRITICAL: Workflows Require a Default Team.** Without a default team, `create_activity` fails with confusing errors. After creating a workflow: Hailer UI → Workflow Settings → Default Team.
|
|
121
82
|
|
|
122
83
|
### Workflow Creation Flow
|
|
123
84
|
|
|
124
85
|
**Order matters: Workflow → Phases → Fields → Phase Links → Workflow Settings**
|
|
125
86
|
|
|
126
|
-
All items get server-generated IDs. You must push each step and pull to get the IDs before referring to them.
|
|
127
|
-
|
|
128
87
|
```
|
|
129
88
|
1. npm run pull
|
|
130
|
-
2. Edit workflows.ts (add
|
|
131
|
-
3.
|
|
132
|
-
4.
|
|
89
|
+
2. Edit workflows.ts (add entry without _id/folder)
|
|
90
|
+
3. npm run workflows-sync:force
|
|
91
|
+
4. Verify "✨ Created: N" in output — if N=0, do NOT pull
|
|
92
|
+
5. npm run pull ← generates folder + enums with workflow ID
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
7. npm run
|
|
94
|
+
6. Edit phases.ts (add phases without _id, empty fields array)
|
|
95
|
+
Use plain name strings only — enums don't exist yet for new workflows.
|
|
96
|
+
7. npm run phases-push:force
|
|
97
|
+
8. npm run pull ← generates phase IDs in enums
|
|
137
98
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
99
|
+
9. Edit fields.ts (add fields without _id)
|
|
100
|
+
10. npm run fields-push
|
|
101
|
+
11. npm run pull ← generates field IDs in enums
|
|
141
102
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
12. Return ["npm run phases-push:force"]
|
|
103
|
+
12. Edit phases.ts: add field IDs to phase.fields arrays + possibleNextPhase
|
|
104
|
+
13. npm run phases-push:force
|
|
105
|
+
(NO pull — updates only, no new IDs)
|
|
146
106
|
|
|
147
|
-
|
|
148
|
-
|
|
107
|
+
14. (If needed) Edit main.ts: add nameFunction + nameFunctionVariables
|
|
108
|
+
15. npm run workflows-push
|
|
109
|
+
(NO pull — nameFunction is local, pull would overwrite it)
|
|
149
110
|
```
|
|
150
111
|
|
|
151
112
|
### Workflow Permissions (main.ts)
|
|
152
113
|
|
|
153
|
-
Control who can access the workflow and what they can do.
|
|
154
|
-
|
|
155
114
|
```typescript
|
|
156
|
-
// In main.ts
|
|
157
115
|
export const workflowConfig = {
|
|
158
116
|
_id: WorkflowIds.tasks_abc,
|
|
159
117
|
name: "Tasks",
|
|
160
|
-
|
|
161
|
-
// WHO CAN ACCESS THIS WORKFLOW
|
|
162
118
|
members: [
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
permissions: ["admin"] // Full control
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
id: WorkspaceTeams.sales_team_fc0, // Team ID
|
|
169
|
-
permissions: ["any"] // Standard access
|
|
170
|
-
}
|
|
119
|
+
{ id: HailerMembers.timo_ahonen_94d, permissions: ["admin"] },
|
|
120
|
+
{ id: WorkspaceTeams.sales_team_fc0, permissions: ["any"] }
|
|
171
121
|
],
|
|
172
|
-
|
|
173
|
-
// DEFAULT TEAM for new activities
|
|
174
|
-
preselectedTeam: {
|
|
175
|
-
account: "workspace_account_id",
|
|
176
|
-
team: WorkspaceTeams.default_team_abc
|
|
177
|
-
},
|
|
122
|
+
preselectedTeam: { account: "workspace_account_id", team: WorkspaceTeams.default_team_abc },
|
|
178
123
|
enablePreselectedTeam: true,
|
|
179
|
-
|
|
180
|
-
// GUEST ACCESS
|
|
181
|
-
allowGuests: false, // Allow external guests
|
|
182
|
-
enableGuestEditing: false, // Can guests edit?
|
|
183
|
-
|
|
184
|
-
// ... other config
|
|
185
124
|
};
|
|
186
125
|
```
|
|
187
126
|
|
|
188
|
-
|
|
189
|
-
| Level | Meaning |
|
|
190
|
-
|-------|---------|
|
|
191
|
-
| `"admin"` | Full control - can edit workflow settings, delete activities |
|
|
192
|
-
| `"any"` | Standard access - can view, create, edit based on phase settings |
|
|
127
|
+
`members[]` (and any `HailerMemberId`-typed field) requires `HailerMembers.*` — the prefixed enum that has both Users + Teams sections. `WorkspaceTeams.*` is plain hex and fails TS2322.
|
|
193
128
|
|
|
194
|
-
|
|
195
|
-
- Users: `HailerMembers.user_name_id`
|
|
196
|
-
- Teams: `WorkspaceTeams.team_name_id`
|
|
129
|
+
---
|
|
197
130
|
|
|
198
|
-
|
|
131
|
+
## Workflow Settings (workflowConfig toggles)
|
|
199
132
|
|
|
200
|
-
|
|
133
|
+
`primaryDateField` / `primaryNumericField` are **phase-level** — set them in `phases.ts`, not here.
|
|
201
134
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
"user_id_123" // Or specific user
|
|
220
|
-
],
|
|
221
|
-
|
|
222
|
-
// PHASE TRANSITIONS - where activities can move TO from here
|
|
223
|
-
possibleNextPhase: [
|
|
224
|
-
PhaseIds.review_ghi,
|
|
225
|
-
PhaseIds.done_jkl
|
|
226
|
-
],
|
|
227
|
-
|
|
228
|
-
// TRANSITION SETTINGS (optional)
|
|
229
|
-
possibleNextPhaseSettings: {
|
|
230
|
-
[PhaseIds.done_jkl]: {
|
|
231
|
-
requireComment: true // Must add comment when moving to Done
|
|
232
|
-
}
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
// WEBHOOKS - trigger external systems
|
|
236
|
-
webhooksEnabled: true,
|
|
237
|
-
webhookUrl: "https://hooks.example.com/phase-entered"
|
|
238
|
-
}
|
|
239
|
-
];
|
|
240
|
-
```
|
|
135
|
+
| Toggle | Type | Notes |
|
|
136
|
+
|--------|------|-------|
|
|
137
|
+
| `name` | `string` | Change in `main.ts` AND `workflows.ts` before pushing. |
|
|
138
|
+
| `key` | `string` | Human-readable ID (e.g. `"workOrders"`). See Keys section. |
|
|
139
|
+
| `defaultView` | `"table"\|"kanban"\|"wall"\|"calendar"` | View mode on first open. |
|
|
140
|
+
| `enableUnlinkedMode` | `boolean` | `true` = standalone dataset list; `false` = requires linking (kanban). Naming is inverted from intuition. |
|
|
141
|
+
| `nameEditable` | `boolean` | `false` when `nameFunction` drives the title. |
|
|
142
|
+
| `fieldsOrder` | `string[]` | Workflow-level field column order. |
|
|
143
|
+
| `phasesOrder` | `string[]` | Phase display order. |
|
|
144
|
+
| `allowGuests` | `boolean` | Grants guest/external users view access via shared link. |
|
|
145
|
+
| `enableGuestEditing` | `boolean` | Lets guests edit. Requires `allowGuests: true`. |
|
|
146
|
+
| `enableAttachments` | `boolean` | File attachments panel on activities. |
|
|
147
|
+
| `enableMapLocation` | `boolean` | Map/location picker on activities. |
|
|
148
|
+
| `enableMessenger` | `boolean` | Private messenger thread per activity. |
|
|
149
|
+
| `inviteActivityCreator` | `boolean` | Automatically adds the creator as a discussion follower. Use this instead of building creator-invite logic in an app. |
|
|
150
|
+
| `enableUniqueName` | `boolean` | Server rejects create/update if the activity name already exists in the workflow. |
|
|
151
|
+
| `discussionPermissions` | `DiscussionPermission[]` | Controls what members can do in the discussion. |
|
|
241
152
|
|
|
242
|
-
**
|
|
243
|
-
| Setting | Purpose |
|
|
244
|
-
|---------|---------|
|
|
245
|
-
| `fields[]` | Which fields are visible/editable in this phase |
|
|
246
|
-
| `followers[]` | Users/teams auto-notified for activities in this phase |
|
|
247
|
-
| `possibleNextPhase[]` | Allowed phase transitions (empty = cannot move out) |
|
|
248
|
-
| `isInitial` | New activities start here |
|
|
249
|
-
| `isEndpoint` | Activities here are "complete" |
|
|
153
|
+
**Activity tagging is not configurable via `workflows-push`.** `normalizeWorkflow` strips `enableActivityTagging` from the payload. Configure activity tagging via `process.set_info` / the Hailer UI. Same class as workflow `translations`.
|
|
250
154
|
|
|
251
|
-
###
|
|
252
|
-
|
|
253
|
-
Teams and groups are defined at workspace root level (not per-workflow).
|
|
254
|
-
|
|
255
|
-
**Location:** `workspace/teams.ts` and `workspace/groups.ts`
|
|
155
|
+
### Name Function Pattern (main.ts)
|
|
256
156
|
|
|
257
157
|
```typescript
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
{
|
|
263
|
-
|
|
264
|
-
name: "Sales Team",
|
|
265
|
-
description: "Sales department members",
|
|
266
|
-
members: [
|
|
267
|
-
WorkspaceMembers.john_doe_123,
|
|
268
|
-
WorkspaceMembers.jane_smith_456
|
|
269
|
-
],
|
|
270
|
-
public: false, // Only members can see team activities
|
|
271
|
-
defaultView: {
|
|
272
|
-
type: "app", // "app" | "workflow" | "phase"
|
|
273
|
-
value: "app_id_here" // ID of default view
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
];
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
**Team properties:**
|
|
280
|
-
| Property | Purpose |
|
|
281
|
-
|----------|---------|
|
|
282
|
-
| `_id` | Team ID (from enums after first push) |
|
|
283
|
-
| `name` | Display name |
|
|
284
|
-
| `description` | Team description |
|
|
285
|
-
| `members[]` | Array of user IDs (WorkspaceMembers enum) |
|
|
286
|
-
| `public` | If true, visible to all workspace users |
|
|
287
|
-
| `defaultView` | What opens when team is selected |
|
|
288
|
-
|
|
289
|
-
**Groups** (less common) are collections of teams:
|
|
290
|
-
```typescript
|
|
291
|
-
// workspace/groups.ts
|
|
292
|
-
export const groups: HailerGroupUpdatePayload[] = [
|
|
293
|
-
{
|
|
294
|
-
_id: GroupIds.all_managers_abc,
|
|
295
|
-
name: "All Managers",
|
|
296
|
-
teams: [TeamIds.sales_managers, TeamIds.tech_managers],
|
|
297
|
-
users: [WorkspaceMembers.ceo_123] // Direct user members
|
|
298
|
-
}
|
|
299
|
-
];
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
**Push commands:**
|
|
303
|
-
```bash
|
|
304
|
-
npm run teams-push:force # Push team changes
|
|
305
|
-
npm run groups-push:force # Push group changes
|
|
158
|
+
nameEditable: false,
|
|
159
|
+
nameFunction: "const meta = dep.activityMeta; if (!meta) return 'PREFIX-' + Date.now(); const created = Number(meta.created) || Date.now(); const year = new Date(created).getUTCFullYear(); const seq = Number(meta.sequence) || 0; let seqStr = ''; if (seq > 0) { seqStr = String(seq); while (seqStr.length < 4) seqStr = '0' + seqStr; } else { const id = meta._id || '0000'; seqStr = id.slice(-4); } return 'PREFIX-' + year + '-' + seqStr;",
|
|
160
|
+
nameFunctionEnabled: true,
|
|
161
|
+
nameFunctionVariables: {
|
|
162
|
+
activityMeta: { data: ["data"], type: "=" } // magic key is "data" (NOT "meta" — "meta" silently returns null). data is plain STRINGS, not objects. type: "=" is required.
|
|
163
|
+
},
|
|
306
164
|
```
|
|
307
165
|
|
|
308
|
-
|
|
309
|
-
- Workflow `members[]` - who can access workflow
|
|
310
|
-
- Workflow `preselectedTeam` - default team for new activities
|
|
311
|
-
- Phase `followers[]` - auto-notify teams
|
|
312
|
-
- App permissions - grant team access to apps
|
|
166
|
+
`nameFunction` push does NOT retroactively recompute existing activity names — existing activities keep their stored titles until next individual save.
|
|
313
167
|
|
|
314
168
|
---
|
|
315
169
|
|
|
316
|
-
|
|
317
|
-
### CRITICAL: Phase Links (possibleNextPhase)
|
|
318
|
-
|
|
319
|
-
**Problem:** Without phase links, users cannot move activities between phases.
|
|
320
|
-
|
|
321
|
-
Every phase needs `possibleNextPhase` array defining allowed transitions:
|
|
170
|
+
## Teams & Groups
|
|
322
171
|
|
|
323
172
|
```typescript
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
name: "Lead",
|
|
327
|
-
isInitial: true,
|
|
328
|
-
possibleNextPhase: [
|
|
329
|
-
Tasks_PhaseIds.qualified_def,
|
|
330
|
-
Tasks_PhaseIds.lost_ghi
|
|
331
|
-
]
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**Without this:** Activities are stuck in their initial phase forever.
|
|
336
|
-
</critical-rules>
|
|
337
|
-
|
|
338
|
-
<critical-rules>
|
|
339
|
-
### CRITICAL: Primary Date and Number Fields
|
|
340
|
-
|
|
341
|
-
**Set in main.ts** to enable calendar view and number aggregation:
|
|
173
|
+
// workspace/teams.ts — push: teams-push:force
|
|
174
|
+
{ _id: WorkspaceTeams.sales_abc, name: "Sales", members: [WorkspaceMembers.john_123], public: false }
|
|
342
175
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
export const workflowConfig = {
|
|
346
|
-
// ... other config
|
|
347
|
-
primaryDateField: Tasks_FieldIds.due_date_abc, // For calendar view
|
|
348
|
-
primaryNumberField: Tasks_FieldIds.amount_def, // For aggregations
|
|
349
|
-
};
|
|
176
|
+
// workspace/groups.ts — push: groups-push:force
|
|
177
|
+
{ _id: GroupIds.managers_abc, name: "Managers", teams: [TeamIds.sales], users: [WorkspaceMembers.ceo_123] }
|
|
350
178
|
```
|
|
351
179
|
|
|
352
|
-
**
|
|
353
|
-
|
|
354
|
-
|
|
180
|
+
**Teams — single-pass creation:**
|
|
181
|
+
1. Add object **without `_id`** to `workspace/teams.ts`
|
|
182
|
+
2. `npm run teams-push` — push itself creates the team
|
|
183
|
+
3. `npm run pull` — writes server-assigned `_id` into `enums.ts`
|
|
184
|
+
|
|
185
|
+
**Groups — two-pass creation (group `teams` can't be set in the same push that creates the group):**
|
|
186
|
+
1. Add group without `_id` and with empty `teams: []` to `workspace/groups.ts`
|
|
187
|
+
2. `npm run groups-push` — creates the group
|
|
188
|
+
3. `npm run pull` — gets the generated `_id`
|
|
189
|
+
4. Add desired `teams` array using enum symbols now in `enums.ts`
|
|
190
|
+
5. `npm run groups-push` again — sets the team membership
|
|
355
191
|
|
|
356
192
|
---
|
|
357
193
|
|
|
@@ -360,153 +196,107 @@ export const workflowConfig = {
|
|
|
360
196
|
### Basic Field Structure
|
|
361
197
|
|
|
362
198
|
```typescript
|
|
363
|
-
// In fields.ts
|
|
364
|
-
import { Tasks_FieldIds, WorkflowIds } from "../enums";
|
|
365
|
-
|
|
366
199
|
export const fields: HailerFieldGeneric[] = [
|
|
367
200
|
// NEW field - omit _id
|
|
368
|
-
{
|
|
369
|
-
label: "Due Date",
|
|
370
|
-
type: "date",
|
|
371
|
-
required: false
|
|
372
|
-
},
|
|
201
|
+
{ label: "Due Date", type: "date", required: false },
|
|
373
202
|
// EXISTING field - use enum for _id
|
|
374
|
-
{
|
|
375
|
-
_id: Tasks_FieldIds.due_date_abc,
|
|
376
|
-
label: "Due Date",
|
|
377
|
-
type: "date",
|
|
378
|
-
required: false
|
|
379
|
-
}
|
|
203
|
+
{ _id: Tasks_FieldIds.due_date_abc, label: "Due Date", type: "date", required: false }
|
|
380
204
|
];
|
|
381
205
|
```
|
|
382
206
|
|
|
383
|
-
|
|
207
|
+
**CRITICAL: Never invent an `_id` for a new item.**
|
|
384
208
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
| `label` | string | Display name shown in UI |
|
|
389
|
-
| `type` | string | Field type (see below) |
|
|
390
|
-
| `key` | string | URL-safe identifier (see Key Naming below) |
|
|
391
|
-
| `description` | string | Help text shown below field in forms |
|
|
392
|
-
| `required` | boolean | Is field required |
|
|
393
|
-
| `defaultTo` | boolean | Use default value |
|
|
394
|
-
| `defaultValue` | string | Default value |
|
|
395
|
-
| `inviteToDiscussionOnChange` | boolean | Notify discussion on change |
|
|
396
|
-
|
|
397
|
-
### Field Key Naming
|
|
398
|
-
|
|
399
|
-
The `key` property is an optional URL-safe identifier for the field. If omitted, Hailer auto-generates one from the label.
|
|
400
|
-
|
|
401
|
-
**Conventions:**
|
|
402
|
-
- Use `snake_case` (lowercase with underscores)
|
|
403
|
-
- Keep it short but descriptive
|
|
404
|
-
- Use consistent prefixes for related fields
|
|
405
|
-
- Avoid reserved words
|
|
406
|
-
|
|
407
|
-
**Examples:**
|
|
408
|
-
```typescript
|
|
409
|
-
// Good keys
|
|
410
|
-
{ label: "Customer Name", key: "customer_name" }
|
|
411
|
-
{ label: "Invoice #", key: "invoice_number" }
|
|
412
|
-
{ label: "Due Date", key: "due_date" }
|
|
413
|
-
{ label: "Total (€)", key: "total_eur" }
|
|
414
|
-
|
|
415
|
-
// Prefixed for grouping
|
|
416
|
-
{ label: "Billing Address", key: "billing_address" }
|
|
417
|
-
{ label: "Billing City", key: "billing_city" }
|
|
418
|
-
{ label: "Shipping Address", key: "shipping_address" }
|
|
419
|
-
{ label: "Shipping City", key: "shipping_city" }
|
|
420
|
-
```
|
|
209
|
+
Two valid shapes for `_id`:
|
|
210
|
+
1. **New item — the `_id` property is absent.** Not `null`, not `""`, not `"new"`. The key doesn't appear at all.
|
|
211
|
+
2. **Existing item — `_id` references an enum symbol that already exists in `enums.ts`**.
|
|
421
212
|
|
|
422
|
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
-
|
|
213
|
+
Known failure modes:
|
|
214
|
+
- `_id: "new_field_1"` — server rejects or silently miswrites
|
|
215
|
+
- `_id: "000000000000000000000001"` — fake hex, same failure
|
|
216
|
+
- `_id: Tasks_FieldIds.not_yet_in_enums` — TypeScript catches this only with `npx tsc --noEmit`
|
|
217
|
+
- `_id: Tasks_FieldIds.some_existing_field` copied from neighbor — silently overwrites that field
|
|
426
218
|
|
|
427
|
-
###
|
|
219
|
+
### fields-push slow ack — not a hang
|
|
428
220
|
|
|
429
|
-
**
|
|
221
|
+
`npm run fields-push` can appear to hang for minutes. This is **slow server-side backfill**, not a deadlock. Fields in `json_snapshot`'s dependency graph trigger synchronous snapshot recomputation across every existing activity.
|
|
430
222
|
|
|
431
|
-
**
|
|
223
|
+
**Fire-and-forget recipe:**
|
|
432
224
|
|
|
433
225
|
```typescript
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
{
|
|
445
|
-
type: "linkedfrom",
|
|
446
|
-
label: "Orders",
|
|
447
|
-
data: [WorkflowIds.orders_def],
|
|
448
|
-
modifier: {
|
|
449
|
-
quickAdd: {
|
|
450
|
-
fieldIds: [], // Fields shown in quick-add form
|
|
451
|
-
targetFieldId: Orders_FieldIds.customer_link
|
|
452
|
-
}
|
|
453
|
-
}
|
|
226
|
+
const TIMEOUT_MS = 30_000;
|
|
227
|
+
for (const field of fieldsToPush) {
|
|
228
|
+
await Promise.race([
|
|
229
|
+
client.updateField(workflowId, field._id, field),
|
|
230
|
+
new Promise((_, rej) => setTimeout(() => rej(new Error('ack pending')), TIMEOUT_MS))
|
|
231
|
+
]).catch(err => {
|
|
232
|
+
if (err.message === 'ack pending') {
|
|
233
|
+
console.log(`Field ${field.label}: ack pending — server is still processing`);
|
|
234
|
+
} else { throw err; }
|
|
235
|
+
});
|
|
454
236
|
}
|
|
237
|
+
// After all calls fired, verify: npm run pull → count fields in enums.ts
|
|
455
238
|
```
|
|
456
239
|
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
data: [WorkflowIds.customers_abc] // ✅
|
|
460
|
-
data: [{ workflowId: "697b9b54..." }] // ❌ causes API error
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
**`textpredefinedoptions`** — `data` is a string array (not `options`, not objects):
|
|
464
|
-
```typescript
|
|
465
|
-
{ type: "textpredefinedoptions", data: ["High", "Medium", "Low"] } // ✅
|
|
466
|
-
{ type: "dropdown", options: [...] } // ❌
|
|
467
|
-
```
|
|
240
|
+
### Common Field Properties
|
|
468
241
|
|
|
469
|
-
|
|
242
|
+
| Property | Description |
|
|
243
|
+
|----------|-------------|
|
|
244
|
+
| `_id` | Omit for new, use enum for existing |
|
|
245
|
+
| `label` | Display name |
|
|
246
|
+
| `type` | Field type (see below) |
|
|
247
|
+
| `key` | URL-safe identifier (snake_case). Specify for integration/insight fields. |
|
|
248
|
+
| `description` | Help text shown below field in forms |
|
|
249
|
+
| `required` | Mark essential fields required. Don't mark everything required — fields not yet known at creation time add friction. |
|
|
250
|
+
| `defaultValue` | Default value |
|
|
251
|
+
|
|
252
|
+
### Field Types
|
|
253
|
+
|
|
254
|
+
| Type | Use For | Config Notes |
|
|
255
|
+
|------|---------|-------------|
|
|
256
|
+
| `text` | Short text | |
|
|
257
|
+
| `textarea` | Long text | |
|
|
258
|
+
| `textunit` / `numericunit` | Text/number with unit | Set `unit` property. Prefer `numericunit` over `numeric` for most numeric fields — adding a unit (€, kg, h) makes the field self-documenting. |
|
|
259
|
+
| `numeric` | Numbers | NOT `number`! Unit-less values only. |
|
|
260
|
+
| `date` / `datetime` | Single date/datetime | |
|
|
261
|
+
| `daterange` / `datetimerange` | Date spans | |
|
|
262
|
+
| `time` / `timerange` | Time only | |
|
|
263
|
+
| `textpredefinedoptions` | Dropdown | `data: ["High", "Medium", "Low"]` (string array, NOT `options`, NOT `[{label,value}]`) |
|
|
264
|
+
| `users` | Single-user picker | |
|
|
265
|
+
| `teams` | Single-team picker | Token is `teams` (not `team`) |
|
|
266
|
+
| `activitylink` | Link to activity | `data: [WorkflowIds.target]` (plain string array!) |
|
|
267
|
+
| `linkedfrom` | Backlink (read-only) | Auto-populated |
|
|
268
|
+
| `country` | Country picker | ISO code |
|
|
269
|
+
| `subheader` | Section divider | UI only, use `collapsedByDefault: true` for grouping |
|
|
270
|
+
| `numeric` + `modifier.checkbox` | Checkbox | |
|
|
271
|
+
| `text` + `modifier.file` | File upload | |
|
|
272
|
+
|
|
273
|
+
**No multi-select field types exist in Hailer — period.**
|
|
470
274
|
|
|
471
|
-
|
|
275
|
+
```typescript
|
|
276
|
+
// Dropdown
|
|
277
|
+
{ type: "textpredefinedoptions", label: "Priority", data: ["High", "Medium", "Low"] }
|
|
472
278
|
|
|
473
|
-
|
|
279
|
+
// Activity link
|
|
280
|
+
{ type: "activitylink", label: "Customer", data: [WorkflowIds.customers, WorkflowIds.projects] }
|
|
281
|
+
// data MUST be plain string array — data: [{workflowId: "..."}] causes API error
|
|
474
282
|
|
|
475
|
-
|
|
283
|
+
// Checkbox / File
|
|
284
|
+
{ label: "Active", type: "numeric", modifier: { checkbox: true, file: false } }
|
|
285
|
+
{ label: "Attachments", type: "text", modifier: { checkbox: false, file: true } }
|
|
476
286
|
|
|
477
|
-
|
|
478
|
-
{
|
|
479
|
-
|
|
480
|
-
label: "Total",
|
|
481
|
-
type: "numericunit", // Output type
|
|
482
|
-
unit: "€",
|
|
483
|
-
functionEnabled: true,
|
|
484
|
-
function: "@function:total_abc", // Function reference
|
|
485
|
-
functionVariables: { // Input dependencies
|
|
486
|
-
quantity: {
|
|
487
|
-
type: "=",
|
|
488
|
-
data: [Tasks_FieldIds.quantity_def]
|
|
489
|
-
},
|
|
490
|
-
price: {
|
|
491
|
-
type: "=",
|
|
492
|
-
data: [Tasks_FieldIds.unit_price_ghi]
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
}
|
|
287
|
+
// numericunit — unit is a TOP-LEVEL property, NOT inside data[]
|
|
288
|
+
{ label: "Amount", type: "numericunit", unit: "€", data: [] }
|
|
289
|
+
// WRONG: { type: "numericunit", data: [{ unit: "€" }] } — API rejects unit inside data array
|
|
496
290
|
```
|
|
497
291
|
|
|
498
|
-
|
|
292
|
+
### Field Limitations
|
|
499
293
|
|
|
500
|
-
|
|
294
|
+
- Cannot change field type after creation — delete and create new; new `_id` cascades into enums, phases, function field variables.
|
|
295
|
+
- `linkedfrom` adding on a NEW workflow requires 2-pass push: push source workflow first → pull → push `linkedfrom` reference.
|
|
501
296
|
|
|
502
|
-
|
|
297
|
+
### Field Visibility
|
|
503
298
|
|
|
504
|
-
|
|
505
|
-
|------------|---------|
|
|
506
|
-
| **Cannot change type** | Once created, field type cannot be changed via API. Must delete and recreate (loses data) or change in Hailer UI. |
|
|
507
|
-
| **linkedfrom is read-only** | Cannot set values - auto-populated from links |
|
|
508
|
-
| **richtext size** | Large HTML can slow down activity loading |
|
|
509
|
-
| **attachment storage** | Files stored in Hailer, count toward workspace storage |
|
|
299
|
+
When `fields-push` creates a new field, the server appends it to every phase's `fields[]` array automatically — field is visible everywhere immediately. Only push `phases.ts` if you want non-default placement (reordering, removing from specific phases).
|
|
510
300
|
|
|
511
301
|
---
|
|
512
302
|
|
|
@@ -515,339 +305,131 @@ data: [{ workflowId: "697b9b54..." }] // ❌ causes API error
|
|
|
515
305
|
### Basic Phase Structure
|
|
516
306
|
|
|
517
307
|
```typescript
|
|
518
|
-
// In phases.ts
|
|
519
|
-
import { Tasks_FieldIds, Tasks_PhaseIds } from "../enums";
|
|
520
|
-
|
|
521
308
|
export const phases: HailerPhaseUpdatePayload[] = [
|
|
522
309
|
// NEW phase - omit _id
|
|
523
|
-
{
|
|
524
|
-
|
|
525
|
-
color: "#57636E",
|
|
526
|
-
fields: [],
|
|
527
|
-
possibleNextPhase: []
|
|
528
|
-
},
|
|
529
|
-
// EXISTING phase - use enum for _id
|
|
310
|
+
{ name: "Todo", color: "#57636E", fields: [], possibleNextPhase: [] },
|
|
311
|
+
// EXISTING phase - use enum
|
|
530
312
|
{
|
|
531
313
|
_id: Tasks_PhaseIds.todo_abc,
|
|
532
314
|
name: "Todo",
|
|
533
315
|
color: "#57636E",
|
|
534
|
-
fields: [
|
|
535
|
-
|
|
536
|
-
Tasks_FieldIds.assignee_ghi
|
|
537
|
-
],
|
|
538
|
-
possibleNextPhase: [
|
|
539
|
-
Tasks_PhaseIds.in_progress_jkl,
|
|
540
|
-
Tasks_PhaseIds.done_mno
|
|
541
|
-
]
|
|
316
|
+
fields: [Tasks_FieldIds.title_def, Tasks_FieldIds.assignee_ghi],
|
|
317
|
+
possibleNextPhase: [Tasks_PhaseIds.in_progress_jkl, Tasks_PhaseIds.done_mno]
|
|
542
318
|
}
|
|
543
319
|
];
|
|
544
320
|
```
|
|
545
321
|
|
|
546
|
-
### Phase Properties
|
|
322
|
+
### Phase Properties
|
|
547
323
|
|
|
548
324
|
| Property | Type | Description |
|
|
549
325
|
|----------|------|-------------|
|
|
550
326
|
| `_id` | string | Phase ID (omit for new) |
|
|
551
327
|
| `name` | string | Display name |
|
|
552
|
-
| `
|
|
553
|
-
| `
|
|
554
|
-
|
|
555
|
-
|
|
328
|
+
| `key` | string | URL-safe identifier (snake_case) |
|
|
329
|
+
| `color` | string | Hex color — set on every phase |
|
|
330
|
+
| `fields[]` | FieldId[] | Fields visible in this phase |
|
|
331
|
+
| `isInitial` | boolean | New activities start here |
|
|
332
|
+
| `isEndpoint` | boolean | Activities here are "complete" |
|
|
333
|
+
| `possibleNextPhase[]` | PhaseId[] | Allowed transitions — without this, activities are stuck forever |
|
|
334
|
+
| `followers[]` | string[] | Users/teams auto-notified |
|
|
556
335
|
|
|
557
|
-
**
|
|
336
|
+
**CRITICAL: Phase Links (possibleNextPhase) — without them, activities are stuck forever.**
|
|
558
337
|
|
|
559
|
-
|
|
560
|
-
- **Category phases:** Products workflow might use phases as categories (Doors, Windows, etc.)
|
|
561
|
-
- **Status phases:** Support tickets with Open, Waiting, Resolved
|
|
338
|
+
**CRITICAL: Primary Date and Number Fields are phase properties, not workflow properties.**
|
|
562
339
|
|
|
563
|
-
|
|
340
|
+
| Property | Accepted values | Use a custom field ID? |
|
|
341
|
+
|---|---|---|
|
|
342
|
+
| `primaryDateField` | `"created"` \| `"updated"` \| `"completedOn"` \| `undefined` | No — TS rejects custom field IDs |
|
|
343
|
+
| `primaryNumericField` | `string` (field ID) \| `undefined` | Yes — pass the field ID enum |
|
|
564
344
|
|
|
565
|
-
|
|
345
|
+
Setting them via `process.set_info` (workflow-level) fails with error code 191.
|
|
566
346
|
|
|
567
|
-
**
|
|
347
|
+
**Dropping a phase is destructive — all activities in that phase are cascade-deleted.** Verify count before removing (`list_activities` with `countOnly:true`). Move activities to another phase first. No undo.
|
|
568
348
|
|
|
569
|
-
|
|
570
|
-
- Visible in ALL phases (add to every phase's `fields` array)
|
|
571
|
-
- Visible in SOME phases (add only to specific phases)
|
|
572
|
-
- Hidden in certain phases (omit from those phases)
|
|
349
|
+
**`possibleNextPhase` enforces phase walks — intermediate phases cannot be skipped.** If a phase only allows certain next phases, jumping to a non-adjacent phase fails silently with `[object Object]` error. Walk the chain in hops.
|
|
573
350
|
|
|
574
|
-
|
|
575
|
-
// Phase 1: Only basic fields visible
|
|
576
|
-
{
|
|
577
|
-
_id: Tasks_PhaseIds.draft_abc,
|
|
578
|
-
name: "Draft",
|
|
579
|
-
fields: [
|
|
580
|
-
Tasks_FieldIds.title_def,
|
|
581
|
-
Tasks_FieldIds.description_ghi
|
|
582
|
-
// assignee and due_date NOT visible in draft
|
|
583
|
-
]
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Phase 2: More fields become visible
|
|
587
|
-
{
|
|
588
|
-
_id: Tasks_PhaseIds.active_jkl,
|
|
589
|
-
name: "Active",
|
|
590
|
-
fields: [
|
|
591
|
-
Tasks_FieldIds.title_def,
|
|
592
|
-
Tasks_FieldIds.description_ghi,
|
|
593
|
-
Tasks_FieldIds.assignee_mno, // NOW visible
|
|
594
|
-
Tasks_FieldIds.due_date_pqr // NOW visible
|
|
595
|
-
]
|
|
596
|
-
}
|
|
597
|
-
```
|
|
351
|
+
**Disabling a phase webhook:** Set `webhooksEnabled: false` and keep `webhookUrl` as any non-empty string. Setting `webhookUrl: ""` causes error 191. `webhookAdded`/`webhookUpdated` flags are immutable from SDK side — flip the webhook toggle in Hailer UI instead.
|
|
598
352
|
|
|
599
|
-
|
|
600
|
-
1. Push the field first (`fields-push:force`)
|
|
601
|
-
2. Pull to get the field ID in enums
|
|
602
|
-
3. Decide which phases should show this field
|
|
603
|
-
4. Add the field ID to those phases' `fields` arrays
|
|
604
|
-
5. Push phases (`phases-push:force`)
|
|
353
|
+
### Phase Configuration Defaults
|
|
605
354
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
{
|
|
609
|
-
isInitial: true, // Starting phase (activities created here)
|
|
610
|
-
isEndpoint: true, // Final phase (workflow complete)
|
|
611
|
-
possibleNextPhase: [ // Allowed transitions
|
|
612
|
-
Tasks_PhaseIds.review_abc,
|
|
613
|
-
Tasks_PhaseIds.done_def
|
|
614
|
-
],
|
|
615
|
-
possibleNextPhaseSettings: {
|
|
616
|
-
// Phase transition settings (optional)
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
#### Calendar Integration
|
|
622
|
-
```typescript
|
|
623
|
-
{
|
|
624
|
-
primaryDateField: Tasks_FieldIds.due_date_abc // Show on calendar
|
|
625
|
-
// OR
|
|
626
|
-
primaryDateField: "updated" // Use last updated timestamp
|
|
627
|
-
}
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
#### Auto-Follow
|
|
631
|
-
```typescript
|
|
632
|
-
{
|
|
633
|
-
followers: [
|
|
634
|
-
"user_id_abc", // User ID
|
|
635
|
-
"team_id_def" // Team ID
|
|
636
|
-
]
|
|
637
|
-
// These users/teams auto-follow activities in this phase
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
#### Webhooks
|
|
642
|
-
```typescript
|
|
643
|
-
{
|
|
644
|
-
webhooksEnabled: true,
|
|
645
|
-
webhookUrl: "https://hooks.zapier.com/..."
|
|
646
|
-
// Triggers when activity enters this phase
|
|
647
|
-
}
|
|
648
|
-
```
|
|
649
|
-
|
|
650
|
-
#### Announcements
|
|
651
|
-
```typescript
|
|
652
|
-
{
|
|
653
|
-
announcementFields: [
|
|
654
|
-
Tasks_FieldIds.title_abc,
|
|
655
|
-
Tasks_FieldIds.due_date_def
|
|
656
|
-
],
|
|
657
|
-
announcementFieldsOrder: [
|
|
658
|
-
Tasks_FieldIds.title_abc,
|
|
659
|
-
Tasks_FieldIds.due_date_def
|
|
660
|
-
],
|
|
661
|
-
announcementRecipients: [
|
|
662
|
-
"team_id_abc"
|
|
663
|
-
]
|
|
664
|
-
// Send notification when activity enters phase
|
|
665
|
-
}
|
|
666
|
-
```
|
|
355
|
+
1. **Set a `color` on every phase.** Uncoloured phases are indistinguishable on the board.
|
|
356
|
+
2. **Set primaryDate/Numeric only when meaningful.** For reference/Register-archetype datasets, leaving both unset is correct.
|
|
667
357
|
|
|
668
358
|
---
|
|
669
359
|
|
|
670
|
-
##
|
|
671
|
-
|
|
672
|
-
| Command | What It Does |
|
|
673
|
-
|---------|--------------|
|
|
674
|
-
| `npm run workflows-sync:force` | Create/update/delete workflows |
|
|
675
|
-
| `npm run fields-push:force` | Create/update/delete fields |
|
|
676
|
-
| `npm run phases-push:force` | Create/update/delete phases |
|
|
677
|
-
| `npm run push:force` | Push all changes |
|
|
678
|
-
|
|
679
|
-
**:force variants** may delete resources not in local files.
|
|
680
|
-
**Non-force variants** only update, never delete.
|
|
360
|
+
## Human-Readable Keys (workflow / phase / field)
|
|
681
361
|
|
|
682
|
-
|
|
362
|
+
**When keys matter:**
|
|
363
|
+
- Hailer apps (`@hailer/app-sdk`) — read fields by key via scaffolded `field-resolver.ts`
|
|
364
|
+
- MCP tools — accept keys anywhere they accept IDs
|
|
365
|
+
- Direct REST integrations — human-readable URLs/bodies
|
|
366
|
+
- Marketplace-distributable apps — must use keys (installed workflow IDs differ per tenant)
|
|
683
367
|
|
|
684
|
-
|
|
685
|
-
|---------|---------|---------|---------|
|
|
686
|
-
| `workflows push` | No | Yes | No |
|
|
687
|
-
| `workflows sync` | Yes | No | Yes |
|
|
688
|
-
| `fields push` | Yes | Yes | Yes (with confirmation) |
|
|
689
|
-
| `templates push` | No | Yes | No |
|
|
690
|
-
| `templates sync` | Yes | No | Yes |
|
|
368
|
+
**When keys don't matter:** Insights (use enums), Function fields (enum substitution at compile time).
|
|
691
369
|
|
|
692
|
-
|
|
370
|
+
### Where keys work — production-verified
|
|
693
371
|
|
|
694
|
-
|
|
372
|
+
| Surface | Keys work? |
|
|
373
|
+
|---|---|
|
|
374
|
+
| MCP tools (`list_activities`, `create_activity`, etc.) | Yes |
|
|
375
|
+
| Direct REST — `v3.activity.list/createMany/updateMany` — `phaseId` and field refs | Yes |
|
|
376
|
+
| Direct REST — `processId` (workflow) | No — hex only |
|
|
377
|
+
| Direct REST — `v3.activity.list` response with `returnFlat: true` | Fields keyed by key name (if field has `key` set) |
|
|
378
|
+
| Insight SQL | No |
|
|
379
|
+
| Activity IDs | Never — always 24-char hex |
|
|
695
380
|
|
|
696
|
-
|
|
381
|
+
### Easiest setup — `auto_set_keys`
|
|
697
382
|
|
|
698
383
|
```
|
|
699
|
-
|
|
700
|
-
1. Edit fields.ts (add new field)
|
|
701
|
-
2. npm run fields-push:force
|
|
702
|
-
3. npm run pull ← Now safe, gets generated IDs
|
|
703
|
-
|
|
704
|
-
# WRONG order (loses your work):
|
|
705
|
-
1. Edit fields.ts
|
|
706
|
-
2. npm run pull ← Overwrites your changes!
|
|
384
|
+
auto_set_keys({ workflowId: 'hex-id' })
|
|
707
385
|
```
|
|
708
386
|
|
|
709
|
-
|
|
387
|
+
- Derives camelCase keys from names/labels across the workflow, phases, and fields
|
|
388
|
+
- Skips entities that already have a key
|
|
389
|
+
- **Phase key writes fail silently in `auto_set_keys`** (known MCP bug). Set phase keys via push path instead: add `key` to each phase in `phases.ts` and run `npm run phases-push`.
|
|
390
|
+
- The push path is recommended for all three key types (workflow, field, phase) — they round-trip cleanly.
|
|
710
391
|
|
|
711
392
|
---
|
|
712
393
|
|
|
713
|
-
##
|
|
394
|
+
## Push Commands Reference
|
|
714
395
|
|
|
715
|
-
###
|
|
396
|
+
### Deleted Workflow Cleanup
|
|
716
397
|
|
|
717
|
-
|
|
398
|
+
After deleting a workflow in Hailer and running `npm run pull`:
|
|
399
|
+
- The workflow's directory is auto-removed
|
|
400
|
+
- Enum references in other `workspace/*.ts` files are NOT cleaned up
|
|
718
401
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
// === BASIC INFO (always visible) ===
|
|
723
|
-
{ label: "Customer Name", type: "text", required: true },
|
|
724
|
-
{ label: "Contact Email", type: "text" },
|
|
725
|
-
|
|
726
|
-
// === FINANCIAL SECTION (collapsible) ===
|
|
727
|
-
{
|
|
728
|
-
type: "subheader",
|
|
729
|
-
label: "Financial Details",
|
|
730
|
-
collapsedByDefault: true // Starts collapsed
|
|
731
|
-
},
|
|
732
|
-
{ label: "Unit Price", type: "numericunit", unit: "€" },
|
|
733
|
-
{ label: "Quantity", type: "numeric" },
|
|
734
|
-
{ label: "Discount %", type: "numeric" },
|
|
735
|
-
{ label: "Total", type: "numericunit", unit: "€", functionEnabled: true, ... },
|
|
736
|
-
|
|
737
|
-
// === DELIVERY SECTION (collapsible) ===
|
|
738
|
-
{
|
|
739
|
-
type: "subheader",
|
|
740
|
-
label: "Delivery Information",
|
|
741
|
-
collapsedByDefault: true
|
|
742
|
-
},
|
|
743
|
-
{ label: "Delivery Address", type: "textarea" },
|
|
744
|
-
{ label: "Delivery Date", type: "date" },
|
|
745
|
-
{ label: "Delivery Notes", type: "textarea" },
|
|
746
|
-
|
|
747
|
-
// === INTERNAL/HELPER FIELDS (hidden by default) ===
|
|
748
|
-
{
|
|
749
|
-
type: "subheader",
|
|
750
|
-
label: "Internal Fields",
|
|
751
|
-
collapsedByDefault: true
|
|
752
|
-
},
|
|
753
|
-
{ label: "Integration ID", type: "text" },
|
|
754
|
-
{ label: "Last Sync", type: "datetime" }
|
|
755
|
-
];
|
|
402
|
+
After delete + pull:
|
|
403
|
+
```bash
|
|
404
|
+
grep -r "deleted_workflow_id" workspace/
|
|
756
405
|
```
|
|
406
|
+
Fix stale references before next push.
|
|
757
407
|
|
|
758
|
-
|
|
759
|
-
- Workflows with 15+ fields
|
|
760
|
-
- Logical groupings exist (financial, delivery, contact, internal)
|
|
761
|
-
- Some fields are rarely edited after initial entry
|
|
762
|
-
- Helper/integration fields that clutter the UI
|
|
763
|
-
|
|
764
|
-
**Subheader properties:**
|
|
765
|
-
| Property | Description |
|
|
766
|
-
|----------|-------------|
|
|
767
|
-
| `type: "subheader"` | Required - marks as section divider |
|
|
768
|
-
| `label` | Section title shown in UI |
|
|
769
|
-
| `collapsedByDefault` | `true` = starts collapsed, `false` = starts expanded |
|
|
770
|
-
|
|
771
|
-
**Common section patterns:**
|
|
772
|
-
- "Basic Information" - core fields (often expanded)
|
|
773
|
-
- "Financial Details" - prices, totals, discounts
|
|
774
|
-
- "Delivery/Shipping" - addresses, dates
|
|
775
|
-
- "Contact Information" - emails, phones
|
|
776
|
-
- "Internal/System" - IDs, sync fields, helper fields (collapsed)
|
|
777
|
-
|
|
778
|
-
**Note:** Subheaders have no data - they're UI-only for organization.
|
|
779
|
-
|
|
780
|
-
---
|
|
781
|
-
|
|
782
|
-
### Add Field to Existing Workflow
|
|
408
|
+
### Renaming a Workflow
|
|
783
409
|
|
|
784
410
|
```
|
|
785
|
-
1.
|
|
786
|
-
2.
|
|
787
|
-
|
|
788
|
-
3. Return ["npm run fields-push:force"]
|
|
789
|
-
4. npm run pull (to get generated field ID in enums)
|
|
790
|
-
5. Edit phases.ts - add new field ID to phase.fields arrays using enum
|
|
791
|
-
6. Return ["npm run phases-push:force"]
|
|
411
|
+
1. Change name in workflows.ts AND main.ts
|
|
412
|
+
2. npm run workflows-push
|
|
413
|
+
3. npm run pull
|
|
792
414
|
```
|
|
793
415
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
Common pattern: reveal more fields as activity progresses through phases.
|
|
797
|
-
|
|
798
|
-
```typescript
|
|
799
|
-
// Draft: minimal fields
|
|
800
|
-
{ name: "Draft", fields: [title, description] }
|
|
801
|
-
|
|
802
|
-
// Review: add reviewer
|
|
803
|
-
{ name: "Review", fields: [title, description, reviewer] }
|
|
416
|
+
After rename: re-grep `enums.ts` for actual generated member names — they often shift to Finnish-label-derived stems.
|
|
804
417
|
|
|
805
|
-
|
|
806
|
-
{ name: "Active", fields: [title, description, reviewer, assignee, due_date, priority] }
|
|
807
|
-
|
|
808
|
-
// Done: keep all visible for reference
|
|
809
|
-
{ name: "Done", fields: [title, description, reviewer, assignee, due_date, priority] }
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
### Linear Phase Flow
|
|
418
|
+
---
|
|
813
419
|
|
|
814
|
-
|
|
815
|
-
{
|
|
816
|
-
_id: Tasks_PhaseIds.todo_abc,
|
|
817
|
-
name: "Todo",
|
|
818
|
-
isInitial: true,
|
|
819
|
-
possibleNextPhase: [Tasks_PhaseIds.in_progress_def]
|
|
820
|
-
},
|
|
821
|
-
{
|
|
822
|
-
_id: Tasks_PhaseIds.in_progress_def,
|
|
823
|
-
name: "In Progress",
|
|
824
|
-
possibleNextPhase: [Tasks_PhaseIds.done_ghi]
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
_id: Tasks_PhaseIds.done_ghi,
|
|
828
|
-
name: "Done",
|
|
829
|
-
isEndpoint: true,
|
|
830
|
-
possibleNextPhase: []
|
|
831
|
-
}
|
|
832
|
-
```
|
|
420
|
+
## Common Patterns
|
|
833
421
|
|
|
834
|
-
###
|
|
422
|
+
### Add Field to Existing Workflow
|
|
423
|
+
1. Edit `fields.ts` (omit `_id` for new) → `npm run fields-push` → `pull` (need new field IDs)
|
|
424
|
+
2. Add field ID to relevant `phase.fields` arrays → `phases-push:force` (no pull needed)
|
|
835
425
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
Tasks_PhaseIds.doing_ghi,
|
|
841
|
-
Tasks_PhaseIds.done_jkl
|
|
842
|
-
];
|
|
426
|
+
### Phase Flow Patterns
|
|
427
|
+
- **Linear:** Each phase points to next only. End phase: `isEndpoint: true, possibleNextPhase: []`
|
|
428
|
+
- **Kanban:** Each phase's `possibleNextPhase` includes all other phases
|
|
429
|
+
- **Progressive visibility:** Early phases show fewer fields, later phases show more
|
|
843
430
|
|
|
844
|
-
|
|
845
|
-
{
|
|
846
|
-
_id: Tasks_PhaseIds.todo_def,
|
|
847
|
-
name: "Todo",
|
|
848
|
-
possibleNextPhase: allPhases.filter(p => p !== Tasks_PhaseIds.todo_def)
|
|
849
|
-
}
|
|
850
|
-
```
|
|
431
|
+
### Subheaders
|
|
432
|
+
For 15+ fields, use `{ type: "subheader", label: "Section", collapsedByDefault: true }` to group.
|
|
851
433
|
|
|
852
434
|
---
|
|
853
435
|
|
|
@@ -856,53 +438,16 @@ const allPhases = [
|
|
|
856
438
|
| Wrong | Right |
|
|
857
439
|
|-------|-------|
|
|
858
440
|
| Creating fields before phases | Order: Workflow → Phases → Fields |
|
|
859
|
-
| Adding `_id` to new items | Omit
|
|
860
|
-
|
|
|
861
|
-
| Hardcoding IDs
|
|
862
|
-
|
|
|
863
|
-
|
|
|
864
|
-
|
|
|
865
|
-
|
|
|
866
|
-
|
|
|
867
|
-
|
|
|
868
|
-
|
|
|
869
|
-
|
|
|
870
|
-
|
|
|
871
|
-
|
|
|
872
|
-
| Two fields with same name/label | One will be removed during push — deduplicate labels! |
|
|
873
|
-
|
|
874
|
-
---
|
|
875
|
-
|
|
876
|
-
## CRITICAL: Field Types Cannot Be Changed via API
|
|
877
|
-
|
|
878
|
-
**Problem:** Once a field is created, you cannot change its `type` via the SDK push commands.
|
|
879
|
-
|
|
880
|
-
**Example:** Cannot change `textarea` → `activitylink` via `npm run fields-push`
|
|
881
|
-
|
|
882
|
-
**Workarounds:**
|
|
883
|
-
1. Create a **new field** with the correct type (deprecate old one)
|
|
884
|
-
2. Change field type manually in **Hailer UI** (Settings → Workflow → Edit field)
|
|
885
|
-
3. Delete and recreate the field (loses existing data)
|
|
886
|
-
|
|
887
|
-
**What CAN be changed via API:**
|
|
888
|
-
- `label`, `description`, `required`
|
|
889
|
-
- `data` (options for predefinedoptions, linked workflows for activitylink)
|
|
890
|
-
- Field order in workflow
|
|
891
|
-
|
|
892
|
-
---
|
|
893
|
-
|
|
894
|
-
## Checklist
|
|
895
|
-
|
|
896
|
-
Before pushing workflow changes:
|
|
897
|
-
|
|
898
|
-
- [ ] Used enums for all existing IDs
|
|
899
|
-
- [ ] Omitted `_id` for new items
|
|
900
|
-
- [ ] **Asked user which phases should show new fields**
|
|
901
|
-
- [ ] New fields added ONLY to relevant `phase.fields` arrays
|
|
902
|
-
- [ ] Phase transitions make sense (`possibleNextPhase`)
|
|
903
|
-
- [ ] Required fields have `required: true`
|
|
904
|
-
- [ ] Link fields have correct workflow IDs in `data`
|
|
905
|
-
- [ ] Using `:force` variant for commands that need it
|
|
906
|
-
|
|
907
|
-
When adding a new field, ALWAYS ask:
|
|
908
|
-
> "Which phases should this field be visible in? All phases or specific ones?"
|
|
441
|
+
| Adding `_id` to new items | Omit — server generates |
|
|
442
|
+
| `npm run pull` before push | Push first, then pull only if new IDs needed |
|
|
443
|
+
| Hardcoding IDs / editing `enums.ts` | Use enums, never edit |
|
|
444
|
+
| Manually wiring new fields into phases | New fields auto-wire into ALL phases on creation; only push `phase.fields[]` for non-default placement |
|
|
445
|
+
| `workflows-push` for new workflow | `workflows-sync:force` |
|
|
446
|
+
| `type: "dropdown"`, `options: [...]` | `type: "textpredefinedoptions"`, `data: [...]` |
|
|
447
|
+
| Forgetting `possibleNextPhase` | Activities stuck forever |
|
|
448
|
+
| `{ type: "numericunit", data: [{ unit: "€" }] }` | `{ type: "numericunit", unit: "€", data: [] }` — unit is top-level |
|
|
449
|
+
| `enableUnlinkedMode: true` = requires linking | Inverted: `true` = standalone items (dataset), `false` = requires linking |
|
|
450
|
+
| `primaryDateField` in `main.ts` | Set on phases in `phases.ts` |
|
|
451
|
+
| `primaryNumberField` (typo) | `primaryNumericField` |
|
|
452
|
+
| Pulling without reading "✨ Created: N" first | If N=0, pull wipes local edits without creating anything |
|
|
453
|
+
| Batching un-pushed field additions with a new-workflow create | Un-pushed fields get wiped by the create-pull; push existing-workflow changes first |
|