Skip to content

Commit cb3d26e

Browse files
committed
fix: added self healing
1 parent 4da62d2 commit cb3d26e

7 files changed

Lines changed: 158 additions & 75 deletions

File tree

src/DebugOverlay.jsx

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
8484
}, [resizing, resizeInitial, size]);
8585

8686
return (
87-
<div
87+
<div
8888
className="debug-overlay"
8989
style={{
9090
position: 'fixed',
@@ -102,7 +102,7 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
102102
borderRadius: '10px'
103103
}}
104104
>
105-
<div
105+
<div
106106
className="drag-handle"
107107
style={{
108108
width: '60px',
@@ -114,10 +114,10 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
114114
}}
115115
onMouseDown={handleMouseDown}
116116
/>
117-
<div style={{
118-
position: 'sticky',
119-
top: 0,
120-
backgroundColor: 'rgba(0, 0, 0, 0.9)',
117+
<div style={{
118+
position: 'sticky',
119+
top: 0,
120+
backgroundColor: 'rgba(0, 0, 0, 0.9)',
121121
padding: '5px 10px',
122122
marginBottom: '10px',
123123
borderBottom: '1px solid rgba(255,255,255,0.2)',
@@ -126,12 +126,12 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
126126
alignItems: 'center'
127127
}}>
128128
<strong>Debug Mode</strong>
129-
<button
130-
onClick={hideOverlay}
131-
style={{
132-
background: 'none',
133-
border: 'none',
134-
color: '#EFE8E5',
129+
<button
130+
onClick={hideOverlay}
131+
style={{
132+
background: 'none',
133+
border: 'none',
134+
color: '#EFE8E5',
135135
cursor: 'pointer',
136136
padding: 0
137137
}}
@@ -141,22 +141,26 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
141141
</button>
142142
</div>
143143

144-
{debugData && typeof debugData === 'object' && Object.entries(debugData).map(([key, value]) => (
145-
<div key={key} style={{
146-
borderBottom: '1px solid rgba(255,255,255,0.1)',
147-
padding: '5px 0',
148-
marginBottom: '5px'
149-
}}>
150-
<strong style={{ color: '#66d9ef' }}>{key}:</strong>
151-
<pre style={{
152-
margin: '5px 0',
153-
whiteSpace: 'pre-wrap',
154-
wordBreak: 'break-all'
144+
145+
146+
{
147+
debugData && typeof debugData === 'object' && Object.entries(debugData).map(([key, value]) => (
148+
<div key={key} style={{
149+
borderBottom: '1px solid rgba(255,255,255,0.1)',
150+
padding: '5px 0',
151+
marginBottom: '5px'
155152
}}>
156-
{typeof value === 'object' ? JSON.stringify(value, null, 2) : value}
157-
</pre>
158-
</div>
159-
))}
153+
<strong style={{ color: '#66d9ef' }}>{key}:</strong>
154+
<pre style={{
155+
margin: '5px 0',
156+
whiteSpace: 'pre-wrap',
157+
wordBreak: 'break-all'
158+
}}>
159+
{typeof value === 'object' ? JSON.stringify(value, null, 2) : value}
160+
</pre>
161+
</div>
162+
))
163+
}
160164
<div style={{ marginTop: '10px', borderTop: '1px solid rgba(255,255,255,0.2)', paddingTop: '10px' }}>
161165
<strong>Full Debug Data:</strong>
162166
<pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-all', margin: 0, fontFamily: "'EmOne', sans-serif", fontSize: '12px' }}>
@@ -176,7 +180,7 @@ const DebugOverlay = ({ debugData, hideOverlay }) => {
176180
}}
177181
onMouseDown={handleResizeMouseDown}
178182
/>
179-
</div>
183+
</div >
180184
);
181185
};
182186

src/Node.jsx

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,20 @@ const Node = ({
196196
// Filter nodes and edges for the current graph definition
197197
const currentGraphNodes = useMemo(() => {
198198
if (!isPreviewing || !currentGraphId) return [];
199-
return getHydratedNodesForGraph(currentGraphId)(storeState);
200-
}, [isPreviewing, currentGraphId, storeState]);
199+
const nodes = getHydratedNodesForGraph(currentGraphId)(storeState);
200+
// Diagnostic logging
201+
console.log('[Node Decompose Debug]', {
202+
nodeName,
203+
prototypeId,
204+
definitionGraphIds,
205+
currentGraphId,
206+
graphData: storeState.graphs.get(currentGraphId),
207+
instanceCount: storeState.graphs.get(currentGraphId)?.instances?.size || 0,
208+
hydratedNodeCount: nodes.length,
209+
nodes: nodes.map(n => ({ id: n.id, name: n.name, prototypeId: n.prototypeId }))
210+
});
211+
return nodes;
212+
}, [isPreviewing, currentGraphId, storeState, nodeName, prototypeId, definitionGraphIds]);
201213

202214
const currentGraphEdges = useMemo(() => {
203215
if (!isPreviewing || !currentGraphId) return [];
@@ -228,41 +240,7 @@ const Node = ({
228240
}
229241
}, [isPreviewing]);
230242

231-
// Self-healing effect: when previewing starts but no definitions found, search for orphan graphs
232-
useEffect(() => {
233-
if (!isPreviewing || !prototypeId || !storeActions) return;
234-
235-
// Check if we already have definitions (from prototype spread)
236-
if (definitionGraphIds.length > 0) return;
237-
238-
// No definitions on the hydrated node - check if there are orphan graphs
239-
const currentState = useGraphStore.getState();
240-
const graphs = currentState.graphs;
241-
242-
let orphanGraphId = null;
243-
try {
244-
for (const [gId, g] of graphs.entries()) {
245-
if (Array.isArray(g.definingNodeIds) && g.definingNodeIds.includes(prototypeId)) {
246-
orphanGraphId = gId;
247-
break;
248-
}
249-
}
250-
} catch (_) { }
251-
252-
if (orphanGraphId) {
253-
console.log('[Node] Self-healing: Found orphan definition graph. Repairing link.', {
254-
prototypeId,
255-
orphanGraphId
256-
});
257-
// Repair the link
258-
storeActions.updateNodePrototype(prototypeId, draft => {
259-
draft.definitionGraphIds = Array.isArray(draft.definitionGraphIds) ? draft.definitionGraphIds : [];
260-
if (!draft.definitionGraphIds.includes(orphanGraphId)) {
261-
draft.definitionGraphIds.push(orphanGraphId);
262-
}
263-
});
264-
}
265-
}, [isPreviewing, prototypeId, definitionGraphIds.length, storeActions]);
243+
// (Self-healing removed - issue is not orphan graphs but empty definition graphs)
266244

267245
// Navigation functions
268246
const navigateToPreviousDefinition = () => {

src/NodeCanvas.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4716,9 +4716,8 @@ function NodeCanvas() {
47164716

47174717
// Keep currentPieMenuData.buttons in sync with targetPieMenuButtons so UI reflects state changes (e.g., Save/Unsave) immediately
47184718
useEffect(() => {
4719-
if (!currentPieMenuData) return;
47204719
setCurrentPieMenuData(prev => prev ? { ...prev, buttons: targetPieMenuButtons } : prev);
4721-
}, [targetPieMenuButtons, currentPieMenuData]);
4720+
}, [targetPieMenuButtons]);
47224721

47234722
// Effect to restore view state on graph change or center if no stored state
47244723
useLayoutEffect(() => {
@@ -6631,6 +6630,9 @@ function NodeCanvas() {
66316630

66326631
// Dragging Node or Group Logic (only after long-press has set draggingNodeInfo)
66336632
if (draggingNodeInfo) {
6633+
if (!mouseMoved.current) {
6634+
mouseMoved.current = true;
6635+
}
66346636
// Movement Zoom-Out: Trigger when drag actually starts moving (not on mousedown)
66356637
// This ensures zoom-out happens after movement threshold, preventing reset during delay
66366638
if (!zoomOutInitiatedRef.current && dragZoomSettings.enabled) {

src/Panel.jsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ const INITIAL_PANEL_WIDTH = 280; // Match NodeCanvas default
359359

360360
// Feature flag: toggle visibility of the "All Things" tab in the left panel header
361361
const ENABLE_ALL_THINGS_TAB = true;
362+
const ENABLE_AI_AGENT = false;
362363

363364
// Helper to read width from storage
364365
const getInitialWidth = (side, defaultValue) => {
@@ -1660,7 +1661,7 @@ const Panel = memo(forwardRef(
16601661
onLoadWikidataCatalog={handleLoadWikidataCatalog}
16611662
/>
16621663
);
1663-
} else if (leftViewActive === 'ai') {
1664+
} else if (ENABLE_AI_AGENT && leftViewActive === 'ai') {
16641665
panelContent = (
16651666
<LeftAIView
16661667
compact={panelWidth < 300}
@@ -1912,13 +1913,15 @@ const Panel = memo(forwardRef(
19121913
</div>
19131914

19141915
{/* AI Wizard Button */}
1915-
<div
1916-
title="AI Wizard"
1917-
style={{ /* Common Button Styles */ width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', backgroundColor: leftViewActive === 'ai' ? '#bdb5b5' : '#979090', zIndex: 2 }}
1918-
onClick={() => setLeftViewActive('ai')}
1919-
>
1920-
<Sparkles size={20} color="#260000" />
1921-
</div>
1916+
{ENABLE_AI_AGENT && (
1917+
<div
1918+
title="AI Wizard"
1919+
style={{ /* Common Button Styles */ width: 40, height: 40, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', backgroundColor: leftViewActive === 'ai' ? '#bdb5b5' : '#979090', zIndex: 2 }}
1920+
onClick={() => setLeftViewActive('ai')}
1921+
>
1922+
<Sparkles size={20} color="#260000" />
1923+
</div>
1924+
)}
19221925

19231926
{/* History Button */}
19241927
<div

src/RedstringMenu.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -893,6 +893,19 @@ const RedstringMenu = ({
893893
Reset Onboarding Flow
894894
</div>
895895

896+
<div
897+
className="submenu-item"
898+
onClick={() => {
899+
console.log('[Debug] Repair Graph Links clicked');
900+
closeAllMenus();
901+
useGraphStore.getState().repairGraphLinkages();
902+
}}
903+
style={{ cursor: 'pointer' }}
904+
>
905+
<RefreshCw size={14} style={{ marginRight: '8px' }} />
906+
Repair Broken Graph Links
907+
</div>
908+
896909
<div
897910
className="submenu-item"
898911
onMouseEnter={handleRegularSubmenuItemHover}

src/components/panel/SharedPanelContent.jsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,6 +1663,13 @@ const SharedPanelContent = ({
16631663
</button>
16641664
</div>
16651665
)}
1666+
1667+
<div style={{ marginTop: '8px', fontSize: '9px', color: '#999', fontFamily: "'EmOne', sans-serif" }}>
1668+
{isHomeTab && graphData?.id && (
1669+
<div style={{ marginBottom: '2px' }}>Graph ID: {graphData.id}</div>
1670+
)}
1671+
ID: {nodeData.id}
1672+
</div>
16661673
</div>
16671674
) : (
16681675
// Default origin information for all nodes
@@ -1809,6 +1816,13 @@ const SharedPanelContent = ({
18091816
</>
18101817
);
18111818
})()}
1819+
1820+
<div style={{ marginTop: '8px', fontSize: '9px', color: '#999', fontFamily: "'EmOne', sans-serif" }}>
1821+
{isHomeTab && graphData?.id && (
1822+
<div style={{ marginBottom: '2px' }}>Graph ID: {graphData.id}</div>
1823+
)}
1824+
ID: {nodeData.id}
1825+
</div>
18121826
</div>
18131827
)}
18141828
</CollapsibleSection>

src/store/graphStore.jsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,12 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
754754
}
755755
});
756756

757+
// Validate: don't create empty definition graphs
758+
if (memberInstances.length === 0) {
759+
console.warn(`[convertGroupToNodeGroup] Group ${groupId} has no valid members. Aborting conversion to prevent empty definition.`);
760+
return;
761+
}
762+
757763
memberInstances.forEach(({ instId, instance }) => {
758764
const newInstId = uuidv4();
759765
instanceIdMap.set(instId, newInstId);
@@ -1448,6 +1454,16 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
14481454
// Delete the instance
14491455
graph.instances.delete(instanceId);
14501456

1457+
// Clean up group membership - remove this instance from any groups it belongs to
1458+
if (graph.groups) {
1459+
for (const [groupId, group] of graph.groups.entries()) {
1460+
if (group.memberInstanceIds?.includes(instanceId)) {
1461+
group.memberInstanceIds = group.memberInstanceIds.filter(id => id !== instanceId);
1462+
console.log(`[removeNodeInstance] Removed instance ${instanceId} from group ${groupId}`);
1463+
}
1464+
}
1465+
}
1466+
14511467
// Ensure any soft-deletion bookkeeping is cleared
14521468
draft.pendingDeletions.delete(instanceId);
14531469

@@ -1495,6 +1511,19 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
14951511
draft.pendingDeletions.delete(instanceId);
14961512
});
14971513

1514+
// Clean up group membership for all deleted instances
1515+
if (graph.groups) {
1516+
for (const [groupId, group] of graph.groups.entries()) {
1517+
if (group.memberInstanceIds) {
1518+
const originalLength = group.memberInstanceIds.length;
1519+
group.memberInstanceIds = group.memberInstanceIds.filter(id => !instanceIdSet.has(id));
1520+
if (group.memberInstanceIds.length !== originalLength) {
1521+
console.log(`[removeMultipleNodeInstances] Cleaned up ${originalLength - group.memberInstanceIds.length} stale members from group ${groupId}`);
1522+
}
1523+
}
1524+
}
1525+
}
1526+
14981527
console.log(`[removeMultipleNodeInstances] Deleted ${instanceIdSet.size} instances and ${edgesToDelete.length} edges`);
14991528
}));
15001529
},
@@ -2269,6 +2298,46 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
22692298
console.log(`[Store createGraphWithId] Created and activated graph ${graphId} ('${name}')`);
22702299
})),
22712300

2301+
// Repair tool to re-sync bidirectional links between graphs and their defining nodes
2302+
repairGraphLinkages: () => {
2303+
console.log('[Repair Tool] Starting bidirectional link repair...');
2304+
set(produce((draft) => {
2305+
let repairCount = 0;
2306+
2307+
// Iterate all graphs
2308+
for (const [graphId, graph] of draft.graphs.entries()) {
2309+
// Check if graph defines any nodes
2310+
const definingNodeIds = graph.definingNodeIds || [];
2311+
2312+
definingNodeIds.forEach(prototypeId => {
2313+
const prototype = draft.nodePrototypes.get(prototypeId);
2314+
if (!prototype) {
2315+
console.warn(`[Repair Tool] Graph "${graph.name}" (${graphId}) defines missing prototype ${prototypeId}`);
2316+
return;
2317+
}
2318+
2319+
// Ensure prototype links back to this graph
2320+
if (!Array.isArray(prototype.definitionGraphIds)) {
2321+
prototype.definitionGraphIds = [];
2322+
}
2323+
2324+
if (!prototype.definitionGraphIds.includes(graphId)) {
2325+
prototype.definitionGraphIds.push(graphId);
2326+
console.log(`[Repair Tool] 🛠️ FIXED: Linked Node "${prototype.name}" back to definition Graph "${graph.name}"`);
2327+
repairCount++;
2328+
}
2329+
});
2330+
}
2331+
2332+
if (repairCount > 0) {
2333+
console.log(`[Repair Tool] ✅ Completed with ${repairCount} repairs.`);
2334+
// Force a store update trigger if needed, though Immer should handle it
2335+
} else {
2336+
console.log('[Repair Tool] No broken links found.');
2337+
}
2338+
}));
2339+
},
2340+
22722341
// Creates a new graph, assigns it as a definition to a prototype, and makes it active
22732342
createAndAssignGraphDefinition: (prototypeId) => {
22742343
let newGraphId = null;

0 commit comments

Comments
 (0)