@hailer/mcp 1.0.29 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/.claude/.session-checked +1 -0
  2. package/.claude/agents/agent-ada-skill-builder.md +10 -2
  3. package/.claude/agents/agent-alejandro-function-fields.md +104 -37
  4. package/.claude/agents/agent-bjorn-config-audit.md +41 -21
  5. package/.claude/agents/agent-builder-agent-creator.md +13 -3
  6. package/.claude/agents/agent-code-simplifier.md +53 -0
  7. package/.claude/agents/agent-dmitri-activity-crud.md +126 -11
  8. package/.claude/agents/agent-giuseppe-app-builder.md +212 -22
  9. package/.claude/agents/agent-gunther-mcp-tools.md +7 -36
  10. package/.claude/agents/agent-helga-workflow-config.md +75 -10
  11. package/.claude/agents/agent-igor-activity-mover-automation.md +125 -0
  12. package/.claude/agents/agent-ingrid-doc-templates.md +164 -36
  13. package/.claude/agents/agent-ivan-monolith.md +154 -0
  14. package/.claude/agents/agent-kenji-data-reader.md +15 -8
  15. package/.claude/agents/agent-lars-code-inspector.md +56 -8
  16. package/.claude/agents/agent-marco-mockup-builder.md +110 -0
  17. package/.claude/agents/agent-marcus-api-documenter.md +323 -0
  18. package/.claude/agents/agent-marketplace-publisher.md +232 -72
  19. package/.claude/agents/agent-marketplace-reviewer.md +255 -79
  20. package/.claude/agents/agent-permissions-handler.md +208 -0
  21. package/.claude/agents/agent-simple-writer.md +48 -0
  22. package/.claude/agents/agent-svetlana-code-review.md +127 -14
  23. package/.claude/agents/agent-tanya-test-runner.md +333 -0
  24. package/.claude/agents/agent-ui-designer.md +100 -0
  25. package/.claude/agents/agent-viktor-sql-insights.md +19 -6
  26. package/.claude/agents/agent-web-search.md +55 -0
  27. package/.claude/agents/agent-yevgeni-discussions.md +7 -1
  28. package/.claude/agents/agent-zara-zapier.md +159 -0
  29. package/.claude/commands/app-squad.md +135 -0
  30. package/.claude/commands/audit-squad.md +158 -0
  31. package/.claude/commands/autoplan.md +563 -0
  32. package/.claude/commands/cleanup-squad.md +98 -0
  33. package/.claude/commands/config-squad.md +106 -0
  34. package/.claude/commands/crud-squad.md +87 -0
  35. package/.claude/commands/data-squad.md +97 -0
  36. package/.claude/commands/debug-squad.md +303 -0
  37. package/.claude/commands/doc-squad.md +65 -0
  38. package/.claude/commands/handoff.md +137 -0
  39. package/.claude/commands/health.md +49 -0
  40. package/.claude/commands/help.md +2 -1
  41. package/.claude/commands/help:agents.md +96 -16
  42. package/.claude/commands/help:commands.md +55 -11
  43. package/.claude/commands/help:faq.md +16 -1
  44. package/.claude/commands/help:skills.md +93 -0
  45. package/.claude/commands/hotfix-squad.md +112 -0
  46. package/.claude/commands/integration-squad.md +82 -0
  47. package/.claude/commands/janitor-squad.md +167 -0
  48. package/.claude/commands/learn-auto.md +120 -0
  49. package/.claude/commands/learn.md +120 -0
  50. package/.claude/commands/mcp-list.md +27 -0
  51. package/.claude/commands/onboard-squad.md +140 -0
  52. package/.claude/commands/plan-workspace.md +732 -0
  53. package/.claude/commands/prd.md +131 -0
  54. package/.claude/commands/project-status.md +82 -0
  55. package/.claude/commands/publish.md +138 -0
  56. package/.claude/commands/recap.md +69 -0
  57. package/.claude/commands/restore.md +64 -0
  58. package/.claude/commands/review-squad.md +152 -0
  59. package/.claude/commands/save.md +24 -0
  60. package/.claude/commands/stats.md +19 -0
  61. package/.claude/commands/swarm.md +210 -0
  62. package/.claude/commands/tool-builder.md +3 -1
  63. package/.claude/commands/ws-pull.md +1 -1
  64. package/.claude/commands/yolo-off.md +17 -0
  65. package/.claude/commands/yolo.md +82 -0
  66. package/.claude/hooks/_shared-memory.cjs +305 -0
  67. package/.claude/hooks/_utils.cjs +134 -0
  68. package/.claude/hooks/agent-failure-detector.cjs +164 -79
  69. package/.claude/hooks/agent-usage-logger.cjs +204 -0
  70. package/.claude/hooks/app-edit-guard.cjs +20 -4
  71. package/.claude/hooks/auto-learn.cjs +316 -0
  72. package/.claude/hooks/bash-guard.cjs +282 -0
  73. package/.claude/hooks/builder-mode-manager.cjs +183 -54
  74. package/.claude/hooks/bulk-activity-guard.cjs +283 -0
  75. package/.claude/hooks/context-watchdog.cjs +292 -0
  76. package/.claude/hooks/delegation-reminder.cjs +478 -0
  77. package/.claude/hooks/design-system-lint.cjs +283 -0
  78. package/.claude/hooks/post-scaffold-hook.cjs +16 -3
  79. package/.claude/hooks/prompt-guard.cjs +366 -0
  80. package/.claude/hooks/publish-template-guard.cjs +16 -0
  81. package/.claude/hooks/session-start.cjs +35 -0
  82. package/.claude/hooks/shared-memory-writer.cjs +147 -0
  83. package/.claude/hooks/skill-injector.cjs +140 -0
  84. package/.claude/hooks/skill-usage-logger.cjs +258 -0
  85. package/.claude/hooks/src-edit-guard.cjs +16 -1
  86. package/.claude/hooks/sync-marketplace-agents.cjs +53 -8
  87. package/.claude/scripts/yolo-toggle.cjs +142 -0
  88. package/.claude/settings.json +141 -14
  89. package/.claude/skills/SDK-activity-patterns/SKILL.md +428 -0
  90. package/.claude/skills/SDK-document-templates/SKILL.md +1033 -0
  91. package/.claude/skills/SDK-function-fields/SKILL.md +542 -0
  92. package/.claude/skills/SDK-generate-skill/SKILL.md +92 -0
  93. package/.claude/skills/SDK-init-skill/SKILL.md +127 -0
  94. package/.claude/skills/SDK-insight-queries/SKILL.md +787 -0
  95. package/.claude/skills/SDK-ws-config-skill/SKILL.md +1139 -0
  96. package/.claude/skills/agent-structure/SKILL.md +98 -0
  97. package/.claude/skills/api-documentation-patterns/SKILL.md +474 -0
  98. package/.claude/skills/chrome-mcp-reference/SKILL.md +370 -0
  99. package/.claude/skills/delegation-routing/SKILL.md +202 -0
  100. package/.claude/skills/frontend-design/SKILL.md +254 -0
  101. package/.claude/skills/hailer-activity-mover/SKILL.md +213 -0
  102. package/.claude/skills/hailer-api-client/SKILL.md +518 -0
  103. package/.claude/skills/hailer-app-builder/SKILL.md +939 -11
  104. package/.claude/skills/hailer-apps-pictures/SKILL.md +269 -0
  105. package/.claude/skills/hailer-design-system/SKILL.md +235 -0
  106. package/.claude/skills/hailer-monolith-automations/SKILL.md +686 -0
  107. package/.claude/skills/hailer-permissions-system/SKILL.md +121 -0
  108. package/.claude/skills/hailer-project-protocol/SKILL.md +488 -0
  109. package/.claude/skills/hailer-rest-api/SKILL.md +61 -0
  110. package/.claude/skills/hailer-rest-api/hailer-activities.md +184 -0
  111. package/.claude/skills/hailer-rest-api/hailer-admin.md +473 -0
  112. package/.claude/skills/hailer-rest-api/hailer-calendar.md +256 -0
  113. package/.claude/skills/hailer-rest-api/hailer-feed.md +249 -0
  114. package/.claude/skills/hailer-rest-api/hailer-insights.md +195 -0
  115. package/.claude/skills/hailer-rest-api/hailer-messaging.md +276 -0
  116. package/.claude/skills/hailer-rest-api/hailer-workflows.md +283 -0
  117. package/.claude/skills/insight-join-patterns/SKILL.md +3 -0
  118. package/.claude/skills/integration-patterns/SKILL.md +421 -0
  119. package/.claude/skills/json-only-output/SKILL.md +52 -12
  120. package/.claude/skills/lsp-setup/SKILL.md +160 -0
  121. package/.claude/skills/mcp-direct-tools/SKILL.md +153 -0
  122. package/.claude/skills/optional-parameters/SKILL.md +32 -23
  123. package/.claude/skills/publish-hailer-app/SKILL.md +76 -12
  124. package/.claude/skills/testing-patterns/SKILL.md +630 -0
  125. package/.claude/skills/tool-builder/SKILL.md +250 -0
  126. package/.claude/skills/tool-parameter-usage/SKILL.md +59 -45
  127. package/.claude/skills/tool-response-verification/SKILL.md +82 -48
  128. package/.claude/skills/zapier-hailer-patterns/SKILL.md +581 -0
  129. package/.env.example +26 -7
  130. package/CLAUDE.md +290 -224
  131. package/dist/CLAUDE.md +370 -0
  132. package/dist/app.d.ts +1 -1
  133. package/dist/app.js +101 -101
  134. package/dist/bot/bot-config.d.ts +26 -0
  135. package/dist/bot/bot-config.js +135 -0
  136. package/dist/bot/bot-manager.d.ts +40 -0
  137. package/dist/bot/bot-manager.js +137 -0
  138. package/dist/bot/bot.d.ts +127 -0
  139. package/dist/bot/bot.js +1328 -0
  140. package/dist/bot/operation-logger.d.ts +28 -0
  141. package/dist/bot/operation-logger.js +132 -0
  142. package/dist/bot/services/conversation-manager.d.ts +60 -0
  143. package/dist/bot/services/conversation-manager.js +246 -0
  144. package/dist/bot/services/index.d.ts +9 -0
  145. package/dist/bot/services/index.js +18 -0
  146. package/dist/bot/services/message-classifier.d.ts +42 -0
  147. package/dist/bot/services/message-classifier.js +228 -0
  148. package/dist/bot/services/message-formatter.d.ts +88 -0
  149. package/dist/bot/services/message-formatter.js +411 -0
  150. package/dist/bot/services/session-logger.d.ts +162 -0
  151. package/dist/bot/services/session-logger.js +724 -0
  152. package/dist/bot/services/token-billing.d.ts +78 -0
  153. package/dist/bot/services/token-billing.js +233 -0
  154. package/dist/bot/services/types.d.ts +169 -0
  155. package/dist/bot/services/types.js +12 -0
  156. package/dist/bot/services/typing-indicator.d.ts +23 -0
  157. package/dist/bot/services/typing-indicator.js +60 -0
  158. package/dist/bot/services/workspace-schema-cache.d.ts +122 -0
  159. package/dist/bot/services/workspace-schema-cache.js +506 -0
  160. package/dist/bot/tool-executor.d.ts +28 -0
  161. package/dist/bot/tool-executor.js +48 -0
  162. package/dist/bot/workspace-overview.d.ts +12 -0
  163. package/dist/bot/workspace-overview.js +94 -0
  164. package/dist/cli.d.ts +1 -8
  165. package/dist/cli.js +1 -253
  166. package/dist/config.d.ts +96 -3
  167. package/dist/config.js +148 -37
  168. package/dist/core.d.ts +5 -0
  169. package/dist/core.js +61 -8
  170. package/dist/lib/discussion-lock.d.ts +42 -0
  171. package/dist/lib/discussion-lock.js +110 -0
  172. package/dist/lib/logger.d.ts +0 -1
  173. package/dist/lib/logger.js +39 -23
  174. package/dist/lib/request-logger.d.ts +77 -0
  175. package/dist/lib/request-logger.js +147 -0
  176. package/dist/mcp/UserContextCache.js +16 -13
  177. package/dist/mcp/hailer-clients.js +18 -17
  178. package/dist/mcp/signal-handler.js +29 -13
  179. package/dist/mcp/tool-registry.d.ts +4 -15
  180. package/dist/mcp/tool-registry.js +94 -32
  181. package/dist/mcp/tools/activity.js +28 -69
  182. package/dist/mcp/tools/app-core.js +9 -4
  183. package/dist/mcp/tools/app-marketplace.js +22 -12
  184. package/dist/mcp/tools/app-member.js +5 -2
  185. package/dist/mcp/tools/app-scaffold.js +32 -18
  186. package/dist/mcp/tools/bot-config/constants.d.ts +23 -0
  187. package/dist/mcp/tools/bot-config/constants.js +94 -0
  188. package/dist/mcp/tools/bot-config/core.d.ts +253 -0
  189. package/dist/mcp/tools/bot-config/core.js +2456 -0
  190. package/dist/mcp/tools/bot-config/index.d.ts +10 -0
  191. package/dist/mcp/tools/bot-config/index.js +59 -0
  192. package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
  193. package/dist/mcp/tools/bot-config/tools.js +15 -0
  194. package/dist/mcp/tools/bot-config/types.d.ts +50 -0
  195. package/dist/mcp/tools/bot-config/types.js +6 -0
  196. package/dist/mcp/tools/discussion.js +107 -77
  197. package/dist/mcp/tools/document.d.ts +11 -0
  198. package/dist/mcp/tools/document.js +741 -0
  199. package/dist/mcp/tools/file.js +5 -2
  200. package/dist/mcp/tools/insight.js +36 -12
  201. package/dist/mcp/tools/investigate.d.ts +9 -0
  202. package/dist/mcp/tools/investigate.js +254 -0
  203. package/dist/mcp/tools/user.d.ts +2 -4
  204. package/dist/mcp/tools/user.js +9 -50
  205. package/dist/mcp/tools/workflow.d.ts +1 -0
  206. package/dist/mcp/tools/workflow.js +164 -52
  207. package/dist/mcp/utils/hailer-api-client.js +26 -17
  208. package/dist/mcp/webhook-handler.d.ts +64 -3
  209. package/dist/mcp/webhook-handler.js +219 -9
  210. package/dist/mcp-server.d.ts +4 -0
  211. package/dist/mcp-server.js +237 -25
  212. package/dist/plugins/bug-fixer/index.d.ts +2 -0
  213. package/dist/plugins/bug-fixer/index.js +18 -0
  214. package/dist/plugins/bug-fixer/tools.d.ts +45 -0
  215. package/dist/plugins/bug-fixer/tools.js +1096 -0
  216. package/package.json +10 -10
  217. package/scripts/test-hal-tools.ts +154 -0
  218. package/.claude/agents/agent-nora-name-functions.md +0 -123
  219. package/.claude/assistant-knowledge.md +0 -23
  220. package/.claude/commands/install-plugin.md +0 -261
  221. package/.claude/commands/list-plugins.md +0 -42
  222. package/.claude/commands/marketplace-setup.md +0 -33
  223. package/.claude/commands/publish-plugin.md +0 -55
  224. package/.claude/commands/uninstall-plugin.md +0 -87
  225. package/.claude/hooks/interactive-mode.cjs +0 -87
  226. package/.claude/hooks/mcp-server-guard.cjs +0 -108
  227. package/.claude/skills/marketplace-publishing.md +0 -155
  228. package/dist/bot/chat-bot.d.ts +0 -31
  229. package/dist/bot/chat-bot.js +0 -357
  230. package/dist/mcp/tools/metrics.d.ts +0 -13
  231. package/dist/mcp/tools/metrics.js +0 -546
  232. package/dist/stdio-server.d.ts +0 -14
  233. package/dist/stdio-server.js +0 -114
