Skip to content

Scope trace length checks to exact routed endpoints - #251

Merged
seveibar merged 1 commit into
tscircuit:mainfrom
seveibar:fix/exact-endpoint-trace-length
Aug 27, 2026
Merged

Scope trace length checks to exact routed endpoints#251
seveibar merged 1 commit into
tscircuit:mainfrom
seveibar:fix/exact-endpoint-trace-length

Conversation

@seveibar

Copy link
Copy Markdown
Contributor

Summary

  • when a two-port source trace has a PCB trace that explicitly references both mapped PCB endpoints, measure only that exact-endpoint trace
  • ignore unrelated MST branches that merely inherit the same source trace ID
  • preserve current behavior for one-port-to-net traces, authoritative trace_length values, through-pad or metadata-poor routes, and other cases without an exact endpoint match

Regression coverage

  • suppresses the three smart-watch-style explicit two-port MST false positives
  • retains a real violation on the direct endpoint trace
  • retains the C9-style one-port-to-net warning
  • falls back to current attribution when exact endpoint metadata is unavailable

Validation

  • bun test tests/lib/check-pcb-trace-lengths.test.ts (6 passed)
  • bun test (212 passed)
  • bunx tsc --noEmit
  • bun run build
  • bunx biome format lib/check-pcb-trace-lengths.ts tests/lib/check-pcb-trace-lengths.test.ts
  • winning smart-watch circuit JSON: warnings reduced from 4 to the one genuine C9 GND drop (5.062790 mm > 5 mm)

The repository-wide format command still reports the existing Biome 1 MiB limit on three unchanged JSON fixtures; both changed files pass the formatter directly.

Comment on lines +129 to +273
test("ignores unrelated long MST branches when exact two-port traces are short", () => {
const cases = [
{ id: "charger_ground", maximum: 5, exact: 3.94, branch: 10.71 },
{ id: "vdd1_decoupling", maximum: 3, exact: 1.16, branch: 6.47 },
{ id: "regulator_output", maximum: 6, exact: 1.57, branch: 17.37 },
]
const multidropCircuitJson = cases.flatMap(
({ id, maximum, exact, branch }): AnyCircuitElement[] => {
const sourceTraceId = `source_trace_${id}`
const startSourcePortId = `source_port_${id}_start`
const endSourcePortId = `source_port_${id}_end`
const branchSourcePortId = `source_port_${id}_branch`
const startPcbPortId = `pcb_port_${id}_start`
const endPcbPortId = `pcb_port_${id}_end`
const branchPcbPortId = `pcb_port_${id}_branch`

return [
{
type: "source_trace",
source_trace_id: sourceTraceId,
connected_source_port_ids: [startSourcePortId, endSourcePortId],
connected_source_net_ids: [],
max_length: maximum,
},
makePcbPort(startPcbPortId, startSourcePortId),
makePcbPort(endPcbPortId, endSourcePortId),
makePcbPort(branchPcbPortId, branchSourcePortId),
makeStraightPcbTrace({
pcbTraceId: `pcb_trace_${id}_exact`,
sourceTraceId,
startPcbPortId,
endPcbPortId,
length: exact,
}),
makeStraightPcbTrace({
pcbTraceId: `pcb_trace_${id}_unrelated_branch`,
sourceTraceId,
startPcbPortId,
endPcbPortId: branchPcbPortId,
length: branch,
}),
]
},
)

expect(checkPcbTraceLengths(multidropCircuitJson)).toEqual([])
})

test("retains a violation on the exact two-port PCB trace", () => {
const exactViolationCircuitJson = [
{
type: "source_trace",
source_trace_id: "source_trace_exact_violation",
connected_source_port_ids: ["source_port_a", "source_port_b"],
connected_source_net_ids: [],
max_length: 5,
},
makePcbPort("pcb_port_a", "source_port_a"),
makePcbPort("pcb_port_b", "source_port_b"),
makePcbPort("pcb_port_branch", "source_port_branch"),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_exact_violation",
sourceTraceId: "source_trace_exact_violation",
startPcbPortId: "pcb_port_a",
endPcbPortId: "pcb_port_b",
length: 7,
}),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_unrelated_long_branch",
sourceTraceId: "source_trace_exact_violation",
startPcbPortId: "pcb_port_a",
endPcbPortId: "pcb_port_branch",
length: 12,
}),
] as AnyCircuitElement[]

