@reticlehq/server 3.3.0 → 3.5.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 (326) 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 +6 -2
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  17. package/dist/command/cli/cli-doctor.js +7 -5
  18. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  19. package/dist/command/cli/cli-flow-commands.js +67 -4
  20. package/dist/command/cli/cli-parse.d.ts +8 -1
  21. package/dist/command/cli/cli-parse.js +24 -4
  22. package/dist/command/cli/cli-update-commands.js +8 -1
  23. package/dist/command/cli/cli-verify.d.ts +24 -3
  24. package/dist/command/cli/cli-verify.js +54 -11
  25. package/dist/command/cli/cloud-cli.js +64 -2
  26. package/dist/command/cli/cloud-kit.js +3 -5
  27. package/dist/command/cli/cloud-login.d.ts +1 -1
  28. package/dist/command/cli/cloud-login.js +27 -8
  29. package/dist/command/cli/demo/demo-page.js +6 -3
  30. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  31. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  32. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  33. package/dist/command/cli/launch/cli-launch.js +21 -9
  34. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  35. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  36. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  37. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  38. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  39. package/dist/command/cli/setup-mcp-cli.js +21 -13
  40. package/dist/command/cli/tutorial.d.ts +2 -2
  41. package/dist/command/cli/tutorial.js +21 -7
  42. package/dist/command/cli.js +79 -11
  43. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  44. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  45. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  46. package/dist/command/daemon/daemon-resolve.js +1 -1
  47. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  48. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  49. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  50. package/dist/command/dev/stale-issue-guard.js +8 -8
  51. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  52. package/dist/command/setup/bringup/connect-lease.js +38 -0
  53. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  54. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  55. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  56. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  57. package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
  58. package/dist/command/setup/bringup/dev-server-wait.js +1 -1
  59. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  60. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  61. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  62. package/dist/command/setup/bringup/static-page-server.js +151 -0
  63. package/dist/command/setup/desktop-shape.d.ts +4 -0
  64. package/dist/command/setup/desktop-shape.js +18 -2
  65. package/dist/command/setup/init/init-port.d.ts +15 -0
  66. package/dist/command/setup/init/init-port.js +44 -0
  67. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  68. package/dist/command/setup/init/init-runtime.js +91 -9
  69. package/dist/command/setup/node-effects.d.ts +27 -14
  70. package/dist/command/setup/node-effects.js +86 -45
  71. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  72. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  73. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  74. package/dist/command/setup/probe/page-probe.js +18 -3
  75. package/dist/command/setup/run-setup.d.ts +28 -1
  76. package/dist/command/setup/run-setup.js +103 -15
  77. package/dist/command/setup/session-pick.d.ts +13 -0
  78. package/dist/command/setup/session-pick.js +20 -0
  79. package/dist/command/setup/setup-command.d.ts +2 -14
  80. package/dist/command/setup/setup-command.js +105 -31
  81. package/dist/command/setup/setup-mcp.d.ts +2 -0
  82. package/dist/command/setup/setup-mcp.js +17 -8
  83. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  84. package/dist/command/setup/terminal/wait-progress.js +17 -0
  85. package/dist/command/version/peer-announce.d.ts +6 -2
  86. package/dist/command/version/peer-announce.js +8 -6
  87. package/dist/command/version/version-nudge.d.ts +17 -3
  88. package/dist/command/version/version-nudge.js +25 -4
  89. package/dist/features/crawl/crawl-empty.js +1 -1
  90. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  91. package/dist/features/crawl/crawl-tools.js +57 -0
  92. package/dist/features/crawl/crawl.js +171 -157
  93. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  94. package/dist/features/exhaust/app-coverage.js +31 -0
  95. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  96. package/dist/features/exhaust/code-coverage.js +77 -0
  97. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  98. package/dist/features/exhaust/exhaust-run.js +82 -0
  99. package/dist/features/exhaust/explorer.d.ts +103 -0
  100. package/dist/features/exhaust/explorer.js +195 -0
  101. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  102. package/dist/features/exhaust/failure-branches.js +92 -0
  103. package/dist/features/exhaust/ledger.d.ts +202 -0
  104. package/dist/features/exhaust/ledger.js +252 -0
  105. package/dist/features/exhaust/session-fold.d.ts +31 -0
  106. package/dist/features/exhaust/session-fold.js +79 -0
  107. package/dist/features/exhaust/session-port.d.ts +36 -0
  108. package/dist/features/exhaust/session-port.js +102 -0
  109. package/dist/features/harness/drive-report.d.ts +0 -9
  110. package/dist/features/harness/drive-report.js +18 -0
  111. package/dist/features/harness/driver.js +4 -7
  112. package/dist/features/harness/fill-values.d.ts +10 -0
  113. package/dist/features/harness/fill-values.js +50 -11
  114. package/dist/features/harness/jev-driver.js +30 -13
  115. package/dist/features/harness/openai-driver.d.ts +12 -0
  116. package/dist/features/harness/openai-driver.js +21 -12
  117. package/dist/features/harness/platform-config.js +5 -6
  118. package/dist/features/license/license.d.ts +2 -2
  119. package/dist/index.d.ts +9 -0
  120. package/dist/index.js +56 -19
  121. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  122. package/dist/judgement/domain/domain-model.js +3 -1
  123. package/dist/judgement/oracles/self-instrument.js +4 -2
  124. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  125. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  126. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  127. package/dist/judgement/runs/verification-sync.js +1 -1
  128. package/dist/language/flows/change/affected.d.ts +9 -0
  129. package/dist/language/flows/change/affected.js +35 -0
  130. package/dist/language/flows/change/gate.d.ts +20 -0
  131. package/dist/language/flows/change/gate.js +12 -1
  132. package/dist/language/flows/flow-author.d.ts +7 -0
  133. package/dist/language/flows/flow-author.js +19 -0
  134. package/dist/language/flows/flow-expect-grammar.js +1 -1
  135. package/dist/language/flows/flow-journey.d.ts +54 -0
  136. package/dist/language/flows/flow-journey.js +119 -0
  137. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  138. package/dist/language/flows/flow-replay-run.js +13 -47
  139. package/dist/language/flows/flow-replay.d.ts +22 -8
  140. package/dist/language/flows/flow-replay.js +55 -9
  141. package/dist/language/flows/flow-result.d.ts +2 -0
  142. package/dist/language/flows/flow-result.js +25 -0
  143. package/dist/language/flows/flow-tools.js +17 -5
  144. package/dist/language/flows/flows.d.ts +10 -1
  145. package/dist/language/flows/flows.js +90 -14
  146. package/dist/language/flows/recording/replay-halt.js +3 -1
  147. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  148. package/dist/language/flows/recording/tape/recordings.js +55 -1
  149. package/dist/language/flows/replay.d.ts +19 -0
  150. package/dist/language/flows/replay.js +42 -6
  151. package/dist/language/flows/verify-change-tools.js +5 -2
  152. package/dist/launch-chromium.d.ts +80 -0
  153. package/dist/launch-chromium.js +218 -0
  154. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  155. package/dist/memory/cloud/cloud-config.js +47 -7
  156. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  157. package/dist/memory/cloud/cloud-sync.js +6 -9
  158. package/dist/memory/cloud/harness-offer.js +5 -6
  159. package/dist/memory/cloud/harness-switch.js +5 -6
  160. package/dist/memory/cloud/sync-cycle.d.ts +77 -3
  161. package/dist/memory/cloud/sync-cycle.js +325 -55
  162. package/dist/memory/cloud/sync-daemon.js +24 -20
  163. package/dist/memory/cloud/sync-disk.js +16 -2
  164. package/dist/memory/intent/intent-shard.d.ts +10 -32
  165. package/dist/memory/intent/intent-shard.js +0 -10
  166. package/dist/memory/intent/intent-tools.js +8 -3
  167. package/dist/memory/journal/drive-flow.d.ts +18 -13
  168. package/dist/memory/journal/drive-flow.js +45 -37
  169. package/dist/memory/journal/envelope.d.ts +8 -8
  170. package/dist/memory/journal/journal-query.d.ts +14 -0
  171. package/dist/memory/journal/journal-query.js +15 -0
  172. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  173. package/dist/memory/journal/session-end.d.ts +9 -1
  174. package/dist/memory/journal/session-end.js +24 -12
  175. package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
  176. package/dist/memory/project/artifact-root-resolver.js +46 -19
  177. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  178. package/dist/memory/project/dir/reticle-dir.js +1 -0
  179. package/dist/memory/project/project-tools.js +3 -1
  180. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  181. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  182. package/dist/portal/bridge/bridge.d.ts +12 -3
  183. package/dist/portal/bridge/bridge.js +49 -3
  184. package/dist/portal/input/js-coverage.d.ts +25 -0
  185. package/dist/portal/input/js-coverage.js +30 -0
  186. package/dist/portal/input/real-input.d.ts +11 -0
  187. package/dist/portal/input/real-input.js +29 -4
  188. package/dist/portal/input/viewport-tools.js +16 -6
  189. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  190. package/dist/portal/pool/playwright-launcher.js +20 -13
  191. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  192. package/dist/portal/session/dev-server/dev-command.js +30 -12
  193. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  194. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  195. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  196. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  197. package/dist/portal/session/facts/observed-state.js +34 -0
  198. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  199. package/dist/portal/session/no-session-diagnosis.js +62 -5
  200. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  201. package/dist/portal/session/no-session-next-action.js +48 -9
  202. package/dist/portal/session/no-session-watch.d.ts +18 -0
  203. package/dist/portal/session/no-session-watch.js +88 -2
  204. package/dist/portal/session/session-health.js +3 -2
  205. package/dist/portal/session/session-manager.d.ts +20 -6
  206. package/dist/portal/session/session-manager.js +19 -3
  207. package/dist/portal/session/session-reaper.d.ts +28 -0
  208. package/dist/portal/session/session-reaper.js +49 -1
  209. package/dist/portal/session/session.d.ts +10 -8
  210. package/dist/portal/session/session.js +24 -32
  211. package/dist/surface/http-server.d.ts +3 -2
  212. package/dist/surface/http-server.js +8 -5
  213. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  214. package/dist/surface/mcp/mcp-proxy.js +2 -2
  215. package/dist/surface/mcp/mcp.js +5 -3
  216. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  217. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  218. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  219. package/dist/surface/mcp/server-instructions.js +9 -7
  220. package/dist/surface/tools/act/after-match.d.ts +17 -0
  221. package/dist/surface/tools/act/after-match.js +30 -0
  222. package/dist/surface/tools/act/durable.d.ts +69 -0
  223. package/dist/surface/tools/act/durable.js +139 -0
  224. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  225. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  226. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  227. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  228. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  229. package/dist/surface/tools/act/proved-controls.js +11 -0
  230. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  231. package/dist/surface/tools/act/sequence-grade.js +32 -1
  232. package/dist/surface/tools/act-capsule.d.ts +7 -0
  233. package/dist/surface/tools/act-capsule.js +46 -1
  234. package/dist/surface/tools/act-sequence-tool.js +25 -8
  235. package/dist/surface/tools/act-tools.js +28 -52
  236. package/dist/surface/tools/assert/assert-grade.js +12 -1
  237. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  238. package/dist/surface/tools/browser-tools.js +14 -2
  239. package/dist/surface/tools/coverage-tools.js +27 -1
  240. package/dist/surface/tools/dynamic-tools.js +29 -2
  241. package/dist/surface/tools/error-recovery.js +5 -6
  242. package/dist/surface/tools/feedback-invite.js +6 -4
  243. package/dist/surface/tools/harness-explore.d.ts +3 -2
  244. package/dist/surface/tools/harness-explore.js +12 -7
  245. package/dist/surface/tools/invoke-tool.js +12 -8
  246. package/dist/surface/tools/lease-tools.js +15 -6
  247. package/dist/surface/tools/live-call-text.js +4 -1
  248. package/dist/surface/tools/observe-tools.js +9 -1
  249. package/dist/surface/tools/tool-kit.d.ts +10 -0
  250. package/dist/surface/tools/tools.js +4 -0
  251. package/dist/telemetry/argument-shape.d.ts +0 -2
  252. package/dist/telemetry/argument-shape.js +1 -5
  253. package/dist/telemetry/error-class.d.ts +2 -3
  254. package/dist/telemetry/error-class.js +2 -3
  255. package/dist/telemetry/harness-drive.d.ts +0 -2
  256. package/dist/telemetry/harness-drive.js +0 -4
  257. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  258. package/dist/telemetry/onboarding-firsts.js +2 -2
  259. package/dist/wire-journal.d.ts +3 -0
  260. package/dist/wire-journal.js +4 -0
  261. package/dist/workspace-packages.d.ts +0 -2
  262. package/dist/workspace-packages.js +1 -9
  263. package/docs/README.md +4 -2
  264. package/docs/actions.mdx +3 -4
  265. package/docs/agent-cheatsheet.md +4 -4
  266. package/docs/architecture.md +6 -6
  267. package/docs/best-practices.mdx +1 -1
  268. package/docs/capabilities.mdx +32 -1
  269. package/docs/cli/cloud.mdx +20 -8
  270. package/docs/cli/doctor.mdx +4 -0
  271. package/docs/cli/feedback.mdx +2 -2
  272. package/docs/cli/gate.mdx +9 -4
  273. package/docs/cli/update.mdx +10 -0
  274. package/docs/cli.mdx +22 -7
  275. package/docs/desktop.mdx +14 -10
  276. package/docs/environment.mdx +87 -0
  277. package/docs/faq.mdx +18 -16
  278. package/docs/first-drive.md +5 -13
  279. package/docs/flows.md +21 -2
  280. package/docs/frameworks.mdx +29 -10
  281. package/docs/getting-started.md +15 -5
  282. package/docs/http-transport.md +6 -3
  283. package/docs/human-control.md +2 -2
  284. package/docs/index.mdx +16 -6
  285. package/docs/install-agentic.mdx +2 -2
  286. package/docs/install-manual.mdx +1 -1
  287. package/docs/integration-patterns.md +3 -3
  288. package/docs/multi-agent-testing.md +1 -1
  289. package/docs/packages/babel-plugin.mdx +1 -1
  290. package/docs/packages/browser.mdx +2 -2
  291. package/docs/packages/next.mdx +3 -0
  292. package/docs/packages/react.mdx +2 -2
  293. package/docs/packages/tauri.mdx +1 -1
  294. package/docs/packages/vite-plugin.mdx +4 -2
  295. package/docs/packages.mdx +9 -9
  296. package/docs/predicates.mdx +50 -5
  297. package/docs/quickstart.mdx +12 -10
  298. package/docs/recipes.mdx +2 -2
  299. package/docs/state-management.mdx +2 -2
  300. package/docs/system-map.md +1 -1
  301. package/docs/telemetry.md +1 -1
  302. package/docs/token-efficiency.md +3 -3
  303. package/docs/tools/act-and-wait.mdx +1 -0
  304. package/docs/tools/act-sequence.mdx +1 -1
  305. package/docs/tools/act.mdx +1 -1
  306. package/docs/tools/console.mdx +1 -1
  307. package/docs/tools/inspect.mdx +1 -1
  308. package/docs/tools/network.mdx +1 -1
  309. package/docs/tools/query.mdx +1 -1
  310. package/docs/tools/sessions.mdx +1 -1
  311. package/docs/tools/snapshot.mdx +1 -1
  312. package/docs/tools/state.mdx +1 -1
  313. package/docs/tools/wait-for.mdx +1 -1
  314. package/docs/usage.md +7 -7
  315. package/docs/use-cases.mdx +157 -0
  316. package/docs/vs-screenshots.mdx +3 -3
  317. package/docs/what-is-recorded.md +2 -2
  318. package/docs/what-is-reticle.mdx +17 -20
  319. package/docs/why-reticle.mdx +14 -21
  320. package/package.json +10 -9
  321. package/dist/judgement/capsule/minimize.d.ts +0 -12
  322. package/dist/judgement/capsule/minimize.js +0 -22
  323. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  324. package/dist/judgement/phenomena/phenomena.js +0 -101
  325. package/dist/language/flows/flow-report.d.ts +0 -22
  326. package/dist/language/flows/flow-report.js +0 -106
