@ttsc/playground 0.30.4 → 0.31.1

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.
Files changed (180) hide show
  1. package/README.md +6 -4
  2. package/lib/src/compiler/buildTsconfigJSON.d.ts +5 -0
  3. package/lib/src/compiler/buildTsconfigJSON.js +5 -0
  4. package/lib/src/compiler/buildTsconfigJSON.js.map +1 -1
  5. package/lib/src/compiler/createTypiaSourcePackMount.d.ts +9 -3
  6. package/lib/src/compiler/createTypiaSourcePackMount.js +9 -3
  7. package/lib/src/compiler/createTypiaSourcePackMount.js.map +1 -1
  8. package/lib/src/compiler/createWorkerCompiler.d.ts +5 -0
  9. package/lib/src/compiler/createWorkerCompiler.js +5 -0
  10. package/lib/src/compiler/createWorkerCompiler.js.map +1 -1
  11. package/lib/src/compiler/installDependenciesIntoMemFS.d.ts +5 -0
  12. package/lib/src/compiler/installDependenciesIntoMemFS.js +5 -0
  13. package/lib/src/compiler/installDependenciesIntoMemFS.js.map +1 -1
  14. package/lib/src/compiler/installTypiaSourcePack.d.ts +7 -2
  15. package/lib/src/compiler/installTypiaSourcePack.js +7 -2
  16. package/lib/src/compiler/installTypiaSourcePack.js.map +1 -1
  17. package/lib/src/compiler/internal/createWorkerCompilerService.d.ts +31 -0
  18. package/lib/src/compiler/internal/createWorkerCompilerService.js +22 -4
  19. package/lib/src/compiler/internal/createWorkerCompilerService.js.map +1 -1
  20. package/lib/src/compiler/internal/joinUnder.d.ts +5 -0
  21. package/lib/src/compiler/internal/joinUnder.js +5 -0
  22. package/lib/src/compiler/internal/joinUnder.js.map +1 -1
  23. package/lib/src/compiler/internal/parseLintDiagnostics.d.ts +5 -0
  24. package/lib/src/compiler/internal/parseLintDiagnostics.js +8 -3
  25. package/lib/src/compiler/internal/parseLintDiagnostics.js.map +1 -1
  26. package/lib/src/compiler/internal/safeParseTypiaTransform.d.ts +14 -1
  27. package/lib/src/compiler/internal/safeParseTypiaTransform.js +12 -1
  28. package/lib/src/compiler/internal/safeParseTypiaTransform.js.map +1 -1
  29. package/lib/src/compiler/lineColumnOf.d.ts +9 -1
  30. package/lib/src/compiler/lineColumnOf.js +23 -7
  31. package/lib/src/compiler/lineColumnOf.js.map +1 -1
  32. package/lib/src/compiler/loadTypiaSourcePack.d.ts +23 -1
  33. package/lib/src/compiler/loadTypiaSourcePack.js +48 -12
  34. package/lib/src/compiler/loadTypiaSourcePack.js.map +1 -1
  35. package/lib/src/compiler/mapDiagnostic.d.ts +7 -2
  36. package/lib/src/compiler/mapDiagnostic.js +53 -6
  37. package/lib/src/compiler/mapDiagnostic.js.map +1 -1
  38. package/lib/src/compiler/normalizeError.d.ts +9 -1
  39. package/lib/src/compiler/normalizeError.js +9 -1
  40. package/lib/src/compiler/normalizeError.js.map +1 -1
  41. package/lib/src/compiler/normalizeNodeModulePath.d.ts +5 -0
  42. package/lib/src/compiler/normalizeNodeModulePath.js +5 -0
  43. package/lib/src/compiler/normalizeNodeModulePath.js.map +1 -1
  44. package/lib/src/compiler/pickEmittedJS.d.ts +9 -2
  45. package/lib/src/compiler/pickEmittedJS.js +23 -6
  46. package/lib/src/compiler/pickEmittedJS.js.map +1 -1
  47. package/lib/src/npm/collectExternalPackageNames.d.ts +17 -0
  48. package/lib/src/npm/collectExternalPackageNames.js +51 -11
  49. package/lib/src/npm/collectExternalPackageNames.js.map +1 -1
  50. package/lib/src/npm/installPlaygroundDependencies.d.ts +10 -2
  51. package/lib/src/npm/installPlaygroundDependencies.js +18 -4
  52. package/lib/src/npm/installPlaygroundDependencies.js.map +1 -1
  53. package/lib/src/npm/internal/npmRegistry.d.ts +149 -2
  54. package/lib/src/npm/internal/npmRegistry.js +95 -4
  55. package/lib/src/npm/internal/npmRegistry.js.map +1 -1
  56. package/lib/src/npm/packageNameFromSpecifier.d.ts +5 -0
  57. package/lib/src/npm/packageNameFromSpecifier.js +7 -0
  58. package/lib/src/npm/packageNameFromSpecifier.js.map +1 -1
  59. package/lib/src/react/ConsoleViewer.d.ts +9 -0
  60. package/lib/src/react/ConsoleViewer.js +11 -2
  61. package/lib/src/react/ConsoleViewer.js.map +1 -1
  62. package/lib/src/react/DependencyProgressModal.d.ts +9 -0
  63. package/lib/src/react/DependencyProgressModal.js +9 -0
  64. package/lib/src/react/DependencyProgressModal.js.map +1 -1
  65. package/lib/src/react/DiagnosticsPanel.d.ts +9 -0
  66. package/lib/src/react/DiagnosticsPanel.js +9 -0
  67. package/lib/src/react/DiagnosticsPanel.js.map +1 -1
  68. package/lib/src/react/ExamplePicker.d.ts +9 -0
  69. package/lib/src/react/ExamplePicker.js +14 -2
  70. package/lib/src/react/ExamplePicker.js.map +1 -1
  71. package/lib/src/react/LintPane.d.ts +5 -0
  72. package/lib/src/react/LintPane.js +5 -0
  73. package/lib/src/react/LintPane.js.map +1 -1
  74. package/lib/src/react/OptionsPanel.d.ts +10 -0
  75. package/lib/src/react/OptionsPanel.js +19 -4
  76. package/lib/src/react/OptionsPanel.js.map +1 -1
  77. package/lib/src/react/PlaygroundShell.d.ts +13 -0
  78. package/lib/src/react/PlaygroundShell.js +17 -5
  79. package/lib/src/react/PlaygroundShell.js.map +1 -1
  80. package/lib/src/react/ResultViewer.d.ts +5 -0
  81. package/lib/src/react/ResultViewer.js +27 -7
  82. package/lib/src/react/ResultViewer.js.map +1 -1
  83. package/lib/src/react/SourceEditor.d.ts +10 -0
  84. package/lib/src/react/SourceEditor.js +10 -0
  85. package/lib/src/react/SourceEditor.js.map +1 -1
  86. package/lib/src/react/createCompilerClient.d.ts +5 -0
  87. package/lib/src/react/createCompilerClient.js +5 -0
  88. package/lib/src/react/createCompilerClient.js.map +1 -1
  89. package/lib/src/react/internal/PlaygroundCompilerLifecycle.d.ts +64 -1
  90. package/lib/src/react/internal/PlaygroundCompilerLifecycle.js +48 -0
  91. package/lib/src/react/internal/PlaygroundCompilerLifecycle.js.map +1 -1
  92. package/lib/src/react/internal/PlaygroundExecutionLifecycle.d.ts +42 -2
  93. package/lib/src/react/internal/PlaygroundExecutionLifecycle.js +18 -0
  94. package/lib/src/react/internal/PlaygroundExecutionLifecycle.js.map +1 -1
  95. package/lib/src/react/internal/recoverTerminalCompilerWorker.d.ts +46 -4
  96. package/lib/src/react/internal/recoverTerminalCompilerWorker.js +14 -1
  97. package/lib/src/react/internal/recoverTerminalCompilerWorker.js.map +1 -1
  98. package/lib/src/sandbox/createSandboxRequire.d.ts +12 -3
  99. package/lib/src/sandbox/createSandboxRequire.js +26 -12
  100. package/lib/src/sandbox/createSandboxRequire.js.map +1 -1
  101. package/lib/src/sandbox/loadTypiaRuntimePack.d.ts +22 -0
  102. package/lib/src/sandbox/loadTypiaRuntimePack.js +30 -1
  103. package/lib/src/sandbox/loadTypiaRuntimePack.js.map +1 -1
  104. package/lib/src/structures/IBuildTsconfigOptions.d.ts +9 -1
  105. package/lib/src/structures/ICompilerService.d.ts +112 -2
  106. package/lib/src/structures/IConsoleMessage.d.ts +5 -0
  107. package/lib/src/structures/ICreateCompilerClientOptions.d.ts +9 -2
  108. package/lib/src/structures/ICreateWorkerCompilerOptions.d.ts +11 -3
  109. package/lib/src/structures/IInstallTypiaSourcePackOptions.d.ts +7 -1
  110. package/lib/src/structures/ILintPluginConfig.d.ts +8 -1
  111. package/lib/src/structures/ILoadTypiaRuntimePackOptions.d.ts +8 -1
  112. package/lib/src/structures/IOptionToggle.d.ts +5 -0
  113. package/lib/src/structures/IPlaygroundDependencyInstallOptions.d.ts +41 -6
  114. package/lib/src/structures/IPlaygroundDependencyInstallResult.d.ts +8 -1
  115. package/lib/src/structures/IPlaygroundDependencyPackage.d.ts +9 -1
  116. package/lib/src/structures/IPlaygroundDependencyProgress.d.ts +9 -1
  117. package/lib/src/structures/IPlaygroundDependencyProgressPhase.d.ts +7 -2
  118. package/lib/src/structures/IPlaygroundDependencyRequest.d.ts +8 -1
  119. package/lib/src/structures/IPlaygroundExample.d.ts +5 -0
  120. package/lib/src/structures/IPlaygroundInstalledDependency.d.ts +8 -1
  121. package/lib/src/structures/IPlaygroundShellProps.d.ts +53 -19
  122. package/lib/src/structures/ISourceEditorProps.d.ts +16 -1
  123. package/lib/src/structures/ITransformOptions.d.ts +5 -0
  124. package/lib/src/structures/ITypiaPluginConfig.d.ts +25 -7
  125. package/package.json +6 -4
  126. package/src/compiler/buildTsconfigJSON.ts +5 -0
  127. package/src/compiler/createTypiaSourcePackMount.ts +9 -3
  128. package/src/compiler/createWorkerCompiler.ts +5 -0
  129. package/src/compiler/installDependenciesIntoMemFS.ts +5 -0
  130. package/src/compiler/installTypiaSourcePack.ts +7 -2
  131. package/src/compiler/internal/createWorkerCompilerService.ts +47 -4
  132. package/src/compiler/internal/joinUnder.ts +5 -0
  133. package/src/compiler/internal/parseLintDiagnostics.ts +8 -3
  134. package/src/compiler/internal/safeParseTypiaTransform.ts +25 -2
  135. package/src/compiler/lineColumnOf.ts +24 -8
  136. package/src/compiler/loadTypiaSourcePack.ts +57 -14
  137. package/src/compiler/mapDiagnostic.ts +67 -7
  138. package/src/compiler/normalizeError.ts +9 -1
  139. package/src/compiler/normalizeNodeModulePath.ts +5 -0
  140. package/src/compiler/pickEmittedJS.ts +22 -5
  141. package/src/npm/collectExternalPackageNames.ts +54 -13
  142. package/src/npm/installPlaygroundDependencies.ts +18 -6
  143. package/src/npm/internal/npmRegistry.ts +156 -4
  144. package/src/npm/packageNameFromSpecifier.ts +6 -0
  145. package/src/react/ConsoleViewer.tsx +10 -1
  146. package/src/react/DependencyProgressModal.tsx +9 -0
  147. package/src/react/DiagnosticsPanel.tsx +9 -0
  148. package/src/react/ExamplePicker.tsx +22 -6
  149. package/src/react/LintPane.tsx +5 -0
  150. package/src/react/OptionsPanel.tsx +22 -4
  151. package/src/react/PlaygroundShell.tsx +17 -5
  152. package/src/react/ResultViewer.tsx +28 -7
  153. package/src/react/SourceEditor.tsx +10 -0
  154. package/src/react/createCompilerClient.ts +5 -0
  155. package/src/react/internal/PlaygroundCompilerLifecycle.ts +64 -1
  156. package/src/react/internal/PlaygroundExecutionLifecycle.ts +44 -2
  157. package/src/react/internal/recoverTerminalCompilerWorker.ts +48 -4
  158. package/src/sandbox/createSandboxRequire.ts +28 -15
  159. package/src/sandbox/loadTypiaRuntimePack.ts +35 -2
  160. package/src/structures/IBuildTsconfigOptions.ts +13 -1
  161. package/src/structures/ICompilerService.ts +119 -2
  162. package/src/structures/IConsoleMessage.ts +5 -0
  163. package/src/structures/ICreateCompilerClientOptions.ts +9 -2
  164. package/src/structures/ICreateWorkerCompilerOptions.ts +15 -3
  165. package/src/structures/IInstallTypiaSourcePackOptions.ts +11 -1
  166. package/src/structures/ILintPluginConfig.ts +8 -1
  167. package/src/structures/ILoadTypiaRuntimePackOptions.ts +8 -1
  168. package/src/structures/IOptionToggle.ts +5 -0
  169. package/src/structures/IPlaygroundDependencyInstallOptions.ts +56 -6
  170. package/src/structures/IPlaygroundDependencyInstallResult.ts +12 -1
  171. package/src/structures/IPlaygroundDependencyPackage.ts +11 -1
  172. package/src/structures/IPlaygroundDependencyProgress.ts +9 -1
  173. package/src/structures/IPlaygroundDependencyProgressPhase.ts +7 -2
  174. package/src/structures/IPlaygroundDependencyRequest.ts +10 -1
  175. package/src/structures/IPlaygroundExample.ts +6 -0
  176. package/src/structures/IPlaygroundInstalledDependency.ts +11 -1
  177. package/src/structures/IPlaygroundShellProps.ts +62 -22
  178. package/src/structures/ISourceEditorProps.ts +20 -1
  179. package/src/structures/ITransformOptions.ts +7 -0
  180. package/src/structures/ITypiaPluginConfig.ts +31 -7
