@vscode/component-explorer-cli 0.2.1-16 → 0.2.1-160

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 (237) hide show
  1. package/README.md +36 -12
  2. package/dist/WorktreePool.d.ts +5 -4
  3. package/dist/WorktreePool.d.ts.map +1 -1
  4. package/dist/WorktreePool.js +3 -3
  5. package/dist/WorktreePool.js.map +1 -1
  6. package/dist/_virtual/_build-info.js +1 -1
  7. package/dist/browserPage.d.ts +55 -1
  8. package/dist/browserPage.d.ts.map +1 -1
  9. package/dist/browserPage.js +98 -6
  10. package/dist/browserPage.js.map +1 -1
  11. package/dist/commands/acceptCommand.d.ts +1 -1
  12. package/dist/commands/acceptCommand.d.ts.map +1 -1
  13. package/dist/commands/acceptCommand.js +21 -14
  14. package/dist/commands/acceptCommand.js.map +1 -1
  15. package/dist/commands/checkStabilityCommand.d.ts +2 -0
  16. package/dist/commands/checkStabilityCommand.d.ts.map +1 -1
  17. package/dist/commands/checkStabilityCommand.js +25 -8
  18. package/dist/commands/checkStabilityCommand.js.map +1 -1
  19. package/dist/commands/compareCommand.d.ts +1 -1
  20. package/dist/commands/compareCommand.d.ts.map +1 -1
  21. package/dist/commands/compareCommand.js +31 -22
  22. package/dist/commands/compareCommand.js.map +1 -1
  23. package/dist/commands/inputArg.d.ts +33 -0
  24. package/dist/commands/inputArg.d.ts.map +1 -0
  25. package/dist/commands/inputArg.js +103 -0
  26. package/dist/commands/inputArg.js.map +1 -0
  27. package/dist/commands/mcpCommand.d.ts.map +1 -1
  28. package/dist/commands/mcpCommand.js +12 -11
  29. package/dist/commands/mcpCommand.js.map +1 -1
  30. package/dist/commands/renderCommand.d.ts +10 -1
  31. package/dist/commands/renderCommand.d.ts.map +1 -1
  32. package/dist/commands/renderCommand.js +390 -61
  33. package/dist/commands/renderCommand.js.map +1 -1
  34. package/dist/commands/renderCommand.test.d.ts +2 -0
  35. package/dist/commands/renderCommand.test.d.ts.map +1 -0
  36. package/dist/commands/serveCommand.d.ts.map +1 -1
  37. package/dist/commands/serveCommand.js +14 -11
  38. package/dist/commands/serveCommand.js.map +1 -1
  39. package/dist/commands/serviceDiffCommitsCommand.d.ts.map +1 -1
  40. package/dist/commands/serviceDiffCommitsCommand.js +10 -9
  41. package/dist/commands/serviceDiffCommitsCommand.js.map +1 -1
  42. package/dist/commands/watchCommand.d.ts +1 -0
  43. package/dist/commands/watchCommand.d.ts.map +1 -1
  44. package/dist/commands/watchCommand.js +31 -20
  45. package/dist/commands/watchCommand.js.map +1 -1
  46. package/dist/comparison.d.ts +4 -3
  47. package/dist/comparison.d.ts.map +1 -1
  48. package/dist/comparison.js +17 -14
  49. package/dist/comparison.js.map +1 -1
  50. package/dist/component-explorer-config.schema.json +55 -2
  51. package/dist/componentExplorer.d.ts +110 -12
  52. package/dist/componentExplorer.d.ts.map +1 -1
  53. package/dist/componentExplorer.js +177 -24
  54. package/dist/componentExplorer.js.map +1 -1
  55. package/dist/config.d.ts +33 -4
  56. package/dist/config.d.ts.map +1 -1
  57. package/dist/config.js +64 -27
  58. package/dist/config.js.map +1 -1
  59. package/dist/coverage.d.ts +64 -0
  60. package/dist/coverage.d.ts.map +1 -0
  61. package/dist/coverage.js +212 -0
  62. package/dist/coverage.js.map +1 -0
  63. package/dist/daemon/DaemonService.d.ts +14 -5
  64. package/dist/daemon/DaemonService.d.ts.map +1 -1
  65. package/dist/daemon/DaemonService.js +47 -23
  66. package/dist/daemon/DaemonService.js.map +1 -1
  67. package/dist/daemon/approvalStore.d.ts +2 -1
  68. package/dist/daemon/approvalStore.d.ts.map +1 -1
  69. package/dist/daemon/approvalStore.js +4 -3
  70. package/dist/daemon/approvalStore.js.map +1 -1
  71. package/dist/daemon/inProcessClient.js +2 -2
  72. package/dist/daemon/lifecycle.d.ts +2 -1
  73. package/dist/daemon/lifecycle.d.ts.map +1 -1
  74. package/dist/daemon/lifecycle.js +1 -1
  75. package/dist/daemon/lifecycle.js.map +1 -1
  76. package/dist/daemon/pipeClient.js +8 -7
  77. package/dist/daemon/pipeClient.js.map +1 -1
  78. package/dist/daemon/pipeName.d.ts +3 -3
  79. package/dist/daemon/pipeName.d.ts.map +1 -1
  80. package/dist/daemon/pipeName.js +1 -2
  81. package/dist/daemon/pipeName.js.map +1 -1
  82. package/dist/daemon/pipeServer.js +3 -3
  83. package/dist/daemon/version.d.ts +1 -1
  84. package/dist/daemon/version.js +2 -2
  85. package/dist/dependencyInstaller.d.ts +2 -1
  86. package/dist/dependencyInstaller.d.ts.map +1 -1
  87. package/dist/dependencyInstaller.js +4 -4
  88. package/dist/dependencyInstaller.js.map +1 -1
  89. package/dist/explorerSession.d.ts +5 -1
  90. package/dist/explorerSession.d.ts.map +1 -1
  91. package/dist/explorerSession.js +24 -7
  92. package/dist/explorerSession.js.map +1 -1
  93. package/dist/git/gitCommitResolver.d.ts +2 -1
  94. package/dist/git/gitCommitResolver.d.ts.map +1 -1
  95. package/dist/git/gitCommitResolver.js +8 -8
  96. package/dist/git/gitCommitResolver.js.map +1 -1
  97. package/dist/git/gitIndexResolver.d.ts +2 -1
  98. package/dist/git/gitIndexResolver.d.ts.map +1 -1
  99. package/dist/git/gitIndexResolver.js +7 -7
  100. package/dist/git/gitIndexResolver.js.map +1 -1
  101. package/dist/git/gitService.d.ts +5 -4
  102. package/dist/git/gitService.d.ts.map +1 -1
  103. package/dist/git/gitService.js.map +1 -1
  104. package/dist/git/gitUtils.d.ts +6 -5
  105. package/dist/git/gitUtils.d.ts.map +1 -1
  106. package/dist/git/gitUtils.js +5 -4
  107. package/dist/git/gitUtils.js.map +1 -1
  108. package/dist/git/gitWorktreeManager.d.ts +15 -14
  109. package/dist/git/gitWorktreeManager.d.ts.map +1 -1
  110. package/dist/git/gitWorktreeManager.js +10 -11
  111. package/dist/git/gitWorktreeManager.js.map +1 -1
  112. package/dist/git/testUtils.d.ts +2 -1
  113. package/dist/git/testUtils.d.ts.map +1 -1
  114. package/dist/index.js +10 -1
  115. package/dist/index.js.map +1 -1
  116. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js +21 -0
  117. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js.map +1 -0
  118. package/dist/js-component-explorer/packages/common/dist/renderManifest.js +68 -0
  119. package/dist/js-component-explorer/packages/common/dist/renderManifest.js.map +1 -0
  120. package/dist/js-component-explorer/packages/semver/dist/index.js.map +1 -0
  121. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +1 -0
  122. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js.map +1 -0
  123. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js.map +1 -0
  124. package/dist/js-component-explorer/packages/simple-api/dist/express.js.map +1 -0
  125. package/dist/manifest.schema.json +44 -16
  126. package/dist/mcp/McpServer.d.ts.map +1 -1
  127. package/dist/mcp/McpServer.js +183 -101
  128. package/dist/mcp/McpServer.js.map +1 -1
  129. package/dist/path.d.ts +30 -0
  130. package/dist/path.d.ts.map +1 -0
  131. package/dist/path.js +78 -0
  132. package/dist/path.js.map +1 -0
  133. package/dist/processTree.d.ts +6 -1
  134. package/dist/processTree.d.ts.map +1 -1
  135. package/dist/processTree.js +48 -5
  136. package/dist/processTree.js.map +1 -1
  137. package/dist/renderManifest.js +1 -1
  138. package/dist/server/httpServer.d.ts +1 -1
  139. package/dist/server/httpServer.d.ts.map +1 -1
  140. package/dist/server/httpServer.js +4 -8
  141. package/dist/server/httpServer.js.map +1 -1
  142. package/dist/server/serverConfig.d.ts +23 -6
  143. package/dist/server/serverConfig.d.ts.map +1 -1
  144. package/dist/server/serverConfig.js +11 -18
  145. package/dist/server/serverConfig.js.map +1 -1
  146. package/dist/server/viteServer.d.ts +16 -1
  147. package/dist/server/viteServer.d.ts.map +1 -1
  148. package/dist/server/viteServer.js +86 -5
  149. package/dist/server/viteServer.js.map +1 -1
  150. package/dist/storage.d.ts +2 -1
  151. package/dist/storage.d.ts.map +1 -1
  152. package/dist/storage.js +1 -1
  153. package/dist/storage.js.map +1 -1
  154. package/dist/utils.d.ts +3 -30
  155. package/dist/utils.d.ts.map +1 -1
  156. package/dist/utils.js +4 -24
  157. package/dist/utils.js.map +1 -1
  158. package/dist/visualCache.d.ts +2 -1
  159. package/dist/visualCache.d.ts.map +1 -1
  160. package/dist/visualCache.js +4 -3
  161. package/dist/visualCache.js.map +1 -1
  162. package/dist/viteProjectRef.d.ts +4 -12
  163. package/dist/viteProjectRef.d.ts.map +1 -1
  164. package/dist/viteProjectRef.js +9 -17
  165. package/dist/viteProjectRef.js.map +1 -1
  166. package/dist/vscode-observables/observables/dist/disposables.js.map +1 -0
  167. package/dist/vscode-observables/observables/dist/observableInternal/base.js.map +1 -0
  168. package/dist/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +1 -0
  169. package/dist/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +1 -0
  170. package/dist/vscode-observables/observables/dist/observableInternal/debugName.js.map +1 -0
  171. package/dist/vscode-observables/observables/dist/observableInternal/index.js.map +1 -0
  172. package/dist/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +1 -0
  173. package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/debugGetDependencyGraph.js +1 -1
  174. package/dist/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +1 -0
  175. package/dist/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +1 -0
  176. package/dist/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +1 -0
  177. package/dist/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +1 -0
  178. package/dist/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +1 -0
  179. package/dist/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +1 -0
  180. package/dist/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +1 -0
  181. package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js → vscode-observables/observables/dist/observableInternal/reactions/createEffect.js} +9 -5
  182. package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffect.js.map +1 -0
  183. package/dist/{external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js → vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js} +1 -1
  184. package/dist/vscode-observables/observables/dist/observableInternal/reactions/createEffectImpl.js.map +1 -0
  185. package/dist/vscode-observables/observables/dist/observableInternal/transaction.js.map +1 -0
  186. package/dist/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +1 -0
  187. package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utilsCancellation.js +2 -2
  188. package/dist/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +1 -0
  189. package/package.json +11 -9
  190. package/dist/commands/artifactCommand.d.ts +0 -14
  191. package/dist/commands/artifactCommand.d.ts.map +0 -1
  192. package/dist/external/vscode-observables/observables/dist/disposables.js.map +0 -1
  193. package/dist/external/vscode-observables/observables/dist/observableInternal/base.js.map +0 -1
  194. package/dist/external/vscode-observables/observables/dist/observableInternal/commonFacade/deps.js.map +0 -1
  195. package/dist/external/vscode-observables/observables/dist/observableInternal/debugLocation.js.map +0 -1
  196. package/dist/external/vscode-observables/observables/dist/observableInternal/debugName.js.map +0 -1
  197. package/dist/external/vscode-observables/observables/dist/observableInternal/index.js.map +0 -1
  198. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/consoleObservableLogger.js.map +0 -1
  199. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/debugGetDependencyGraph.js.map +0 -1
  200. package/dist/external/vscode-observables/observables/dist/observableInternal/logging/logging.js.map +0 -1
  201. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/baseObservable.js.map +0 -1
  202. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derived.js.map +0 -1
  203. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/derivedImpl.js.map +0 -1
  204. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js.map +0 -1
  205. package/dist/external/vscode-observables/observables/dist/observableInternal/observables/observableValue.js.map +0 -1
  206. package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js.map +0 -1
  207. package/dist/external/vscode-observables/observables/dist/observableInternal/reactions/autorunImpl.js.map +0 -1
  208. package/dist/external/vscode-observables/observables/dist/observableInternal/transaction.js.map +0 -1
  209. package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utils.js.map +0 -1
  210. package/dist/external/vscode-observables/observables/dist/observableInternal/utils/utilsCancellation.js.map +0 -1
  211. package/dist/packages/common/dist/renderManifest.js +0 -46
  212. package/dist/packages/common/dist/renderManifest.js.map +0 -1
  213. package/dist/packages/semver/dist/index.js.map +0 -1
  214. package/dist/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +0 -1
  215. package/dist/packages/simple-api/dist/chunk-TTRCY65Z.js.map +0 -1
  216. package/dist/packages/simple-api/dist/chunk-WNXMRXWV.js.map +0 -1
  217. package/dist/packages/simple-api/dist/express.js.map +0 -1
  218. /package/dist/{packages → js-component-explorer/packages}/semver/dist/index.js +0 -0
  219. /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-FJ7AVNQE.js +0 -0
  220. /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-TTRCY65Z.js +0 -0
  221. /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/chunk-WNXMRXWV.js +0 -0
  222. /package/dist/{packages → js-component-explorer/packages}/simple-api/dist/express.js +0 -0
  223. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/disposables.js +0 -0
  224. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/base.js +0 -0
  225. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/commonFacade/deps.js +0 -0
  226. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugLocation.js +0 -0
  227. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/debugName.js +0 -0
  228. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/index.js +0 -0
  229. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/consoleObservableLogger.js +0 -0
  230. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/logging/logging.js +0 -0
  231. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/baseObservable.js +0 -0
  232. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derived.js +0 -0
  233. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/derivedImpl.js +0 -0
  234. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableFromEvent.js +0 -0
  235. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/observables/observableValue.js +0 -0
  236. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/transaction.js +0 -0
  237. /package/dist/{external/vscode-observables → vscode-observables}/observables/dist/observableInternal/utils/utils.js +0 -0
