@hailer/mcp 0.0.4 → 0.0.6

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.
@@ -7,6 +7,61 @@ description: Complete guide to ONE-SHOT Hailer app creation - scaffolds project,
7
7
 
8
8
  Complete guide to using the `scaffold_hailer_app` tool - a ONE-SHOT solution that scaffolds your project, creates the dev app entry in Hailer, shares it with your workspace, updates manifest.json, and starts the dev server automatically.
9
9
 
10
+ ---
11
+
12
+ ## ⚠️ MANDATORY: SPAWN BUILDER AGENT AFTER SCAFFOLDING
13
+
14
+ **THIS IS NOT OPTIONAL.** After `scaffold_hailer_app` completes successfully, you MUST:
15
+
16
+ 1. **IMMEDIATELY spawn the app builder agent** using the Task tool
17
+ 2. **DO NOT** start building components in the current session
18
+ 3. **DO NOT** skip this step
19
+
20
+ ### Why This Is Required
21
+
22
+ - The builder agent has **isolated context** for focused work
23
+ - It's **pre-loaded with TypeScript + SDK patterns**
24
+ - Building in main session leads to **context overflow** and **mistakes**
25
+ - The agent produces **higher quality code**
26
+
27
+ ### Exact Steps After Scaffold Succeeds
28
+
29
+ ```javascript
30
+ // Step 1: Get workflow context (REQUIRED)
31
+ list_workflows_minimal()
32
+
33
+ // Step 2: Spawn the builder agent with Task tool
34
+ Task({
35
+ subagent_type: "general-purpose",
36
+ description: "Build Hailer app components",
37
+ prompt: `You are building a Hailer app.
38
+
39
+ PROJECT: <project-path>
40
+ DESCRIPTION: <from scaffold description>
41
+ WORKFLOWS: <paste list_workflows_minimal output>
42
+
43
+ Load these skills FIRST:
44
+ 1. Skill("building-hailer-apps-skill")
45
+ 2. Skill("hailer-app-builder")
46
+
47
+ Then build the app components following the patterns in those skills.
48
+
49
+ Requirements:
50
+ - Use Chakra UI for components
51
+ - Follow TypeScript strict mode
52
+ - Use hailer.activity.list(workflowId, phaseId, options) - 3 params
53
+ - Access fields via activity.fields?.[fieldId]
54
+ - Build full CRUD: list, create, edit, delete
55
+ `
56
+ })
57
+ ```
58
+
59
+ ### DO NOT PROCEED WITHOUT SPAWNING
60
+
61
+ If you find yourself writing React components after scaffolding WITHOUT spawning an agent first, **STOP** and spawn the agent.
62
+
63
+ ---
64
+
10
65
  ## Table of Contents
