@tscircuit/schematic-trace-solver 0.0.1

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.
Files changed (51) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/.github/workflows/bun-formatcheck.yml +26 -0
  3. package/.github/workflows/bun-pver-release.yml +28 -0
  4. package/.github/workflows/bun-test.yml +28 -0
  5. package/.github/workflows/bun-typecheck.yml +26 -0
  6. package/LICENSE +21 -0
  7. package/README.md +75 -0
  8. package/biome.json +93 -0
  9. package/bunfig.toml +5 -0
  10. package/cosmos.config.json +6 -0
  11. package/cosmos.decorator.tsx +21 -0
  12. package/dist/index.d.ts +420 -0
  13. package/dist/index.js +7863 -0
  14. package/index.html +17 -0
  15. package/lib/data-structures/ChipObstacleSpatialIndex.ts +70 -0
  16. package/lib/index.ts +2 -0
  17. package/lib/solvers/BaseSolver/BaseSolver.ts +83 -0
  18. package/lib/solvers/GuidelinesSolver/GuidelinesSolver.ts +138 -0
  19. package/lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts +39 -0
  20. package/lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts +18 -0
  21. package/lib/solvers/GuidelinesSolver/getInputChipBounds.ts +20 -0
  22. package/lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts +18 -0
  23. package/lib/solvers/GuidelinesSolver/visualizeGuidelines.ts +45 -0
  24. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +156 -0
  25. package/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +20 -0
  26. package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +96 -0
  27. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +192 -0
  28. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +479 -0
  29. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +127 -0
  30. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +191 -0
  31. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +132 -0
  32. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +42 -0
  33. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +244 -0
  34. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +89 -0
  35. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +180 -0
  36. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +264 -0
  37. package/lib/types/InputProblem.ts +40 -0
  38. package/lib/utils/dir.ts +16 -0
  39. package/lib/utils/getAllPossibleOrderingsGenerator.ts +47 -0
  40. package/lib/utils/getColorFromString.ts +7 -0
  41. package/package.json +29 -0
  42. package/site/components/GenericSolverDebugger.tsx +0 -0
  43. package/site/components/PipelineDebugger.tsx +29 -0
  44. package/site/components/PipelineStageTable.tsx +149 -0
  45. package/site/components/SolverBreadcrumbInputDownloader.tsx +62 -0
  46. package/site/components/SolverToolbar.tsx +128 -0
  47. package/site/examples/example01-basic.page.tsx +104 -0
  48. package/tests/functions/generateElbowVariants.test.ts +98 -0
  49. package/tests/functions/getOrthogonalMinimumSpanningTree.test.ts +23 -0
  50. package/tsconfig.json +37 -0
  51. package/vite.config.ts +12 -0