@@ -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:
@@ -5,7 +5,7 @@ description: Install Reticle, connect your coding agent, and get a real verdict
5
5
  icon: rocket
6
6
  ---
7
7
 
8
- **Run the installer in a terminal, open your coding agent, then run `init` in your project.** Reticle is a verification layer for AI coding agents: it embeds a dev-only SDK in your running web app so an agent can prove a change works instead of guessing.
8
+ **Run the installer once, then run `reticle connect --project "My App"` in your project.** Reticle embeds a dev-only SDK in your running app, signs you in through browser approval when needed, and syncs your checks to the project you name.
9
9
 
10
10
  By the end of this page your agent will click a button in your app and prove what happened: the request that fired, the state that changed, and the file to open if it didn't.
11
11
 
@@ -53,18 +53,20 @@ npm install -g @reticlehq/server
53
53
  npx @reticlehq/server setup mcp
54
54
  ```
55
55
 
56
- ## Step 2. Open your coding agent, and wire the project
56
+ ## Step 2. Connect this project
57
57
 
58
- The `reticle_*` tools are already there. In your project directory:
58
+ In your project directory, before opening your coding agent:
59
59
 
60
60
  ```bash
61
- npx @reticlehq/server init
61
+ npx @reticlehq/server connect --project "My App"
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
+ This installs the SDK, wires your app, starts the dev server, and checks that a browser session connected. It then opens browser sign-in approval if needed, links this folder to the named cloud project, and sends existing local history. Approve the short code shown in the browser and terminal; the command resumes by itself. A saved sign-in is reused after the cloud accepts it. Open your coding agent once this command finishes; if it was already open during installation, restart it to load the new tools.
65
65
 