@@ -1,12 +1,95 @@
1
1
  ---
2
2
  name: hailer-app-builder
3
3
  description: Patterns for building Hailer apps with @hailer/app-sdk
4
+ version: 1.3.1
5
+ triggers:
6
+ - build app
7
+ - hailer app
8
+ - app sdk
4
9
  ---
5
10
 
6
11
  # Hailer App Builder Skill
7
12
 
8
13
  Patterns and templates for building Hailer apps with @hailer/app-sdk.
9
14
 
15
+ <critical-rules>
16
+ ## CRITICAL: Scaffolding and Data Sources
17
+
18
+ **ALWAYS use scaffold_hailer_app MCP tool** to create new apps. Never manually create the project structure.
19
+
20
+ ### scaffold_hailer_app - One-Shot Full Setup
21
+
22
+ This tool does EVERYTHING in one call:
23
+ - Scaffolds project from template (Vite + React + TypeScript)
24
+ - Runs `npm install`
25
+ - Configures CORS in `vite.config.ts`
26
+ - **Creates dev app entry in Hailer** (with auto-generated icon)
27
+ - Shares app with entire workspace
28
+ - Adds appId to `manifest.json`
29
+ - Starts dev server on port 3000
30
+
31
+ **You're customizing a working starter app**, not building from scratch.
32
+
33
+ ### create_app - Entry Only (No Local Files)
34
+
35
+ Use `mcp__hailer__create_app` when you:
36
+ - Need a production app entry pointing to a deployed URL
37
+ - Want to register an external/existing app in Hailer
38
+ - Already have app code and just need the Hailer entry
39
+
40
+ ```
41
+ mcp__hailer__create_app({
42
+ name: "Production App",
43
+ url: "https://app.example.com"
44
+ })
45
+ ```
46
+
47
+ **scaffold = full development setup**
48
+ **create_app = just the Hailer entry/frame**
49
+
50
+ **For project data structure (workflows, fields, phases):**
51
+ - READ workspace/ TypeScript files directly (fields.ts, phases.ts, enums.ts)
52
+ - Do NOT use MCP tools for data structure queries
53
+ - The SDK pull provides all needed type information locally
54
+
55
+ ```typescript
56
+ // For app constants, read from local workspace files:
57
+ // - workspace/enums.ts (IDs)
58
+ // - workspace/[Workflow]_[id]/fields.ts (field definitions)
59
+ // - workspace/[Workflow]_[id]/phases.ts (phase definitions)
60
+ ```
61
+ </critical-rules>
62
+
63
+ <local-dev-flow>
64
+ ## Development Flow
65
+
66
+ **Default: Local development.** `scaffold_hailer_app` handles everything automatically:
67
+ 1. Creates local project files
68
+ 2. Creates a dev app entry in Hailer at `http://localhost:3000`
69
+ 3. Shares the app with the workspace
70
+ 4. Starts the dev server
71
+
72
+ After scaffolding, run `npm run dev` and test inside Hailer iframe.
73
+
74
+ **Publishing: Only when user explicitly asks.** Load the `publish-hailer-app` skill, which covers manifest validation, file upload via `publish_hailer_app`, and URL switch from localhost to production via `update_app`.
75
+
76
+ ### Manual Local Dev App (Rare Cases)
77
+
78
+ Only needed if:
79
+ - You have existing code without a dev app entry
80
+ - The scaffold's dev app was deleted
81
+
82
+ ```
83
+ mcp__hailer__create_app({
84
+ name: "Local Dev",
85
+ url: "http://localhost:3000",
86
+ description: "Local development testing"
87
+ })
88
+ ```
89
+
90
+ Or manually in Hailer UI: Apps → Create App → URL: http://localhost:3000
91
+ </local-dev-flow>
92
+
10
93
  <sdk-setup>
