Skip to content

Commit 9cdfe48

Browse files
committed
fix: fixed docs
1 parent 555931b commit 9cdfe48

4 files changed

Lines changed: 99 additions & 31 deletions

File tree

docs/archive/SEMANTIC_IMPROVEMENTS_SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ console.log(match.shouldMerge); // true
357357
## Testing Checklist
358358
359359
- [x] Orbit resolver imports work without errors
360-
- [ ] Property-path queries return results in <3 seconds
360+
- [ ] Property-path queries return results in \<3 seconds
361361
- [ ] Entity deduplication merges obvious duplicates
362362
- [ ] Radial layout has no node overlaps
363363
- [ ] Connection routing avoids node collisions

scripts/generate-docs.js

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,12 +190,12 @@ class DocumentationGenerator {
190190
*/
191191
isReactComponent(content) {
192192
return content.includes('import React') ||
193-
content.includes('from "react"') ||
194-
content.includes('from \'react\'') ||
195-
content.includes('jsx') ||
196-
content.includes('return (') ||
197-
content.includes('useState') ||
198-
content.includes('useEffect');
193+
content.includes('from "react"') ||
194+
content.includes('from \'react\'') ||
195+
content.includes('jsx') ||
196+
content.includes('return (') ||
197+
content.includes('useState') ||
198+
content.includes('useEffect');
199199
}
200200

201201
/**
@@ -315,6 +315,17 @@ class DocumentationGenerator {
315315
.trim();
316316
}
317317

318+
/**
319+
* Sanitize text for MDX body to prevent parsing errors
320+
*/
321+
sanitizeForMdx(text) {
322+
if (!text) return '';
323+
return text
324+
.replace(/<([0-9])/g, '\\<$1') // Escape < when followed by a digit
325+
.replace(/{/g, '\\{') // Escape { to prevent MDX expression parsing
326+
.replace(/}/g, '\\}'); // Escape }
327+
}
328+
318329
/**
319330
* Generate API reference documentation
320331
*/
@@ -337,23 +348,23 @@ This documentation is automatically generated from the Redstring source code.
337348
338349
<CardGroup cols={2}>
339350
${this.extractedData.classes.map(cls => ` <Card title="${cls.name}" href="/api/${cls.name.toLowerCase()}">
340-
${cls.description || `${cls.name} class documentation`}
351+
${this.sanitizeForMdx(cls.description) || `${cls.name} class documentation`}
341352
</Card>`).join('\n')}
342353
</CardGroup>
343354
344355
## Services
345356
346357
<CardGroup cols={2}>
347358
${this.extractedData.services.map(service => ` <Card title="${service.name}" href="/api/${service.name.toLowerCase()}">
348-
${service.description || `${service.name} service documentation`}
359+
${this.sanitizeForMdx(service.description) || `${service.name} service documentation`}
349360
</Card>`).join('\n')}
350361
</CardGroup>
351362
352363
## Components
353364
354365
<CardGroup cols={2}>
355366
${this.extractedData.components.map(comp => ` <Card title="${comp.name}" href="/components/${comp.name.toLowerCase()}">
356-
${comp.description || `${comp.name} component documentation`}
367+
${this.sanitizeForMdx(comp.description) || `${comp.name} component documentation`}
357368
</Card>`).join('\n')}
358369
</CardGroup>
359370
@@ -377,7 +388,7 @@ description: "React component: ${component.description || component.name}"
377388
378389
# ${component.name}
379390
380-
${component.description || `The ${component.name} component.`}
391+
${this.sanitizeForMdx(component.description) || `The ${component.name} component.`}
381392
382393
## Location
383394
\`${component.filePath}\`
@@ -419,7 +430,7 @@ description: "Service: ${service.description || service.name}"
419430
420431
# ${service.name}
421432
422-
${service.description || `The ${service.name} service.`}
433+
${this.sanitizeForMdx(service.description) || `The ${service.name} service.`}
423434
424435
## Location
425436
\`${service.filePath}\`
@@ -433,7 +444,7 @@ ${service.functions.length > 0 ? service.functions.map(func => `
433444
${func.signature}
434445
\`\`\`
435446
436-
${func.description || 'No description available.'}
447+
${this.sanitizeForMdx(func.description) || 'No description available.'}
437448
`).join('\n') : 'No functions detected.'}
438449
439450
---