66
66
  Step 1 is once per machine. This step is once per project.
67
67
 
68
+ Want local-only verification without an account? Run `reticle init` in this step instead. You can run `reticle connect` later to sync your history.
69
+
68
70
  Prefer to install the skill and let the agent run setup itself? On Claude Code the plugin registers the MCP server and the skill in one step:
69
71
 
70
72
  ```text
@@ -80,7 +82,7 @@ npx skills add reticlehq/reticle
80
82
 
81
83
  Then type `/reticle` and the agent runs the install from the skill.
82
84
 
83
- Useful flags, from `reticle init` itself:
85
+ Useful flags for the local-only `reticle init` path:
84
86
 
85
87
  ```
86
88
  reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
@@ -90,7 +92,7 @@ reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
90
92
  only make sense once the tools are reachable.
91
93
  ```
92
94
 
93
- Start with `--dry-run` if you want to see the plan before anything is written.
95
+ Start with `reticle init --dry-run` if you want to see the app changes before anything is written.
94
96
 
95
97
  <Tip>
96
98
  In a monorepo, `init` will find several apps and ask which one you mean. `--app packages/web`
@@ -107,9 +109,9 @@ npx @reticlehq/server doctor
107
109
 
108
110
  `doctor` diagnoses the whole setup in one command: Chromium, the daemon, the port. If something is wrong this is the page you want before you start debugging your app instead.
109
111
 
110
- ## Step 4. Start your app and open it
112
+ ## Step 4. Open your app again when you need it
111
113
 
112
- Start your dev server as usual, then:
114
+ `connect` already started the dev server and opened the app. On a later day, start your dev server as usual, then:
113
115
 
114
116
  ```bash