expect(checkPcbTraceLengths(exactViolationCircuitJson)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_exact_violation",
source_trace_id: "source_trace_exact_violation",
actual_trace_length: 7,
maximum_trace_length: 5,
}),
])
})

test("retains one-port-to-net trace length warnings", () => {
const portToNetCircuitJson = [
{
type: "source_trace",
source_trace_id: "source_trace_ground_drop",
connected_source_port_ids: ["source_port_c9_ground"],
connected_source_net_ids: ["source_net_ground"],
max_length: 5,
},
makePcbPort("pcb_port_c9_ground", "source_port_c9_ground"),
makePcbPort("pcb_port_c3_ground", "source_port_c3_ground"),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_c9_to_ground",
sourceTraceId: "source_trace_ground_drop",
startPcbPortId: "pcb_port_c9_ground",
endPcbPortId: "pcb_port_c3_ground",
length: 5.062790456754819,
}),
] as AnyCircuitElement[]

expect(checkPcbTraceLengths(portToNetCircuitJson)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_c9_to_ground",
source_trace_id: "source_trace_ground_drop",
actual_trace_length: 5.062790456754819,
maximum_trace_length: 5,
}),
])
})

test("keeps existing attribution when no exact endpoint route is exposed", () => {
const routeWithoutEndpointMetadata = [
{
type: "source_trace",
source_trace_id: "source_trace_without_endpoint_metadata",
connected_source_port_ids: ["source_port_a", "source_port_b"],
connected_source_net_ids: [],
max_length: 5,
},
makePcbPort("pcb_port_a", "source_port_a"),
makePcbPort("pcb_port_b", "source_port_b"),
{
type: "pcb_trace",
pcb_trace_id: "pcb_trace_without_endpoint_metadata",
source_trace_id: "source_trace_without_endpoint_metadata",
trace_length: 7,
route: [],
},
] as AnyCircuitElement[]

expect(checkPcbTraceLengths(routeWithoutEndpointMetadata)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_without_endpoint_metadata",
source_trace_id: "source_trace_without_endpoint_metadata",
actual_trace_length: 7,
maximum_trace_length: 5,
}),
])
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A *.test.ts file may have AT MOST one test(...) call, but this file now contains at least 4 test(...) calls (at lines 129, 177, 215, and 245). The new tests added in this diff should be split into separate, numbered test files. For example: check-pcb-trace-lengths1.test.ts, check-pcb-trace-lengths2.test.ts, check-pcb-trace-lengths3.test.ts, check-pcb-trace-lengths4.test.ts, etc. Each file should contain exactly one test(...) call.

