@vscode/component-explorer-cli 0.2.1-8 → 0.2.1-80

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 +6 -6
  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 +50 -0
  8. package/dist/browserPage.d.ts.map +1 -1
  9. package/dist/browserPage.js +78 -0
  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 +0 -2
  28. package/dist/commands/mcpCommand.d.ts.map +1 -1
  29. package/dist/commands/mcpCommand.js +143 -75
  30. package/dist/commands/mcpCommand.js.map +1 -1
  31. package/dist/commands/renderCommand.d.ts +9 -1
  32. package/dist/commands/renderCommand.d.ts.map +1 -1
  33. package/dist/commands/renderCommand.js +313 -28
  34. package/dist/commands/renderCommand.js.map +1 -1
  35. package/dist/commands/serveCommand.d.ts.map +1 -1
  36. package/dist/commands/serveCommand.js +14 -11
  37. package/dist/commands/serveCommand.js.map +1 -1
  38. package/dist/commands/serviceDiffCommitsCommand.d.ts.map +1 -1
  39. package/dist/commands/serviceDiffCommitsCommand.js +10 -9
  40. package/dist/commands/serviceDiffCommitsCommand.js.map +1 -1
  41. package/dist/commands/watchCommand.d.ts +1 -0
  42. package/dist/commands/watchCommand.d.ts.map +1 -1
  43. package/dist/commands/watchCommand.js +31 -20
  44. package/dist/commands/watchCommand.js.map +1 -1
  45. package/dist/comparison.d.ts +4 -3
  46. package/dist/comparison.d.ts.map +1 -1
  47. package/dist/comparison.js +17 -14
  48. package/dist/comparison.js.map +1 -1
  49. package/dist/component-explorer-config.schema.json +55 -2
  50. package/dist/componentExplorer.d.ts +106 -11
  51. package/dist/componentExplorer.d.ts.map +1 -1
  52. package/dist/componentExplorer.js +166 -23
  53. package/dist/componentExplorer.js.map +1 -1
  54. package/dist/config.d.ts +33 -4
  55. package/dist/config.d.ts.map +1 -1
  56. package/dist/config.js +64 -27
  57. package/dist/config.js.map +1 -1
  58. package/dist/coverage.d.ts +64 -0
  59. package/dist/coverage.d.ts.map +1 -0
  60. package/dist/coverage.js +212 -0
  61. package/dist/coverage.js.map +1 -0
  62. package/dist/daemon/DaemonService.d.ts +14 -5
  63. package/dist/daemon/DaemonService.d.ts.map +1 -1
  64. package/dist/daemon/DaemonService.js +47 -23
  65. package/dist/daemon/DaemonService.js.map +1 -1
  66. package/dist/daemon/approvalStore.d.ts +2 -1
  67. package/dist/daemon/approvalStore.d.ts.map +1 -1
  68. package/dist/daemon/approvalStore.js +4 -3
  69. package/dist/daemon/approvalStore.js.map +1 -1
  70. package/dist/daemon/inProcessClient.js +2 -2
  71. package/dist/daemon/lifecycle.d.ts +4 -1
  72. package/dist/daemon/lifecycle.d.ts.map +1 -1
  73. package/dist/daemon/lifecycle.js +1 -1
  74. package/dist/daemon/lifecycle.js.map +1 -1
  75. package/dist/daemon/pipeClient.d.ts +2 -0
  76. package/dist/daemon/pipeClient.d.ts.map +1 -1
  77. package/dist/daemon/pipeClient.js +26 -8
  78. package/dist/daemon/pipeClient.js.map +1 -1
  79. package/dist/daemon/pipeName.d.ts +3 -3
  80. package/dist/daemon/pipeName.d.ts.map +1 -1
  81. package/dist/daemon/pipeName.js +1 -2
  82. package/dist/daemon/pipeName.js.map +1 -1
  83. package/dist/daemon/pipeServer.js +3 -3
  84. package/dist/daemon/version.d.ts +1 -1
  85. package/dist/daemon/version.js +2 -2
  86. package/dist/dependencyInstaller.d.ts +2 -1
  87. package/dist/dependencyInstaller.d.ts.map +1 -1
  88. package/dist/dependencyInstaller.js +4 -4
  89. package/dist/dependencyInstaller.js.map +1 -1
  90. package/dist/git/gitCommitResolver.d.ts +2 -1
  91. package/dist/git/gitCommitResolver.d.ts.map +1 -1
  92. package/dist/git/gitCommitResolver.js +8 -8
  93. package/dist/git/gitCommitResolver.js.map +1 -1
  94. package/dist/git/gitIndexResolver.d.ts +2 -1
  95. package/dist/git/gitIndexResolver.d.ts.map +1 -1
  96. package/dist/git/gitIndexResolver.js +7 -7
  97. package/dist/git/gitIndexResolver.js.map +1 -1
  98. package/dist/git/gitService.d.ts +5 -4
  99. package/dist/git/gitService.d.ts.map +1 -1
  100. package/dist/git/gitService.js.map +1 -1
  101. package/dist/git/gitUtils.d.ts +6 -5
  102. package/dist/git/gitUtils.d.ts.map +1 -1
  103. package/dist/git/gitUtils.js +5 -4
  104. package/dist/git/gitUtils.js.map +1 -1
  105. package/dist/git/gitWorktreeManager.d.ts +15 -14
  106. package/dist/git/gitWorktreeManager.d.ts.map +1 -1
  107. package/dist/git/gitWorktreeManager.js +10 -11
  108. package/dist/git/gitWorktreeManager.js.map +1 -1
  109. package/dist/git/testUtils.d.ts +2 -1
  110. package/dist/git/testUtils.d.ts.map +1 -1
  111. package/dist/index.js +10 -1
  112. package/dist/index.js.map +1 -1
  113. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js +21 -0
  114. package/dist/js-component-explorer/packages/common/dist/explorerUrl.js.map +1 -0
  115. package/dist/js-component-explorer/packages/common/dist/renderManifest.js +67 -0
  116. package/dist/js-component-explorer/packages/common/dist/renderManifest.js.map +1 -0
  117. package/dist/js-component-explorer/packages/semver/dist/index.js.map +1 -0
  118. package/dist/js-component-explorer/packages/simple-api/dist/chunk-FJ7AVNQE.js.map +1 -0
  119. package/dist/js-component-explorer/packages/simple-api/dist/chunk-TTRCY65Z.js.map +1 -0
  120. package/dist/js-component-explorer/packages/simple-api/dist/chunk-WNXMRXWV.js.map +1 -0
  121. package/dist/js-component-explorer/packages/simple-api/dist/express.js.map +1 -0
  122. package/dist/manifest.schema.json +38 -16
  123. package/dist/mcp/DaemonAccessor.d.ts +22 -0
  124. package/dist/mcp/DaemonAccessor.d.ts.map +1 -0
  125. package/dist/mcp/McpServer.d.ts +4 -19
  126. package/dist/mcp/McpServer.d.ts.map +1 -1
  127. package/dist/mcp/McpServer.js +299 -305
  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 +9 -8
  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,127 +1,24 @@
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';
15
+ import { isPipeConnectionError } from '../daemon/pipeClient.js';
13
16
 