115
117
  npx @reticlehq/server open
@@ -236,7 +238,7 @@ That third state is the one that matters. Most tools have two outcomes and quiet
236
238
  <Accordion title="How long does it actually take to install Reticle?">
237
239
  Two commands, and no restart if you run them in order. The installer registers the MCP server
238
240
  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
241
+ `npx @reticlehq/server init` wires the project, starts your dev server and opens the app for you. The MCP
240
242
  registration is global, so every later project starts with the tools already there.
241
243
  </Accordion>
242
244
 
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 |
package/docs/telemetry.md CHANGED
@@ -122,7 +122,7 @@ Reticle verifies apps for AI agents, and for a long time it had no way to hear w
122
122
 
123
123
  | Who | How | What it carries |
124
124
  | --- | --- | --- |
125
- | Your agent | Calls the `reticle_feedback` MCP tool (after a failure, **or to ask for a feature**) | What it wrote: an analysis, or a request with the goal behind it, what would improve, and how it works around the gap today. Plus the model it is running, which it tells us because MCP cannot |
125
+ | Your agent | Calls the `reticle_session { action: "feedback" }` MCP tool (after a failure, **or to ask for a feature**) | What it wrote: an analysis, or a request with the goal behind it, what would improve, and how it works around the gap today. Plus the model it is running, which it tells us because MCP cannot |
126
126
  | You | Run `npx @reticlehq/server feedback [--rating 1-5] [--bug] "your words"` | Your words and your rating |
127
127
  | Your agent, before Reticle works | Runs `reticle feedback --agent --kind <bug\|gap\|ambiguity\|feature_request\|improvement> "what happened"` | The same words, filed from the shell. This exists for the phase where there is no daemon and no MCP tools (a failed `init`, half-finished wiring), which is the failure we are otherwise never told about |
128
128
 
@@ -4,7 +4,7 @@ description: Why asking narrow questions costs a fraction of feeding the whole a
4
4
  icon: coins
5
5
  ---
6
6
 
7
- A full Reticle verify loop costs about **100 tokens**, against roughly **7,300** for a full-tree accessibility snapshot on the same page: about 73x leaner, because Reticle asks a narrow question instead of returning the page. If you re-look after an action with `reticle_snapshot({ diff: true })`, the re-read costs about 99% less again.
7
+ A full Reticle verify loop costs about **100 tokens**, against roughly **7,300** for a full-tree accessibility snapshot on the same page: about 73x leaner, because Reticle asks a narrow question instead of returning the page. If you re-look after an action with `reticle_look({ action: "page", diff: true })`, the re-read costs about 99% less again.
8
8
 
