@reticlehq/server 3.3.0 → 3.4.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 (299) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +7 -5
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse.d.ts +8 -1
  20. package/dist/command/cli/cli-parse.js +21 -3
  21. package/dist/command/cli/cli-update-commands.js +8 -1
  22. package/dist/command/cli/cli-verify.d.ts +24 -3
  23. package/dist/command/cli/cli-verify.js +54 -11
  24. package/dist/command/cli/cloud-cli.js +5 -2
  25. package/dist/command/cli/cloud-kit.js +3 -5
  26. package/dist/command/cli/cloud-login.js +21 -3
  27. package/dist/command/cli/demo/demo-page.js +6 -3
  28. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  29. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  30. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  31. package/dist/command/cli/launch/cli-launch.js +21 -9
  32. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  33. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  34. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  35. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  36. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  37. package/dist/command/cli/setup-mcp-cli.js +13 -13
  38. package/dist/command/cli/tutorial.d.ts +2 -2
  39. package/dist/command/cli/tutorial.js +21 -7
  40. package/dist/command/cli.js +62 -10
  41. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  42. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  43. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  44. package/dist/command/daemon/daemon-resolve.js +1 -1
  45. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  46. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  47. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  48. package/dist/command/dev/stale-issue-guard.js +8 -8
  49. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  50. package/dist/command/setup/bringup/connect-lease.js +38 -0
  51. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  52. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  53. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  54. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  55. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  56. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  57. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  58. package/dist/command/setup/bringup/static-page-server.js +151 -0
  59. package/dist/command/setup/desktop-shape.d.ts +4 -0
  60. package/dist/command/setup/desktop-shape.js +18 -2
  61. package/dist/command/setup/init/init-port.d.ts +15 -0
  62. package/dist/command/setup/init/init-port.js +44 -0
  63. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  64. package/dist/command/setup/init/init-runtime.js +39 -2
  65. package/dist/command/setup/node-effects.d.ts +27 -14
  66. package/dist/command/setup/node-effects.js +80 -44
  67. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  68. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  69. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  70. package/dist/command/setup/probe/page-probe.js +18 -3
  71. package/dist/command/setup/run-setup.d.ts +26 -1
  72. package/dist/command/setup/run-setup.js +96 -13
  73. package/dist/command/setup/session-pick.d.ts +13 -0
  74. package/dist/command/setup/session-pick.js +20 -0
  75. package/dist/command/setup/setup-command.d.ts +2 -14
  76. package/dist/command/setup/setup-command.js +105 -31
  77. package/dist/command/setup/setup-mcp.js +3 -3
  78. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  79. package/dist/command/setup/terminal/wait-progress.js +17 -0
  80. package/dist/features/crawl/crawl-empty.js +1 -1
  81. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  82. package/dist/features/crawl/crawl-tools.js +57 -0
  83. package/dist/features/crawl/crawl.js +171 -157
  84. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  85. package/dist/features/exhaust/app-coverage.js +31 -0
  86. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  87. package/dist/features/exhaust/code-coverage.js +77 -0
  88. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  89. package/dist/features/exhaust/exhaust-run.js +82 -0
  90. package/dist/features/exhaust/explorer.d.ts +103 -0
  91. package/dist/features/exhaust/explorer.js +195 -0
  92. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  93. package/dist/features/exhaust/failure-branches.js +92 -0
  94. package/dist/features/exhaust/ledger.d.ts +202 -0
  95. package/dist/features/exhaust/ledger.js +252 -0
  96. package/dist/features/exhaust/session-fold.d.ts +31 -0
  97. package/dist/features/exhaust/session-fold.js +79 -0
  98. package/dist/features/exhaust/session-port.d.ts +36 -0
  99. package/dist/features/exhaust/session-port.js +102 -0
  100. package/dist/features/harness/drive-report.d.ts +0 -9
  101. package/dist/features/harness/drive-report.js +18 -0
  102. package/dist/features/harness/driver.js +4 -7
  103. package/dist/features/harness/fill-values.d.ts +6 -0
  104. package/dist/features/harness/fill-values.js +3 -0
  105. package/dist/features/harness/jev-driver.js +30 -13
  106. package/dist/features/harness/openai-driver.js +4 -7
  107. package/dist/features/harness/platform-config.js +5 -6
  108. package/dist/index.d.ts +9 -0
  109. package/dist/index.js +40 -11
  110. package/dist/judgement/domain/domain-model.js +3 -1
  111. package/dist/judgement/oracles/self-instrument.js +4 -2
  112. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  113. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  114. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  115. package/dist/judgement/runs/verification-sync.js +1 -1
  116. package/dist/language/flows/change/affected.d.ts +9 -0
  117. package/dist/language/flows/change/affected.js +35 -0
  118. package/dist/language/flows/change/gate.d.ts +20 -0
  119. package/dist/language/flows/change/gate.js +12 -1
  120. package/dist/language/flows/flow-author.d.ts +7 -0
  121. package/dist/language/flows/flow-author.js +19 -0
  122. package/dist/language/flows/flow-expect-grammar.js +1 -1
  123. package/dist/language/flows/flow-journey.d.ts +54 -0
  124. package/dist/language/flows/flow-journey.js +119 -0
  125. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  126. package/dist/language/flows/flow-replay-run.js +13 -47
  127. package/dist/language/flows/flow-replay.d.ts +22 -8
  128. package/dist/language/flows/flow-replay.js +55 -9
  129. package/dist/language/flows/flow-result.d.ts +2 -0
  130. package/dist/language/flows/flow-result.js +25 -0
  131. package/dist/language/flows/flow-tools.js +17 -5
  132. package/dist/language/flows/flows.d.ts +10 -1
  133. package/dist/language/flows/flows.js +90 -14
  134. package/dist/language/flows/recording/replay-halt.js +3 -1
  135. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  136. package/dist/language/flows/recording/tape/recordings.js +55 -1
  137. package/dist/language/flows/replay.d.ts +19 -0
  138. package/dist/language/flows/replay.js +42 -6
  139. package/dist/language/flows/verify-change-tools.js +5 -2
  140. package/dist/launch-chromium.d.ts +80 -0
  141. package/dist/launch-chromium.js +218 -0
  142. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  143. package/dist/memory/cloud/cloud-config.js +47 -7
  144. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  145. package/dist/memory/cloud/cloud-sync.js +6 -9
  146. package/dist/memory/cloud/harness-offer.js +5 -6
  147. package/dist/memory/cloud/harness-switch.js +5 -6
  148. package/dist/memory/cloud/sync-cycle.d.ts +68 -3
  149. package/dist/memory/cloud/sync-cycle.js +298 -55
  150. package/dist/memory/cloud/sync-daemon.js +24 -20
  151. package/dist/memory/cloud/sync-disk.js +16 -2
  152. package/dist/memory/intent/intent-shard.d.ts +10 -32
  153. package/dist/memory/intent/intent-shard.js +0 -10
  154. package/dist/memory/journal/drive-flow.d.ts +18 -13
  155. package/dist/memory/journal/drive-flow.js +45 -37
  156. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  157. package/dist/memory/journal/session-end.d.ts +9 -1
  158. package/dist/memory/journal/session-end.js +24 -12
  159. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  160. package/dist/memory/project/dir/reticle-dir.js +1 -0
  161. package/dist/memory/project/project-tools.js +3 -1
  162. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  163. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  164. package/dist/portal/bridge/bridge.d.ts +8 -0
  165. package/dist/portal/bridge/bridge.js +43 -2
  166. package/dist/portal/input/js-coverage.d.ts +25 -0
  167. package/dist/portal/input/js-coverage.js +30 -0
  168. package/dist/portal/input/real-input.d.ts +11 -0
  169. package/dist/portal/input/real-input.js +29 -4
  170. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  171. package/dist/portal/pool/playwright-launcher.js +20 -13
  172. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  173. package/dist/portal/session/dev-server/dev-command.js +30 -12
  174. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  175. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  176. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  177. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  178. package/dist/portal/session/facts/observed-state.js +34 -0
  179. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  180. package/dist/portal/session/no-session-diagnosis.js +62 -5
  181. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  182. package/dist/portal/session/no-session-next-action.js +48 -9
  183. package/dist/portal/session/no-session-watch.d.ts +18 -0
  184. package/dist/portal/session/no-session-watch.js +88 -2
  185. package/dist/portal/session/session-health.js +3 -2
  186. package/dist/portal/session/session-manager.d.ts +20 -6
  187. package/dist/portal/session/session-manager.js +19 -3
  188. package/dist/portal/session/session-reaper.d.ts +28 -0
  189. package/dist/portal/session/session-reaper.js +49 -1
  190. package/dist/portal/session/session.d.ts +5 -0
  191. package/dist/portal/session/session.js +15 -19
  192. package/dist/surface/http-server.d.ts +2 -1
  193. package/dist/surface/http-server.js +4 -2
  194. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  195. package/dist/surface/mcp/mcp-proxy.js +2 -2
  196. package/dist/surface/mcp/mcp.js +4 -2
  197. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  198. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  199. package/dist/surface/mcp/server-instructions.js +9 -7
  200. package/dist/surface/tools/act/after-match.d.ts +17 -0
  201. package/dist/surface/tools/act/after-match.js +30 -0
  202. package/dist/surface/tools/act/durable.d.ts +69 -0
  203. package/dist/surface/tools/act/durable.js +139 -0
  204. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  205. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  206. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  207. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  208. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  209. package/dist/surface/tools/act/proved-controls.js +11 -0
  210. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  211. package/dist/surface/tools/act/sequence-grade.js +32 -1
  212. package/dist/surface/tools/act-capsule.d.ts +7 -0
  213. package/dist/surface/tools/act-capsule.js +46 -1
  214. package/dist/surface/tools/act-sequence-tool.js +25 -8
  215. package/dist/surface/tools/act-tools.js +26 -50
  216. package/dist/surface/tools/assert/assert-grade.js +12 -1
  217. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  218. package/dist/surface/tools/browser-tools.js +14 -2
  219. package/dist/surface/tools/coverage-tools.js +27 -1
  220. package/dist/surface/tools/error-recovery.js +4 -4
  221. package/dist/surface/tools/feedback-invite.js +6 -4
  222. package/dist/surface/tools/harness-explore.d.ts +3 -2
  223. package/dist/surface/tools/harness-explore.js +7 -6
  224. package/dist/surface/tools/lease-tools.js +15 -6
  225. package/dist/surface/tools/live-call-text.js +4 -1
  226. package/dist/surface/tools/observe-tools.js +9 -1
  227. package/dist/surface/tools/tools.js +4 -0
  228. package/dist/telemetry/argument-shape.d.ts +0 -2
  229. package/dist/telemetry/argument-shape.js +0 -4
  230. package/dist/telemetry/error-class.d.ts +2 -3
  231. package/dist/telemetry/error-class.js +2 -3
  232. package/dist/telemetry/harness-drive.d.ts +0 -2
  233. package/dist/telemetry/harness-drive.js +0 -4
  234. package/dist/wire-journal.d.ts +3 -0
  235. package/dist/wire-journal.js +4 -0
  236. package/dist/workspace-packages.d.ts +0 -2
  237. package/dist/workspace-packages.js +1 -9
  238. package/docs/README.md +4 -2
  239. package/docs/actions.mdx +3 -3
  240. package/docs/agent-cheatsheet.md +4 -4
  241. package/docs/architecture.md +6 -6
  242. package/docs/best-practices.mdx +1 -1
  243. package/docs/capabilities.mdx +32 -1
  244. package/docs/cli/cloud.mdx +14 -4
  245. package/docs/cli/doctor.mdx +4 -0
  246. package/docs/cli/feedback.mdx +2 -2
  247. package/docs/cli/gate.mdx +9 -4
  248. package/docs/cli/update.mdx +10 -0
  249. package/docs/cli.mdx +9 -0
  250. package/docs/desktop.mdx +14 -10
  251. package/docs/environment.mdx +87 -0
  252. package/docs/faq.mdx +18 -16
  253. package/docs/first-drive.md +5 -13
  254. package/docs/flows.md +21 -2
  255. package/docs/frameworks.mdx +29 -10
  256. package/docs/getting-started.md +3 -3
  257. package/docs/http-transport.md +6 -3
  258. package/docs/human-control.md +2 -2
  259. package/docs/index.mdx +16 -6
  260. package/docs/install-agentic.mdx +1 -1
  261. package/docs/install-manual.mdx +1 -1
  262. package/docs/integration-patterns.md +3 -3
  263. package/docs/multi-agent-testing.md +1 -1
  264. package/docs/packages/babel-plugin.mdx +1 -1
  265. package/docs/packages/browser.mdx +2 -2
  266. package/docs/packages/react.mdx +2 -2
  267. package/docs/packages/tauri.mdx +1 -1
  268. package/docs/packages/vite-plugin.mdx +2 -2
  269. package/docs/packages.mdx +9 -9
  270. package/docs/predicates.mdx +50 -5
  271. package/docs/quickstart.mdx +2 -2
  272. package/docs/recipes.mdx +2 -2
  273. package/docs/state-management.mdx +2 -2
  274. package/docs/system-map.md +1 -1
  275. package/docs/telemetry.md +1 -1
  276. package/docs/token-efficiency.md +3 -3
  277. package/docs/tools/act-and-wait.mdx +1 -0
  278. package/docs/tools/act-sequence.mdx +1 -1
  279. package/docs/tools/console.mdx +1 -1
  280. package/docs/tools/inspect.mdx +1 -1
  281. package/docs/tools/network.mdx +1 -1
  282. package/docs/tools/query.mdx +1 -1
  283. package/docs/tools/sessions.mdx +1 -1
  284. package/docs/tools/snapshot.mdx +1 -1
  285. package/docs/tools/state.mdx +1 -1
  286. package/docs/tools/wait-for.mdx +1 -1
  287. package/docs/usage.md +7 -6
  288. package/docs/use-cases.mdx +157 -0
  289. package/docs/vs-screenshots.mdx +3 -3
  290. package/docs/what-is-recorded.md +2 -2
  291. package/docs/what-is-reticle.mdx +17 -20
  292. package/docs/why-reticle.mdx +14 -21
  293. package/package.json +10 -9
  294. package/dist/judgement/capsule/minimize.d.ts +0 -12
  295. package/dist/judgement/capsule/minimize.js +0 -22
  296. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  297. package/dist/judgement/phenomena/phenomena.js +0 -101
  298. package/dist/language/flows/flow-report.d.ts +0 -22
  299. package/dist/language/flows/flow-report.js +0 -106
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: 'Frameworks: proven, wired, and the difference'
3
3
  sidebarTitle: Frameworks
