From 08618201dce655427277d3afd21ada5b2f1fe735 Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 18 Aug 2026 14:52:29 -0700 Subject: [PATCH 1/2] fix: resolve stable static solver names --- README.md | 8 +++++ lib/BaseSolver.ts | 25 ++++++++++++++- lib/react/DownloadDropdown.tsx | 22 ++++++------- lib/react/GenericSolverToolbar.tsx | 4 +-- lib/react/PipelineStagesTable.tsx | 4 +-- lib/react/SolverBreadcrumbInputDownloader.tsx | 4 +-- tests/BaseSolver.test.ts | 32 +++++++++++++++++-- tests/PipelineStagesTable.test.ts | 16 ++++++++++ 8 files changed, 95 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a7d03d3..24deef5 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ import { BaseSolver } from "@tscircuit/solver-utils" import type { GraphicsObject } from "graphics-debug" class CountToTenSolver extends BaseSolver { + static override solverName = "CountToTenSolver" + target = 10 value = 0 @@ -205,6 +207,10 @@ export default function SolverPage() { - `getConstructorParams()`: return reproducible constructor input (used by download helpers). - `getOutput()`: standardized solved result (especially useful in pipelines). +Set `static solverName` on solver classes that may pass through a minifying +bundler. `getSolverName()` and the React debugger prefer this explicit name and +fall back to the JavaScript constructor name when it is absent. + ## BaseSolver Guide ### Lifecycle @@ -380,6 +386,7 @@ bun test - `BaseSolver` - `BasePipelineSolver` - `definePipelineStep` + - `getSolverName` - React: `@tscircuit/solver-utils/react` - `GenericSolverDebugger` - `GenericSolverToolbar` @@ -396,6 +403,7 @@ bun test - `error = null` - `stats = {}` - `MAX_ITERATIONS = 100_000` +- `static solverName` (optional stable display name) ### `BasePipelineSolver` notable state diff --git a/lib/BaseSolver.ts b/lib/BaseSolver.ts index e071bf8..78e5d24 100644 --- a/lib/BaseSolver.ts +++ b/lib/BaseSolver.ts @@ -12,6 +12,8 @@ import type { GraphicsObject } from "graphics-debug" * needed to construct the solver. */ export class BaseSolver { + static solverName?: string + MAX_ITERATIONS = 100e3 solved = false failed = false @@ -25,7 +27,10 @@ export class BaseSolver { _setupDone = false getSolverName(): string { - return this.constructor.name + return ( + (this.constructor as typeof BaseSolver).solverName ?? + this.constructor.name + ) } setup() { @@ -119,3 +124,21 @@ export class BaseSolver { } } } + +/** + * Returns the explicit solver name when one is attached to the constructor. + * + * Keeping this lookup outside the instance method allows the debugger to show + * stable names for solvers that bundle an older or package-local BaseSolver. + */ +export const getSolverName = (solver: object): string => { + const solverWithLegacyName = solver as { + getSolverName?: () => string + } + + return ( + (solver.constructor as typeof BaseSolver).solverName ?? + solverWithLegacyName.getSolverName?.() ?? + solver.constructor.name + ) +} diff --git a/lib/react/DownloadDropdown.tsx b/lib/react/DownloadDropdown.tsx index e5d0a6a..00ab47b 100644 --- a/lib/react/DownloadDropdown.tsx +++ b/lib/react/DownloadDropdown.tsx @@ -1,5 +1,5 @@ -import { useState, useRef, useEffect } from "react" -import type { BaseSolver } from "../BaseSolver" +import { useEffect, useRef, useState } from "react" +import { type BaseSolver, getSolverName } from "../BaseSolver" interface DownloadDropdownProps { solver: BaseSolver @@ -49,7 +49,7 @@ export const DownloadDropdown = ({ try { if (typeof solver.getConstructorParams !== "function") { alert( - `getConstructorParams() is not implemented for ${solver.getSolverName()}`, + `getConstructorParams() is not implemented for ${getSolverName(solver)}`, ) return } @@ -63,12 +63,12 @@ export const DownloadDropdown = ({ const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url - a.download = `${solver.getSolverName()}_params.json` + a.download = `${getSolverName(solver)}_params.json` a.click() URL.revokeObjectURL(url) } catch (error) { alert( - `Error downloading params for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, + `Error downloading params for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -79,7 +79,7 @@ export const DownloadDropdown = ({ const params = deepRemoveUnderscoreProperties( solver.getConstructorParams(), ) - const solverName = solver.getSolverName() + const solverName = getSolverName(solver) const isSchematicTracePipelineSolver = solverName === "SchematicTracePipelineSolver" @@ -118,7 +118,7 @@ export default () => { URL.revokeObjectURL(url) } catch (error) { alert( - `Error generating page.tsx for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, + `Error generating page.tsx for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -129,7 +129,7 @@ export default () => { const params = deepRemoveUnderscoreProperties( solver.getConstructorParams(), ) - const solverName = solver.getSolverName() + const solverName = getSolverName(solver) const content = `import { ${solverName} } from "lib/solvers/${solverName}/${solverName}" import { test, expect } from "bun:test" @@ -156,7 +156,7 @@ test("${solverName} should solve problem correctly", () => { URL.revokeObjectURL(url) } catch (error) { alert( - `Error generating test.ts for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, + `Error generating test.ts for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -167,9 +167,9 @@ test("${solverName} should solve problem correctly", () => { {isOpen && ( diff --git a/lib/react/GenericSolverToolbar.tsx b/lib/react/GenericSolverToolbar.tsx index 018a350..c50848f 100644 --- a/lib/react/GenericSolverToolbar.tsx +++ b/lib/react/GenericSolverToolbar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useReducer, useRef, useState } from "react" -import type { BaseSolver } from "../BaseSolver" +import { type BaseSolver, getSolverName } from "../BaseSolver" import { GenericSolverStatsSummary } from "./GenericSolverStatsSummary" import { SolverBreadcrumbInputDownloader } from "./SolverBreadcrumbInputDownloader" @@ -303,7 +303,7 @@ export const GenericSolverToolbar = ({ {solver.stats && Object.keys(solver.stats).length > 0 && ( )} diff --git a/lib/react/PipelineStagesTable.tsx b/lib/react/PipelineStagesTable.tsx index 5f6342d..07d043a 100644 --- a/lib/react/PipelineStagesTable.tsx +++ b/lib/react/PipelineStagesTable.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react" import type { BasePipelineSolver } from "../BasePipelineSolver" -import type { BaseSolver } from "../BaseSolver" +import { type BaseSolver, getSolverName } from "../BaseSolver" interface PipelineStagesTableProps { solver: BaseSolver @@ -151,7 +151,7 @@ const getActiveSubSolverInfo = (solver: BaseSolver): StageInfo[] => { return [ { index: null, - name: activeSubSolver.getSolverName(), + name: getSolverName(activeSubSolver), status: getSubSolverStatus(activeSubSolver), firstIteration: null, iterations: activeSubSolver.iterations, diff --git a/lib/react/SolverBreadcrumbInputDownloader.tsx b/lib/react/SolverBreadcrumbInputDownloader.tsx index ff78286..e2246c0 100644 --- a/lib/react/SolverBreadcrumbInputDownloader.tsx +++ b/lib/react/SolverBreadcrumbInputDownloader.tsx @@ -1,4 +1,4 @@ -import type { BaseSolver } from "../BaseSolver" +import { type BaseSolver, getSolverName } from "../BaseSolver" import { DownloadDropdown } from "./DownloadDropdown" export const getSolverChain = (solver: BaseSolver): BaseSolver[] => { @@ -21,7 +21,7 @@ export const SolverBreadcrumbInputDownloader = ({ return (
{solverChain.map((s, index) => ( -
+
{index > 0 && }
diff --git a/tests/BaseSolver.test.ts b/tests/BaseSolver.test.ts index 76ee9e3..155f441 100644 --- a/tests/BaseSolver.test.ts +++ b/tests/BaseSolver.test.ts @@ -1,5 +1,5 @@ -import { test, expect } from "bun:test" -import { BaseSolver } from "../lib/BaseSolver" +import { expect, test } from "bun:test" +import { BaseSolver, getSolverName } from "../lib/BaseSolver" class TestSolver extends BaseSolver { target = 10 @@ -44,6 +44,34 @@ test("BaseSolver basic functionality", () => { expect(solver._setupDone).toBe(false) }) +test("BaseSolver uses an explicit static solver name", () => { + class MinifiedSolver extends BaseSolver { + static override solverName = "ReadableSolver" + } + + expect(new MinifiedSolver().getSolverName()).toBe("ReadableSolver") +}) + +test("getSolverName supports solvers with a legacy instance method", () => { + class MinifiedLegacySolver extends BaseSolver { + static override solverName = "ReadableLegacySolver" + + override getSolverName() { + return "aby" + } + } + + expect(getSolverName(new MinifiedLegacySolver())).toBe("ReadableLegacySolver") +}) + +test("getSolverName supports solvers without an instance name method", () => { + class StandaloneSolver { + static solverName = "ReadableStandaloneSolver" + } + + expect(getSolverName(new StandaloneSolver())).toBe("ReadableStandaloneSolver") +}) + test("BaseSolver setup and solving", () => { const solver = new TestSolver() solver.solve() diff --git a/tests/PipelineStagesTable.test.ts b/tests/PipelineStagesTable.test.ts index 1c7c52a..16b151f 100644 --- a/tests/PipelineStagesTable.test.ts +++ b/tests/PipelineStagesTable.test.ts @@ -26,6 +26,14 @@ class NestedSolver extends BaseSolver { } } +class StaticallyNamedLegacySolver extends BaseSolver { + static override solverName = "ReadableNestedSolver" + + override getSolverName() { + return "aby" + } +} + class FirstStageSolver extends BaseSolver {} class SecondStageSolver extends BaseSolver {} @@ -115,6 +123,14 @@ describe("getDisplayedStages", () => { ]) }) + test("prefers an active subsolver's static name", () => { + const activeSubSolver = new StaticallyNamedLegacySolver() + + expect(getDisplayedStages(new NestedSolver(activeSubSolver))[0]?.name).toBe( + "ReadableNestedSolver", + ) + }) + test("marks solved and failed subsolvers with the matching display status", () => { const solvedSubSolver = new LeafSolver("Solved") solvedSubSolver.solved = true From 87e02e8a4efef7669e707923d92daa45dc978412 Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 18 Aug 2026 15:28:56 -0700 Subject: [PATCH 2/2] revert: use existing getSolverName contract --- README.md | 8 ----- lib/BaseSolver.ts | 25 +-------------- lib/react/DownloadDropdown.tsx | 22 ++++++------- lib/react/GenericSolverToolbar.tsx | 4 +-- lib/react/PipelineStagesTable.tsx | 4 +-- lib/react/SolverBreadcrumbInputDownloader.tsx | 4 +-- tests/BaseSolver.test.ts | 32 ++----------------- tests/PipelineStagesTable.test.ts | 16 ---------- 8 files changed, 20 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 24deef5..a7d03d3 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,6 @@ import { BaseSolver } from "@tscircuit/solver-utils" import type { GraphicsObject } from "graphics-debug" class CountToTenSolver extends BaseSolver { - static override solverName = "CountToTenSolver" - target = 10 value = 0 @@ -207,10 +205,6 @@ export default function SolverPage() { - `getConstructorParams()`: return reproducible constructor input (used by download helpers). - `getOutput()`: standardized solved result (especially useful in pipelines). -Set `static solverName` on solver classes that may pass through a minifying -bundler. `getSolverName()` and the React debugger prefer this explicit name and -fall back to the JavaScript constructor name when it is absent. - ## BaseSolver Guide ### Lifecycle @@ -386,7 +380,6 @@ bun test - `BaseSolver` - `BasePipelineSolver` - `definePipelineStep` - - `getSolverName` - React: `@tscircuit/solver-utils/react` - `GenericSolverDebugger` - `GenericSolverToolbar` @@ -403,7 +396,6 @@ bun test - `error = null` - `stats = {}` - `MAX_ITERATIONS = 100_000` -- `static solverName` (optional stable display name) ### `BasePipelineSolver` notable state diff --git a/lib/BaseSolver.ts b/lib/BaseSolver.ts index 78e5d24..e071bf8 100644 --- a/lib/BaseSolver.ts +++ b/lib/BaseSolver.ts @@ -12,8 +12,6 @@ import type { GraphicsObject } from "graphics-debug" * needed to construct the solver. */ export class BaseSolver { - static solverName?: string - MAX_ITERATIONS = 100e3 solved = false failed = false @@ -27,10 +25,7 @@ export class BaseSolver { _setupDone = false getSolverName(): string { - return ( - (this.constructor as typeof BaseSolver).solverName ?? - this.constructor.name - ) + return this.constructor.name } setup() { @@ -124,21 +119,3 @@ export class BaseSolver { } } } - -/** - * Returns the explicit solver name when one is attached to the constructor. - * - * Keeping this lookup outside the instance method allows the debugger to show - * stable names for solvers that bundle an older or package-local BaseSolver. - */ -export const getSolverName = (solver: object): string => { - const solverWithLegacyName = solver as { - getSolverName?: () => string - } - - return ( - (solver.constructor as typeof BaseSolver).solverName ?? - solverWithLegacyName.getSolverName?.() ?? - solver.constructor.name - ) -} diff --git a/lib/react/DownloadDropdown.tsx b/lib/react/DownloadDropdown.tsx index 00ab47b..e5d0a6a 100644 --- a/lib/react/DownloadDropdown.tsx +++ b/lib/react/DownloadDropdown.tsx @@ -1,5 +1,5 @@ -import { useEffect, useRef, useState } from "react" -import { type BaseSolver, getSolverName } from "../BaseSolver" +import { useState, useRef, useEffect } from "react" +import type { BaseSolver } from "../BaseSolver" interface DownloadDropdownProps { solver: BaseSolver @@ -49,7 +49,7 @@ export const DownloadDropdown = ({ try { if (typeof solver.getConstructorParams !== "function") { alert( - `getConstructorParams() is not implemented for ${getSolverName(solver)}`, + `getConstructorParams() is not implemented for ${solver.getSolverName()}`, ) return } @@ -63,12 +63,12 @@ export const DownloadDropdown = ({ const url = URL.createObjectURL(blob) const a = document.createElement("a") a.href = url - a.download = `${getSolverName(solver)}_params.json` + a.download = `${solver.getSolverName()}_params.json` a.click() URL.revokeObjectURL(url) } catch (error) { alert( - `Error downloading params for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, + `Error downloading params for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -79,7 +79,7 @@ export const DownloadDropdown = ({ const params = deepRemoveUnderscoreProperties( solver.getConstructorParams(), ) - const solverName = getSolverName(solver) + const solverName = solver.getSolverName() const isSchematicTracePipelineSolver = solverName === "SchematicTracePipelineSolver" @@ -118,7 +118,7 @@ export default () => { URL.revokeObjectURL(url) } catch (error) { alert( - `Error generating page.tsx for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, + `Error generating page.tsx for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -129,7 +129,7 @@ export default () => { const params = deepRemoveUnderscoreProperties( solver.getConstructorParams(), ) - const solverName = getSolverName(solver) + const solverName = solver.getSolverName() const content = `import { ${solverName} } from "lib/solvers/${solverName}/${solverName}" import { test, expect } from "bun:test" @@ -156,7 +156,7 @@ test("${solverName} should solve problem correctly", () => { URL.revokeObjectURL(url) } catch (error) { alert( - `Error generating test.ts for ${getSolverName(solver)}: ${error instanceof Error ? error.message : String(error)}`, + `Error generating test.ts for ${solver.getSolverName()}: ${error instanceof Error ? error.message : String(error)}`, ) } setIsOpen(false) @@ -167,9 +167,9 @@ test("${solverName} should solve problem correctly", () => { {isOpen && ( diff --git a/lib/react/GenericSolverToolbar.tsx b/lib/react/GenericSolverToolbar.tsx index c50848f..018a350 100644 --- a/lib/react/GenericSolverToolbar.tsx +++ b/lib/react/GenericSolverToolbar.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useReducer, useRef, useState } from "react" -import { type BaseSolver, getSolverName } from "../BaseSolver" +import type { BaseSolver } from "../BaseSolver" import { GenericSolverStatsSummary } from "./GenericSolverStatsSummary" import { SolverBreadcrumbInputDownloader } from "./SolverBreadcrumbInputDownloader" @@ -303,7 +303,7 @@ export const GenericSolverToolbar = ({ {solver.stats && Object.keys(solver.stats).length > 0 && ( )} diff --git a/lib/react/PipelineStagesTable.tsx b/lib/react/PipelineStagesTable.tsx index 07d043a..5f6342d 100644 --- a/lib/react/PipelineStagesTable.tsx +++ b/lib/react/PipelineStagesTable.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react" import type { BasePipelineSolver } from "../BasePipelineSolver" -import { type BaseSolver, getSolverName } from "../BaseSolver" +import type { BaseSolver } from "../BaseSolver" interface PipelineStagesTableProps { solver: BaseSolver @@ -151,7 +151,7 @@ const getActiveSubSolverInfo = (solver: BaseSolver): StageInfo[] => { return [ { index: null, - name: getSolverName(activeSubSolver), + name: activeSubSolver.getSolverName(), status: getSubSolverStatus(activeSubSolver), firstIteration: null, iterations: activeSubSolver.iterations, diff --git a/lib/react/SolverBreadcrumbInputDownloader.tsx b/lib/react/SolverBreadcrumbInputDownloader.tsx index e2246c0..ff78286 100644 --- a/lib/react/SolverBreadcrumbInputDownloader.tsx +++ b/lib/react/SolverBreadcrumbInputDownloader.tsx @@ -1,4 +1,4 @@ -import { type BaseSolver, getSolverName } from "../BaseSolver" +import type { BaseSolver } from "../BaseSolver" import { DownloadDropdown } from "./DownloadDropdown" export const getSolverChain = (solver: BaseSolver): BaseSolver[] => { @@ -21,7 +21,7 @@ export const SolverBreadcrumbInputDownloader = ({ return (
{solverChain.map((s, index) => ( -
+
{index > 0 && }
diff --git a/tests/BaseSolver.test.ts b/tests/BaseSolver.test.ts index 155f441..76ee9e3 100644 --- a/tests/BaseSolver.test.ts +++ b/tests/BaseSolver.test.ts @@ -1,5 +1,5 @@ -import { expect, test } from "bun:test" -import { BaseSolver, getSolverName } from "../lib/BaseSolver" +import { test, expect } from "bun:test" +import { BaseSolver } from "../lib/BaseSolver" class TestSolver extends BaseSolver { target = 10 @@ -44,34 +44,6 @@ test("BaseSolver basic functionality", () => { expect(solver._setupDone).toBe(false) }) -test("BaseSolver uses an explicit static solver name", () => { - class MinifiedSolver extends BaseSolver { - static override solverName = "ReadableSolver" - } - - expect(new MinifiedSolver().getSolverName()).toBe("ReadableSolver") -}) - -test("getSolverName supports solvers with a legacy instance method", () => { - class MinifiedLegacySolver extends BaseSolver { - static override solverName = "ReadableLegacySolver" - - override getSolverName() { - return "aby" - } - } - - expect(getSolverName(new MinifiedLegacySolver())).toBe("ReadableLegacySolver") -}) - -test("getSolverName supports solvers without an instance name method", () => { - class StandaloneSolver { - static solverName = "ReadableStandaloneSolver" - } - - expect(getSolverName(new StandaloneSolver())).toBe("ReadableStandaloneSolver") -}) - test("BaseSolver setup and solving", () => { const solver = new TestSolver() solver.solve() diff --git a/tests/PipelineStagesTable.test.ts b/tests/PipelineStagesTable.test.ts index 16b151f..1c7c52a 100644 --- a/tests/PipelineStagesTable.test.ts +++ b/tests/PipelineStagesTable.test.ts @@ -26,14 +26,6 @@ class NestedSolver extends BaseSolver { } } -class StaticallyNamedLegacySolver extends BaseSolver { - static override solverName = "ReadableNestedSolver" - - override getSolverName() { - return "aby" - } -} - class FirstStageSolver extends BaseSolver {} class SecondStageSolver extends BaseSolver {} @@ -123,14 +115,6 @@ describe("getDisplayedStages", () => { ]) }) - test("prefers an active subsolver's static name", () => { - const activeSubSolver = new StaticallyNamedLegacySolver() - - expect(getDisplayedStages(new NestedSolver(activeSubSolver))[0]?.name).toBe( - "ReadableNestedSolver", - ) - }) - test("marks solved and failed subsolvers with the matching display status", () => { const solvedSubSolver = new LeafSolver("Solved") solvedSubSolver.solved = true