@@ -1,14 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import '../external/vscode-observables/observables/dist/observableInternal/index.js';
5
- import { Disposable } from '../external/vscode-observables/observables/dist/disposables.js';
6
- import '../external/vscode-observables/observables/dist/observableInternal/debugLocation.js';
7
- import { autorun } from '../external/vscode-observables/observables/dist/observableInternal/reactions/autorun.js';
8
- import '../external/vscode-observables/observables/dist/observableInternal/observables/derived.js';
9
- import '../external/vscode-observables/observables/dist/observableInternal/utils/utils.js';
10
- import '../external/vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
11
- import { buildExplorerUrl } from '../utils.js';
4
+ import '../vscode-observables/observables/dist/observableInternal/index.js';
5
+ import { Disposable } from '../vscode-observables/observables/dist/disposables.js';
6
+ import '../vscode-observables/observables/dist/observableInternal/debugLocation.js';
7
+ import { autorun } from '../vscode-observables/observables/dist/observableInternal/reactions/createEffect.js';
8
+ import '../vscode-observables/observables/dist/observableInternal/observables/derived.js';
9
+ import '../vscode-observables/observables/dist/observableInternal/utils/utils.js';
10
+ import '../vscode-observables/observables/dist/observableInternal/observables/observableFromEvent.js';
11
+ import '../js-component-explorer/packages/common/dist/renderManifest.js';
12
+ import { buildExplorerUrl } from '../js-component-explorer/packages/common/dist/explorerUrl.js';
13
+ import { EXPLORER_ROUTE } from '../utils.js';
12
14
  import { TaskManager } from './TaskManager.js';