14
- // ---------------------------------------------------------------------------
15
- // Client-local state
16
- // ---------------------------------------------------------------------------
17
- class ImageLruCache {
18
- _maxSize;
19
- _entries = [];
20
- constructor(_maxSize = 10) {
21
- this._maxSize = _maxSize;
22
- }
23
- put(hash, image) {
24
- const idx = this._entries.findIndex(e => e.hash === hash);
25
- if (idx !== -1) {
26
- this._entries.splice(idx, 1);
27
- }
28
- this._entries.unshift({ hash, image });
29
- if (this._entries.length > this._maxSize) {
30
- this._entries.length = this._maxSize;
31
- }
32
- }
33
- get(hash) {
34
- const idx = this._entries.findIndex(e => e.hash === hash);
35
- if (idx === -1) {
36
- return undefined;
37
- }
38
- const [entry] = this._entries.splice(idx, 1);
39
- this._entries.unshift(entry);
40
- return entry.image;
41
- }
42
- keys() {
43
- return this._entries.map(e => e.hash);
44
- }
45
- }
46
- class WatchList {
47
- _fixtureIds = new Set();
48
- _hashes = new Map();
49
- get fixtureIds() { return this._fixtureIds; }
50
- add(ids) {
51
- for (const id of ids) {
52
- this._fixtureIds.add(id);
53
- }
54
- }
55
- remove(ids) {
56
- for (const id of ids) {
57
- this._fixtureIds.delete(id);
58
- this._hashes.delete(id);
59
- }
60
- }
61
- set(ids) {
62
- this._fixtureIds.clear();
63
- this._hashes.clear();
64
- for (const id of ids) {
65
- this._fixtureIds.add(id);
66
- }
67
- }
68
- getHash(fixtureId) {
69
- return this._hashes.get(fixtureId);
70
- }
71
- setHash(fixtureId, hash) {
72
- this._hashes.set(fixtureId, hash);
73
- }
74
- toJSON() {
75
- return {
76
- fixtureIds: [...this._fixtureIds],
77
- hashes: Object.fromEntries(this._hashes),
78
- };
79
- }
80
- }
81
- function noDaemonError(hint) {
82
- let text = 'Error: No daemon is currently running.';
83
- if (hint) {
84
- text += ` ${hint}`;
85
- }
86
- else {
87
- text += ' Please start the Component Explorer daemon first by running:\n\n' +
88
- ' component-explorer serve --project <config.json>\n\n' +
89
- 'Or start it in the background:\n\n' +
90
- ' component-explorer serve --project <config.json> --background\n\n' +
91
- 'The daemon manages dev servers and enables fixture screenshots.';
92
- }
93
- return {
94
- content: [{ type: 'text', text }],
95
- isError: true,
96
- };
97
- }
98
- // ---------------------------------------------------------------------------
99
- // DaemonConnection - wrapper to avoid Proxy issues with observables
100
- // ---------------------------------------------------------------------------
101
- class DaemonConnection {
102
- client;
103
- _stale = false;
104
- constructor(client) {
105
- this.client = client;
106
- }
107
- get isStale() { return this._stale; }
108
- markStale() { this._stale = true; }
109
- }
110
- function isPipeConnectionError(e) {
111
- if (!(e instanceof Error)) {
112
- return false;
113
- }
114
- const code = e.code;
115
- if (code === 'ENOENT' || code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'EPIPE') {
116
- return true;
117
- }
118
- return /connect ENOENT|ECONNREFUSED|ECONNRESET|EPIPE/.test(e.message);
119
- }
120
17
  // ---------------------------------------------------------------------------
121
18
  // ComponentExplorerMcpServer
122
19
  // ---------------------------------------------------------------------------