9
9
  Agent browser tools that feed the **whole accessibility tree** to the model every step get expensive fast. Playwright MCP's own ecosystem notes its snapshots _"can exceed 50,000 tokens on complex pages,"_ with a _typical task ~114,000 tokens through MCP._ Reticle is built to ask **narrow questions** instead, so the per-interaction cost stays tiny.
10
10
 
@@ -28,7 +28,7 @@ Measured against the bench dashboard (`apps/bench-app`) **with a 1,000-item list
28
28
 
29
29
  ## Diffed snapshots: pay once, then only for changes
30
30
 
31
- After the first snapshot, pass `reticle_snapshot({ diff: true })` to get back **only what changed** since your last look of the same scope/mode (`mode:delta` with added/removed lines, or `mode:unchanged`). A route change auto-resets to a full snapshot, so you never read a misleading cross-page diff.
31
+ After the first snapshot, pass `reticle_look({ action: "page", diff: true })` to get back **only what changed** since your last look of the same scope/mode (`mode:delta` with added/removed lines, or `mode:unchanged`). A route change auto-resets to a full snapshot, so you never read a misleading cross-page diff.
32
32
 
33
33
  Measured on a representative 150-row dashboard (the shipped regression benchmark `server/src/surface/tools/snapshot-cost.test.ts`, char/4 proxy):
34
34
 
