@hailer/mcp 1.1.16 → 1.1.17-beta.0
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/CLAUDE.md +117 -320
- package/.claude/commands/app-squad.md +86 -90
- package/.claude/commands/audit-squad.md +19 -19
- package/.claude/commands/autoplan.md +3 -3
- package/.claude/commands/cleanup-squad.md +16 -16
- package/.claude/commands/config-squad.md +30 -30
- package/.claude/commands/crud-squad.md +23 -23
- package/.claude/commands/data-squad.md +21 -21
- package/.claude/commands/debug-squad.md +44 -44
- package/.claude/commands/doc-squad.md +16 -16
- package/.claude/commands/help:agents.md +130 -99
- package/.claude/commands/help:commands.md +15 -15
- package/.claude/commands/help:faq.md +17 -17
- package/.claude/commands/help:plugins.md +1 -1
- package/.claude/commands/help:skills.md +18 -24
- package/.claude/commands/hotfix-squad.md +22 -22
- package/.claude/commands/integration-squad.md +22 -22
- package/.claude/commands/janitor-squad.md +31 -31
- package/.claude/commands/learn-auto.md +5 -5
- package/.claude/commands/learn.md +12 -20
- package/.claude/commands/onboard-squad.md +39 -49
- package/.claude/commands/plan-workspace.md +2 -2
- package/.claude/commands/publish.md +32 -37
- package/.claude/commands/review-squad.md +27 -27
- package/.claude/commands/stats.md +26 -12
- package/.claude/commands/swarm.md +25 -25
- package/.claude/skills/chrome-mcp-reference/SKILL.md +5 -0
- package/.claude/skills/hailer-api-client/SKILL.md +55 -16
- package/.claude/skills/hailer-app-builder/SKILL.md +4 -270
- package/.claude/skills/hailer-apps-pictures/SKILL.md +3 -3
- package/.claude/skills/hailer-design-system/SKILL.md +96 -4
- package/.claude/skills/hailer-monolith-automations/SKILL.md +138 -116
- package/.claude/skills/hailer-permissions-system/SKILL.md +6 -9
- package/.claude/skills/hailer-project-protocol/SKILL.md +20 -110
- package/.claude/skills/integration-patterns/SKILL.md +6 -6
- package/.claude/skills/lsp-setup/SKILL.md +8 -9
- package/.claude/skills/sdk-activity-patterns/SKILL.md +238 -0
- package/.claude/skills/{SDK-document-templates → sdk-document-templates}/SKILL.md +13 -340
- package/.claude/skills/{SDK-function-fields → sdk-function-fields}/SKILL.md +8 -40
- package/.claude/skills/{SDK-insight-queries → sdk-insight-queries}/SKILL.md +114 -392
- package/.claude/skills/{SDK-ws-config-skill → sdk-ws-config-skill}/SKILL.md +79 -310
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +84 -361
- package/.opencode/package-lock.json +117 -0
- package/CLAUDE.md +5 -358
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +10 -127
- package/dist/app.js.map +1 -1
- package/dist/bot/bot-manager.d.ts +3 -14
- package/dist/bot/bot-manager.d.ts.map +1 -1
- package/dist/bot/bot-manager.js +13 -4
- package/dist/bot/bot-manager.js.map +1 -1
- package/dist/bot/bot.d.ts +23 -102
- package/dist/bot/bot.d.ts.map +1 -1
- package/dist/bot/bot.js +356 -1212
- package/dist/bot/bot.js.map +1 -1
- package/dist/bot/services/bot-permissions.d.ts +50 -0
- package/dist/bot/services/bot-permissions.d.ts.map +1 -0
- package/dist/bot/services/bot-permissions.js +198 -0
- package/dist/bot/services/bot-permissions.js.map +1 -0
- package/dist/bot/services/index.d.ts +4 -2
- package/dist/bot/services/index.d.ts.map +1 -1
- package/dist/bot/services/index.js +10 -5
- package/dist/bot/services/index.js.map +1 -1
- package/dist/bot/services/message-classifier.d.ts +1 -1
- package/dist/bot/services/message-classifier.d.ts.map +1 -1
- package/dist/bot/services/message-classifier.js.map +1 -1
- package/dist/bot/services/signal-router.d.ts +32 -0
- package/dist/bot/services/signal-router.d.ts.map +1 -0
- package/dist/bot/services/signal-router.js +132 -0
- package/dist/bot/services/signal-router.js.map +1 -0
- package/dist/bot/services/system-prompt.d.ts +12 -0
- package/dist/bot/services/system-prompt.d.ts.map +1 -0
- package/dist/bot/services/system-prompt.js +93 -0
- package/dist/bot/services/system-prompt.js.map +1 -0
- package/dist/bot/services/types.d.ts +7 -34
- package/dist/bot/services/types.d.ts.map +1 -1
- package/dist/bot/services/types.js +0 -3
- package/dist/bot/services/types.js.map +1 -1
- package/dist/bot/services/workspace-refresh.d.ts +47 -0
- package/dist/bot/services/workspace-refresh.d.ts.map +1 -0
- package/dist/bot/services/workspace-refresh.js +154 -0
- package/dist/bot/services/workspace-refresh.js.map +1 -0
- package/dist/bot-config/constants.d.ts +0 -36
- package/dist/bot-config/constants.d.ts.map +1 -1
- package/dist/bot-config/constants.js +1 -76
- package/dist/bot-config/constants.js.map +1 -1
- package/dist/bot-config/context.d.ts +2 -42
- package/dist/bot-config/context.d.ts.map +1 -1
- package/dist/bot-config/context.js +13 -134
- package/dist/bot-config/context.js.map +1 -1
- package/dist/bot-config/index.d.ts +6 -15
- package/dist/bot-config/index.d.ts.map +1 -1
- package/dist/bot-config/index.js +5 -80
- package/dist/bot-config/index.js.map +1 -1
- package/dist/bot-config/loader.d.ts +16 -4
- package/dist/bot-config/loader.d.ts.map +1 -1
- package/dist/bot-config/loader.js +187 -96
- package/dist/bot-config/loader.js.map +1 -1
- package/dist/bot-config/persistence.d.ts +1 -52
- package/dist/bot-config/persistence.d.ts.map +1 -1
- package/dist/bot-config/persistence.js +3 -213
- package/dist/bot-config/persistence.js.map +1 -1
- package/dist/bot-config/state.d.ts +0 -41
- package/dist/bot-config/state.d.ts.map +1 -1
- package/dist/bot-config/state.js +0 -151
- package/dist/bot-config/state.js.map +1 -1
- package/dist/bot-config/tools.d.ts +1 -1
- package/dist/bot-config/tools.js +27 -27
- package/dist/bot-config/tools.js.map +1 -1
- package/dist/bot-config/types.d.ts +39 -32
- package/dist/bot-config/types.d.ts.map +1 -1
- package/dist/bot-config/types.js +0 -3
- package/dist/bot-config/types.js.map +1 -1
- package/dist/bot-config/webhooks.d.ts +0 -4
- package/dist/bot-config/webhooks.d.ts.map +1 -1
- package/dist/bot-config/webhooks.js +0 -13
- package/dist/bot-config/webhooks.js.map +1 -1
- package/dist/commands/seed-config.js +16 -31
- package/dist/commands/seed-config.js.map +1 -1
- package/dist/config.d.ts +0 -9
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -15
- package/dist/config.js.map +1 -1
- package/dist/mcp/hailer-clients.js +2 -2
- package/dist/mcp/hailer-clients.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts +10 -115
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +39 -363
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/activity.d.ts +3 -0
- package/dist/mcp/tools/activity.d.ts.map +1 -1
- package/dist/mcp/tools/activity.js +8 -1
- package/dist/mcp/tools/activity.js.map +1 -1
- package/dist/mcp/tools/app-core.d.ts +3 -0
- package/dist/mcp/tools/app-core.d.ts.map +1 -1
- package/dist/mcp/tools/app-core.js +9 -2
- package/dist/mcp/tools/app-core.js.map +1 -1
- package/dist/mcp/tools/app-marketplace.d.ts +3 -0
- package/dist/mcp/tools/app-marketplace.d.ts.map +1 -1
- package/dist/mcp/tools/app-marketplace.js +13 -1
- package/dist/mcp/tools/app-marketplace.js.map +1 -1
- package/dist/mcp/tools/app-member.d.ts +3 -0
- package/dist/mcp/tools/app-member.d.ts.map +1 -1
- package/dist/mcp/tools/app-member.js +6 -1
- package/dist/mcp/tools/app-member.js.map +1 -1
- package/dist/mcp/tools/app-scaffold.d.ts +3 -0
- package/dist/mcp/tools/app-scaffold.d.ts.map +1 -1
- package/dist/mcp/tools/app-scaffold.js +15 -11
- package/dist/mcp/tools/app-scaffold.js.map +1 -1
- package/dist/mcp/tools/company.d.ts +3 -0
- package/dist/mcp/tools/company.d.ts.map +1 -1
- package/dist/mcp/tools/company.js +5 -1
- package/dist/mcp/tools/company.js.map +1 -1
- package/dist/mcp/tools/discussion.d.ts +3 -0
- package/dist/mcp/tools/discussion.d.ts.map +1 -1
- package/dist/mcp/tools/discussion.js +13 -2
- package/dist/mcp/tools/discussion.js.map +1 -1
- package/dist/mcp/tools/file.d.ts +3 -0
- package/dist/mcp/tools/file.d.ts.map +1 -1
- package/dist/mcp/tools/file.js +6 -1
- package/dist/mcp/tools/file.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +7 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +34 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/insight.d.ts +3 -0
- package/dist/mcp/tools/insight.d.ts.map +1 -1
- package/dist/mcp/tools/insight.js +18 -8
- package/dist/mcp/tools/insight.js.map +1 -1
- package/dist/mcp/tools/user.d.ts +3 -0
- package/dist/mcp/tools/user.d.ts.map +1 -1
- package/dist/mcp/tools/user.js +6 -1
- package/dist/mcp/tools/user.js.map +1 -1
- package/dist/mcp/tools/workflow-permissions.d.ts +3 -0
- package/dist/mcp/tools/workflow-permissions.d.ts.map +1 -1
- package/dist/mcp/tools/workflow-permissions.js +8 -1
- package/dist/mcp/tools/workflow-permissions.js.map +1 -1
- package/dist/mcp/tools/workflow.d.ts +3 -0
- package/dist/mcp/tools/workflow.d.ts.map +1 -1
- package/dist/mcp/tools/workflow.js +29 -28
- package/dist/mcp/tools/workflow.js.map +1 -1
- package/dist/mcp/utils/index.d.ts +4 -11
- package/dist/mcp/utils/index.d.ts.map +1 -1
- package/dist/mcp/utils/index.js +5 -36
- package/dist/mcp/utils/index.js.map +1 -1
- package/dist/mcp/utils/role-utils.d.ts +0 -32
- package/dist/mcp/utils/role-utils.d.ts.map +1 -1
- package/dist/mcp/utils/role-utils.js +0 -73
- package/dist/mcp/utils/role-utils.js.map +1 -1
- package/dist/mcp/utils/tool-helpers.d.ts +0 -25
- package/dist/mcp/utils/tool-helpers.d.ts.map +1 -1
- package/dist/mcp/utils/tool-helpers.js +0 -34
- package/dist/mcp/utils/tool-helpers.js.map +1 -1
- package/dist/mcp/webhook-handler.d.ts +4 -34
- package/dist/mcp/webhook-handler.d.ts.map +1 -1
- package/dist/mcp/webhook-handler.js +57 -74
- package/dist/mcp/webhook-handler.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +3 -78
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -2
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -208
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -204
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sdk-activity-patterns
|
|
3
|
+
description: Activity CRUD patterns - field value formats, dates, users, links, dropdowns
|
|
4
|
+
version: 1.5.0
|
|
5
|
+
triggers: Create activity, update activity, field values, date format, activitylink value
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Activity CRUD Patterns
|
|
9
|
+
|
|
10
|
+
## Create Activity
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
mcp__hailer__create_activity({
|
|
14
|
+
workflowId: "682ac815fba468d857d498f7",
|
|
15
|
+
phaseId: "682ac815fba468d857d49904",
|
|
16
|
+
name: "New Task", // Optional
|
|
17
|
+
fields: {
|
|
18
|
+
"fieldId1": "value1",
|
|
19
|
+
"fieldId2": "value2"
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Update Activity
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
mcp__hailer__update_activity({
|
|
28
|
+
activityId: "692abc123def456",
|
|
29
|
+
fields: { "fieldId1": "new value" },
|
|
30
|
+
phaseId: "682ac815fba468d857d49906" // Optional: move to different phase
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Critical Field Value Rules
|
|
37
|
+
|
|
38
|
+
**All single-value fields use STRING, not array.** No multi-select types exist in Hailer.
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
// ✅ Correct
|
|
42
|
+
fields: { priority: "High", assignee: "userId", customer: "actId" }
|
|
43
|
+
|
|
44
|
+
// ❌ Wrong — arrays rejected
|
|
45
|
+
fields: { priority: ["High"], assignee: ["userId"], customer: ["actId"] }
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Dates are Unix timestamps in milliseconds** (not ISO strings):
|
|
49
|
+
```javascript
|
|
50
|
+
fields: { due_date: new Date('2024-11-07').getTime() } // 1730937600000
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Time fields** also use full millisecond timestamps including date:
|
|
54
|
+
```javascript
|
|
55
|
+
const today = new Date();
|
|
56
|
+
today.setUTCHours(9, 30, 0, 0);
|
|
57
|
+
fields: { start_time: today.getTime() }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**daterange / datetimerange** use object with `start` + `end`:
|
|
61
|
+
```javascript
|
|
62
|
+
fields: { period: { start: 1730937600000, end: 1731024000000 } }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**activitylink** is plain string ID — READ format (object with `_id`, `name`) differs from WRITE format:
|
|
66
|
+
```javascript
|
|
67
|
+
// ✅ Write: plain string ID
|
|
68
|
+
fields: { customer: "692abc123def456" }
|
|
69
|
+
|
|
70
|
+
// ❌ Wrong: object (this is what you get when reading)
|
|
71
|
+
fields: { customer: { _id: "692abc123def456", name: "Acme" } }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**textpredefinedoptions** is the exact option string, not an array:
|
|
75
|
+
```javascript
|
|
76
|
+
fields: { status: "In Progress" }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**text fields must always be strings**, even for numeric-looking values:
|
|
80
|
+
```javascript
|
|
81
|
+
fields: { code: "300" } // not 300
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Modified Fields (Checkbox & File)
|
|
87
|
+
|
|
88
|
+
These use base types with a `modifier` in the field config:
|
|
89
|
+
|
|
90
|
+
| Config | Write Format |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `numeric` + `modifier.checkbox: true` | `1` (true) or `0` (false) |
|
|
93
|
+
| `text` + `modifier.file: true` | File reference (UI handles upload) |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Clearing Field Values
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
mcp__hailer__update_activity({
|
|
101
|
+
activityId: "692abc123def456",
|
|
102
|
+
fields: {
|
|
103
|
+
"assignee_abc": "", // Clear user
|
|
104
|
+
"due_date_def": null, // Clear date
|
|
105
|
+
"notes_ghi": "" // Clear text
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Bulk Creation
|
|
113
|
+
|
|
114
|
+
Use the `activities[]` array for bulk creation. **Each activity must include its own `teamId` and `phaseId`.**
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
mcp__hailer__create_activity({
|
|
118
|
+
workflowId: "682ac815fba468d857d498f7",
|
|
119
|
+
activities: [
|
|
120
|
+
{
|
|
121
|
+
name: "Task 1",
|
|
122
|
+
phaseId: "682ac815fba468d857d49904",
|
|
123
|
+
teamId: "690d2b2e2b3a4c5d6e7f8a9b", // REQUIRED per activity!
|
|
124
|
+
fields: { "priority_abc": "High" }
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: "Task 2",
|
|
128
|
+
phaseId: "682ac815fba468d857d49904",
|
|
129
|
+
teamId: "690d2b2e2b3a4c5d6e7f8a9b",
|
|
130
|
+
fields: { "priority_abc": "Medium" }
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Single vs Bulk: Parameter Placement
|
|
137
|
+
|
|
138
|
+
| Parameter | Single mode | Bulk mode |
|
|
139
|
+
|-----------|-------------|-----------|
|
|
140
|
+
| `name` | Top-level | Inside each `activities[]` item |
|
|
141
|
+
| `phaseId` | Top-level | Inside each `activities[]` item |
|
|
142
|
+
| `teamId` | Top-level | **Inside each `activities[]` item** |
|
|
143
|
+
| `fields` | Top-level | Inside each `activities[]` item |
|
|
144
|
+
| `workflowId` | Top-level | Top-level (shared) |
|
|
145
|
+
|
|
146
|
+
**CRITICAL:** In bulk mode, `teamId` at the top level is **IGNORED**. Each activity in the array must have its own `teamId`. Omitting it causes "Missing team(s)" (code 127) unless the workflow has a default team.
|
|
147
|
+
|
|
148
|
+
### Team ID Fallback Chain
|
|
149
|
+
|
|
150
|
+
The MCP tool auto-fills `teamId` if omitted:
|
|
151
|
+
1. Per-activity `teamId` (if provided) — **always provide this**
|
|
152
|
+
2. Workflow's default team (`workflow.team`)
|
|
153
|
+
3. First workspace team (last resort, may be wrong team)
|
|
154
|
+
|
|
155
|
+
**Best practice:** Always pass `teamId` explicitly. Get IDs from `workspace/teams.ts`.
|
|
156
|
+
|
|
157
|
+
### Common Error: "Missing team(s)" (Code 127)
|
|
158
|
+
|
|
159
|
+
**Fix options:**
|
|
160
|
+
1. Pass `teamId` explicitly in every create call (best)
|
|
161
|
+
2. Set default team on the workflow in Hailer UI
|
|
162
|
+
3. Ask orchestrator for available team IDs before creating
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Enum Key vs ObjectId Gotcha
|
|
167
|
+
|
|
168
|
+
MCP tools require actual MongoDB ObjectIds (24-char hex strings), not enum key names.
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// ❌ WRONG - enum KEY NAME, not the ID
|
|
172
|
+
mcp__hailer__create_activity({
|
|
173
|
+
workflowId: "Asiakkaat", // enum key
|
|
174
|
+
fields: { "projekti_f84": "value" } // enum key
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
// ✅ CORRECT - real ObjectIds
|
|
178
|
+
mcp__hailer__create_activity({
|
|
179
|
+
workflowId: "682ac815fba468d857d498f7",
|
|
180
|
+
fields: { "68cbfec59b3869137fe2af84": "value" }
|
|
181
|
+
})
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**TypeScript code vs MCP calls:**
|
|
185
|
+
- **TypeScript:** Use enums for type safety → `WorkflowIds.Asiakkaat` resolves to the ObjectId
|
|
186
|
+
- **MCP tools:** Pass the actual ObjectId string directly
|
|
187
|
+
|
|
188
|
+
**Where to get real IDs:**
|
|
189
|
+
- `workspace/enums.ts` → enum values ARE the real ObjectIds
|
|
190
|
+
- `list_workflows` → workflow `_id` field
|
|
191
|
+
- `get_workflow_schema` → field `_id` property
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Phase Transitions
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
// Move only
|
|
199
|
+
mcp__hailer__update_activity({ activityId: "...", phaseId: "..." })
|
|
200
|
+
|
|
201
|
+
// Move + update fields
|
|
202
|
+
mcp__hailer__update_activity({
|
|
203
|
+
activityId: "692abc123def456",
|
|
204
|
+
phaseId: "682ac815fba468d857d49906",
|
|
205
|
+
fields: { "completed_date_abc": Date.now() }
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Checklist
|
|
212
|
+
|
|
213
|
+
Before creating/updating activities:
|
|
214
|
+
|
|
215
|
+
- [ ] Have workflow ID (from enum or API)
|
|
216
|
+
- [ ] Have phase ID (from enum or API)
|
|
217
|
+
- [ ] Have field IDs (from enum or API)
|
|
218
|
+
- [ ] Have **team ID** (from teams.ts or API) — don't rely on fallbacks
|
|
219
|
+
- [ ] All values are **STRINGS** for select/user/link fields (no multi-select types exist)
|
|
220
|
+
- [ ] Dates are **Unix timestamps in milliseconds**
|
|
221
|
+
- [ ] Times are **Unix timestamps in milliseconds** (like dates)
|
|
222
|
+
- [ ] **Bulk mode:** teamId, phaseId, fields are **inside each activity object**, not top-level
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Common Mistakes
|
|
227
|
+
|
|
228
|
+
| Wrong | Right |
|
|
229
|
+
|-------|-------|
|
|
230
|
+
| `{ priority: ["High"] }` | `{ priority: "High" }` (string) |
|
|
231
|
+
| `{ assignee: ["userId"] }` | `{ assignee: "userId" }` (string) |
|
|
232
|
+
| `{ customer: ["actId"] }` | `{ customer: "actId" }` (string) |
|
|
233
|
+
| `{ date: "2024-11-07" }` | `{ date: 1730937600000 }` (timestamp ms) |
|
|
234
|
+
| `{ time: "09:00" }` | `{ time: 1765863000000 }` (ms timestamp) |
|
|
235
|
+
| `{ daterange: 1730937600000 }` | `{ daterange: { start: ..., end: ... } }` |
|
|
236
|
+
| `{ textField: 300 }` | `{ textField: "300" }` (must be string) |
|
|
237
|
+
| Passing enum key names as IDs | Use real 24-char hex ObjectIds |
|
|
238
|
+
| Bulk create with top-level teamId | Put teamId INSIDE each activity object |
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: sdk-document-templates
|
|
3
3
|
description: PDF/CSV document template patterns - field mappings, pdfmake structure, generation code
|
|
4
4
|
version: 1.2.0
|
|
5
5
|
triggers: Document template, PDF generation, CSV export, pdfmake, field mapping
|
|
@@ -21,6 +21,17 @@ workspace/[Workflow]_[id]/
|
|
|
21
21
|
|
|
22
22
|
## Template Lifecycle
|
|
23
23
|
|
|
24
|
+
### Sync vs Push
|
|
25
|
+
|
|
26
|
+
| Command | Creates new | Updates existing | Deletes removed |
|
|
27
|
+
|---------|-------------|------------------|-----------------|
|
|
28
|
+
| `templates sync` | Yes | No | Yes |
|
|
29
|
+
| `templates push` | No | Yes | No |
|
|
30
|
+
|
|
31
|
+
**New template flow:** Add entry to `templates.ts` → `npm run templates-sync` (creates template on server) → `npm run pull` (downloads generated folder + `template.config.ts` + `template.code.ts`) → edit config/code → `npm run templates-push`.
|
|
32
|
+
|
|
33
|
+
**Updating existing:** Just edit `template.config.ts` or `template.code.ts` and run `npm run templates-push`.
|
|
34
|
+
|
|
24
35
|
### Creating New Template
|
|
25
36
|
|
|
26
37
|
```
|
|
@@ -317,91 +328,7 @@ export class invoice_abc {
|
|
|
317
328
|
|
|
318
329
|
---
|
|
319
330
|
|
|
320
|
-
##
|
|
321
|
-
|
|
322
|
-
### Basic Text
|
|
323
|
-
```javascript
|
|
324
|
-
{ text: 'Hello World' }
|
|
325
|
-
{ text: 'Bold text', bold: true }
|
|
326
|
-
{ text: 'Large text', fontSize: 14 }
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Using Field Values
|
|
330
|
-
```javascript
|
|
331
|
-
const fields = this.fieldMap.fields;
|
|
332
|
-
|
|
333
|
-
{ text: fields.invoiceNumber?.value || 'N/A' }
|
|
334
|
-
{ text: fields.customerName?.label + ': ' + fields.customerName?.value }
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### Columns
|
|
338
|
-
```javascript
|
|
339
|
-
{
|
|
340
|
-
columns: [
|
|
341
|
-
{ width: '*', text: 'Left side' },
|
|
342
|
-
{ width: 'auto', text: 'Right side' }
|
|
343
|
-
]
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
### Tables
|
|
348
|
-
```javascript
|
|
349
|
-
{
|
|
350
|
-
table: {
|
|
351
|
-
headerRows: 1,
|
|
352
|
-
widths: ['*', 100, 100],
|
|
353
|
-
body: [
|
|
354
|
-
// Header row
|
|
355
|
-
[
|
|
356
|
-
{ text: 'Item', bold: true },
|
|
357
|
-
{ text: 'Qty', bold: true },
|
|
358
|
-
{ text: 'Price', bold: true }
|
|
359
|
-
],
|
|
360
|
-
// Data rows
|
|
361
|
-
['Product A', '10', '€100.00'],
|
|
362
|
-
['Product B', '5', '€50.00']
|
|
363
|
-
]
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### Table Spacing for Label:Value Grids
|
|
369
|
-
|
|
370
|
-
For 4-column label:value layouts, use narrower label columns to keep values close:
|
|
371
|
-
```javascript
|
|
372
|
-
// ✅ Good: Values stay close to labels
|
|
373
|
-
{
|
|
374
|
-
table: {
|
|
375
|
-
widths: [90, 'auto', 90, 'auto'],
|
|
376
|
-
body: [
|
|
377
|
-
[
|
|
378
|
-
{ text: 'Tilaaja:', bold: true }, fields.customer?.value || '',
|
|
379
|
-
{ text: 'Päivämäärä:', bold: true }, fields.date?.value || ''
|
|
380
|
-
]
|
|
381
|
-
]
|
|
382
|
-
},
|
|
383
|
-
layout: 'noBorders'
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ❌ Avoid: [120, '*', 120, '*'] spreads content too wide
|
|
387
|
-
```
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### Images
|
|
391
|
-
```javascript
|
|
392
|
-
// From fieldMap
|
|
393
|
-
{
|
|
394
|
-
image: this.fieldMap.images?.logo?.value,
|
|
395
|
-
width: 150
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// With fallback
|
|
399
|
-
this.fieldMap.images?.logo?.value
|
|
400
|
-
? { image: this.fieldMap.images.logo.value, width: 150 }
|
|
401
|
-
: { text: 'No logo', italics: true }
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Logo in Header (Two-Step Requirement)
|
|
331
|
+
## Logo in Header (Two-Step Requirement)
|
|
405
332
|
|
|
406
333
|
**CRITICAL:** Logo in PDF header requires BOTH config AND code reference:
|
|
407
334
|
|
|
@@ -437,111 +364,6 @@ getEmptyPdfDoc(fieldMap) {
|
|
|
437
364
|
|
|
438
365
|
**Common mistake:** Adding logo to config but not referencing in header function → logo doesn't appear.
|
|
439
366
|
|
|
440
|
-
### Margins & Spacing
|
|
441
|
-
```javascript
|
|
442
|
-
// [left, top, right, bottom]
|
|
443
|
-
{ text: 'Padded', margin: [10, 20, 10, 20] }
|
|
444
|
-
|
|
445
|
-
// Blank line
|
|
446
|
-
{ text: ' ' }
|
|
447
|
-
{ text: '', margin: [0, 10, 0, 0] }
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Styles
|
|
451
|
-
```javascript
|
|
452
|
-
// Define in getEmptyPdfDoc
|
|
453
|
-
styles: {
|
|
454
|
-
header: { fontSize: 18, bold: true },
|
|
455
|
-
subheader: { fontSize: 14, bold: true },
|
|
456
|
-
tableHeader: { bold: true, fillColor: '#eeeeee' }
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Use
|
|
460
|
-
{ text: 'Title', style: 'header' }
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
---
|
|
464
|
-
|
|
465
|
-
## Complete PDF Example
|
|
466
|
-
|
|
467
|
-
```javascript
|
|
468
|
-
async setPdfDefinition() {
|
|
469
|
-
const fields = this.fieldMap.fields;
|
|
470
|
-
const images = this.fieldMap.images;
|
|
471
|
-
|
|
472
|
-
const doc = this.getEmptyPdfDoc(this.fieldMap);
|
|
473
|
-
|
|
474
|
-
// Header with logo
|
|
475
|
-
doc.content.push({
|
|
476
|
-
columns: [
|
|
477
|
-
images?.logo?.value
|
|
478
|
-
? { image: images.logo.value, width: 120 }
|
|
479
|
-
: { text: '' },
|
|
480
|
-
{
|
|
481
|
-
alignment: 'right',
|
|
482
|
-
stack: [
|
|
483
|
-
{ text: 'INVOICE', style: 'header' },
|
|
484
|
-
{ text: fields.invoiceNumber?.value || '' }
|
|
485
|
-
]
|
|
486
|
-
}
|
|
487
|
-
]
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
// Customer info
|
|
491
|
-
doc.content.push({ text: ' ' });
|
|
492
|
-
doc.content.push({
|
|
493
|
-
text: fields.customerName?.value || 'Unknown Customer',
|
|
494
|
-
style: 'subheader'
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// Line items table
|
|
498
|
-
doc.content.push({ text: ' ' });
|
|
499
|
-
doc.content.push(this.buildLineItemsTable());
|
|
500
|
-
|
|
501
|
-
// Total
|
|
502
|
-
doc.content.push({
|
|
503
|
-
alignment: 'right',
|
|
504
|
-
text: `Total: €${fields.amount?.value || '0.00'}`,
|
|
505
|
-
bold: true,
|
|
506
|
-
margin: [0, 20, 0, 0]
|
|
507
|
-
});
|
|
508
|
-
|
|
509
|
-
const filename = `invoice_${fields.invoiceNumber?.value || 'draft'}`;
|
|
510
|
-
this.setDocument(doc, filename);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
buildLineItemsTable() {
|
|
514
|
-
// Build from itemTable if available
|
|
515
|
-
const items = this.fieldMap.itemTable || [];
|
|
516
|
-
|
|
517
|
-
const body = [
|
|
518
|
-
// Header
|
|
519
|
-
[
|
|
520
|
-
{ text: 'Description', bold: true },
|
|
521
|
-
{ text: 'Qty', bold: true },
|
|
522
|
-
{ text: 'Price', bold: true }
|
|
523
|
-
]
|
|
524
|
-
];
|
|
525
|
-
|
|
526
|
-
// Data rows
|
|
527
|
-
items.forEach(item => {
|
|
528
|
-
body.push([
|
|
529
|
-
item.description || '',
|
|
530
|
-
item.quantity || '',
|
|
531
|
-
item.price || ''
|
|
532
|
-
]);
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
return {
|
|
536
|
-
table: {
|
|
537
|
-
headerRows: 1,
|
|
538
|
-
widths: ['*', 60, 80],
|
|
539
|
-
body
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
```
|
|
544
|
-
|
|
545
367
|
---
|
|
546
368
|
|
|
547
369
|
## CSV Templates
|
|
@@ -729,17 +551,6 @@ getEmptyPdfDoc(fieldMap) {
|
|
|
729
551
|
|
|
730
552
|
## Additional Patterns
|
|
731
553
|
|
|
732
|
-
### Using Raw Field IDs vs Enums
|
|
733
|
-
|
|
734
|
-
When orchestrator provides field IDs directly, use them as strings:
|
|
735
|
-
```typescript
|
|
736
|
-
// Raw field ID (common)
|
|
737
|
-
value: "::67e697da6ada809b961c35b5"
|
|
738
|
-
|
|
739
|
-
// With enums (when imported)
|
|
740
|
-
value: `::${Workflow_FieldIds.date_abc}`
|
|
741
|
-
```
|
|
742
|
-
|
|
743
554
|
### Clickable Links
|
|
744
555
|
```javascript
|
|
745
556
|
if (fields.materialsLink?.value) {
|
|
@@ -881,144 +692,6 @@ if (isValidValue(fields.weight?.value)) {
|
|
|
881
692
|
|
|
882
693
|
---
|
|
883
694
|
|
|
884
|
-
### Distinguishing Meaningful Zero from Empty/Null
|
|
885
|
-
|
|
886
|
-
**Problem:** CO2 fields in Spolia required multiple iterations to filter correctly. Some zeros are meaningful (calculated values), others are empty/null placeholders.
|
|
887
|
-
|
|
888
|
-
**Solution:** Use type-specific filtering logic:
|
|
889
|
-
|
|
890
|
-
```typescript
|
|
891
|
-
// Core pattern: Filter out truly empty values but keep meaningful zeros
|
|
892
|
-
function shouldDisplayValue(value: unknown): boolean {
|
|
893
|
-
// Null and undefined are always empty
|
|
894
|
-
if (value === null || value === undefined) return false;
|
|
895
|
-
|
|
896
|
-
// Empty string is empty
|
|
897
|
-
if (value === '') return false;
|
|
898
|
-
|
|
899
|
-
// NaN is empty (for numeric fields)
|
|
900
|
-
if (typeof value === 'number' && isNaN(value)) return false;
|
|
901
|
-
|
|
902
|
-
// CRITICAL: Keep 0 - it's a meaningful numeric value
|
|
903
|
-
if (value === 0) return true;
|
|
904
|
-
|
|
905
|
-
return true;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Pattern: Filter fields for display based on value type
|
|
909
|
-
const displayFields = fields.filter(f => {
|
|
910
|
-
const value = activity.fields?.[f.id]?.value;
|
|
911
|
-
|
|
912
|
-
// Different rules per field type
|
|
913
|
-
switch (f.fieldType) {
|
|
914
|
-
case 'number':
|
|
915
|
-
// Keep 0, filter null/undefined/NaN
|
|
916
|
-
return shouldDisplayValue(value);
|
|
917
|
-
|
|
918
|
-
case 'text':
|
|
919
|
-
// Filter null, undefined, empty string
|
|
920
|
-
// Consider whitespace-only as empty
|
|
921
|
-
if (!value) return false;
|
|
922
|
-
if (typeof value === 'string' && value.trim() === '') return false;
|
|
923
|
-
return true;
|
|
924
|
-
|
|
925
|
-
case 'reference':
|
|
926
|
-
// Reference fields must have a valid ID string
|
|
927
|
-
// ID length varies by workflow - check for non-empty string
|
|
928
|
-
return typeof value === 'string' && value.trim().length > 0;
|
|
929
|
-
|
|
930
|
-
case 'date':
|
|
931
|
-
// Valid ISO date or timestamp
|
|
932
|
-
return value && !isNaN(new Date(value).getTime());
|
|
933
|
-
|
|
934
|
-
case 'boolean':
|
|
935
|
-
// Keep false and true - both are valid
|
|
936
|
-
return typeof value === 'boolean';
|
|
937
|
-
|
|
938
|
-
default:
|
|
939
|
-
return shouldDisplayValue(value);
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
**Field-type specific examples:**
|
|
945
|
-
|
|
946
|
-
```typescript
|
|
947
|
-
// Numeric field: 0 is valid, undefined is empty
|
|
948
|
-
const co2Value = fields.co2_emissions?.value; // Could be 0, could be undefined
|
|
949
|
-
if (shouldDisplayValue(co2Value)) {
|
|
950
|
-
// Display: "0 kgCO2e" ✓
|
|
951
|
-
// Don't display: undefined/null/NaN ✓
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
// Text field: Empty string is empty, whitespace-only is empty
|
|
955
|
-
const description = fields.description?.value;
|
|
956
|
-
if (description && typeof description === 'string' && description.trim() !== '') {
|
|
957
|
-
// Display: "Sample text" ✓
|
|
958
|
-
// Don't display: "" or " " ✓
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Reference field: Check for non-empty string ID
|
|
962
|
-
const linkedActivityId = fields.linked_activity?.value;
|
|
963
|
-
if (typeof linkedActivityId === 'string' && linkedActivityId.trim().length > 0) {
|
|
964
|
-
// Display: "507f1f77bcf86cd799439011" ✓
|
|
965
|
-
// Don't display: "" or null ✓
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// Boolean field: Both true and false are valid values
|
|
969
|
-
const isActive = fields.active?.value;
|
|
970
|
-
if (typeof isActive === 'boolean') {
|
|
971
|
-
// Display: false ✓ (don't filter out)
|
|
972
|
-
// Don't display: null/undefined ✓
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// Function field returning formatted number: Keep meaningful zeros
|
|
976
|
-
const calculatedCost = fields.total_cost?.value; // Example: "0 €" or "150 €"
|
|
977
|
-
if (shouldDisplayValue(calculatedCost)) {
|
|
978
|
-
// Display: "0 €" if it's a calculated zero ✓
|
|
979
|
-
// But if underlying value was null, filtered function may still output placeholder
|
|
980
|
-
// → Use isValidValue() helper (above) for extra safety
|
|
981
|
-
}
|
|
982
|
-
```
|
|
983
|
-
|
|
984
|
-
**Real-world scenario from Spolia CO2 case:**
|
|
985
|
-
|
|
986
|
-
```typescript
|
|
987
|
-
// ❌ Bad: Filters out valid zeros
|
|
988
|
-
const fields = activity.fields.filter(f => f.value); // Loses 0 values!
|
|
989
|
-
|
|
990
|
-
// ❌ Also bad: Checks truthiness
|
|
991
|
-
if (activity.fields[fieldId]?.value) { // false for 0!
|
|
992
|
-
// Won't render CO2: 0 kgCO2e/yksikkö
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
// ✅ Good: Type-aware filtering
|
|
996
|
-
if (shouldDisplayValue(activity.fields[fieldId]?.value)) {
|
|
997
|
-
// Renders CO2: 0 kgCO2e/yksikkö ✓
|
|
998
|
-
// Skips CO2: undefined ✓
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// ✅ Better: Field-specific logic
|
|
1002
|
-
const co2 = activity.fields[CO2_FIELD_ID]?.value;
|
|
1003
|
-
if (co2 !== null && co2 !== undefined && !isNaN(Number(co2))) {
|
|
1004
|
-
doc.content.push({
|
|
1005
|
-
text: `CO2: ${co2} kgCO2e/yksikkö`
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
```
|
|
1009
|
-
|
|
1010
|
-
**Troubleshooting:**
|
|
1011
|
-
|
|
1012
|
-
| Symptom | Likely Cause | Fix |
|
|
1013
|
-
|---------|--------------|-----|
|
|
1014
|
-
| "0 € " values missing from PDF | Using truthiness check (if value) | Use `shouldDisplayValue()` |
|
|
1015
|
-
| Function field shows "−" or placeholder | Underlying data is null but function formatted it | Use `isValidValue()` helper |
|
|
1016
|
-
| Some zeros showing, some not | Inconsistent filter logic per field type | Use switch statement by fieldType |
|
|
1017
|
-
| NaN appearing in output | Not checking `isNaN()` for numbers | Add NaN check |
|
|
1018
|
-
| Whitespace-only text showing | Not trimming string values | Use `value.trim() !== ''` for text |
|
|
1019
|
-
|
|
1020
|
-
---
|
|
1021
|
-
|
|
1022
695
|
## Checklist
|
|
1023
696
|
|
|
1024
697
|
Before pushing template changes:
|