@tscircuit/runframe 0.0.2946 → 0.0.2948

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.
@@ -2865,7 +2865,7 @@ var usePostHogActivity = (properties) => {
2865
2865
  };
2866
2866
 
2867
2867
  // package.json
2868
- var version = "0.0.2945";
2868
+ var version = "0.0.2947";
2869
2869
  var package_default = {
2870
2870
  name: "@tscircuit/runframe",
2871
2871
  main: "dist/preview.js",
@@ -2993,7 +2993,7 @@ var package_default = {
2993
2993
  },
2994
2994
  dependencies: {
2995
2995
  "@tscircuit/core": "^0.0.1911",
2996
- "@tscircuit/eval": "0.0.1584",
2996
+ "@tscircuit/eval": "0.0.1585",
2997
2997
  "@tscircuit/solver-utils": "^0.0.16",
2998
2998
  debug: "^4.4.0",
2999
2999
  "format-si-unit": "^0.0.12"
package/dist/preview.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  useOrderDialogCli,
11
11
  usePcbViewerController,
12
12
  useSchematicViewerController
13
- } from "./chunk-3W76R6HC.js";
13
+ } from "./chunk-T3FFIUMM.js";
14
14
  export {
15
15
  BomTable,
16
16
  CadViewer,
package/dist/runner.js CHANGED
@@ -43,14 +43,14 @@ import {
43
43
  useRunnerStore,
44
44
  useSchematicViewerController,
45
45
  useStyles
46
- } from "./chunk-3W76R6HC.js";
46
+ } from "./chunk-T3FFIUMM.js";
47
47
 
48
48
  // lib/components/RunFrame/RunFrame.tsx
49
49
  import { createCircuitWebWorker } from "@tscircuit/eval/worker";
50
50
  import Debug from "debug";
51
51
  import { HTTPError } from "ky";
52
52
  import { Loader2, Play, Square } from "lucide-react";
53
- import { useEffect, useReducer, useRef as useRef2, useState } from "react";
53
+ import { useEffect, useMemo as useMemo2, useReducer, useRef as useRef2, useState } from "react";
54
54
  import { ErrorBoundary } from "react-error-boundary";
55
55
 
56
56
  // lib/components/RunFrame/RunFrameErrorFallback.tsx
