@tscircuit/rectdiff 0.0.2 → 0.0.4

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/dist/index.js CHANGED
@@ -585,19 +585,51 @@ function buildZIndexMap(srj) {
585
585
  const names = canonicalizeLayerOrder(
586
586
  (srj.obstacles ?? []).flatMap((o) => o.layers ?? [])
587
587
  );
588
+ const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1);
588
589
  const fallback = Array.from(
589
- { length: Math.max(1, srj.layerCount || 1) },
590
- (_, i) => i === 0 ? "top" : i === (srj.layerCount || 1) - 1 ? "bottom" : `inner${i}`
590
+ { length: declaredLayerCount },
591
+ (_, i) => i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`
591
592
  );
592
- const layerNames = names.length ? names : fallback;
593
+ const ordered = [];
594
+ const seen = /* @__PURE__ */ new Set();
595
+ const push = (n) => {
596
+ const key = n.toLowerCase();
597
+ if (seen.has(key)) return;
598
+ seen.add(key);
599
+ ordered.push(n);
600
+ };
601
+ fallback.forEach(push);
602
+ names.forEach(push);
603
+ const layerNames = ordered.slice(0, declaredLayerCount);
604
+ const clampIndex = (nameLower) => {
605
+ if (layerNames.length <= 1) return 0;
606
+ if (nameLower === "top") return 0;
607
+ if (nameLower === "bottom") return layerNames.length - 1;
608
+ const m = /^inner(\d+)$/i.exec(nameLower);
609
+ if (m) {
610
+ if (layerNames.length <= 2) return layerNames.length - 1;
611
+ const parsed = parseInt(m[1], 10);
612
+ const maxInner = layerNames.length - 2;
613
+ const clampedInner = Math.min(
614
+ maxInner,
615
+ Math.max(1, Number.isFinite(parsed) ? parsed : 1)
616
+ );
617
+ return clampedInner;
618
+ }
619
+ return 0;
620
+ };
593
621
  const map = /* @__PURE__ */ new Map();
594
- layerNames.forEach((n, i) => map.set(n, i));
622
+ layerNames.forEach((n, i) => map.set(n.toLowerCase(), i));
623
+ ordered.slice(layerNames.length).forEach((n) => {
624
+ const key = n.toLowerCase();
625
+ map.set(key, clampIndex(key));
626
+ });
595
627
  return { layerNames, zIndexByName: map };
596
628
  }
597
629
  function obstacleZs(ob, zIndexByName) {
598
630
  if (ob.zLayers?.length)
599
631
  return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b);
600
- const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n)).filter((v) => typeof v === "number");
632
+ const fromNames = (ob.layers ?? []).map((n) => zIndexByName.get(n.toLowerCase())).filter((v) => typeof v === "number");
601
633
  return Array.from(new Set(fromNames)).sort((a, b) => a - b);
602
634
  }
603
635
  function obstacleToXYRect(ob) {
@@ -625,8 +657,16 @@ function initState(srj, opts) {
625
657
  const r = obstacleToXYRect(ob);
626
658
  if (!r) continue;
627
659
  const zs = obstacleZs(ob, zIndexByName);
628
- for (const z of zs)
629
- if (z >= 0 && z < layerCount) obstaclesByLayer[z].push(r);
660
+ const invalidZs = zs.filter((z) => z < 0 || z >= layerCount);
661
+ if (invalidZs.length) {
662
+ throw new Error(
663
+ `RectDiffSolver: obstacle uses z-layer indices ${invalidZs.join(
664
+ ","
665
+ )} outside 0-${layerCount - 1}`
666
+ );
667
+ }
668
+ if ((!ob.zLayers || ob.zLayers.length === 0) && zs.length) ob.zLayers = zs;
669
+ for (const z of zs) obstaclesByLayer[z].push(r);
630
670
  }
631
671
  const trace = Math.max(0.01, srj.minTraceWidth || 0.15);
632
672
  const defaults = {
package/index.html CHANGED
@@ -3,10 +3,10 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>React Cosmos Vite Renderer</title>
6
+ <title>React Cosmos</title>
7
7
  </head>
8
8
  <body>
9
9
  <div id="root"></div>
10
- <script type="module" src="/src/main.tsx"></script>
10
+ <script type="module" src="/main.tsx"></script>
11
11
  </body>
12
12
  </html>
@@ -45,8 +45,17 @@ export function initState(
45
45
  const r = obstacleToXYRect(ob)
46
46
  if (!r) continue
47
47
  const zs = obstacleZs(ob, zIndexByName)
48
- for (const z of zs)
49
- if (z >= 0 && z < layerCount) obstaclesByLayer[z]!.push(r)
48
+ const invalidZs = zs.filter((z) => z < 0 || z >= layerCount)
49
+ if (invalidZs.length) {
50
+ throw new Error(
51
+ `RectDiffSolver: obstacle uses z-layer indices ${invalidZs.join(
52
+ ",",
53
+ )} outside 0-${layerCount - 1}`,
54
+ )
55
+ }
56
+ // Persist normalized zLayers back onto the shared SRJ so downstream solvers see them.
57
+ if ((!ob.zLayers || ob.zLayers.length === 0) && zs.length) ob.zLayers = zs
58
+ for (const z of zs) obstaclesByLayer[z]!.push(r)
50
59
  }
51
60
 
52
61
  const trace = Math.max(0.01, srj.minTraceWidth || 0.15)
@@ -24,18 +24,46 @@ export function buildZIndexMap(srj: SimpleRouteJson) {
24
24
  const names = canonicalizeLayerOrder(
25
25
  (srj.obstacles ?? []).flatMap((o) => o.layers ?? []),
26
26
  )
27
- const fallback = Array.from(
28
- { length: Math.max(1, srj.layerCount || 1) },
29
- (_, i) =>
30
- i === 0
31
- ? "top"
32
- : i === (srj.layerCount || 1) - 1
33
- ? "bottom"
34
- : `inner${i}`,
27
+ const declaredLayerCount = Math.max(1, srj.layerCount || names.length || 1)
28
+ const fallback = Array.from({ length: declaredLayerCount }, (_, i) =>
29
+ i === 0 ? "top" : i === declaredLayerCount - 1 ? "bottom" : `inner${i}`,
35
30
  )
36
- const layerNames = names.length ? names : fallback
31
+ const ordered: string[] = []
32
+ const seen = new Set<string>()
33
+ const push = (n: string) => {
34
+ const key = n.toLowerCase()
35
+ if (seen.has(key)) return
36
+ seen.add(key)
37
+ ordered.push(n)
38
+ }
39
+ fallback.forEach(push)
40
+ names.forEach(push)
41
+ const layerNames = ordered.slice(0, declaredLayerCount)
42
+ // Clamp any exotic layer names (extra inner layers, mixed casing, etc.)
43
+ // onto the declared layerCount so every obstacle resolves to a legal z index.
44
+ const clampIndex = (nameLower: string) => {
45
+ if (layerNames.length <= 1) return 0
46
+ if (nameLower === "top") return 0
47
+ if (nameLower === "bottom") return layerNames.length - 1
48
+ const m = /^inner(\d+)$/i.exec(nameLower)
49
+ if (m) {
50
+ if (layerNames.length <= 2) return layerNames.length - 1
51
+ const parsed = parseInt(m[1]!, 10)
52
+ const maxInner = layerNames.length - 2
53
+ const clampedInner = Math.min(
54
+ maxInner,
55
+ Math.max(1, Number.isFinite(parsed) ? parsed : 1),
56
+ )
57
+ return clampedInner
58
+ }
59
+ return 0
60
+ }
37
61
  const map = new Map<string, number>()
38
- layerNames.forEach((n, i) => map.set(n, i))
62
+ layerNames.forEach((n, i) => map.set(n.toLowerCase(), i))
63
+ ordered.slice(layerNames.length).forEach((n) => {
64
+ const key = n.toLowerCase()
65
+ map.set(key, clampIndex(key))
66
+ })
39
67
  return { layerNames, zIndexByName: map }
40
68
  }
41
69
 
@@ -43,7 +71,7 @@ export function obstacleZs(ob: Obstacle, zIndexByName: Map<string, number>) {
43
71
  if (ob.zLayers?.length)
44
72
  return Array.from(new Set(ob.zLayers)).sort((a, b) => a - b)
45
73
  const fromNames = (ob.layers ?? [])
46
- .map((n) => zIndexByName.get(n))
74
+ .map((n) => zIndexByName.get(n.toLowerCase()))
47
75
  .filter((v): v is number => typeof v === "number")
48
76
  return Array.from(new Set(fromNames)).sort((a, b) => a - b)
49
77
  }
package/main.tsx ADDED
@@ -0,0 +1 @@
1
+ import "react-cosmos/client"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/rectdiff",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -15,23 +15,26 @@
15
15
  "@react-hook/resize-observer": "^2.0.2",
16
16
  "@tscircuit/solver-utils": "^0.0.3",
17
17
  "@types/bun": "latest",
18
+ "@types/react": "^18",
19
+ "@types/react-dom": "^18",
18
20
  "@types/three": "^0.181.0",
19
21
  "biome": "^0.3.3",
20
22
  "bun-match-svg": "^0.0.14",
21
23
  "graphics-debug": "^0.0.70",
22
24
  "rbush": "^4.0.1",
23
- "react": "^19.2.0",
24
- "react-cosmos": "^7.0.0",
25
- "react-cosmos-plugin-vite": "^7.0.0",
26
- "react-dom": "^19.2.0",
25
+ "react": "18",
26
+ "react-cosmos": "^6.2.3",
27
+ "react-cosmos-plugin-vite": "^6.2.0",
28
+ "react-dom": "18",
27
29
  "three": "^0.181.1",
28
- "tsup": "^8.5.1"
30
+ "tsup": "^8.5.1",
31
+ "vite": "^6.0.11",
32
+ "@vitejs/plugin-react": "^4"
29
33
  },
30
34
  "peerDependencies": {
31
35
  "typescript": "^5"
32
36
  },
33
37
  "dependencies": {
34
- "D": "^1.0.0",
35
- "vite": "^7.2.2"
38
+ "D": "^1.0.0"
36
39
  }
37
40
  }
@@ -0,0 +1,37 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { SimpleRouteJson } from "../lib/types/srj-types"
3
+ import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
4
+
5
+ // Legacy SRJs sometimes reference "inner" layers beyond layerCount; ensure we clamp.
6
+ test("RectDiffSolver clamps extra layer names to available z indices", () => {
7
+ const srj: SimpleRouteJson = {
8
+ bounds: { minX: 0, maxX: 5, minY: 0, maxY: 5 },
9
+ connections: [],
10
+ minTraceWidth: 0.2,
11
+ layerCount: 2,
12
+ obstacles: [
13
+ {
14
+ type: "rect",
15
+ center: { x: 1, y: 1 },
16
+ width: 1,
17
+ height: 1,
18
+ layers: ["inner1"],
19
+ connectedTo: [],
20
+ },
21
+ {
22
+ type: "rect",
23
+ center: { x: 3, y: 3 },
24
+ width: 1,
25
+ height: 1,
26
+ layers: ["inner2"],
27
+ connectedTo: [],
28
+ },
29
+ ],
30
+ }
31
+
32
+ const solver = new RectDiffSolver({ simpleRouteJson: srj, mode: "grid" })
33
+ solver.setup()
34
+
35
+ expect(srj.obstacles[0]?.zLayers).toEqual([1])
36
+ expect(srj.obstacles[1]?.zLayers).toEqual([1])
37
+ })
@@ -0,0 +1,37 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { SimpleRouteJson } from "../lib/types/srj-types"
3
+ import { RectDiffSolver } from "../lib/solvers/RectDiffSolver"
4
+
5
+ // Baseline: plain string layers should be auto-converted to numeric zLayers.
6
+ test("RectDiffSolver maps obstacle layers to numeric zLayers", () => {
7
+ const srj: SimpleRouteJson = {
8
+ bounds: { minX: 0, maxX: 10, minY: 0, maxY: 10 },
9
+ connections: [],
10
+ minTraceWidth: 0.2,
11
+ layerCount: 3,
12
+ obstacles: [
13
+ {
14
+ type: "rect",
15
+ center: { x: 1, y: 1 },
16
+ width: 1,
17
+ height: 1,
18
+ layers: ["top"],
19
+ connectedTo: [],
20
+ },
21
+ {
22
+ type: "rect",
23
+ center: { x: 2, y: 2 },
24
+ width: 1,
25
+ height: 1,
26
+ layers: ["inner1", "bottom"],
27
+ connectedTo: [],
28
+ },
29
+ ],
30
+ }
31
+
32
+ const solver = new RectDiffSolver({ simpleRouteJson: srj, mode: "grid" })
33
+ solver.setup()
34
+
35
+ expect(srj.obstacles[0]?.zLayers).toEqual([0])
36
+ expect(srj.obstacles[1]?.zLayers).toEqual([1, 2])
37
+ })
package/vite.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite"
2
+ import react from "@vitejs/plugin-react"
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })