@reticlehq/server 2.8.0 → 2.10.0

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 (318) hide show
  1. package/SKILL.md +130 -813
  2. package/dist/bridge/bridge.d.ts +23 -0
  3. package/dist/bridge/bridge.js +80 -2
  4. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  5. package/dist/bridge/bridge.test-harness.js +6 -4
  6. package/dist/capsule/capsule-store.d.ts +2 -2
  7. package/dist/capsule/causal-summary.d.ts +16 -1
  8. package/dist/capsule/causal-summary.js +2 -1
  9. package/dist/cli/cli-doctor.js +41 -13
  10. package/dist/cli/cli-flow-commands.js +8 -8
  11. package/dist/cli/cli-launch.d.ts +0 -17
  12. package/dist/cli/cli-launch.js +0 -28
  13. package/dist/cli/cli-parse.d.ts +1 -1
  14. package/dist/cli/cli-parse.js +1 -1
  15. package/dist/cli/cli-port.d.ts +24 -0
  16. package/dist/cli/cli-port.js +66 -35
  17. package/dist/cli/cli-verify.d.ts +21 -0
  18. package/dist/cli/cli-verify.js +45 -1
  19. package/dist/cli/config-discovery.d.ts +35 -0
  20. package/dist/cli/config-discovery.js +163 -0
  21. package/dist/cli/doctor-daemon-line.js +4 -1
  22. package/dist/cli/doctor-project-line.d.ts +37 -0
  23. package/dist/cli/doctor-project-line.js +35 -0
  24. package/dist/cli/doctor-rows.d.ts +51 -0
  25. package/dist/cli/doctor-rows.js +54 -0
  26. package/dist/cli/doctor-sessions-line.js +6 -3
  27. package/dist/cli/drive-attach.d.ts +99 -0
  28. package/dist/cli/drive-attach.js +203 -0
  29. package/dist/cli/drive-command.d.ts +13 -0
  30. package/dist/cli/drive-command.js +103 -0
  31. package/dist/cli/open-note.d.ts +20 -0
  32. package/dist/cli/open-note.js +37 -0
  33. package/dist/cli/status-next-action.d.ts +37 -0
  34. package/dist/cli/status-next-action.js +61 -0
  35. package/dist/cli.js +65 -70
  36. package/dist/crawl/crawl.d.ts +7 -2
  37. package/dist/crawl/crawl.js +52 -9
  38. package/dist/ee/audit-log.js +2 -2
  39. package/dist/events/body-failures.d.ts +2 -2
  40. package/dist/events/contradiction-folds.d.ts +45 -0
  41. package/dist/events/contradiction-folds.js +42 -0
  42. package/dist/events/contradictions.d.ts +51 -1
  43. package/dist/events/contradictions.js +26 -7
  44. package/dist/events/declared.d.ts +45 -0
  45. package/dist/events/declared.js +73 -0
  46. package/dist/events/echo-mismatch.d.ts +9 -3
  47. package/dist/events/echo-mismatch.js +33 -5
  48. package/dist/events/event-filters.d.ts +1 -1
  49. package/dist/events/event-filters.js +7 -4
  50. package/dist/events/predicate-eval.d.ts +17 -91
  51. package/dist/events/predicate-eval.js +85 -326
  52. package/dist/events/predicate-parse.js +31 -3
  53. package/dist/events/predicate-route.d.ts +32 -0
  54. package/dist/events/predicate-route.js +102 -0
  55. package/dist/events/predicate-schema.d.ts +99 -0
  56. package/dist/events/predicate-schema.js +309 -0
  57. package/dist/events/predicate.d.ts +8 -0
  58. package/dist/events/predicate.js +127 -5
  59. package/dist/events/stale-response.d.ts +2 -3
  60. package/dist/events/stale-response.js +49 -2
  61. package/dist/events/unit-mismatch.d.ts +2 -2
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-replay-run.js +8 -1
  69. package/dist/flows/flow-tools.js +9 -1
  70. package/dist/flows/replay.js +3 -0
  71. package/dist/flows/verify-change-tools.js +1 -1
  72. package/dist/honesty/accepted-write.d.ts +3 -0
  73. package/dist/honesty/accepted-write.js +8 -3
  74. package/dist/honesty/already-true.js +15 -0
  75. package/dist/honesty/blind-spots.js +4 -0
  76. package/dist/honesty/honesty.d.ts +11 -0
  77. package/dist/honesty/honesty.js +1 -0
  78. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  79. package/dist/honesty/uncaptured-bodies.js +9 -2
  80. package/dist/honesty/unread-outcome.d.ts +8 -0
  81. package/dist/honesty/unread-outcome.js +36 -9
  82. package/dist/honesty/unsettled.d.ts +9 -0
  83. package/dist/honesty/unsettled.js +7 -3
  84. package/dist/honesty/verified.d.ts +29 -3
  85. package/dist/honesty/verified.js +67 -8
  86. package/dist/http-server.d.ts +19 -0
  87. package/dist/http-server.js +202 -4
  88. package/dist/impact/impact-recorder.d.ts +23 -0
  89. package/dist/impact/impact-recorder.js +76 -0
  90. package/dist/impact/impact-store.d.ts +47 -0
  91. package/dist/impact/impact-store.js +171 -0
  92. package/dist/index.d.ts +25 -0
  93. package/dist/index.js +60 -10
  94. package/dist/init/agent-rules.d.ts +20 -0
  95. package/dist/init/agent-rules.js +36 -0
  96. package/dist/init/astro-patch.js +27 -1
  97. package/dist/init/confirm.d.ts +21 -3
  98. package/dist/init/confirm.js +32 -5
  99. package/dist/init/connect-steps.d.ts +2 -0
  100. package/dist/init/connect-steps.js +50 -0
  101. package/dist/init/csp-check.d.ts +38 -0
  102. package/dist/init/csp-check.js +92 -0
  103. package/dist/init/csp-doctor.d.ts +25 -0
  104. package/dist/init/csp-doctor.js +49 -0
  105. package/dist/init/cursor.js +6 -9
  106. package/dist/init/detect.d.ts +8 -0
  107. package/dist/init/detect.js +33 -3
  108. package/dist/init/mcp-clients.d.ts +1 -0
  109. package/dist/init/mcp-clients.js +51 -7
  110. package/dist/init/mcp.d.ts +16 -2
  111. package/dist/init/mcp.js +23 -1
  112. package/dist/init/plan-framework.d.ts +10 -0
  113. package/dist/init/plan-framework.js +25 -1
  114. package/dist/init/plan.d.ts +7 -2
  115. package/dist/init/plan.js +66 -30
  116. package/dist/init/run.js +11 -5
  117. package/dist/init/slash-command.d.ts +1 -1
  118. package/dist/init/slash-command.js +12 -4
  119. package/dist/init/snippets.d.ts +1 -1
  120. package/dist/init/snippets.js +35 -7
  121. package/dist/init/vite-config.js +24 -2
  122. package/dist/input/network-mock-tools.js +8 -1
  123. package/dist/input/scroll-find.d.ts +6 -0
  124. package/dist/input/scroll-find.js +17 -2
  125. package/dist/input/scroll-tools.js +4 -0
  126. package/dist/journal/attach-journal.d.ts +9 -1
  127. package/dist/journal/attach-journal.js +13 -2
  128. package/dist/journal/journal-query.d.ts +13 -0
  129. package/dist/journal/journal-query.js +28 -3
  130. package/dist/journal/workspace-gitignore.d.ts +10 -0
  131. package/dist/journal/workspace-gitignore.js +59 -0
  132. package/dist/license/license.d.ts +69 -4
  133. package/dist/license/license.js +84 -15
  134. package/dist/mcp/mcp-proxy.d.ts +7 -93
  135. package/dist/mcp/mcp-proxy.js +183 -250
  136. package/dist/mcp/mcp.d.ts +27 -2
  137. package/dist/mcp/mcp.js +77 -36
  138. package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
  139. package/dist/mcp/proxy-daemon-probe.js +48 -0
  140. package/dist/mcp/proxy-handshake.d.ts +82 -1
  141. package/dist/mcp/proxy-handshake.js +110 -3
  142. package/dist/mcp/proxy-lifecycle.d.ts +10 -1
  143. package/dist/mcp/proxy-lifecycle.js +11 -2
  144. package/dist/mcp/proxy-log.d.ts +63 -0
  145. package/dist/mcp/proxy-log.js +132 -0
  146. package/dist/mcp/server-instructions.d.ts +17 -0
  147. package/dist/mcp/server-instructions.js +56 -0
  148. package/dist/mcp/shared-params.d.ts +27 -0
  149. package/dist/mcp/shared-params.js +36 -0
  150. package/dist/mcp/sse-frame-parser.d.ts +19 -0
  151. package/dist/mcp/sse-frame-parser.js +41 -0
  152. package/dist/project/reticle-dir.d.ts +2 -0
  153. package/dist/project/reticle-dir.js +1 -0
  154. package/dist/session/connection-memory.d.ts +59 -0
  155. package/dist/session/connection-memory.js +108 -0
  156. package/dist/session/dev-command.d.ts +26 -0
  157. package/dist/session/dev-command.js +89 -0
  158. package/dist/session/dev-server-probe.d.ts +68 -0
  159. package/dist/session/dev-server-probe.js +102 -7
  160. package/dist/session/last-act.d.ts +26 -0
  161. package/dist/session/last-act.js +35 -1
  162. package/dist/session/live-control-tools.js +42 -5
  163. package/dist/session/no-session-diagnosis.d.ts +55 -2
  164. package/dist/session/no-session-diagnosis.js +199 -22
  165. package/dist/session/no-session-next-action.d.ts +46 -0
  166. package/dist/session/no-session-next-action.js +85 -0
  167. package/dist/session/no-session-watch.d.ts +40 -1
  168. package/dist/session/no-session-watch.js +172 -11
  169. package/dist/session/pending-commands.d.ts +12 -0
  170. package/dist/session/pending-commands.js +13 -1
  171. package/dist/session/session-manager.d.ts +4 -0
  172. package/dist/session/session-manager.js +35 -5
  173. package/dist/session/session-readiness.js +1 -1
  174. package/dist/session/session.d.ts +10 -1
  175. package/dist/session/session.js +146 -5
  176. package/dist/telemetry/app-instrumented.d.ts +8 -0
  177. package/dist/telemetry/app-instrumented.js +10 -0
  178. package/dist/telemetry/bug-found.d.ts +18 -0
  179. package/dist/telemetry/bug-found.js +14 -0
  180. package/dist/telemetry/daemon-telemetry.js +29 -0
  181. package/dist/telemetry/install-source.d.ts +20 -0
  182. package/dist/telemetry/install-source.js +34 -0
  183. package/dist/telemetry/instrumentation-stall.d.ts +57 -0
  184. package/dist/telemetry/instrumentation-stall.js +103 -0
  185. package/dist/telemetry/license-activation.d.ts +48 -0
  186. package/dist/telemetry/license-activation.js +57 -0
  187. package/dist/telemetry/session-metrics.d.ts +8 -0
  188. package/dist/telemetry/session-metrics.js +10 -0
  189. package/dist/telemetry/telemetry.d.ts +3 -1
  190. package/dist/telemetry/telemetry.js +23 -3
  191. package/dist/tools/act-tools.js +166 -29
  192. package/dist/tools/assert-verdict.js +32 -7
  193. package/dist/tools/browser-tools.js +10 -0
  194. package/dist/tools/dynamic-tools.js +17 -8
  195. package/dist/tools/error-recovery.js +10 -6
  196. package/dist/tools/feedback-tools.js +31 -6
  197. package/dist/tools/invoke-tool.d.ts +1 -1
  198. package/dist/tools/invoke-tool.js +30 -8
  199. package/dist/tools/lease-hint.d.ts +30 -8
  200. package/dist/tools/lease-hint.js +65 -14
  201. package/dist/tools/lease-tools.d.ts +11 -0
  202. package/dist/tools/lease-tools.js +141 -96
  203. package/dist/tools/observe-tools.js +9 -3
  204. package/dist/tools/react-grace.d.ts +4 -0
  205. package/dist/tools/react-grace.js +6 -3
  206. package/dist/tools/read-tools.js +47 -1
  207. package/dist/tools/resolve-target.d.ts +33 -0
  208. package/dist/tools/resolve-target.js +53 -0
  209. package/dist/tools/settle-in-flight.d.ts +15 -0
  210. package/dist/tools/settle-in-flight.js +42 -2
  211. package/dist/tools/snapshot-delta.d.ts +3 -0
  212. package/dist/tools/snapshot-delta.js +67 -6
  213. package/dist/tools/tool-kit.d.ts +38 -3
  214. package/dist/tools/tool-names.d.ts +2 -0
  215. package/dist/tools/tool-names.js +2 -0
  216. package/dist/tools/tool-surface.d.ts +60 -0
  217. package/dist/tools/tool-surface.js +84 -19
  218. package/dist/tools/tools.js +61 -5
  219. package/dist/tools/unadvertised-help.js +9 -6
  220. package/docs/README.md +1 -2
  221. package/docs/actions.mdx +41 -8
  222. package/docs/agent-cheatsheet.md +6 -4
  223. package/docs/architecture.md +6 -0
  224. package/docs/benchmarks.md +60 -12
  225. package/docs/best-practices.mdx +11 -6
  226. package/docs/capabilities.mdx +204 -57
  227. package/docs/cli/affected.mdx +6 -4
  228. package/docs/cli/capsules.mdx +9 -8
  229. package/docs/cli/cloud.mdx +30 -4
  230. package/docs/cli/doctor.mdx +41 -8
  231. package/docs/cli/drive.mdx +34 -15
  232. package/docs/cli/gate.mdx +6 -6
  233. package/docs/cli/help.mdx +8 -6
  234. package/docs/cli/hunt.mdx +20 -4
  235. package/docs/cli/identify.mdx +16 -2
  236. package/docs/cli/init.mdx +35 -8
  237. package/docs/cli/kill.mdx +5 -5
  238. package/docs/cli/license.mdx +1 -1
  239. package/docs/cli/open.mdx +6 -0
  240. package/docs/cli/restart.mdx +6 -6
  241. package/docs/cli/serve.mdx +10 -3
  242. package/docs/cli/status.mdx +29 -5
  243. package/docs/cli/stop.mdx +8 -0
  244. package/docs/cli/verify.mdx +20 -1
  245. package/docs/cli/version.mdx +1 -1
  246. package/docs/cli/watch.mdx +9 -2
  247. package/docs/cli.mdx +44 -22
  248. package/docs/debugging.md +3 -1
  249. package/docs/deploy-checks.md +31 -2
  250. package/docs/desktop.mdx +276 -80
  251. package/docs/docs.json +13 -12
  252. package/docs/enterprise.md +9 -3
  253. package/docs/faq.mdx +49 -35
  254. package/docs/fixtures.md +2 -0
  255. package/docs/flows.md +7 -5
  256. package/docs/for-agents.md +3 -3
  257. package/docs/frameworks.mdx +46 -7
  258. package/docs/gate-plan.md +4 -2
  259. package/docs/gates.md +8 -6
  260. package/docs/getting-started.md +53 -12
  261. package/docs/human-control.md +2 -0
  262. package/docs/index.mdx +43 -1
  263. package/docs/install-agentic.mdx +13 -4
  264. package/docs/install-manual.mdx +6 -4
  265. package/docs/instrumentation.mdx +7 -1
  266. package/docs/integration-patterns.md +3 -1
  267. package/docs/local-registry.md +4 -2
  268. package/docs/multi-agent-testing.md +3 -1
  269. package/docs/packages/babel-plugin.mdx +15 -6
  270. package/docs/packages/browser.mdx +2 -2
  271. package/docs/packages/core.mdx +26 -16
  272. package/docs/packages/electron.mdx +5 -7
  273. package/docs/packages/eslint-plugin.mdx +5 -3
  274. package/docs/packages/next.mdx +3 -5
  275. package/docs/packages/react.mdx +3 -3
  276. package/docs/packages/server.mdx +2 -2
  277. package/docs/packages/tauri.mdx +6 -6
  278. package/docs/packages/test.mdx +18 -7
  279. package/docs/packages/vite-plugin.mdx +4 -6
  280. package/docs/packages.mdx +14 -13
  281. package/docs/platform-integration.md +2 -0
  282. package/docs/predicates.mdx +76 -7
  283. package/docs/quickstart.mdx +51 -5
  284. package/docs/recipes.mdx +63 -23
  285. package/docs/skill-file.mdx +3 -1
  286. package/docs/state-management.mdx +2 -0
  287. package/docs/system-map.md +3 -1
  288. package/docs/telemetry-contract.md +33 -5
  289. package/docs/telemetry-events.mdx +63 -18
  290. package/docs/telemetry.md +8 -5
  291. package/docs/testing.md +35 -1
  292. package/docs/token-efficiency.md +8 -2
  293. package/docs/tools-act-and-wait.mdx +64 -14
  294. package/docs/tools-act-sequence.mdx +34 -17
  295. package/docs/tools-act.mdx +24 -11
  296. package/docs/tools-assert.mdx +69 -10
  297. package/docs/tools-console.mdx +11 -7
  298. package/docs/tools-inspect.mdx +11 -8
  299. package/docs/tools-navigate.mdx +41 -5
  300. package/docs/tools-network.mdx +42 -9
  301. package/docs/tools-observe.mdx +66 -10
  302. package/docs/tools-overview.mdx +26 -23
  303. package/docs/tools-query.mdx +52 -8
  304. package/docs/tools-session-and-feedback.mdx +35 -9
  305. package/docs/tools-sessions.mdx +39 -10
  306. package/docs/tools-snapshot.mdx +57 -3
  307. package/docs/tools-state.mdx +24 -1
  308. package/docs/tools-tools-and-run.mdx +55 -24
  309. package/docs/tools-wait-for.mdx +34 -15
  310. package/docs/troubleshooting.mdx +80 -10
  311. package/docs/usage.md +46 -24
  312. package/docs/vs-chrome-devtools-mcp.mdx +41 -0
  313. package/docs/vs-playwright-mcp.mdx +42 -2
  314. package/docs/vs-screenshots.mdx +42 -1
  315. package/docs/what-is-reticle.mdx +1 -1
  316. package/docs/why-reticle.mdx +3 -1
  317. package/package.json +5 -5
  318. package/docs/desktop-apps.md +0 -265
@@ -1,11 +1,13 @@
1
1
  ---
2
- title: Manual install: by hand, on purpose
2
+ title: 'Manual install: by hand, on purpose'
3
3
  sidebarTitle: Manual install
4
4
  description: Wire Reticle by hand. Register the MCP server for your agent, add the SDK, and connect it. Every step init automates, written out.
5
5
  icon: screwdriver-wrench
6
6
  ---
7
7
 
8
- Two halves, and they are independent. **Your agent** needs the MCP server registered so the `reticle_*` tools exist. **Your app** needs the SDK so there is something for those tools to talk to. Do one and the tools appear but every call reports no session. Do the other and your app connects to a daemon nobody is asking questions.
8
+ **Installing Reticle by hand is two independent halves: register the MCP server so your agent gets the `reticle_*` tools, then add the SDK to your app so there is something for those tools to talk to.** You need both. `npx @reticlehq/server init` automates all of it, so use this page for the steps it marked `⚠`, or for an agent it does not know how to register.
9
+
10
+ Do only the first half and the tools appear but every call reports no session. Do only the second and your app connects to a daemon nobody is asking questions.
9
11
 
10
12
  You want both. Take them in that order.
11
13
 
@@ -168,7 +170,7 @@ tauri::Builder::default()
168
170
 
169
171
  IPC observation needs nothing on the Rust side. An `invoke('load_todos')` already reaches Reticle as `ipc://load_todos`.
170
172
 
171
- <Card title="Electron, and the rest of desktop" icon="display" href="/desktop-apps">
173
+ <Card title="Electron, and the rest of desktop" icon="display" href="/desktop">
172
174
  Main-process and renderer wiring, IPC observation, and what differs from the web install.
173
175
  </Card>
174
176
 
@@ -180,7 +182,7 @@ npx @reticlehq/server status
180
182
 
181
183
  A session in the output means the app connected. That. Not a tick in a checklist, is the install finished.
182
184
 
183
- If it says nothing connected, the usual cause is a port disagreement between your app and the daemon. The browser console will name the exact port it tried; set it explicitly with `VITE_RETICLE_WS_URL=ws://localhost:4400/reticle` or `reticle.connect({ url })`.
185
+ If it says nothing connected, the usual cause is a port disagreement between your app and the daemon. The browser console will carry `[Reticle] this page could not open a websocket to …` naming the exact address it tried; set it explicitly with `VITE_RETICLE_WS_URL=ws://localhost:4400/reticle` or `reticle.connect({ url })`.
184
186
 
185
187
  Still stuck? `npx @reticlehq/server doctor` checks Chromium, the daemon and the port in one command.
186
188
 
@@ -5,6 +5,8 @@ description: Register stores, emit signals, add testids. The three things that d
5
5
  icon: wrench
6
6
  ---
7
7
 
8
+ **Three things raise a Reticle verdict from "something appeared" to "the app declared success": register your store, emit named signals, and add testids.** All three live in one dev-only file, `src/reticle-dev.ts`, which `npx @reticlehq/server init` creates for you.
9
+
8
10
  Reticle works with zero instrumentation. You get DOM, network, console and routing out of the box, and that is already more than a screenshot can tell you.
9
11
 
10
12
  But there is a ceiling, and this page is about raising it. Instrumentation is the difference between a verdict that says _something appeared_ and one that says _the application declared success_.
@@ -90,14 +92,18 @@ export const Sig = {
90
92
  TOAST_SHOWN: 'toast:shown',
91
93
  } as const;
92
94
 
95
+ export type SigName = (typeof Sig)[keyof typeof Sig];
96
+
93
97
  const isDev = import.meta.env.DEV;
94
98
 
95
99
  /** Emit a Reticle signal (dev only, no-op in production). */
96
- export function emit(name: Sig, data: Record<string, unknown> = {}): void {
100
+ export function emit(name: SigName, data: Record<string, unknown> = {}): void {
97
101
  if (isDev) reticle.signal(name, data);
98
102
  }
99
103
  ```
100
104
 
105
+ `Sig` is a value, so it cannot be used as a type directly. `SigName` is the union of its values, which is what makes `emit(Sig.AUTH_GRANTED)` check and `emit('auth:granded')` fail at compile time rather than at 2am.
106
+
101
107
  Then call `emit(Sig.AUTH_GRANTED, { email })` where the thing actually succeeds. Not where you _think_ it succeeds. In the success branch, after the state update, not in the click handler.
102
108
 
103
109
  **Emit the failures too.** `auth:denied` is as valuable as `auth:granted`, for a reason the next section demonstrates.
@@ -4,6 +4,8 @@ description: 'The recommended shape for a real codebase: a minimal production fo
4
4
  icon: shapes
5
5
  ---
6
6
 
7
+ The recommended shape for a real codebase is three decisions: put `createReticleEmitter()` in one `app/emit.ts` so components depend on an interface and `@reticlehq/browser` never reaches your production bundle; emit signals from the **store layer** rather than from N call sites; and let each feature self-register its testids and signals with `registerReticleDomain`. Then turn on `reticle/require-signal-on-mutation` so the signal layer cannot silently rot.
8
+
7
9
  The basics in [Getting Started](getting-started.md) work with **zero app changes**. This doc is the _recommended_ shape for a real codebase: a minimal production footprint, a signal layer that can't silently drift, and an adoption path that starts paying off on day one, with no rewrite required.
8
10
 
9
11
  - [1. Start here: reuse what you already have](#1-start-here-reuse-what-you-already-have)
@@ -156,7 +158,7 @@ export default [
156
158
  ];
157
159
  ```
158
160
 
159
- Or turn it on with the shipped preset (warns, with empty no-op defaults you then configure): `plugin.configs.recommended`. A function that calls a mutator and a signal together passes; a mutator with no signal reports `store mutation without a mapped Reticle signal`. The documented view-level exceptions from §3 simply don't list those view callees as `mutators`, so they never fire.
161
+ Or turn it on with the shipped preset: `plugin.configs.recommended`, which sets `reticle/require-signal-on-mutation` to `warn` (with empty no-op defaults you then configure) and the plugin's other rule, `reticle/no-internal-tags`, to `error`. A function that calls a mutator and a signal together passes; a mutator with no signal reports `store mutation without a mapped Reticle signal`. The documented view-level exceptions from §3 simply don't list those view callees as `mutators`, so they never fire.
160
162
 
161
163
  ## 6. Limitation: un-scriptable tabs, use `reticle drive`
162
164
 
@@ -4,6 +4,8 @@ description: 'Exercise local, unpublished Reticle changes in a real external app
4
4
  icon: box
5
5
  ---
6
6
 
7
+ To test unpublished `@reticlehq/*` changes in a real external app, publish the workspace to a local Verdaccio with `bash scripts/local-registry.sh`, then point that app's `.npmrc` at `http://localhost:4873/` for the `@reticlehq` scope. That is the whole procedure; the rest of this page is the detail.
8
+
7
9
  > **For normal use, Reticle is on public npm.** Just `npm i -D @reticlehq/react @reticlehq/vite-plugin` (see [Getting Started](getting-started.md)). You only need this guide to test **local, unpublished changes** to the Reticle packages in a real external app before they ship.
8
10
 
9
11
  Because the `@reticlehq/*` packages depend on each other via the workspace protocol, plain `npm pack` tarballs don't resolve cleanly. The reliable way to exercise your in-progress changes in a real app is a tiny **local registry** (Verdaccio), the same path CI uses to validate a publish.
@@ -35,7 +37,7 @@ For a browser app, install `@reticlehq/react` plus the build plugin for your fra
35
37
 
36
38
  ## 2. Point your app at the local registry
37
39
 
38
- In your app's project root, add an `.npmrc` (scopes only `@reticle` to the local registry; everything else still comes from npm):
40
+ In your app's project root, add an `.npmrc`. The scope is **`@reticlehq`**, not `@reticle`: a scope typo here does not error, it silently resolves the PUBLISHED packages from npm, so you measure shipped code and think you tested your branch. Everything outside that scope still comes from npm:
39
41
 
40
42
  ```ini
41
43
  @reticlehq:registry=http://localhost:4873/
@@ -53,7 +55,7 @@ npm i -D @reticlehq/react @reticlehq/vite-plugin
53
55
 
54
56
  Then follow [Getting Started](getting-started.md): embed `reticle.connect()` (dev only) from `@reticlehq/react`, add the MCP server to your agent, and (React) `install()` the adapter from `@reticlehq/react`. For the fastest agent loop, also do [Step 6: make your app agent-legible](getting-started.md) (testids, `reticle.signal`, `registerStore`, `registerCapabilities`) and the [integration patterns](integration-patterns.md) (`createReticleEmitter` for zero prod-bundle cost).
55
57
 
56
- > **Upgrading.** The packages are currently **1.2.0**; new tools land as minor bumps. `scripts/local-registry.sh` resets Verdaccio and republishes the current version, so pull the latest in your app explicitly with `npm install @reticlehq/react@latest`:
58
+ > **Upgrading.** `scripts/local-registry.sh` resets Verdaccio and republishes at whatever version the workspace is on right now, so your app will not pick up a rebuild on its own. Pull it explicitly with `@latest`:
57
59
  >
58
60
  > ```bash
59
61
  > npm i -D @reticlehq/react@latest @reticlehq/vite-plugin@latest @reticlehq/eslint-plugin@latest
@@ -4,6 +4,8 @@ description: 'Several apps at once, ports that shift between runs, and many agen
4
4
  icon: users
5
5
  ---
6
6
 
7
+ Many agents can drive the same app at once without a browser each: `reticle_lease {action:"acquire"}` hands each one an **isolated context** (its own cookies, storage and DOM) from a single shared headless Chromium. One daemon per machine, and identity is the **app** (a stable `projectId` stamped by the build plugin), not the port, so an app that boots on `:3001` today is still the same app.
8
+
7
9
  Reticle is built for the messy real world: several apps running at once, ports that shift between runs, and many agents driving different flows of the same app in parallel, without each one spinning up its own Chromium. This page explains how that works and how to use it.
8
10
 
9
11
  ## The mental model
@@ -11,7 +13,7 @@ Reticle is built for the messy real world: several apps running at once, ports t
11
13
  - **One daemon per machine.** `reticle mcp` discovers a running daemon (via `~/.reticle`) or starts one. A crashed daemon's stale pidfile is reclaimed automatically, so you never chase "port already in use" or a zombie server.
12
14
  - **Identity is the app, not the port.** The build plugin stamps a stable `projectId` that travels in every connection. If your Next app usually runs on `:3000` but boots on `:3001` today, Reticle still knows which app it is, and an agent scoped to project A will never accidentally drive project B's tab. Origin is only a fallback hint.
13
15
  - **One browser, many contexts.** When agents need their own headless tabs, the daemon's **browser pool** launches a single Chromium and hands out isolated contexts (one per flow). They are cheap, and capped so a big fan-out can't exhaust the machine. Over-cap requests queue.
14
- - **Attach-only.** Reticle never starts your dev server. It connects to an app you're already running (or opens a headless tab pointed at it).
16
+ - **Attach-only daemon.** The daemon never starts your dev server. It connects to an app that is running (or opens a headless tab pointed at it). When nothing is running, the agent starts your project's own dev script in the background and tells you. A build process spawned by a background daemon would be invisible, unconsented, and orphaned when the daemon exits.
15
17
 
16
18
  ## Manual testing: ~5 minutes
17
19
 
@@ -4,22 +4,31 @@ description: A Babel plugin that stamps data-reticle-source on JSX host elements
4
4
  icon: file-code
5
5
  ---
6
6
 
7
- `@reticlehq/babel-plugin` puts the source location back. React 19 dropped `_debugSource`, which is what source mapping used to rely on, so without this the tools can tell you the component name but not the file.
7
+ `@reticlehq/babel-plugin` stamps `data-reticle-source="file:line:col"` onto your JSX at build time, in dev only, so Reticle can name the file a DOM node came from. **You probably do not install this one directly**: [`@reticlehq/vite-plugin`](/packages/vite-plugin) and [`@reticlehq/next`](/packages/next) both pull it in. Reach for it only if you drive Babel yourself.
8
8
 
9
- **Version 2.8.0. Apache 2.0. CommonJS. No runtime dependencies. Peer dependency: `@babel/core ^7`.**
9
+ React 19 dropped `_debugSource`, which is what source mapping used to rely on, so without this the tools can tell you the component name but not the file.
10
+
11
+ **Apache 2.0. CommonJS. No runtime dependencies. Peer dependency: `@babel/core ^7`.**
10
12
 
11
13
  ## What it stamps
12
14
 
13
15
  ```jsx
14
- <button onClick={submit}>Sign in</button>
16
+ // src/components/Login.jsx
17
+ export function Login({ submit }) {
18
+ return <button onClick={submit}>Sign in</button>;
19
+ }
15
20
  ```
16
21
 
17
22
  becomes, in dev only:
18
23
 
19
24
  ```jsx
20
- <button onClick={submit} data-reticle-source="src/components/Login.tsx:81:6">
21
- Sign in
22
- </button>
25
+ export function Login({ submit }) {
26
+ return (
27
+ <button onClick={submit} data-reticle-source="src/components/Login.jsx:2:9">
28
+ Sign in
29
+ </button>
30
+ );
31
+ }
23
32
  ```
24
33
 
25
34
  The path is relative to `process.cwd()`, with backslashes normalized to forward slashes. A file with no name stamps `unknown`.
@@ -4,9 +4,9 @@ description: The Reticle instrumentation SDK that runs inside your page, observi
4
4
  icon: window
5
5
  ---
6
6
 
7
- `@reticlehq/browser` is the half of Reticle that lives in your app. It installs the observers, builds semantic snapshots, executes actions, and talks to the bridge.
7
+ `@reticlehq/browser` is the Reticle SDK that runs inside your page. It installs the observers, builds semantic snapshots, executes actions, and talks to the bridge. Install it directly only if you are **not** on React or want to call `connect()` yourself, because [`@reticlehq/react`](/packages/react) already re-exports all of it.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `@reticlehq/core` and `@testing-library/dom`.**
9
+ **Apache 2.0. Depends on `@reticlehq/core` and `@testing-library/dom`.**
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -1,12 +1,12 @@
1
1
  ---
2
- title: "@reticlehq/core"
3
- description: The shared wire contract for Reticle: constants, zod schemas, and the isomorphic kernel every other package imports.
2
+ title: '@reticlehq/core'
3
+ description: 'The shared wire contract for Reticle: constants, zod schemas, and the isomorphic kernel every other package imports.'
4
4
  icon: cube
5
5
  ---
6
6
 
7
- `@reticlehq/core` is the bottom of the dependency graph. Every message that crosses a boundary, browser to bridge, bridge to agent, is defined here as a named constant plus a zod schema.
7
+ `@reticlehq/core` is the shared wire contract at the bottom of Reticle's dependency graph. Every message that crosses a boundary, browser to bridge, bridge to agent, is defined here as a named constant plus a zod schema. **You almost certainly do not need to install it**: it arrives with whatever Reticle package you did install, and you only reach for it directly if you are building your own integration against the wire format.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `zod` and nothing else.**
9
+ **Apache 2.0. Depends on `zod` and nothing else.**
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -27,7 +27,7 @@ npm i @reticlehq/core
27
27
  | Subpath | What it is |
28
28
  | --- | --- |
29
29
  | `.` | The ESM barrel. Types at `dist/index.d.ts` |
30
- | `./desktop-contract` | A CommonJS shim, generated at build, for the five strings the desktop adapters share |
30
+ | `./desktop-contract` | A CommonJS shim, generated at build, for the strings the desktop adapters share |
31
31
  | `./schema/*.json` | Generated JSON Schema for the wire types |
32
32
  | `./package.json` | The manifest |
33
33
 
@@ -38,7 +38,7 @@ npm i @reticlehq/core
38
38
 
39
39
  ## What the barrel exports
40
40
 
41
- `src/index.ts` is a pure barrel: 26 `export *` re-exports plus one named export. Grouped by what they are for:
41
+ `src/index.ts` is a pure barrel: a stack of `export *` re-exports plus one named export. Grouped by what they are for, and not exhaustive:
42
42
 
43
43
  ### Wire constants
44
44
 
@@ -50,7 +50,9 @@ npm i @reticlehq/core
50
50
 
51
51
  ### Enum-like frozen objects
52
52
 
53
- `EventType`, `ActionType`, `ElementState`, `QueryBy`, `MessageKind`, `ReticleCommand`, `PhenomenonType`, `SettleReason`, `ComponentStateReason`, `ComponentStateResult`, `InputMode`, `InputModeReason`, `ActionWarning`, `DriveErrorCode`, `TruncationChannel`, `EventAttribution`, `PerfMetric`, `PresenterMode`, `SnapshotMode`, `VisualReason`, `CONSOLE_LEVELS`, `Verified`.
53
+ `EventType`, `ActionType`, `ElementState`, `QueryBy`, `MessageKind`, `ReticleCommand`, `PhenomenonType`, `SettleReason`, `ComponentStateReason`, `InputMode`, `InputModeReason`, `ActionWarning`, `DriveErrorCode`, `TruncationChannel`, `EventAttribution`, `PerfMetric`, `PresenterMode`, `SnapshotMode`, `VisualReason`, `CONSOLE_LEVELS`, `Verified`.
54
+
55
+ `ComponentStateResult` is an interface, not one of these: it is a type-only export, so importing it as a value fails with `'ComponentStateResult' only refers to a type, but is being used as a value here`.
54
56
 
55
57
  ### Zod schemas
56
58
 
@@ -78,18 +80,26 @@ Pure, isomorphic, and safe on both sides of the wire:
78
80
  ## The desktop contract
79
81
 
80
82
  ```ts
81
- import { DESKTOP_CONTRACT, RETICLE_IPC_GLOBAL } from '@reticlehq/core/desktop-contract';
83
+ import { RETICLE_IPC_GLOBAL, RETICLE_CAPTURE_CHANNEL } from '@reticlehq/core/desktop-contract';
82
84
  ```
83
85
 
84
- Five strings shared by the Electron preload and the Tauri crate, in CommonJS because a preload script is required before any ESM transform:
86
+ Six strings shared by the Electron preload and the Tauri crate, in CommonJS because a preload script is required before any ESM transform:
87
+
88
+ | Constant | Value |
89
+ | ------------------------------- | ---------------------------------------------------------- |
90
+ | `RETICLE_IPC_GLOBAL` | `__reticleIpc` |
91
+ | `RETICLE_CAPTURE_CHANNEL` | `__reticle:capture` |
92
+ | `RETICLE_CAPTURE_FILE_PREFIX` | `reticle-capture-` |
93
+ | `RETICLE_TAURI_CAPTURE_COMMAND` | `reticle_capture` |
94
+ | `RETICLE_FULL_PAGE_UNSUPPORTED` | `full-page-unsupported`, for a refused full-page capture |
95
+ | `RETICLE_NOT_COMPOSITED` | `window-not-composited`, for a window that had not painted |
85
96
 
86
- | Constant | Value |
87
- | ------------------------------- | -------------------------------------------------------- |
88
- | `RETICLE_IPC_GLOBAL` | `__reticleIpc` |
89
- | `RETICLE_CAPTURE_CHANNEL` | `__reticle:capture` |
90
- | `RETICLE_CAPTURE_FILE_PREFIX` | `reticle-capture-` |
91
- | `RETICLE_TAURI_CAPTURE_COMMAND` | `reticle_capture` |
92
- | `RETICLE_FULL_PAGE_UNSUPPORTED` | The `VisualReason` value for a refused full-page capture |
97
+ <Warning>
98
+ The subpath exports those six constants individually and nothing else. The `DESKTOP_CONTRACT`
99
+ record they are collected into exists on the ESM side only, so importing it here fails with
100
+ `Module '"@reticlehq/core/desktop-contract"' has no exported member 'DESKTOP_CONTRACT'`. Import
101
+ `DESKTOP_CONTRACT` from `@reticlehq/core` instead.
102
+ </Warning>
93
103
 
94
104
  ## The JSON schemas
95
105
 
@@ -4,9 +4,9 @@ description: The Reticle adapter for Electron, making main-process IPC observabl
4
4
  icon: display
5
5
  ---
6
6
 
7
- `@reticlehq/electron` reaches the two places the renderer cannot: the IPC boundary, and `capturePage()`.
7
+ `@reticlehq/electron` reaches the two places the renderer-side SDK cannot: the IPC boundary, and `capturePage()`. Install it if your app is Electron and you want main-process IPC observable or the window screenshottable. A web app never needs it.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `@reticlehq/core`. Peer dependency: `electron >= 22`. No build step.**
9
+ **Apache 2.0. Depends on `@reticlehq/core`. Peer dependency: `electron >= 22`. No build step.**
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -62,15 +62,13 @@ It then exposes `window.__reticleIpc` through the context bridge, with three mem
62
62
  ```js
63
63
  const { installReticleCapture } = require('@reticlehq/electron/main');
64
64
 
65
- const win = new BrowserWindow({
66
- /* ... */
67
- });
65
+ const win = new BrowserWindow({/* ... */});
68
66
  installReticleCapture(win);
69
67
  ```
70
68
 
71
69
  `installReticleCapture(win): void` is a no-op on `null` or `undefined`, tracks the window until it closes, and registers the capture handler once for the whole app.
72
70
 
73
- The handler calls `webContents.capturePage()`, writes the PNG into a per-process `0700` captures directory as `reticle-capture-<n>.png`, and **returns the file path rather than the bytes**, because the transport has a 64KB cap. It returns `null` on an empty image or any error, and it refuses `fullPage: true` explicitly rather than silently returning a viewport shot.
71
+ The handler calls `webContents.capturePage()`, writes the PNG into a per-process `0700` captures directory as `reticle-capture-<n>.png`, and **returns the file path rather than the bytes**, because the transport has a 64KB cap. It refuses `fullPage: true` explicitly rather than silently returning a viewport shot, and it throws named reasons rather than one blanket silence: `full-page-unsupported` for that refusal, `window-not-composited` when the window had nothing painted yet, `no usable webContents to capture` when the window is gone. The preload re-throws the full-page refusal and turns everything else into `null`.
74
72
 
75
73
  ## Renderer wiring
76
74
 
@@ -82,6 +80,6 @@ reticle.connect({ allowInProduction: true });
82
80
 
83
81
  Or, with the Vite plugin, `reticle({ desktop: true })`.
84
82
 
85
- <Card title="Desktop setup in full" icon="display" href="/desktop-apps">
83
+ <Card title="Desktop setup in full" icon="display" href="/desktop">
86
84
  Electron and Tauri wiring, and what `reticle doctor` checks for each.
87
85
  </Card>
@@ -4,9 +4,11 @@ description: Two lint rules that keep Reticle's signal layer and its comment hyg
4
4
  icon: shield-check
5
5
  ---
6
6
 
7
- `@reticlehq/eslint-plugin` exists because conventions rot. Someone adds a mutation, forgets the signal, and every verdict for that flow quietly drops a grade. Nothing breaks, the tests still pass, the evidence just gets weaker. A lint rule is the only way a convention survives contact with a deadline.
7
+ `@reticlehq/eslint-plugin` is two ESLint rules that keep Reticle's signal layer self-enforcing. It is entirely optional: nothing in Reticle needs it, and you want it only once your app emits signals and you would like the compiler to notice when a new mutation forgets one.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `@typescript-eslint/utils`. Peer dependency: `eslint >= 9`.**
9
+ It exists because conventions rot. Someone adds a mutation, forgets the signal, and every verdict for that flow quietly drops a grade. Nothing breaks, the tests still pass, the evidence just gets weaker. A lint rule is the only way a convention survives contact with a deadline.
10
+
11
+ **Apache 2.0. Depends on `@typescript-eslint/utils`. Peer dependency: `eslint >= 9`.**
10
12
 
11
13
  ```bash
12
14
  npm i -D @reticlehq/eslint-plugin
@@ -33,7 +35,7 @@ Warns when a store mutation happens without a mapped Reticle signal.
33
35
 
34
36
  | Option | Type | Default |
35
37
  | --- | --- | --- |
36
- | `mutators` | `string[]` | `[]`. With the default the rule is a no-op, which is deliberate: name your own mutators |
38
+ | `mutators` | `string[]` | `[]`. With the default the rule is a no-op, which is deliberate: name your own mutators. Matched by BARE NAME, so `set` also matches `map.set()` and `headers.set()`; prefer an unambiguous name |
37
39
  | `signalCallee` | `string \| string[]` | `['reticleSignal', 'signal']` |
38
40
 
39
41
  ```js
@@ -4,9 +4,9 @@ description: Next.js source mapping for Reticle without giving up SWC, via a dev
4
4
  icon: n
5
5
  ---
6
6
 
7
- `@reticlehq/next` gives Next.js users the same `data-reticle-source` stamping the Vite plugin does, without asking them to migrate to Babel.
7
+ `@reticlehq/next` gives a Next.js app the same `data-reticle-source` stamping the Vite plugin does, without asking it to migrate off SWC to Babel. Install it if your app is Next.js; on Vite use [`@reticlehq/vite-plugin`](/packages/vite-plugin) instead. Unlike the Vite plugin it does **not** inject `connect()`, so you still call that yourself.
8
8
 
9
- **Version 2.8.0. Apache 2.0. CommonJS, no build step. Depends on `@babel/core` and `@reticlehq/babel-plugin`. Peer dependency: `next >= 13`.**
9
+ **Apache 2.0. CommonJS, no build step. Depends on `@babel/core` and `@reticlehq/babel-plugin`. Peer dependency: `next >= 13`.**
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -22,9 +22,7 @@ npm i -D @reticlehq/next @reticlehq/react
22
22
  // next.config.js
23
23
  const { withReticle } = require('@reticlehq/next');
24
24
 
25
- module.exports = withReticle({
26
- /* your config */
27
- });
25
+ module.exports = withReticle({/* your config */});
28
26
  ```
29
27
 
30
28
  <Note>
@@ -4,9 +4,9 @@ description: The React kit for Reticle, mapping a DOM node to its component stac
4
4
  icon: react
5
5
  ---
6
6
 
7
- `@reticlehq/react` is the package most React users install, and usually the only SDK package they need. It re-exports the whole browser SDK and adds the fiber-tree work on top.
7
+ `@reticlehq/react` is the Reticle kit for React apps, and usually the only SDK package a React user needs: it re-exports the whole browser SDK and adds fiber-tree work that maps a DOM node to its component stack, its hook state, and its source file. If your app is React, install this one and not `@reticlehq/browser`.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `@reticlehq/browser` and `@reticlehq/core`. Peer dependency: `react >= 18`.**
9
+ **Apache 2.0. Depends on `@reticlehq/browser` and `@reticlehq/core`. Peer dependency: `react >= 18`.**
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -34,7 +34,7 @@ reticle.connect();
34
34
  | --- | --- | --- |
35
35
  | `install` | `(): void` | Idempotent. Registers the `react` adapter and installs the render meter. Call it once, before `connect()` |
36
36
 
37
- The [Vite plugin](/packages/vite-plugin) injects exactly these two lines for you, so a Vite user never writes them.
37
+ The [Vite plugin](/packages/vite-plugin) injects `install()` and `reticle.connect()` for you, so a Vite user never writes either line.
38
38
 
39
39
  ### Mapping a node to its component
40
40
 
@@ -4,9 +4,9 @@ description: The Reticle bridge, MCP server, and the reticle CLI, all in one Nod
4
4
  icon: server
5
5
  ---
6
6
 
7
- `@reticlehq/server` hosts the WebSocket your app dials into, exposes the MCP tools your agent calls, and ships the `reticle` binary.
7
+ `@reticlehq/server` is the Reticle bridge, MCP server and CLI in one Node package. It hosts the WebSocket your app dials into, exposes the MCP tools your agent calls, and ships the `reticle` binary. **You do not add it to your project**: your agent runs it with `npx @reticlehq/server mcp`.
8
8
 
9
- **Version 2.8.0. Licensed under `SEE LICENSE IN LICENSE`, not Apache 2.0 like the rest.** This is the package the enterprise terms apply to.
9
+ **Licensed under `SEE LICENSE IN LICENSE`, not Apache 2.0 like the rest.** This is the package the enterprise terms apply to.
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -4,17 +4,17 @@ description: The Rust crate that gives a Tauri app Reticle screenshots and headl
4
4
  icon: rust
5
5
  ---
6
6
 
7
- `reticle-tauri` is a Rust crate on crates.io, not an npm package. It is versioned **independently** of the ten npm packages, and is currently **0.1.0**.
7
+ `reticle-tauri` is a Rust crate on crates.io, not an npm package. You need it only if your Tauri app should answer `reticle_screenshot`, or run headless in CI. Everything else about a Tauri app, including IPC observation, works without it.
8
8
 
9
- **MIT licensed, unlike the Apache 2.0 npm packages. Edition 2021.**
9
+ It is versioned **independently** of the ten npm packages. **MIT licensed, unlike the Apache 2.0 npm packages. Edition 2021.**
10
10
 
11
11
  ## Why it exists, and why it is small
12
12
 
13
13
  Tauri IPC observation needs nothing on the Rust side. An `invoke('load_todos')` already reaches Reticle as `ipc://load_todos` through the SDK in the webview. You only need this crate for two things: screenshots, and headless mode.
14
14
 
15
15
  <Warning>
16
- Do not expect this to be version `2.7`. Matching the npm version would imply a release cadence the
17
- crate does not have, and it would make every npm release a crates.io release for no reason.
16
+ Do not expect this to match the npm version. Doing so would imply a release cadence the crate does
17
+ not have, and it would make every npm release a crates.io release for no reason.
18
18
  </Warning>
19
19
 
20
20
  ## Install
@@ -48,7 +48,7 @@ There are no public traits and no macros.
48
48
 
49
49
  ### `reticle_capture`
50
50
 
51
- Runs on a blocking thread, writes a PNG into a temporary directory as `reticle-capture-<nanos>.png`, and returns the **path**, not the bytes. Full-page capture is supported on Linux only; elsewhere it returns `FULL_PAGE_UNSUPPORTED`, which is pinned to core's `VisualReason` value so the refusal is the same string everywhere.
51
+ Runs on a blocking thread, writes a PNG into a per-process `0700` directory under the OS temp dir as `reticle-capture-<nanos>.png`, and returns the **path**, not the bytes. Full-page capture is supported on Linux only; elsewhere it returns `FULL_PAGE_UNSUPPORTED`, which is pinned to core's `VisualReason` value so the refusal is the same string everywhere.
52
52
 
53
53
  ### `on_page_load`
54
54
 
@@ -58,6 +58,6 @@ Hides the window on `PageLoadEvent::Finished` when the environment variable `RET
58
58
 
59
59
  A Tauri app with the default CSP runs perfectly and never connects to Reticle, with nothing said anywhere. [`reticle doctor`](/cli/doctor) diagnoses this explicitly on a desktop project, because it is invisible otherwise.
60
60
 
61
- <Card title="Desktop setup in full" icon="display" href="/desktop-apps">
61
+ <Card title="Desktop setup in full" icon="display" href="/desktop">
62
62
  The CSP entry, the headless flag, and how capture differs from a browser screenshot.
63
63
  </Card>
@@ -4,9 +4,9 @@ description: The Reticle spec runner for CI, driving the tools directly with no
4
4
  icon: vial
5
5
  ---
6
6
 
7
- `@reticlehq/test` turns an interactive session into a suite that runs on every pull request, without an agent and without paying model tokens to re-derive the same checks.
7
+ `@reticlehq/test` is Reticle's spec runner for CI. It turns an interactive session into a suite that runs on every pull request, without an agent and without paying model tokens to re-derive the same checks. Install it when you want unattended verification; skip it if you only ever drive Reticle interactively through MCP.
8
8
 
9
- **Version 2.8.0. Licensed under `SEE LICENSE IN LICENSE`. Depends on `@reticlehq/core` and `@reticlehq/server`. Optional peer dependency: `vitest ^3.2.6`.**
9
+ **Licensed under `SEE LICENSE IN LICENSE`. Depends on `@reticlehq/core` and `@reticlehq/server`. Optional peer dependency: `vitest ^3.2.6`.**
10
10
 
11
11
  ```bash
12
12
  npm i -D @reticlehq/test
@@ -27,7 +27,7 @@ const { results, summary } = await runSpecs({
27
27
  buildContext: (invoke) => createTestContext(invoke),
28
28
  now: () => Date.now(),
29
29
  });
30
- printSummary(summary);
30
+ printSummary(summary, results, (line) => process.stdout.write(`${line}\n`));
31
31
  await run.close();
32
32
  ```
33
33
 
@@ -47,9 +47,10 @@ await run.close();
47
47
  ## Writing specs
48
48
 
49
49
  ```ts
50
- import { reticleTest } from '@reticlehq/test';
50
+ import { reticleTest, type TestContext } from '@reticlehq/test';
51
51
 
52
- reticleTest('login grants a session', async (t) => {
52
+ reticleTest('login grants a session', async (spec) => {
53
+ const t = spec as TestContext;
53
54
  await t.fill('email', 'user@example.com');
54
55
  await t.fill('password', 'hunter2');
55
56
  await t.actAndWait('login-submit', 'click', { kind: 'signal', name: 'auth:granted' });
@@ -60,6 +61,13 @@ reticleTest('login grants a session', async (t) => {
60
61
 
61
62
  `reticleTest(name: string, fn: SpecFn): void` registers into a module-level registry. `register`, `getRegistered` and `clearRegistry` are exported for anyone driving it themselves.
62
63
 
64
+ <Note>
65
+ `SpecFn` types its argument as `SpecContext`, which declares only `invoke`. The runner actually
66
+ hands the spec whatever `buildContext` returned, which is a `TestContext`. In plain JavaScript
67
+ this is invisible; in TypeScript you get `Property 'fill' does not exist on type 'SpecContext'`
68
+ unless you narrow it as above.
69
+ </Note>
70
+
63
71
  ## The test context
64
72
 
65
73
  `createTestContext(invoke: ToolInvoker, options?: TestContextOptions): TestContext`, where `TestContextOptions` is `{ sessionId?: string; defaultTimeoutMs?: number }` and the timeout defaults to `DEFAULT_ASSERT_TIMEOUT_MS`.
@@ -86,7 +94,8 @@ reticleTest('login grants a session', async (t) => {
86
94
  | --- | --- |
87
95
  | `runOne` | `(spec: ReticleSpec, opts: RunnerOptions) => Promise<SpecResult>` |
88
96
  | `runSpecs` | `(opts: RunnerOptions) => Promise<{ results: SpecResult[]; summary: RunSummary }>` |
89
- | `summarize` / `printSummary` | Build and print the summary |
97
+ | `summarize` | `(results: readonly SpecResult[]) => RunSummary` |
98
+ | `printSummary` | `(summary: RunSummary, results: readonly SpecResult[], print: (line: string) => void) => void`. The sink is injected, never `console` |
90
99
  | `toJUnitXml` / `writeJUnit` | JUnit output for CI |
91
100
 
92
101
  `RunnerOptions` is `{ invoke; buildContext; now; print?; specs? }`. `RunSummary` is `{ total, passed, failed, skipped, ok }`.
@@ -101,7 +110,9 @@ A recorded flow becomes a spec without being rewritten:
101
110
 
102
111
  `ReticleSkip` and `isSkip` for skipping, `ReticleAssertionError` with an `AssertionDetail`, `ReticleQueryEmptyError` when a query matched nothing.
103
112
 
104
- Constants: `TestStatus`, `SpecKind`, `SpecOutcome`, `SpecMessage`, `PredicateKind`, `STATUS_GLYPH`, `JUnit`, `SKIP_REASON_REAL_INPUT`, `PROBE_TESTID`.
113
+ Constants: `TestStatus`, `SpecKind`, `SpecOutcome`, `SpecMessage`, `PredicateKind`, `STATUS_GLYPH`, `SUMMARY_FOOTER_PREFIX`, `JUnit`, `DEFAULT_JUNIT_SUITE_NAME`, `SKIP_REASON_REAL_INPUT`, `PROBE_TESTID`, `FLOW_LOAD_ERROR_PREFIX`, `DEFAULT_ASSERT_TIMEOUT_MS`.
114
+
115
+ Also exported: `resolveTestid`, and the standalone `expectInputModeReal`, `InputModeTracker` and `readInputMode`.
105
116
 
106
117
  <Card title="Turning a session into a suite" icon="flask" href="/testing">
107
118
  Writing specs that bind to signals rather than DOM structure.
@@ -4,9 +4,9 @@ description: One line in vite.config.ts that stamps source locations on your JSX
4
4
  icon: bolt
5
5
  ---
6
6
 
7
- `@reticlehq/vite-plugin` does the two things everyone forgets: wiring the SDK, and enabling source mapping.
7
+ `@reticlehq/vite-plugin` is one line in `vite.config.ts` that does the two things everyone forgets: injecting `reticle.connect()` and enabling source mapping. Install it if your app builds with Vite. On Next.js use [`@reticlehq/next`](/packages/next) instead; on any other bundler, wire [`@reticlehq/browser`](/packages/browser) by hand.
8
8
 
9
- **Version 2.8.0. Apache 2.0. Depends on `@babel/core`, `@reticlehq/babel-plugin` and `@reticlehq/core`. Optional peer dependency: `vite >= 4`.**
9
+ **Apache 2.0. Depends on `@babel/core`, `@reticlehq/babel-plugin` and `@reticlehq/core`. Optional peer dependency: `vite >= 4`.**
10
10
 
11
11
  ## Install and use
12
12
 
@@ -42,9 +42,7 @@ A virtual module at `/@reticle-connect`, added to the HTML, whose contents are:
42
42
  ```ts
43
43
  import { reticle, install } from '@reticlehq/react';
44
44
  install();
45
- reticle.connect({
46
- /* resolved args */
47
- });
45
+ reticle.connect({/* resolved args */});
48
46
  ```
49
47
 
50
48
  Plus a dynamic import of your dev module if one exists. It looks for `src/reticle-dev.ts`, `.js`, `.tsx` or `.jsx`, in that order, and leaves the file alone if you have one: that file is yours to edit.
@@ -91,6 +89,6 @@ reticle({ desktop: true });
91
89
 
92
90
  A packaged Electron or Tauri renderer is a production build loaded from a custom protocol, so the default guards would refuse. This one flag drops `apply: 'serve'` and opts the connect call into production.
93
91
 
94
- <Card title="Desktop wiring in full" icon="display" href="/desktop-apps">
92
+ <Card title="Desktop wiring in full" icon="display" href="/desktop">
95
93
  Including the Tauri CSP whose failure is completely silent.
96
94
  </Card>
package/docs/packages.mdx CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Packages: what is in the box
2
+ title: 'Packages: what is in the box'
3
3
  sidebarTitle: Packages
4
4
  description: Every Reticle package. What it does, why it exists, when you actually need it, and what it depends on.
5
5
  icon: cubes
@@ -8,9 +8,9 @@ icon: cubes
8
8
  Reticle is eleven packages, and most people install two. This page exists so you can tell which two, and so that the ones you skip stop looking mysterious.
9
9
 
10
10
  <Note>
11
- Ten npm packages ship together at the same version, all at **2.8.0**, verified against the
12
- registry. The Rust crate is versioned **independently** and is currently **0.1.0**. That is
13
- deliberate, not drift.
11
+ The ten npm packages ship together at one version: whatever `npm view @reticlehq/core version`
12
+ says, every other `@reticlehq/*` package says too. The Rust crate `reticle-tauri` is versioned
13
+ **independently**. That is deliberate, not drift.
14
14
  </Note>
15
15
 
16
16
  ## Which ones do I need?
@@ -99,7 +99,7 @@ npx @reticlehq/server mcp
99
99
 
100
100
  ## @reticlehq/react
101
101
 
102
- **The kit most React users install.** It re-exports the browser sensor. So one install gives you both `reticle` and `registerCapabilities`. And maps a DOM node to its React component and source file through the fiber tree.
102
+ **The kit most React users install.** It re-exports the browser sensor, so one install gives you `reticle`, `registerCapabilities` and everything else, and it maps a DOM node to its React component and source file through the fiber tree.
103
103
 
104
104
  **Why it exists:** this is the package that turns "the button is broken" into `src/components/Login.tsx:81`. Finding a bug is half the job. Reticle's answer to the other half lives here.
105
105
 
@@ -120,7 +120,10 @@ npm i -D @reticlehq/react
120
120
  **Depends on:** `@babel/core`, `@reticlehq/babel-plugin`, `@reticlehq/core`. **Peer:** `vite`.
121
121
 
122
122
  ```ts
123
+ import { defineConfig } from 'vite';
124
+ import react from '@vitejs/plugin-react';
123
125
  import { reticle } from '@reticlehq/vite-plugin';
126
+
124
127
  export default defineConfig({ plugins: [reticle(), react()] });
125
128
  ```
126
129
 
@@ -148,9 +151,7 @@ export default defineConfig({ plugins: [reticle(), react()] });
148
151
 
149
152
  ```js
150
153
  const { withReticle } = require('@reticlehq/next');
151
- module.exports = withReticle({
152
- /* your config */
153
- });
154
+ module.exports = withReticle({/* your config */});
154
155
  ```
155
156
 
156
157
  <Note>
@@ -176,7 +177,7 @@ module.exports = withReticle({
176
177
 
177
178
  ## @reticlehq/eslint-plugin
178
179
 
179
- **One rule: state changed, so a signal must fire.**
180
+ **Two rules, the main one being: state changed, so a signal must fire.**
180
181
 
181
182
  **Why it exists:** signals are the strongest evidence Reticle can use, and they rot the way all conventions rot. Someone adds a mutation, forgets the signal, and every verdict for that flow quietly drops a grade. Nothing breaks. The tests still pass. The evidence just gets weaker.
182
183
 
@@ -198,7 +199,7 @@ A lint rule makes the signal layer self-enforcing, which is the only way a conve
198
199
 
199
200
  ## reticle-tauri
200
201
 
201
- **The Rust crate**, on crates.io, currently **0.1.0**. Versioned independently of the npm packages.
202
+ **The Rust crate**, on crates.io. Versioned independently of the npm packages.
202
203
 
203
204
  **Why it exists:** Tauri IPC observation needs nothing on the Rust side; an `invoke('load_todos')` already reaches Reticle as `ipc://load_todos`. You only need this crate for screenshots and headless mode.
204
205
 
@@ -208,10 +209,10 @@ reticle-tauri = "0.1"
208
209
  ```
209
210
 
210
211
  <Warning>
211
- Do not expect this to be `2.7`. Matching the npm version would imply a release cadence the crate
212
- does not have, and it would make every npm release a crates.io release for no reason.
212
+ Do not expect this to match the npm version. Doing so would imply a release cadence the crate does
213
+ not have, and it would make every npm release a crates.io release for no reason.
213
214
  </Warning>
214
215
 
215
- <Card title="Desktop setup in full" icon="display" href="/desktop-apps">
216
+ <Card title="Desktop setup in full" icon="display" href="/desktop">
216
217
  Electron and Tauri wiring, including the Tauri CSP whose failure is silent.
217
218
  </Card>