@hailer/mcp 0.0.5 → 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.
|
@@ -638,6 +638,42 @@ npm run dev
|
|
|
638
638
|
|
|
639
639
|
App is already created in Hailer, just needs server running.
|
|
640
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
|
+
|
|
641
677
|
### CORS Error When Opening App in Hailer
|
|
642
678
|
|
|
643
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"
|
|
@@ -807,8 +843,10 @@ const competition = match.competition; // undefined
|
|
|
807
843
|
|
|
808
844
|
### Complete Working Example
|
|
809
845
|
|
|
846
|
+
**⚠️ CRITICAL: Do NOT use `hailer` in useEffect dependencies - it causes infinite loops!**
|
|
847
|
+
|
|
810
848
|
```typescript
|
|
811
|
-
import { useEffect, useState } from 'react';
|
|
849
|
+
import { useEffect, useState, useRef } from 'react';
|
|
812
850
|
import useHailer from './hailer/use-hailer';
|
|
813
851
|
|
|
814
852
|
// Define your activity interface based on workflow fields
|
|
@@ -816,7 +854,7 @@ interface Activity {
|
|
|
816
854
|
_id: string;
|
|
817
855
|
name: string;
|
|
818
856
|
fields?: {
|
|
819
|
-
[fieldId: string]: unknown; // Fields
|
|
857
|
+
[fieldId: string]: { type: string; value: unknown }; // Fields have type and value
|
|
820
858
|
};
|
|
821
859
|
}
|
|
822
860
|
|
|
@@ -829,16 +867,23 @@ export default function ActivityList({ workflowId, phaseId }: Props) {
|
|
|
829
867
|
const { hailer, inside } = useHailer();
|
|
830
868
|
const [activities, setActivities] = useState<Activity[]>([]);
|
|
831
869
|
const [loading, setLoading] = useState(true);
|
|
870
|
+
const loadedRef = useRef(false);
|
|
832
871
|
|
|
833
872
|
useEffect(() => {
|
|
834
|
-
|
|
873
|
+
// ✅ Use `inside` as trigger - it's stable
|
|
874
|
+
if (!inside) return;
|
|
875
|
+
if (loadedRef.current) return;
|
|
876
|
+
|
|
877
|
+
// ✅ Access hailer from window to avoid reference changes
|
|
878
|
+
const hailerApi = (window as any).hailerApiInstance;
|
|
879
|
+
if (!hailerApi) return;
|
|
835
880
|
|
|
836
881
|
async function fetchActivities() {
|
|
837
882
|
try {
|
|
838
883
|
// ✅ CORRECT: activity.list with separate parameters
|
|
839
|
-
const result = await
|
|
840
|
-
workflowId,
|
|
841
|
-
phaseId,
|
|
884
|
+
const result = await hailerApi.activity.list(
|
|
885
|
+
workflowId,
|
|
886
|
+
phaseId,
|
|
842
887
|
{
|
|
843
888
|
sortBy: 'created',
|
|
844
889
|
sortOrder: 'asc',
|
|
@@ -847,6 +892,7 @@ export default function ActivityList({ workflowId, phaseId }: Props) {
|
|
|
847
892
|
);
|
|
848
893
|
|
|
849
894
|
setActivities(result || []);
|
|
895
|
+
loadedRef.current = true;
|
|
850
896
|
} catch (err) {
|
|
851
897
|
console.error('Failed to fetch:', err);
|
|
852
898
|
} finally {
|
|
@@ -855,7 +901,7 @@ export default function ActivityList({ workflowId, phaseId }: Props) {
|
|
|
855
901
|
}
|
|
856
902
|
|
|
857
903
|
fetchActivities();
|
|
858
|
-
}, [
|
|
904
|
+
}, [inside, workflowId, phaseId]); // ✅ NO hailer in deps!
|
|
859
905
|
|
|
860
906
|
if (loading) return <div>Loading...</div>;
|
|
861
907
|
|
|
@@ -864,7 +910,7 @@ export default function ActivityList({ workflowId, phaseId }: Props) {
|
|
|
864
910
|
{activities.map((activity) => (
|
|
865
911
|
<div key={activity._id}>
|
|
866
912
|
<h3>{activity.name}</h3>
|
|
867
|
-
{/* Access fields
|
|
913
|
+
{/* Access fields: activity.fields?.[fieldId]?.value */}
|
|
868
914
|
</div>
|
|
869
915
|
))}
|
|
870
916
|
</div>
|
|
@@ -1119,20 +1165,30 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
1119
1165
|
|
|
1120
1166
|
**If you skip this, the app will render with NO STYLES!**
|
|
1121
1167
|
|
|
1122
|
-
## Data Loading Pattern (
|
|
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.
|
|
1123
1171
|
|
|
1124
|
-
Use
|
|
1172
|
+
Use `inside` boolean as the trigger and access hailer from window:
|
|
1125
1173
|
|
|
1126
1174
|
\`\`\`typescript
|
|
1127
|
-
import { useState, useEffect } from 'react';
|
|
1175
|
+
import { useState, useEffect, useRef } from 'react';
|
|
1128
1176
|
|
|
1129
|
-
|
|
1177
|
+
// Hook takes `inside` boolean, NOT hailer object
|
|
1178
|
+
function useMyData(inside: boolean) {
|
|
1130
1179
|
const [data, setData] = useState([]);
|
|
1131
1180
|
const [loading, setLoading] = useState(true);
|
|
1132
1181
|
const [error, setError] = useState<string | null>(null);
|
|
1133
1182
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
1183
|
+
const loadedRef = useRef(false);
|
|
1134
1184
|
|
|
1135
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;
|
|
1136
1192
|
if (!hailer) return;
|
|
1137
1193
|
|
|
1138
1194
|
async function loadData() {
|
|
@@ -1148,6 +1204,7 @@ function useMyData(hailer: HailerApi | undefined) {
|
|
|
1148
1204
|
);
|
|
1149
1205
|
|
|
1150
1206
|
setData(result || []);
|
|
1207
|
+
loadedRef.current = true;
|
|
1151
1208
|
} catch (err) {
|
|
1152
1209
|
setError(err instanceof Error ? err.message : 'Failed to load');
|
|
1153
1210
|
} finally {
|
|
@@ -1156,15 +1213,35 @@ function useMyData(hailer: HailerApi | undefined) {
|
|
|
1156
1213
|
}
|
|
1157
1214
|
|
|
1158
1215
|
loadData();
|
|
1159
|
-
}, [
|
|
1216
|
+
}, [inside, refreshTrigger]); // ✅ inside is stable, hailer is NOT
|
|
1160
1217
|
|
|
1161
|
-
const refresh = () =>
|
|
1218
|
+
const refresh = () => {
|
|
1219
|
+
loadedRef.current = false;
|
|
1220
|
+
setRefreshTrigger(prev => prev + 1);
|
|
1221
|
+
};
|
|
1162
1222
|
|
|
1163
1223
|
return { data, loading, error, refresh };
|
|
1164
1224
|
}
|
|
1165
1225
|
\`\`\`
|
|
1166
1226
|
|
|
1167
|
-
**
|
|
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
|
|
1168
1245
|
|
|
1169
1246
|
## Field Access Pattern
|
|
1170
1247
|
|