@tscircuit/schematic-trace-solver 0.0.87 → 0.0.89
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.
- package/.agents/skills/schematic-trace-visualization/SKILL.md +115 -0
- package/.github/scripts/import-json-bug-report.ts +15 -1
- package/.github/workflows/json-bug-report.yml +3 -2
- package/AGENTS.md +22 -0
- package/dist/index.js +30 -22
- package/lib/solvers/Example28Solver/reroute.ts +10 -0
- package/lib/testing/PipelineStageDebugRunner.ts +344 -0
- package/package.json +3 -2
- package/scripts/debug-pipeline-stages.ts +146 -0
- package/site/bug-reports/bug-report-20260706T220324Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T020342Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T092615Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T134549Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T134722Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T140410Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T141025Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260707T141421Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +386 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.json +1471 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/bug-report-20260706T213649Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +357 -3448
- package/tests/bug-reports/bug-report-20260707T020342Z/__snapshots__/bug-report-20260707T020342Z.snap.svg +64 -0
- package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.json +233 -0
- package/tests/bug-reports/bug-report-20260707T020342Z/bug-report-20260707T020342Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +252 -0
- package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.json +1392 -0
- package/tests/bug-reports/bug-report-20260707T092615Z/bug-report-20260707T092615Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T134549Z/__snapshots__/bug-report-20260707T134549Z.snap.svg +124 -0
- package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.json +652 -0
- package/tests/bug-reports/bug-report-20260707T134549Z/bug-report-20260707T134549Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +73 -0
- package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.json +265 -0
- package/tests/bug-reports/bug-report-20260707T134722Z/bug-report-20260707T134722Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +154 -0
- package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.json +610 -0
- package/tests/bug-reports/bug-report-20260707T140410Z/bug-report-20260707T140410Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T141025Z/__snapshots__/bug-report-20260707T141025Z.snap.svg +67 -0
- package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.json +185 -0
- package/tests/bug-reports/bug-report-20260707T141025Z/bug-report-20260707T141025Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260707T141421Z/__snapshots__/bug-report-20260707T141421Z.snap.svg +114 -0
- package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.json +499 -0
- package/tests/bug-reports/bug-report-20260707T141421Z/bug-report-20260707T141421Z.test.ts +23 -0
- package/tests/examples/__snapshots__/example01.snap.svg +59 -162
- package/tests/examples/__snapshots__/example02.snap.svg +64 -273
- package/tests/examples/__snapshots__/example03.snap.svg +85 -290
- package/tests/examples/__snapshots__/example04.snap.svg +50 -94
- package/tests/examples/__snapshots__/example05.snap.svg +73 -191
- package/tests/examples/__snapshots__/example06.snap.svg +49 -95
- package/tests/examples/__snapshots__/example07.snap.svg +65 -218
- package/tests/examples/__snapshots__/example08.snap.svg +61 -143
- package/tests/examples/__snapshots__/example09.snap.svg +123 -493
- package/tests/examples/__snapshots__/example10.snap.svg +57 -136
- package/tests/examples/__snapshots__/example11.snap.svg +64 -173
- package/tests/examples/__snapshots__/example12.snap.svg +58 -159
- package/tests/examples/__snapshots__/example13.snap.svg +78 -348
- package/tests/examples/__snapshots__/example14.snap.svg +77 -293
- package/tests/examples/__snapshots__/example15.snap.svg +140 -984
- package/tests/examples/__snapshots__/example16.snap.svg +62 -162
- package/tests/examples/__snapshots__/example17.snap.svg +67 -198
- package/tests/examples/__snapshots__/example18.snap.svg +69 -249
- package/tests/examples/__snapshots__/example19.snap.svg +63 -202
- package/tests/examples/__snapshots__/example20.snap.svg +60 -148
- package/tests/examples/__snapshots__/example21.snap.svg +73 -298
- package/tests/examples/__snapshots__/example22.snap.svg +62 -156
- package/tests/examples/__snapshots__/example23.snap.svg +66 -172
- package/tests/examples/__snapshots__/example24.snap.svg +64 -164
- package/tests/examples/__snapshots__/example25.snap.svg +76 -299
- package/tests/examples/__snapshots__/example26.snap.svg +64 -164
- package/tests/examples/__snapshots__/example27.snap.svg +83 -300
- package/tests/examples/__snapshots__/example28.snap.svg +54 -115
- package/tests/examples/__snapshots__/example29.snap.svg +257 -1299
- package/tests/examples/__snapshots__/example30.snap.svg +91 -293
- package/tests/examples/__snapshots__/example31.snap.svg +47 -88
- package/tests/examples/__snapshots__/example32.snap.svg +107 -405
- package/tests/examples/__snapshots__/example33.snap.svg +71 -180
- package/tests/examples/__snapshots__/example34.snap.svg +55 -116
- package/tests/examples/__snapshots__/example35.snap.svg +69 -176
- package/tests/examples/__snapshots__/example36.snap.svg +65 -159
- package/tests/examples/__snapshots__/example37.snap.svg +75 -223
- package/tests/examples/__snapshots__/example38.snap.svg +46 -76
- package/tests/examples/__snapshots__/example39.snap.svg +51 -106
- package/tests/examples/__snapshots__/example40.snap.svg +83 -270
- package/tests/examples/__snapshots__/example41.snap.svg +54 -118
- package/tests/examples/__snapshots__/example42.snap.svg +67 -194
- package/tests/examples/__snapshots__/example43.snap.svg +62 -168
- package/tests/examples/__snapshots__/example44.snap.svg +86 -369
- package/tests/examples/__snapshots__/example45.snap.svg +60 -169
- package/tests/examples/__snapshots__/example46.snap.svg +80 -342
- package/tests/examples/__snapshots__/example47.snap.svg +82 -323
- package/tests/examples/__snapshots__/example48.snap.svg +64 -167
- package/tests/examples/__snapshots__/example49.snap.svg +120 -565
- package/tests/examples/__snapshots__/example50.snap.svg +71 -189
- package/tests/fixtures/getLastStepGraphicsObject.ts +38 -0
- package/tests/fixtures/getLastStepSvg.ts +9 -0
- package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +67 -218
- package/tests/repros/__snapshots__/manufacturePartNumber-text-box.snap.svg +47 -87
- package/tests/repros/__snapshots__/netlabel-connector-through-rail-label.snap.svg +73 -272
- package/tests/repros/__snapshots__/repro-atmega328p-fault-pullup.snap.svg +88 -0
- package/tests/repros/__snapshots__/repro-atmega328p-missing-gnd-netlabel.snap.svg +118 -0
- package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +94 -395
- package/tests/repros/__snapshots__/repro-cc2340r5.snap.svg +88 -467
- package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +109 -569
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +95 -459
- package/tests/repros/__snapshots__/repro51-overlap-junction-crossing.snap.svg +64 -176
- package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +51 -112
- package/tests/repros/assets/repro-atmega328p-fault-pullup.input.json +239 -0
- package/tests/repros/assets/repro-atmega328p-missing-gnd-netlabel.input.json +432 -0
- package/tests/repros/repro-atmega328p-fault-pullup.test.ts +11 -0
- package/tests/repros/repro-atmega328p-missing-gnd-netlabel.test.ts +16 -0
- package/tests/solvers/SchematicTraceSingleLineSolver2/__snapshots__/SchematicTraceSingleLineSolver2_01-example17-d1_1-u1_1.snap.svg +59 -159
- package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +54 -118
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +58 -245
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +73 -222
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +65 -204
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +65 -183
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +54 -139
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +61 -215
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +54 -124
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: schematic-trace-visualization
|
|
3
|
+
description: Work with schematic trace solver visualization in this repo. Use when changing solver `visualize()` or `preview()` output, adding debug graphics, creating SVG snapshots, inspecting pipeline stages, or generating graphics-debug artifacts for AI/headless review.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Schematic Trace Visualization
|
|
7
|
+
|
|
8
|
+
Use `GraphicsObject` from `graphics-debug` for solver debug views, SVG snapshots, and headless artifacts.
|
|
9
|
+
|
|
10
|
+
## Graphics Object Types
|
|
11
|
+
|
|
12
|
+
Import visualization types from `graphics-debug`:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import type {
|
|
16
|
+
GraphicsObject,
|
|
17
|
+
Point,
|
|
18
|
+
Line,
|
|
19
|
+
Rect,
|
|
20
|
+
Circle,
|
|
21
|
+
Text,
|
|
22
|
+
} from "graphics-debug"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Do not redefine these shapes in repo docs, tests, or utilities.
|
|
26
|
+
|
|
27
|
+
## Image Functions
|
|
28
|
+
|
|
29
|
+
This repo uses `graphics-debug` for SVG and PNG rendering:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import {
|
|
33
|
+
getPngBufferFromGraphicsObject,
|
|
34
|
+
getSvgFromGraphicsObject,
|
|
35
|
+
} from "graphics-debug"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Call them with a white background for stable artifacts:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const svg = getSvgFromGraphicsObject(graphics, { backgroundColor: "white" })
|
|
42
|
+
const png = await getPngBufferFromGraphicsObject(graphics, {
|
|
43
|
+
backgroundColor: "white",
|
|
44
|
+
pngWidth: 1536,
|
|
45
|
+
pngHeight: 1536,
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Existing Repo Helpers
|
|
50
|
+
|
|
51
|
+
- `tests/fixtures/matcher.ts`: `expect(solver).toMatchSolverSnapshot(import.meta.path)` converts `solver.visualize()` to SVG and snapshots it.
|
|
52
|
+
- `tests/fixtures/getLastStepGraphicsObject.ts`: filters a `GraphicsObject` to its highest numbered `step`.
|
|
53
|
+
- `tests/fixtures/getLastStepSvg.ts`: converts only the last step to SVG.
|
|
54
|
+
- `lib/testing/PipelineStageDebugRunner.ts`: runs `SchematicTracePipelineSolver` and writes per-stage PNG/SVG/graphics JSON artifacts.
|
|
55
|
+
- `scripts/debug-pipeline-stages.ts`: CLI wrapper around the stage debug runner.
|
|
56
|
+
|
|
57
|
+
## Minimal SVG Tests
|
|
58
|
+
|
|
59
|
+
Prefer focused solver SVG snapshots over broad dumps:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const solver = new SchematicTracePipelineSolver(inputProblem as any)
|
|
63
|
+
solver.solve()
|
|
64
|
+
expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
For multi-step graphics where only the final stage matters:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
expect(getLastStepSvg(solver.visualize())).toMatchSvgSnapshot(import.meta.path)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Update snapshots with:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
BUN_UPDATE_SNAPSHOTS=1 bun test
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Headless Stage Debugging
|
|
80
|
+
|
|
81
|
+
To inspect a pipeline run without Cosmos:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bun run debug:pipeline tests/assets/example01.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Useful options:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bun run debug:pipeline tests/assets/example17.json --out tmp/schematic-trace-debug/example17
|
|
91
|
+
bun run debug:pipeline tests/assets/example17.json --stop-after schematicTraceLinesSolver
|
|
92
|
+
bun run debug:pipeline tests/assets/example17.json --no-json
|
|
93
|
+
bun run debug:pipeline tests/assets/example17.json --svg --step-pngs
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The runner writes:
|
|
97
|
+
|
|
98
|
+
- `logs.txt`
|
|
99
|
+
- `stageNN-stageName.png`
|
|
100
|
+
- `stageNN-stageName.graphics.json` unless `--no-json` is passed
|
|
101
|
+
|
|
102
|
+
Pass `--svg` when SVG companion artifacts are useful.
|
|
103
|
+
Pass `--step-pngs` to split graphics with `step` values into per-step PNGs.
|
|
104
|
+
|
|
105
|
+
Stage numbers are important because this pipeline can reuse the same solver name more than once, for example `netLabelPlacementSolver`.
|
|
106
|
+
|
|
107
|
+
## Choosing The View
|
|
108
|
+
|
|
109
|
+
Use solver-native `visualize()` for internal state, candidates, failed geometry, staged progress, net labels, and trace cleanup state.
|
|
110
|
+
|
|
111
|
+
Use `SchematicTracePipelineSolver.visualize()` for final combined output after `solve()`.
|
|
112
|
+
|
|
113
|
+
Use `visualizeInputProblem(inputProblem)` when the question is about the raw input geometry, schematic components, pins, direct connections, or net connection labels before routing.
|
|
114
|
+
|
|
115
|
+
Keep SVG backgrounds white. Treat colors, labels, line widths, opacity, and `step` values as snapshot-sensitive.
|
|
@@ -48,15 +48,19 @@ async function main() {
|
|
|
48
48
|
const reportId = `bug-report-${timestamp}`
|
|
49
49
|
const branchName = `json-bug-report/${reportId}`
|
|
50
50
|
const reportDir = path.join("tests", "bug-reports", reportId)
|
|
51
|
+
const siteReportDir = path.join("site", "bug-reports")
|
|
51
52
|
const reportDirAbs = path.join(repoRoot, reportDir)
|
|
52
53
|
const jsonPath = path.join(reportDir, `${reportId}.json`)
|
|
53
54
|
const testPath = path.join(reportDir, `${reportId}.test.ts`)
|
|
55
|
+
const pagePath = path.join(siteReportDir, `${reportId}.page.tsx`)
|
|
54
56
|
|
|
55
57
|
writeOutput("report_id", reportId)
|
|
56
58
|
writeOutput("branch_name", branchName)
|
|
57
59
|
writeOutput("report_dir", reportDir)
|
|
60
|
+
writeOutput("site_report_dir", siteReportDir)
|
|
58
61
|
writeOutput("json_path", jsonPath)
|
|
59
62
|
writeOutput("test_path", testPath)
|
|
63
|
+
writeOutput("page_path", pagePath)
|
|
60
64
|
|
|
61
65
|
if (fs.existsSync(reportDirAbs)) {
|
|
62
66
|
writeOutput("created", "false")
|
|
@@ -70,15 +74,17 @@ async function main() {
|
|
|
70
74
|
assertInputProblem(jsonAttachment.value)
|
|
71
75
|
|
|
72
76
|
fs.mkdirSync(reportDirAbs, { recursive: true })
|
|
77
|
+
fs.mkdirSync(path.join(repoRoot, siteReportDir), { recursive: true })
|
|
73
78
|
fs.writeFileSync(
|
|
74
79
|
path.join(repoRoot, jsonPath),
|
|
75
80
|
`${JSON.stringify(jsonAttachment.value, null, 2)}\n`,
|
|
76
81
|
)
|
|
77
82
|
fs.writeFileSync(path.join(repoRoot, testPath), getTestFile(reportId))
|
|
83
|
+
fs.writeFileSync(path.join(repoRoot, pagePath), getPageFile(reportId))
|
|
78
84
|
|
|
79
85
|
writeOutput("created", "true")
|
|
80
86
|
writeOutput("attachment_url", jsonAttachment.url)
|
|
81
|
-
console.log(`Generated ${jsonPath} and ${
|
|
87
|
+
console.log(`Generated ${jsonPath}, ${testPath}, and ${pagePath}`)
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
function isJsonBugReportIssue(issue: IssueEvent["issue"], body: string) {
|
|
@@ -458,6 +464,14 @@ test("${reportId}", () => {
|
|
|
458
464
|
`
|
|
459
465
|
}
|
|
460
466
|
|
|
467
|
+
function getPageFile(reportId: string) {
|
|
468
|
+
return `import { PipelineDebugger } from "site/components/PipelineDebugger"
|
|
469
|
+
import inputProblem from "../../tests/bug-reports/${reportId}/${reportId}.json"
|
|
470
|
+
|
|
471
|
+
export default () => <PipelineDebugger inputProblem={inputProblem as any} />
|
|
472
|
+
`
|
|
473
|
+
}
|
|
474
|
+
|
|
461
475
|
function writeOutput(key: string, value: string) {
|
|
462
476
|
if (!githubOutputPath) return
|
|
463
477
|
fs.appendFileSync(githubOutputPath, `${key}=${value}\n`)
|
|
@@ -64,7 +64,7 @@ jobs:
|
|
|
64
64
|
|
|
65
65
|
- name: Format generated test
|
|
66
66
|
if: steps.import.outputs.created == 'true'
|
|
67
|
-
run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}"
|
|
67
|
+
run: ./node_modules/.bin/biome format --write "${{ steps.import.outputs.test_path }}" "${{ steps.import.outputs.page_path }}"
|
|
68
68
|
|
|
69
69
|
- name: Open pull request
|
|
70
70
|
id: pr
|
|
@@ -82,6 +82,7 @@ jobs:
|
|
|
82
82
|
git config user.email "tscircuit-bot@users.noreply.github.com"
|
|
83
83
|
git checkout -B "$BRANCH_NAME"
|
|
84
84
|
git add "${{ steps.import.outputs.report_dir }}"
|
|
85
|
+
git add "${{ steps.import.outputs.page_path }}"
|
|
85
86
|
|
|
86
87
|
if git diff --cached --quiet; then
|
|
87
88
|
echo "No changes to commit."
|
|
@@ -96,7 +97,7 @@ jobs:
|
|
|
96
97
|
{
|
|
97
98
|
echo "Generated from #${ISSUE_NUMBER}."
|
|
98
99
|
echo
|
|
99
|
-
echo "Adds a snapshot-only regression test for the attached JSON solver input."
|
|
100
|
+
echo "Adds a snapshot-only regression test and debugger page for the attached JSON solver input."
|
|
100
101
|
echo
|
|
101
102
|
echo "Workflow run: ${RUN_URL}"
|
|
102
103
|
} > "$pr_body_file"
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Agent Notes
|
|
2
|
+
|
|
3
|
+
- Use `bunx tsc --noEmit` to type check.
|
|
4
|
+
- Use `bun test path/to/test.ts` to run a test.
|
|
5
|
+
- Use `bun add <package>` to add a package.
|
|
6
|
+
|
|
7
|
+
## Solver Visualization
|
|
8
|
+
|
|
9
|
+
For schematic trace solver visualization work, use the repo-local skill:
|
|
10
|
+
|
|
11
|
+
- `.agents/skills/schematic-trace-visualization/SKILL.md`
|
|
12
|
+
|
|
13
|
+
This repo supports headless stage artifact generation without starting Cosmos:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun run debug:pipeline tests/assets/example01.json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The command writes per-stage PNG and graphics JSON artifacts under `tmp/schematic-trace-debug/...`.
|
|
20
|
+
Pass `--svg` when SVG companion files are needed.
|
|
21
|
+
|
|
22
|
+
Interactive debugging still exists through `site/**/*.page.tsx`, but agents should prefer the headless debug runner when they need to inspect solver state programmatically.
|
package/dist/index.js
CHANGED
|
@@ -5419,6 +5419,28 @@ var moveAttachedLabelsToReroutedTrace = ({
|
|
|
5419
5419
|
};
|
|
5420
5420
|
});
|
|
5421
5421
|
|
|
5422
|
+
// lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
|
|
5423
|
+
var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
|
|
5424
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
5425
|
+
const start = path[i];
|
|
5426
|
+
const end = path[i + 1];
|
|
5427
|
+
for (const obstacle of chipObstacles) {
|
|
5428
|
+
if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
|
|
5429
|
+
if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
|
|
5430
|
+
return true;
|
|
5431
|
+
}
|
|
5432
|
+
}
|
|
5433
|
+
}
|
|
5434
|
+
return false;
|
|
5435
|
+
};
|
|
5436
|
+
var getSegmentOverlapWithRectSpan = (start, end, rect) => {
|
|
5437
|
+
const isVertical4 = Math.abs(start.x - end.x) < EPS7;
|
|
5438
|
+
if (isVertical4) {
|
|
5439
|
+
return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
|
|
5440
|
+
}
|
|
5441
|
+
return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
|
|
5442
|
+
};
|
|
5443
|
+
|
|
5422
5444
|
// lib/solvers/Example28Solver/reroute.ts
|
|
5423
5445
|
var LABEL_CLEARANCE = 0.1;
|
|
5424
5446
|
var findBestReroutePath = ({
|
|
@@ -5529,6 +5551,14 @@ var createCandidateResult = ({
|
|
|
5529
5551
|
selected: false
|
|
5530
5552
|
};
|
|
5531
5553
|
}
|
|
5554
|
+
if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
|
|
5555
|
+
return {
|
|
5556
|
+
path,
|
|
5557
|
+
status: "chip-collision",
|
|
5558
|
+
usesHorizontalSegmentPush,
|
|
5559
|
+
selected: false
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5532
5562
|
return {
|
|
5533
5563
|
path,
|
|
5534
5564
|
score: scoreTracePath({
|
|
@@ -5792,28 +5822,6 @@ var countCrossingsWithOtherTraces = (params) => {
|
|
|
5792
5822
|
return crossingCount;
|
|
5793
5823
|
};
|
|
5794
5824
|
|
|
5795
|
-
// lib/solvers/Example28Solver/doesPathRunAlongChipBoundary.ts
|
|
5796
|
-
var doesPathRunAlongChipBoundary = (path, chipObstacles) => {
|
|
5797
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
5798
|
-
const start = path[i];
|
|
5799
|
-
const end = path[i + 1];
|
|
5800
|
-
for (const obstacle of chipObstacles) {
|
|
5801
|
-
if (!segmentRunsAlongRectBoundary(start, end, obstacle)) continue;
|
|
5802
|
-
if (getSegmentOverlapWithRectSpan(start, end, obstacle) > EPS7) {
|
|
5803
|
-
return true;
|
|
5804
|
-
}
|
|
5805
|
-
}
|
|
5806
|
-
}
|
|
5807
|
-
return false;
|
|
5808
|
-
};
|
|
5809
|
-
var getSegmentOverlapWithRectSpan = (start, end, rect) => {
|
|
5810
|
-
const isVertical4 = Math.abs(start.x - end.x) < EPS7;
|
|
5811
|
-
if (isVertical4) {
|
|
5812
|
-
return Math.min(Math.max(start.y, end.y), rect.maxY) - Math.max(Math.min(start.y, end.y), rect.minY);
|
|
5813
|
-
}
|
|
5814
|
-
return Math.min(Math.max(start.x, end.x), rect.maxX) - Math.max(Math.min(start.x, end.x), rect.minX);
|
|
5815
|
-
};
|
|
5816
|
-
|
|
5817
5825
|
// lib/solvers/Example28Solver/scoreSolutionTracePath.ts
|
|
5818
5826
|
var scoreSolutionTracePath = (params) => {
|
|
5819
5827
|
const { traces, outputTraces, chipObstacles } = params;
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getPathLength,
|
|
15
15
|
isPathCollidingWithChipInterior,
|
|
16
16
|
} from "./geometry"
|
|
17
|
+
import { doesPathRunAlongChipBoundary } from "./doesPathRunAlongChipBoundary"
|
|
17
18
|
import type {
|
|
18
19
|
ChipObstacle,
|
|
19
20
|
RerouteCandidateResult,
|
|
@@ -165,6 +166,15 @@ const createCandidateResult = ({
|
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
168
|
|
|
169
|
+
if (doesPathRunAlongChipBoundary(path, chipObstacles)) {
|
|
170
|
+
return {
|
|
171
|
+
path,
|
|
172
|
+
status: "chip-collision",
|
|
173
|
+
usesHorizontalSegmentPush,
|
|
174
|
+
selected: false,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
168
178
|
return {
|
|
169
179
|
path,
|
|
170
180
|
score: scoreTracePath({
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import {
|
|
4
|
+
getPngBufferFromGraphicsObject,
|
|
5
|
+
getSvgFromGraphicsObject,
|
|
6
|
+
} from "graphics-debug"
|
|
7
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
8
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
9
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
10
|
+
|
|
11
|
+
type PipelineStepLike = {
|
|
12
|
+
solverName: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type VisualizingSolver = {
|
|
16
|
+
visualize: () => GraphicsObject
|
|
17
|
+
iterations?: number
|
|
18
|
+
error?: string | null
|
|
19
|
+
solved?: boolean
|
|
20
|
+
failed?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type PipelineStageArtifact = {
|
|
24
|
+
stageName: string
|
|
25
|
+
stageNumber: number
|
|
26
|
+
pngPath?: string
|
|
27
|
+
svgPath?: string
|
|
28
|
+
graphicsJsonPath?: string
|
|
29
|
+
stepPngPaths: string[]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type PipelineStageDebugRunnerResult = {
|
|
33
|
+
outputDir: string
|
|
34
|
+
logsPath: string
|
|
35
|
+
stageArtifacts: PipelineStageArtifact[]
|
|
36
|
+
solved: boolean
|
|
37
|
+
failed: boolean
|
|
38
|
+
error: string | null
|
|
39
|
+
stoppedAfterStage: string | null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class PipelineStageDebugRunner {
|
|
43
|
+
readonly pipelineSolver: SchematicTracePipelineSolver
|
|
44
|
+
readonly outputDir: string
|
|
45
|
+
readonly logsPath: string
|
|
46
|
+
readonly pngWidth: number
|
|
47
|
+
readonly pngHeight: number
|
|
48
|
+
readonly writePng: boolean
|
|
49
|
+
readonly writeSvg: boolean
|
|
50
|
+
readonly writeGraphicsJson: boolean
|
|
51
|
+
readonly writeStepPngs: boolean
|
|
52
|
+
readonly stopAfterStage?: string
|
|
53
|
+
readonly context: Record<string, string | number | boolean | null | undefined>
|
|
54
|
+
readonly onLog?: (line: string) => void
|
|
55
|
+
|
|
56
|
+
private readonly stageArtifacts: PipelineStageArtifact[] = []
|
|
57
|
+
private readonly capturedStageNumbers = new Set<number>()
|
|
58
|
+
|
|
59
|
+
constructor(opts: {
|
|
60
|
+
pipelineSolver: SchematicTracePipelineSolver
|
|
61
|
+
outputDir: string
|
|
62
|
+
pngWidth?: number
|
|
63
|
+
pngHeight?: number
|
|
64
|
+
writePng?: boolean
|
|
65
|
+
writeSvg?: boolean
|
|
66
|
+
writeGraphicsJson?: boolean
|
|
67
|
+
writeStepPngs?: boolean
|
|
68
|
+
stopAfterStage?: string
|
|
69
|
+
context?: Record<string, string | number | boolean | null | undefined>
|
|
70
|
+
onLog?: (line: string) => void
|
|
71
|
+
}) {
|
|
72
|
+
this.pipelineSolver = opts.pipelineSolver
|
|
73
|
+
this.outputDir = opts.outputDir
|
|
74
|
+
this.logsPath = path.join(this.outputDir, "logs.txt")
|
|
75
|
+
this.pngWidth = opts.pngWidth ?? 1536
|
|
76
|
+
this.pngHeight = opts.pngHeight ?? 1536
|
|
77
|
+
this.writePng = opts.writePng ?? true
|
|
78
|
+
this.writeSvg = opts.writeSvg ?? false
|
|
79
|
+
this.writeGraphicsJson = opts.writeGraphicsJson ?? true
|
|
80
|
+
this.writeStepPngs = opts.writeStepPngs ?? false
|
|
81
|
+
this.stopAfterStage = opts.stopAfterStage
|
|
82
|
+
this.context = opts.context ?? {}
|
|
83
|
+
this.onLog = opts.onLog
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async run(): Promise<PipelineStageDebugRunnerResult> {
|
|
87
|
+
await mkdir(this.outputDir, { recursive: true })
|
|
88
|
+
await writeFile(this.logsPath, "")
|
|
89
|
+
|
|
90
|
+
await this.log(`startedAt=${new Date().toISOString()}`)
|
|
91
|
+
await this.log(`outputDir=${this.toDisplayPath(this.outputDir)}`)
|
|
92
|
+
await this.log(
|
|
93
|
+
`artifacts png=${this.writePng} svg=${this.writeSvg} graphicsJson=${this.writeGraphicsJson} stepPngs=${this.writeStepPngs} pngSize=${this.pngWidth}x${this.pngHeight}`,
|
|
94
|
+
)
|
|
95
|
+
for (const [key, value] of Object.entries(this.context)) {
|
|
96
|
+
await this.log(`${key}=${value ?? ""}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let stoppedAfterStage: string | null = null
|
|
100
|
+
let thrownError: unknown = null
|
|
101
|
+
|
|
102
|
+
await this.log(
|
|
103
|
+
this.getStageEnterLogLine(this.pipelineSolver.getCurrentPhase(), 0),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
while (!this.pipelineSolver.solved && !this.pipelineSolver.failed) {
|
|
107
|
+
const previousStageNumber = this.pipelineSolver.currentPipelineStepIndex
|
|
108
|
+
const previousStageName = this.pipelineSolver.getCurrentPhase()
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
this.pipelineSolver.step()
|
|
112
|
+
} catch (error) {
|
|
113
|
+
thrownError = error
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const currentStageNumber = this.pipelineSolver.currentPipelineStepIndex
|
|
117
|
+
const currentStageName = this.pipelineSolver.getCurrentPhase()
|
|
118
|
+
|
|
119
|
+
if (
|
|
120
|
+
currentStageNumber !== previousStageNumber &&
|
|
121
|
+
previousStageName !== "none"
|
|
122
|
+
) {
|
|
123
|
+
await this.captureStage(previousStageNumber)
|
|
124
|
+
if (
|
|
125
|
+
previousStageName === this.stopAfterStage ||
|
|
126
|
+
String(previousStageNumber + 1) === this.stopAfterStage
|
|
127
|
+
) {
|
|
128
|
+
stoppedAfterStage = previousStageName
|
|
129
|
+
break
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
currentStageNumber !== previousStageNumber &&
|
|
135
|
+
currentStageName !== "none"
|
|
136
|
+
) {
|
|
137
|
+
await this.log(
|
|
138
|
+
this.getStageEnterLogLine(currentStageName, currentStageNumber),
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (thrownError) break
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (this.pipelineSolver.failed) {
|
|
146
|
+
await this.captureActiveStage()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (thrownError) {
|
|
150
|
+
await this.log(`thrownError=${this.formatError(thrownError)}`)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const status = stoppedAfterStage
|
|
154
|
+
? "stopped"
|
|
155
|
+
: this.pipelineSolver.solved
|
|
156
|
+
? "solved"
|
|
157
|
+
: this.pipelineSolver.failed
|
|
158
|
+
? "failed"
|
|
159
|
+
: "incomplete"
|
|
160
|
+
|
|
161
|
+
await this.log(
|
|
162
|
+
`completed status=${status} iterations=${this.pipelineSolver.iterations} error=${this.pipelineSolver.error ?? ""}`,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
outputDir: this.outputDir,
|
|
167
|
+
logsPath: this.logsPath,
|
|
168
|
+
stageArtifacts: [...this.stageArtifacts],
|
|
169
|
+
solved: this.pipelineSolver.solved,
|
|
170
|
+
failed: this.pipelineSolver.failed,
|
|
171
|
+
error:
|
|
172
|
+
this.pipelineSolver.error ??
|
|
173
|
+
(thrownError ? this.formatError(thrownError) : null),
|
|
174
|
+
stoppedAfterStage,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async captureActiveStage() {
|
|
179
|
+
const stageNumber = this.pipelineSolver.currentPipelineStepIndex
|
|
180
|
+
if (this.pipelineSolver.getCurrentPhase() === "none") return
|
|
181
|
+
if (this.capturedStageNumbers.has(stageNumber)) return
|
|
182
|
+
|
|
183
|
+
await this.captureStage(stageNumber, this.pipelineSolver.activeSubSolver)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private async captureStage(stageNumber: number, solver?: BaseSolver | null) {
|
|
187
|
+
if (this.capturedStageNumbers.has(stageNumber)) return
|
|
188
|
+
|
|
189
|
+
const stageDef = this.getStageDef(stageNumber)
|
|
190
|
+
if (!stageDef) return
|
|
191
|
+
|
|
192
|
+
const stageSolver = solver ?? this.getStageSolver(stageDef.solverName)
|
|
193
|
+
if (!stageSolver) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`Unable to resolve solver for stage ${stageNumber + 1} "${stageDef.solverName}"`,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const basePath = path.join(
|
|
200
|
+
this.outputDir,
|
|
201
|
+
`stage${String(stageNumber + 1).padStart(2, "0")}-${this.getSafeStageName(stageDef.solverName)}`,
|
|
202
|
+
)
|
|
203
|
+
const pngPath = this.writePng ? `${basePath}.png` : undefined
|
|
204
|
+
const svgPath = this.writeSvg ? `${basePath}.svg` : undefined
|
|
205
|
+
const graphicsJsonPath = this.writeGraphicsJson
|
|
206
|
+
? `${basePath}.graphics.json`
|
|
207
|
+
: undefined
|
|
208
|
+
const graphics = stageSolver.visualize()
|
|
209
|
+
const svg = getSvgFromGraphicsObject(graphics, { backgroundColor: "white" })
|
|
210
|
+
|
|
211
|
+
if (pngPath) {
|
|
212
|
+
await writeFile(pngPath, await this.renderGraphicsToPng(graphics))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const stepPngPaths =
|
|
216
|
+
this.writeStepPngs && this.writePng
|
|
217
|
+
? await this.writeStepPngsForGraphics(basePath, graphics)
|
|
218
|
+
: []
|
|
219
|
+
|
|
220
|
+
if (svgPath) {
|
|
221
|
+
await writeFile(svgPath, svg)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (graphicsJsonPath) {
|
|
225
|
+
await writeFile(graphicsJsonPath, JSON.stringify(graphics, null, 2))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.capturedStageNumbers.add(stageNumber)
|
|
229
|
+
const artifact = {
|
|
230
|
+
stageName: stageDef.solverName,
|
|
231
|
+
stageNumber: stageNumber + 1,
|
|
232
|
+
pngPath,
|
|
233
|
+
svgPath,
|
|
234
|
+
graphicsJsonPath,
|
|
235
|
+
stepPngPaths,
|
|
236
|
+
} satisfies PipelineStageArtifact
|
|
237
|
+
this.stageArtifacts.push(artifact)
|
|
238
|
+
|
|
239
|
+
await this.log(
|
|
240
|
+
`captured stage=${artifact.stageNumber} name=${artifact.stageName} iterations=${stageSolver.iterations ?? 0} png=${pngPath ? this.toDisplayPath(pngPath) : ""} svg=${svgPath ? this.toDisplayPath(svgPath) : ""} steps=${stepPngPaths.length}`,
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private async writeStepPngsForGraphics(
|
|
245
|
+
basePath: string,
|
|
246
|
+
graphics: GraphicsObject,
|
|
247
|
+
): Promise<string[]> {
|
|
248
|
+
const stepPngPaths: string[] = []
|
|
249
|
+
|
|
250
|
+
for (const step of this.getObjectSteps(graphics)) {
|
|
251
|
+
const stepPngPath = `${basePath}.step-${step}.png`
|
|
252
|
+
await writeFile(
|
|
253
|
+
stepPngPath,
|
|
254
|
+
await this.renderGraphicsToPng(
|
|
255
|
+
this.filterGraphicsByStep(graphics, step),
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
stepPngPaths.push(stepPngPath)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return stepPngPaths
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
private renderGraphicsToPng(graphics: GraphicsObject): Promise<Uint8Array> {
|
|
265
|
+
return getPngBufferFromGraphicsObject(graphics, {
|
|
266
|
+
backgroundColor: "white",
|
|
267
|
+
pngWidth: this.pngWidth,
|
|
268
|
+
pngHeight: this.pngHeight,
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private getObjectSteps(graphics: GraphicsObject): number[] {
|
|
273
|
+
const steps = new Set<number>()
|
|
274
|
+
for (const objects of this.getGraphicsObjectArrays(graphics)) {
|
|
275
|
+
for (const object of objects) {
|
|
276
|
+
if (typeof object.step === "number") steps.add(object.step)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return [...steps].sort((a, b) => a - b)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private filterGraphicsByStep(
|
|
284
|
+
graphics: GraphicsObject,
|
|
285
|
+
step: number,
|
|
286
|
+
): GraphicsObject {
|
|
287
|
+
const filtered: Record<string, unknown[]> = {}
|
|
288
|
+
|
|
289
|
+
for (const key of Object.keys(graphics)) {
|
|
290
|
+
const value = (graphics as Record<string, unknown>)[key]
|
|
291
|
+
filtered[key] = Array.isArray(value)
|
|
292
|
+
? value.filter((object) => {
|
|
293
|
+
return (
|
|
294
|
+
typeof object === "object" &&
|
|
295
|
+
object !== null &&
|
|
296
|
+
(object as { step?: number }).step === step
|
|
297
|
+
)
|
|
298
|
+
})
|
|
299
|
+
: []
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return filtered as GraphicsObject
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private getGraphicsObjectArrays(
|
|
306
|
+
graphics: GraphicsObject,
|
|
307
|
+
): Array<Array<{ step?: number }>> {
|
|
308
|
+
return Object.values(graphics).filter(Array.isArray) as Array<
|
|
309
|
+
Array<{ step?: number }>
|
|
310
|
+
>
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private getStageDef(stageNumber: number): PipelineStepLike | undefined {
|
|
314
|
+
return this.pipelineSolver.pipelineDef[stageNumber]
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private getStageSolver(stageName: string): VisualizingSolver | undefined {
|
|
318
|
+
return (
|
|
319
|
+
this.pipelineSolver as unknown as Record<string, VisualizingSolver>
|
|
320
|
+
)[stageName]
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private getSafeStageName(stageName: string): string {
|
|
324
|
+
return stageName.replace(/[^a-zA-Z0-9._-]/g, "_")
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private getStageEnterLogLine(stageName: string, stageNumber: number) {
|
|
328
|
+
return `enter stage=${stageNumber + 1} name=${stageName}`
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private async log(line: string) {
|
|
332
|
+
await writeFile(this.logsPath, `${line}\n`, { flag: "a" })
|
|
333
|
+
this.onLog?.(line)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private toDisplayPath(filePath: string): string {
|
|
337
|
+
return path.relative(process.cwd(), filePath) || "."
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private formatError(error: unknown) {
|
|
341
|
+
if (error instanceof Error) return `${error.name}: ${error.message}`
|
|
342
|
+
return String(error)
|
|
343
|
+
}
|
|
344
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/schematic-trace-solver",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.89",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "cosmos",
|
|
8
|
+
"debug:pipeline": "bun scripts/debug-pipeline-stages.ts",
|
|
8
9
|
"build": "tsup-node lib/index.ts --format esm --dts",
|
|
9
10
|
"format": "biome format --write .",
|
|
10
11
|
"format:check": "biome format ."
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
"calculate-elbow": "^0.0.12",
|
|
19
20
|
"connectivity-map": "^1.0.0",
|
|
20
21
|
"flatbush": "^4.5.0",
|
|
21
|
-
"graphics-debug": "^0.0.
|
|
22
|
+
"graphics-debug": "^0.0.96",
|
|
22
23
|
"react": "^19.1.1",
|
|
23
24
|
"react-cosmos": "^7.0.0",
|
|
24
25
|
"react-cosmos-plugin-vite": "^7.0.0",
|