Routing nchor added

This commit is contained in:
2026-05-29 21:51:57 +08:00
parent 1215bf978a
commit 07ee7f9dd7
22 changed files with 3230 additions and 426 deletions
+281 -2
View File
@@ -4,11 +4,16 @@ const path = require('path');
const root = path.resolve(__dirname, '..');
const canvasHtml = fs.readFileSync(path.join(root, 'frontend', 'canvas.html'), 'utf8');
const canvasHelpers = fs.readFileSync(path.join(root, 'frontend', 'canvas-helpers.js'), 'utf8');
assert(
canvasHtml.includes('Build GDS'),
'Project Tree header should include a Build GDS button'
);
assert(
canvasHtml.includes('Save YAML for all canvases') && canvasHtml.includes('handleSaveProjectLayouts'),
'Project Tree should include a save button that writes YAML for all canvases'
);
assert(
canvasHtml.includes('/api/build-gds'),
'Build GDS button should call the backend build-gds API'
@@ -70,6 +75,280 @@ assert(
'route editor should offer standard_bend as a routing type'
);
assert(
canvasHtml.includes('findSameFamilyRouteCrossing'),
'canvas should validate same-family route crossings'
canvasHtml.includes('findSameTypeRouteCrossing'),
'canvas should validate same-xsection route crossings'
);
assert(
canvasHtml.includes('link-mode-tabs') &&
canvasHtml.includes('link-mode-summary') &&
canvasHtml.includes('link-mode-menu') &&
canvasHtml.includes('currentLinkXsection') &&
canvasHtml.includes('setCurrentLinkXsection') &&
canvasHtml.includes("['strip', 'rib_low', 'metal_1', 'metal_2']"),
'canvas should expose a collapsed route-type selector for new links'
);
assert(
canvasHtml.includes('handleBasicConnection') &&
canvasHtml.includes('onConnect={handleBasicConnection}') &&
canvasHtml.includes('nodesConnectable={true}') &&
canvasHtml.includes('connectionMode="loose"') &&
canvasHtml.includes('data: { route }') &&
canvasHtml.includes('addEdge(candidate, p.edges)'),
'canvas should use React Flow native pin-to-pin connections for new links'
);
assert(
!canvasHtml.includes('linkDraft') &&
!canvasHtml.includes('routingMode') &&
!canvasHtml.includes('toggleRoutingMode') &&
!canvasHtml.includes('handleLinkCanvasMouseDown') &&
!canvasHtml.includes('handleLinkCanvasMouseMove') &&
!canvasHtml.includes('finalizeLinkDraft') &&
!canvasHtml.includes('__link_draft_edge__') &&
!canvasHtml.includes('Routing mode: click anywhere to start a point route.'),
'current interactive point-link drawing mode should be removed from the canvas'
);
assert(
canvasHtml.includes('<Handle') &&
canvasHtml.includes('type="source"') &&
canvasHtml.includes('type="target"') &&
!canvasHtml.includes('nodesConnectable={false}'),
'component and port handles should remain connectable for basic pin linking'
);
assert(
canvasHtml.includes("selectable: true") &&
canvasHtml.includes("type: 'parallelRoute'") &&
canvasHtml.includes('hasRoutePoints') &&
canvasHtml.includes("hasRoutePoints ? 'parallelRoute' : view.type") &&
canvasHtml.includes('data-route-edge-id') &&
canvasHtml.includes('handleRouteEdgeMouseDown') &&
canvasHtml.includes('selectEdgeById') &&
canvasHtml.includes('vectorEffect="non-scaling-stroke"') &&
canvasHtml.includes('onEdgeMouseDown={handleReactFlowEdgeMouseDown}'),
'saved manual routes should remain selectable through a zoom-independent custom SVG hit path and React Flow edge mouse handling'
);
assert(
canvasHtml.includes('panOnDrag={false}'),
'left mouse drag should not pan the canvas'
);
assert(
canvasHtml.includes('selectionOnDrag={true}') &&
canvasHtml.includes('selectionMode={FULL_SELECTION_MODE}') &&
canvasHtml.includes('SelectionMode'),
'left mouse drag should draw a full-coverage selection area instead of panning'
);
assert(
canvasHtml.includes('const path = points.length >= 2') &&
canvasHtml.includes('points.slice(1).map(point => `L ${point.x},${point.y}`)') &&
canvasHtml.includes('position: { x: first.x - 6, y: first.y - 6 }') &&
canvasHtml.includes('position: { x: last.x - 6, y: last.y - 6 }'),
'free routes should render all saved point-to-point line segments and keep hidden endpoints aligned'
);
assert(
!canvasHtml.includes('buildOrthogonalPoints') &&
!canvasHtml.includes('buildManhattanRoutePoints') &&
!canvasHtml.includes('findNearestPort') &&
!canvasHtml.includes('linkPreviewSnapPort'),
'custom link drawing geometry helpers should be deleted from the current canvas code'
);
const routePointNodeBlock = canvasHtml.slice(
canvasHtml.indexOf('const RulerPointNode'),
canvasHtml.indexOf('const RulerMeasurementNode')
);
assert(
routePointNodeBlock.includes('<Handle') &&
routePointNodeBlock.includes('type="source"') &&
routePointNodeBlock.includes('type="target"') &&
routePointNodeBlock.includes('id="route"'),
'route point nodes should expose hidden source and target handles so route edges can attach and render'
);
assert(
canvasHtml.includes('build-progress') && canvasHtml.includes('buildProgress'),
'Build Layout and Build GDS should show progress while running'
);
assert(
canvasHtml.includes('port-name-label'),
'component nodes should render visible port name labels'
);
assert(
canvasHtml.includes('multiSelectionKeyCode="Shift"'),
'ReactFlow should allow shift multi-select for links'
);
assert(
canvasHtml.includes("deleteKeyCode={['Backspace', 'Delete']}"),
'ReactFlow should allow selected links to be deleted with the Delete key'
);
assert(
canvasHtml.includes('selectedEdges'),
'canvas should track multiple selected links for batch route editing'
);
assert(
canvasHtml.includes('__mixed__'),
'multi-link route editor should show mixed values as --'
);
assert(
canvasHtml.includes('Flip X') && canvasHtml.includes('Mirror Y'),
'component inspector should expose flip/flop controls'
);
assert(
canvasHtml.includes('onNodeMouseDown') && canvasHtml.includes('spaceRotateNodeIdRef'),
'holding a component and pressing Space should rotate it by 90 degrees'
);
assert(
canvasHtml.includes('getSpaceRotationTarget') && canvasHtml.includes('selectedSpaceNode'),
'Space rotation should also use the currently selected component when no mouse-hold target is active'
);
assert(
canvasHtml.includes('normalizeAngle,') && canvasHtml.includes('normalizeAngle(Number(node.data?.rotation || 0) + 90)'),
'Space rotation should import normalizeAngle before using it'
);
assert(
canvasHtml.includes('component-floating-label') && canvasHtml.includes('component-visual-body'),
'component labels should float outside the rotated body'
);
assert(
canvasHtml.includes('--floating-label-bg') && canvasHtml.includes('--port-label-bg') && canvasHtml.includes('--mini-button-bg'),
'theme variables should keep labels, port chips, and header buttons readable in light and dark modes'
);
assert(
canvasHtml.includes('className="site-nav-actions"') &&
canvasHtml.includes('className="canvas-toolbar"') &&
canvasHtml.includes('grid-snap-label') &&
canvasHtml.includes('body.light-mode .canvas-toolbar'),
'dashboard/logout should move to a site-level top-right action group and the canvas toolbar should keep readable grid text in light mode'
);
assert(
canvasHtml.includes('body.light-mode .component-floating-label') &&
canvasHtml.includes('body.light-mode .port-name-label') &&
canvasHtml.includes('body.light-mode .mini-btn'),
'light mode should override dark translucent label and button surfaces'
);
assert(
canvasHtml.includes('Canvas Size') && canvasHtml.includes('canvasSize') && canvasHtml.includes('DEFAULT_CANVAS_SIZE'),
'project tree should expose a canvas size control with a 5000 um default'
);
assert(
canvasHtml.includes('CanvasBoundaryNode') && canvasHtml.includes('canvas-boundary-node'),
'canvas should render a bold boundary rectangle in flow coordinates'
);
assert(
canvasHtml.includes('renderNodes') && canvasHtml.includes('nodeExtent={canvasNodeExtent}'),
'ReactFlow should render the boundary node and constrain draggable nodes to the canvas extent'
);
assert(
canvasHtml.includes('minZoom={0.02}') && canvasHtml.includes('defaultViewport={{ x: 80, y: 80, zoom: 0.12 }}'),
'large 5000 um canvases should zoom out far enough to fit on one screen'
);
assert(
canvasHtml.includes('onWheel={handleWheel}') &&
canvasHtml.includes('calculateLayoutBounds(activePage)') &&
canvasHtml.includes('layoutBounds') &&
canvasHtml.includes('stageWidth') &&
canvasHtml.includes('stageHeight'),
'layout preview should mouse-wheel zoom and size 100% from calculated box_size layout bounds'
);
assert(
canvasHtml.includes('reactFlowInstance.fitBounds') &&
canvasHtml.includes('width: activeCanvasSize.width') &&
canvasHtml.includes('height: activeCanvasSize.height'),
'switching canvases should fit the full canvas boundary instead of resetting to 100% zoom'
);
assert(
canvasHtml.includes('boxSize') && canvasHtml.includes('normalizeBoxSize'),
'component metadata box_size should drive rendered component box dimensions'
);
assert(
canvasHtml.includes('chooseCategoryComponent') && canvasHtml.includes('[id, data.ports, data.componentName, data.boxSize]'),
'category drops and metadata refreshes should remeasure components with YAML box sizes'
);
assert(
canvasHtml.includes('Background color="#334155" gap={10} size={1}'),
'default grid spacing should be 10 um'
);
assert(
canvasHtml.includes('Ruler') &&
canvasHtml.includes('rulerMode') &&
canvasHtml.includes('onPaneClick={handleRulerPaneClick}') &&
canvasHtml.includes('onNodeClick={handleRulerPaneClick}') &&
canvasHtml.includes('onPaneMouseMove={handleRulerMouseMove}'),
'canvas should expose a ruler mode controlled from the top toolbar, allow measuring on component bodies, and preview to the mouse'
);
assert(
canvasHtml.includes('createRulerMeasurement') &&
canvasHtml.includes('rulerPointNode') &&
canvasHtml.includes('rulerMeasurementNode') &&
canvasHtml.includes('rulerPreviewPoint') &&
canvasHtml.includes('strokeDasharray: rulerPreviewMeasurement ? undefined') &&
canvasHtml.includes('renderEdges'),
'ruler mode should render temporary point nodes, a live solid preview edge, and a measurement label'
);
assert(
canvasHtml.includes('Box Size') &&
canvasHtml.includes('selectedNodeBoxSize') &&
canvasHtml.includes('box-size-readout'),
'component inspector should show the selected component YAML box size'
);
assert(
canvasHtml.includes('coordinate-grid'),
'selected component coordinates should be displayed horizontally in the right panel'
);
assert(
canvasHtml.includes('selectedPositionNodes.length > 1') &&
canvasHtml.includes('const delta = val - Number') &&
canvasHtml.includes('selectedNodes={selectedNodes}'),
'multi-selected components should move together when editing X or Y in the inspector'
);
assert(
canvasHtml.includes('portInfo.description') || canvasHtml.includes('port.description'),
'port information should append optional human-readable port descriptions'
);
assert(
canvasHtml.includes('BASIC_COMPONENTS') &&
canvasHelpers.includes('BASIC_COMPONENTS') &&
canvasHelpers.includes('waveguide') &&
canvasHelpers.includes('90 bend') &&
canvasHelpers.includes('180 bend') &&
canvasHelpers.includes('circle') &&
canvasHelpers.includes('taper'),
'component library should expose basic Nazca primitives'
);
assert(
canvasHtml.includes('Cells: cellEntries') &&
canvasHtml.includes('Basic: basicEntries') &&
canvasHtml.includes('PDK: library || {}'),
'component library should keep top-level Cells, Basic, and PDK folders'
);
assert(
canvasHtml.includes('isDirectLeafGrid ? (') &&
canvasHtml.includes('<div className="category-grid">') &&
canvasHtml.includes("isUserCell ? 'compact-tree-card' : ''"),
'Basic, Port, and Anchor entries should render as consistent 2D cards instead of compact list rows'
);
assert(
canvasHtml.includes('ParallelRouteEdge') &&
canvasHtml.includes('parallelOffset') &&
canvasHtml.includes("type: 'parallelRoute'") &&
canvasHtml.includes('edgeTypes={edgeTypes}'),
'overlapped links should render with separated parallel edge paths'
);
assert(
canvasHtml.includes('data: { route, points: routePoints }') &&
canvasHtml.includes('normalizeRoutePoints(link.points'),
'manual link route points should be stored on edges and restored from saved YAML'
);
assert(
canvasHtml.includes('createComponentSymbolMetrics') &&
!canvasHtml.includes('Math.min(128') &&
!canvasHtml.includes('Math.min(64'),
'component icon/symbol dimensions should scale with component box size without old hard caps'
);
assert(
canvasHtml.includes('/api/library?project=') &&
canvasHtml.includes('/api/component/${encodeURIComponent(componentName)}?project=') &&
canvasHtml.includes('/api/component/${encodeURIComponent(compName)}?project='),
'canvas should pass project context when loading library and component metadata'
);
assert(
canvasHtml.includes('/api/component/${encodeURIComponent(componentData.name)}/image?project=') &&
canvasHtml.includes('return obj.__path__.split'),
'component images and saved component paths should use technology-scoped library metadata'
);