src/NodeCanvas.jsx

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,19 +2158,17 @@ function NodeCanvas() {
21582158

21592159
const mousePositionRef = useRef({ x: 0, y: 0 });
21602160

2161-
// Document-level mouse tracking during drag (captures events even over panels)
2161+
// Document-level mouse tracking (captures events even over panels or when propagation is stopped)
21622162
useEffect(() => {
2163-
if (!draggingNodeInfo) return;
2164-
21652163
const handleDocumentMouseMove = (e) => {
21662164
mousePositionRef.current = { x: e.clientX, y: e.clientY };
21672165
};
21682166

2169-
document.addEventListener('mousemove', handleDocumentMouseMove);
2167+
document.addEventListener('mousemove', handleDocumentMouseMove, { passive: true });
21702168
return () => {
21712169
document.removeEventListener('mousemove', handleDocumentMouseMove);
21722170
};
2173-
}, [draggingNodeInfo]);
2171+
}, []);
21742172

21752173
// --- Grid Snapping Helpers ---
21762174
const snapToGrid = (mouseX, mouseY, nodeWidth, nodeHeight) => {
@@ -8090,6 +8088,27 @@ function NodeCanvas() {
80908088
if (currentGraph) {
80918089
const copied = copySelection(selectedInstanceIds, currentGraph, nodePrototypesMap, edgesMap);
80928090
clipboardRef.current = copied;
8091+
console.log(`[NodeCanvas] Copied ${selectedInstanceIds.size} nodes to clipboard`);
8092+
}
8093+
return;
8094+
}
8095+
8096+
// Cut (Ctrl/Cmd+X)
8097+
if (cmdOrCtrl && e.key === 'x' && selectedInstanceIds.size > 0) {
8098+
e.preventDefault();
8099+
const currentGraph = graphsMap.get(activeGraphId);
8100+
if (currentGraph) {
8101+
// First copy
8102+
const copied = copySelection(selectedInstanceIds, currentGraph, nodePrototypesMap, edgesMap);
8103+
clipboardRef.current = copied;
8104+
8105+
// Then remove
8106+
storeActions.removeMultipleNodeInstances(activeGraphId, selectedInstanceIds);
8107+
8108+
// Clear selection
8109+
setSelectedInstanceIds(new Set());
8110+
8111+
console.log(`[NodeCanvas] Cut ${selectedInstanceIds.size} nodes to clipboard`);
80938112
}
80948113
return;
80958114
}
@@ -8101,23 +8120,25 @@ function NodeCanvas() {
81018120
if (currentGraph) {
81028121
// Determine target position
81038122
let targetPos;
8104-
const svgElement = document.querySelector('.node-canvas-svg');
8123+
const svgElement = document.querySelector('.canvas');
81058124
const rect = svgElement?.getBoundingClientRect();
81068125

8107-
if (rect && !isTouchDeviceRef.current && mousePositionRef.current) {
8126+
if (rect && mousePositionRef.current) {
81088127
// Desktop: use mouse position converted to canvas coords
81098128
const clientX = mousePositionRef.current.x;
81108129
const clientY = mousePositionRef.current.y;
81118130
targetPos = {
81128131
x: (clientX - rect.left - panOffset.x) / zoomLevel + canvasSize.offsetX,
81138132
y: (clientY - rect.top - panOffset.y) / zoomLevel + canvasSize.offsetY
81148133
};
8134+
console.log(`[NodeCanvas] Pasting at mouse position:`, targetPos, { clientX, clientY, rectLeft: rect.left, rectTop: rect.top, panX: panOffset.x, panY: panOffset.y, zoom: zoomLevel });
81158135
} else {
81168136
// Mobile fallback: offset from original center
81178137
targetPos = {
81188138
x: clipboardRef.current.originalCenter.x + 50,
81198139
y: clipboardRef.current.originalCenter.y + 50
81208140
};
8141+
console.log(`[NodeCanvas] Pasting at fallback position:`, targetPos);
81218142
}
81228143

81238144
const result = pasteClipboard(
@@ -8139,15 +8160,7 @@ function NodeCanvas() {
81398160

81408161
if (isDeleteKey && nodesSelected) {
81418162
e.preventDefault();
8142-
const idsToDelete = new Set(selectedInstanceIds); // Use local selection state
8143-
8144-
// Call removeNodeInstance action for each selected ID
8145-
idsToDelete.forEach(id => {
8146-
storeActions.removeNodeInstance(activeGraphId, id);
8147-
});
8148-
8149-
// Clear local selection state AFTER dispatching actions
8150-
8163+
storeActions.removeMultipleNodeInstances(activeGraphId, selectedInstanceIds);
81518164
setSelectedInstanceIds(new Set());
81528165
} else if (isDeleteKey && edgeSelected) {
81538166
console.log('[NodeCanvas] Delete key pressed with edge selected:', {

src/store/graphStore.jsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,50 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
14551455
}));
14561456
},
14571457

1458+
// Remove multiple instances and their connected edges in a single transaction
1459+
removeMultipleNodeInstances: (graphId, instanceIds) => {
1460+
if (!instanceIds || (instanceIds instanceof Set ? instanceIds.size === 0 : instanceIds.length === 0)) return;
1461+
1462+
const instanceIdSet = instanceIds instanceof Set ? instanceIds : new Set(instanceIds);
1463+
1464+
api.setChangeContext({
1465+
type: 'node_delete_batch',
1466+
target: 'instance',
1467+
graphId,
1468+
count: instanceIdSet.size
1469+
});
1470+
1471+
set(produce((draft) => {
1472+
const graph = draft.graphs.get(graphId);
1473+
if (!graph) return;
1474+
1475+
// Find all edges connected to any of the instances being removed
1476+
const edgesToDelete = [];
1477+
for (const [edgeId, edge] of draft.edges.entries()) {
1478+
if (instanceIdSet.has(edge.sourceId) || instanceIdSet.has(edge.destinationId)) {
1479+
edgesToDelete.push(edgeId);
1480+
}
1481+
}
1482+
1483+
// Delete the edges
1484+
edgesToDelete.forEach(edgeId => {
1485+
draft.edges.delete(edgeId);
1486+
if (graph.edgeIds) {
1487+
const index = graph.edgeIds.indexOf(edgeId);
1488+
if (index > -1) graph.edgeIds.splice(index, 1);
1489+
}
1490+
});
1491+
1492+
// Delete the instances
1493+
instanceIdSet.forEach(instanceId => {
1494+
graph.instances.delete(instanceId);
1495+
draft.pendingDeletions.delete(instanceId);
1496+
});
1497+
1498+
console.log(`[removeMultipleNodeInstances] Deleted ${instanceIdSet.size} instances and ${edgesToDelete.length} edges`);
1499+
}));
1500+
},
1501+
14581502
// Immediately and permanently deletes a node instance (bypasses grace period)
14591503
forceDeleteNodeInstance: (graphId, instanceId) => {
14601504
const state = get();
@@ -2669,7 +2713,7 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
26692713
draft.textSettings.fontSize = v;
26702714
try {
26712715
localStorage.setItem('redstring_text_font_size', String(v));
2672-
} catch (_) {}
2716+
} catch (_) { }
26732717
})),
26742718

26752719
setTextLineSpacing: (value) => set(produce((draft) => {
@@ -2681,7 +2725,7 @@ const useGraphStore = create(saveCoordinatorMiddleware((set, get, api) => {
26812725
draft.textSettings.lineSpacing = v;
26822726
try {
26832727
localStorage.setItem('redstring_text_line_spacing', String(v));
2684-
} catch (_) {}
2728+
} catch (_) { }
26852729
})),
26862730

26872731
setLayoutScalePreset: (preset) => set(produce((draft) => {

0 commit comments

Comments
 (0)