@@ -40,7 +40,7 @@ Measured on a representative 150-row dashboard (the shipped regression benchmark
40
40
 
41
41
  **~99% fewer tokens** to re-look after an action, and because a `delta` carries no stale full tree, it also removes the 60K to 80K-token stale-context buildup that makes long-running agents start hallucinating selectors that no longer exist.
42
42
 
43
- Every `reticle_snapshot`/`reticle_query` result also carries `cost:{ bytes, tokens }` (estimated) so you can **re-scope before reading** a large body (`mode:interactive`/`status`, a tighter `scope`, or a narrower `query`) instead of paying for it first.
43
+ Every `reticle_look { action: "page" }`/`reticle_look { action: "find" }` result also carries `cost:{ bytes, tokens }` (estimated) so you can **re-scope before reading** a large body (`mode:interactive`/`status`, a tighter `scope`, or a narrower `query`) instead of paying for it first.
44
44
 
45
45
  ## The other tax: tool schemas, paid on every request
46
46
 
@@ -162,6 +162,7 @@ A verification tool that cannot describe the quality of its own evidence is aski
162
162
  | `predicate` | Alias for `until`, the name `assert` and `wait_for` use |
163
163
  | `timeout_ms` | Maximum wait, defaulting to 4000. `0` evaluates the predicate once, no wait |
164
164
  | `refuseWhenThrottled` | Throw rather than act on a throttled tab |
165
+ | `durable` | After a `yes`, reload the page and require the element, text and state parts of `until` to hold again. A consequence that is gone after the reload turns the verdict into `no`: the UI advanced, the change did not persist |
165
166
 
166
167
  Omitting `until` waits for network and DOM idle, which is the deterministic replacement for a fixed sleep. To assert a consequence **and** settle, combine them:
167
168
 
@@ -5,7 +5,7 @@ icon: layer-group
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_act_sequence is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_act_sequence is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_act { steps: [...] }`** instead: the same call, routed on the steps array. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -59,7 +59,7 @@ Real response:
59
59
 
60
60
  ## Actions
61
61
 
62
- `click` · `dblclick` · `hover` · `focus` · `blur` · `fill` · `type` · `clear` · `select` · `check` · `uncheck` · `submit` · `press` · `upload` · `scrollIntoView` · `drag` · `webmcp`
62
+ `click` · `dblclick` · `hover` · `focus` · `blur` · `fill` · `type` · `clear` · `select` · `check` · `uncheck` · `submit` · `press` · `upload` · `scrollIntoView` · `drag`
63
63
 
64
64
  | Action | Arguments |
65
65
  | ---------------- | --------------------------------------------------------------- |
@@ -5,7 +5,7 @@ icon: terminal
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_console is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_console is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_observe { action: "console" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: crosshairs
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_inspect is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_inspect is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_look { action: "element" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: satellite-dish
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_network is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_network is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_observe { action: "network" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: magnifying-glass
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_query is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_query is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_look { action: "find" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: list-check
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_sessions is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_sessions is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_session { action: "list" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: camera
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_snapshot is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_snapshot is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_look { action: "page" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: database
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_state is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_state is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_look { action: "state" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
@@ -5,7 +5,7 @@ icon: hourglass
5
5
  ---
6
6
 
7
7
  <Warning>
8
- **reticle_wait_for is not advertised.** The default surface is the merged nine, so an agent does not see
8
+ **reticle_wait_for is not advertised.** The default surface is the merged ten, so an agent does not see
9
9
  this name. Call **`reticle_assert { action: "wait" }`** instead. Everything below describes what that call does;
10
10
  only the spelling changed. A call to the old name is answered with the new one, but an
11
11
  `allowedTools` allowlist or an MCP permission rule naming it refuses before Reticle is asked.
package/docs/usage.md CHANGED
@@ -4,7 +4,7 @@ description: 'The full reference and cookbook: every tool, flag, and workflow, w
4
4
  icon: book
5
5
  ---
6
6
 
7
- Every Reticle drive is the same four steps: **look** (`reticle_look { action: "page" }` / `reticle_look { action: "find" }`), **act** (`reticle_act` / `reticle_act { steps: [...] }`), **observe** (`reticle_observe` / `reticle_observe { action: "network" }` / `reticle_look { action: "state" }`), **assert** (`reticle_assert`). Only `reticle_act_and_wait` and `reticle_assert` produce a verdict, so a run that ends anywhere else proved nothing. This page is the full reference for every tool, predicate, action and flag in that loop.
7
+ Every Reticle drive is the same four steps: **look** (`reticle_look { action: "page" }` / `reticle_look { action: "find" }`), **act** (`reticle_act` / `reticle_act { steps: [...] }`), **observe** (`reticle_observe` / `reticle_observe { action: "network" }` / `reticle_look { action: "state" }`), **assert** (`reticle_assert`). Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`, so a run that ends anywhere else proved nothing. This page is the full reference for every tool, predicate, action and flag in that loop.
8
8
 
9
9
  If you haven't set up Reticle yet, start with [Getting Started](getting-started.md).
10
10
 
@@ -183,7 +183,7 @@ The timeline + summary of what happened.
183
183
 
184
184
  Act, then wait for a predicate: the whole act→observe→assert loop in one hop.
185
185
 
186
- - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `sessionId?`.
186
+ - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `intent?`, `durable?` (after a yes, reload and require `until`'s element/text/state parts to hold again), `sessionId?`.
187
187
  - **returns:** `{ effect, verdict, trace, session, warning? }`. `effect` is the action result (`{ ok, ref, action }`), `verdict` is `{ pass, evidence?, failureReason? }`, `trace` is the reaction report of everything the app did after the action, and `session` is the tab-health block `{ lastSeenMs, throttled, focused }` (with a `warning` when throttled). A failing `verdict` still returns `effect` + `trace` so you can see what _did_ happen. The predicate is automatically floored at this act's cursor, so it only matches events the action actually caused.
188
188
 
189
189
  ### `reticle_assert { action: "wait" }`
@@ -350,7 +350,7 @@ A **predicate** declares what should be true. `reticle_assert` / `reticle_assert
350
350
  // An element exists / is in a state
351
351
  { "kind": "element", "query": { "role": "dialog", "name": "Confirm" }, "state": "visible" }
352
352
  // query supports: role, name, text, label, placeholder, testid, alt, scope
353
- // state: visible | hidden | enabled | disabled | checked | expanded | focused | present | inViewport
353
+ // state: visible | hidden | enabled | disabled | checked | expanded | pressed | focused | present | inViewport
354
354
  // inViewport asserts the element is in the viewport NOW (not just in the DOM), so a scrollIntoView is gradeable
355
355
  // add "absent": true to assert it is NOT there (regression / removal)
356
356
 
@@ -396,7 +396,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
396
396
  - `timeout_ms` (on `assert`/`wait_for`): wait up to N ms for it to become true.
397
397
  - `since` (on `net`/`console` leaves): only consider events after this cursor (from `act`).
398
398
 
399
- `dataMatches` uses shallow JSON matching; `*` means "present, any value".
399
+ `dataMatches` matches field by field; a key can be a dotted path (`"order.total"`, `"items.0.id"`), and `*` means "present, any value".
400
400
 
401
401
  ---
402
402
 
@@ -419,7 +419,6 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
419
419
  | `scrollIntoView` | n/a | |
420
420
  | `upload` | `{ name, content?, type? }` | sets a file on `<input type=file>` |
421
421
  | `drag` | `{ toRef }` | pointer-based drag (dnd-kit / rbd) + HTML5 DnD |
422
- | `webmcp` | `{ tool, params }` | calls a `navigator.modelContext` tool if the site exposes one |
423
422
 
424
423
  ---
425
424
 
@@ -640,7 +639,7 @@ Capture everything that happens across a span. Useful for "run my whole checkout
640
639
 
641
640
  ```jsonc
642
641
  reticle_record {action:"start"}({ recordingName: "checkout" })
643
- // …agent performs the flow (reticle_act / reticle_act_sequence)…
642
+ // …agent performs the flow (reticle_act, single or with steps)…
644
643
  reticle_record {action:"stop"}({ recordingName: "checkout" })
645
644
  // → {
646
645
  // recordingName,
@@ -732,7 +731,7 @@ No, for basic look/act/observe. You'll get better results by adding `data-testid
732
731
 
733
732
  ### Does it work without React?
734
733
 
735
- The core (DOM/network/route/console/animation/snapshot/actions) is framework-agnostic and is gated against a vanilla-TS app. React, Next.js, Remix and Astro each have an app and a CI gate. SvelteKit is wired end-to-end. `reticle init` writes the client hook and the Vite plugin, and the plugin stamps `data-reticle-source` into `.svelte` components so verdicts carry `file:line`. But there is still no SvelteKit app in CI, so it is unverified rather than supported. Vue has a Pinia store adapter and nothing else: no detection, no `.vue` stamping, no gate. See [what Svelte support is and is not](getting-started.md#what-svelte-support-is-and-what-it-is-not).
734
+ The core (DOM/network/route/console/animation/snapshot/actions) is framework-agnostic and is gated against a vanilla-TS app. What is proven for each framework, from driven to a verdict in CI to install-gated to wired but unverified, is listed once in [Frameworks](frameworks.mdx). See also [what Svelte support is and is not](getting-started.md#what-svelte-support-is-and-what-it-is-not).
736
735
 
737
736
  ### Can it judge whether my UI looks good?
738
737
 
@@ -858,6 +857,7 @@ It does **not** freeze `requestAnimationFrame`/microtasks (React's scheduler kee
858
857
  "dataMatches": { "count": { "$gte": 1 }, "sections": { "$contains": "hook" } },
859
858
  }
860
859
  // operators: $gte $lte $gt $lt $contains (array/substring) $length ; "*" = present
860
+ // keys: a field name, or a dotted path into nested data ("order.total", "items.0.id")
861
861
  ```
862
862
 
863
863
  On a failed signal assert, the result includes a **near-miss**: the signals that _did_ fire with that name + their data. And `reticle_observe`'s summary now includes `domChanged` (in-place text/attribute re-renders, not just added/removed nodes).
@@ -0,0 +1,157 @@
1
+ ---
2
+ title: 'Use cases: what people verify with Reticle'
3
+ sidebarTitle: Use cases
4
+ description: 'Verifying agent-built changes, security behaviour, accessibility and UX, performance, SEO basics, and driving the app as different users. What Reticle checks in each, with a call you can run.'
5
+ icon: list-check
6
+ ---
7
+
8
+ Reticle runs inside your app while it is running, so anything the app does (a request, a state change, a console line, a focus move, a route change, a performance entry) is something an agent can check and get a verdict on. People use it for more than "did my change work". This page covers the main uses. Each section lists what Reticle checks and gives one call you can run.
9
+
10
+ Every predicate below is a `json` value you pass as `until` to `reticle_act_and_wait` (the consequence of an action) or as `predicate` to `reticle_assert` (a check with no action). The URLs, names and store paths are placeholders for your own. The full grammar is in [Predicates](/predicates).
11
+
12
+ ## Verifying agent-built changes
13
+
14
+ This is what Reticle was built for: an agent says a change works, and Reticle checks it against the running app.
15
+
16
+ - The request fired, returned the status you expected, and fired **once** (a double-submit is a `count` of 2).
17
+ - Application state moved the way the UI says it did. A total on screen that disagrees with the store is a failure.
18
+ - No console error appeared during the action.
19
+ - A failure names the `file:line` to open (on React, and on any framework whose build plugin stamps source; see [Frameworks](/frameworks)).
20
+
21
+ Click "Place order", then check all three at once:
22
+
23
+ ```json
24
+ {
25
+ "kind": "allOf",
26
+ "predicates": [
27
+ { "kind": "net", "method": "POST", "urlContains": "/api/orders", "status": 201, "count": 1 },
28
+ { "kind": "state", "store": "cart", "path": "count", "equals": 0 },
29
+ { "kind": "console", "level": "error", "absent": true }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ Once a journey has been driven, it is saved as a flow and replays with no model in the loop. `npx @reticlehq/server gate --since HEAD~1` fails unless a passing run covers every saved flow your edit touched.
35
+
36
+ ## Security checks
37
+
38
+ This proves how your app behaves around security. It does not search for vulnerabilities, so it complements a vulnerability scanner rather than replacing one.
39
+
40
+ - **Access control.** Signed in as a role that should be refused, the protected call returns `403` and the app shows the denial, not a success toast.
41
+ - **Forbidden calls.** An endpoint that must not fire (an analytics beacon before consent, an admin API from a viewer's page) fires zero times: `{ "kind": "net", "urlContains": "/collect", "count": 0 }`.
42
+ - **Secrets not rendered.** A value that must never reach the page is absent from its text. Separately, the SDK redacts credential-shaped values (tokens, API keys, passwords, card numbers) from captured request and response bodies, storage and state before they reach the agent, and shows them as `[REDACTED]`.
43
+ - **CSP violations.** A Content Security Policy violation shows up as a console error (as a warning for report-only policies), so a CSP check needs nothing extra.
44
+
45
+ As a viewer, click "Delete user" and require the refusal:
46
+
47
+ ```json
48
+ {
49
+ "kind": "allOf",
50
+ "predicates": [
51
+ { "kind": "net", "method": "DELETE", "urlContains": "/api/admin/users", "status": 403 },
52
+ { "kind": "element", "query": { "role": "alert" }, "state": "visible" },
53
+ { "kind": "text", "contains": "sk_live_", "absent": true },
54
+ { "kind": "console", "contains": "Content Security Policy", "absent": true }
55
+ ]
56
+ }
57
+ ```
58
+
59
+ To check reachability for each role, see [Personas and simulation](#personas-and-simulation) below.
60
+
61
+ ## Accessibility and UX
62
+
63
+ Reticle finds elements the way assistive technology does: by role and accessible name. That makes some accessibility behaviour directly checkable. For contrast, full screen-reader behaviour and complete WCAG coverage, pair it with an audit tool such as axe.
64
+
65
+ - A control is reachable by its **role and accessible name**. An icon button with no name cannot be found with `{ "role": "button", "name": "Close" }`.
66
+ - **Focus lands where it should.** When a dialog opens, focus moves into it (`state: "focused"`), and Reticle records focus dropping to `<body>` when a dialog closes.
67
+ - **The keyboard works.** `press` with `Enter` or `Escape` fires the control's action, not just a mouse click. The [verify-keyboard-access](https://github.com/reticlehq/reticle/tree/main/skills/verify-keyboard-access) skill walks through it.
68
+ - **Visible, enabled, checked, expanded, pressed and in-viewport** are all element states you can assert.
69
+
70
+ A synthetic Tab key does not move focus in a browser, so Reticle cannot verify Tab order, and it reports that as unknown rather than as a pass.
71
+
72
+ Open the "Delete project" dialog with `press` (`args: { "text": "Enter" }`) and require focus to move into it:
73
+
74
+ ```json
75
+ {
76
+ "kind": "allOf",
77
+ "predicates": [
78
+ {
79
+ "kind": "element",
80
+ "query": { "role": "dialog", "name": "Delete project" },
81
+ "state": "visible"
82
+ },
83
+ { "kind": "element", "query": { "role": "button", "name": "Cancel" }, "state": "focused" }
84
+ ]
85
+ }
86
+ ```
87
+
88
+ ## Performance and monitoring
89
+
90
+ - **Request cardinality.** One keystroke in a debounced search sends one request, not one per character. The `duplicate-request` finding flags a write that fired more than once in a single action.
91
+ - **Page performance entries.** The SDK records largest-contentful-paint, a running layout-shift total and long tasks as `perf` events, which you read with `reticle_observe { filters: ["perf"] }`. The layout-shift figure is a running sum without the windowing a Core Web Vitals CLS score uses, so it works for "no layout shift on load" and trends, not as an exact CWV number.
92
+ - **Request timing.** Every captured request carries its duration in `reticle_observe { action: "network" }`.
93
+ - **React render rate.** With the React adapter, the commit count is readable as the `__reticle_renders` store, so a component that re-renders continuously while the screen looks idle shows up.
94
+ - **Recurring re-verification.** `npx @reticlehq/server verify <url>` replays your saved flows against a dev server or a staging/preview deployment and exits non-zero on a failure, so it runs in CI or on a schedule. The SDK refuses to connect in a production build, so this covers environments you control, not live production traffic.
95
+
96
+ Type "shoes" into a debounced search box and require exactly one request:
97
+
98
+ ```json
99
+ { "kind": "net", "method": "GET", "urlContains": "/api/search", "count": 1 }
100
+ ```
101
+
102
+ ## SEO checks
103
+
104
+ Reticle sees the rendered page and the routes, so it covers the behavioural half of SEO. It does not read `<head>` meta tags or score a page, so pair it with an SEO crawler for those.
105
+
106
+ - `reticle_look { action: "page" }` reports the document `title` and the current route.
107
+ - **Headings and links.** The page's heading exists under its accessible name, and `reticle_look { action: "find", by: "role", value: "link", attrs: ["href"] }` lists every link with its `href`.
108
+ - **Redirects and routing.** A legacy URL lands on the right path: `{ "kind": "route", "pathname": "/pricing" }`.
109
+ - **Broken links and dead controls.** `reticle_verify { action: "crawl" }` drives every reachable control and reports failed requests, console errors and controls that do nothing.
110
+
111
+ After navigating to the old `/plans` URL, require the redirect and the page's main heading:
112
+
113
+ ```json
114
+ {
115
+ "kind": "allOf",
116
+ "predicates": [
117
+ { "kind": "route", "pathname": "/pricing" },
118
+ { "kind": "element", "query": { "role": "heading", "name": "Pricing" }, "state": "visible" }
119
+ ]
120
+ }
121
+ ```
122
+
123
+ ## Personas and simulation
124
+
125
+ - **Drive the app as a user.** `explore` takes a persona in plain words, drives that whole journey with a model inside the daemon, and saves what it drove as a flow that later replays with no model.
126
+ - **One context per role.** `reticle_lease { action: "acquire" }` opens an isolated browser context (its own cookies, storage and DOM) and can seed storage or cookies first, so an admin and a viewer drive the same app side by side. Reach it with `reticle_run`, or use `reticle verify <url> --storage-state <file>` from a terminal.
127
+ - **Reachability testing.** Signed in as a role, a control that role must not have is absent, and the call behind it never fires. `reticle_verify { action: "coverage" }` lists the controls you have and have not driven this session.
128
+ - **Multi-agent runs.** Several agents lease contexts from one shared headless Chromium, capped and queued, instead of starting a browser each. See [Multi-agent](/multi-agent-testing).
129
+
130
+ Have Reticle drive a first-time user's journey and record it:
131
+
132
+ ```bash
133
+ npx @reticlehq/server verify http://localhost:3000 --explore --persona "a new user who signs up and creates a first project"
134
+ ```
135
+
136
+ Then, signed in as a viewer, check what that role can reach:
137
+
138
+ ```json
139
+ {
140
+ "kind": "allOf",
141
+ "predicates": [
142
+ { "kind": "element", "query": { "role": "button", "name": "Invite member" }, "absent": true },
143
+ { "kind": "net", "urlContains": "/api/billing", "count": 0 }
144
+ ]
145
+ }
146
+ ```
147
+
148
+ ## Pairs well with
149
+
150
+ Reticle checks what your app does from inside it. Some jobs belong to other tools, and it works alongside them:
151
+
152
+ - **Pixel-level visual diffs:** a visual testing tool. Reticle can take screenshots, but its verdicts come from behaviour, not pixels.
153
+ - **Driving sites you don't own, or a cross-browser matrix:** Playwright. Reticle needs its dev-only SDK in the app, and its driven browser is Chromium.
154
+ - **Full WCAG audits:** axe or a similar audit tool.
155
+ - **Vulnerability discovery:** a security scanner.
156
+
157
+ What Reticle cannot see yet: IndexedDB, Web Workers, closed shadow roots and cross-origin iframes. When part of the page was out of reach, the verdict carries a `coverage` note, so a pass reads as "nothing failed in the part I could see". When the evidence cannot decide, the verdict is `unknown`, which is never counted as a pass.