11
94
  ## Hook Import (CRITICAL)
12
95
 
@@ -37,40 +120,400 @@ function App() {
37
120
  ```
38
121
  </sdk-setup>
39
122
 
123
+ <usehailer-fix>
124
+ ## CRITICAL: Replace Scaffold's useHailer Hook
125
+
126
+ **The scaffold generates a buggy useHailer hook.** After scaffolding, ALWAYS replace `src/hailer/use-hailer.ts` with this shared-state implementation:
127
+
128
+ ### Why the Scaffold's Hook is Broken
129
+
130
+ The scaffold creates a hook using per-component `useState`:
131
+
132
+ ```typescript
133
+ // ❌ BUGGY - each component gets its own state
134
+ function useHailer() {
135
+ const [inside, setInside] = useState(false); // Each component gets separate copy!
136
+
137
+ useEffect(() => {
138
+ hailer.init({ config: () => setInside(true) }); // Only updates THIS component
139
+ }, []);
140
+
141
+ return { inside, hailer };
142
+ }
143
+ ```
144
+
145
+ **Result:** App.tsx sees `inside: true`, but child pages (Dashboard, Settings) still see `inside: false`.
146
+
147
+ ### The Fix: Shared State with useSyncExternalStore
148
+
149
+ Replace `src/hailer/use-hailer.ts` with:
150
+
151
+ ```typescript
152
+ import { useSyncExternalStore } from 'react';
153
+ import HailerApi from '@hailer/app-sdk';
154
+
155
+ // Types
156
+ interface HailerState {
157
+ inside: boolean;
158
+ hailer: ReturnType<typeof HailerApi> | null;
159
+ config: Record<string, unknown> | null;
160
+ }
161
+
162
+ declare global {
163
+ interface Window {
164
+ __hailerStore?: {
165
+ state: HailerState;
166
+ listeners: Set<() => void>;
167
+ subscribe: (listener: () => void) => () => void;
168
+ getSnapshot: () => HailerState;
169
+ setState: (newState: Partial<HailerState>) => void;
170
+ };
171
+ }
172
+ }
173
+
174
+ // Initialize store once on window
175
+ function getStore() {
176
+ if (!window.__hailerStore) {
177
+ window.__hailerStore = {
178
+ state: { inside: false, hailer: null, config: null },
179
+ listeners: new Set(),
180
+ subscribe(listener) {
181
+ this.listeners.add(listener);
182
+ return () => this.listeners.delete(listener);
183
+ },
184
+ getSnapshot() {
185
+ return this.state;
186
+ },
187
+ setState(newState) {
188
+ this.state = { ...this.state, ...newState };
189
+ this.listeners.forEach((l) => l());
190
+ },
191
+ };
192
+
193
+ // Initialize SDK once
194
+ const api = HailerApi({
195
+ config: (inside, cfg) => { // SDK passes (inside: boolean, config: object)
196
+ window.__hailerStore!.setState({
197
+ inside: inside,
198
+ config: cfg?.fields ?? null,
199
+ });
200
+ },
201
+ error: (err) => {
202
+ console.error('Hailer SDK error:', err);
203
+ },
204
+ });
205
+ window.__hailerStore.setState({ hailer: api });
206
+ }
207
+ return window.__hailerStore;
208
+ }
209
+
210
+ export default function useHailer() {
211
+ const store = getStore();
212
+ const state = useSyncExternalStore(
213
+ store.subscribe.bind(store),
214
+ store.getSnapshot.bind(store)
215
+ );
216
+
217
+ return {
218
+ inside: state.inside,
219
+ hailer: state.hailer!,
220
+ config: state.config,
221
+ };
222
+ }
223
+ ```
224
+
225
+ ### Why This Works
226
+
227
+ 1. **Single store on `window`** - All components share one source of truth
228
+ 2. **SDK initialized once** - No duplicate callbacks
229
+ 3. **useSyncExternalStore** - React 18's official pattern for external state
230
+ 4. **All components update together** - When `inside` changes, every subscriber re-renders
231
+
232
+ ### Giuseppe Rule
233
+
234
+ After `scaffold_hailer_app`, ALWAYS replace `src/hailer/use-hailer.ts` with the shared-state version above.
235
+ </usehailer-fix>
236
+
40
237
  <sdk-api>
41
238
  ## Activity API
42
239
 
43
240
  ```typescript
44
241
  // List activities from workflow phase
45
- const activities = await hailer.activity.list(workflowId, phaseId, {
46
- limit: 100,
47
- fields: ['fieldId1', 'fieldId2'], // Optional: specific fields only
48
- });
242
+ const activities = await hailer.activity.list(workflowId, phaseId, options?);
49
243
 
50
244
  // Get single activity
51
245
  const activity = await hailer.activity.get(activityId);
52
246
 
53
- // Activity structure
247
+ // Create activities
248
+ const created = await hailer.activity.create(workflowId, activities[], options?);
249
+
250
+ // Update activities (returns count)
251
+ const count = await hailer.activity.update(activities[], options);
252
+
253
+ // Remove activities (returns count)
254
+ const count = await hailer.activity.remove(activityIds[]);
255
+ ```
256
+
257
+ ### ActivityListOptions
258
+
259
+ ```typescript
260
+ interface ActivityListOptions {
261
+ sortBy?: 'name' | 'created' | 'updated' | 'following' | 'owner' | 'team' | 'completedOn' | 'priority';
262
+ sortOrder?: 'asc' | 'desc';
263
+ limit?: number;
264
+ skip?: number;
265
+ includeUsers?: boolean;
266
+ includeTeams?: boolean;
267
+ includeHistory?: boolean;
268
+ filters?: any;
269
+ }
270
+ ```
271
+
272
+ ### ActivityCreateOptions
273
+
274
+ ```typescript
275
+ interface ActivityCreateOptions {
276
+ teamId?: string; // Assign to team
277
+ fileIds?: string[]; // Attach files (use ui.files.uploadFile first)
278
+ followerIds?: string[]; // Add followers
279
+ location?: {
280
+ label?: string;
281
+ type: 'area' | 'point' | 'polyline';
282
+ data: [{ lat: number; lng: number }];
283
+ };
284
+ discussionId?: string; // Link to discussion
285
+ phaseId?: string; // Initial phase (defaults to first)
286
+ returnDocument?: boolean; // Return full activity after create
287
+ ignoreRequired?: boolean; // Skip required field validation
288
+ }
289
+ ```
290
+
291
+ ### Activity Interface
292
+
293
+ ```typescript
54
294
  interface Activity {
55
295
  _id: string;
56
296
  name: string;
297
+ process: string;
298
+ currentPhase: string;
57
299
  fields?: Record<string, { value: unknown }>;
300
+ files?: string[];
301
+ followers?: string[];
58
302
  created?: number;
59
303
  updated?: number;
304
+ updatedBy?: string;
305
+ priority?: number;
306
+ location?: {
307
+ type: 'point' | 'area' | 'polyline';
308
+ label: string | null;
309
+ data: Array<{ lat: number; lng: number }>;
310
+ };
311
+ }
312
+ ```
313
+
314
+ ### Phase Transitions (Moving Activities Between Phases)
315
+
316
+ **CRITICAL:** The SDK does NOT have a `hailer.activity.move()` method. To move activities between phases, use `hailer.activity.update()` with the `phaseId` parameter.
317
+
318
+ ```typescript
319
+ // CORRECT - Use activity.update() with phaseId
320
+ await hailer.activity.update([
321
+ {
322
+ _id: activityId,
323
+ phaseId: newPhaseId, // Move to different phase
324
+ },
325
+ ], {});
326
+
327
+ // ❌ WRONG - activity.move() DOES NOT EXIST
328
+ // await hailer.activity.move(activityId, newPhaseId); // This method doesn't exist in the SDK!
329
+ ```
330
+
331
+ **Example: Move multiple activities to new phase**
332
+ ```typescript
333
+ const activityIds = ['id1', 'id2', 'id3'];
334
+ const targetPhaseId = 'phaseId789';
335
+
336
+ await hailer.activity.update(
337
+ activityIds.map(id => ({
338
+ _id: id,
339
+ phaseId: targetPhaseId,
340
+ })),
341
+ {}
342
+ );
343
+ ```
344
+
345
+ ### CRITICAL: activity.list() Requires Valid phaseId
346
+
347
+ **Problem:** `hailer.activity.list(workflowId, '', options)` fails - empty phaseId not allowed.
348
+
349
+ **Solution:** Query all phases in parallel:
350
+ ```typescript
351
+ const ALL_PHASES = ['phaseId1', 'phaseId2', 'phaseId3'];
352
+
353
+ const phaseResults = await Promise.all(
354
+ ALL_PHASES.map((phaseId) =>
355
+ hailer.activity.list(workflowId, phaseId, { limit: 500 }).catch(() => [])
356
+ )
357
+ );
358
+ const allActivities = phaseResults.flat();
359
+ ```
360
+
361
+ ### Phase Selection: Which Phases to Fetch
362
+
363
+ **Don't blindly fetch all phases.** Consider what the user needs to see:
364
+
365
+ | App Type | Phases to Fetch | Rationale |
366
+ |----------|-----------------|-----------|
367
+ | Sales dashboard | Active only | Don't show internal drafts |
368
+ | Product manager view | Draft + Active | Need to see work-in-progress |
369
+ | Archive browser | Archived only | Historical data |
370
+ | Kanban board | All except Archived | Full workflow visibility |
371
+
372
+ **Example: Role-based phase selection**
373
+ ```typescript
374
+ // Define phases per user role
375
+ const PHASE_CONFIG = {
376
+ sales: ['activePhaseId'],
377
+ manager: ['draftPhaseId', 'activePhaseId'],
378
+ admin: ['draftPhaseId', 'activePhaseId', 'archivedPhaseId'],
379
+ };
380
+
381
+ // Fetch based on role
382
+ const userRole = 'sales'; // from app config or user check
383
+ const phases = PHASE_CONFIG[userRole] || PHASE_CONFIG.sales;
384
+
385
+ const results = await Promise.all(
386
+ phases.map(phaseId => hailer.activity.list(workflowId, phaseId))
387
+ );
388
+ ```
389
+
390
+ **Document phase selection in PRD:**
391
+ ```markdown
392
+ ## Data Access
393
+ - **Product Browser**: Shows Draft + Active phases (managers need WIP visibility)
394
+ - **Public Dashboard**: Active only (no internal data exposed)
395
+ - **Insights**: Active only (accurate counts, no duplicates from drafts)
396
+ ```
397
+
398
+ ## Kanban API
399
+
400
+ ```typescript
401
+ // List activities grouped by phase (kanban view)
402
+ const kanban = await hailer.activity.kanban.list(workflowId, options?);
403
+ // Returns: { map: { [phaseId]: { activities: [...], meta: { count, ... } } } }
404
+
405
+ // Load single activity in kanban format
406
+ const item = await hailer.activity.kanban.load(activityId);
407
+ // Returns: { activity: {...}, process: string, phase: string }
408
+
409
+ // Update activity priority
410
+ await hailer.activity.kanban.updatePriority(activityId, priority);
411
+ ```
412
+
413
+ ### KanbanListOptions
414
+
415
+ ```typescript
416
+ interface ActivityKanbanListOptions {
417
+ filter: {
418
+ user?: { uid: string; field: string };
419
+ account?: string;
420
+ team?: string;
421
+ dates?: { field: string; start: number; end: number };
422
+ };
423
+ search?: string;
424
+ limit?: number;
425
+ skip?: number;
426
+ phase?: string;
427
+ includeUsers?: boolean;
428
+ includeTeams?: boolean;
60
429
  }
61
430
  ```
62
431
 
432
+ ## UI API
433
+
434
+ ### Activity UI
435
+
436
+ ```typescript
437
+ // Open activity in sidebar
438
+ await hailer.ui.activity.open(activityId, options?);
439
+
440
+ type ActivityTabTypes = 'detail' | 'discussion' | 'files' | 'location' | 'linkedFrom' | 'options';
441
+
442
+ // Open with specific tab
443
+ await hailer.ui.activity.open(activityId, { tab: 'files' });
444
+
445
+ // Open create form (returns created activity or null if cancelled)
446
+ const result = await hailer.ui.activity.create(workflowId, {
447
+ name?: string,
448
+ fields?: { [fieldId]: value },
449
+ location?: { type: 'point', data: [{ lat, lng }] }
450
+ });
451
+
452
+ // Bulk edit multiple activities
453
+ await hailer.ui.activity.editMultiple(activityIds[]);
454
+ ```
455
+
456
+ **Note:** `hailer.openSidebar()` does NOT exist - use `hailer.ui.activity.open()`.
457
+
458
+ ### File Upload
459
+
460
+ ```typescript
461
+ // Upload file to Hailer (returns file ID)
462
+ const fileId = await hailer.ui.files.uploadFile(
463
+ file, // File object from <input type="file">
464
+ filename, // Desired filename
465
+ { isPublic?: boolean }
466
+ );
467
+
468
+ // Then attach to activity on create:
469
+ await hailer.activity.create(workflowId, [{ name: 'Doc' }], { fileIds: [fileId] });
470
+ ```
471
+
472
+ ### Snackbar (Toast Notifications)
473
+
474
+ ```typescript
475
+ // Show notification
476
+ await hailer.ui.snackbar.open(text, buttonLabel, duration?);
477
+
478
+ // Examples
479
+ hailer.ui.snackbar.open('Saved!', 'OK');
480
+ hailer.ui.snackbar.open('Deleted', 'Undo', 5000);
481
+ ```
482
+
483
+ ### Insight UI
484
+
485
+ ```typescript
486
+ await hailer.ui.insight.create(workspaceId?); // Open create dialog
487
+ await hailer.ui.insight.edit(insightId); // Open edit dialog
488
+ await hailer.ui.insight.delete(insightId); // Open delete confirmation
489
+ await hailer.ui.insight.permission(insightId); // Open permissions dialog
490
+ ```
491
+
63
492
  ## Insight API
64
493
 
65
494
  ```typescript
66
495
  // Get insight data (SQL query results)
67
- const data = await hailer.insight.get(insightId, { update: true });
496
+ const data = await hailer.insight.data(insightId, { update?: true });
497
+
498
+ // List all insights
499
+ const insights = await hailer.insight.list();
68
500
 
69
- // Response structure
70
- interface InsightResponse {
501
+ // Update insight
502
+ const updated = await hailer.insight.update(insightId, partialUpdate);
503
+ ```
504
+
505
+ **Response structure:**
506
+ ```typescript
507
+ interface InsightData {
71
508
  columns: string[];
72
509
  rows: any[][];
73
510
  }
511
+
512
+ interface InsightDoc {
513
+ _id: string;
514
+ name: string;
515
+ // ... other insight properties
516
+ }
74
517
  ```
75
518
 
76
519
  ## Workflow API
@@ -79,9 +522,58 @@ interface InsightResponse {
79
522
  // List all workflows
80
523
  const workflows = await hailer.workflow.list();
81
524
 
82
- // Get single workflow
525
+ // Get single workflow with full schema
83
526
  const workflow = await hailer.workflow.get(workflowId);
84
527
  ```
528
+
529
+ ## User API
530
+
531
+ ```typescript
532
+ // Get current logged-in user
533
+ const me = await hailer.user.current();
534
+ // Returns: { _id, email, firstname, lastname, ... }
535
+
536
+ // Get specific user by ID
537
+ const user = await hailer.user.get(userId);
538
+
539
+ // List all workspace users (returns object keyed by user ID)
540
+ const users = await hailer.user.list();
541
+ ```
542
+
543
+ ### Getting User Info for Personalized Greeting
544
+
545
+ **The config callback does NOT include user info:**
546
+ ```typescript
547
+ // ❌ WRONG - config only has { fields: {} }
548
+ const { config } = useHailer();
549
+ const userName = config?.userName; // undefined!
550
+ ```
551
+
552
+ **Use hailer.user.current() instead:**
553
+ ```typescript
554
+ const { inside, hailer } = useHailer();
555
+ const [userName, setUserName] = useState<string>('');
556
+
557
+ useEffect(() => {
558
+ if (!inside) return;
559
+
560
+ hailer.user.current().then(user => {
561
+ setUserName(user.firstname || 'there');
562
+ });
563
+ }, [inside]);
564
+
565
+ return <Heading>Hello, {userName}!</Heading>;
566
+ ```
567
+
568
+ ## Workspace API
569
+
570
+ ```typescript
571
+ // Get current workspace
572
+ const workspace = await hailer.workspace.current();
573
+
574
+ // List workspaces (personal apps only)
575
+ const workspaces = await hailer.workspace.list();
576
+ ```
85
577
  </sdk-api>
86
578
 
87
579
  <field-patterns>
@@ -121,12 +613,14 @@ const formatted = new Date(date).toLocaleDateString();
121
613
  const status = activity.fields?.['fieldId']?.value as string;
122
614
 
123
615
  // ActivityLink field (reference to another activity)
616
+ // IMPORTANT: ActivityLink has nested structure, not direct values
124
617
  interface ActivityLinkValue {
125
618
  _id: string;
126
619
  name: string;
127
620
  }
128
621
  const linked = activity.fields?.['fieldId']?.value as ActivityLinkValue;
129
- const linkedName = linked?.name || 'Unknown';
622
+ const linkedId = linked?._id; // Get linked activity ID
623
+ const linkedName = linked?.name || 'Unknown'; // Get display name
130
624
 
131
625
  // User field
132
626
  interface UserValue {
@@ -137,6 +631,59 @@ interface UserValue {
137
631
  const user = activity.fields?.['fieldId']?.value as UserValue;
138
632
  const userName = user ? `${user.firstname} ${user.lastname}` : 'Unknown';
139
633
  ```
634
+
635
+ ## Finnish Date Parsing
636
+
637
+ Insights and some fields return Finnish date strings (`dd.mm.yyyy`) instead of timestamps. These can't be sorted directly.
638
+
639
+ ```typescript
640
+ // ❌ WRONG - Number("03.02.2026") returns NaN
641
+ dates.sort((a, b) => Number(a) - Number(b));
642
+
643
+ // ✅ CORRECT - Parse Finnish dates to Date objects
644
+ function parseFinnishDate(dateStr: string): Date | null {
645
+ // Matches "03.02.2026" or "03.02.2026 10:00"
646
+ const match = dateStr.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?/);
647
+ if (!match) return null;
648
+
649
+ const [, day, month, year, hours = '0', minutes = '0'] = match;
650
+ return new Date(
651
+ parseInt(year),
652
+ parseInt(month) - 1, // JS months are 0-indexed
653
+ parseInt(day),
654
+ parseInt(hours),
655
+ parseInt(minutes)
656
+ );
657
+ }
658
+
659
+ // Sorting Finnish dates
660
+ items.sort((a, b) => {
661
+ const dateA = parseFinnishDate(a.dateField);
662
+ const dateB = parseFinnishDate(b.dateField);
663
+ if (!dateA || !dateB) return 0;
664
+ return dateA.getTime() - dateB.getTime();
665
+ });
666
+
667
+ // Formatting back to Finnish
668
+ function formatFinnishDate(date: Date): string {
669
+ return date.toLocaleDateString('fi-FI'); // "3.2.2026"
670
+ }
671
+
672
+ function formatFinnishDateTime(date: Date): string {
673
+ return date.toLocaleString('fi-FI', {
674
+ day: 'numeric',
675
+ month: 'numeric',
676
+ year: 'numeric',
677
+ hour: '2-digit',
678
+ minute: '2-digit',
679
+ }); // "3.2.2026 klo 10.00"
680
+ }
681
+ ```
682
+
683
+ **When to use:**
684
+ - Insight data returns formatted dates (not timestamps)
685
+ - Text fields containing dates
686
+ - Displaying dates to Finnish users
140
687
  </field-patterns>
141
688
 
142
689
  <component-templates>
@@ -372,7 +919,7 @@ export default App;
372
919
  ## Hailer Theme Colors
373
920
 
374
921
  ```typescript
375
- // Dark mode support
922
+ // Dark mode support - ALWAYS use useColorModeValue
376
923
  const bg = useColorModeValue('white', 'gray.700');
377
924
  const borderColor = useColorModeValue('gray.200', 'gray.600');
378
925
  const textColor = useColorModeValue('gray.800', 'white');
@@ -384,6 +931,121 @@ const mutedColor = useColorModeValue('gray.500', 'gray.400');
384
931
  // red.500, green.500, blue.500, yellow.500, purple.500
385
932
  ```
386
933
 
934
+ ## Light/Dark Mode Safety Rules
935
+
936
+ **DON'T use these patterns:**
937
+ ```typescript
938
+ // ❌ WRONG - brand colors may not be defined in theme
939
+ color="brand.600"
940
+
941
+ // ❌ WRONG - hard-coded white fails on light backgrounds
942
+ <Text color="white">Always white</Text>
943
+
944
+ // ❌ WRONG - assumes light mode background
945
+ <Badge bg="blue.100" color="blue.800">Status</Badge>
946
+ ```
947
+
948
+ **DO use these patterns:**
949
+ ```typescript
950
+ // ✅ CORRECT - explicit colors that exist in Chakra
951
+ color="blue.600"
952
+
953
+ // ✅ CORRECT - adapts to color mode
954
+ <Text color={useColorModeValue('gray.800', 'white')}>Adapts</Text>
955
+
956
+ // ✅ CORRECT - badge adapts to mode
957
+ const badgeBg = useColorModeValue('blue.100', 'blue.700');
958
+ const badgeColor = useColorModeValue('blue.800', 'blue.100');
959
+ <Badge bg={badgeBg} color={badgeColor}>Status</Badge>
960
+ ```
961
+
962
+ **Semantic tokens (define once, use everywhere):**
963
+ ```typescript
964
+ // In theme.ts extendTheme
965
+ semanticTokens: {
966
+ colors: {
967
+ appBg: { _light: 'gray.50', _dark: 'gray.800' },
968
+ cardBg: { _light: 'white', _dark: 'gray.700' },
969
+ textPrimary: { _light: 'gray.800', _dark: 'white' },
970
+ textMuted: { _light: 'gray.500', _dark: 'gray.400' },
971
+ }
972
+ }
973
+
974
+ // Usage - no useColorModeValue needed
975
+ <Box bg="appBg"><Text color="textPrimary">Clean!</Text></Box>
976
+ ```
977
+
978
+ ## Phase-Colored Badges and Bars
979
+
980
+ When displaying phase/status colors that need to work in both modes:
981
+
982
+ ```typescript
983
+ import { useColorMode } from '@chakra-ui/react';
984
+
985
+ // Helper for phase-colored elements
986
+ function usePhaseColors(baseColor: string) {
987
+ const { colorMode } = useColorMode();
988
+
989
+ if (colorMode === 'light') {
990
+ return {
991
+ bg: `${baseColor}.100`,
992
+ color: `${baseColor}.800`,
993
+ borderColor: `${baseColor}.200`,
994
+ };
995
+ } else {
996
+ return {
997
+ bg: `${baseColor}.700`,
998
+ color: `${baseColor}.100`,
999
+ borderColor: `${baseColor}.600`,
1000
+ };
1001
+ }
1002
+ }
1003
+
1004
+ // Usage
1005
+ function PhaseBadge({ phase, color }: { phase: string; color: string }) {
1006
+ const colors = usePhaseColors(color);
1007
+
1008
+ return (
1009
+ <Badge bg={colors.bg} color={colors.color}>
1010
+ {phase}
1011
+ </Badge>
1012
+ );
1013
+ }
1014
+
1015
+ // Event bar with phase color
1016
+ function EventBar({ title, phaseColor }: { title: string; phaseColor: string }) {
1017
+ const colors = usePhaseColors(phaseColor);
1018
+
1019
+ return (
1020
+ <Box
1021
+ px={2}
1022
+ py={1}
1023
+ bg={colors.bg}
1024
+ color={colors.color}
1025
+ borderLeft="3px solid"
1026
+ borderLeftColor={colors.borderColor}
1027
+ borderRadius="sm"
1028
+ >
1029
+ {title}
1030
+ </Box>
1031
+ );
1032
+ }
1033
+ ```
1034
+
1035
+ **Color mapping from Hailer phases:**
1036
+ ```typescript
1037
+ // Map Hailer phase colors to Chakra color names
1038
+ const PHASE_COLOR_MAP: Record<string, string> = {
1039
+ 'blue': 'blue',
1040
+ 'green': 'green',
1041
+ 'red': 'red',
1042
+ 'yellow': 'yellow',
1043
+ 'purple': 'purple',
1044
+ 'orange': 'orange',
1045
+ 'gray': 'gray',
1046
+ };
1047
+ ```
1048
+
387
1049
  ## Common UI Patterns
388
1050
 
389
1051
  ```typescript
@@ -398,6 +1060,27 @@ const mutedColor = useColorModeValue('gray.500', 'gray.400');
398
1060
  ```
399
1061
  </theme-patterns>
400
1062
 
1063
+ <troubleshooting>
1064
+ ## Workflow Permission Errors
1065
+
1066
+ When SDK calls fail with permission/not-allowed errors, check **workflow configuration in Hailer** first.
1067
+
1068
+ **Common symptoms:**
1069
+ - `hailer.activity.move()` fails with permission error
1070
+ - Phase transitions not working
1071
+ - "Not allowed" errors on operations that should work
1072
+
1073
+ **Cause:** Features like phase transitions must be **enabled in workflow configuration** in Hailer. The SDK can't do operations that aren't configured.
1074
+
1075
+ **Debug steps:**
1076
+ 1. Check Hailer UI: Workflow Settings → Phase settings
1077
+ 2. Verify phase transitions are configured (`possibleNextPhase`)
1078
+ 3. Verify user has permission to the workflow/phase
1079
+ 4. Check if the feature (move, archive, etc.) is enabled for that phase
1080
+
1081
+ **Example:** Phase move fails because `possibleNextPhase` doesn't include the target phase.
1082
+ </troubleshooting>
1083
+
401
1084
  <build-fixes>
402
1085
  ## Common Build Errors
403
1086
 
@@ -447,8 +1130,115 @@ const [data, setData] = useState([]); // Error!
447
1130
  const [data, setData] = useState([]);
448
1131
  if (!inside) return <Text>Error</Text>;
449
1132
  ```
1133
+
1134
+ ### Hooks inside map() or conditionals
1135
+ ```typescript
1136
+ // WRONG - hook in map
1137
+ {items.map((item) => (
1138
+ <Tr _hover={{ bg: useColorModeValue('gray.50', 'gray.600') }}>
1139
+ ))}
1140
+
1141
+ // CORRECT - hook at top level
1142
+ const hoverBg = useColorModeValue('gray.50', 'gray.600');
1143
+ {items.map((item) => (
1144
+ <Tr _hover={{ bg: hoverBg }}>
1145
+ ))}
1146
+ ```
1147
+
1148
+ ### hailer.activity.move is not a function
1149
+ ```typescript
1150
+ // ❌ WRONG - activity.move() DOES NOT EXIST (common mistake)
1151
+ // await hailer.activity.move(activityId, newPhaseId); // This will fail!
1152
+
1153
+ // CORRECT - use activity.update() with phaseId
1154
+ await hailer.activity.update([
1155
+ {
1156
+ _id: activityId,
1157
+ phaseId: newPhaseId,
1158
+ },
1159
+ ], {});
1160
+ ```
1161
+
1162
+ **Explanation:** Phase transitions in Hailer are done via the `update()` method by setting the `phaseId` field. There is no separate `move()` method in the SDK.
1163
+
1164
+ ### Routing: HashRouter vs BrowserRouter
1165
+
1166
+ **Most Hailer apps don't need routing at all.** Use state-based page switching:
1167
+ ```typescript
1168
+ const [page, setPage] = useState('home');
1169
+ {page === 'home' && <HomePage />}
1170
+ {page === 'settings' && <SettingsPage />}
1171
+ ```
1172
+
1173
+ **If you need routing** (bookmarkable URLs, back button, public apps):
1174
+ ```typescript
1175
+ // ✅ USE HashRouter - works in iframe, no server config
1176
+ import { createHashRouter, RouterProvider } from 'react-router-dom';
1177
+
1178
+ const router = createHashRouter([
1179
+ { path: '/', element: <HomePage /> },
1180
+ { path: '/settings', element: <SettingsPage /> },
1181
+ ]);
1182
+
1183
+ // URLs look like: yourapp.com/#/settings
1184
+ ```
1185
+
1186
+ ```typescript
1187
+ // ⚠️ AVOID BrowserRouter in iframe apps
1188
+ // Can cause "No routes matched location /index.html" errors
1189
+ // because iframe URL contains /index.html path
1190
+ ```
1191
+
1192
+ **When to use routing:**
1193
+ - Public apps with shareable URLs
1194
+ - Complex multi-section apps (like hailer-admin)
1195
+ - Apps where back button navigation matters
450
1196
  </build-fixes>
451
1197
 
1198
+ <sdk-crud>
1199
+ ## SDK Create/Update Formats
1200
+
1201
+ ### activity.create() Format
1202
+
1203
+ **Signature:** `hailer.activity.create(workflowId, activities[], options)`
1204
+
1205
+ **CRITICAL:** Takes array of activities, raw field values (not wrapped).
1206
+
1207
+ ```typescript
1208
+ await hailer.activity.create(WORKFLOW_ID, [
1209
+ {
1210
+ name: 'Activity name',
1211
+ fields: {
1212
+ [FIELD_ID]: 'string or number value', // NOT wrapped in { value: ... }
1213
+ [ACTIVITYLINK_FIELD]: 'linkedActivityId', // Just the ID, not { _id, name }
1214
+ },
1215
+ },
1216
+ ], {});
1217
+ ```
1218
+
1219
+ **Common mistakes:**
1220
+ - Passing single object instead of array
1221
+ - Wrapping field values in `{ value: ... }`
1222
+ - Passing `{ _id, name }` for activitylinks instead of just ID
1223
+
1224
+ ### activity.update() Format
1225
+
1226
+ **Signature:** `hailer.activity.update(activities[], options)`
1227
+
1228
+ ```typescript
1229
+ await hailer.activity.update([
1230
+ {
1231
+ _id: 'activityId',
1232
+ name: 'New name', // optional
1233
+ fields: {
1234
+ [FIELD_ID]: newValue,
1235
+ },
1236
+ phaseId: 'newPhaseId', // optional - move to different phase
1237
+ },
1238
+ ], {});
1239
+ ```
1240
+ </sdk-crud>
1241
+
452
1242
  <file-structure>
453
1243
  ## Required Files
454
1244
 
@@ -504,3 +1294,141 @@ export interface UserValue {
504
1294
  }
505
1295
  ```
506
1296
  </file-structure>
1297
+
1298
+ <app-manifest>
1299
+ ## App Manifest Configuration
1300
+
1301
+ The `manifest.json` file in app root defines configurable settings exposed in Hailer UI.
1302
+
1303
+ ### config.fields Structure
1304
+
1305
+ ```json
1306
+ {
1307
+ "name": "My App",
1308
+ "version": "1.0.0",
1309
+ "config": {
1310
+ "fields": {
1311
+ "defaultView": {
1312
+ "type": "string",
1313
+ "label": "Default View",
1314
+ "default": "month"
1315
+ },
1316
+ "slotMinTime": {
1317
+ "type": "string",
1318
+ "label": "Day Start Time",
1319
+ "default": "08:00"
1320
+ },
1321
+ "showWeekends": {
1322
+ "type": "boolean",
1323
+ "label": "Show Weekends",
1324
+ "default": true
1325
+ },
1326
+ "itemsPerPage": {
1327
+ "type": "number",
1328
+ "label": "Items Per Page",
1329
+ "default": 25
1330
+ },
1331
+ "enabledFeatures": {
1332
+ "type": "array",
1333
+ "label": "Enabled Features",
1334
+ "default": ["search", "export"]
1335
+ }
1336
+ }
1337
+ }
1338
+ }
1339
+ ```
1340
+
1341
+ ### Field Types
1342
+
1343
+ | Type | Description | Example Default |
1344
+ |------|-------------|-----------------|
1345
+ | `string` | Text value | `"month"` |
1346
+ | `number` | Numeric value | `25` |
1347
+ | `boolean` | True/false toggle | `true` |
1348
+ | `array` | List of values | `["a", "b"]` |
1349
+
1350
+ ### Accessing Config in App
1351
+
1352
+ Config values come through HailerApi callback when app loads inside Hailer:
1353
+
1354
+ ```typescript
1355
+ const { inside, hailer, config } = useHailer();
1356
+
1357
+ // Access configured values
1358
+ const defaultView = config?.defaultView ?? 'month';
1359
+ const showWeekends = config?.showWeekends ?? true;
1360
+ ```
1361
+
1362
+ ### Best Practices
1363
+
1364
+ **DO expose in config.fields:**
1365
+ - User-customizable options (default views, display preferences)
1366
+ - Workflow/phase IDs that vary per installation
1367
+ - Feature toggles
1368
+
1369
+ **DON'T expose in config.fields:**
1370
+ - Values hardcoded in app code (wastes config UI space)
1371
+ - Sensitive data (use environment variables)
1372
+ - Internal constants that users shouldn't change
1373
+
1374
+ **RULE:** If a config field is defined but the app ignores it, remove it from manifest.
1375
+ </app-manifest>
1376
+
1377
+ <public-api>
1378
+ ## Public API (Apps Outside Hailer)
1379
+
1380
+ For apps that run standalone (outside Hailer iframe) without authentication:
1381
+
1382
+ ```typescript
1383
+ // Public insight data
1384
+ const data = await hailer.public.insight.data(insightKey);
1385
+ const objects = await hailer.public.insight.dataAsObject(insightKey);
1386
+
1387
+ // Public forms
1388
+ const formData = await hailer.public.form.data(formsKey);
1389
+ const result = await hailer.public.form.submit(formsKey, formData);
1390
+
1391
+ // Public app config
1392
+ const config = await hailer.public.app.config();
1393
+
1394
+ // Public products
1395
+ const products = await hailer.public.product.list(filter?, options?);
1396
+ const product = await hailer.public.product.get(productId);
1397
+ ```
1398
+
1399
+ **When to use Public API:**
1400
+ - Building external-facing apps (not in Hailer iframe)
1401
+ - Public dashboards using insight keys
1402
+ - Public form submissions
1403
+ - No user authentication available
1404
+
1405
+ **Note:** Public APIs use keys (not IDs) and don't require authentication.
1406
+ </public-api>
1407
+
1408
+ <sdk-reference>
1409
+ ## SDK Type Definitions (For Edge Cases)
1410
+
1411
+ The skill covers common SDK methods. For less common APIs, read the type definitions in any Hailer app project:
1412
+
1413
+ ```
1414
+ node_modules/@hailer/app-sdk/lib/modules/
1415
+ ├── activity.d.ts - list, get, create, update, remove
1416
+ ├── activity/kanban.d.ts - kanban.list, load, updatePriority
1417
+ ├── user.d.ts - current, get, list
1418
+ ├── ui.d.ts - snackbar, activity, insight, files
1419
+ ├── ui/activity.d.ts - open, create, editMultiple
1420
+ ├── ui/snackbar.d.ts - open
1421
+ ├── ui/files.d.ts - uploadFile
1422
+ ├── insight.d.ts - data, list, update
1423
+ ├── process.d.ts - list, get (workflow API uses this)
1424
+ ├── workspace.d.ts - current, list, product methods
1425
+ ├── permission.d.ts - map
1426
+ ├── app.d.ts - config.update, product methods
1427
+ └── public/ - insight, form, product (no auth)
1428
+ ```
1429
+
1430
+ **When to check types:**
1431
+ - Method not documented here → read the relevant `.d.ts` file
1432
+ - Need method signature details → types are the source of truth
1433
+ - New SDK version → types show what's available
1434
+ </sdk-reference>