4
- description: Which frameworks Reticle supports, which are wired but unverified, and the exact wiring for each one. React, Next.js, Remix, Astro, SvelteKit, TanStack Start, Nuxt, Vue and plain HTML.
4
+ description: Which frameworks Reticle supports, which are wired but unverified, and the exact wiring for each one. React, Next.js, Remix, Astro, SvelteKit, TanStack Start, Nuxt, Vue, Angular and plain HTML.
5
5
  icon: layer-group
6
6
  ---
7
7
 
8
- **React + Vite, Next.js, Remix and Astro are fully supported, each with an example app driven to a verdict in CI. Astro, SvelteKit, Nuxt, React Router framework mode, Create React App and Vue have their INSTALL gated: the install gate scaffolds each from scratch on every change and requires a real session, but no gate drives them to a verdict. TanStack Start, Preact and plain HTML are wired and documented but not gated at all, so treat them as plausible rather than proven.** The exact wiring for each is below.
8
+ **React + Vite, Next.js, Remix and Astro are fully supported, each with an example app driven to a verdict in CI. Astro, SvelteKit, Nuxt, React Router framework mode, Create React App, Vue and Angular have their INSTALL gated: the install gate scaffolds each from scratch on every change and requires a real session, but no gate drives them to a verdict. TanStack Start, Preact and plain HTML are wired by `init` and documented but not gated at all, so treat them as plausible rather than proven.** The exact wiring for each is below.
9
9
 
10
10
  Reticle is a verification layer that embeds a dev-only SDK in your running web app so an AI coding agent can prove a change works. Its core is framework-neutral: DOM, network, console, routing, animations and source mapping work anywhere JavaScript runs.
11
11
 
@@ -32,9 +32,10 @@ Two columns say different things and it is worth keeping them apart. **CI gate**
32
32
  | **Nuxt** | yes | no | install only | unproven | Install gated, drive unverified |
33
33
  | **React Router (framework mode)** | yes | no | install only | yes | Install gated, drive unverified |
34
34
  | **Create React App** | yes | no | install only | unproven | Install gated, drive unverified |
35
+ | **Angular 17+** | yes | no | install only | no | Install gated, drive unverified |
35
36
  | **Plain HTML** | yes | no | no | no | Wired, unverified |
36
37
  | **Vue (non-Nuxt)** | as Vite | no | install only | no | Install gated, drive unverified |
37
- | **Preact** | as Vite | no | no | unproven | Sensor, React adapter via `preact/compat` |
38
+ | **Preact** | as Vite | no | no | transform-tested; drive unproven | Sensor, React adapter via `preact/compat` |
38
39
  | **Server-rendered (Django, Flask, FastAPI, Streamlit, Rails)** | yes | no | no | no | Wired, unverified |
39
40
 
40
41
  <Warning>
@@ -86,9 +87,9 @@ module.exports = withReticle({/* your config */});
86
87
 
87
88
  ## Remix and Astro
88
89
 
89
- Remix uses the Vite plugin with `inject: false`: Remix SSRs its own HTML, so the plugin's connect-injection does not fire, but the stamping half still puts `file:line` on a verdict.
90
+ Remix v2 on Vite is its own framework to `init`, not plain Vite. Remix SSRs its own HTML, so the plugin's `index.html` injection never reaches the page and the connect comes from `app/entry.client.tsx` instead, the same way as React Router framework mode. `init` adds the dev-only connect line to that entry, or writes the file when the app has none: `app/entry.client.tsx` only exists after `remix reveal`, so the written file is Remix's own default entry (`RemixBrowser`, `hydrateRoot`) with the one line added. The Vite plugin, `reticle()` in the `vite.config` that carries `vitePlugin`, stamps `file:line` and serves the connect module the entry imports. The classic Remix compiler (no `vite.config`) gets a manual step rather than a green plan, because there is no plugin to serve that module.
90
91
 
91
- Astro is the same on the plugin (wire it with `inject: false` if you want source pointers on JSX islands) and uses a different channel for the pairing token. `vite.define` does not reach Astro's client pipeline on 7.2+, so the identifier stays literal and the bridge refuses the dial. `init` reads the token in frontmatter and puts it on a `<meta name="reticle-pairing-token">`. Reading it per request also means a token written after the dev server started is picked up on the next load, with no restart.
92
+ Astro uses the Vite plugin (wire it with `inject: false` if you want source pointers on JSX islands) and uses a different channel for the pairing token. `vite.define` does not reach Astro's client pipeline on 7.2+, so the identifier stays literal and the bridge refuses the dial. `init` reads the token in frontmatter and puts it on a `<meta name="reticle-pairing-token">`. Reading it per request also means a token written after the dev server started is picked up on the next load, with no restart.
92
93
 
93
94
  Use a processed page `<script>` with a static SDK import. Vite owns that module graph from the first transform:
94
95
 
@@ -155,7 +156,7 @@ useEffect(() => {
155
156
  }, []);
156
157
  ```
157
158
 
158
- `init` detects Start and prints this recipe. There is no example app and no CI gate, so treat a connected session as the proof, not the plan.
159
+ `init` writes this for you: a `src/reticle-connect.tsx` client effect that connects, declares `__RETICLE_TOKEN__` so `tsc` passes, and loads `src/reticle-dev.ts`, plus `<ReticleConnect />` rendered before `<Scripts />` in `src/routes/__root.tsx`. When the root route is not a shape it can patch, it prints a recipe naming the real parts (`shellComponent`, `<Scripts />`) instead. There is no example app and no CI gate, so treat a connected session as the proof, not the plan.
159
160
 
160
161
  ## Nuxt
161
162
 
@@ -217,6 +218,10 @@ Detected as its own UI library, because a Preact app using `preact/compat` alias
217
218
 
218
219
  The sensor works. If you use `preact/compat`, the React adapter can attach through the alias, but that path is not gated.
219
220
 
221
+ **The source-pointer transform works on Preact code, and that is measured rather than assumed.** The stamper is JSX-syntactic: it tags a host element by its tag name and reads nothing about React, so a Preact `.jsx` or `.tsx` file is stamped exactly as a React one is. `class` instead of `className`, a `@jsxImportSource preact` pragma and a `@preact/signals` value in the tree all leave it unmoved. There are tests for each of those in `adapters/build/babel-plugin`. They prove the Babel transform, not the `@preact/preset-vite` pipeline end to end: nobody has yet driven a Preact app and seen a verdict name a `file:line`, so the drive stays unproven until someone does.
222
+
223
+ What is still missing on Preact is the enrichment above the DOM: `reticle_inspect` returns no `component`/`componentStack`, `@preact/signals` has no `reticle_state` adapter, and the render meter counts React commits only. Tracked in [#129](https://github.com/reticlehq/reticle/issues/129).
224
+
220
225
  ## Create React App and plain bundlers
221
226
 
222
227
  No config file to patch, so `connect()` goes in your entry module.
@@ -231,14 +236,28 @@ The same shape works for webpack, Parcel, and the Vue and Svelte CLIs.
231
236
 
232
237
  ## Plain HTML
233
238
 
239
+ A directory with an `index.html` and no `package.json` is wired by `init` itself: it writes the connect snippet into `index.html`, before `</body>`, writes `.reticle.json`, and hands over to setup. The block is marked, so a re-run finds it and never writes a second one, and a re-run with a new `--port` rewrites the port inside it. It loads the SDK from a CDN and runs only when the page is opened on `localhost`, `127.0.0.1` or `[::1]`: a static file has no development build to strip it, so the host is the only line between the snippet and a published page. The page carries no pairing token: `init` writes it into `reticle.local.js` beside the page, which the snippet imports, and adds that file to `.gitignore`. Keep it out of anything you publish, and let each teammate run `reticle init` for their own. Several pages and no `index.html` is a choice `init` does not make for you: it prints the snippet, filled in with your project id and token, for you to paste:
240
+
234
241
  ```html
235
242
  <script type="module">
236
- import { reticle } from '/node_modules/@reticlehq/browser/dist/index.js';
237
- reticle.connect();
243
+ import { reticle } from 'https://cdn.jsdelivr.net/npm/@reticlehq/browser@<version>/+esm';
244
+ reticle.connect({ projectId: '<your-project-id>', token: '<pairing token>' });
238
245
  </script>
239
246
  ```
240
247
 
241
- Keep it out of your production template. Reticle self-disables when the build reports `NODE_ENV=production`, but a second lock on a door leading to your users is a reasonable number.
248
+ A bare `/node_modules/...` import does not work here: the SDK's own `dist` imports bare specifiers, which a browser cannot resolve without a bundler. A page with a `package.json` and a bundler takes the entry-module path above instead.
249
+
250
+ ## Angular
251
+
252
+ Angular 17 and later, on the Vite-based `@angular/build` dev server. `init` installs only `@reticlehq/browser`, the framework-neutral sensor, and makes three edits:
253
+
254
+ - **The browser entry** (`src/main.ts`, or whatever `angular.json` names) gets a connect guarded on Angular's `ngDevMode` global (`typeof ngDevMode === 'undefined' || ngDevMode`), through a dynamic `import('@reticlehq/browser')` appended after the bootstrap. A production `ng build` defines `ngDevMode` as `false`, so the build drops the block and the chunk with it. The output carries no Reticle code at all, and the app never waits on Reticle. `isDevMode()` is not used because the build cannot fold a function call; an entry wired that way by an older `init` is rewritten in place on the next run.
255
+ - **`reticle.proxy.mjs`**, beside `angular.json`, answers one same-origin route, `/__reticle/pairing-token`, with this machine's pairing token, read from `~/.reticle/pairing-token` at request time. It refuses a request that did not arrive over loopback or came from another origin.
256
+ - **`angular.json`** gets the serve target's `proxyConfig` pointed at that file. `ng serve` loads it; `ng build` never reads a proxy config, so a production bundle has no route to the token and the token is never written into the project.
257
+
258
+ The token takes that route because the Angular CLI has no config file that runs code: a literal in `angular.json` commits a per-machine secret, and a gitignored module imported from `main.ts` is bundled into `ng build` whether or not anything can reach it. An existing `proxyConfig` is yours and is never replaced: the step goes manual and names the entry to add to it. A workspace with several served applications is also manual, because which one is the app is not `init`'s to guess.
259
+
260
+ You get DOM, network, console, routing and state tools. You do not get component identity (that is the React adapter) or a `source` field (the stamper covers JSX and Svelte). The install gate scaffolds an Angular app (no SSR) on every change and requires a session; SSR was proven by hand, and nothing drives an Angular app to a verdict.
242
261
 
243
262
  ## Server-rendered apps (Django, Flask, FastAPI, Rails)
244
263
 
@@ -327,7 +346,7 @@ Electron and Tauri invert the direction: your app dials the daemon, and there is
327
346
 
328
347
  ## Not supported
329
348
 
330
- Angular and Solid appear in dependency detection but have no wiring, no adapter and no gate. The sensor may work if you call `connect()` yourself. Nothing proves it, so we are not going to list them as supported.
349
+ Solid appears in dependency detection but has no wiring, no adapter and no gate. The sensor may work if you call `connect()` yourself. Nothing proves it, so we are not going to list them as supported.
331
350
 
332
351
  ## Frequently asked questions
333
352
 
@@ -78,7 +78,7 @@ The bridge + MCP server is a single process that serves all your projects, so it
78
78
 
79
79
  Re-running is safe: already-registered and already-patched steps are skipped, and on a wired project it goes straight to proving the app still works. Preview without writing via `npx @reticlehq/server init --dry-run`.
80
80
 
81
- `init` does not stop at writing files. It starts your dev server (restarting one whose bundle predates the config edit), opens the app, and waits for a session to connect from inside it. That connection is the whole proof that onboarding worked. It exits non-zero if nothing connected and prints what is left to do. It does NOT drive. Then prove a flow. That is the FIRST RUN, and it is a separate call: `reticle_verify { action: "explore", persona: "<who does what>" }`. It drives with a model inside the daemon and records what it drove, so later checks replay it with no model in the loop.
81
+ `init` does not stop at writing files. It starts your dev server, opens the app, and waits for a session to connect from inside it. That connection is the whole proof that onboarding worked. If a dev server was already running without Reticle, restart it once: it read the build config before `init` edited it. It exits non-zero if nothing connected and prints what is left to do. It does NOT drive. Then prove a flow. That is the FIRST RUN, and it is a separate call: `reticle_verify { action: "explore", persona: "<who does what>" }`. It drives with a model inside the daemon and records what it drove, so later checks replay it with no model in the loop.
82
82
 
83
83
  These carry what the command cannot work out for itself:
84
84
 
@@ -90,7 +90,7 @@ These carry what the command cannot work out for itself:
90
90
 
91
91
  The rest are dials: `--license <key>` (writes it to `.env` and keeps `.env` out of git), `--json` (one object for an agent to read), `--files-only` (write, register, pre-approve, and stop, which is what `init` did before it learned to boot the app, and what an existing install re-runs to pick up new wiring), `--relaunch` (prints the command that restarts the conversation you are in, with the tools loaded: the restart step most installs stall on, and it composes with `--files-only`), `--no-open`, `--dry-run`, `--port N`, `--no-mcp`, `--no-install`.
92
92
 
93
- Then restart your dev server and skip to [Step 4](#step-4-run-it-and-verify-the-connection). The manual steps below explain what `init` sets up, if you prefer to wire it yourself.
93
+ If you ran `--files-only` with a dev server already running, restart it, then skip to [Step 4](#step-4-run-it-and-verify-the-connection). The manual steps below explain what `init` sets up, if you prefer to wire it yourself.
94
94
 
95
95
  ---
96
96
 
@@ -464,7 +464,7 @@ Everything below comes from the `@reticlehq/react` kit plus your framework's bui
464
464
 
465
465
  **You do not get** component identity. `@reticlehq/react` walks the fiber tree to answer "which component rendered this element"; there is no Svelte equivalent, so snapshots carry the file and line but no component name. Stamping targets Svelte 5's compiler AST and also accepts Svelte 4's; `.svelte.ts` runes modules are code rather than markup and are not stamped.
466
466
 
467
- **It is still unverified.** There is no SvelteKit app in `apps/` and no CI gate for one, so nothing would tell us when this breaks; `reticle init` says so out loud in its plan. React, Next.js, Remix and Astro each have an app and a gate. Treat SvelteKit as wired and plausible, not as supported.
467
+ **It is install-gated, drive-unverified.** The install gate scaffolds a SvelteKit app from scratch, runs `init`, boots it and waits for a session, so the setup is proven. There is no SvelteKit example app that CI drives to a verdict, so the drive is not. [Frameworks](/frameworks) has the full status table.
468
468
 
469
469
  **Vue is install-gated, drive-unverified.** `init` detects a Vue app (as a Vite app with Vue as the UI library) and the install gate scaffolds one from scratch, runs `init`, boots it and waits for a session, so the setup is proven. What is not proven is the drive: there is no Vue example app in CI. `piniaStore` reads a Pinia store, and everything the framework-neutral core provides works. What you do not get is a `source` field, because source stamping covers JSX and Svelte components and a `.vue` single-file component is neither. See [Frameworks](/frameworks) for the full status table.
470
470
 
@@ -8,7 +8,7 @@ Most clients reach Reticle over stdio: they spawn `reticle mcp`, and the tools a
8
8
 
9
9
  The daemon also speaks MCP over HTTP, on the same port as everything else. Nothing has to reload for a client to reach it, so this is the transport for scripted runs, CI, a language with no MCP client library, and any editor that will not pick up tools mid-session.
10
10
 
11
- It is the same tool surface. Not a subset, not a simplified one. `reticle_snapshot`, `reticle_act_and_wait`, `reticle_assert` and the rest behave exactly as they do over stdio, because they are the same server behind a different transport.
11
+ It is the same tool surface. Not a subset, not a simplified one. `reticle_look { action: "page" }`, `reticle_act_and_wait`, `reticle_assert` and the rest behave exactly as they do over stdio, because they are the same server behind a different transport.
12
12
 
13
13
  ## The two endpoints
14
14
 
@@ -136,7 +136,10 @@ async function start() {
136
136
  const listed = await send('tools/list', {});
137
137
  console.log(listed.result.tools.map((t) => t.name).join(', '));
138
138
 
139
- const snapshot = await send('tools/call', { name: 'reticle_snapshot', arguments: {} });
139
+ const snapshot = await send('tools/call', {
140
+ name: 'reticle_look',
141
+ arguments: { action: 'page' },
142
+ });
140
143
  console.log(snapshot.result.content[0].text);
141
144
  }
142
145
  ```
@@ -159,6 +162,6 @@ The `202` comes back here; the tool list appears in the first shell.
159
162
 
160
163
  ## What this does not change
161
164
 
162
- The transport carries the tools. It does not replace the rest of the setup. The app still has to be instrumented and connected, and `reticle_sessions` is still what tells you whether a session is there to drive. A verdict reached over HTTP is a verdict reached the usual way: only `verified: "yes"` is a pass.
165
+ The transport carries the tools. It does not replace the rest of the setup. The app still has to be instrumented and connected, and `reticle_session { action: "list" }` is still what tells you whether a session is there to drive. A verdict reached over HTTP is a verdict reached the usual way: only `verified: "yes"` is a pass.
163
166
 
164
167
  The endpoints, the handshake, the three status codes and the `202`-then-SSE shape are pinned by `server/src/mcp-http-transport.test.ts`, which drives them over raw HTTP with no client library, so the contract this page describes fails the build if it changes.
@@ -17,7 +17,7 @@ The panel header shows a running **✓N ✗M** tally: the verdict score, live. I
17
17
  MCP is **pull-based**: the agent only receives data when it calls a tool. Reticle can't freeze the model mid-thought, but it **intercepts the agent's next tool call**, which is exactly when the agent would touch the page. That makes steering reliable:
18
18
 
19
19
  - **Nudge** (a message, no pause). Your text is queued and rides back on the agent's **next** tool result as `guidance` (delivered once). The agent reads it and adjusts.
20
- - **Pause.** The agent's next `reticle_act` / `reticle_act_and_wait` / `reticle_act_sequence` **refuses to execute** and returns `{ paused: true, guidance: ["<your message>"], hint: "…" }`. The page is untouched until resume. _Read-only tools (snapshot/query/observe) still work_, so the agent can look while paused.
20
+ - **Pause.** The agent's next `reticle_act` (single or `steps`) / `reticle_act_and_wait` **refuses to execute** and returns `{ paused: true, guidance: ["<your message>"], hint: "…" }`. The page is untouched until resume. _Read-only tools (snapshot/query/observe) still work_, so the agent can look while paused.
21
21
  - **End.** The session is over; the panel shows "Session ended" and clears.
22
22
 
23
23
  ## From the panel (the human)
@@ -35,7 +35,7 @@ The floating panel (bottom-center, `present: true`) gives you:
35
35
  You don't have to describe a bug in prose. **Point at it.** The flag captures the element's re-resolvable anchor _and_ the source `file:line` (when the framework stamped one), so the agent fixes the exact element and code, not a guess. The loop:
36
36
 
37
37
  1. **You** flag the element and type the problem → Reticle emits a `HUMAN_MARK`.
38
- 2. **The agent** drains it with `reticle_session {action:"review"}`, getting note + element label + `source: { file, line }` + a ready-to-act `fix` hint. `reticle_sessions` also reports `pendingMarks` so the agent notices flags.
38
+ 2. **The agent** drains it with `reticle_session {action:"review"}`, getting note + element label + `source: { file, line }` + a ready-to-act `fix` hint. `reticle_session { action: "list" }` also reports `pendingMarks` so the agent notices flags.
39
39
  3. **The agent** opens the file, fixes it, and calls `reticle_session {action:"review"}({ resolve: "m1" })`.
40
40
  4. **You** see **"✓ fixed: \<your note\>"** land in the panel. Flag → fix → confirmation.
41
41
 
package/docs/index.mdx CHANGED
@@ -73,13 +73,16 @@ Afterwards, use it whenever you change any user-facing behaviour.
73
73
  The four files it writes, the marks it prints, and how to read its report.
74
74
  </Card>
75
75
 
76
- <CardGroup cols={2}>
76
+ <CardGroup cols={3}>
77
77
  <Card title="Quickstart" icon="rocket" href="/quickstart">
78
78
  Install, connect your agent, and get a real verdict. Five minutes.
79
79
  </Card>
80
80
  <Card title="Why Reticle" icon="lightbulb" href="/why-reticle">
81
81
  The false-green problem, and the evidence that this fixes it.
82
82
  </Card>
83
+ <Card title="Use cases" icon="list-check" href="/use-cases">
84
+ Agent-built changes, security behaviour, accessibility, performance, SEO basics and personas.
85
+ </Card>
83
86
  </CardGroup>
84
87
 
85
88
  <CardGroup cols={3}>
@@ -127,18 +130,25 @@ Reticle clicks, watches, and reports what actually happened. A verdict of `unkno
127
130
  single step.
128
131
  </Card>
129
132
  <Card title="Yours, locally" icon="lock">
130
- Dev-only, localhost-only, offline. Your app's data never leaves the machine.
133
+ Dev-only and localhost-only. Your app's data stays on your machine; anonymous usage counts are
134
+ sent unless you [turn them off](/telemetry).
131
135
  </Card>
132
136
  </CardGroup>
133
137
 
138
+ ## What people use it for
139
+
140
+ Beyond proving an agent's change, people use Reticle to check security behaviour (access control by role, calls that must never fire, CSP violations), accessibility and UX (roles, names, focus, keyboard), performance (request counts, long tasks, layout shift), SEO basics (titles, headings, links, redirects), and to drive the app as different users. [Use cases](/use-cases) has what each one checks and a call to run.
141
+
142
+ It pairs well with a visual testing tool for pixel diffs, Playwright for sites you don't own, axe for full WCAG audits and a security scanner for vulnerability discovery.
143
+
134
144
  ## The evidence
135
145
 
136
- We publish the losses too. Reticle is not the right tool for driving a site you don't own, and it does not see pixels.
146
+ Every number is measured by a committed harness, including the ties.
137
147
 
138
148
  ![One honest test, two apps: Reticle has the highest Verification Efficiency on the controlled app and the lowest observation cost on the real dashboard](/images/bench-two-apps.png)
139
149
 
140
150
  <Card title="Read the full benchmark" icon="chart-column" href="/benchmarks">
141
- How every number was measured, what it means, and where Reticle comes second.
151
+ How every number was measured, what it means, and where the tools tie.
142
152
  </Card>
143
153
 
144
154
  ## Built for agents, readable by humans
@@ -151,7 +161,7 @@ Reticle is used by a coding agent far more often than by a person, so these docs
151
161
 
152
162
  ## Build with us
153
163
 
154
- Reticle is early, and the sharp edges get filed down fastest when someone tells us where they are.
164
+ The roadmap moves on what people actually hit, so tell us.
155
165
 
156
166
  <CardGroup cols={2}>
157
167
  <Card title="Join the Discord" icon="discord" href="https://discord.gg/BwAbzv9ZRz">
@@ -163,4 +173,4 @@ Reticle is early, and the sharp edges get filed down fastest when someone tells
163
173
  </Card>
164
174
  </CardGroup>
165
175
 
166
- Found a bug, or something you wished existed? `reticle_feedback` sends it in one call, from wherever you are. If Reticle could not tell you what happened, that is our defect and we want to know.
176
+ Found a bug, or something you wished existed? `reticle_session { action: "feedback" }` sends it in one call from your agent, and `npx @reticlehq/server feedback "…"` from a terminal. If Reticle could not tell you what happened, that is our defect and we want to know.
@@ -131,7 +131,7 @@ That `ℹ` line is the one people skim past, so here it is in full:
131
131
  ```
132
132
  [ℹ] AGENT: finish the capabilities file → src/reticle-dev.ts
133
133
  src/reticle-dev.ts was written but registers nothing, so `hasCapabilities` stays false
134
- and reticle_state has nothing to read.
134
+ and reticle_look { action: "state" } has nothing to read.
135
135
  ```
136
136
 
137
137
  `init` wrote the file, but a brand-new Vite template has no testids and no state library, so there was nothing to register. Reticle tells you this instead of counting it as a win. See [Instrument your app](/instrumentation) for what to put in it.
@@ -175,7 +175,7 @@ Screenshots and headless mode need the Rust crate, which is versioned independen
175
175
 
176
176
  ```toml
177
177
  [dependencies]
178
- reticle-tauri = "3.3"
178
+ reticle-tauri = "3.4"
179
179
  ```
180
180
 
181
181
  ```rust
@@ -22,7 +22,7 @@ The basics in [Getting Started](getting-started.md) work with **zero app changes
22
22
 
23
23
  Adoption is **free → cheap → targeted**. You don't instrument everything; you reuse what you have, then add the handful of facts the DOM can't express.
24
24
 
25
- **1. Reuse your existing `data-testid` (free).** If you already test with Playwright or Cypress, your testids work in Reticle unchanged; `reticle_query({ by: 'testid', value: 'checkout' })` matches them exactly. No new markup, no new code.
25
+ **1. Reuse your existing `data-testid` (free).** If you already test with Playwright or Cypress, your testids work in Reticle unchanged; `reticle_look({ action: "find", by: 'testid', value: 'checkout' })` matches them exactly. No new markup, no new code.
26
26
 
27
27
  **2. Advertise the surface from your existing constants (cheap).** You already keep a `TestIds` constant object for your E2E suite. Pass it straight in. Now `reticle_capabilities()` tells a fresh agent your whole surface without reading source.
28
28
 
@@ -107,7 +107,7 @@ If `mutate` throws, the mutation never happened, so **the signal is not emitted
107
107
 
108
108
  > **The documented exception:** genuinely view-level signals (render or async completions like `diff:shown` or `caption:generated`, which aren't store state) legitimately stay in your components. Only commit-point signals belong in the store layer.
109
109
 
110
- Pair this with store registration so the agent can _read_ state instead of you emitting a signal per fact: `registerStore('workspace', useWorkspace)`, then `reticle_state({ store: 'workspace' })`.
110
+ Pair this with store registration so the agent can _read_ state instead of you emitting a signal per fact: `registerStore('workspace', useWorkspace)`, then `reticle_look({ action: "state", store: 'workspace' })`.
111
111
 
112
112
  ## 4. Self-registering domains (`registerReticleDomain`)
113
113
 
@@ -164,7 +164,7 @@ Or turn it on with the shipped preset: `plugin.configs.recommended`, which sets
164
164
 
165
165
  Reticle observes and drives a tab through the in-page SDK plus (optionally) CDP. It **cannot bring to front or recover a browser tab the OS won't let it script**: a backgrounded tab, say, or a non-default browser (Dia, etc.) reporting `hidden:true` / `throttled:true`.
166
166
 
167
- When that happens, `reticle_sessions` and every act/assert result carry a `session.recommendation` saying so. The escape hatch is **`reticle drive <url>`** (add `--headed` to watch). Reticle launches and owns a guaranteed-scriptable browser. See [usage §18](usage.md#18-real-input-mode-native-hover--drag) for the full note.
167
+ When that happens, `reticle_session { action: "list" }` and every act/assert result carry a `session.recommendation` saying so. The escape hatch is **`reticle drive <url>`** (add `--headed` to watch). Reticle launches and owns a guaranteed-scriptable browser. See [usage §18](usage.md#18-real-input-mode-native-hover--drag) for the full note.
168
168
 
169
169
  ## Checklist
170
170
 
@@ -45,7 +45,7 @@ This is the design target, and it needs no special setup:
45
45
  - Flows can't bleed into each other: contexts are isolated and every session is scoped by `projectId`.
46
46
  - A single crashed page is reclaimed on its own, and if an agent crashes or hangs its lease stops being touched and the **lease reaper** reclaims the context after a TTL, freeing the slot. One dead agent never starves the others.
47
47
 
48
- `reticle_sessions` lists everything with `projectId` (group by app) and `leased` (pool context vs a human tab), so an orchestrator can see the whole fleet at a glance.
48
+ `reticle_session { action: "list" }` lists everything with `projectId` (group by app) and `leased` (pool context vs a human tab), so an orchestrator can see the whole fleet at a glance.
49
49
 
50
50
  ## Knobs
51
51
 
@@ -76,4 +76,4 @@ module.exports = {
76
76
 
77
77
  ## Why an attribute rather than a runtime lookup
78
78
 
79
- The stamp survives minification of everything else, costs nothing at runtime, and is readable from the DOM node itself. That is what lets [`reticle_inspect`](/tools/inspect) answer with a file and a line rather than a component name and a shrug.
79
+ The stamp survives minification of everything else, costs nothing at runtime, and is readable from the DOM node itself. That is what lets [`reticle_look { action: "element" }`](/tools/inspect) answer with a file and a line rather than a component name and a shrug.
@@ -12,7 +12,7 @@ icon: window
12
12
 
13
13
  This is what makes Reticle different from a browser-automation tool. Because it runs _inside_ the app rather than driving it from outside, it can see a state mutation and a fired signal, not just a rendered pixel. It never imports a Node API; that boundary is enforced by the dependency graph.
14
14
 
15
- [`reticle_query`](/tools/query) speaks role, label and testid instead of CSS selectors through its own local accessibility engine: one engine computes both what elements are called and what they match by, so a reported identity always round-trips.
15
+ [`reticle_look { action: "find" }`](/tools/query) speaks role, label and testid instead of CSS selectors through its own local accessibility engine: one engine computes both what elements are called and what they match by, so a reported identity always round-trips.
16
16
 
17
17
  ## When you need it directly
18
18
 
@@ -90,7 +90,7 @@ Also exported: `getCapabilities()`, `hasCapabilities()`, `setCapabilitiesListene
90
90
 
91
91
  ## State adapters
92
92
 
93
- Register a store and its contents become readable through [`reticle_state`](/tools/state).
93
+ Register a store and its contents become readable through [`reticle_look { action: "state" }`](/tools/state).
94
94
 
95
95
  `registerStore`, `unregisterStore`, `storeNames`, `readStores`, `readStoresWithTruncation`.
96
96
 
@@ -76,7 +76,7 @@ import { useReticleStore } from '@reticlehq/react/store';
76
76
 
77
77
  function Cart() {
78
78
  const [items, setItems] = useState([]);
79
- useReticleStore('cart', items); // now readable via reticle_state
79
+ useReticleStore('cart', items); // now readable via reticle_look { action: "state" }
80
80
  }
81
81
  ```
82
82
 
@@ -87,5 +87,5 @@ function Cart() {
87
87
  React 19 dropped `_debugSource`, so `identify` can name the component but not the file unless something stamps the source at build time. That is [`@reticlehq/babel-plugin`](/packages/babel-plugin), pulled in for you by [`@reticlehq/vite-plugin`](/packages/vite-plugin) or [`@reticlehq/next`](/packages/next).
88
88
 
89
89
  <Card title="Reading component state" icon="layer-group" href="/state-management">
90
- What `reticle_state` can see, and how to widen it.
90
+ What `reticle_look { action: "state" }` can see, and how to widen it.
91
91
  </Card>
@@ -21,7 +21,7 @@ Tauri IPC observation needs nothing on the Rust side. An `invoke('load_todos')`
21
21
 
22
22
  ```toml
23
23
  [dependencies]
24
- reticle-tauri = "3.3"
24
+ reticle-tauri = "3.4"
25
25
  ```
26
26
 
27
27
  Its own dependencies are `tauri = "2"` plus the per-platform capture backends: `objc2` and friends on macOS, `webview2-com` and `windows` on Windows, `webkit2gtk` and `cairo-rs` on Linux and the BSDs.
@@ -61,7 +61,7 @@ Plus a dynamic import of your dev module if one exists. It looks for `src/reticl
61
61
  | `token` | `string` | `readPairingToken()`, from `~/.reticle/pairing-token` |
62
62
  | `sourceMapping` | `boolean` | `true`. Set `false` to skip the Babel pass |
63
63
  | `inject` | `boolean` | `true`. Set `false` to call `connect()` yourself |
64
- | `desktop` | `boolean` | `false`. When `true` the plugin also applies to `vite build` and passes `allowInProduction: true`, which is what an Electron or Tauri renderer needs |
64
+ | `desktop` | `boolean` | `false`. When `true` the connect passes `allowInProduction: true`, which an Electron or Tauri renderer needs, and a `vite build` in a non-production mode (`--mode development`) is instrumented too. A production-mode build is stubbed like a web build |
65
65
  | `captureNetworkBodies` | `boolean` | `false`. Also enabled by `VITE_RETICLE_CAPTURE_BODIES=1` |
66
66
  | `onWarn` | `(message: string) => void` | `console.warn` |
67
67
 
@@ -87,7 +87,7 @@ Types: `ReticleVitePluginOptions`, `ReticleVitePlugin`, `ViteDevServerLike`.
87
87
  reticle({ desktop: true });
88
88
  ```
89
89
 
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.
90
+ A packaged Electron or Tauri renderer is loaded from a custom protocol with no dev server and reports `NODE_ENV=production`, so the default guards would refuse. This flag injects the connect into the entry module and opts it into production. It does NOT instrument a release: a production-mode `vite build` (the default) ships no Reticle code, exactly like a web build. To drive a packaged renderer with no dev server, build it with `vite build --mode development`.
91
91
 
92
92
  <Card title="Desktop wiring in full" icon="display" href="/desktop">
93
93
  Including the Tauri CSP whose failure is completely silent.
package/docs/packages.mdx CHANGED
@@ -38,12 +38,12 @@ You never install `@reticlehq/server`. Your agent runs it with `npx`.
38
38
 
39
39
  The package split is not cosmetic. Each boundary enforces something:
40
40
 
41
- | Package | Runs in | Hard rule |
42
- | --------- | ----------- | ------------------------------------------------------ |
43
- | `core` | both | Depends only on `zod`. Sits at the bottom of the graph |
44
- | `browser` | the browser | Never imports a Node API |
45
- | `server` | Node | Never imports a DOM API |
46
- | `react` | the browser | Optional. Core must work without it |
41
+ | Package | Runs in | Hard rule |
42
+ | --- | --- | --- |
43
+ | `core` | both | Depends only on `zod` and `open-verification`. Sits at the bottom of the graph |
44
+ | `browser` | the browser | Never imports a Node API |
45
+ | `server` | Node | Never imports a DOM API |
46
+ | `react` | the browser | Optional. Core must work without it |
47
47
 
48
48
  Those rules are why the browser SDK cannot accidentally pull in a filesystem call, and why the server cannot accidentally depend on `window`. They are enforced by the dependency graph, not by discipline.
49
49
 
@@ -72,7 +72,7 @@ Those rules are why the browser SDK cannot accidentally pull in a filesystem cal
72
72
 
73
73
  **Why it exists:** this is the part that makes Reticle different from a browser-automation tool. Because it runs _inside_ the app rather than driving it from outside, it can see a state mutation and a fired signal, not just a rendered pixel.
74
74
 
75
- **Depends on:** `@reticlehq/core`. [`reticle_query`](/tools/query) speaks role, label and testid rather than CSS selectors through its own local accessibility engine, so what a snapshot reports is exactly what a query matches.
75
+ **Depends on:** `@reticlehq/core`. [`reticle_look { action: "find" }`](/tools/query) speaks role, label and testid rather than CSS selectors through its own local accessibility engine, so what a snapshot reports is exactly what a query matches.
76
76
 
77
77
  **Install it directly when** you are not on React, or you want `connect()` under your own control:
78
78
 
@@ -147,7 +147,7 @@ export default defineConfig({ plugins: [reticle(), react()] });
147
147
 
148
148
  **Stamps `data-reticle-source="file:line:col"` onto JSX host elements.**
149
149
 
150
- **Why it exists:** React 19 dropped `_debugSource`, which is what source mapping used to rely on. This plugin puts the information back, at build time, in dev only. Without it, `reticle_inspect` can still tell you the component name but not the file.
150
+ **Why it exists:** React 19 dropped `_debugSource`, which is what source mapping used to rely on. This plugin puts the information back, at build time, in dev only. Without it, `reticle_look { action: "element" }` can still tell you the component name but not the file.
151
151
 
152
152
  **Depends on:** nothing. **Peer:** `@babel/core`.
153
153
 
@@ -239,7 +239,7 @@ A lint rule makes the signal layer self-enforcing, which is the only way a conve
239
239
 
240
240
  ```toml
241
241
  [dependencies]
242
- reticle-tauri = "3.3"
242
+ reticle-tauri = "3.4"
243
243
  ```
244
244
 
245
245
  <Warning>
@@ -7,7 +7,7 @@ icon: filter
7
7
 
8
8
  A predicate declares what should be true, as `{ kind, ...fields }`. Reticle has twelve kinds: `signal`, `state`, `net`, `route`, `element`, `text`, `console`, `animation`, `settled`, and the combinators `allOf`, `anyOf` and `not`. Which one you pick decides whether a green means anything.
9
9
 
10
- It is the argument to `until` on [`reticle_act_and_wait`](/tools/act-and-wait), and to `predicate` on [`reticle_assert`](/tools/assert) and [`reticle_wait_for`](/tools/wait-for).
10
+ It is the argument to `until` on [`reticle_act_and_wait`](/tools/act-and-wait), and to `predicate` on [`reticle_assert`](/tools/assert) and [`reticle_assert { action: "wait" }`](/tools/wait-for).
11
11
 
12
12
  Every predicate is `{ kind, ...fields }`.
13
13
 
@@ -35,6 +35,7 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
35
35
  | `element` / `text` | weak | Something rendered. A mock renders too |
36
36
  | `animation` | weak | An animation ran or finished |
37
37
  | `settled` | timing | The page went quiet. A precondition, not a proof |
38
+ | `compare` | its sides | Two readings agree: the stronger side's grade |
38
39
 
39
40
  ## The leaves
40
41
 
@@ -48,13 +49,13 @@ Take the advice. `element` and `text` are convenient and weak; `signal`, `state`
48
49
  }
49
50
  ```
50
51
 
51
- `query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `focused`, `inViewport`, `present`.
52
+ `query` accepts `role`, `name`, `text`, `label`, `placeholder`, `testid`, `alt`, `value`, `by`, `component`, `source`, `scope`, `self`, `attrs`. `state` is one of `visible`, `hidden`, `enabled`, `disabled`, `checked`, `expanded`, `pressed`, `focused`, `inViewport`, `present`.
52
53
 
53
54
  `value` is the one worth knowing about. `{ "role": "textbox", "name": "Amount" }` collapses to "a textbox called Amount exists", which passes against an empty field; adding `value` is what makes it an assertion about what the user will actually send. `by` selects the locator strategy explicitly, `component` and `source` match React component identity and source location, and `self` restricts a scoped match to the scope element itself rather than its descendants.
54
55
 
55
56
  `attrs` is a list of attribute **names**, such as `["href", "data-status"]`. It **projects** those attributes onto each match so you can read them back, and it does not filter. `["data-status=complete"]` is refused: it would otherwise look up an attribute literally named `data-status=complete`, find none, and leave the predicate resting on the locator alone, which is a green for any value the element happens to hold.
56
57
 
57
- To READ the values, call `reticle_query` with `attrs` and look at the `attrs` map on each match. To ASSERT one: a form field's value goes in the query as `value`, while a state expressed as a data attribute is better asserted as the consequence it stands for, such as a `signal`, a `net` call, or the text the user actually sees.
58
+ To READ the values, call `reticle_look { action: "find" }` with `attrs` and look at the `attrs` map on each match. To ASSERT one: a form field's value goes in the query as `value`, while a state expressed as a data attribute is better asserted as the consequence it stands for, such as a `signal`, a `net` call, or the text the user actually sees.
58
59
 
59
60
  ```json
60
61
  { "kind": "element", "query": { "role": "textbox", "name": "Amount", "value": "274.58" } }
@@ -118,7 +119,7 @@ Two fields read the RESPONSE BODY, so a request that succeeded with the wrong nu
118
119
  { "kind": "net", "urlContains": "/api/job", "bodyMatches": { "status": "completed" } }
119
120
  ```
120
121
 
121
- `bodyMatches` is a shallow field match, keyed like `signal.dataMatches` and taking the same operators (`*`, `$gte`, `$contains`). Reach for it whenever the value is an enum, because that is the case a substring cannot decide: `bodyContains: "completed"` passes on a body carrying `"completedAt": null`, where the needle matched the KEY and the job was still queued. `completed`/`completedAt`, `success`/`successRate`, `sent`/`unsent` all have that shape.
122
+ `bodyMatches` is a field match, keyed like `signal.dataMatches` and taking the same operators (`*`, `$gte`, `$contains`). A key can be a dotted path into a wrapped answer: `{ "data.status": "completed" }`, `{ "items.0.id": "a1" }`, `{ "items.length": 3 }`. A nested object written out as a value is compared whole, so use the path to match one field inside it. Reach for it whenever the value is an enum, because that is the case a substring cannot decide: `bodyContains: "completed"` passes on a body carrying `"completedAt": null`, where the needle matched the KEY and the job was still queued. `completed`/`completedAt`, `success`/`successRate`, `sent`/`unsent` all have that shape.
122
123
 
123
124
  Both require body capture, `reticle({ captureNetworkBodies: true })`, and say so when the body was never recorded rather than reporting an ordinary mismatch.
124
125
 
@@ -161,7 +162,7 @@ A passing route verdict quotes the transition itself:
161
162
  { "kind": "signal", "name": "webhook:received", "dataMatches": { "provider": "stripe", "id": "*" } }
162
163
  ```
163
164
 
164
- The app emitted a named signal via `reticle.signal()`. `dataMatches` is shallow JSON matching, and `*` means "present, any value".
165
+ The app emitted a named signal via `reticle.signal()`. `dataMatches` matches field by field, a key can be a dotted path (`"order.total"`), and `*` means "present, any value".
165
166
 
166
167
  `count` works here exactly as it does on `net`:
167
168
 
@@ -303,6 +304,50 @@ This is the predicate that catches a UI-versus-store desync. A deploy that only
303
304
 
304
305
  Network and DOM idle. This is the deterministic replacement for a fixed sleep, not a proof of anything: combine it with a real consequence rather than asserting it alone. Omitting `until` on [`reticle_act_and_wait`](/tools/act-and-wait) waits for exactly this.
305
306
 
307
+ ### compare
308
+
309
+ ```json
310
+ {
311
+ "kind": "compare",
312
+ "left": { "from": "text", "scope": "#refunded" },
313
+ "right": { "from": "net", "urlContains": "/api/refund", "path": "refunded" },
314
+ "as": "number"
315
+ }
316
+ ```
317
+
318
+ Two observed values must agree, and you do not have to know either one. Every other predicate checks one reading against a value you write down. That cannot catch a page that echoes the user's input instead of the server's answer unless you already know the right answer: a refund of `1187.01` that the server read as paise answered `{"refunded":11.87}`, the page showed `₹1,187.01`, and every other channel was green.
319
+
320
+ Each side reads one value from a source:
321
+
322
+ | `from` | Fields | Reads |
323
+ | --- | --- | --- |
324
+ | `net` | `urlContains`, `path`, `method?`, `body?` | A field of the LAST matching call's body. `body` is `response` (default) or `request` |
325
+ | `signal` | `name`, `path` | A field of the last `name` signal's payload |
326
+ | `state` | `path`, `store?` | A store value, as `state` reads it |
327
+ | `text` | `scope` | The whole text of the element `scope` names (a CSS selector) |
328
+
329
+ A `path` is a field name or a dotted path into nested data (`data.total`, `items.0.price`), and it must end on a single value, not an object.
330
+
331
+ `as` is `value` (the default) or `number`:
332
+
333
+ - `value` is strict equality. The text `"11.87"` and the number `11.87` are different, and the failure says so and suggests `as: "number"`.
334
+ - `number` reads one number from each side. A JSON number is used as it is; a string contributes its only number, so `"Refunded ₹1,187.01"` reads as `1187.01`. A string holding two numbers is unknown rather than guessed at, so scope the text to the one value. Add `tolerance` for a band.
335
+
336
+ A side nobody could read is never a pass. A call that never happened, a missing element or a missing field fails. A body that was not recorded, was truncated before the field, or had the field redacted is unknown. Comparing a source with itself is refused before it runs, because it would agree on every page.
337
+
338
+ Two live sides (`text`, `state`) are also read before the action. If they already agreed, the result is `already_true`, because their agreement says nothing about what the action did. Put a `net` or `signal` side in the comparison to tie it to the action.
339
+
340
+ Comparing request with response on the same call is legitimate, and it is the direct check for a unit mismatch:
341
+
342
+ ```json
343
+ {
344
+ "kind": "compare",
345
+ "left": { "from": "net", "urlContains": "/api/refund", "body": "request", "path": "amount" },
346
+ "right": { "from": "net", "urlContains": "/api/refund", "path": "refunded" },
347
+ "as": "number"
348
+ }
349
+ ```
350
+
306
351
  ## Asserting a failure on purpose
307
352
 
308
353
  A fault-injection test (mock a 500, check the error UI) is asserting that a request **fails**. Say so in the predicate, or the verdict will disagree with you:
@@ -61,7 +61,7 @@ The `reticle_*` tools are already there. In your project directory:
61
61
  npx @reticlehq/server init
62
62
  ```
63
63
 
64
- Like `npm init` or `git init`, this is the per-project step: it installs the SDK, wires it into your app's entry point, restarts your dev server and then opens your app to prove a session connected. It is idempotent. Run it twice and the second run reports what was already done rather than doing it again.
64
+ Like `npm init` or `git init`, this is the per-project step: it installs the SDK, wires it into your app's entry point, starts your dev server and then opens your app to prove a session connected. It is idempotent. Run it twice and the second run reports what was already done rather than doing it again.
65
65
 
66
66
  Step 1 is once per machine. This step is once per project.
67
67
 
@@ -236,7 +236,7 @@ That third state is the one that matters. Most tools have two outcomes and quiet
236
236
  <Accordion title="How long does it actually take to install Reticle?">
237
237
  Two commands, and no restart if you run them in order. The installer registers the MCP server
238
238
  with every agent on the machine, so opening your agent afterwards is all that is needed. Then
239
- `npx @reticlehq/server init` wires the project and restarts your dev server for you. The MCP
239
+ `npx @reticlehq/server init` wires the project, starts your dev server and opens the app for you. The MCP
240
240
  registration is global, so every later project starts with the tools already there.
241
241
  </Accordion>
242
242
 
package/docs/recipes.mdx CHANGED
@@ -5,7 +5,7 @@ description: Worked examples for the situations you are actually in, with the re
5
5
  icon: book-open
6
6
  ---
7
7
 
8
- Each recipe is a situation, the call that verifies it, and what came back. Every response on this page was captured from a running app on 2026-08-16. Refs like `e605` are session-specific: take your own from [`reticle_query`](/tools/query) before copying a call.
8
+ Each recipe is a situation, the call that verifies it, and what came back. Every response on this page was captured from a running app on 2026-08-16. Refs like `e605` are session-specific: take your own from [`reticle_look { action: "find" }`](/tools/query) before copying a call.
9
9
 
10
10
  ## "Did the login actually work?"
11
11
 
@@ -135,7 +135,7 @@ The right predicate was sitting in the response the whole time:
135
135
 
136
136
  ## "Nothing matched, and I do not know why"
137
137
 
138
- An empty `reticle_query` result tells you what _is_ there:
138
+ An empty `reticle_look { action: "find" }` result tells you what _is_ there:
139
139
 
140
140
  ```json
141
141
  {
@@ -179,7 +179,7 @@ worker.onmessage = (e) => push(e.data);
179
179
  { "store": "app", "path": "cart.items.0.qty" }
180
180
  ```
181
181
 
182
- `reticle_state` walks a dot-path with numeric array indices. `depth` collapses anything deeper to a size marker, which is the difference between a cheap look and a very expensive one on a large store.
182
+ `reticle_look { action: "state" }` walks a dot-path with numeric array indices. `depth` collapses anything deeper to a size marker, which is the difference between a cheap look and a very expensive one on a large store.
183
183
 
184
184
  You can also assert on it directly, which is stronger than reading it afterwards:
185
185
 
@@ -190,7 +190,7 @@ You can also assert on it directly, which is stronger than reading it afterwards
190
190
  That catches a UI-versus-store desync, a deploy that only looks shipped, deterministically and with no model involved.
191
191
 
192
192
  <CardGroup cols={2}>
193
- <Card title="reticle_state" icon="database" href="/tools/state">
193
+ <Card title='reticle_look { action: "state" }' icon="database" href="/tools/state">
194
194
  Arguments, depth control, and reading a component's hook state.
195
195
  </Card>
196
196
  <Card title="Instrument your app" icon="wrench" href="/instrumentation">
@@ -66,7 +66,7 @@ These three were each wrong by the time somebody checked: the table read 68 / 48
66
66
 
67
67
  | Token | Produced by | Consumed by | Invalidated by |
68
68
  | --- | --- | --- | --- |
69
- | `sessionId` | `reticle_sessions`, HELLO | ~every tool | tab close; **survives reload** via `sessionStorage` |
69
+ | `sessionId` | `reticle_session { action: "list" }`, HELLO | ~every tool | tab close; **survives reload** via `sessionStorage` |
70
70
  | `ref` (`e115`) | `snapshot`, `query`, `scroll_to` | `inspect`, `act`, `act_and_wait`, `state` | any re-render; refuses cleanly rather than clicking the new occupant |
71
71
  | `actionId` (`a2`) | `act`, `act_and_wait` | `observe`, `network`, `console` filters | window expiry |
72
72
  | `since` cursor | every read | every read | ring-buffer eviction |