11
66
  - [Quick Reference](#quick-reference)
12
67
  - [Overview](#overview)
@@ -583,6 +638,42 @@ npm run dev
583
638
 
584
639
  App is already created in Hailer, just needs server running.
585
640
 
641
+ ### Infinite Loop When Loading Data
642
+
643
+ **Problem:** App keeps re-fetching data infinitely, causing performance issues.
644
+
645
+ **Cause:** Using `hailer` object in useEffect dependencies. The `hailer` object from `useHailer()` changes reference on every render.
646
+
647
+ **Solution:**
648
+ ```typescript
649
+ // ❌ WRONG - causes infinite loop
650
+ useEffect(() => {
651
+ if (!hailer) return;
652
+ hailer.activity.list(...);
653
+ }, [hailer]); // hailer changes every render!
654
+
655
+ // ✅ CORRECT - use `inside` and access hailer from window
656
+ useEffect(() => {
657
+ if (!inside) return;
658
+ const hailer = (window as any).hailerApiInstance;
659
+ if (!hailer) return;
660
+ hailer.activity.list(...);
661
+ }, [inside]); // inside is a stable boolean
662
+ ```
663
+
664
+ ### Chakra UI Table Not Showing Text
665
+
666
+ **Problem:** Table rows render but text is invisible/white.
667
+
668
+ **Cause:** CSS inheritance issues when running inside Hailer iframe.
669
+
670
+ **Solution:** Add explicit `color="black"` to Td components:
671
+ ```typescript
672
+ <Tr key={activity._id} bg="white">
673
+ <Td color="black">{value}</Td>
674
+ </Tr>
675
+ ```
676
+
586
677
  ### CORS Error When Opening App in Hailer
587
678
 
588
679
  **Problem:** Browser console shows "Access to fetch at 'http://localhost:3000/manifest.json' from origin 'https://next.hailer.com' has been blocked by CORS policy"
@@ -682,10 +773,10 @@ const activities = await hailer.activity.list(
682
773
  options // Third parameter: options object
683
774
  );
684
775
 
685
- // Example with real IDs
776
+ // Example with generic IDs (replace with actual IDs from your workspace)
686
777
  const activities = await hailer.activity.list(
687
- '69087299c0e0b9944c620172', // Matches workflow
688
- '69087299c0e0b9944c620173', // Scheduled phase
778
+ workflowId, // Get from list_workflows_minimal() or hailer.workflow.list()
779
+ phaseId, // Get from hailer.workflow.get(workflowId).phases
689
780
  {
690
781
  sortBy: 'created',
691
782
  sortOrder: 'asc',
@@ -752,43 +843,56 @@ const competition = match.competition; // undefined
752
843
 
753
844
  ### Complete Working Example
754
845
 
846
+ **⚠️ CRITICAL: Do NOT use `hailer` in useEffect dependencies - it causes infinite loops!**
847
+
755
848
  ```typescript
756
- import { useEffect, useState } from 'react';
849
+ import { useEffect, useState, useRef } from 'react';
757
850
  import useHailer from './hailer/use-hailer';
758
851
 
759
- interface Match {
852
+ // Define your activity interface based on workflow fields
853
+ interface Activity {
760
854
  _id: string;
761
855
  name: string;
762
856
  fields?: {
763
- opponent?: { _id: string; name: string };
764
- matchDate?: number;
765
- competition?: string;
766
- venue?: string;
857
+ [fieldId: string]: { type: string; value: unknown }; // Fields have type and value
767
858
  };
768
859
  }
769
860
 
770
- export default function MatchesList() {
861
+ interface Props {
862
+ workflowId: string;
863
+ phaseId: string;
864
+ }
865
+
866
+ export default function ActivityList({ workflowId, phaseId }: Props) {
771
867
  const { hailer, inside } = useHailer();
772
- const [matches, setMatches] = useState<Match[]>([]);
868
+ const [activities, setActivities] = useState<Activity[]>([]);
773
869
  const [loading, setLoading] = useState(true);
870
+ const loadedRef = useRef(false);
774
871
 
775
872
  useEffect(() => {
776
- if (!hailer || !inside) return;
873
+ // Use `inside` as trigger - it's stable
874
+ if (!inside) return;
875
+ if (loadedRef.current) return;
777
876
 
778
- async function fetchMatches() {
877
+ // Access hailer from window to avoid reference changes
878
+ const hailerApi = (window as any).hailerApiInstance;
879
+ if (!hailerApi) return;
880
+
881
+ async function fetchActivities() {
779
882
  try {
780
883
  // ✅ CORRECT: activity.list with separate parameters
781
- const activities = await hailer.activity.list(
782
- '69087299c0e0b9944c620172', // processId
783
- '69087299c0e0b9944c620173', // phaseId
884
+ const result = await hailerApi.activity.list(
885
+ workflowId,
886
+ phaseId,
784
887
  {
785
888
  sortBy: 'created',
786
889
  sortOrder: 'asc',
787
- limit: 20
890
+ limit: 50
788
891
  }
789
892
  );
790
893
 
791
- setMatches(activities || []);
894
+ setActivities(result || []);
895
+ loadedRef.current = true;
792
896
  } catch (err) {
793
897
  console.error('Failed to fetch:', err);
794
898
  } finally {
@@ -796,19 +900,17 @@ export default function MatchesList() {
796
900
  }
797
901
  }
798
902
 
799
- fetchMatches();
800
- }, [hailer, inside]);
903
+ fetchActivities();
904
+ }, [inside, workflowId, phaseId]); // ✅ NO hailer in deps!
801
905
 
802
906
  if (loading) return <div>Loading...</div>;
803
907
 
804
908
  return (
805
909
  <div>
806
- {matches.map((match) => (
807
- <div key={match._id}>
808
- {/* ✅ CORRECT: Access via fields */}
809
- <h3>{match.fields?.opponent?.name || match.name}</h3>
810
- <p>Competition: {match.fields?.competition}</p>
811
- <p>Venue: {match.fields?.venue}</p>
910
+ {activities.map((activity) => (
911
+ <div key={activity._id}>
912
+ <h3>{activity.name}</h3>
913
+ {/* Access fields: activity.fields?.[fieldId]?.value */}
812
914
  </div>
813
915
  ))}
814
916
  </div>
@@ -846,14 +948,16 @@ await hailer.activity.update(
846
948
  await hailer.activity.remove(['activity-id-1', 'activity-id-2']);
847
949
  ```
848
950
 
849
- #### Workflow/Process Operations
951
+ #### Workflow Operations
850
952
 
851
953
  ```typescript
852
- // Get workflow phases
853
- const phases = await hailer.process.getPhases(processId);
954
+ // Get workflow with fields and phases
955
+ const workflow = await hailer.workflow.get(workflowId);
956
+ // workflow.fields = { fieldId: { label, type, key, ... } }
957
+ // workflow.phases = { phaseId: { name, fields: [...] } }
854
958
 
855
- // Get workflow fields
856
- const fields = await hailer.process.getFields(processId, phaseId);
959
+ // List all workflows
960
+ const workflows = await hailer.workflow.list();
857
961
  ```
858
962
 
859
963
  #### User Operations
@@ -1007,28 +1111,204 @@ That's it! The one-shot approach eliminates all manual setup steps.
1007
1111
 
1008
1112
  ---
1009
1113
 
1010
- ## Next Steps: Building Your App
1114
+ ## Next Steps: SPAWN THE BUILDER AGENT (MANDATORY)
1011
1115
 
1012
- After scaffolding, you need to load data from Hailer workflows. **IMPORTANT:** The Hailer App SDK has specific patterns you must follow!
1116
+ **⚠️ DO NOT BUILD IN CURRENT SESSION.** After scaffolding completes:
1013
1117
 
1014
- ### ⚠️ Critical: Load the Building Hailer Apps Skill
1118
+ ### Step 1: Get Workflow Context
1015
1119
 
1016
- ```typescript
1017
- // Load comprehensive guide for data loading
1018
- await get_skill({ skillName: "building-hailer-apps-skill" })
1120
+ ```javascript
1121
+ list_workflows_minimal()
1019
1122
  ```
1020
1123
 
1021
- **This skill covers:**
1022
- - ✅ How to properly call `hailer.activity.list()` (3 positional params)
1023
- - ✅ How field access works (fields are keyed by IDs, not readable keys)
1024
- - ✅ How to use MCP tools to get workflow schema
1025
- - ✅ Complete working examples with TypeScript
1026
- - Common pitfalls and how to avoid them
1027
- - Debugging techniques when data won't load
1124
+ ### Step 2: Spawn the Builder Agent
1125
+
1126
+ ```javascript
1127
+ Task({
1128
+ subagent_type: "general-purpose",
1129
+ description: "Build Hailer app components",
1130
+ prompt: `You are building a Hailer app.
1131
+
1132
+ PROJECT PATH: /path/to/project
1133
+ DESCRIPTION: <description from scaffold>
1134
+ APP TYPE: Full CRUD application
1135
+
1136
+ WORKFLOWS IN WORKSPACE:
1137
+ <paste list_workflows_minimal output here>
1138
+
1139
+ ## MANDATORY: Load Skills First
1140
+
1141
+ Before writing ANY code, load these skills:
1142
+ 1. Skill("building-hailer-apps-skill") - SDK API patterns
1143
+ 2. Skill("hailer-app-builder") - TypeScript standards
1144
+
1145
+ ## ⚠️ CRITICAL FIRST STEP: Fix main.tsx
1146
+
1147
+ **THE TEMPLATE IS MISSING ChakraProvider!** You MUST fix this FIRST or styles won't work:
1148
+
1149
+ \`\`\`typescript
1150
+ // src/main.tsx - REPLACE the entire file with this:
1151
+ import React from 'react'
1152
+ import ReactDOM from 'react-dom/client'
1153
+ import { ChakraProvider } from '@chakra-ui/react'
1154
+ import App from './App.tsx'
1155
+ import './index.css'
1156
+
1157
+ ReactDOM.createRoot(document.getElementById('root')!).render(
1158
+ <React.StrictMode>
1159
+ <ChakraProvider>
1160
+ <App />
1161
+ </ChakraProvider>
1162
+ </React.StrictMode>,
1163
+ )
1164
+ \`\`\`
1165
+
1166
+ **If you skip this, the app will render with NO STYLES!**
1167
+
1168
+ ## Data Loading Pattern (CRITICAL - PREVENTS INFINITE LOOPS)
1169
+
1170
+ **⚠️ THE `hailer` OBJECT CHANGES ON EVERY RENDER!** Using `hailer` in useEffect dependencies causes infinite loops.
1171
+
1172
+ Use `inside` boolean as the trigger and access hailer from window:
1173
+
1174
+ \`\`\`typescript
1175
+ import { useState, useEffect, useRef } from 'react';
1176
+
1177
+ // Hook takes `inside` boolean, NOT hailer object
1178
+ function useMyData(inside: boolean) {
1179
+ const [data, setData] = useState([]);
1180
+ const [loading, setLoading] = useState(true);
1181
+ const [error, setError] = useState<string | null>(null);
1182
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
1183
+ const loadedRef = useRef(false);
1184
+
1185
+ useEffect(() => {
1186
+ // Use `inside` as trigger - it's a stable boolean
1187
+ if (!inside) return;
1188
+ if (loadedRef.current && refreshTrigger === 0) return;
1189
+
1190
+ // Access hailer from window to avoid reference changes
1191
+ const hailer = (window as any).hailerApiInstance;
1192
+ if (!hailer) return;
1193
+
1194
+ async function loadData() {
1195
+ try {
1196
+ setLoading(true);
1197
+ setError(null);
1198
+
1199
+ // 3 positional params: workflowId, phaseId, options
1200
+ const result = await hailer.activity.list(
1201
+ WORKFLOW_ID,
1202
+ PHASE_ID,
1203
+ { limit: 100 }
1204
+ );
1205
+
1206
+ setData(result || []);
1207
+ loadedRef.current = true;
1208
+ } catch (err) {
1209
+ setError(err instanceof Error ? err.message : 'Failed to load');
1210
+ } finally {
1211
+ setLoading(false);
1212
+ }
1213
+ }
1214
+
1215
+ loadData();
1216
+ }, [inside, refreshTrigger]); // ✅ inside is stable, hailer is NOT
1217
+
1218
+ const refresh = () => {
1219
+ loadedRef.current = false;
1220
+ setRefreshTrigger(prev => prev + 1);
1221
+ };
1222
+
1223
+ return { data, loading, error, refresh };
1224
+ }
1225
+ \`\`\`
1226
+
1227
+ **In App.tsx, pass `inside` not `hailer`:**
1228
+ \`\`\`typescript
1229
+ export default function App() {
1230
+ const { hailer, inside } = useHailer();
1231
+ const { data, loading, error } = useMyData(inside); // ✅ Pass inside, not hailer
1232
+
1233
+ if (!hailer || !inside) {
1234
+ return <Spinner />;
1235
+ }
1236
+ // ...
1237
+ }
1238
+ \`\`\`
1239
+
1240
+ **KEY RULES:**
1241
+ 1. ❌ NEVER use `hailer` in useEffect dependencies - causes infinite loops
1242
+ 2. ✅ Use `inside` boolean as the stable trigger
1243
+ 3. ✅ Access hailer via `(window as any).hailerApiInstance` inside useEffect
1244
+ 4. ✅ Use `loadedRef` to prevent duplicate loads
1245
+
1246
+ ## Field Access Pattern
1247
+
1248
+ Fields are keyed by FIELD IDs, not readable names:
1249
+
1250
+ \`\`\`typescript
1251
+ // Get field IDs from get_workflow_schema() first!
1252
+ const FIELDS = {
1253
+ PLAYER_NAME: '691ffdf84217e9e8434e5694', // From schema
1254
+ JERSEY_NUMBER: '691ffdf84217e9e8434e5695',
1255
+ };
1256
+
1257
+ // Helper function
1258
+ function getFieldValue<T>(fields: Record<string, {value: unknown}> | undefined, fieldId: string): T | null {
1259
+ return (fields?.[fieldId]?.value as T) ?? null;
1260
+ }
1261
+
1262
+ // Usage
1263
+ const name = getFieldValue<string>(activity.fields, FIELDS.PLAYER_NAME);
1264
+ \`\`\`
1265
+
1266
+ ## Requirements
1267
+
1268
+ Build a functional app with:
1269
+ - Dashboard showing overview stats
1270
+ - List view with data from workflows
1271
+ - Proper error handling and loading states
1272
+ - Styled with Chakra UI (AFTER fixing main.tsx!)
1273
+
1274
+ ## Technical Requirements
1275
+
1276
+ - Fix main.tsx with ChakraProvider FIRST
1277
+ - Chakra UI components for all UI
1278
+ - TypeScript strict mode (no 'any')
1279
+ - hailer.activity.list(workflowId, phaseId, options) - 3 params
1280
+ - Access fields via getFieldValue helper with field IDs
1281
+ - Simple useEffect pattern (no useCallback/useRef for data loading)
1282
+
1283
+ ## Deliverables
1284
+
1285
+ 1. **FIRST**: Fix src/main.tsx with ChakraProvider
1286
+ 2. Create src/constants.ts with workflow/field IDs
1287
+ 3. Create src/hooks/ with data fetching hooks
1288
+ 4. Create src/components/ with UI components
1289
+ 5. Update src/App.tsx with the dashboard
1290
+
1291
+ ## Verification
1292
+
1293
+ After building, the app should:
1294
+ 1. Show styled UI (not plain HTML) - confirms ChakraProvider works
1295
+ 2. Load data without infinite loops - confirms useEffect pattern works
1296
+ 3. Display field values correctly - confirms field access works
1297
+
1298
+ Report back what was created and confirm the 3 checks above pass.
1299
+ `
1300
+ })
1301
+ ```
1302
+
1303
+ ### Why Spawn Is Required
1304
+
1305
+ | Current Session | Spawned Agent |
1306
+ |-----------------|---------------|
1307
+ | Context gets cluttered | Clean, focused context |
1308
+ | May forget patterns | Skills pre-loaded |
1309
+ | Easier to make mistakes | Follows patterns strictly |
1310
+ | Can't see full picture | Dedicated to this task |
1028
1311
 
1029
- **Why you need it:** The Hailer App SDK is different from the MCP API. Without this knowledge, you'll get errors like:
1030
- - `Cannot read properties of undefined (reading 'activity')`
1031
- - `"processId" must be a string`
1032
- - Fields showing as `undefined` even though data loads
1312
+ ### DO NOT Skip This Step
1033
1313
 
1034
- **Load it before writing any data-loading code!**
1314
+ If you catch yourself writing components without spawning, **STOP** and spawn the agent first.