Suggested change
test("ignores unrelated long MST branches when exact two-port traces are short", () => {
const cases = [
{ id: "charger_ground", maximum: 5, exact: 3.94, branch: 10.71 },
{ id: "vdd1_decoupling", maximum: 3, exact: 1.16, branch: 6.47 },
{ id: "regulator_output", maximum: 6, exact: 1.57, branch: 17.37 },
]
const multidropCircuitJson = cases.flatMap(
({ id, maximum, exact, branch }): AnyCircuitElement[] => {
const sourceTraceId = `source_trace_${id}`
const startSourcePortId = `source_port_${id}_start`
const endSourcePortId = `source_port_${id}_end`
const branchSourcePortId = `source_port_${id}_branch`
const startPcbPortId = `pcb_port_${id}_start`
const endPcbPortId = `pcb_port_${id}_end`
const branchPcbPortId = `pcb_port_${id}_branch`
return [
{
type: "source_trace",
source_trace_id: sourceTraceId,
connected_source_port_ids: [startSourcePortId, endSourcePortId],
connected_source_net_ids: [],
max_length: maximum,
},
makePcbPort(startPcbPortId, startSourcePortId),
makePcbPort(endPcbPortId, endSourcePortId),
makePcbPort(branchPcbPortId, branchSourcePortId),
makeStraightPcbTrace({
pcbTraceId: `pcb_trace_${id}_exact`,
sourceTraceId,
startPcbPortId,
endPcbPortId,
length: exact,
}),
makeStraightPcbTrace({
pcbTraceId: `pcb_trace_${id}_unrelated_branch`,
sourceTraceId,
startPcbPortId,
endPcbPortId: branchPcbPortId,
length: branch,
}),
]
},
)
expect(checkPcbTraceLengths(multidropCircuitJson)).toEqual([])
})
test("retains a violation on the exact two-port PCB trace", () => {
const exactViolationCircuitJson = [
{
type: "source_trace",
source_trace_id: "source_trace_exact_violation",
connected_source_port_ids: ["source_port_a", "source_port_b"],
connected_source_net_ids: [],
max_length: 5,
},
makePcbPort("pcb_port_a", "source_port_a"),
makePcbPort("pcb_port_b", "source_port_b"),
makePcbPort("pcb_port_branch", "source_port_branch"),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_exact_violation",
sourceTraceId: "source_trace_exact_violation",
startPcbPortId: "pcb_port_a",
endPcbPortId: "pcb_port_b",
length: 7,
}),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_unrelated_long_branch",
sourceTraceId: "source_trace_exact_violation",
startPcbPortId: "pcb_port_a",
endPcbPortId: "pcb_port_branch",
length: 12,
}),
] as AnyCircuitElement[]
expect(checkPcbTraceLengths(exactViolationCircuitJson)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_exact_violation",
source_trace_id: "source_trace_exact_violation",
actual_trace_length: 7,
maximum_trace_length: 5,
}),
])
})
test("retains one-port-to-net trace length warnings", () => {
const portToNetCircuitJson = [
{
type: "source_trace",
source_trace_id: "source_trace_ground_drop",
connected_source_port_ids: ["source_port_c9_ground"],
connected_source_net_ids: ["source_net_ground"],
max_length: 5,
},
makePcbPort("pcb_port_c9_ground", "source_port_c9_ground"),
makePcbPort("pcb_port_c3_ground", "source_port_c3_ground"),
makeStraightPcbTrace({
pcbTraceId: "pcb_trace_c9_to_ground",
sourceTraceId: "source_trace_ground_drop",
startPcbPortId: "pcb_port_c9_ground",
endPcbPortId: "pcb_port_c3_ground",
length: 5.062790456754819,
}),
] as AnyCircuitElement[]
expect(checkPcbTraceLengths(portToNetCircuitJson)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_c9_to_ground",
source_trace_id: "source_trace_ground_drop",
actual_trace_length: 5.062790456754819,
maximum_trace_length: 5,
}),
])
})
test("keeps existing attribution when no exact endpoint route is exposed", () => {
const routeWithoutEndpointMetadata = [
{
type: "source_trace",
source_trace_id: "source_trace_without_endpoint_metadata",
connected_source_port_ids: ["source_port_a", "source_port_b"],
connected_source_net_ids: [],
max_length: 5,
},
makePcbPort("pcb_port_a", "source_port_a"),
makePcbPort("pcb_port_b", "source_port_b"),
{
type: "pcb_trace",
pcb_trace_id: "pcb_trace_without_endpoint_metadata",
source_trace_id: "source_trace_without_endpoint_metadata",
trace_length: 7,
route: [],
},
] as AnyCircuitElement[]
expect(checkPcbTraceLengths(routeWithoutEndpointMetadata)).toEqual([
expect.objectContaining({
pcb_trace_id: "pcb_trace_without_endpoint_metadata",
source_trace_id: "source_trace_without_endpoint_metadata",
actual_trace_length: 7,
maximum_trace_length: 5,
}),
])
})

Spotted by Graphite (based on custom rule: Custom rule)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@seveibar
seveibar merged commit 7638e9a into tscircuit:main Aug 27, 2026
7 checks passed
@tscircuitbot

Copy link
Copy Markdown
Contributor

Thank you for your contribution! 🎉

PR Rating: ⭐⭐⭐
Impact: Major

Track your contributions and see the leaderboard at: tscircuit Contribution Tracker


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants