@tscircuit/schematic-trace-solver 0.0.202 → 0.0.204
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 +54 -7
- package/lib/solvers/TraceCleanupSolver/getInteriorShortcutPaths.ts +41 -0
- package/lib/solvers/TraceCleanupSolver/rerouteGeneratedNetLabelConnectorCrossings.ts +7 -0
- package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/getFixedLabelCoordinate.ts +17 -8
- package/lib/solvers/TraceElbowTransitionSimplificationSolver/TraceElbowTransitionSimplificationSolver.ts +25 -3
- package/package.json +1 -1
- package/site/SchematicTracePipelineSolver/repro-smart-lock-ble-module.page.tsx +7 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +78 -60
- package/tests/bug-reports/bug-report-20260707T134722Z/__snapshots__/bug-report-20260707T134722Z.snap.svg +62 -62
- package/tests/bug-reports/bug-report-20260708T055430Z/__snapshots__/bug-report-20260708T055430Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260728T144234Z/__snapshots__/bug-report-20260728T144234Z.snap.svg +2 -2
- package/tests/bug-reports/bug-report-20260901T055358Z/__snapshots__/bug-report-20260901T055358Z.snap.svg +10 -10
- package/tests/bug-reports/bug-report-20260905T041712Z/__snapshots__/bug-report-20260905T041712Z.snap.svg +10 -10
- package/tests/bug-reports/bug-report-20260907T110144Z/__snapshots__/bug-report-20260907T110144Z.snap.svg +9 -9
- package/tests/bug-reports/bug-report-20260907T145640Z/__snapshots__/bug-report-20260907T145640Z.snap.svg +8 -8
- package/tests/bug-reports/bug-report-20260911T195723Z/__snapshots__/bug-report-20260911T195723Z.snap.svg +19 -19
- package/tests/bug-reports/bug-report-20260916T054012Z/__snapshots__/bug-report-20260916T054012Z.snap.svg +292 -292
- package/tests/examples/__snapshots__/example30.snap.svg +123 -123
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +18 -18
- package/tests/repros/__snapshots__/board-15984-same-net-junction.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-am3352-06-boot.snap.svg +448 -0
- package/tests/repros/__snapshots__/repro-am3352-sheet3-power-rails.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-hub-usb-sheet.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-ina237-current-monitor.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-robot-controller-imu-tof.snap.svg +7 -7
- package/tests/repros/__snapshots__/repro-smart-lock-ble-module.snap.svg +465 -0
- package/tests/repros/__snapshots__/repro-tida010076-page02.snap.svg +10 -10
- package/tests/repros/assets/repro-am3352-06-boot.input.json +1875 -0
- package/tests/repros/assets/repro-smart-lock-ble-module.input.json +2183 -0
- package/tests/repros/repro-am3352-06-boot.test.ts +43 -0
- package/tests/repros/repro-smart-lock-ble-module.test.ts +69 -0
- package/tests/solvers/TraceCleanupSolver/alignSameNetRails-fixed-backbone.test.ts +135 -0
- package/tests/solvers/TraceCleanupSolver/reroute-generated-net-label-connector-crossings.test.ts +49 -0
- package/tests/solvers/TraceElbowTransitionSimplificationSolver/interior-staircase.test.ts +146 -0
package/dist/index.js
CHANGED
|
@@ -6328,10 +6328,6 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
|
|
|
6328
6328
|
const groupIsWithinSingleComponent = [...groupTraceIds].every(
|
|
6329
6329
|
(traceId) => traceMap.get(traceId)?.pins.every((pin) => pin.chipId === group[0].componentId) === true
|
|
6330
6330
|
);
|
|
6331
|
-
const groupHasOnlySingleRailTraces = [...groupTraceIds].every(
|
|
6332
|
-
(traceId) => traceMap.get(traceId)?.tracePath.length === 4
|
|
6333
|
-
);
|
|
6334
|
-
if (!groupHasOnlySingleRailTraces) return null;
|
|
6335
6331
|
const orientation = group[0].orientation;
|
|
6336
6332
|
const linkedLabels = getLabelsLinkedToRailGroup(
|
|
6337
6333
|
group,
|
|
@@ -6362,6 +6358,11 @@ var getFixedLabelCoordinate = (group, netLabelPlacements, traces) => {
|
|
|
6362
6358
|
const coordinate = group.find(
|
|
6363
6359
|
(segment) => Math.abs(segment.coordinate - labelCoordinate) <= RAIL_ALIGNMENT_EPSILON
|
|
6364
6360
|
)?.coordinate ?? labelCoordinate;
|
|
6361
|
+
if (group.some(
|
|
6362
|
+
(segment) => traceMap.get(segment.traceId)?.tracePath.length !== 4 && !nearlyEqual(segment.coordinate, coordinate)
|
|
6363
|
+
)) {
|
|
6364
|
+
return null;
|
|
6365
|
+
}
|
|
6365
6366
|
const labelIsAnchoredToRoutedBackbone = anchoringLabels.some(
|
|
6366
6367
|
(label) => label.mspConnectionPairIds.some(
|
|
6367
6368
|
(traceId) => (traceMap.get(traceId)?.tracePath.length ?? 0) > 4
|
|
@@ -7810,6 +7811,36 @@ var is4PointRectangle = (path) => {
|
|
|
7810
7811
|
return isHVHC || isVHVC;
|
|
7811
7812
|
};
|
|
7812
7813
|
|
|
7814
|
+
// lib/solvers/TraceCleanupSolver/getInteriorShortcutPaths.ts
|
|
7815
|
+
var getInteriorShortcutPaths = (path) => {
|
|
7816
|
+
const candidates = [path];
|
|
7817
|
+
for (let startIndex = 1; startIndex < path.length - 3; startIndex++) {
|
|
7818
|
+
for (let endIndex = startIndex + 2; endIndex < path.length - 1; endIndex++) {
|
|
7819
|
+
for (const connection of tryConnectPoints(
|
|
7820
|
+
path[startIndex],
|
|
7821
|
+
path[endIndex]
|
|
7822
|
+
)) {
|
|
7823
|
+
const candidate = simplifyPath([
|
|
7824
|
+
...path.slice(0, startIndex),
|
|
7825
|
+
...connection,
|
|
7826
|
+
...path.slice(endIndex + 1)
|
|
7827
|
+
]);
|
|
7828
|
+
const preservesTerminalDirections = [
|
|
7829
|
+
[0, 1],
|
|
7830
|
+
[-1, -2]
|
|
7831
|
+
].every(([endIndex2, neighborIndex]) => {
|
|
7832
|
+
const endpoint = path.at(endIndex2);
|
|
7833
|
+
const neighbor = path.at(neighborIndex);
|
|
7834
|
+
const candidateNeighbor = candidate.at(neighborIndex);
|
|
7835
|
+
return (neighbor.x - endpoint.x) * (candidateNeighbor.x - endpoint.x) + (neighbor.y - endpoint.y) * (candidateNeighbor.y - endpoint.y) > 0;
|
|
7836
|
+
});
|
|
7837
|
+
if (preservesTerminalDirections) candidates.push(candidate);
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
return candidates;
|
|
7842
|
+
};
|
|
7843
|
+
|
|
7813
7844
|
// lib/solvers/TraceCleanupSolver/rerouteGeneratedNetLabelConnectorCrossings.ts
|
|
7814
7845
|
var EPS13 = 1e-6;
|
|
7815
7846
|
var getConnectorObstacleBounds = (connector, segmentIndex, labels) => {
|
|
@@ -7902,7 +7933,12 @@ var rerouteGeneratedNetLabelConnectorCrossings = ({
|
|
|
7902
7933
|
obstacleBounds,
|
|
7903
7934
|
chipBounds: [],
|
|
7904
7935
|
clearance
|
|
7905
|
-
}).
|
|
7936
|
+
}).flatMap(
|
|
7937
|
+
(candidate) => getInteriorShortcutPaths(candidate.path).map((path) => ({
|
|
7938
|
+
...candidate,
|
|
7939
|
+
path
|
|
7940
|
+
}))
|
|
7941
|
+
).filter(
|
|
7906
7942
|
(candidate) => getPathLength4(candidate.path) <= getPathLength4(trace.tracePath) + EPS13 && countTurns(candidate.path) <= countTurns(trace.tracePath) + 2 && !isPathCollidingWithObstacles(
|
|
7907
7943
|
candidate.path,
|
|
7908
7944
|
componentAndTextObstacles
|
|
@@ -15470,7 +15506,17 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
|
|
|
15470
15506
|
const otherNetTraces = this.outputTraces.filter(
|
|
15471
15507
|
(otherTrace) => otherTrace.mspPairId !== trace.mspPairId && otherTrace.globalConnNetId !== trace.globalConnNetId
|
|
15472
15508
|
);
|
|
15509
|
+
const initialPathLength = getPathLength6(tracePath);
|
|
15473
15510
|
const candidateByPath = /* @__PURE__ */ new Map();
|
|
15511
|
+
for (const candidate of getInteriorShortcutPaths(tracePath)) {
|
|
15512
|
+
if (Math.abs(getPathLength6(candidate) - initialPathLength) > PATH_LENGTH_EPSILON3) {
|
|
15513
|
+
continue;
|
|
15514
|
+
}
|
|
15515
|
+
candidateByPath.set(
|
|
15516
|
+
candidate.map((point) => `${point.x},${point.y}`).join(";"),
|
|
15517
|
+
candidate
|
|
15518
|
+
);
|
|
15519
|
+
}
|
|
15474
15520
|
const completedReroutes = this.input.completedReroutes.filter(
|
|
15475
15521
|
(completedReroute) => completedReroute.initialTrace.mspPairId === trace.mspPairId
|
|
15476
15522
|
);
|
|
@@ -15522,7 +15568,6 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
|
|
|
15522
15568
|
({ label }) => `${label.globalConnNetId}:${label.netId}`
|
|
15523
15569
|
)
|
|
15524
15570
|
);
|
|
15525
|
-
const initialPathLength = getPathLength6(tracePath);
|
|
15526
15571
|
const validCandidates = [...candidateByPath.values()].filter(
|
|
15527
15572
|
(candidatePath) => {
|
|
15528
15573
|
const candidateTrace = { ...trace, tracePath: candidatePath };
|
|
@@ -15539,7 +15584,9 @@ var TraceElbowTransitionSimplificationSolver = class extends BaseSolver {
|
|
|
15539
15584
|
this.input.netLabelPlacements,
|
|
15540
15585
|
[trace],
|
|
15541
15586
|
[candidateTrace]
|
|
15542
|
-
) && !isPathCollidingWithObstacles(candidatePath, this.obstacles) && !doesPathCoincideWithTraces(candidatePath, otherNetTraces)
|
|
15587
|
+
) && !isPathCollidingWithObstacles(candidatePath, this.obstacles) && !doesPathCoincideWithTraces(candidatePath, otherNetTraces) && otherNetTraces.every(
|
|
15588
|
+
(otherTrace) => countPathIntersections(candidatePath, otherTrace.tracePath) <= countPathIntersections(tracePath, otherTrace.tracePath)
|
|
15589
|
+
);
|
|
15543
15590
|
}
|
|
15544
15591
|
);
|
|
15545
15592
|
const getOverlapCount = (candidatePath) => detectTraceLabelOverlap({
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { simplifyPath } from "./simplifyPath"
|
|
3
|
+
import { tryConnectPoints } from "./tryConnectPoints"
|
|
4
|
+
|
|
5
|
+
export const getInteriorShortcutPaths = (path: Point[]): Point[][] => {
|
|
6
|
+
const candidates: Point[][] = [path]
|
|
7
|
+
// Reconnect interior points to remove old loops left behind by a connector detour.
|
|
8
|
+
for (let startIndex = 1; startIndex < path.length - 3; startIndex++) {
|
|
9
|
+
for (
|
|
10
|
+
let endIndex = startIndex + 2;
|
|
11
|
+
endIndex < path.length - 1;
|
|
12
|
+
endIndex++
|
|
13
|
+
) {
|
|
14
|
+
for (const connection of tryConnectPoints(
|
|
15
|
+
path[startIndex]!,
|
|
16
|
+
path[endIndex]!,
|
|
17
|
+
)) {
|
|
18
|
+
const candidate = simplifyPath([
|
|
19
|
+
...path.slice(0, startIndex),
|
|
20
|
+
...connection,
|
|
21
|
+
...path.slice(endIndex + 1),
|
|
22
|
+
])
|
|
23
|
+
const preservesTerminalDirections = [
|
|
24
|
+
[0, 1],
|
|
25
|
+
[-1, -2],
|
|
26
|
+
].every(([endIndex, neighborIndex]) => {
|
|
27
|
+
const endpoint = path.at(endIndex!)!
|
|
28
|
+
const neighbor = path.at(neighborIndex!)!
|
|
29
|
+
const candidateNeighbor = candidate.at(neighborIndex!)!
|
|
30
|
+
return (
|
|
31
|
+
(neighbor.x - endpoint.x) * (candidateNeighbor.x - endpoint.x) +
|
|
32
|
+
(neighbor.y - endpoint.y) * (candidateNeighbor.y - endpoint.y) >
|
|
33
|
+
0
|
|
34
|
+
)
|
|
35
|
+
})
|
|
36
|
+
if (preservesTerminalDirections) candidates.push(candidate)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return candidates
|
|
41
|
+
}
|
|
@@ -9,6 +9,7 @@ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/Schemati
|
|
|
9
9
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
10
10
|
import { doesPathCoincideWithTraces } from "lib/utils/doesPathCoincideWithTraces"
|
|
11
11
|
import { countTurns } from "./countTurns"
|
|
12
|
+
import { getInteriorShortcutPaths } from "./getInteriorShortcutPaths"
|
|
12
13
|
import { hasCollisionsWithLabels } from "./hasCollisionsWithLabels"
|
|
13
14
|
import { findPerpendicularPathCrossings } from "./sub-solver/findIntersectionsWithObstacles"
|
|
14
15
|
import { generatePerpendicularTraceDetours } from "./sub-solver/generateLShapeRerouteCandidates"
|
|
@@ -150,6 +151,12 @@ export const rerouteGeneratedNetLabelConnectorCrossings = ({
|
|
|
150
151
|
chipBounds: [],
|
|
151
152
|
clearance,
|
|
152
153
|
})
|
|
154
|
+
.flatMap((candidate) =>
|
|
155
|
+
getInteriorShortcutPaths(candidate.path).map((path) => ({
|
|
156
|
+
...candidate,
|
|
157
|
+
path,
|
|
158
|
+
})),
|
|
159
|
+
)
|
|
153
160
|
.filter(
|
|
154
161
|
(candidate) =>
|
|
155
162
|
getPathLength(candidate.path) <=
|
|
@@ -2,7 +2,11 @@ import { distance } from "@tscircuit/math-utils"
|
|
|
2
2
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
3
3
|
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
4
4
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getDistinctCoordinates,
|
|
7
|
+
nearlyEqual,
|
|
8
|
+
RAIL_ALIGNMENT_EPSILON,
|
|
9
|
+
} from "./geometry"
|
|
6
10
|
import type { RailSegment } from "./types"
|
|
7
11
|
|
|
8
12
|
const getTransitivelyConnectedTraceIds = (
|
|
@@ -59,13 +63,6 @@ export const getFixedLabelCoordinate = (
|
|
|
59
63
|
.get(traceId)
|
|
60
64
|
?.pins.every((pin) => pin.chipId === group[0]!.componentId) === true,
|
|
61
65
|
)
|
|
62
|
-
const groupHasOnlySingleRailTraces = [...groupTraceIds].every(
|
|
63
|
-
(traceId) => traceMap.get(traceId)?.tracePath.length === 4,
|
|
64
|
-
)
|
|
65
|
-
// A fixed label may lengthen the endpoint legs around one movable rail. A
|
|
66
|
-
// trace with more elbows still has to pass the normal readability scoring.
|
|
67
|
-
if (!groupHasOnlySingleRailTraces) return null
|
|
68
|
-
|
|
69
66
|
const orientation = group[0]!.orientation
|
|
70
67
|
const linkedLabels = getLabelsLinkedToRailGroup(
|
|
71
68
|
group,
|
|
@@ -107,6 +104,18 @@ export const getFixedLabelCoordinate = (
|
|
|
107
104
|
Math.abs(segment.coordinate - labelCoordinate) <=
|
|
108
105
|
RAIL_ALIGNMENT_EPSILON,
|
|
109
106
|
)?.coordinate ?? labelCoordinate
|
|
107
|
+
// A fixed label may lengthen the endpoint legs around one movable rail.
|
|
108
|
+
// A multi-turn backbone may share the group when it already lies on that
|
|
109
|
+
// coordinate, but moving its elbows still requires normal readability scoring.
|
|
110
|
+
if (
|
|
111
|
+
group.some(
|
|
112
|
+
(segment) =>
|
|
113
|
+
traceMap.get(segment.traceId)?.tracePath.length !== 4 &&
|
|
114
|
+
!nearlyEqual(segment.coordinate, coordinate),
|
|
115
|
+
)
|
|
116
|
+
) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
110
119
|
const labelIsAnchoredToRoutedBackbone = anchoringLabels.some((label) =>
|
|
111
120
|
label.mspConnectionPairIds.some(
|
|
112
121
|
(traceId) => (traceMap.get(traceId)?.tracePath.length ?? 0) > 4,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { Point } from "@tscircuit/math-utils"
|
|
2
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
3
3
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
4
|
+
import { countPathIntersections } from "lib/solvers/Example28Solver/geometry"
|
|
4
5
|
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
6
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
6
7
|
import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
7
8
|
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
8
9
|
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
10
|
+
import { getInteriorShortcutPaths } from "lib/solvers/TraceCleanupSolver/getInteriorShortcutPaths"
|
|
9
11
|
import { preservesLabelAnchors } from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/preservesLabelAnchors"
|
|
10
12
|
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
11
13
|
import { detectTraceLabelOverlap } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/detectTraceLabelOverlap"
|
|
@@ -82,7 +84,24 @@ export class TraceElbowTransitionSimplificationSolver extends BaseSolver {
|
|
|
82
84
|
otherTrace.mspPairId !== trace.mspPairId &&
|
|
83
85
|
otherTrace.globalConnNetId !== trace.globalConnNetId,
|
|
84
86
|
)
|
|
87
|
+
const initialPathLength = getPathLength(tracePath)
|
|
85
88
|
const candidateByPath = new Map<string, Point[]>()
|
|
89
|
+
// Consecutive label detours can leave a staircase between two clear legs.
|
|
90
|
+
// Keep equivalent-length shortcuts, as with the transition candidates below;
|
|
91
|
+
// shortening the route belongs to turn minimization. Terminal directions,
|
|
92
|
+
// label anchors, and collision checks still apply.
|
|
93
|
+
for (const candidate of getInteriorShortcutPaths(tracePath)) {
|
|
94
|
+
if (
|
|
95
|
+
Math.abs(getPathLength(candidate) - initialPathLength) >
|
|
96
|
+
PATH_LENGTH_EPSILON
|
|
97
|
+
) {
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
candidateByPath.set(
|
|
101
|
+
candidate.map((point) => `${point.x},${point.y}`).join(";"),
|
|
102
|
+
candidate,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
86
105
|
const completedReroutes = this.input.completedReroutes.filter(
|
|
87
106
|
(completedReroute) =>
|
|
88
107
|
completedReroute.initialTrace.mspPairId === trace.mspPairId,
|
|
@@ -145,8 +164,6 @@ export class TraceElbowTransitionSimplificationSolver extends BaseSolver {
|
|
|
145
164
|
({ label }) => `${label.globalConnNetId}:${label.netId}`,
|
|
146
165
|
),
|
|
147
166
|
)
|
|
148
|
-
const initialPathLength = getPathLength(tracePath)
|
|
149
|
-
|
|
150
167
|
const validCandidates = [...candidateByPath.values()].filter(
|
|
151
168
|
(candidatePath) => {
|
|
152
169
|
const candidateTrace = { ...trace, tracePath: candidatePath }
|
|
@@ -175,7 +192,12 @@ export class TraceElbowTransitionSimplificationSolver extends BaseSolver {
|
|
|
175
192
|
[candidateTrace],
|
|
176
193
|
) &&
|
|
177
194
|
!isPathCollidingWithObstacles(candidatePath, this.obstacles) &&
|
|
178
|
-
!doesPathCoincideWithTraces(candidatePath, otherNetTraces)
|
|
195
|
+
!doesPathCoincideWithTraces(candidatePath, otherNetTraces) &&
|
|
196
|
+
otherNetTraces.every(
|
|
197
|
+
(otherTrace) =>
|
|
198
|
+
countPathIntersections(candidatePath, otherTrace.tracePath) <=
|
|
199
|
+
countPathIntersections(tracePath, otherTrace.tracePath),
|
|
200
|
+
)
|
|
179
201
|
)
|
|
180
202
|
},
|
|
181
203
|
)
|
package/package.json
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
import { PipelineDebugger } from "site/components/PipelineDebugger"
|
|
3
|
+
import input from "../../tests/repros/assets/repro-smart-lock-ble-module.input.json"
|
|
4
|
+
|
|
5
|
+
export default () => (
|
|
6
|
+
<PipelineDebugger inputProblem={input as unknown as InputProblem} />
|
|
7
|
+
)
|