13
15
  import { isPipeConnectionError } from '../daemon/pipeClient.js';
14
16
 
@@ -179,23 +181,23 @@ class ComponentExplorerMcpServer extends Disposable {
179
181
  }
180
182
  }
181
183
  // -- Tool registration ---------------------------------------------------
182
- _filterFixtures(allFixtures, fixtureIdPattern, labelPattern) {
184
+ _filterFixtures(allFixtures, fixtureIdRegexStr, labelRegexStr) {
183
185
  let fixtureIdRegex;
184
- if (fixtureIdPattern) {
186
+ if (fixtureIdRegexStr) {
185
187
  try {
186
- fixtureIdRegex = new RegExp(fixtureIdPattern);
188
+ fixtureIdRegex = new RegExp(fixtureIdRegexStr);
187
189
  }
188
190
  catch {
189
- return { error: `Error: Invalid fixtureIdPattern: ${fixtureIdPattern}` };
191
+ return { error: `Error: Invalid fixtureIdRegex: ${fixtureIdRegexStr}` };
190
192
  }
191
193
  }
192
194
  let labelRegex;
193
- if (labelPattern) {
195
+ if (labelRegexStr) {
194
196
  try {
195
- labelRegex = new RegExp(labelPattern);
197
+ labelRegex = new RegExp(labelRegexStr);
196
198
  }
197
199
  catch {
198
- return { error: `Error: Invalid labelPattern: ${labelPattern}` };
200
+ return { error: `Error: Invalid labelRegex: ${labelRegexStr}` };
199
201
  }
200
202
  }
201
203
  return {
@@ -233,12 +235,12 @@ class ComponentExplorerMcpServer extends Disposable {
233
235
  _registerListFixtures() {
234
236
  this._mcp.registerTool('list_fixtures', {
235
237
  description: 'List all fixtures from a session',
236
- inputSchema: {
237
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
238
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
238
+ inputSchema: z.strictObject({
239
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
240
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
239
241
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
240
242
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
241
- },
243
+ }),
242
244
  annotations: { readOnlyHint: true },
243
245
  }, async (args) => this._withDaemon(async (daemon) => {
244
246
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -246,7 +248,7 @@ class ComponentExplorerMcpServer extends Disposable {
246
248
  return this._withSourceTreeRetry(daemon, async () => {
247
249
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
248
250
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
249
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
251
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
250
252
  if ('error' in filtered) {
251
253
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
252
254
  }
@@ -262,13 +264,22 @@ class ComponentExplorerMcpServer extends Disposable {
262
264
  _registerScreenshot() {
263
265
  this._mcp.registerTool('screenshot', {
264
266
  description: 'Take a screenshot of a single fixture. ' +
265
- 'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ',
266
- inputSchema: {
267
+ 'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ' +
268
+ 'By default the fixture stays mounted after the screenshot (so a follow-up `evaluate_js` call can still operate on it); ' +
269
+ 'pass `disposeAfter: true` to dispose immediately and surface any teardown errors as `currentDispose` in the result. ' +
270
+ 'Errors / events captured while disposing the previously-mounted fixture (the implicit dispose at the start of every render) are reported as `previousDispose`.',
271
+ inputSchema: z.strictObject({
267
272
  fixtureId: z.string().describe('The fixture ID'),
268
273
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
269
274
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
270
275
  stabilityCheck: z.boolean().optional().describe('If true, takes three screenshots (at render time, ~500ms, ~3000ms) to check visual stability. Expensive (~3s extra) — only use it when you need to verify rendering stability..'),
271
- },
276
+ includeConsoleLog: z.boolean().optional().describe('If true, include console.log events in the returned events array. Defaults to false — console.log entries are filtered out to reduce noise.'),
277
+ input: z.unknown().optional().describe('Arbitrary JSON-serializable data passed to the fixture as `RenderContext.input`. ' +
278
+ 'The fixture decides how to interpret it (e.g. theme switch, scenario selection, mock data).'),
279
+ disposeAfter: z.boolean().optional().describe('If true, dispose the fixture after taking the screenshot. ' +
280
+ 'Default: false — the fixture stays mounted until the next render, so `evaluate_js` can still operate on it. ' +
281
+ 'Use this when validating fixture teardown: dispose errors / events appear in `currentDispose`.'),
282
+ }),
272
283
  annotations: { readOnlyHint: true },
273
284
  }, async (args) => this._withDaemon(async (daemon) => {
274
285
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -282,6 +293,8 @@ class ComponentExplorerMcpServer extends Disposable {
282
293
  sourceTreeId,
283
294
  includeImage: true,
284
295
  stabilityCheck: args.stabilityCheck,
296
+ input: args.input,
297
+ disposeAfter: args.disposeAfter,
285
298
  });
286
299
  const r = result;
287
300
  this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
@@ -289,6 +302,7 @@ class ComponentExplorerMcpServer extends Disposable {
289
302
  if (r.hash && r.image) {
290
303
  this._imageLru.put(r.hash, r.image);
291
304
  }
305
+ const filterEvents = (events) => args.includeConsoleLog ? events : events.filter(e => e.type !== 'console.log');
292
306
  const info = {
293
307
  hash: r.hash,
294
308
  sourceTreeId: r.sourceTreeId,
@@ -300,14 +314,37 @@ class ComponentExplorerMcpServer extends Disposable {
300
314
  info.error = r.error;
301
315
  }
302
316
  if (r.events && r.events.length > 0) {
303
- info.events = r.events;
317
+ const filtered = filterEvents(r.events);
318
+ if (filtered.length > 0) {
319
+ info.events = filtered;
320
+ }
304
321
  }
305
- if (r.resultData !== undefined) {
306
- info.resultData = r.resultData;
322
+ if (r.output !== undefined) {
323
+ info.output = r.output;
307
324
  }
308
325
  if (r.isStable !== undefined) {
309
326
  info.isStable = r.isStable;
310
327
  }
328
+ if (r.previousDispose) {
329
+ const filtered = filterEvents(r.previousDispose.events);
330
+ if (r.previousDispose.hasError || r.previousDispose.errors.length > 0 || filtered.length > 0) {
331
+ info.previousDispose = {
332
+ hasError: r.previousDispose.hasError,
333
+ errors: r.previousDispose.errors,
334
+ events: filtered,
335
+ };
336
+ }
337
+ }
338
+ if (r.currentDispose) {
339
+ const filtered = filterEvents(r.currentDispose.events);
340
+ if (r.currentDispose.hasError || r.currentDispose.errors.length > 0 || filtered.length > 0) {
341
+ info.currentDispose = {
342
+ hasError: r.currentDispose.hasError,
343
+ errors: r.currentDispose.errors,
344
+ events: filtered,
345
+ };
346
+ }
347
+ }
311
348
  // Visual review status
312
349
  if (r.hash) {
313
350
  try {
@@ -351,13 +388,13 @@ class ComponentExplorerMcpServer extends Disposable {
351
388
  _registerCompareScreenshot() {
352
389
  const tool = this._mcp.registerTool('compare_screenshot', {
353
390
  description: 'Compare a fixture\'s screenshot across two sessions (e.g. baseline vs current)',
354
- inputSchema: {
391
+ inputSchema: z.strictObject({
355
392
  fixtureId: z.string().describe('The fixture ID'),
356
393
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
357
394
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
358
395
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
359
396
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
360
- },
397
+ }),
361
398
  annotations: { readOnlyHint: true },
362
399
  }, async (args) => this._withDaemon(async (daemon) => {
363
400
  const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
@@ -391,8 +428,8 @@ class ComponentExplorerMcpServer extends Disposable {
391
428
  if (r.baselineEvents && r.baselineEvents.length > 0) {
392
429
  info.baselineEvents = r.baselineEvents;
393
430
  }
394
- if (r.baselineResultData !== undefined) {
395
- info.baselineResultData = r.baselineResultData;
431
+ if (r.baselineOutput !== undefined) {
432
+ info.baselineOutput = r.baselineOutput;
396
433
  }
397
434
  if (r.currentHasError) {
398
435
  info.currentHasError = true;
@@ -403,8 +440,8 @@ class ComponentExplorerMcpServer extends Disposable {
403
440
  if (r.currentEvents && r.currentEvents.length > 0) {
404
441
  info.currentEvents = r.currentEvents;
405
442
  }
406
- if (r.currentResultData !== undefined) {
407
- info.currentResultData = r.currentResultData;
443
+ if (r.currentOutput !== undefined) {
444
+ info.currentOutput = r.currentOutput;
408
445
  }
409
446
  if (r.approval) {
410
447
  info.approval = r.approval;
@@ -427,12 +464,12 @@ class ComponentExplorerMcpServer extends Disposable {
427
464
  _registerApproveDiff() {
428
465
  const tool = this._mcp.registerTool('approve_diff', {
429
466
  description: 'Approve a visual diff so it won\'t require re-inspection next time',
430
- inputSchema: {
467
+ inputSchema: z.strictObject({
431
468
  fixtureId: z.string(),
432
469
  originalHash: z.string(),
433
470
  modifiedHash: z.string(),
434
471
  comment: z.string().describe('Reason for approving this diff'),
435
- },
472
+ }),
436
473
  }, async (args) => this._withDaemon(async (daemon) => {
437
474
  await daemon.methods.approvals.approve(args);
438
475
  return {
@@ -450,14 +487,14 @@ class ComponentExplorerMcpServer extends Disposable {
450
487
  description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
451
488
  'You must take a screenshot first and pass the resulting hash. ' +
452
489
  'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
453
- inputSchema: {
490
+ inputSchema: z.strictObject({
454
491
  fixtureId: z.string().describe('The fixture ID'),
455
492
  screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
456
493
  verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
457
494
  comment: z.string().describe('Reason for the verdict'),
458
495
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
459
496
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
460
- },
497
+ }),
461
498
  }, async (args) => this._withDaemon(async (daemon) => {
462
499
  const sessionName = args.sessionName ?? this._defaultSessionName();
463
500
  this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
@@ -485,13 +522,15 @@ class ComponentExplorerMcpServer extends Disposable {
485
522
  }
486
523
  else {
487
524
  return {
488
- content: [{ type: 'text', text: JSON.stringify({
525
+ content: [{
526
+ type: 'text', text: JSON.stringify({
489
527
  fixtureId: args.fixtureId,
490
528
  verdict: 'rejected',
491
529
  comment: args.comment,
492
530
  screenshotHash: args.screenshotHash,
493
531
  expectedVisualDescriptions: fixture.expectedVisualDescriptions,
494
- }, null, 2) }],
532
+ }, null, 2)
533
+ }],
495
534
  };
496
535
  }
497
536
  });
@@ -501,12 +540,12 @@ class ComponentExplorerMcpServer extends Disposable {
501
540
  this._mcp.registerTool('check_visuals', {
502
541
  description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
503
542
  'Returns lists of approved, needs-review, and no-expectation fixtures.',
504
- inputSchema: {
505
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
506
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label'),
543
+ inputSchema: z.strictObject({
544
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
545
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label'),
507
546
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
508
547
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
509
- },
548
+ }),
510
549
  annotations: { readOnlyHint: true },
511
550
  }, async (args) => this._withDaemon(async (daemon) => {
512
551
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -517,7 +556,7 @@ class ComponentExplorerMcpServer extends Disposable {
517
556
  if (listResult.loadError) {
518
557
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
519
558
  }
520
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
559
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
521
560
  if ('error' in filtered) {
522
561
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
523
562
  }
@@ -575,12 +614,12 @@ class ComponentExplorerMcpServer extends Disposable {
575
614
  'Returns the expression result as JSON. The expression can return a Promise (it will be awaited). ' +
576
615
  'Use this to inspect DOM state, computed styles, element dimensions, or component output. ' +
577
616
  'Do NOT use this to modify the DOM — this tool is for read-only inspection and debugging only.',
578
- inputSchema: {
617
+ inputSchema: z.strictObject({
579
618
  expression: z.string().describe('JavaScript expression to evaluate. Can return a Promise. The result must be JSON-serializable.'),
580
619
  fixtureId: z.string().optional().describe('If provided, renders this fixture before evaluating the expression'),
581
620
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
582
621
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
583
- },
622
+ }),
584
623
  }, async (args) => this._withDaemon(async (daemon) => {
585
624
  const sessionName = args.sessionName ?? this._defaultSessionName();
586
625
  this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
@@ -612,9 +651,9 @@ class ComponentExplorerMcpServer extends Disposable {
612
651
  description: 'Force-reload the browser page used for rendering fixtures. ' +
613
652
  'Only use this as a last resort if screenshots or evaluate_js return stale/broken results ' +
614
653
  'that persist after source changes. Normal HMR updates should handle most cases automatically.',
615
- inputSchema: {
654
+ inputSchema: z.strictObject({
616
655
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
617
- },
656
+ }),
618
657
  annotations: { destructiveHint: true },
619
658
  }, async (args) => this._withDaemon(async (daemon) => {
620
659
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -632,9 +671,9 @@ class ComponentExplorerMcpServer extends Disposable {
632
671
  _registerWatchAdd() {
633
672
  this._mcp.registerTool('watch_add', {
634
673
  description: 'Add fixtures to the watch list. Watched fixtures are automatically re-screenshotted when source changes.',
635
- inputSchema: {
674
+ inputSchema: z.strictObject({
636
675
  fixtureIds: z.array(z.string()).describe('Fixture IDs to add'),
637
- },
676
+ }),
638
677
  }, async (args) => {
639
678
  this._watchList.add(args.fixtureIds);
640
679
  return {
@@ -648,9 +687,9 @@ class ComponentExplorerMcpServer extends Disposable {
648
687
  _registerWatchRemove() {
649
688
  this._mcp.registerTool('watch_remove', {
650
689
  description: 'Remove fixtures from the watch list',
651
- inputSchema: {
690
+ inputSchema: z.strictObject({
652
691
  fixtureIds: z.array(z.string()).describe('Fixture IDs to remove'),
653
- },
692
+ }),
654
693
  }, async (args) => {
655
694
  this._watchList.remove(args.fixtureIds);
656
695
  return {
@@ -664,9 +703,9 @@ class ComponentExplorerMcpServer extends Disposable {
664
703
  _registerWatchSet() {
665
704
  this._mcp.registerTool('watch_set', {
666
705
  description: 'Replace the watch list entirely',
667
- inputSchema: {
706
+ inputSchema: z.strictObject({
668
707
  fixtureIds: z.array(z.string()).describe('Fixture IDs to watch'),
669
- },
708
+ }),
670
709
  }, async (args) => {
671
710
  this._watchList.set(args.fixtureIds);
672
711
  return {
@@ -680,12 +719,12 @@ class ComponentExplorerMcpServer extends Disposable {
680
719
  _registerWatchCompare() {
681
720
  const tool = this._mcp.registerTool('watch_compare', {
682
721
  description: 'Compare all watched fixtures across two sessions. Takes fresh screenshots from both sessions and reports which fixtures differ.',
683
- inputSchema: {
722
+ inputSchema: z.strictObject({
684
723
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
685
724
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
686
725
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
687
726
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
688
- },
727
+ }),
689
728
  annotations: { readOnlyHint: true },
690
729
  }, async (args) => this._withDaemon(async (daemon) => {
691
730
  const ids = [...this._watchList.fixtureIds];
@@ -750,10 +789,10 @@ class ComponentExplorerMcpServer extends Disposable {
750
789
  'Pass the sourceTreeId you already observed — resolves immediately if it already differs, ' +
751
790
  'otherwise waits for a source-change or ref-change event. ' +
752
791
  'If fixtures are on the watch list, automatically re-screenshots them and reports which changed.',
753
- inputSchema: {
792
+ inputSchema: z.strictObject({
754
793
  sourceTreeId: z.string().describe('The sourceTreeId the client currently knows about. The call resolves once the source tree differs from this value.'),
755
794
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
756
- },
795
+ }),
757
796
  annotations: { readOnlyHint: true },
758
797
  }, async (args) => this._withDaemon(async (daemon) => {
759
798
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -855,9 +894,9 @@ class ComponentExplorerMcpServer extends Disposable {
855
894
  this._mcp.registerTool('restart_session', {
856
895
  description: 'Restart a session by disposing its browser page and dev server, then recreating them. ' +
857
896
  'Use this when a session appears stuck (e.g. after a timeout).',
858
- inputSchema: {
897
+ inputSchema: z.strictObject({
859
898
  sessionName: z.string().optional().describe('Session name to restart (defaults to first session)'),
860
- },
899
+ }),
861
900
  annotations: { destructiveHint: true },
862
901
  }, async (args) => this._withDaemon(async (daemon) => {
863
902
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -875,10 +914,10 @@ class ComponentExplorerMcpServer extends Disposable {
875
914
  'The ref can be a branch name, tag, commit SHA, or the special value "INDEX" to snapshot staged changes. ' +
876
915
  'The daemon allocates a reusable worktree slot from a fixed pool (max configured in component-explorer.json). ' +
877
916
  'Returns the updated session list on success.',
878
- inputSchema: {
917
+ inputSchema: z.strictObject({
879
918
  name: z.string().describe('Unique session name (e.g. "baseline", "bisect")'),
880
919
  ref: z.string().describe('Git ref: branch, tag, commit SHA, or "INDEX" for staged changes'),
881
- },
920
+ }),
882
921
  }, async (args) => this._withDaemon(async (daemon) => {
883
922
  this._log('info', { type: 'tool-call', tool: 'open_session', name: args.name, ref: args.ref });
884
923
  const result = await daemon.methods.openSession({ name: args.name, ref: args.ref });
@@ -896,9 +935,9 @@ class ComponentExplorerMcpServer extends Disposable {
896
935
  this._mcp.registerTool('close_session', {
897
936
  description: 'Close a dynamic worktree session and release its worktree slot back to the pool. ' +
898
937
  'Cannot close static sessions configured in component-explorer.json.',
899
- inputSchema: {
938
+ inputSchema: z.strictObject({
900
939
  name: z.string().describe('Session name to close'),
901
- },
940
+ }),
902
941
  annotations: { destructiveHint: true },
903
942
  }, async (args) => this._withDaemon(async (daemon) => {
904
943
  this._log('info', { type: 'tool-call', tool: 'close_session', name: args.name });
@@ -919,10 +958,10 @@ class ComponentExplorerMcpServer extends Disposable {
919
958
  'The worktree is checked out to the new ref and Vite\'s HMR handles the incremental update (no server restart). ' +
920
959
  'Fails if the worktree has uncommitted changes — the error will list the dirty files. ' +
921
960
  'The ref can be a branch, tag, commit SHA, or "INDEX" for staged changes.',
922
- inputSchema: {
961
+ inputSchema: z.strictObject({
923
962
  name: z.string().describe('Session name to update'),
924
963
  ref: z.string().describe('New git ref: branch, tag, commit SHA, or "INDEX"'),
925
- },
964
+ }),
926
965
  }, async (args) => this._withDaemon(async (daemon) => {
927
966
  this._log('info', { type: 'tool-call', tool: 'update_session_ref', name: args.name, ref: args.ref });
928
967
  const result = await daemon.methods.updateSessionRef({ name: args.name, ref: args.ref });
@@ -939,11 +978,11 @@ class ComponentExplorerMcpServer extends Disposable {
939
978
  this._mcp.registerTool('get_url', {
940
979
  description: 'Get URL for viewing fixtures. Returns the full Component Explorer UI by default. ' +
941
980
  'Use `embedded: true` for a minimal single-fixture view (requires fixtureId).',
942
- inputSchema: {
981
+ inputSchema: z.strictObject({
943
982
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
944
983
  fixtureId: z.string().optional().describe('Specific fixture ID to view. In explorer mode, pre-selects this fixture. In embedded mode (required), shows only this fixture.'),
945
984
  embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
946
- },
985
+ }),
947
986
  annotations: { readOnlyHint: true },
948
987
  }, async (args) => this._withDaemon(async (daemon) => {
949
988
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -984,6 +1023,7 @@ class ComponentExplorerMcpServer extends Disposable {
984
1023
  const mode = args.embedded ? 'embedded' : undefined;
985
1024
  const url = buildExplorerUrl({
986
1025
  baseUrl,
1026
+ pathname: EXPLORER_ROUTE,
987
1027
  mode,
988
1028
  fixtureId: args.fixtureId,
989
1029
  });
@@ -1002,32 +1042,45 @@ class ComponentExplorerMcpServer extends Disposable {
1002
1042
  }
1003
1043
  _registerCheckFixtureErrors() {
1004
1044
  this._mcp.registerTool('check_fixture_errors', {
1005
- description: 'Render fixtures and check for errors. Returns each fixture\'s error status. ' +
1045
+ description: 'Render fixtures and check for errors. Each fixture is rendered and then disposed; ' +
1046
+ 'render exceptions appear as `error`, dispose exceptions as `disposeError`, and console / window ' +
1047
+ 'events from both phases are collected in `events` (each tagged with `phase: "render" | "dispose"`). ' +
1048
+ 'A fixture is considered errored if either the render or the dispose phase reports `hasError` ' +
1049
+ '(i.e. an exception was thrown or an event of type `console.error` / `window.error` / `window.unhandledrejection` was captured). ' +
1050
+ 'Only errored fixtures are listed; successful fixtures are summarised via the `summary` counts. ' +
1006
1051
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task.',
1007
- inputSchema: {
1008
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1009
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1052
+ inputSchema: z.strictObject({
1053
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1054
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1010
1055
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1011
1056
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1012
- },
1057
+ reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
1058
+ 'Default: false (the page is only reloaded after a fixture errors).'),
1059
+ input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
1060
+ 'See `screenshot` for details.'),
1061
+ }),
1013
1062
  annotations: { readOnlyHint: true },
1014
1063
  }, async (args) => this._withDaemon(async (daemon) => {
1015
1064
  const sessionName = args.sessionName ?? this._defaultSessionName();
1016
- this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1065
+ this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1017
1066
  return this._withSourceTreeRetry(daemon, async () => {
1018
1067
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1019
1068
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1020
1069
  if (listResult.loadError) {
1021
1070
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1022
1071
  }
1023
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1072
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1024
1073
  if ('error' in filtered) {
1025
1074
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1026
1075
  }
1027
1076
  const fixtures = filtered.fixtures;
1028
1077
  const task = this._taskManager.startTask(async (report, signal) => {
1029
- const results = [];
1030
- report({ completed: 0, total: fixtures.length, partialResult: results });
1078
+ // Only errored fixtures are stored/reported; successful fixtures would just bloat the response.
1079
+ // The summary below still surfaces the total/ok/errored counts.
1080
+ const erroredResults = [];
1081
+ let okCount = 0;
1082
+ let processed = 0;
1083
+ report({ completed: 0, total: fixtures.length, partialResult: erroredResults });
1031
1084
  for (let i = 0; i < fixtures.length; i++) {
1032
1085
  if (signal.aborted) {
1033
1086
  break;
@@ -1039,30 +1092,52 @@ class ComponentExplorerMcpServer extends Disposable {
1039
1092
  sessionName,
1040
1093
  sourceTreeId,
1041
1094
  includeImage: false,
1095
+ // Skip the reload before the very first fixture — the page is already fresh.
1096
+ reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
1097
+ input: args.input,
1098
+ // Always dispose after rendering so teardown errors / events are observable for every fixture
1099
+ // (including the last one). Because we explicitly dispose here, the implicit
1100
+ // `previousDispose` at the start of the next render is a no-op and need not be inspected.
1101
+ disposeAfter: true,
1042
1102
  });
1043
1103
  const r = result;
1044
- const entry = { fixtureId: fixture.fixtureId, hasError: r.hasError };
1045
- if (r.error) {
1046
- entry.error = r.error;
1104
+ const renderEvents = (r.events ?? []).map(e => ({ phase: 'render', ...e }));
1105
+ const disposeEvents = (r.currentDispose?.events ?? []).map(e => ({ phase: 'dispose', ...e }));
1106
+ const events = [...renderEvents, ...disposeEvents];
1107
+ const disposeError = r.currentDispose?.errors[0];
1108
+ // Trust the upstream `hasError` flags so this stays in sync with the render-report rules
1109
+ // (exception OR an event of type console.error / window.error / window.unhandledrejection).
1110
+ const hasError = r.hasError || !!r.currentDispose?.hasError;
1111
+ if (hasError) {
1112
+ const entry = { fixtureId: fixture.fixtureId, hasError: true };
1113
+ if (r.error) {
1114
+ entry.error = r.error;
1115
+ }
1116
+ if (disposeError) {
1117
+ entry.disposeError = disposeError;
1118
+ }
1119
+ if (events.length > 0) {
1120
+ entry.events = events;
1121
+ }
1122
+ erroredResults.push(entry);
1047
1123
  }
1048
- if (r.events && r.events.length > 0) {
1049
- entry.events = r.events;
1124
+ else {
1125
+ okCount++;
1050
1126
  }
1051
- results.push(entry);
1052
1127
  }
1053
1128
  catch (e) {
1054
- results.push({
1129
+ erroredResults.push({
1055
1130
  fixtureId: fixture.fixtureId,
1056
1131
  hasError: true,
1057
1132
  error: { message: e instanceof Error ? e.message : String(e) },
1058
1133
  });
1059
1134
  }
1060
- report({ completed: i + 1, total: fixtures.length, partialResult: results });
1135
+ processed++;
1136
+ report({ completed: i + 1, total: fixtures.length, partialResult: erroredResults });
1061
1137
  }
1062
- const withErrors = results.filter(r => r.hasError);
1063
1138
  return {
1064
- fixtures: results,
1065
- summary: { total: results.length, ok: results.length - withErrors.length, errored: withErrors.length },
1139
+ fixtures: erroredResults,
1140
+ summary: { total: processed, ok: okCount, errored: erroredResults.length },
1066
1141
  };
1067
1142
  });
1068
1143
  const waited = await this._taskManager.waitForTask(task.id, 10_000);
@@ -1096,23 +1171,27 @@ class ComponentExplorerMcpServer extends Disposable {
1096
1171
  description: 'Check rendering stability of fixtures. Each fixture is unmounted, re-mounted, and screenshotted 3 times (~3s per fixture). ' +
1097
1172
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task. ' +
1098
1173
  'When returning a taskId, includes partial results collected so far.',
1099
- inputSchema: {
1100
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1101
- labelPattern: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1174
+ inputSchema: z.strictObject({
1175
+ fixtureIdRegex: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1176
+ labelRegex: z.string().optional().describe('RegExp to filter fixtures by label (matched against inherited labels)'),
1102
1177
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1103
1178
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1104
- },
1179
+ reloadBetweenFixtures: z.boolean().optional().describe('If true, reload the browser page between fixtures. Provides cleaner isolation but is slower. ' +
1180
+ 'Default: false (the page is only reloaded after a fixture errors).'),
1181
+ input: z.unknown().optional().describe('Arbitrary JSON object passed to every fixture as `RenderContext.input`. ' +
1182
+ 'See `screenshot` for details.'),
1183
+ }),
1105
1184
  annotations: { readOnlyHint: true },
1106
1185
  }, async (args) => this._withDaemon(async (daemon) => {
1107
1186
  const sessionName = args.sessionName ?? this._defaultSessionName();
1108
- this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1187
+ this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1109
1188
  return this._withSourceTreeRetry(daemon, async () => {
1110
1189
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1111
1190
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1112
1191
  if (listResult.loadError) {
1113
1192
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1114
1193
  }
1115
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1194
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1116
1195
  if ('error' in filtered) {
1117
1196
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1118
1197
  }
@@ -1133,6 +1212,9 @@ class ComponentExplorerMcpServer extends Disposable {
1133
1212
  sourceTreeId,
1134
1213
  includeImage: false,
1135
1214
  stabilityCheck: true,
1215
+ // Skip the reload before the very first fixture — the page is already fresh.
1216
+ reloadBeforeRender: args.reloadBetweenFixtures && i > 0,
1217
+ input: args.input,
1136
1218
  });
1137
1219
  const r = result;
1138
1220
  results.push({
@@ -1177,9 +1259,9 @@ class ComponentExplorerMcpServer extends Disposable {
1177
1259
  _registerCheckTask() {
1178
1260
  this._mcp.registerTool('check_task', {
1179
1261
  description: 'Check on a running task. Waits up to ~2s for completion; if still running, returns progress and new results since last check.',
1180
- inputSchema: {
1262
+ inputSchema: z.strictObject({
1181
1263
  taskId: z.string().describe('The task ID returned by a previous tool call'),
1182
- },
1264
+ }),
1183
1265
  annotations: { readOnlyHint: true },
1184
1266
  }, async (args) => {
1185
1267
  const waited = await this._taskManager.waitForTask(args.taskId, 2_000);
@@ -1227,9 +1309,9 @@ class ComponentExplorerMcpServer extends Disposable {
1227
1309
  _registerCancelTask() {
1228
1310
  this._mcp.registerTool('cancel_task', {
1229
1311
  description: 'Cancel a running task',
1230
- inputSchema: {
1312
+ inputSchema: z.strictObject({
1231
1313
  taskId: z.string().describe('The task ID to cancel'),
1232
- },
1314
+ }),
1233
1315
  }, async (args) => {
1234
1316
  const task = this._taskManager.getTask(args.taskId);
1235
1317
  if (!task) {
@@ -1249,9 +1331,9 @@ class ComponentExplorerMcpServer extends Disposable {
1249
1331
  description: 'Retrieve a recently-taken screenshot image by its hash. ' +
1250
1332
  'Keeps the last ~10 images in an LRU cache. ' +
1251
1333
  'Useful for debugging when screenshot hashes behave unexpectedly.',
1252
- inputSchema: {
1334
+ inputSchema: z.strictObject({
1253
1335
  hash: z.string().describe('The screenshot hash to look up'),
1254
- },
1336
+ }),
1255
1337
  annotations: { readOnlyHint: true },
1256
1338
  }, async (args) => {
1257
1339
  const image = this._imageLru.get(args.hash);
@@ -1275,9 +1357,9 @@ class ComponentExplorerMcpServer extends Disposable {
1275
1357
  'Only use this tool when the user explicitly asks to show or hide the browser. ' +
1276
1358
  'Do not call this tool automatically or as part of other workflows. ' +
1277
1359
  'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
1278
- inputSchema: {
1360
+ inputSchema: z.strictObject({
1279
1361
  visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
1280
- },
1362
+ }),
1281
1363
  annotations: { destructiveHint: true },
1282
1364
  }, async (args) => this._withDaemon(async (daemon) => {
1283
1365
  await daemon.methods.setBrowserVisibility({ visible: args.visible });