@@ -7,10 +7,20 @@ import type { IPlaygroundExample } from "../structures/IPlaygroundExample";
7
7
  interface ExamplePickerProps {
8
8
  examples: readonly IPlaygroundExample[];
9
9
  onPick: (id: string) => void;
10
+
10
11
  /** Display labels for groups. Maps `group` key → rendered heading. */
11
12
  groupLabels?: Record<string, string>;
12
13
  }
13
14
 
15
+ /**
16
+ * Group site examples and publish the selected id. Outside clicks and Escape
17
+ * close the menu; DOM listeners exist only while it is open.
18
+ *
19
+ * @evidence contracts/common.md#principled-implementation Group keys index a prototype-free record and label overrides require own properties, so arbitrary site labels remain ordinary groups; picking preserves each example's stable id.
20
+ * @evidence contracts/common.md#clear-and-simple-design Grouping is memoized from examples while local state owns only menu visibility; the parent owns source changes.
21
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Site-provided examples and group labels drive presentation without reserved consumer names or foreign DOM mutation.
22
+ * @evidence contracts/common.md#meaningful-documentation Native prose states id ownership and listener lifetime with tag separation under the documentation skill.
23
+ */
14
24
  export function ExamplePicker({
15
25
  examples,
16
26
  onPick,
@@ -38,11 +48,14 @@ export function ExamplePicker({
38
48
  }, [open]);
39
49
 
40
50
  const grouped = useMemo(() => {
41
- return examples.reduce<Record<string, IPlaygroundExample[]>>((acc, e) => {
42
- const key = e.group ?? "Examples";
43
- (acc[key] ??= []).push(e);
44
- return acc;
45
- }, {});
51
+ return examples.reduce<Record<string, IPlaygroundExample[]>>(
52
+ (acc, e) => {
53
+ const key = e.group ?? "Examples";
54
+ (acc[key] ??= []).push(e);
55
+ return acc;
56
+ },
57
+ Object.create(null) as Record<string, IPlaygroundExample[]>,
58
+ );
46
59
  }, [examples]);
47
60
 
48
61
  if (examples.length === 0) return null;
@@ -65,7 +78,10 @@ export function ExamplePicker({
65
78
  className="border-b border-[#d8e7f4] last:border-b-0"
66
79
  >
67
80
  <div className="bg-[#f7fbff] px-3 py-1.5 font-mono text-[10px] uppercase tracking-wider text-slate-500">
68
- {groupLabels?.[group] ?? group}
81
+ {groupLabels &&
82
+ Object.prototype.hasOwnProperty.call(groupLabels, group)
83
+ ? (groupLabels[group] ?? group)
84
+ : group}
69
85
  </div>
70
86
  {items.map((item) => (
71
87
  <button
@@ -5,6 +5,11 @@ import type { ICompilerService } from "../structures/ICompilerService";
5
5
  /**
6
6
  * Renders the lint plugin's findings in a list view. Shown by `PlaygroundShell`
7
7
  * when the active tab is "lint". Empty state is the green checkmark.
8
+ *
9
+ * @evidence contracts/common.md#principled-implementation Supplied normalized findings map to severity, code, location and message; producer failure interpretation remains outside the renderer.
10
+ * @evidence contracts/common.md#clear-and-simple-design This view is stateless apart from its props and does not initiate lint work.
11
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Empty-state presentation follows the supplied result and does not discard error diagnostics.
12
+ * @evidence contracts/common.md#meaningful-documentation Native prose explains placement and empty presentation, separated from tags under the documentation skill.
8
13
  */
9
14
  export function LintPane({
10
15
  diagnostics,
@@ -10,12 +10,24 @@ interface OptionsPanelProps {
10
10
  options: ITransformOptions;
11
11
  onChange: (next: ITransformOptions) => void;
12
12
  onClose: () => void;
13
+
13
14
  /** Defaults to the typia + lint pair from `DEFAULT_OPTION_TOGGLES`. */
14
15
  toggles?: readonly IOptionToggle[];
16
+
15
17
  /** Dialog heading. Defaults to "Transform Options". */
16
18
  title?: string;
17
19
  }
18
20
 
21
+ /**
22
+ * Render site-defined boolean toggles in a dialog, merging a changed flag into
23
+ * the controlled option record. Focus moves to the first control once when the
24
+ * dialog opens, Escape closes and Tab wraps within the dialog.
25
+ *
26
+ * @evidence contracts/common.md#principled-implementation Computed-key copying preserves unrelated options; focus traversal and Escape use DOM events with cleanup on callback replacement or unmount.
27
+ * @evidence contracts/common.md#clear-and-simple-design Parent callbacks own values and visibility while this component owns dialog focus behavior and toggle presentation.
28
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Metadata keys drive all toggles through the same supported callback rather than plugin-specific mutation paths.
29
+ * @evidence contracts/common.md#meaningful-documentation Native prose states merge and keyboard behavior; prop comments state UI defaults with documentation-skill tag and member separation.
30
+ */
19
31
  export function OptionsPanel({
20
32
  options,
21
33
  onChange,
@@ -28,7 +40,9 @@ export function OptionsPanel({
28
40
  const toggle = (key: string) =>
29
41
  onChange({ ...options, [key]: !options[key] });
30
42
 
31
- // Escape closes. Focus traps inside the dialog.
43
+ // Escape closes. Focus traps inside the dialog. The listener re-registers
44
+ // whenever the parent supplies a new `onClose`, which an inline callback does
45
+ // on every render, so it must not move focus.
32
46
  useEffect(() => {
33
47
  const onKey = (e: KeyboardEvent) => {
34
48
  if (e.key === "Escape") {
@@ -52,13 +66,17 @@ export function OptionsPanel({
52
66
  }
53
67
  };
54
68
  window.addEventListener("keydown", onKey);
55
- // Auto-focus the first focusable on mount.
69
+ return () => window.removeEventListener("keydown", onKey);
70
+ }, [onClose]);
71
+
72
+ // Auto-focus the first focusable once, when the dialog opens. Toggling an
73
+ // option re-renders the parent, and focus must stay on the toggled control.
74
+ useEffect(() => {
56
75
  const focusable = dialogRef.current?.querySelector<HTMLElement>(
57
76
  'button, input, [tabindex]:not([tabindex="-1"])',
58
77
  );
59
78
  focusable?.focus();
60
- return () => window.removeEventListener("keydown", onKey);
61
- }, [onClose]);
79
+ }, []);
62
80
 
63
81
  return (
64
82
  <div
@@ -43,6 +43,19 @@ const SHARE_URL_WARN_BYTES = 2000;
43
43
 
44
44
  type Tab = "javascript" | "lint";
45
45
 
46
+ /**
47
+ * Coordinate the compiler Worker, dependency graph, controlled editors and
48
+ * site execution hook. Compile epochs and Worker generations fence stale RPC
49
+ * results; Execute has its own cancellation boundary.
50
+ *
51
+ * Source-root removal replaces the Worker and all dependency maps together.
52
+ * Site execution owns isolation; cancellation cannot preempt synchronous code.
53
+ *
54
+ * @evidence contracts/common.md#principled-implementation Epoch and generation checks precede asynchronous state publication; serialized dependency mutations reconcile stale writes before reuse, and removed roots trigger full graph replacement. Execute receives a source snapshot and attempt signal without invalidating independent compile work.
55
+ * @evidence contracts/common.md#clear-and-simple-design The shell owns React state and delegates Worker transport, dependency solving, lifecycle fencing and display to their named operations; separate compile and Execute boundaries serve their different invalidation rules.
56
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Generation resets correct actual obsolete-worker state rather than wrapping errors with unbounded retry; ordinary plugin and transport errors stay visible and only terminal runtime failures require replacement.
57
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs define ownership, stale-result boundaries and isolation limits; local comments explain dependency replacement and callback dependencies under the documentation skill.
58
+ */
46
59
  export function PlaygroundShell({
47
60
  workerUrl,
48
61
  defaultScript,
@@ -103,11 +116,10 @@ export function PlaygroundShell({
103
116
  // Direct source roots selecting the mounted graph. Removing one requires a
104
117
  // full solve and worker replacement so obsolete package files cannot survive.
105
118
  const dependencyRoots = useRef<Set<string>>(new Set());
106
- // Accumulated runtime-file map produced by every successful
107
- // installPlaygroundDependencies call. Threaded through to executeBundle so
108
- // the in-page Execute sandbox's require can resolve any npm package the
109
- // user installed (without it, `import {v4} from "uuid"` compiles fine but
110
- // Execute throws because the worker mounts uuid into the wasm MemFS only).
119
+ // Runtime-file map for the current dependency graph. Additive installs merge
120
+ // files; removing a source root replaces the map after a full solve. Pass it
121
+ // to executeBundle so its require can resolve current npm dependencies.
122
+ // The compiler's MemFS mount alone does not make them available to Execute.
111
123
  const runtimeDependencyFiles = useRef<Record<string, string>>({});
112
124
  const sourceVersion = useRef(0);
113
125
  const latestSource = useRef(source);
@@ -11,13 +11,21 @@ interface ResultViewerProps {
11
11
  /**
12
12
  * Read-only Monaco pane used to render the compiled / transformed output with a
13
13
  * copy button. Wraps `<Editor readOnly>` and adds the toast UI.
14
+ *
15
+ * @evidence contracts/common.md#principled-implementation Controlled text and language select a read-only model; copy feedback follows actual clipboard settlement, and attempt identity prevents old or unmounted requests from publishing feedback.
16
+ * @evidence contracts/common.md#clear-and-simple-design Output viewing and local copy feedback stay separate from compilation and source editing.
17
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The read-only editor uses supported Monaco options rather than replacing editor methods.
18
+ * @evidence contracts/common.md#meaningful-documentation Native prose explains viewing and copy presentation; copied and failed labels distinguish actual clipboard outcomes under the documentation skill.
14
19
  */
15
20
  export function ResultViewer({ language, value }: ResultViewerProps) {
16
21
  const [copied, setCopied] = useState(false);
22
+ const [copyFailed, setCopyFailed] = useState(false);
23
+ const copyEpoch = useRef(0);
17
24
  const copiedTimer = useRef<number | null>(null);
18
25
 
19
26
  useEffect(
20
27
  () => () => {
28
+ ++copyEpoch.current;
21
29
  if (copiedTimer.current !== null)
22
30
  window.clearTimeout(copiedTimer.current);
23
31
  },
@@ -25,13 +33,26 @@ export function ResultViewer({ language, value }: ResultViewerProps) {
25
33
  );
26
34
 
27
35
  const onCopy = () => {
28
- void navigator.clipboard.writeText(value);
29
- setCopied(true);
36
+ const epoch = ++copyEpoch.current;
37
+ setCopied(false);
38
+ setCopyFailed(false);
30
39
  if (copiedTimer.current !== null) window.clearTimeout(copiedTimer.current);
31
- copiedTimer.current = window.setTimeout(() => {
32
- setCopied(false);
33
- copiedTimer.current = null;
34
- }, 1500);
40
+ copiedTimer.current = null;
41
+ void Promise.resolve()
42
+ .then(() => navigator.clipboard.writeText(value))
43
+ .then(
44
+ () => {
45
+ if (copyEpoch.current !== epoch) return;
46
+ setCopied(true);
47
+ copiedTimer.current = window.setTimeout(() => {
48
+ setCopied(false);
49
+ copiedTimer.current = null;
50
+ }, 1500);
51
+ },
52
+ () => {
53
+ if (copyEpoch.current === epoch) setCopyFailed(true);
54
+ },
55
+ );
35
56
  };
36
57
 
37
58
  return (
@@ -41,7 +62,7 @@ export function ResultViewer({ language, value }: ResultViewerProps) {
41
62
  onClick={onCopy}
42
63
  className="absolute right-3 top-2 z-10 rounded-md border border-[#b9d5ee] bg-white/90 px-2 py-1 font-mono text-[10px] text-[#235a97] shadow-sm transition-colors hover:bg-[#eaf4ff]"
43
64
  >
44
- {copied ? "Copied ✓" : "Copy"}
65
+ {copied ? "Copied ✓" : copyFailed ? "Copy failed" : "Copy"}
45
66
  </button>
46
67
  )}
47
68
  <Editor
@@ -6,6 +6,16 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
6
6
  import { DEFAULT_PLAYGROUND_COMPILER_OPTIONS } from "../compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS";
7
7
  import type { ISourceEditorProps } from "../structures/ISourceEditorProps";
8
8
 
9
+ /**
10
+ * Bind controlled source text to Monaco and replace declaration libraries when
11
+ * their map changes. Owned extra-lib registrations are disposed on replacement
12
+ * and unmount; this component uses Monaco's shared TypeScript defaults.
13
+ *
14
+ * @evidence contracts/common.md#principled-implementation Controlled changes publish full text, virtual URIs identify models, and supported extra-lib disposables replace prior declarations. Monaco preview suppresses unresolved-module diagnostics; the real Worker compile remains authoritative.
15
+ * @evidence contracts/common.md#clear-and-simple-design Editor presentation and declaration registration stay local while source state and compiler execution remain with the parent.
16
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Monaco's documented defaults and disposable registration APIs provide the integration boundary; suppression is preview policy rather than a change to real compiler errors.
17
+ * @evidence contracts/common.md#meaningful-documentation Native prose states registration lifetime and shared defaults; inline comments explain preview limitations under the documentation skill.
18
+ */
9
19
  export function SourceEditor({
10
20
  value,
11
21
  onChange,
@@ -12,6 +12,11 @@ import type { ICreateCompilerClientOptions } from "../structures/ICreateCompiler
12
12
  * One connection generation owns the cached promise and connector. A reset
13
13
  * invalidates that generation before awaiting it, so a late settlement cannot
14
14
  * replace a newer connection or clear its retry state.
15
+ *
16
+ * @evidence contracts/common.md#principled-implementation Each connection record owns its promise and connector; identity-checked failure eviction and invalidation-before-await prevent old settlements from replacing a newer generation.
17
+ * @evidence contracts/common.md#clear-and-simple-design A factory closure exposes only connect and reset while keeping transport state private; close sharing prevents duplicate disposal.
18
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Worker replacement uses tgrid's connector API; ordinary connection rejection remains failure rather than a synthetic driver.
19
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs define promise ownership and stale-settlement behavior under the documentation skill; reset may wait for an underlying connection that tgrid does not expose cancellation for.
15
20
  */
16
21
  export function createCompilerClient(options: ICreateCompilerClientOptions): {
17
22
  connect(): Promise<ICompilerService>;
@@ -1,5 +1,20 @@
1
+ /**
2
+ * Token for the compiler generation captured before asynchronous work.
3
+ *
4
+ * @evidence contracts/common.md#principled-implementation A live predicate compares captured epoch identity with the lifecycle's current epoch; the token does not grant ownership of a replacement Worker.
5
+ * @evidence contracts/common.md#clear-and-simple-design One capability hides epoch storage from callers while permitting stale-work fencing.
6
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Generation validity is actual owner identity rather than timing guesses or retries around stale state.
7
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines capture and ownership meaning with tag separation under the documentation skill.
8
+ */
1
9
  export interface IPlaygroundCompilerGeneration {
2
- /** Whether this token still owns the active compiler Worker generation. */
10
+ /**
11
+ * Whether this token still owns the active compiler Worker generation.
12
+ *
13
+ * @evidence contracts/common.md#principled-implementation Equality with the captured epoch determines validity synchronously before a caller publishes state.
14
+ * @evidence contracts/common.md#clear-and-simple-design A predicate exposes validity without exposing mutable lifecycle state.
15
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Validity depends on ownership rather than a grace period after replacement.
16
+ * @evidence contracts/common.md#meaningful-documentation Native prose states current ownership with tag separation under the documentation skill.
17
+ */
3
18
  isCurrent(): boolean;
4
19
  }
5
20
 
@@ -11,11 +26,24 @@ export interface IPlaygroundCompilerGeneration {
11
26
  * prevents its queued tasks from starting. New-generation tasks remain
12
27
  * serialized behind an active old task so two dependency installers can never
13
28
  * mutate the shared compiler filesystem concurrently.
29
+ *
30
+ * @evidence contracts/common.md#principled-implementation Monotonic epochs fence old callbacks and one promise chain orders mutations, retaining serialization even when an earlier task rejects.
31
+ * @evidence contracts/common.md#clear-and-simple-design The lifecycle owns generation and ordering only; callers provide Worker mutation, reset and metadata clearing.
32
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Stale work loses publication rights through explicit identity checks instead of compensating retries or fixed timing windows.
33
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain invalidation and old-task serialization; operation comments define reset ordering under the documentation skill.
14
34
  */
15
35
  export class PlaygroundCompilerLifecycle {
16
36
  private epoch: number = 0;
17
37
  private queue: Promise<void> = Promise.resolve();
18
38
 
39
+ /**
40
+ * Capture the current epoch without advancing it.
41
+ *
42
+ * @evidence contracts/common.md#principled-implementation A closure over the captured scalar remains valid exactly while the owning epoch is unchanged.
43
+ * @evidence contracts/common.md#clear-and-simple-design Token creation exposes one predicate and keeps epoch mutation internal.
44
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Capture uses actual generation identity rather than inspecting unrelated Worker internals.
45
+ * @evidence contracts/common.md#meaningful-documentation Native prose distinguishes observation from invalidation with tag separation under the documentation skill.
46
+ */
19
47
  public capture(): IPlaygroundCompilerGeneration {
20
48
  const epoch = this.epoch;
21
49
  return {
@@ -23,11 +51,27 @@ export class PlaygroundCompilerLifecycle {
23
51
  };
24
52
  }
25
53
 
54
+ /**
55
+ * Advance the epoch and return the replacement generation's token.
56
+ *
57
+ * @evidence contracts/common.md#principled-implementation Incrementing the epoch makes all prior captured predicates false before replacement work begins.
58
+ * @evidence contracts/common.md#clear-and-simple-design One operation owns the generation transition and reuses token construction.
59
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Invalidation changes the owning state directly instead of preserving stale assumptions beneath wrappers.
60
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines transition and return identity with tag separation under the documentation skill.
61
+ */
26
62
  public invalidate(): IPlaygroundCompilerGeneration {
27
63
  this.epoch++;
28
64
  return this.capture();
29
65
  }
30
66
 
67
+ /**
68
+ * Claim replacement only if the supplied generation still owns the lifecycle.
69
+ *
70
+ * @evidence contracts/common.md#principled-implementation Synchronous validity checking and epoch advance prevent a stale claimant from invalidating newer work.
71
+ * @evidence contracts/common.md#clear-and-simple-design Conditional claiming delegates the actual transition to invalidate.
72
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Stale claims return undefined rather than retrying against a replacement they do not own.
73
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines the ownership condition with tag separation under the documentation skill.
74
+ */
31
75
  public invalidateIfCurrent(
32
76
  generation: IPlaygroundCompilerGeneration,
33
77
  ): IPlaygroundCompilerGeneration | undefined {
@@ -43,6 +87,11 @@ export class PlaygroundCompilerLifecycle {
43
87
  * its metadata must become empty too. A Worker-generation replacement
44
88
  * performs its own synchronous clear and prevents this stale reset from
45
89
  * clearing the replacement.
90
+ *
91
+ * @evidence contracts/common.md#principled-implementation Validity is checked before and after asynchronous reset; metadata clears only for the same Worker generation even if its source changed.
92
+ * @evidence contracts/common.md#clear-and-simple-design Callbacks separate Worker disposal from metadata ownership while this method orders both effects.
93
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The reset corrects actual stale Worker contents instead of suppressing source errors or clearing a newer generation's state.
94
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain why source-version checking must follow metadata clearing under the documentation skill.
46
95
  */
47
96
  public async resetWorkerIfCurrent(
48
97
  generation: IPlaygroundCompilerGeneration,
@@ -62,6 +111,11 @@ export class PlaygroundCompilerLifecycle {
62
111
  * An RPC cannot be cancelled after it has started mutating the Worker's
63
112
  * MemFS. If its source becomes stale before completion, reset that Worker and
64
113
  * clear the matching dependency metadata before another source can reuse it.
114
+ *
115
+ * @evidence contracts/common.md#principled-implementation Both source and Worker identity gate mutation; a source becoming stale after a non-cancellable RPC triggers reset before the generation is reusable.
116
+ * @evidence contracts/common.md#clear-and-simple-design Mutation and reconciliation share one owning method, delegating reset ordering to the lifecycle helper.
117
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Obsolete writes are removed at their owner rather than hidden by a stale-result wrapper alone.
118
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain the non-cancellable RPC and required reconciliation under the documentation skill.
65
119
  */
66
120
  public async mutateWorkerIfCurrent(
67
121
  generation: IPlaygroundCompilerGeneration,
@@ -78,6 +132,15 @@ export class PlaygroundCompilerLifecycle {
78
132
  return false;
79
133
  }
80
134
 
135
+ /**
136
+ * Serialize a task behind prior mutations; stale queued tasks never start.
137
+ * Rejection propagates to the caller without poisoning later tasks.
138
+ *
139
+ * @evidence contracts/common.md#principled-implementation The captured generation gates task start and a fulfilled continuation tail preserves total ordering after either prior settlement.
140
+ * @evidence contracts/common.md#clear-and-simple-design One queue boundary coordinates every caller instead of duplicating ordering policy.
141
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Queue recovery preserves the original rejection for its caller and does not fabricate a successful mutation.
142
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines stale-task omission and rejection ownership with tag separation under the documentation skill.
143
+ */
81
144
  public enqueue<T>(
82
145
  task: (generation: IPlaygroundCompilerGeneration) => Promise<T>,
83
146
  ): Promise<T | undefined> {
@@ -1,9 +1,33 @@
1
+ /**
2
+ * Attempt token whose signal and publication rights end on supersession.
3
+ *
4
+ * @evidence contracts/common.md#principled-implementation Signal propagates cancellation while isCurrent and finish condition state publication and completion on the same captured owner.
5
+ * @evidence contracts/common.md#clear-and-simple-design One token bundles the attempt capabilities without exposing the controller or lifecycle state.
6
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Attempt rights are explicit rather than guessed from elapsed time or the last completed bundle.
7
+ * @evidence contracts/common.md#meaningful-documentation Native prose and member comments explain cancellation and ownership with documentation-skill member spacing.
8
+ */
1
9
  export interface IPlaygroundExecutionAttempt {
2
10
  /** Signal passed through every cancellable step owned by this attempt. */
3
11
  readonly signal: AbortSignal;
4
- /** Whether this attempt may still commit messages or state. */
12
+
13
+ /**
14
+ * Whether this attempt may still commit messages or state.
15
+ *
16
+ * @evidence contracts/common.md#principled-implementation Current epoch and active controller identity jointly decide publication rights.
17
+ * @evidence contracts/common.md#clear-and-simple-design The predicate keeps lifecycle storage private while exposing one decision.
18
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Superseded work cannot regain rights through a successful late result.
19
+ * @evidence contracts/common.md#meaningful-documentation Native prose states publication meaning with tag separation under the documentation skill.
20
+ */
5
21
  isCurrent(): boolean;
6
- /** Release the active slot if this is still the current attempt. */
22
+
23
+ /**
24
+ * Release the active slot only while this attempt remains its owner.
25
+ *
26
+ * @evidence contracts/common.md#principled-implementation Conditional completion cannot clear a newer attempt's controller and returns whether the caller owned the transition.
27
+ * @evidence contracts/common.md#clear-and-simple-design One completion capability centralizes active-slot release.
28
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Old finally callbacks cannot force a new attempt into completed state.
29
+ * @evidence contracts/common.md#meaningful-documentation Native prose states conditional release with tag separation under the documentation skill.
30
+ */
7
31
  finish(): boolean;
8
32
  }
9
33
 
@@ -13,11 +37,24 @@ export interface IPlaygroundExecutionAttempt {
13
37
  * React state stays in `PlaygroundShell`; this small state machine keeps the
14
38
  * supersession rules independently testable and makes every invalidation path
15
39
  * use the same abort behavior.
40
+ *
41
+ * @evidence contracts/common.md#principled-implementation Epoch and controller identity fence stale messages; invalidation clears ownership before dispatching synchronous abort listeners.
42
+ * @evidence contracts/common.md#clear-and-simple-design This state machine owns attempts only while React owns rendering and the site owns execution isolation.
43
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Shared invalidation corrects ownership directly rather than using separate timing-based cancellation wrappers.
44
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state cancellation ownership and shared invalidation purpose under the documentation skill.
16
45
  */
17
46
  export class PlaygroundExecutionLifecycle {
18
47
  private active: AbortController | null = null;
19
48
  private epoch = 0;
20
49
 
50
+ /**
51
+ * Supersede the active attempt and capture a fresh controller and epoch.
52
+ *
53
+ * @evidence contracts/common.md#principled-implementation Invalidation aborts the prior owner before the new controller is installed; returned closures compare both epoch and controller.
54
+ * @evidence contracts/common.md#clear-and-simple-design One constructor supplies cancellation, validity and conditional completion for an attempt.
55
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Supersession uses the common owning transition instead of stacking compensating callbacks.
56
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines fresh ownership and prior-attempt behavior with tag separation under the documentation skill.
57
+ */
21
58
  public begin(): IPlaygroundExecutionAttempt {
22
59
  this.invalidate("a newer Execute started");
23
60
  const controller = new AbortController();
@@ -38,6 +75,11 @@ export class PlaygroundExecutionLifecycle {
38
75
  * Abort the active attempt and make its callbacks stale.
39
76
  *
40
77
  * Returns whether an active attempt was canceled.
78
+ *
79
+ * @evidence contracts/common.md#principled-implementation Clearing active identity and advancing epoch precede abort notification, preventing reentrant old callbacks from publishing.
80
+ * @evidence contracts/common.md#clear-and-simple-design One transition handles every invalidation reason and delegates error construction locally.
81
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The actual owner receives cancellation; an absent owner is reported without inventing a task to cancel.
82
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state abort effect and return meaning under the documentation skill.
41
83
  */
42
84
  public invalidate(reason: string): boolean {
43
85
  const active = this.active;
@@ -1,17 +1,53 @@
1
1
  import { BootTtscWorkerTerminationError } from "@ttsc/wasm";
2
2
 
3
+ /**
4
+ * Owner callbacks for replacing a Worker whose Go runtime cannot be restarted.
5
+ *
6
+ * @evidence contracts/common.md#principled-implementation Claim, reset and failure publication express the ordered ownership transition while leaving actual Worker identity with the client.
7
+ * @evidence contracts/common.md#clear-and-simple-design The adapter exposes only the three effects recovery needs, independently of React state.
8
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Recovery is limited to terminal runtime identity rather than a universal retry wrapper around ordinary failures.
9
+ * @evidence contracts/common.md#meaningful-documentation Native prose states terminal scope and member ownership, with documentation-skill member spacing.
10
+ */
3
11
  export interface ITerminalCompilerWorkerRecovery {
4
- /** Atomically claim and fence the failed Worker generation. */
12
+ /**
13
+ * Atomically claim and fence the failed Worker generation.
14
+ *
15
+ * @evidence contracts/common.md#principled-implementation A boolean reports whether this caller obtained replacement ownership before asynchronous disposal.
16
+ * @evidence contracts/common.md#clear-and-simple-design The client retains generation state while recovery consumes a narrow claim decision.
17
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Stale claimants cannot reset another caller's replacement.
18
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines atomic claim and fencing with tag separation under the documentation skill.
19
+ */
5
20
  claim(): boolean;
6
- /** Close and clear that Worker generation. */
21
+
22
+ /**
23
+ * Close and clear the claimed Worker generation before failure publication.
24
+ *
25
+ * @evidence contracts/common.md#principled-implementation The promise marks the asynchronous disposal boundary for the claimed owner.
26
+ * @evidence contracts/common.md#clear-and-simple-design Disposal remains the client's responsibility rather than being recreated in the recovery helper.
27
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Disposal uses the supported client boundary instead of replacing runtime internals.
28
+ * @evidence contracts/common.md#meaningful-documentation Native prose states ownership and ordering with tag separation under the documentation skill.
29
+ */
7
30
  reset(): Promise<void>;
8
- /** Publish the terminal error after the old Worker is no longer reachable. */
31
+
32
+ /**
33
+ * Publish the terminal error after reset settles, even if reset rejects.
34
+ *
35
+ * @evidence contracts/common.md#principled-implementation Publication preserves the actual terminal error independently of whether disposal succeeded.
36
+ * @evidence contracts/common.md#clear-and-simple-design State presentation remains with the owner callback, separated from error classification.
37
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Disposal failure does not erase the deciding runtime failure or invent readiness.
38
+ * @evidence contracts/common.md#meaningful-documentation Native prose states finally-path behavior with tag separation under the documentation skill.
39
+ */
9
40
  fail(error: unknown): void;
10
41
  }
11
42
 
12
43
  /**
13
44
  * Replace a compiler Worker after its Go runtime started but boot never became
14
45
  * usable. Returns false for ordinary compile, transport, and plugin failures.
46
+ *
47
+ * @evidence contracts/common.md#principled-implementation Stable terminal-error classification gates an atomic claim; reset is awaited and original failure publication runs in finally so terminal state is never replaced by ordinary retry.
48
+ * @evidence contracts/common.md#clear-and-simple-design Classification, ownership and reset effects have separate boundaries, keeping this coordinator independent of UI state.
49
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Only the runtime's explicit terminal identity triggers replacement; ordinary failures do not accumulate reset-and-retry compensation.
50
+ * @evidence contracts/common.md#meaningful-documentation Native prose states terminal scope and false return meaning; callback prose defines reset rejection behavior under the documentation skill.
15
51
  */
16
52
  export async function recoverTerminalCompilerWorker(
17
53
  error: unknown,
@@ -27,7 +63,15 @@ export async function recoverTerminalCompilerWorker(
27
63
  return true;
28
64
  }
29
65
 
30
- /** Recognize both local errors and their plain tgrid/JSON transport shape. */
66
+ /**
67
+ * Recognize the terminal runtime's stable code or message prefix after
68
+ * transport.
69
+ *
70
+ * @evidence contracts/common.md#principled-implementation The host-owned code matches both enumerable error records and prefixed string messages without relying on cross-realm instanceof.
71
+ * @evidence contracts/common.md#clear-and-simple-design One classifier centralizes local and transported identity for every recovery caller.
72
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Classification uses a supported error identity rather than arbitrary message fragments or consumer names.
73
+ * @evidence contracts/common.md#meaningful-documentation Native prose states the stable transport witness with tag separation under the documentation skill.
74
+ */
31
75
  export function requiresCompilerWorkerReplacement(error: unknown): boolean {
32
76
  const code = BootTtscWorkerTerminationError.CODE;
33
77
  const prefix = `[${code}] `;
@@ -45,9 +45,18 @@ class InvalidPackageTargetLoadError extends Error {}
45
45
  class InvalidPackageConfigError extends Error {}
46
46
 
47
47
  /**
48
- * Build a sandboxed `require` function over a runtime pack. Resolves typia /
49
- * `@typia/*` / randexp specifiers from the pack; throws on anything else so the
50
- * caller sees the unsupported dependency.
48
+ * Build a CommonJS resolver and evaluator over a caller-owned runtime pack.
49
+ * Missing modules throw; package exports use require/default conditions and
50
+ * legacy packages retain file-and-directory resolution.
51
+ *
52
+ * The pack must remain unchanged while this resolver is used. Evaluation uses
53
+ * new Function in the current realm; this is not a security isolation
54
+ * boundary.
55
+ *
56
+ * @evidence contracts/common.md#principled-implementation Target selection precedes loading, conditional exports preserve manifest order, validated targets stay under the package mount, and provisional module caching exposes partial exports for cycles. Evaluation assumes CommonJS source in an unchanged pack.
57
+ * @evidence contracts/common.md#clear-and-simple-design Manifest interpretation, specifier resolution and evaluation have local boundaries; the caller owns code isolation and transport.
58
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported exports and legacy resolution decide module access uniformly; missing targets cannot trigger a second exports candidate, and failed evaluation evicts its provisional module instead of fabricating exports.
59
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs define resolver scope, pack immutability and absent isolation; helper comments explain exports priority and cycle semantics under the documentation skill.
51
60
  */
52
61
  export function createSandboxRequire(
53
62
  pack: Record<string, string>,
@@ -55,6 +64,7 @@ export function createSandboxRequire(
55
64
  ): (specifier: string) => unknown {
56
65
  type ModuleObj = { exports: unknown };
57
66
  const cache = new Map<string, ModuleObj>();
67
+ const manifests = new Map<string, IPackJson>();
58
68
 
59
69
  const has = (key: string): boolean =>
60
70
  Object.prototype.hasOwnProperty.call(pack, key);
@@ -65,6 +75,7 @@ export function createSandboxRequire(
65
75
  };
66
76
 
67
77
  const readPackageJson = (mount: string): IPackJson | null => {
78
+ if (manifests.has(mount)) return manifests.get(mount)!;
68
79
  const key = `${mount}/package.json`;
69
80
  if (!has(key)) return null;
70
81
  try {
@@ -76,6 +87,7 @@ export function createSandboxRequire(
76
87
  ) {
77
88
  throw new Error("package.json must contain an object");
78
89
  }
90
+ manifests.set(mount, parsed as IPackJson);
79
91
  return parsed as IPackJson;
80
92
  } catch {
81
93
  throw new InvalidPackageConfigError(
@@ -337,18 +349,19 @@ function resolvePackageExports(
337
349
  ) {
338
350
  target = entries[request];
339
351
  } else {
340
- const patterns = Object.entries(entries)
341
- .map(([pattern, candidate]) => ({
342
- pattern,
343
- replacement: exportPatternReplacement(pattern, request),
344
- target: candidate,
345
- }))
346
- .filter(
347
- (entry): entry is typeof entry & { replacement: string } =>
348
- entry.replacement !== undefined,
349
- )
350
- .sort((a, b) => compareExportPatternKeys(a.pattern, b.pattern));
351
- const selected = patterns[0];
352
+ let selected:
353
+ | { pattern: string; replacement: string; target: unknown }
354
+ | undefined;
355
+ for (const [pattern, candidate] of Object.entries(entries)) {
356
+ const replacement = exportPatternReplacement(pattern, request);
357
+ if (replacement === undefined) continue;
358
+ if (
359
+ selected === undefined ||
360
+ compareExportPatternKeys(pattern, selected.pattern) < 0
361
+ ) {
362
+ selected = { pattern, replacement, target: candidate };
363
+ }
364
+ }
352
365
  if (selected === undefined) return { type: "unresolved" };
353
366
  return resolvePackageTarget(
354
367
  mount,