@@ -342,6 +342,8 @@ var RunFrame = (props) => {
342
342
  const [error, setError] = useState(null);
343
343
  const [componentStack, setComponentStack] = useState(null);
344
344
  const cancelExecutionRef = useRef2(null);
345
+ const componentActiveRef = useRef2(true);
346
+ const workerCreationRef = useRef2(null);
345
347
  const [autoroutingGraphics, setAutoroutingGraphics] = useState(null);
346
348
  const [runCountTrigger, incRunCountTrigger] = useReducer(
347
349
  (acc, s) => acc + 1,
@@ -356,6 +358,58 @@ var RunFrame = (props) => {
356
358
  const [currentDebugOption, setCurrentDebugOption] = useState("");
357
359
  const [showSchematicDebugGrid, setShowSchematicDebugGrid] = useState(false);
358
360
  const [showSchematicPorts, setShowSchematicPorts] = useState(false);
361
+ useEffect(() => {
362
+ componentActiveRef.current = true;
363
+ return () => {
364
+ componentActiveRef.current = false;
365
+ cancelExecutionRef.current?.();
366
+ const pending = workerCreationRef.current;
367
+ void pending?.then(({ worker }) => {
368
+ if (!componentActiveRef.current && globalThis.runFrameWorker !== worker) {
369
+ return worker.kill();
370
+ }
371
+ }).catch(() => {
372
+ });
373
+ };
374
+ }, []);
375
+ const getWorker = () => {
376
+ if (workerCreationRef.current) return workerCreationRef.current;
377
+ const pending = (async () => {
378
+ const evalVersion = await resolveEvalVersion(
379
+ props.evalVersion,
380
+ !globalThis.runFrameWorker && props.forceLatestEvalVersion
381
+ );
382
+ const worker = globalThis.runFrameWorker ?? await createCircuitWebWorker({
383
+ evalVersion,
384
+ webWorkerBlobUrl: props.evalWebWorkerBlobUrl,
385
+ projectConfig: getRunFrameProjectConfig({
386
+ projectBaseUrl: props.projectBaseUrl || `${API_BASE}/files/static`
387
+ }),
388
+ ...props.platformConfig && { platform: props.platformConfig },
389
+ verbose: true,
390
+ ...props.enableFetchProxy && {
391
+ enableFetchProxy: props.enableFetchProxy
392
+ },
393
+ ...window.TSCIRCUIT_USE_RUNFRAME_FOR_CLI && {
394
+ disableCdnLoading: true
395
+ },
396
+ ...props.tscircuitSessionToken && {
397
+ tscircuitSessionToken: props.tscircuitSessionToken
398
+ },
399
+ ...props.easyEdaProxyConfig && {
400
+ easyEdaProxyConfig: props.easyEdaProxyConfig
401
+ }
402
+ });
403
+ return { worker, evalVersion };
404
+ })();
405
+ workerCreationRef.current = pending;
406
+ const clearPending = () => {
407
+ if (workerCreationRef.current === pending)
408
+ workerCreationRef.current = null;
409
+ };
410
+ void pending.then(clearPending, clearPending);
411
+ return pending;
412
+ };
359
413
  const activeEffectName = Object.values(activeAsyncEffects).sort(
360
414
  (a, b) => a.startTime - b.startTime
361
415
  )[0]?.effectName;
@@ -378,40 +432,16 @@ var RunFrame = (props) => {
378
432
  const load = async () => {
379
433
  try {
380
434
  if (!globalThis.runFrameWorker) {
381
- const evalVersion = await resolveEvalVersion(
382
- props.evalVersion,
383
- props.forceLatestEvalVersion
384
- );
385
- const worker = await createCircuitWebWorker({
386
- evalVersion,
387
- webWorkerBlobUrl: props.evalWebWorkerBlobUrl,
388
- projectConfig: getRunFrameProjectConfig({
389
- projectBaseUrl: props.projectBaseUrl || `${API_BASE}/files/static`
390
- }),
391
- ...props.platformConfig && {
392
- platform: props.platformConfig
393
- },
394
- verbose: true,
395
- ...props.enableFetchProxy && {
396
- enableFetchProxy: props.enableFetchProxy
397
- },
398
- ...window.TSCIRCUIT_USE_RUNFRAME_FOR_CLI && {
399
- disableCdnLoading: true
400
- },
401
- ...props.tscircuitSessionToken && {
402
- tscircuitSessionToken: props.tscircuitSessionToken
403
- },
404
- ...props.easyEdaProxyConfig && {
405
- easyEdaProxyConfig: props.easyEdaProxyConfig
406
- }
407
- });
408
- if (cancelled) return;
435
+ const { worker, evalVersion } = await getWorker();
436
+ if (cancelled || !componentActiveRef.current) return;
409
437
  globalThis.runFrameWorker = worker;
410
438
  setLastRunEvalVersion(evalVersion);
411
439
  }
412
440
  if (!cancelled) setDependenciesLoaded(true);
413
441
  } catch (err) {
414
- console.error("Failed to preload eval worker", err);
442
+ if (!cancelled && componentActiveRef.current) {
443
+ console.error("Failed to preload eval worker", err);
444
+ }
415
445
  }
416
446
  };
417
447
  load();
@@ -441,9 +471,9 @@ var RunFrame = (props) => {
441
471
  useEffect(() => {
442
472
  if (props.debug) Debug.enable("run-frame*");
443
473
  }, [props.debug]);
444
- const fsMap = props.fsMap instanceof Map ? props.fsMap : Object.entries(props.fsMap ?? {}).reduce(
445
- (m, [k, v]) => m.set(k, v),
446
- /* @__PURE__ */ new Map()
474
+ const fsMap = useMemo2(
475
+ () => props.fsMap instanceof Map ? props.fsMap : new Map(Object.entries(props.fsMap ?? {})),
476
+ [props.fsMap]
447
477
  );
448
478
  const lastFsMapRef = useRef2(null);
449
479
  const lastEntrypointRef = useRef2(null);
@@ -465,10 +495,18 @@ var RunFrame = (props) => {
465
495
  }
466
496
  }, [isStaticCircuitJson, circuitJsonFileParsedContent, circuitJsonFileError]);
467
497
  useEffect(() => {
468
- if (props.isLoadingFiles) return;
469
- if (isStaticCircuitJson) return;
498
+ if (isStaticCircuitJson || props.isLoadingFiles) {
499
+ autoroutingCaptureGeneration.current += 1;
500
+ cancelExecutionRef.current?.();
501
+ cancelExecutionRef.current = null;
502
+ setIsRunning(false);
503
+ setActiveAsyncEffects({});
504
+ if (props.isLoadingFiles) setCircuitJson(null);
505
+ return;
506
+ }
470
507
  const fsMapObj = fsMap instanceof Map ? Object.fromEntries(fsMap.entries()) : fsMap;
471
508
  if (!fsMapObj || Object.keys(fsMapObj).length === 0) {
509
+ setCircuitJson(null);
472
510
  setError({
473
511
  error: "No files provided. Please provide at least one file with code to execute.",
474
512
  stack: ""
@@ -495,50 +533,29 @@ var RunFrame = (props) => {
495
533
  lastEntrypointRef.current = props.entrypoint ?? null;
496
534
  lastRunCountTriggerRef.current = runCountTrigger;
497
535
  setIsRunning(true);
536
+ let cancelled = false;
537
+ const captureGeneration = ++autoroutingCaptureGeneration.current;
538
+ const isCurrentRun = () => componentActiveRef.current && !cancelled && captureGeneration === autoroutingCaptureGeneration.current;
539
+ let renderResult;
498
540
  const runWorker = async () => {
541
+ if (!isCurrentRun()) return;
499
542
  debug("running render worker");
500
543
  setError(null);
501
544
  setRenderLog(null);
502
545
  setActiveAsyncEffects({});
503
546
  setSolverEvents([]);
504
547
  setAutoroutingPhases([]);
505
- const captureGeneration = ++autoroutingCaptureGeneration.current;
506
548
  const renderLog2 = { progress: 0, debugOutputs: [] };
507
- let cancelled = false;
508
549
  cancelExecutionRef.current = () => {
509
550
  cancelled = true;
510
551
  };
511
- const resolvedEvalVersion = await resolveEvalVersion(
512
- props.evalVersion,
513
- !globalThis.runFrameWorker && props.forceLatestEvalVersion
514
- );
552
+ const { worker, evalVersion: resolvedEvalVersion } = await getWorker();
553
+ if (!isCurrentRun()) return;
515
554
  debug("resolvedEvalVersion", resolvedEvalVersion);
516
- const worker = globalThis.runFrameWorker ?? await createCircuitWebWorker({
517
- evalVersion: resolvedEvalVersion,
518
- webWorkerBlobUrl: props.evalWebWorkerBlobUrl,
519
- verbose: true,
520
- projectConfig: getRunFrameProjectConfig({
521
- projectBaseUrl: props.projectBaseUrl || `${API_BASE}/files/static`
522
- }),
523
- ...props.platformConfig && {
524
- platform: props.platformConfig
525
- },
526
- ...props.enableFetchProxy && {
527
- enableFetchProxy: props.enableFetchProxy
528
- },
529
- ...window.TSCIRCUIT_USE_RUNFRAME_FOR_CLI && {
530
- disableCdnLoading: true
531
- },
532
- ...props.tscircuitSessionToken && {
533
- tscircuitSessionToken: props.tscircuitSessionToken
534
- },
535
- ...props.easyEdaProxyConfig && {
536
- easyEdaProxyConfig: props.easyEdaProxyConfig
537
- }
538
- });
539
555
  globalThis.runFrameWorker = worker;
540
556
  setLastRunEvalVersion(resolvedEvalVersion);
541
557
  await worker.clearEventListeners();
558
+ if (!isCurrentRun()) return;
542
559
  if (currentDebugOption?.trim()) {
543
560
  worker.enableDebug(currentDebugOption.replace("DEBUG=", ""));
544
561
  }
@@ -638,48 +655,32 @@ var RunFrame = (props) => {
638
655
  }
639
656
  });
640
657
  debug("Executing fsMap...");
641
- const evalResult = await worker.executeWithFsMap({
658
+ await worker.executeWithFsMap({
642
659
  entrypoint: props.entrypoint,
643
660
  fsMap: fsMapObj2,
644
661
  ...props.mainComponentPath ? { mainComponentPath: props.mainComponentPath } : {}
645
- }).then(() => {
646
- return { success: true };
647
- }).catch((e) => {
648
- const message = e.message.replace("Error: ", "");
649
- debug(`eval error: ${message}`);
650
- props.onError?.(e);
651
- emitRunCompleted(buildRunCompletedPayload({ executionError: e }));
652
- setError({ error: message, stack: e.stack });
653
- setRenderLog(null);
654
- console.error(e);
655
- return { success: false };
656
662
  });
663
+ if (!isCurrentRun()) return;
657
664
  debug("worker call started");
658
- if (!evalResult.success) {
659
- setIsRunning(false);
660
- setActiveAsyncEffects({});
661
- return;
662
- }
663
- const $renderResult = worker.renderUntilSettled();
665
+ const settlement = worker.renderUntilSettled().then(
666
+ () => ({ success: true }),
667
+ (error2) => ({ success: false, error: error2 })
668
+ );
669
+ renderResult = settlement;
664
670
  debug("waiting for initial circuit json...");
665
- let circuitJson2 = await worker.getCircuitJson().catch((e) => {
666
- debug("error getting initial circuit json", e);
667
- props.onError?.(e);
668
- emitRunCompleted(buildRunCompletedPayload({ executionError: e }));
669
- setError({ error: e.message, stack: e.stack });
670
- setRenderLog(null);
671
- setIsRunning(false);
672
- setActiveAsyncEffects({});
673
- return null;
674
- });
675
- if (!circuitJson2) return;
671
+ let circuitJson2 = await worker.getCircuitJson();
672
+ if (!isCurrentRun()) return;
673
+ if (!circuitJson2) throw new Error("Eval worker returned no Circuit JSON");
676
674
  debug("got initial circuit json");
677
675
  setCircuitJson(circuitJson2);
678
676
  props.onCircuitJsonChange?.(circuitJson2);
679
677
  props.onInitialRender?.({ circuitJson: circuitJson2 });
680
- await $renderResult;
678
+ const settled = await settlement;
679
+ if (!isCurrentRun()) return;
680
+ if (!settled.success) throw settled.error;
681
681
  debug("getting final circuit json");
682
682
  circuitJson2 = await worker.getCircuitJson();
683
+ if (!isCurrentRun()) return;
683
684
  props.onCircuitJsonChange?.(circuitJson2);
684
685
  setCircuitJson(circuitJson2);
685
686
  emitRunCompleted(buildRunCompletedPayload({ circuitJson: circuitJson2 }));
@@ -699,7 +700,29 @@ var RunFrame = (props) => {
699
700
  setCurrentDebugOption("");
700
701
  cancelExecutionRef.current = null;
701
702
  };
702
- runMutex.runWithMutex(runWorker);
703
+ void runMutex.runWithMutex(async () => {
704
+ try {
705
+ await runWorker();
706
+ } finally {
707
+ await renderResult;
708
+ }
709
+ }).catch((failure) => {
710
+ if (!isCurrentRun()) return;
711
+ const error2 = failure instanceof Error ? failure : new Error(String(failure));
712
+ debug("worker execution failed", error2);
713
+ props.onError?.(error2);
714
+ emitRunCompleted(buildRunCompletedPayload({ executionError: error2 }));
715
+ setCircuitJson(null);
716
+ setError({
717
+ error: error2.message.replace("Error: ", ""),
718
+ stack: error2.stack
719
+ });
720
+ setRenderLog(null);
721
+ setIsRunning(false);
722
+ setActiveAsyncEffects({});
723
+ setCurrentDebugOption("");
724
+ cancelExecutionRef.current = null;
725
+ });
703
726
  }, [
704
727
  props.fsMap,
705
728
  props.entrypoint,
@@ -803,7 +826,7 @@ var RunFrame = (props) => {
803
826
  children: /* @__PURE__ */ jsx3(
804
827
  CircuitJsonPreview,
805
828
  {
806
- code: fsMap.get(props.entrypoint ?? props.mainComponentPath),
829
+ code: fsMap.get(props.entrypoint ?? props.mainComponentPath ?? ""),
807
830
  fsMap,
808
831
  defaultActiveTab: props.defaultActiveTab ?? props.defaultTab,
809
832
  defaultTab: props.defaultTab,
@@ -1050,7 +1073,7 @@ var useSyncPageTitle = () => {
1050
1073
  };
1051
1074
 
1052
1075
  // lib/components/RunFrameWithApi/RunFrameWithApi.tsx
1053
- import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo4, useState as useState6 } from "react";
1076
+ import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo5, useState as useState6 } from "react";
1054
1077
 
1055
1078
  // lib/utils/get-board-files-from-config.ts
1056
1079
  import { projectConfig } from "@tscircuit/props";
@@ -1094,7 +1117,7 @@ import {
1094
1117
  Folder,
1095
1118
  Save
1096
1119
  } from "lucide-react";
1097
- import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
1120
+ import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
1098
1121
 
1099
1122
  // lib/components/ui/command.tsx
1100
1123
  import * as React from "react";
@@ -1536,7 +1559,7 @@ var EnhancedFileSelectorCombobox = ({
1536
1559
  }
1537
1560
  }
1538
1561
  }, [currentFiles, currentFile]);
1539
- const recentFiles = useMemo3(() => {
1562
+ const recentFiles = useMemo4(() => {
1540
1563
  const validSavedFiles = recentlySavedFiles.filter(
1541
1564
  (file2) => filteredFiles.includes(file2)
1542
1565
  );
@@ -1853,12 +1876,12 @@ var RunFrameWithApi = (props) => {
1853
1876
  const hasReceivedInitialFiles = useHasReceivedInitialFilesLoaded();
1854
1877
  const fsMap = useRunFrameStore((s) => s.fsMap);
1855
1878
  const recentlySavedFiles = useRunFrameStore((s) => s.recentlySavedFiles);
1856
- const allFiles = useMemo4(() => Array.from(fsMap.keys()), [fsMap]);
1857
- const projectConfigContent = useMemo4(() => {
1879
+ const allFiles = useMemo5(() => Array.from(fsMap.keys()), [fsMap]);
1880
+ const projectConfigContent = useMemo5(() => {
1858
1881
  const rawConfig = fsMap.get("tscircuit.config.json");
1859
1882
  return typeof rawConfig === "string" ? rawConfig : void 0;
1860
1883
  }, [fsMap]);
1861
- const boardFiles = useMemo4(
1884
+ const boardFiles = useMemo5(
1862
1885
  () => getBoardFilesFromConfig(allFiles, projectConfigContent),
1863
1886
  [allFiles, projectConfigContent]
1864
1887
  );
@@ -1883,7 +1906,7 @@ var RunFrameWithApi = (props) => {
1883
1906
  [setFavorites]
1884
1907
  );
1885
1908
  const activeFileFilter = props.fileFilter ?? DEFAULT_UI_FILE_FILTER;
1886
- const visibleBoardFiles = useMemo4(
1909
+ const visibleBoardFiles = useMemo5(
1887
1910
  () => boardFiles.filter(activeFileFilter),
1888
1911
  [boardFiles, activeFileFilter]
1889
1912
  );
@@ -1959,7 +1982,7 @@ var RunFrameWithApi = (props) => {
1959
1982
  startPolling();
1960
1983
  return () => stopPolling();
1961
1984
  }, [startPolling, stopPolling]);
1962
- const componentProp = useMemo4(
1985
+ const componentProp = useMemo5(
1963
1986
  () => String(componentPath).length > 0 ? {
1964
1987
  mainComponentPath: componentPath
1965
1988
  } : {},
@@ -2068,7 +2091,7 @@ var RunFrameWithApi = (props) => {
2068
2091
  };
2069
2092
 
2070
2093
  // lib/components/RunFrameForCli/RunFrameForCli.tsx
2071
- import { useCallback as useCallback6, useMemo as useMemo5, useState as useState8 } from "react";
2094
+ import { useCallback as useCallback6, useMemo as useMemo6, useState as useState8 } from "react";
2072
2095
 
2073
2096
  // lib/components/RunFrameForCli/LoginDialog.tsx
2074
2097
  import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef4, useState as useState7 } from "react";
@@ -2295,7 +2318,7 @@ var RunFrameForCli = (props) => {
2295
2318
  );
2296
2319
  const evalWebWorkerBlobUrl = props.evalWebWorkerBlobUrl ?? props.workerBlobUrl;
2297
2320
  const cacheApiBaseUrl = props.apiBaseUrl ?? API_BASE;
2298
- const platformConfig = useMemo5(
2321
+ const platformConfig = useMemo6(
2299
2322
  () => ({
2300
2323
  ...props.platformConfig,
2301
2324
  localCacheEngine: props.platformConfig?.localCacheEngine ?? createCliLocalCacheEngine({ apiBaseUrl: cacheApiBaseUrl })