@@ -0,0 +1,149 @@
1
+ import type { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
2
+
3
+ type StageStatus = "Solved" | "Failed" | "Running" | "Not Started"
4
+
5
+ /**
6
+ * Displays every stage of the pipeline with the status ("Solved", "Failed", "Running" or "Not Started"),
7
+ * what iteration it started on, what iteration it ended on, and the time it took to solve.
8
+ *
9
+ * The table also has a column "Actions" that has a download icon to download the getConstructorParams()
10
+ * of each stage.
11
+ */
12
+ export const PipelineStageTable = ({
13
+ pipelineSolver,
14
+ triggerRender,
15
+ }: {
16
+ pipelineSolver: SchematicTracePipelineSolver
17
+ triggerRender: () => void
18
+ }) => {
19
+ const getStageStatus = (stageIndex: number): StageStatus => {
20
+ if (pipelineSolver.currentPipelineStepIndex > stageIndex) {
21
+ return "Solved"
22
+ }
23
+ if (pipelineSolver.currentPipelineStepIndex === stageIndex) {
24
+ if (pipelineSolver.failed) return "Failed"
25
+ if (pipelineSolver.activeSubSolver) return "Running"
26
+ }
27
+ return "Not Started"
28
+ }
29
+
30
+ const downloadParams = (stageName: string) => {
31
+ const stage = pipelineSolver.pipelineDef.find(
32
+ (s) => s.solverName === stageName,
33
+ )
34
+ if (!stage) return
35
+
36
+ try {
37
+ const params = stage.getConstructorParams(pipelineSolver)
38
+ const blob = new Blob([JSON.stringify(params, null, 2)], {
39
+ type: "application/json",
40
+ })
41
+ const url = URL.createObjectURL(blob)
42
+ const a = document.createElement("a")
43
+ a.href = url
44
+ a.download = `${stageName}_params.json`
45
+ a.click()
46
+ URL.revokeObjectURL(url)
47
+ } catch (error) {
48
+ alert(
49
+ `Error downloading params for ${stageName}: ${error instanceof Error ? error.message : String(error)}`,
50
+ )
51
+ }
52
+ }
53
+
54
+ return (
55
+ <div className="overflow-x-auto">
56
+ <table className="min-w-full border border-gray-300">
57
+ <thead>
58
+ <tr className="bg-gray-100">
59
+ <th className="border border-gray-300 px-4 py-2 text-left">
60
+ Stage
61
+ </th>
62
+ <th className="border border-gray-300 px-4 py-2 text-left">
63
+ Status
64
+ </th>
65
+ <th className="border border-gray-300 px-4 py-2 text-left">
66
+ Start Iteration
67
+ </th>
68
+ <th className="border border-gray-300 px-4 py-2 text-left">
69
+ End Iteration
70
+ </th>
71
+ <th className="border border-gray-300 px-4 py-2 text-left">
72
+ Time (ms)
73
+ </th>
74
+ <th className="border border-gray-300 px-4 py-2 text-left">
75
+ Actions
76
+ </th>
77
+ </tr>
78
+ </thead>
79
+ <tbody>
80
+ {pipelineSolver.pipelineDef.map((stage, index) => {
81
+ const status = getStageStatus(index)
82
+ const startIteration =
83
+ pipelineSolver.firstIterationOfPhase[stage.solverName]
84
+ const endIteration =
85
+ status === "Solved"
86
+ ? (pipelineSolver.firstIterationOfPhase[stage.solverName] ||
87
+ 0) +
88
+ ((pipelineSolver as any)[stage.solverName]?.iterations || 0)
89
+ : undefined
90
+ const timeSpent = pipelineSolver.timeSpentOnPhase[stage.solverName]
91
+
92
+ return (
93
+ <tr key={stage.solverName} className="hover:bg-gray-50">
94
+ <td className="border border-gray-300 px-4 py-2 font-mono text-sm">
95
+ {stage.solverName}
96
+ </td>
97
+ <td className="border border-gray-300 px-4 py-2">
98
+ <div className="flex items-center gap-2">
99
+ <span
100
+ className={`px-2 py-1 rounded text-sm ${
101
+ status === "Solved"
102
+ ? "bg-green-100 text-green-800"
103
+ : status === "Failed"
104
+ ? "bg-red-100 text-red-800"
105
+ : status === "Running"
106
+ ? "bg-yellow-100 text-yellow-800"
107
+ : "bg-gray-100 text-gray-800"
108
+ }`}
109
+ >
110
+ {status}
111
+ </span>
112
+ <button
113
+ onClick={() => {
114
+ pipelineSolver.solveUntilPhase(stage.solverName)
115
+ triggerRender()
116
+ }}
117
+ className="hover:bg-green-500 text-gray-600 hover:text-white px-2 py-1 rounded text-sm"
118
+ title={`Run until ${stage.solverName} is active`}
119
+ >
120
+ ▶️
121
+ </button>
122
+ </div>
123
+ </td>
124
+ <td className="border border-gray-300 px-4 py-2 text-center">
125
+ {startIteration !== undefined ? startIteration : "-"}
126
+ </td>
127
+ <td className="border border-gray-300 px-4 py-2 text-center">
128
+ {endIteration !== undefined ? endIteration : "-"}
129
+ </td>
130
+ <td className="border border-gray-300 px-4 py-2 text-center">
131
+ {timeSpent !== undefined ? Math.round(timeSpent) : "-"}
132
+ </td>
133
+ <td className="border border-gray-300 px-4 py-2 text-center">
134
+ <button
135
+ onClick={() => downloadParams(stage.solverName)}
136
+ className="bg-blue-500 hover:bg-blue-600 text-white px-2 py-1 rounded text-sm"
137
+ title="Download constructor params"
138
+ >
139
+ ⬇️
140
+ </button>
141
+ </td>
142
+ </tr>
143
+ )
144
+ })}
145
+ </tbody>
146
+ </table>
147
+ </div>
148
+ )
149
+ }
@@ -0,0 +1,62 @@
1
+ import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
+
3
+ export const getSolverChain = (solver: BaseSolver): BaseSolver[] => {
4
+ if (!solver.activeSubSolver) {
5
+ return [solver]
6
+ }
7
+ return [solver, ...getSolverChain(solver.activeSubSolver)]
8
+ }
9
+
10
+ /**
11
+ * Displays each solver in the chain as a breadcrumb with download functionality
12
+ */
13
+ export const SolverBreadcrumbInputDownloader = ({
14
+ solver,
15
+ }: {
16
+ solver: BaseSolver
17
+ }) => {
18
+ const solverChain = getSolverChain(solver)
19
+
20
+ const downloadSolverParams = (s: BaseSolver) => {
21
+ try {
22
+ if (typeof s.getConstructorParams !== "function") {
23
+ alert(
24
+ `getConstructorParams() is not implemented for ${s.constructor.name}`,
25
+ )
26
+ return
27
+ }
28
+
29
+ const params = s.getConstructorParams()
30
+ const blob = new Blob([JSON.stringify(params, null, 2)], {
31
+ type: "application/json",
32
+ })
33
+ const url = URL.createObjectURL(blob)
34
+ const a = document.createElement("a")
35
+ a.href = url
36
+ a.download = `${s.constructor.name}_params.json`
37
+ a.click()
38
+ URL.revokeObjectURL(url)
39
+ } catch (error) {
40
+ alert(
41
+ `Error downloading params for ${s.constructor.name}: ${error instanceof Error ? error.message : String(error)}`,
42
+ )
43
+ }
44
+ }
45
+
46
+ return (
47
+ <div className="flex gap-1 items-center text-sm pt-1">
48
+ {solverChain.map((s, index) => (
49
+ <div key={s.constructor.name} className="flex items-center">
50
+ {index > 0 && <span className="text-gray-400 mx-1">→</span>}
51
+ <button
52
+ className="px-2 py-1 rounded text-xs cursor-pointer"
53
+ onClick={() => downloadSolverParams(s)}
54
+ title={`Download constructor params for ${s.constructor.name}`}
55
+ >
56
+ {s.constructor.name}
57
+ </button>
58
+ </div>
59
+ ))}
60
+ </div>
61
+ )
62
+ }
@@ -0,0 +1,128 @@
1
+ import type { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
2
+ import { useReducer, useRef, useEffect } from "react"
3
+ import { SolverBreadcrumbInputDownloader } from "./SolverBreadcrumbInputDownloader"
4
+
5
+ /**
6
+ * The solver toolbar offers various actions for progressing the solver
7
+ * - "Step" - call solver.step()
8
+ * - "Solve" - call solver.solve()
9
+ * - "Animate" - call solver.step() at 40 iterations/second until solved or failed
10
+ * - When animate is pressed, show "Stop" button
11
+ *
12
+ * The toolbar also displays the number of iterations (solver.iterations)
13
+ *
14
+ * After any action, we need to incRenderCount internally to trigger a rerender
15
+ */
16
+ export const SolverToolbar = ({
17
+ solver,
18
+ triggerRender,
19
+ }: {
20
+ solver: BaseSolver
21
+ triggerRender: Function
22
+ }) => {
23
+ const [isAnimating, setIsAnimating] = useReducer((x) => !x, false)
24
+ const animationRef = useRef<number | undefined>(undefined)
25
+
26
+ const handleStep = () => {
27
+ solver.step()
28
+ triggerRender()
29
+ }
30
+
31
+ const handleSolve = () => {
32
+ solver.solve()
33
+ triggerRender()
34
+ }
35
+
36
+ const handleAnimate = () => {
37
+ if (isAnimating) {
38
+ if (animationRef.current) {
39
+ clearInterval(animationRef.current)
40
+ animationRef.current = undefined
41
+ }
42
+ setIsAnimating()
43
+ } else {
44
+ setIsAnimating()
45
+ animationRef.current = window.setInterval(() => {
46
+ if (solver.solved || solver.failed) {
47
+ if (animationRef.current) {
48
+ clearInterval(animationRef.current)
49
+ animationRef.current = undefined
50
+ }
51
+ setIsAnimating()
52
+ triggerRender()
53
+ return
54
+ }
55
+ solver.step()
56
+ triggerRender()
57
+ }, 25) // 40 iterations/second = 25ms interval
58
+ }
59
+ }
60
+
61
+ useEffect(() => {
62
+ return () => {
63
+ if (animationRef.current) {
64
+ clearInterval(animationRef.current)
65
+ }
66
+ }
67
+ }, [])
68
+
69
+ useEffect(() => {
70
+ if ((solver.solved || solver.failed) && isAnimating) {
71
+ if (animationRef.current) {
72
+ clearInterval(animationRef.current)
73
+ animationRef.current = undefined
74
+ }
75
+ setIsAnimating()
76
+ }
77
+ }, [solver.solved, solver.failed, isAnimating])
78
+
79
+ return (
80
+ <div className="space-y-1 px-1">
81
+ <SolverBreadcrumbInputDownloader solver={solver} />
82
+
83
+ <div className="flex gap-2 pb-1 items-center">
84
+ <button
85
+ onClick={handleStep}
86
+ disabled={solver.solved || solver.failed || isAnimating}
87
+ className="bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white px-3 py-1 rounded"
88
+ >
89
+ Step
90
+ </button>
91
+
92
+ <button
93
+ onClick={handleSolve}
94
+ disabled={solver.solved || solver.failed || isAnimating}
95
+ className="bg-green-500 hover:bg-green-600 disabled:bg-gray-300 text-white px-3 py-1 rounded"
96
+ >
97
+ Solve
98
+ </button>
99
+
100
+ <button
101
+ onClick={handleAnimate}
102
+ disabled={solver.solved || solver.failed}
103
+ className={`px-3 py-1 rounded text-white ${
104
+ isAnimating
105
+ ? "bg-red-500 hover:bg-red-600"
106
+ : "bg-yellow-500 hover:bg-yellow-600"
107
+ } disabled:bg-gray-300`}
108
+ >
109
+ {isAnimating ? "Stop" : "Animate"}
110
+ </button>
111
+
112
+ <div className="ml-4 text-sm text-gray-600">
113
+ Iterations: {solver.iterations}
114
+ </div>
115
+
116
+ {solver.solved && (
117
+ <div className="ml-2 px-2 py-1 bg-green-100 text-green-800 rounded text-sm">
118
+ Solved
119
+ </div>
120
+ )}
121
+ </div>
122
+
123
+ {solver.failed && (
124
+ <div className="text-red-500">Failed: {solver.error}</div>
125
+ )}
126
+ </div>
127
+ )
128
+ }
@@ -0,0 +1,104 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
3
+
4
+ const inputProblem: InputProblem = {
5
+ chips: [
6
+ {
7
+ chipId: "U1",
8
+ center: { x: 0, y: 0 },
9
+ width: 1.6,
10
+ height: 0.6,
11
+ pins: [
12
+ {
13
+ pinId: "U1.1",
14
+ x: -0.8,
15
+ y: 0.2,
16
+ },
17
+ {
18
+ pinId: "U1.2",
19
+ x: -0.8,
20
+ y: 0,
21
+ },
22
+ {
23
+ pinId: "U1.3",
24
+ x: -0.8,
25
+ y: -0.2,
26
+ },
27
+ {
28
+ pinId: "U1.4",
29
+ x: 0.8,
30
+ y: -0.2,
31
+ },
32
+ {
33
+ pinId: "U1.5",
34
+ x: 0.8,
35
+ y: 0,
36
+ },
37
+ {
38
+ pinId: "U1.6",
39
+ x: 0.8,
40
+ y: 0.2,
41
+ },
42
+ ],
43
+ },
44
+ {
45
+ chipId: "C1",
46
+ center: { x: -2, y: 0 },
47
+ width: 0.5,
48
+ height: 1,
49
+ pins: [
50
+ {
51
+ pinId: "C1.1",
52
+ x: -2,
53
+ y: 0.5,
54
+ },
55
+ {
56
+ pinId: "C1.2",
57
+ x: -2,
58
+ y: -0.5,
59
+ },
60
+ ],
61
+ },
62
+ {
63
+ chipId: "C2",
64
+ center: { x: -4, y: 0 },
65
+ width: 0.5,
66
+ height: 1,
67
+ pins: [
68
+ {
69
+ pinId: "C2.1",
70
+ x: -4,
71
+ y: 0.5,
72
+ },
73
+ {
74
+ pinId: "C2.2",
75
+ x: -4,
76
+ y: -0.5,
77
+ },
78
+ ],
79
+ },
80
+ ],
81
+ directConnections: [
82
+ {
83
+ pinIds: ["U1.1", "C1.1"],
84
+ netId: "VCC",
85
+ },
86
+ {
87
+ pinIds: ["U1.2", "C2.1"],
88
+ netId: "EN",
89
+ },
90
+ ],
91
+ netConnections: [
92
+ {
93
+ pinIds: ["U1.3", "C2.2", "C1.2"],
94
+ netId: "GND",
95
+ },
96
+ ],
97
+ availableNetLabelOrientations: {
98
+ VCC: ["y+", "y-"],
99
+ EN: ["x+", "x-"],
100
+ GND: ["y+", "y-"],
101
+ },
102
+ }
103
+
104
+ export default () => <PipelineDebugger inputProblem={inputProblem} />
@@ -0,0 +1,98 @@
1
+ import { generateElbowVariants } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants"
2
+ import { test, expect } from "bun:test"
3
+ import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver"
4
+ import type { Point } from "@tscircuit/math-utils"
5
+
6
+ test("generateElbowVariants - simple horizontal segment", () => {
7
+ const baseElbow: Point[] = [
8
+ { x: 0, y: 0 },
9
+ { x: 1, y: 0 },
10
+ { x: 1, y: 2 },
11
+ { x: 3, y: 2 },
12
+ ]
13
+
14
+ const guidelines: Guideline[] = [
15
+ { orientation: "vertical", x: 0.5, y: undefined },
16
+ { orientation: "vertical", x: 1.5, y: undefined },
17
+ { orientation: "horizontal", x: undefined, y: 1 },
18
+ ]
19
+
20
+ const result = generateElbowVariants({ baseElbow, guidelines })
21
+
22
+ expect(result.movableSegments).toHaveLength(2)
23
+ expect(result.movableSegments[0].freedom).toBe("y+")
24
+ expect(result.movableSegments[1].freedom).toBe("x+")
25
+
26
+ // Should generate variants for each combination of guideline positions
27
+ expect(result.elbowVariants.length).toBeGreaterThan(1)
28
+ })
29
+
30
+ test("generateElbowVariants - no movable segments", () => {
31
+ const baseElbow: Point[] = [
32
+ { x: 0, y: 0 },
33
+ { x: 1, y: 1 },
34
+ ]
35
+
36
+ const guidelines: Guideline[] = [
37
+ { orientation: "vertical", x: 0.5, y: undefined },
38
+ ]
39
+
40
+ const result = generateElbowVariants({ baseElbow, guidelines })
41
+
42
+ expect(result.movableSegments).toHaveLength(0)
43
+ expect(result.elbowVariants).toHaveLength(1)
44
+ expect(result.elbowVariants[0]).toEqual(baseElbow)
45
+ })
46
+
47
+ test("generateElbowVariants - vertical movable segment", () => {
48
+ const baseElbow: Point[] = [
49
+ { x: 0, y: 0 },
50
+ { x: 0, y: 1 },
51
+ { x: 0, y: 2 },
52
+ { x: 2, y: 2 },
53
+ ]
54
+
55
+ const guidelines: Guideline[] = [
56
+ { orientation: "horizontal", x: undefined, y: 0.5 },
57
+ { orientation: "horizontal", x: undefined, y: 1.5 },
58
+ ]
59
+
60
+ const result = generateElbowVariants({ baseElbow, guidelines })
61
+
62
+ expect(result.movableSegments).toHaveLength(1)
63
+ expect(result.movableSegments[0].freedom).toBe("x+")
64
+
65
+ // Should include original position plus guideline positions
66
+ expect(result.elbowVariants.length).toBe(3) // Original + 2 guidelines
67
+ })
68
+
69
+ test("generateElbowVariants - multiple segments with guidelines", () => {
70
+ const baseElbow: Point[] = [
71
+ { x: 0, y: 0 },
72
+ { x: 1, y: 0 },
73
+ { x: 1, y: 1 },
74
+ { x: 2, y: 1 },
75
+ { x: 2, y: 2 },
76
+ ]
77
+
78
+ const guidelines: Guideline[] = [
79
+ { orientation: "vertical", x: 0.5, y: undefined },
80
+ { orientation: "vertical", x: 1.5, y: undefined },
81
+ { orientation: "horizontal", x: undefined, y: 0.5 },
82
+ { orientation: "horizontal", x: undefined, y: 1.5 },
83
+ ]
84
+
85
+ const result = generateElbowVariants({ baseElbow, guidelines })
86
+
87
+ expect(result.movableSegments).toHaveLength(3)
88
+
89
+ // First segment moves vertically (y+/y-)
90
+ expect(result.movableSegments[0].freedom).toMatch(/^y[+-]$/)
91
+ // Second segment moves horizontally (x+/x-)
92
+ expect(result.movableSegments[1].freedom).toMatch(/^x[+-]$/)
93
+ // Third segment moves vertically (y+/y-)
94
+ expect(result.movableSegments[2].freedom).toMatch(/^y[+-]$/)
95
+
96
+ // Should generate multiple variants
97
+ expect(result.elbowVariants.length).toBeGreaterThan(1)
98
+ })
@@ -0,0 +1,23 @@
1
+ import { getOrthogonalMinimumSpanningTree } from "lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins"
2
+ import { test, expect } from "bun:test"
3
+ import type { InputPin } from "lib/types/InputProblem"
4
+
5
+ test("getOrthogonalMinimumSpanningTree", () => {
6
+ const pins: InputPin[] = [
7
+ { x: 0, y: 0, pinId: "A" },
8
+ { x: 1, y: 1, pinId: "B" },
9
+ { x: 10, y: 10, pinId: "C" },
10
+ { x: 11, y: 11, pinId: "D" },
11
+ ]
12
+ const msp = getOrthogonalMinimumSpanningTree(pins)
13
+ expect(
14
+ msp
15
+ .map(([a, b]) => `${a}->${b}`)
16
+ .sort()
17
+ .join("\n"),
18
+ ).toMatchInlineSnapshot(`
19
+ "B->A
20
+ C->B
21
+ D->C"
22
+ `)
23
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ "paths": {
12
+ "lib/*": ["lib/*"],
13
+ "site/*": ["site/*"],
14
+ "tests/*": ["tests/*"]
15
+ },
16
+
17
+ "baseUrl": ".",
18
+
19
+ // Bundler mode
20
+ "moduleResolution": "bundler",
21
+ "allowImportingTsExtensions": true,
22
+ "verbatimModuleSyntax": true,
23
+ "noEmit": true,
24
+
25
+ // Best practices
26
+ "strict": true,
27
+ "skipLibCheck": true,
28
+ "noFallthroughCasesInSwitch": true,
29
+ "noUncheckedIndexedAccess": false,
30
+ "noImplicitOverride": true,
31
+
32
+ // Some stricter flags (disabled by default)
33
+ "noUnusedLocals": false,
34
+ "noUnusedParameters": false,
35
+ "noPropertyAccessFromIndexSignature": false
36
+ }
37
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from "vite"
2
+ import path from "path"
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ lib: path.resolve(__dirname, "lib"),
8
+ site: path.resolve(__dirname, "site"),
9
+ tests: path.resolve(__dirname, "tests"),
10
+ },
11
+ },
12
+ })