@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
@@ -1,18 +1,35 @@
1
1
  /**
2
2
  * Pick the most likely emitted JavaScript file from a compile result's output
3
- * map. Tries common paths first, then falls back to the first `.js` entry.
4
- * Returns null when no `.js` was emitted.
3
+ * map. Tries the default playground layout (`rootDir` `src`, `outDir` `dist`,
4
+ * which emits `dist/playground.js` for `src/playground.ts`) and other common
5
+ * paths first, then falls back to the first `.js` entry. Returns null when no
6
+ * `.js` was emitted.
7
+ *
8
+ * @evidence contracts/common.md#principled-implementation The first candidate is the default playground layout (the entry's path below the default `src` root under `dist`), the others cover layouts rooted at the project; the first remaining JavaScript entry is an explicitly heuristic fallback, not a general entrypoint solver.
9
+ * @evidence contracts/common.md#clear-and-simple-design A small selector separates UI output choice from compiler emit configuration.
10
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Layout candidates are product defaults rather than fixture filenames, and absent JavaScript remains null.
11
+ * @evidence contracts/common.md#meaningful-documentation Native prose honestly documents priority and heuristic fallback, separated from tags under the documentation skill.
5
12
  */
6
13
  export function pickEmittedJS(
7
14
  output: Record<string, string>,
8
15
  entryFile: string,
9
16
  ): string | null {
10
17
  const base = entryFile.replace(/\.[cm]?tsx?$/i, ".js");
11
- const candidates = [`dist/${base}`, `dist/src/${base}`, `src/${base}`, base];
18
+ // `buildTsconfigJSON` roots sources at `src` and emits to `dist`, so the
19
+ // default entry `src/playground.ts` is reported as `dist/playground.js`.
20
+ const underSrcRoot = base.replace(/^src\//, "");
21
+ const candidates = [
22
+ `dist/${underSrcRoot}`,
23
+ `dist/${base}`,
24
+ `dist/src/${base}`,
25
+ `src/${base}`,
26
+ base,
27
+ ];
12
28
  for (const key of candidates) {
13
29
  if (output[key] !== undefined) return output[key];
14
30
  }
15
- const jsKeys = Object.keys(output).filter((k) => k.endsWith(".js"));
16
- if (jsKeys.length > 0) return output[jsKeys[0]!] ?? null;
31
+ for (const key of Object.keys(output)) {
32
+ if (key.endsWith(".js")) return output[key] ?? null;
33
+ }
17
34
  return null;
18
35
  }
@@ -4,6 +4,23 @@ import { packageNameFromSpecifier } from "./packageNameFromSpecifier";
4
4
  /**
5
5
  * Scan `source` for `import` / `require` specifiers and return the unique
6
6
  * sorted list of bare npm package names that are not in `ignoredPackages`.
7
+ *
8
+ * This is a lexical discovery pass, not name binding: a locally shadowed direct
9
+ * require call still looks like a dependency request. Computed strings are
10
+ * omitted.
11
+ *
12
+ * Static arguments are quoted strings and substitution-free, escape-free
13
+ * templates (`require(`x`)`, `import(`x`)`). Deliberate limits of the
14
+ * TypeScript-source lane: JSX is not lexed (the playground entry is a `.ts`
15
+ * file), so quote or `import` text in JSX children can be misread; a type
16
+ * argument between the callee and its parenthesis (`require<T>("x")`) is not
17
+ * recognized because `require` is not generic; and a source-phase import
18
+ * (`import source x from "y"`) collects `y`, the package it loads.
19
+ *
20
+ * @evidence contracts/common.md#principled-implementation Tokenization distinguishes executable quoted and static-template specifiers from inert comments, strings and regex bodies; package-name normalization and a Set produce unique sorted install requests. Lexical discovery does not resolve shadowed require bindings.
21
+ * @evidence contracts/common.md#clear-and-simple-design Lexer, module-construct recognition and package filtering are separate local responsibilities, without importing the full compiler into browser keystroke discovery.
22
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Ignored package names are explicit caller policy; source matching does not fabricate dependency results for known examples.
23
+ * @evidence contracts/common.md#meaningful-documentation Native prose states discovery domain and binding limitations; helper comments explain escaped strings and lexical boundaries under the documentation skill.
7
24
  */
8
25
  export function collectExternalPackageNames(
9
26
  source: string,
@@ -33,14 +50,26 @@ export function collectExternalPackageNames(
33
50
  function collectModuleSpecifiers(source: string): string[] {
34
51
  const tokens = tokenize(source);
35
52
  const out: string[] = [];
53
+ // A quoted literal only; `import "x"` and `from "x"` cannot take a template.
54
+ const asQuoted = (token: Token | undefined): string | null =>
55
+ token && token.kind === "string" && !token.template ? token.value : null;
56
+ // A call argument may also be a static template: `require(`x`)`.
36
57
  const asString = (token: Token | undefined): string | null =>
37
58
  token && token.kind === "string" ? token.value : null;
38
59
  const isOpenParen = (token: Token | undefined): boolean =>
39
60
  token !== undefined && token.kind === "punct" && token.value === "(";
40
- const isMemberAccess = (token: Token | undefined): boolean =>
41
- token !== undefined &&
42
- token.kind === "punct" &&
43
- (token.value === "." || token.value === "?.");
61
+ const isPunct = (token: Token | undefined, value: string): boolean =>
62
+ token !== undefined && token.kind === "punct" && token.value === value;
63
+ // `obj.name`, `obj?.name` and `this.#name` are not the free binding. The
64
+ // three dots of a spread are not a member access.
65
+ const isMemberAccessAt = (index: number): boolean => {
66
+ const previous = tokens[index - 1];
67
+ if (isPunct(previous, "#") || isPunct(previous, "?.")) return true;
68
+ if (!isPunct(previous, ".")) return false;
69
+ return !(
70
+ isPunct(tokens[index - 2], ".") && isPunct(tokens[index - 3], ".")
71
+ );
72
+ };
44
73
  const isOptionalChain = (token: Token | undefined): boolean =>
45
74
  token !== undefined && token.kind === "punct" && token.value === "?.";
46
75
 
@@ -50,7 +79,7 @@ function collectModuleSpecifiers(source: string): string[] {
50
79
 
51
80
  if (token.value === "require") {
52
81
  // `obj.require(...)` is an unrelated method call, not CommonJS require.
53
- if (isMemberAccess(tokens[i - 1])) continue;
82
+ if (isMemberAccessAt(i)) continue;
54
83
  const optional =
55
84
  isOptionalChain(tokens[i + 1]) && isOpenParen(tokens[i + 2]);
56
85
  if (isOpenParen(tokens[i + 1]) || optional) {
@@ -62,7 +91,7 @@ function collectModuleSpecifiers(source: string): string[] {
62
91
 
63
92
  if (token.value === "import" || token.value === "export") {
64
93
  // `foo.import(...)` / `import.meta` are not module-loading imports.
65
- if (token.value === "import" && isMemberAccess(tokens[i - 1])) continue;
94
+ if (token.value === "import" && isMemberAccessAt(i)) continue;
66
95
  // Dynamic `import("x")`.
67
96
  if (token.value === "import" && isOpenParen(tokens[i + 1])) {
68
97
  const spec = asString(tokens[i + 2]);
@@ -71,7 +100,7 @@ function collectModuleSpecifiers(source: string): string[] {
71
100
  }
72
101
  // Side-effect `import "x"`.
73
102
  if (token.value === "import") {
74
- const bare = asString(tokens[i + 1]);
103
+ const bare = asQuoted(tokens[i + 1]);
75
104
  if (bare !== null) {
76
105
  out.push(bare);
77
106
  continue;
@@ -103,7 +132,9 @@ function findFromSpecifier(tokens: Token[], start: number): string | null {
103
132
  return null;
104
133
  if (token.kind === "word" && token.value === "from") {
105
134
  const next = tokens[i + 1];
106
- return next && next.kind === "string" ? next.value : null;
135
+ return next && next.kind === "string" && !next.template
136
+ ? next.value
137
+ : null;
107
138
  }
108
139
  }
109
140
  return null;
@@ -114,14 +145,16 @@ type Token =
114
145
  | { kind: "word"; value: string }
115
146
  // A single- or double-quoted string literal, with escapes decoded to their
116
147
  // literal characters so a specifier survives unchanged.
117
- | { kind: "string"; value: string }
148
+ | { kind: "string"; value: string; template?: true }
118
149
  // A punctuation token; compound forms are retained where lexical state or
119
150
  // module-call recognition depends on them.
120
151
  | {
121
152
  kind: "punct";
122
153
  value: string;
154
+
123
155
  /** Whether a slash after this closing delimiter begins a regex literal. */
124
156
  regexAllowedAfter?: boolean;
157
+
125
158
  /** Whether this closes a function parameter list for an expression. */
126
159
  functionBodyIsExpression?: boolean;
127
160
  }
@@ -362,7 +395,7 @@ function tokenize(source: string): Token[] {
362
395
  // Line comment.
363
396
  if (c === "/" && source[i + 1] === "/") {
364
397
  i += 2;
365
- while (i < n && source[i] !== "\n") i++;
398
+ while (i < n && !isLineTerminator(source[i]!)) i++;
366
399
  continue;
367
400
  }
368
401
  // Block comment.
@@ -390,9 +423,12 @@ function tokenize(source: string): Token[] {
390
423
  // JavaScript and must receive the same lexical treatment as top-level code.
391
424
  if (c === "`") {
392
425
  i++;
426
+ // A template with no substitution and no escape is one static string.
427
+ let staticValue: string | null = "";
393
428
  while (i < n) {
394
429
  const d = source[i];
395
430
  if (d === "\\") {
431
+ staticValue = null;
396
432
  i += 2;
397
433
  continue;
398
434
  }
@@ -403,15 +439,20 @@ function tokenize(source: string): Token[] {
403
439
  if (d === "$" && source[i + 1] === "{") {
404
440
  const start = i + 2;
405
441
  const end = findTemplateSubstitutionEnd(source, start);
442
+ staticValue = null;
406
443
  context.pushOther();
407
- tokens.push(...tokenize(source.slice(start, end)));
444
+ for (const entryToAppend of tokenize(source.slice(start, end)))
445
+ tokens.push(entryToAppend);
408
446
  context.pushOther();
409
447
  i = end < n ? end + 1 : end;
410
448
  continue;
411
449
  }
450
+ if (staticValue !== null) staticValue += d;
412
451
  i++;
413
452
  }
414
- context.pushOther();
453
+ if (staticValue !== null && source[i - 1] === "`")
454
+ tokens.push({ kind: "string", value: staticValue, template: true });
455
+ else context.pushOther();
415
456
  continue;
416
457
  }
417
458
  // Identifier / keyword.
@@ -475,7 +516,7 @@ function findTemplateSubstitutionEnd(source: string, start: number): number {
475
516
  }
476
517
  if (c === "/" && source[i + 1] === "/") {
477
518
  i += 2;
478
- while (i < source.length && source[i] !== "\n") i++;
519
+ while (i < source.length && !isLineTerminator(source[i]!)) i++;
479
520
  continue;
480
521
  }
481
522
  if (c === "/" && source[i + 1] === "*") {
@@ -32,8 +32,16 @@ const DEFAULT_MAX_UNPACKED_BYTES = 64 * 1024 * 1024;
32
32
  * required, optional, and peer dependency fields. Passing a prior call's
33
33
  * `resolvedDependencies` validates new edges against the exact mounted graph
34
34
  * and reuses compatible packages without downloading their tarballs again. The
35
- * walk is bounded by `maxPackages` to keep a single keystroke from exhausting
36
- * the tab's network/memory budget.
35
+ * walk is bounded by the nonnegative safe-integer `maxPackages` to keep a
36
+ * single keystroke from exhausting the tab's network/memory budget. The cap
37
+ * counts distinct names completed in this call, including mounted packages
38
+ * revalidated and optional packages omitted. Unrequested mounted state does not
39
+ * consume this call's budget; zero permits no queued package work.
40
+ *
41
+ * @evidence contracts/common.md#principled-implementation The queue unifies required ranges per exposed name and registry identity, pins reused exact versions and rejects incompatible required edges. Optional edges refine only compatible solves; verified tar bytes are confined before files enter consumer namespaces.
42
+ * @evidence contracts/common.md#clear-and-simple-design Graph coordination stays here while registry transport, version selection, archive validation and file mapping have explicit helper boundaries.
43
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Optional omissions are declared npm semantics; required failures, alias conflicts and integrity failures remain failures instead of fabricated mounted state. Name-only legacy skip state is documented as unable to validate versions.
44
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain prior graph reuse and package budgets; queue comments explain late constraints and optional-to-required transitions under the documentation skill.
37
45
  */
38
46
  export async function installPlaygroundDependencies(
39
47
  packageNames: Iterable<string>,
@@ -44,6 +52,10 @@ export async function installPlaygroundDependencies(
44
52
  throw new Error("installPlaygroundDependencies requires fetch.");
45
53
  }
46
54
  throwIfAborted(options.signal);
55
+ const maxPackages = options.maxPackages ?? DEFAULT_MAX_PACKAGES;
56
+ if (!Number.isSafeInteger(maxPackages) || maxPackages < 0) {
57
+ throw new Error("maxPackages must be a non-negative safe integer.");
58
+ }
47
59
 
48
60
  const ignored = new Set(
49
61
  options.ignoredPackages ?? BUILT_IN_PLAYGROUND_PACKAGES,
@@ -65,9 +77,10 @@ export async function installPlaygroundDependencies(
65
77
  );
66
78
  }
67
79
  if (previous !== undefined) {
68
- previous.requests.push(
69
- ...dependency.requests.map((request) => ({ ...request })),
70
- );
80
+ for (const entryToAppend of dependency.requests.map((request) => ({
81
+ ...request,
82
+ })))
83
+ previous.requests.push(entryToAppend);
71
84
  continue;
72
85
  }
73
86
  installedDependencies.set(dependency.name, {
@@ -75,7 +88,6 @@ export async function installPlaygroundDependencies(
75
88
  requests: dependency.requests.map((request) => ({ ...request })),
76
89
  });
77
90
  }
78
- const maxPackages = options.maxPackages ?? DEFAULT_MAX_PACKAGES;
79
91
  const maxTarballBytes = options.maxTarballBytes ?? DEFAULT_MAX_TARBALL_BYTES;
80
92
  const maxUnpackedBytes =
81
93
  options.maxUnpackedBytes ?? DEFAULT_MAX_UNPACKED_BYTES;
@@ -14,6 +14,15 @@ interface IPackageJson {
14
14
  peerDependenciesMeta?: Record<string, { optional?: boolean }>;
15
15
  }
16
16
 
17
+ /**
18
+ * Version-specific registry manifest; absent dist metadata cannot supply a
19
+ * tarball.
20
+ *
21
+ * @evidence contracts/common.md#principled-implementation Identity, dependency fields and optional distribution witnesses express the registry subset consumed by version selection and authentication.
22
+ * @evidence contracts/common.md#clear-and-simple-design Registry metadata remains separate from unpacked files and mounted state.
23
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Optional authentication fields represent historical metadata compatibility, not fabricated integrity evidence.
24
+ * @evidence contracts/common.md#meaningful-documentation Native prose states subset and absent distribution meaning, separated from tags under the documentation skill.
25
+ */
17
26
  export interface INpmVersionMetadata {
18
27
  name: string;
19
28
  version: string;
@@ -28,17 +37,42 @@ export interface INpmVersionMetadata {
28
37
  };
29
38
  }
30
39
 
40
+ /**
41
+ * Registry packument subset, indexed by exact version with optional tag
42
+ * aliases.
43
+ *
44
+ * @evidence contracts/common.md#principled-implementation Version entries and dist-tag mappings provide the domain over which semver constraints are resolved.
45
+ * @evidence contracts/common.md#clear-and-simple-design The packument index is independent of a particular install request or mounted graph.
46
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Missing entries remain representable instead of inventing a version from a requested tag.
47
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines version indexing and tag purpose with tag separation under the documentation skill.
48
+ */
31
49
  export interface INpmMetadata {
32
50
  name: string;
33
51
  "dist-tags"?: Record<string, string>;
34
52
  versions: Record<string, INpmVersionMetadata | undefined>;
35
53
  }
36
54
 
55
+ /**
56
+ * Confined package-relative text files and the decoded manifest subset.
57
+ *
58
+ * @evidence contracts/common.md#principled-implementation Files omit the safe archive root while packageJson supplies dependency metadata for graph traversal.
59
+ * @evidence contracts/common.md#clear-and-simple-design Extraction output is separate from consumer-specific mounted path maps.
60
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Archive contents determine files without consumer-specific manufactured declarations.
61
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines confinement and path namespace with tag separation under the documentation skill.
62
+ */
37
63
  export interface IUnpackedPackage {
38
64
  files: Record<string, string>;
39
65
  packageJson: IPackageJson;
40
66
  }
41
67
 
68
+ /**
69
+ * One exposed package queued for registry solving, retaining all active edges.
70
+ *
71
+ * @evidence contracts/common.md#principled-implementation Exposed name and optional registryName distinguish aliases; requests preserve intersecting ranges and origin while optionality describes whether omission is permitted.
72
+ * @evidence contracts/common.md#clear-and-simple-design Mutable queue coordination is isolated from published immutable identity records.
73
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Explicit edge state prevents silently ignoring a late required constraint.
74
+ * @evidence contracts/common.md#meaningful-documentation Native prose states queue role and active-edge retention with tag separation under the documentation skill.
75
+ */
42
76
  export interface IQueueItem {
43
77
  name: string;
44
78
  range: string;
@@ -48,12 +82,28 @@ export interface IQueueItem {
48
82
  requests?: IVersionRequest[];
49
83
  }
50
84
 
85
+ /**
86
+ * A normalized semver range or registry tag together with requesting origin.
87
+ *
88
+ * @evidence contracts/common.md#principled-implementation Range and origin retain the constraint while optional marks whether incompatible omission is allowed.
89
+ * @evidence contracts/common.md#clear-and-simple-design This edge record is independent of mounted package identity and registry transport.
90
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Optionality is a declared graph distinction, not guessed from a missing package response.
91
+ * @evidence contracts/common.md#meaningful-documentation Native prose states range, origin and omission role with tag separation under the documentation skill.
92
+ */
51
93
  export interface IVersionRequest {
52
94
  optional: boolean;
53
95
  range: string;
54
96
  requester: string;
55
97
  }
56
98
 
99
+ /**
100
+ * Standard string-URL fetch transport used by registry and tarball requests.
101
+ *
102
+ * @evidence contracts/common.md#principled-implementation RequestInit and Response preserve status, headers, abort and body contracts needed by browser installation.
103
+ * @evidence contracts/common.md#clear-and-simple-design A named callable signature keeps transport injectable without exposing solver state.
104
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Injection uses an explicit callback instead of replacing global transport internals.
105
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines transport responsibility with tag separation under the documentation skill.
106
+ */
57
107
  export type FetchLike = (
58
108
  input: string,
59
109
  init?: RequestInit,
@@ -69,12 +119,30 @@ const TEXT_FILE_REGEXP =
69
119
  export const DECLARATION_FILE_REGEXP = /\.d\.[cm]?ts$/i;
70
120
  const RUNTIME_FILE_REGEXP = /(^package\.json$|\.([cm]?js|json)$)/i;
71
121
 
122
+ /**
123
+ * Preserve the signal's abort reason; use AbortError only when no reason
124
+ * exists.
125
+ *
126
+ * @evidence contracts/common.md#principled-implementation AbortSignal.aborted gates rejection and its reason remains the deciding thrown value.
127
+ * @evidence contracts/common.md#clear-and-simple-design One guard centralizes cancellation semantics across transport and archive phases.
128
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Cancellation is caller policy rather than a fabricated timeout or successful empty result.
129
+ * @evidence contracts/common.md#meaningful-documentation Native prose explains reason preservation with tag separation under the documentation skill.
130
+ */
72
131
  export function throwIfAborted(signal: AbortSignal | undefined): void {
73
132
  if (!signal?.aborted) return;
74
133
  if (signal.reason !== undefined) throw signal.reason;
75
134
  throw new DOMException("The operation was aborted.", "AbortError");
76
135
  }
77
136
 
137
+ /**
138
+ * Fetch the public registry packument; only an optional package's 404 is
139
+ * omitted.
140
+ *
141
+ * @evidence contracts/common.md#principled-implementation Encoded package identity forms the registry URL; status handling distinguishes an allowed optional absence from transport failure and decodes the requested metadata.
142
+ * @evidence contracts/common.md#clear-and-simple-design Transport and abort helpers serve one metadata request while version solving stays separate.
143
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Only the declared optional-404 contract yields null; other failures cannot masquerade as installed metadata.
144
+ * @evidence contracts/common.md#meaningful-documentation Native prose identifies registry ownership and omission semantics with tag separation under the documentation skill.
145
+ */
78
146
  export async function fetchNpmMetadata(
79
147
  fetchImpl: FetchLike,
80
148
  packageName: string,
@@ -115,6 +183,15 @@ export async function fetchNpmMetadata(
115
183
  return metadata;
116
184
  }
117
185
 
186
+ /**
187
+ * Select the highest published exact version satisfying every requested range;
188
+ * tags resolve through registry metadata and missing candidates throw.
189
+ *
190
+ * @evidence contracts/common.md#principled-implementation Semver validates ranges and tag mappings, then filters published versions by the intersection before highest-version selection.
191
+ * @evidence contracts/common.md#clear-and-simple-design This pure selector owns constraint intersection while callers own optional edges and mounted-version pins.
192
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts No package-specific version is chosen and incompatible constraints remain an error rather than an arbitrary latest fallback.
193
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines highest-version and failure semantics with tag separation under the documentation skill.
194
+ */
118
195
  export function selectVersion(
119
196
  metadata: INpmMetadata,
120
197
  ranges: readonly string[] | string,
@@ -145,7 +222,9 @@ export function selectVersion(
145
222
  semverRanges.every((range) => satisfies(version, range)) &&
146
223
  (taggedVersions.size === 0 || taggedVersions.has(version)),
147
224
  );
148
- const selected = maxSatisfying(candidates, "*");
225
+ // Candidates already satisfy every requested constraint, including the
226
+ // explicit prerelease admission of a range or exact registry tag.
227
+ const selected = maxSatisfying(candidates, "*", { includePrerelease: true });
149
228
  if (selected) return selected;
150
229
  throw new Error(
151
230
  `No version of ${metadata.name} satisfies ${requested
@@ -154,6 +233,15 @@ export function selectVersion(
154
233
  );
155
234
  }
156
235
 
236
+ /**
237
+ * Read compressed archive bytes within the validated budget; cancel unused
238
+ * bodies on status failure, excessive declared length or cancellation.
239
+ *
240
+ * @evidence contracts/common.md#principled-implementation Header checks reject obvious excess while streamed accounting enforces actual compressed bytes even when Content-Length is absent or inaccurate.
241
+ * @evidence contracts/common.md#clear-and-simple-design Fetching and body-budget enforcement delegate cancellation and bounded collection to shared helpers.
242
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Every archive receives the same budget and failures remain visible without returning truncated synthetic success.
243
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state byte domain and body disposal with tag separation under the documentation skill.
244
+ */
157
245
  export async function downloadTarball(
158
246
  fetchImpl: FetchLike,
159
247
  tarball: string,
@@ -190,7 +278,16 @@ export async function downloadTarball(
190
278
  );
191
279
  }
192
280
 
193
- /** Verify registry authentication metadata against the compressed bytes. */
281
+ /**
282
+ * Verify registry authentication metadata against compressed bytes. The
283
+ * strongest supported SRI group wins; absent SRI falls back to shasum, and
284
+ * absent both is accepted without an authentication claim.
285
+ *
286
+ * @evidence contracts/common.md#principled-implementation Web Crypto digests the actual compressed bytes and equalBytes compares against the strongest supported witnesses; malformed metadata or mismatch rejects.
287
+ * @evidence contracts/common.md#clear-and-simple-design Authentication precedes decompression and keeps digest parsing separate from byte comparison.
288
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Historical unauthenticated metadata is explicitly supported; missing witnesses are not fabricated and weaker hashes cannot override a failing stronger witness.
289
+ * @evidence contracts/common.md#meaningful-documentation Native prose states hash precedence and unauthenticated compatibility with tag separation under the documentation skill.
290
+ */
194
291
  export async function verifyTarball(
195
292
  tgz: ArrayBuffer,
196
293
  dist: { integrity?: string; shasum?: string },
@@ -199,7 +296,10 @@ export async function verifyTarball(
199
296
  throwIfAborted(signal);
200
297
  if (dist.integrity !== undefined) {
201
298
  const candidates = parseIntegrity(dist.integrity);
202
- const strength = Math.max(...candidates.map(({ rank }) => rank));
299
+ const strength = candidates.reduce(
300
+ (maximum, { rank }) => Math.max(maximum, rank),
301
+ 0,
302
+ );
203
303
  const strongest = candidates.filter(
204
304
  (candidate) => candidate.rank === strength,
205
305
  );
@@ -231,6 +331,16 @@ export async function verifyTarball(
231
331
  }
232
332
  }
233
333
 
334
+ /**
335
+ * Expand one bounded gzip archive and extract supported text files below one
336
+ * safe archive root. Unsupported tar entry kinds are omitted; truncated bodies,
337
+ * unsafe paths and missing end markers reject the archive.
338
+ *
339
+ * @evidence contracts/common.md#principled-implementation Safe integer sizes and padded extents bound every tar slice; PAX and GNU path overrides still pass the common confinement gate before text decoding.
340
+ * @evidence contracts/common.md#clear-and-simple-design The sequential tar reader delegates gzip budgeting, path validation and header decoding to focused local helpers.
341
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported metadata paths use format rules rather than known package names; unsafe entries are rejected instead of redirected into a guessed mount.
342
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs state extraction domain and failure boundaries; confinement comments explain variable npm roots under the documentation skill.
343
+ */
234
344
  export async function unpackNpmTarball(
235
345
  tgz: ArrayBuffer,
236
346
  signal: AbortSignal | undefined,
@@ -336,12 +446,29 @@ async function gunzip(
336
446
  );
337
447
  }
338
448
 
449
+ /**
450
+ * One package's files mapped to compiler, editor URI and runtime namespaces.
451
+ *
452
+ * @evidence contracts/common.md#principled-implementation Distinct string maps carry the path conventions expected by each consumer without changing the source text.
453
+ * @evidence contracts/common.md#clear-and-simple-design The three named lanes make downstream mounting explicit and avoid repeated namespace inference.
454
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Namespace mapping follows consumer protocols rather than per-package exceptions.
455
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines the consumer lanes with tag separation under the documentation skill.
456
+ */
339
457
  export interface IMountedFiles {
340
458
  compilerFiles: Record<string, string>;
341
459
  editorLibs: Record<string, string>;
342
460
  runtimeFiles: Record<string, string>;
343
461
  }
344
462
 
463
+ /**
464
+ * Map confined relative files to consumer namespaces, selecting declarations
465
+ * for Monaco and JavaScript/JSON for the CommonJS runtime pack.
466
+ *
467
+ * @evidence contracts/common.md#principled-implementation Each source file gets a compiler key; extension predicates select appropriate editor and runtime content without coercing text.
468
+ * @evidence contracts/common.md#clear-and-simple-design One traversal constructs all three views while archive confinement stays with extraction.
469
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Extension selection applies uniformly to packages rather than synthesizing missing declarations or runtime code.
470
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines input provenance and view selection with tag separation under the documentation skill.
471
+ */
345
472
  export function mountPackageFiles(
346
473
  packageName: string,
347
474
  files: Record<string, string>,
@@ -364,6 +491,15 @@ export function mountPackageFiles(
364
491
  return { compilerFiles, editorLibs, runtimeFiles };
365
492
  }
366
493
 
494
+ /**
495
+ * Publish supported registry edges: optional dependencies override regular
496
+ * dependencies of the same name; optional peers are not installed.
497
+ *
498
+ * @evidence contracts/common.md#principled-implementation npm dependency precedence and peer optionality determine edge requirements; registry range classification excludes unsupported source transports.
499
+ * @evidence contracts/common.md#clear-and-simple-design This operation emits normalized graph edges while queue deduplication and version solving remain with the installer.
500
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Required peers remain required; unsupported non-registry sources are omitted as a declared browser-installer limitation, not silently claimed installed.
501
+ * @evidence contracts/common.md#meaningful-documentation Native prose states dependency precedence and optional peers; inline comments explain required-peer propagation under the documentation skill.
502
+ */
367
503
  export function enqueuePackageDependencies(
368
504
  packageJson: {
369
505
  dependencies?: Record<string, string>;
@@ -416,6 +552,14 @@ function isRegistryRange(range: string): boolean {
416
552
  return spec.length > 0 && encodeURIComponent(spec) === spec;
417
553
  }
418
554
 
555
+ /**
556
+ * Convert an npm identity to DefinitelyTyped spelling, flattening scoped names.
557
+ *
558
+ * @evidence contracts/common.md#principled-implementation Unscoped names gain @types/ and valid scoped names use the established scope__name convention.
559
+ * @evidence contracts/common.md#clear-and-simple-design One name mapping stays separate from whether a declaration fallback is needed.
560
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The convention applies to every package identity rather than maintaining a list of known declaration packages.
561
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines the mapping purpose with tag separation under the documentation skill.
562
+ */
419
563
  export function toTypesPackageName(packageName: string): string {
420
564
  if (!packageName.startsWith("@")) return `@types/${packageName}`;
421
565
  const [scope, name] = packageName.slice(1).split("/");
@@ -665,7 +809,15 @@ function formatByteLimit(bytes: number): string {
665
809
  return `${bytes.toLocaleString("en-US")}-byte`;
666
810
  }
667
811
 
668
- /** Validate one public npm archive byte budget before starting related work. */
812
+ /**
813
+ * Validate an archive byte budget as a positive safe integer before related
814
+ * work.
815
+ *
816
+ * @evidence contracts/common.md#principled-implementation Safe positive integers support exact byte accounting and a branded result distinguishes budgets that passed the numeric gate.
817
+ * @evidence contracts/common.md#clear-and-simple-design One validation gate serves compressed and expanded budgets without duplicating numeric policy.
818
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Validation is uniform and rejects invalid limits instead of selecting hidden fixture-specific defaults.
819
+ * @evidence contracts/common.md#meaningful-documentation Native prose states units, positivity and validation timing with tag separation under the documentation skill.
820
+ */
669
821
  export function validateNpmByteLimit(
670
822
  maxBytes: number,
671
823
  kind: "compressed" | "expanded",
@@ -48,6 +48,11 @@ const BUILTIN_MODULES = new Set([
48
48
  *
49
49
  * Returns `null` for relative paths, hash imports, URL specifiers, and Node
50
50
  * built-in modules — the caller doesn't install those from npm.
51
+ *
52
+ * @evidence contracts/common.md#principled-implementation Prefix classification removes non-registry inputs and scoped-name slicing keeps both scope and package while discarding subpaths.
53
+ * @evidence contracts/common.md#clear-and-simple-design One classifier isolates package identity from dependency scanning and installation.
54
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The built-in table is the pinned Node built-in module root list, not special cases for individual consumer packages.
55
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs define rejected specifier kinds and npm-install purpose under the documentation skill.
51
56
  */
52
57
  export function packageNameFromSpecifier(specifier: string): string | null {
53
58
  const nodePrefixed = specifier.startsWith("node:");
@@ -65,6 +70,7 @@ export function packageNameFromSpecifier(specifier: string): string | null {
65
70
  const firstSlash = bare.indexOf("/");
66
71
  if (firstSlash < 0) return null;
67
72
  if (firstSlash === 1) return null;
73
+ if (firstSlash === bare.length - 1) return null;
68
74
  const secondSlash = bare.indexOf("/", firstSlash + 1);
69
75
  if (secondSlash === firstSlash + 1) return null;
70
76
  return secondSlash < 0 ? bare : bare.slice(0, secondSlash);
@@ -9,6 +9,15 @@ interface ConsoleViewerProps {
9
9
  empty?: string;
10
10
  }
11
11
 
12
+ /**
13
+ * Render captured console calls in order, with argument values formatted as
14
+ * React text and nested containers truncated after four levels.
15
+ *
16
+ * @evidence contracts/common.md#principled-implementation Console types choose display colors and argument values become escaped React text; depth-limited container recursion handles circular values. Object entry access can invoke getters, so formatting is not a user-code isolation boundary.
17
+ * @evidence contracts/common.md#clear-and-simple-design Row layout delegates color and value formatting to local helpers while the caller owns captured-message state.
18
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Color choices are presentation constants and the depth limit is a general rendering bound, not source-specific output substitution.
19
+ * @evidence contracts/common.md#meaningful-documentation Native prose states ordering, text rendering and truncation semantics, separated from tags under the documentation skill.
20
+ */
12
21
  export function ConsoleViewer({
13
22
  messages,
14
23
  empty = "No output yet. Click Execute to run the compiled JavaScript.",
@@ -79,6 +88,7 @@ function formatValue(value: unknown, depth = 0): JSX.Element {
79
88
  return <span className="text-slate-500">undefined</span>;
80
89
  if (typeof value === "function")
81
90
  return <span className="text-slate-500">[Function]</span>;
91
+ if (depth > 4) return <span className="text-slate-500">[...]</span>;
82
92
  if (Array.isArray(value))
83
93
  return (
84
94
  <span>
@@ -101,7 +111,6 @@ function formatValue(value: unknown, depth = 0): JSX.Element {
101
111
  try {
102
112
  const entries = Object.entries(value as Record<string, unknown>);
103
113
  if (entries.length === 0) return <span>{"{}"}</span>;
104
- if (depth > 4) return <span className="text-slate-500">[...]</span>;
105
114
  return (
106
115
  <span>
107
116
  {"{"}
@@ -7,6 +7,15 @@ interface DependencyProgressModalProps {
7
7
  packages: readonly string[];
8
8
  }
9
9
 
10
+ /**
11
+ * Present current installation phase and package-count progress; null hides
12
+ * the modal and the package chips show at most eight names.
13
+ *
14
+ * @evidence contracts/common.md#principled-implementation Completed/total package counts produce a clamped display ratio and optional version joins only present package identity.
15
+ * @evidence contracts/common.md#clear-and-simple-design The component is a pure progress view; installation and cancellation stay with its owner.
16
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts The minimum visible bar and chip limit are UI presentation policy rather than fabricated install outcomes.
17
+ * @evidence contracts/common.md#meaningful-documentation Native prose defines visibility, count units and chip truncation with tag separation under the documentation skill.
18
+ */
10
19
  export function DependencyProgressModal({
11
20
  progress,
12
21
  packages,
@@ -4,6 +4,15 @@ import { useState } from "react";
4
4
 
5
5
  import type { ICompilerService } from "../structures/ICompilerService";
6
6
 
7
+ /**
8
+ * Display severity totals and a collapsible list of supplied diagnostic
9
+ * locations; expansion changes presentation without rerunning compilation.
10
+ *
11
+ * @evidence contracts/common.md#principled-implementation Counts derive from actual severity fields and location text uses the normalized one-based diagnostic contract.
12
+ * @evidence contracts/common.md#clear-and-simple-design Local expansion state owns presentation only; producers own findings and failure interpretation.
13
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Empty presentation derives from the supplied list rather than inventing a successful compiler result.
14
+ * @evidence contracts/common.md#meaningful-documentation Native prose explains totals and expansion ownership, separated from tags under the documentation skill.
15
+ */
7
16
  export function DiagnosticsPanel({
8
17
  diagnostics,
9
18
  }: {