123
20
  class ComponentExplorerMcpServer extends Disposable {
124
- _daemonConnection;
21
+ _daemon;
125
22
  static async create(daemon, options) {
126
23
  const server = new ComponentExplorerMcpServer(daemon, options ?? {});
127
24
  const transport = new StdioServerTransport();
@@ -133,16 +30,12 @@ class ComponentExplorerMcpServer extends Disposable {
133
30
  _imageLru = new ImageLruCache(10);
134
31
  _taskManager = new TaskManager();
135
32
  _taskLastReportedIndex = new Map();
136
- _pollFn;
137
- _noAutostartHint;
138
33
  _multiSessionTools = [];
139
34
  _sessions = [];
140
35
  _eventStreamAbortController;
141
- constructor(_daemonConnection, options) {
36
+ constructor(_daemon, options) {
142
37
  super();
143
- this._daemonConnection = _daemonConnection;
144
- this._pollFn = options.pollFn;
145
- this._noAutostartHint = options.noAutostartHint;
38
+ this._daemon = _daemon;
146
39
  this._callTimeoutMs = options.callTimeoutMs ?? ComponentExplorerMcpServer._DEFAULT_CALL_TIMEOUT_MS;
147
40
  this._mcp = new McpServer({
148
41
  name: 'component-explorer',
@@ -150,8 +43,8 @@ class ComponentExplorerMcpServer extends Disposable {
150
43
  });
151
44
  this._registerTools();
152
45
  this._store.add(autorun(async (reader) => {
153
- const conn = this._daemonConnection.read(reader);
154
- await this._onDaemonChanged(conn?.client);
46
+ const client = this._daemon.connection.read(reader);
47
+ await this._onDaemonChanged(client);
155
48
  }));
156
49
  }
157
50
  async _onDaemonChanged(daemon) {
@@ -173,49 +66,13 @@ class ComponentExplorerMcpServer extends Disposable {
173
66
  this._sessions = [];
174
67
  }
175
68
  }
176
- _getConnection() {
177
- const conn = this._daemonConnection.get();
178
- if (conn?.isStale) {
179
- return undefined;
180
- }
181
- return conn;
182
- }
183
- async _waitForDaemon() {
184
- let conn = this._getConnection();
185
- if (conn) {
186
- return conn.client;
187
- }
188
- if (!this._pollFn) {
189
- return undefined;
190
- }
191
- this._log('debug', { type: 'waiting-for-daemon' });
192
- const startTime = Date.now();
193
- const timeout = 3000;
194
- while (Date.now() - startTime < timeout) {
195
- await this._pollFn();
196
- conn = this._getConnection();
197
- if (conn) {
198
- return conn.client;
199
- }
200
- await new Promise(resolve => setTimeout(resolve, 200));
201
- }
202
- return undefined;
203
- }
204
- _handleDisconnect() {
205
- const conn = this._daemonConnection.get();
206
- if (conn && !conn.isStale) {
207
- conn.markStale();
208
- this._sessions = [];
209
- this._log('debug', { type: 'daemon-connection-lost' });
210
- }
211
- }
212
69
  _noDaemonError() {
213
- return noDaemonError(this._noAutostartHint);
70
+ return noDaemonError(this._daemon.noDaemonHint);
214
71
  }
215
72
  static _DEFAULT_CALL_TIMEOUT_MS = 15_000;
216
73
  _callTimeoutMs;
217
74
  async _withDaemon(fn, options) {
218
- const daemon = await this._waitForDaemon();
75
+ const daemon = await this._daemon.getDaemonOrStart();
219
76
  if (!daemon) {
220
77
  return this._noDaemonError();
221
78
  }
@@ -235,7 +92,6 @@ class ComponentExplorerMcpServer extends Disposable {
235
92
  }
236
93
  if (isPipeConnectionError(e)) {
237
94
  this._log('debug', { type: 'daemon-call-failed', error: String(e) });
238
- this._handleDisconnect();
239
95
  return this._noDaemonError();
240
96
  }
241
97
  throw e;
@@ -259,7 +115,7 @@ class ComponentExplorerMcpServer extends Disposable {
259
115
  this._updateSessionSourceTreeId(event.sessionName, event.sourceTreeId);
260
116
  }
261
117
  if (event.type === 'ref-change' || event.type === 'session-change') {
262
- await this._refreshSessions();
118
+ await this._refreshSessions(daemon);
263
119
  }
264
120
  this._log(event.type === 'log' && event.level === 'debug' ? 'debug' : 'info', event);
265
121
  }
@@ -293,24 +149,11 @@ class ComponentExplorerMcpServer extends Disposable {
293
149
  s.sourceTreeId = sourceTreeId;
294
150
  }
295
151
  }
296
- async _refreshSessions() {
297
- const conn = this._getConnection();
298
- if (conn) {
299
- try {
300
- const prevCount = this._sessions.length;
301
- this._sessions = await conn.client.methods.sessions();
302
- if (this._sessions.length !== prevCount) {
303
- this._updateMultiSessionToolVisibility();
304
- }
305
- }
306
- catch (e) {
307
- if (isPipeConnectionError(e)) {
308
- this._handleDisconnect();
309
- }
310
- else {
311
- throw e;
312
- }
313
- }
152
+ async _refreshSessions(daemon) {
153
+ const prevCount = this._sessions.length;
154
+ this._sessions = await daemon.methods.sessions();
155
+ if (this._sessions.length !== prevCount) {
156
+ this._updateMultiSessionToolVisibility();
314
157
  }
315
158
  }
316
159
  _updateMultiSessionToolVisibility() {
@@ -324,37 +167,37 @@ class ComponentExplorerMcpServer extends Disposable {
324
167
  }
325
168
  }
326
169
  }
327
- async _withSourceTreeRetry(fn) {
170
+ async _withSourceTreeRetry(daemon, fn) {
328
171
  try {
329
172
  return await fn();
330
173
  }
331
174
  catch (e) {
332
175
  const msg = e instanceof Error ? e.message : String(e);
333
176
  if (msg.includes('Source tree changed')) {
334
- await this._refreshSessions();
177
+ await this._refreshSessions(daemon);
335
178
  return await fn();
336
179
  }
337
180
  throw e;
338
181
  }
339
182
  }
340
183
  // -- Tool registration ---------------------------------------------------
341
- _filterFixtures(allFixtures, fixtureIdPattern, labelPattern) {
184
+ _filterFixtures(allFixtures, fixtureIdRegexStr, labelRegexStr) {
342
185
  let fixtureIdRegex;
343
- if (fixtureIdPattern) {
186
+ if (fixtureIdRegexStr) {
344
187
  try {
345
- fixtureIdRegex = new RegExp(fixtureIdPattern);
188
+ fixtureIdRegex = new RegExp(fixtureIdRegexStr);
346
189
  }
347
190
  catch {
348
- return { error: `Error: Invalid fixtureIdPattern: ${fixtureIdPattern}` };
191
+ return { error: `Error: Invalid fixtureIdRegex: ${fixtureIdRegexStr}` };
349
192
  }
350
193
  }
351
194
  let labelRegex;
352
- if (labelPattern) {
195
+ if (labelRegexStr) {
353
196
  try {
354
- labelRegex = new RegExp(labelPattern);
197
+ labelRegex = new RegExp(labelRegexStr);
355
198
  }
356
199
  catch {
357
- return { error: `Error: Invalid labelPattern: ${labelPattern}` };
200
+ return { error: `Error: Invalid labelRegex: ${labelRegexStr}` };
358
201
  }
359
202
  }
360
203
  return {
@@ -392,20 +235,20 @@ class ComponentExplorerMcpServer extends Disposable {
392
235
  _registerListFixtures() {
393
236
  this._mcp.registerTool('list_fixtures', {
394
237
  description: 'List all fixtures from a session',
395
- inputSchema: {
396
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
397
- 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)'),
398
241
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
399
242
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
400
- },
243
+ }),
401
244
  annotations: { readOnlyHint: true },
402
245
  }, async (args) => this._withDaemon(async (daemon) => {
403
246
  const sessionName = args.sessionName ?? this._defaultSessionName();
404
247
  this._log('debug', { type: 'tool-call', tool: 'list_fixtures', sessionName });
405
- return this._withSourceTreeRetry(async () => {
248
+ return this._withSourceTreeRetry(daemon, async () => {
406
249
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
407
250
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
408
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
251
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
409
252
  if ('error' in filtered) {
410
253
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
411
254
  }
@@ -421,19 +264,28 @@ class ComponentExplorerMcpServer extends Disposable {
421
264
  _registerScreenshot() {
422
265
  this._mcp.registerTool('screenshot', {
423
266
  description: 'Take a screenshot of a single fixture. ' +
424
- 'When stabilityCheck is true, the fixture is unmounted and re-mounted, then three screenshots are taken. ',
425
- 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({
426
272
  fixtureId: z.string().describe('The fixture ID'),
427
273
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
428
274
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
429
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..'),
430
- },
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
+ }),
431
283
  annotations: { readOnlyHint: true },
432
284
  }, async (args) => this._withDaemon(async (daemon) => {
433
285
  const sessionName = args.sessionName ?? this._defaultSessionName();
434
286
  this._log('debug', { type: 'tool-call', tool: 'screenshot', fixtureId: args.fixtureId, sessionName });
435
287
  this._log('trace', { type: 'tool-args', tool: 'screenshot', args });
436
- return this._withSourceTreeRetry(async () => {
288
+ return this._withSourceTreeRetry(daemon, async () => {
437
289
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
438
290
  const result = await daemon.methods.screenshots.take({
439
291
  fixtureId: args.fixtureId,
@@ -441,6 +293,8 @@ class ComponentExplorerMcpServer extends Disposable {
441
293
  sourceTreeId,
442
294
  includeImage: true,
443
295
  stabilityCheck: args.stabilityCheck,
296
+ input: args.input,
297
+ disposeAfter: args.disposeAfter,
444
298
  });
445
299
  const r = result;
446
300
  this._updateSessionSourceTreeId(sessionName, r.sourceTreeId);
@@ -448,6 +302,7 @@ class ComponentExplorerMcpServer extends Disposable {
448
302
  if (r.hash && r.image) {
449
303
  this._imageLru.put(r.hash, r.image);
450
304
  }
305
+ const filterEvents = (events) => args.includeConsoleLog ? events : events.filter(e => e.type !== 'console.log');
451
306
  const info = {
452
307
  hash: r.hash,
453
308
  sourceTreeId: r.sourceTreeId,
@@ -459,14 +314,37 @@ class ComponentExplorerMcpServer extends Disposable {
459
314
  info.error = r.error;
460
315
  }
461
316
  if (r.events && r.events.length > 0) {
462
- info.events = r.events;
317
+ const filtered = filterEvents(r.events);
318
+ if (filtered.length > 0) {
319
+ info.events = filtered;
320
+ }
463
321
  }
464
- if (r.resultData !== undefined) {
465
- info.resultData = r.resultData;
322
+ if (r.output !== undefined) {
323
+ info.output = r.output;
466
324
  }
467
325
  if (r.isStable !== undefined) {
468
326
  info.isStable = r.isStable;
469
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
+ }
470
348
  // Visual review status
471
349
  if (r.hash) {
472
350
  try {
@@ -510,19 +388,19 @@ class ComponentExplorerMcpServer extends Disposable {
510
388
  _registerCompareScreenshot() {
511
389
  const tool = this._mcp.registerTool('compare_screenshot', {
512
390
  description: 'Compare a fixture\'s screenshot across two sessions (e.g. baseline vs current)',
513
- inputSchema: {
391
+ inputSchema: z.strictObject({
514
392
  fixtureId: z.string().describe('The fixture ID'),
515
393
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
516
394
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
517
395
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
518
396
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
519
- },
397
+ }),
520
398
  annotations: { readOnlyHint: true },
521
399
  }, async (args) => this._withDaemon(async (daemon) => {
522
400
  const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
523
401
  const currentSessionName = args.currentSessionName ?? this._defaultCurrentSessionName();
524
402
  this._log('debug', { type: 'tool-call', tool: 'compare_screenshot', fixtureId: args.fixtureId, baselineSessionName, currentSessionName });
525
- return this._withSourceTreeRetry(async () => {
403
+ return this._withSourceTreeRetry(daemon, async () => {
526
404
  const baselineSourceTreeId = args.baselineSourceTreeId ?? this._sourceTreeId(baselineSessionName);
527
405
  const currentSourceTreeId = args.currentSourceTreeId ?? this._sourceTreeId(currentSessionName);
528
406
  const result = await daemon.methods.screenshots.compare({
@@ -550,8 +428,8 @@ class ComponentExplorerMcpServer extends Disposable {
550
428
  if (r.baselineEvents && r.baselineEvents.length > 0) {
551
429
  info.baselineEvents = r.baselineEvents;
552
430
  }
553
- if (r.baselineResultData !== undefined) {
554
- info.baselineResultData = r.baselineResultData;
431
+ if (r.baselineOutput !== undefined) {
432
+ info.baselineOutput = r.baselineOutput;
555
433
  }
556
434
  if (r.currentHasError) {
557
435
  info.currentHasError = true;
@@ -562,8 +440,8 @@ class ComponentExplorerMcpServer extends Disposable {
562
440
  if (r.currentEvents && r.currentEvents.length > 0) {
563
441
  info.currentEvents = r.currentEvents;
564
442
  }
565
- if (r.currentResultData !== undefined) {
566
- info.currentResultData = r.currentResultData;
443
+ if (r.currentOutput !== undefined) {
444
+ info.currentOutput = r.currentOutput;
567
445
  }
568
446
  if (r.approval) {
569
447
  info.approval = r.approval;
@@ -586,12 +464,12 @@ class ComponentExplorerMcpServer extends Disposable {
586
464
  _registerApproveDiff() {
587
465
  const tool = this._mcp.registerTool('approve_diff', {
588
466
  description: 'Approve a visual diff so it won\'t require re-inspection next time',
589
- inputSchema: {
467
+ inputSchema: z.strictObject({
590
468
  fixtureId: z.string(),
591
469
  originalHash: z.string(),
592
470
  modifiedHash: z.string(),
593
471
  comment: z.string().describe('Reason for approving this diff'),
594
- },
472
+ }),
595
473
  }, async (args) => this._withDaemon(async (daemon) => {
596
474
  await daemon.methods.approvals.approve(args);
597
475
  return {
@@ -609,18 +487,18 @@ class ComponentExplorerMcpServer extends Disposable {
609
487
  description: 'Approve or reject a fixture\'s screenshot based on its expectedVisualDescriptions. ' +
610
488
  'You must take a screenshot first and pass the resulting hash. ' +
611
489
  'On approve, caches (expectedVisualDescriptions, screenshotHash) so future runs auto-approve.',
612
- inputSchema: {
490
+ inputSchema: z.strictObject({
613
491
  fixtureId: z.string().describe('The fixture ID'),
614
492
  screenshotHash: z.string().describe('The screenshot hash (from a prior screenshot tool call)'),
615
493
  verdict: z.enum(['approve', 'reject']).describe('Whether the visual matches expectations'),
616
494
  comment: z.string().describe('Reason for the verdict'),
617
495
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
618
496
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
619
- },
497
+ }),
620
498
  }, async (args) => this._withDaemon(async (daemon) => {
621
499
  const sessionName = args.sessionName ?? this._defaultSessionName();
622
500
  this._log('debug', { type: 'tool-call', tool: 'review_visual', fixtureId: args.fixtureId, verdict: args.verdict });
623
- return this._withSourceTreeRetry(async () => {
501
+ return this._withSourceTreeRetry(daemon, async () => {
624
502
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
625
503
  // Get fixture descriptions
626
504
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
@@ -644,13 +522,15 @@ class ComponentExplorerMcpServer extends Disposable {
644
522
  }
645
523
  else {
646
524
  return {
647
- content: [{ type: 'text', text: JSON.stringify({
525
+ content: [{
526
+ type: 'text', text: JSON.stringify({
648
527
  fixtureId: args.fixtureId,
649
528
  verdict: 'rejected',
650
529
  comment: args.comment,
651
530
  screenshotHash: args.screenshotHash,
652
531
  expectedVisualDescriptions: fixture.expectedVisualDescriptions,
653
- }, null, 2) }],
532
+ }, null, 2)
533
+ }],
654
534
  };
655
535
  }
656
536
  });
@@ -660,23 +540,23 @@ class ComponentExplorerMcpServer extends Disposable {
660
540
  this._mcp.registerTool('check_visuals', {
661
541
  description: 'Batch check visual review status for fixtures that have expectedVisualDescription. ' +
662
542
  'Returns lists of approved, needs-review, and no-expectation fixtures.',
663
- inputSchema: {
664
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
665
- 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'),
666
546
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
667
547
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
668
- },
548
+ }),
669
549
  annotations: { readOnlyHint: true },
670
550
  }, async (args) => this._withDaemon(async (daemon) => {
671
551
  const sessionName = args.sessionName ?? this._defaultSessionName();
672
552
  this._log('debug', { type: 'tool-call', tool: 'check_visuals', sessionName });
673
- return this._withSourceTreeRetry(async () => {
553
+ return this._withSourceTreeRetry(daemon, async () => {
674
554
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
675
555
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
676
556
  if (listResult.loadError) {
677
557
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
678
558
  }
679
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
559
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
680
560
  if ('error' in filtered) {
681
561
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
682
562
  }
@@ -734,17 +614,17 @@ class ComponentExplorerMcpServer extends Disposable {
734
614
  'Returns the expression result as JSON. The expression can return a Promise (it will be awaited). ' +
735
615
  'Use this to inspect DOM state, computed styles, element dimensions, or component output. ' +
736
616
  'Do NOT use this to modify the DOM — this tool is for read-only inspection and debugging only.',
737
- inputSchema: {
617
+ inputSchema: z.strictObject({
738
618
  expression: z.string().describe('JavaScript expression to evaluate. Can return a Promise. The result must be JSON-serializable.'),
739
619
  fixtureId: z.string().optional().describe('If provided, renders this fixture before evaluating the expression'),
740
620
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
741
621
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
742
- },
622
+ }),
743
623
  }, async (args) => this._withDaemon(async (daemon) => {
744
624
  const sessionName = args.sessionName ?? this._defaultSessionName();
745
625
  this._log('debug', { type: 'tool-call', tool: 'evaluate_js', sessionName, hasFixtureId: !!args.fixtureId });
746
626
  this._log('trace', { type: 'tool-args', tool: 'evaluate_js', expressionLength: args.expression.length, fixtureId: args.fixtureId });
747
- return this._withSourceTreeRetry(async () => {
627
+ return this._withSourceTreeRetry(daemon, async () => {
748
628
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
749
629
  const result = await daemon.methods.evaluate({
750
630
  sessionName,
@@ -771,9 +651,9 @@ class ComponentExplorerMcpServer extends Disposable {
771
651
  description: 'Force-reload the browser page used for rendering fixtures. ' +
772
652
  'Only use this as a last resort if screenshots or evaluate_js return stale/broken results ' +
773
653
  'that persist after source changes. Normal HMR updates should handle most cases automatically.',
774
- inputSchema: {
654
+ inputSchema: z.strictObject({
775
655
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
776
- },
656
+ }),
777
657
  annotations: { destructiveHint: true },
778
658
  }, async (args) => this._withDaemon(async (daemon) => {
779
659
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -791,9 +671,9 @@ class ComponentExplorerMcpServer extends Disposable {
791
671
  _registerWatchAdd() {
792
672
  this._mcp.registerTool('watch_add', {
793
673
  description: 'Add fixtures to the watch list. Watched fixtures are automatically re-screenshotted when source changes.',
794
- inputSchema: {
674
+ inputSchema: z.strictObject({
795
675
  fixtureIds: z.array(z.string()).describe('Fixture IDs to add'),
796
- },
676
+ }),
797
677
  }, async (args) => {
798
678
  this._watchList.add(args.fixtureIds);
799
679
  return {
@@ -807,9 +687,9 @@ class ComponentExplorerMcpServer extends Disposable {
807
687
  _registerWatchRemove() {
808
688
  this._mcp.registerTool('watch_remove', {
809
689
  description: 'Remove fixtures from the watch list',
810
- inputSchema: {
690
+ inputSchema: z.strictObject({
811
691
  fixtureIds: z.array(z.string()).describe('Fixture IDs to remove'),
812
- },
692
+ }),
813
693
  }, async (args) => {
814
694
  this._watchList.remove(args.fixtureIds);
815
695
  return {
@@ -823,9 +703,9 @@ class ComponentExplorerMcpServer extends Disposable {
823
703
  _registerWatchSet() {
824
704
  this._mcp.registerTool('watch_set', {
825
705
  description: 'Replace the watch list entirely',
826
- inputSchema: {
706
+ inputSchema: z.strictObject({
827
707
  fixtureIds: z.array(z.string()).describe('Fixture IDs to watch'),
828
- },
708
+ }),
829
709
  }, async (args) => {
830
710
  this._watchList.set(args.fixtureIds);
831
711
  return {
@@ -839,12 +719,12 @@ class ComponentExplorerMcpServer extends Disposable {
839
719
  _registerWatchCompare() {
840
720
  const tool = this._mcp.registerTool('watch_compare', {
841
721
  description: 'Compare all watched fixtures across two sessions. Takes fresh screenshots from both sessions and reports which fixtures differ.',
842
- inputSchema: {
722
+ inputSchema: z.strictObject({
843
723
  baselineSessionName: z.string().optional().describe('Baseline session name (defaults to worktree session)'),
844
724
  currentSessionName: z.string().optional().describe('Current session name (defaults to current session)'),
845
725
  baselineSourceTreeId: z.string().optional().describe('Baseline source tree ID (defaults to latest known)'),
846
726
  currentSourceTreeId: z.string().optional().describe('Current source tree ID (defaults to latest known)'),
847
- },
727
+ }),
848
728
  annotations: { readOnlyHint: true },
849
729
  }, async (args) => this._withDaemon(async (daemon) => {
850
730
  const ids = [...this._watchList.fixtureIds];
@@ -853,7 +733,7 @@ class ComponentExplorerMcpServer extends Disposable {
853
733
  }
854
734
  const baselineSessionName = args.baselineSessionName ?? this._defaultBaselineSessionName();
855
735
  const currentSessionName = args.currentSessionName ?? this._defaultCurrentSessionName();
856
- return this._withSourceTreeRetry(async () => {
736
+ return this._withSourceTreeRetry(daemon, async () => {
857
737
  const baselineSourceTreeId = args.baselineSourceTreeId ?? this._sourceTreeId(baselineSessionName);
858
738
  const currentSourceTreeId = args.currentSourceTreeId ?? this._sourceTreeId(currentSessionName);
859
739
  const [baselineResult, currentResult] = await Promise.all([
@@ -909,10 +789,10 @@ class ComponentExplorerMcpServer extends Disposable {
909
789
  'Pass the sourceTreeId you already observed — resolves immediately if it already differs, ' +
910
790
  'otherwise waits for a source-change or ref-change event. ' +
911
791
  'If fixtures are on the watch list, automatically re-screenshots them and reports which changed.',
912
- inputSchema: {
792
+ inputSchema: z.strictObject({
913
793
  sourceTreeId: z.string().describe('The sourceTreeId the client currently knows about. The call resolves once the source tree differs from this value.'),
914
794
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
915
- },
795
+ }),
916
796
  annotations: { readOnlyHint: true },
917
797
  }, async (args) => this._withDaemon(async (daemon) => {
918
798
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -946,7 +826,7 @@ class ComponentExplorerMcpServer extends Disposable {
946
826
  this._updateSessionSourceTreeId(ev.sessionName, ev.sourceTreeId);
947
827
  }
948
828
  if (ev.type === 'ref-change') {
949
- const refreshResult = await Promise.race([this._refreshSessions(), timeout]);
829
+ const refreshResult = await Promise.race([this._refreshSessions(daemon), timeout]);
950
830
  if (refreshResult === 'timeout') {
951
831
  return { content: [{ type: 'text', text: JSON.stringify({ timeout: true, sessionName, sourceTreeId: knownSourceTreeId }, null, 2) }] };
952
832
  }
@@ -1003,8 +883,8 @@ class ComponentExplorerMcpServer extends Disposable {
1003
883
  this._mcp.registerTool('sessions', {
1004
884
  description: 'List active sessions with their names, URLs, and current sourceTreeIds',
1005
885
  annotations: { readOnlyHint: true },
1006
- }, async () => this._withDaemon(async (_daemon) => {
1007
- await this._refreshSessions();
886
+ }, async () => this._withDaemon(async (daemon) => {
887
+ await this._refreshSessions(daemon);
1008
888
  return {
1009
889
  content: [{ type: 'text', text: JSON.stringify(this._sessions, null, 2) }],
1010
890
  };
@@ -1014,9 +894,9 @@ class ComponentExplorerMcpServer extends Disposable {
1014
894
  this._mcp.registerTool('restart_session', {
1015
895
  description: 'Restart a session by disposing its browser page and dev server, then recreating them. ' +
1016
896
  'Use this when a session appears stuck (e.g. after a timeout).',
1017
- inputSchema: {
897
+ inputSchema: z.strictObject({
1018
898
  sessionName: z.string().optional().describe('Session name to restart (defaults to first session)'),
1019
- },
899
+ }),
1020
900
  annotations: { destructiveHint: true },
1021
901
  }, async (args) => this._withDaemon(async (daemon) => {
1022
902
  const sessionName = args.sessionName ?? this._defaultSessionName();
@@ -1034,10 +914,10 @@ class ComponentExplorerMcpServer extends Disposable {
1034
914
  'The ref can be a branch name, tag, commit SHA, or the special value "INDEX" to snapshot staged changes. ' +
1035
915
  'The daemon allocates a reusable worktree slot from a fixed pool (max configured in component-explorer.json). ' +
1036
916
  'Returns the updated session list on success.',
1037
- inputSchema: {
917
+ inputSchema: z.strictObject({
1038
918
  name: z.string().describe('Unique session name (e.g. "baseline", "bisect")'),
1039
919
  ref: z.string().describe('Git ref: branch, tag, commit SHA, or "INDEX" for staged changes'),
1040
- },
920
+ }),
1041
921
  }, async (args) => this._withDaemon(async (daemon) => {
1042
922
  this._log('info', { type: 'tool-call', tool: 'open_session', name: args.name, ref: args.ref });
1043
923
  const result = await daemon.methods.openSession({ name: args.name, ref: args.ref });
@@ -1055,9 +935,9 @@ class ComponentExplorerMcpServer extends Disposable {
1055
935
  this._mcp.registerTool('close_session', {
1056
936
  description: 'Close a dynamic worktree session and release its worktree slot back to the pool. ' +
1057
937
  'Cannot close static sessions configured in component-explorer.json.',
1058
- inputSchema: {
938
+ inputSchema: z.strictObject({
1059
939
  name: z.string().describe('Session name to close'),
1060
- },
940
+ }),
1061
941
  annotations: { destructiveHint: true },
1062
942
  }, async (args) => this._withDaemon(async (daemon) => {
1063
943
  this._log('info', { type: 'tool-call', tool: 'close_session', name: args.name });
@@ -1078,10 +958,10 @@ class ComponentExplorerMcpServer extends Disposable {
1078
958
  'The worktree is checked out to the new ref and Vite\'s HMR handles the incremental update (no server restart). ' +
1079
959
  'Fails if the worktree has uncommitted changes — the error will list the dirty files. ' +
1080
960
  'The ref can be a branch, tag, commit SHA, or "INDEX" for staged changes.',
1081
- inputSchema: {
961
+ inputSchema: z.strictObject({
1082
962
  name: z.string().describe('Session name to update'),
1083
963
  ref: z.string().describe('New git ref: branch, tag, commit SHA, or "INDEX"'),
1084
- },
964
+ }),
1085
965
  }, async (args) => this._withDaemon(async (daemon) => {
1086
966
  this._log('info', { type: 'tool-call', tool: 'update_session_ref', name: args.name, ref: args.ref });
1087
967
  const result = await daemon.methods.updateSessionRef({ name: args.name, ref: args.ref });
@@ -1098,30 +978,17 @@ class ComponentExplorerMcpServer extends Disposable {
1098
978
  this._mcp.registerTool('get_url', {
1099
979
  description: 'Get URL for viewing fixtures. Returns the full Component Explorer UI by default. ' +
1100
980
  'Use `embedded: true` for a minimal single-fixture view (requires fixtureId).',
1101
- inputSchema: {
981
+ inputSchema: z.strictObject({
1102
982
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1103
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.'),
1104
984
  embedded: z.boolean().optional().describe('If true, returns an embedded single-fixture URL (minimal UI, requires fixtureId). Default: false (full explorer UI).'),
1105
- },
985
+ }),
1106
986
  annotations: { readOnlyHint: true },
1107
- }, async (args) => {
987
+ }, async (args) => this._withDaemon(async (daemon) => {
1108
988
  const sessionName = args.sessionName ?? this._defaultSessionName();
1109
989
  let session = this._sessions.find(s => s.name === sessionName);
1110
990
  if (!session) {
1111
- const daemon = await this._waitForDaemon();
1112
- if (daemon) {
1113
- try {
1114
- await this._refreshSessions();
1115
- }
1116
- catch (e) {
1117
- if (isPipeConnectionError(e)) {
1118
- this._handleDisconnect();
1119
- }
1120
- else {
1121
- throw e;
1122
- }
1123
- }
1124
- }
991
+ await this._refreshSessions(daemon);
1125
992
  session = this._sessions.find(s => s.name === sessionName);
1126
993
  if (!session) {
1127
994
  return {
@@ -1156,6 +1023,7 @@ class ComponentExplorerMcpServer extends Disposable {
1156
1023
  const mode = args.embedded ? 'embedded' : undefined;
1157
1024
  const url = buildExplorerUrl({
1158
1025
  baseUrl,
1026
+ pathname: EXPLORER_ROUTE,
1159
1027
  mode,
1160
1028
  fixtureId: args.fixtureId,
1161
1029
  });
@@ -1170,36 +1038,49 @@ class ComponentExplorerMcpServer extends Disposable {
1170
1038
  return {
1171
1039
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
1172
1040
  };
1173
- });
1041
+ }));
1174
1042
  }
1175
1043
  _registerCheckFixtureErrors() {
1176
1044
  this._mcp.registerTool('check_fixture_errors', {
1177
- 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. ' +
1178
1051
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task.',
1179
- inputSchema: {
1180
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1181
- 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)'),
1182
1055
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1183
1056
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1184
- },
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
+ }),
1185
1062
  annotations: { readOnlyHint: true },
1186
1063
  }, async (args) => this._withDaemon(async (daemon) => {
1187
1064
  const sessionName = args.sessionName ?? this._defaultSessionName();
1188
- this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1189
- return this._withSourceTreeRetry(async () => {
1065
+ this._log('debug', { type: 'tool-call', tool: 'check_fixture_errors', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1066
+ return this._withSourceTreeRetry(daemon, async () => {
1190
1067
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1191
1068
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1192
1069
  if (listResult.loadError) {
1193
1070
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1194
1071
  }
1195
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1072
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1196
1073
  if ('error' in filtered) {
1197
1074
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1198
1075
  }
1199
1076
  const fixtures = filtered.fixtures;
1200
1077
  const task = this._taskManager.startTask(async (report, signal) => {
1201
- const results = [];
1202
- 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 });
1203
1084
  for (let i = 0; i < fixtures.length; i++) {
1204
1085
  if (signal.aborted) {
1205
1086
  break;
@@ -1211,30 +1092,52 @@ class ComponentExplorerMcpServer extends Disposable {
1211
1092
  sessionName,
1212
1093
  sourceTreeId,
1213
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,
1214
1102
  });
1215
1103
  const r = result;
1216
- const entry = { fixtureId: fixture.fixtureId, hasError: r.hasError };
1217
- if (r.error) {
1218
- 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);
1219
1123
  }
1220
- if (r.events && r.events.length > 0) {
1221
- entry.events = r.events;
1124
+ else {
1125
+ okCount++;
1222
1126
  }
1223
- results.push(entry);
1224
1127
  }
1225
1128
  catch (e) {
1226
- results.push({
1129
+ erroredResults.push({
1227
1130
  fixtureId: fixture.fixtureId,
1228
1131
  hasError: true,
1229
1132
  error: { message: e instanceof Error ? e.message : String(e) },
1230
1133
  });
1231
1134
  }
1232
- report({ completed: i + 1, total: fixtures.length, partialResult: results });
1135
+ processed++;
1136
+ report({ completed: i + 1, total: fixtures.length, partialResult: erroredResults });
1233
1137
  }
1234
- const withErrors = results.filter(r => r.hasError);
1235
1138
  return {
1236
- fixtures: results,
1237
- summary: { total: results.length, ok: results.length - withErrors.length, errored: withErrors.length },
1139
+ fixtures: erroredResults,
1140
+ summary: { total: processed, ok: okCount, errored: erroredResults.length },
1238
1141
  };
1239
1142
  });
1240
1143
  const waited = await this._taskManager.waitForTask(task.id, 10_000);
@@ -1268,23 +1171,27 @@ class ComponentExplorerMcpServer extends Disposable {
1268
1171
  description: 'Check rendering stability of fixtures. Each fixture is unmounted, re-mounted, and screenshotted 3 times (~3s per fixture). ' +
1269
1172
  'Returns results directly if finished within ~10s, otherwise returns a taskId for polling via check_task. ' +
1270
1173
  'When returning a taskId, includes partial results collected so far.',
1271
- inputSchema: {
1272
- fixtureIdPattern: z.string().optional().describe('RegExp to filter fixtures by fixture ID'),
1273
- 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)'),
1274
1177
  sessionName: z.string().optional().describe('Session name (defaults to first session)'),
1275
1178
  sourceTreeId: z.string().optional().describe('Source tree ID (defaults to latest known)'),
1276
- },
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
+ }),
1277
1184
  annotations: { readOnlyHint: true },
1278
1185
  }, async (args) => this._withDaemon(async (daemon) => {
1279
1186
  const sessionName = args.sessionName ?? this._defaultSessionName();
1280
- this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdPattern: args.fixtureIdPattern, labelPattern: args.labelPattern });
1281
- return this._withSourceTreeRetry(async () => {
1187
+ this._log('debug', { type: 'tool-call', tool: 'check_stability', sessionName, fixtureIdRegex: args.fixtureIdRegex, labelRegex: args.labelRegex });
1188
+ return this._withSourceTreeRetry(daemon, async () => {
1282
1189
  const sourceTreeId = args.sourceTreeId ?? this._sourceTreeId(sessionName);
1283
1190
  const listResult = await daemon.methods.fixtures.list({ sessionName, sourceTreeId });
1284
1191
  if (listResult.loadError) {
1285
1192
  return { content: [{ type: 'text', text: `Error: Fixture loading failed: ${listResult.loadError}\nThe fixture list may be incomplete.` }], isError: true };
1286
1193
  }
1287
- const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdPattern, args.labelPattern);
1194
+ const filtered = this._filterFixtures(listResult.fixtures, args.fixtureIdRegex, args.labelRegex);
1288
1195
  if ('error' in filtered) {
1289
1196
  return { content: [{ type: 'text', text: filtered.error }], isError: true };
1290
1197
  }
@@ -1305,6 +1212,9 @@ class ComponentExplorerMcpServer extends Disposable {
1305
1212
  sourceTreeId,
1306
1213
  includeImage: false,
1307
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,
1308
1218
  });
1309
1219
  const r = result;
1310
1220
  results.push({
@@ -1349,9 +1259,9 @@ class ComponentExplorerMcpServer extends Disposable {
1349
1259
  _registerCheckTask() {
1350
1260
  this._mcp.registerTool('check_task', {
1351
1261
  description: 'Check on a running task. Waits up to ~2s for completion; if still running, returns progress and new results since last check.',
1352
- inputSchema: {
1262
+ inputSchema: z.strictObject({
1353
1263
  taskId: z.string().describe('The task ID returned by a previous tool call'),
1354
- },
1264
+ }),
1355
1265
  annotations: { readOnlyHint: true },
1356
1266
  }, async (args) => {
1357
1267
  const waited = await this._taskManager.waitForTask(args.taskId, 2_000);
@@ -1399,9 +1309,9 @@ class ComponentExplorerMcpServer extends Disposable {
1399
1309
  _registerCancelTask() {
1400
1310
  this._mcp.registerTool('cancel_task', {
1401
1311
  description: 'Cancel a running task',
1402
- inputSchema: {
1312
+ inputSchema: z.strictObject({
1403
1313
  taskId: z.string().describe('The task ID to cancel'),
1404
- },
1314
+ }),
1405
1315
  }, async (args) => {
1406
1316
  const task = this._taskManager.getTask(args.taskId);
1407
1317
  if (!task) {
@@ -1421,9 +1331,9 @@ class ComponentExplorerMcpServer extends Disposable {
1421
1331
  description: 'Retrieve a recently-taken screenshot image by its hash. ' +
1422
1332
  'Keeps the last ~10 images in an LRU cache. ' +
1423
1333
  'Useful for debugging when screenshot hashes behave unexpectedly.',
1424
- inputSchema: {
1334
+ inputSchema: z.strictObject({
1425
1335
  hash: z.string().describe('The screenshot hash to look up'),
1426
- },
1336
+ }),
1427
1337
  annotations: { readOnlyHint: true },
1428
1338
  }, async (args) => {
1429
1339
  const image = this._imageLru.get(args.hash);
@@ -1447,9 +1357,9 @@ class ComponentExplorerMcpServer extends Disposable {
1447
1357
  'Only use this tool when the user explicitly asks to show or hide the browser. ' +
1448
1358
  'Do not call this tool automatically or as part of other workflows. ' +
1449
1359
  'Note: changing visibility closes the current browser instance, so the next screenshot or evaluate_js call will relaunch it.',
1450
- inputSchema: {
1360
+ inputSchema: z.strictObject({
1451
1361
  visible: z.boolean().describe('true to show the browser window (headed mode), false to hide it (headless mode)'),
1452
- },
1362
+ }),
1453
1363
  annotations: { destructiveHint: true },
1454
1364
  }, async (args) => this._withDaemon(async (daemon) => {
1455
1365
  await daemon.methods.setBrowserVisibility({ visible: args.visible });
@@ -1459,6 +1369,90 @@ class ComponentExplorerMcpServer extends Disposable {
1459
1369
  }));
1460
1370
  }
1461
1371
  }
1372
+ // ---------------------------------------------------------------------------
1373
+ // Client-local state
1374
+ // ---------------------------------------------------------------------------
1375
+ class ImageLruCache {
1376
+ _maxSize;
1377
+ _entries = [];
1378
+ constructor(_maxSize = 10) {
1379
+ this._maxSize = _maxSize;
1380
+ }
1381
+ put(hash, image) {
1382
+ const idx = this._entries.findIndex(e => e.hash === hash);
1383
+ if (idx !== -1) {
1384
+ this._entries.splice(idx, 1);
1385
+ }
1386
+ this._entries.unshift({ hash, image });
1387
+ if (this._entries.length > this._maxSize) {
1388
+ this._entries.length = this._maxSize;
1389
+ }
1390
+ }
1391
+ get(hash) {
1392
+ const idx = this._entries.findIndex(e => e.hash === hash);
1393
+ if (idx === -1) {
1394
+ return undefined;
1395
+ }
1396
+ const [entry] = this._entries.splice(idx, 1);
1397
+ this._entries.unshift(entry);
1398
+ return entry.image;
1399
+ }
1400
+ keys() {
1401
+ return this._entries.map(e => e.hash);
1402
+ }
1403
+ }
1404
+ class WatchList {
1405
+ _fixtureIds = new Set();
1406
+ _hashes = new Map();
1407
+ get fixtureIds() { return this._fixtureIds; }
1408
+ add(ids) {
1409
+ for (const id of ids) {
1410
+ this._fixtureIds.add(id);
1411
+ }
1412
+ }
1413
+ remove(ids) {
1414
+ for (const id of ids) {
1415
+ this._fixtureIds.delete(id);
1416
+ this._hashes.delete(id);
1417
+ }
1418
+ }
1419
+ set(ids) {
1420
+ this._fixtureIds.clear();
1421
+ this._hashes.clear();
1422
+ for (const id of ids) {
1423
+ this._fixtureIds.add(id);
1424
+ }
1425
+ }
1426
+ getHash(fixtureId) {
1427
+ return this._hashes.get(fixtureId);
1428
+ }
1429
+ setHash(fixtureId, hash) {
1430
+ this._hashes.set(fixtureId, hash);
1431
+ }
1432
+ toJSON() {
1433
+ return {
1434
+ fixtureIds: [...this._fixtureIds],
1435
+ hashes: Object.fromEntries(this._hashes),
1436
+ };
1437
+ }
1438
+ }
1439
+ function noDaemonError(hint) {
1440
+ let text = 'Error: No daemon is currently running.';
1441
+ if (hint) {
1442
+ text += ` ${hint}`;
1443
+ }
1444
+ else {
1445
+ text += ' Please start the Component Explorer daemon first by running:\n\n' +
1446
+ ' component-explorer serve --project <config.json>\n\n' +
1447
+ 'Or start it in the background:\n\n' +
1448
+ ' component-explorer serve --project <config.json> --background\n\n' +
1449
+ 'The daemon manages dev servers and enables fixture screenshots.';
1450
+ }
1451
+ return {
1452
+ content: [{ type: 'text', text }],
1453
+ isError: true,
1454
+ };
1455
+ }
1462
1456
 
1463
- export { ComponentExplorerMcpServer, DaemonConnection };
1457
+ export { ComponentExplorerMcpServer };
1464
1458
  //# sourceMappingURL=McpServer.js.map