@reticlehq/server 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/SKILL.md +130 -813
  2. package/dist/bridge/bridge.d.ts +23 -0
  3. package/dist/bridge/bridge.js +80 -2
  4. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  5. package/dist/bridge/bridge.test-harness.js +6 -4
  6. package/dist/capsule/capsule-store.d.ts +2 -2
  7. package/dist/capsule/causal-summary.d.ts +16 -1
  8. package/dist/capsule/causal-summary.js +2 -1
  9. package/dist/cli/cli-doctor.js +41 -13
  10. package/dist/cli/cli-flow-commands.js +8 -8
  11. package/dist/cli/cli-launch.d.ts +0 -17
  12. package/dist/cli/cli-launch.js +0 -28
  13. package/dist/cli/cli-parse.d.ts +1 -1
  14. package/dist/cli/cli-parse.js +1 -1
  15. package/dist/cli/cli-port.d.ts +24 -0
  16. package/dist/cli/cli-port.js +66 -35
  17. package/dist/cli/cli-verify.d.ts +21 -0
  18. package/dist/cli/cli-verify.js +45 -1
  19. package/dist/cli/config-discovery.d.ts +35 -0
  20. package/dist/cli/config-discovery.js +163 -0
  21. package/dist/cli/doctor-daemon-line.js +4 -1
  22. package/dist/cli/doctor-project-line.d.ts +37 -0
  23. package/dist/cli/doctor-project-line.js +35 -0
  24. package/dist/cli/doctor-rows.d.ts +51 -0
  25. package/dist/cli/doctor-rows.js +54 -0
  26. package/dist/cli/doctor-sessions-line.js +6 -3
  27. package/dist/cli/drive-attach.d.ts +99 -0
  28. package/dist/cli/drive-attach.js +203 -0
  29. package/dist/cli/drive-command.d.ts +13 -0
  30. package/dist/cli/drive-command.js +103 -0
  31. package/dist/cli/open-note.d.ts +20 -0
  32. package/dist/cli/open-note.js +37 -0
  33. package/dist/cli/status-next-action.d.ts +37 -0
  34. package/dist/cli/status-next-action.js +61 -0
  35. package/dist/cli.js +65 -70
  36. package/dist/crawl/crawl.d.ts +7 -2
  37. package/dist/crawl/crawl.js +52 -9
  38. package/dist/ee/audit-log.js +2 -2
  39. package/dist/events/body-failures.d.ts +2 -2
  40. package/dist/events/contradiction-folds.d.ts +45 -0
  41. package/dist/events/contradiction-folds.js +42 -0
  42. package/dist/events/contradictions.d.ts +51 -1
  43. package/dist/events/contradictions.js +26 -7
  44. package/dist/events/declared.d.ts +45 -0
  45. package/dist/events/declared.js +73 -0
  46. package/dist/events/echo-mismatch.d.ts +9 -3
  47. package/dist/events/echo-mismatch.js +33 -5
  48. package/dist/events/event-filters.d.ts +1 -1
  49. package/dist/events/event-filters.js +7 -4
  50. package/dist/events/predicate-eval.d.ts +17 -91
  51. package/dist/events/predicate-eval.js +85 -326
  52. package/dist/events/predicate-parse.js +31 -3
  53. package/dist/events/predicate-route.d.ts +32 -0
  54. package/dist/events/predicate-route.js +102 -0
  55. package/dist/events/predicate-schema.d.ts +99 -0
  56. package/dist/events/predicate-schema.js +309 -0
  57. package/dist/events/predicate.d.ts +8 -0
  58. package/dist/events/predicate.js +127 -5
  59. package/dist/events/stale-response.d.ts +2 -3
  60. package/dist/events/stale-response.js +49 -2
  61. package/dist/events/unit-mismatch.d.ts +2 -2
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-replay-run.js +8 -1
  69. package/dist/flows/flow-tools.js +9 -1
  70. package/dist/flows/replay.js +3 -0
  71. package/dist/flows/verify-change-tools.js +1 -1
  72. package/dist/honesty/accepted-write.d.ts +3 -0
  73. package/dist/honesty/accepted-write.js +8 -3
  74. package/dist/honesty/already-true.js +15 -0
  75. package/dist/honesty/blind-spots.js +4 -0
  76. package/dist/honesty/honesty.d.ts +11 -0
  77. package/dist/honesty/honesty.js +1 -0
  78. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  79. package/dist/honesty/uncaptured-bodies.js +9 -2
  80. package/dist/honesty/unread-outcome.d.ts +8 -0
  81. package/dist/honesty/unread-outcome.js +36 -9
  82. package/dist/honesty/unsettled.d.ts +9 -0
  83. package/dist/honesty/unsettled.js +7 -3
  84. package/dist/honesty/verified.d.ts +29 -3
  85. package/dist/honesty/verified.js +67 -8
  86. package/dist/http-server.d.ts +19 -0
  87. package/dist/http-server.js +202 -4
  88. package/dist/impact/impact-recorder.d.ts +23 -0
  89. package/dist/impact/impact-recorder.js +76 -0
  90. package/dist/impact/impact-store.d.ts +47 -0
  91. package/dist/impact/impact-store.js +171 -0
  92. package/dist/index.d.ts +25 -0
  93. package/dist/index.js +60 -10
  94. package/dist/init/agent-rules.d.ts +20 -0
  95. package/dist/init/agent-rules.js +36 -0
  96. package/dist/init/astro-patch.js +27 -1
  97. package/dist/init/confirm.d.ts +21 -3
  98. package/dist/init/confirm.js +32 -5
  99. package/dist/init/connect-steps.d.ts +2 -0
  100. package/dist/init/connect-steps.js +50 -0
  101. package/dist/init/csp-check.d.ts +38 -0
  102. package/dist/init/csp-check.js +92 -0
  103. package/dist/init/csp-doctor.d.ts +25 -0
  104. package/dist/init/csp-doctor.js +49 -0
  105. package/dist/init/cursor.js +6 -9
  106. package/dist/init/detect.d.ts +8 -0
  107. package/dist/init/detect.js +33 -3
  108. package/dist/init/mcp-clients.d.ts +1 -0
  109. package/dist/init/mcp-clients.js +51 -7
  110. package/dist/init/mcp.d.ts +16 -2
  111. package/dist/init/mcp.js +23 -1
  112. package/dist/init/plan-framework.d.ts +10 -0
  113. package/dist/init/plan-framework.js +25 -1
  114. package/dist/init/plan.d.ts +7 -2
  115. package/dist/init/plan.js +66 -30
  116. package/dist/init/run.js +11 -5
  117. package/dist/init/slash-command.d.ts +1 -1
  118. package/dist/init/slash-command.js +12 -4
  119. package/dist/init/snippets.d.ts +1 -1
  120. package/dist/init/snippets.js +35 -7
  121. package/dist/init/vite-config.js +24 -2
  122. package/dist/input/network-mock-tools.js +8 -1
  123. package/dist/input/scroll-find.d.ts +6 -0
  124. package/dist/input/scroll-find.js +17 -2
  125. package/dist/input/scroll-tools.js +4 -0
  126. package/dist/journal/attach-journal.d.ts +9 -1
  127. package/dist/journal/attach-journal.js +13 -2
  128. package/dist/journal/journal-query.d.ts +13 -0
  129. package/dist/journal/journal-query.js +28 -3
  130. package/dist/journal/workspace-gitignore.d.ts +10 -0
  131. package/dist/journal/workspace-gitignore.js +59 -0
  132. package/dist/license/license.d.ts +69 -4
  133. package/dist/license/license.js +84 -15
  134. package/dist/mcp/mcp-proxy.d.ts +7 -93
  135. package/dist/mcp/mcp-proxy.js +183 -250
  136. package/dist/mcp/mcp.d.ts +27 -2
  137. package/dist/mcp/mcp.js +77 -36
  138. package/dist/mcp/proxy-daemon-probe.d.ts +8 -0
  139. package/dist/mcp/proxy-daemon-probe.js +48 -0
  140. package/dist/mcp/proxy-handshake.d.ts +82 -1
  141. package/dist/mcp/proxy-handshake.js +110 -3
  142. package/dist/mcp/proxy-lifecycle.d.ts +10 -1
  143. package/dist/mcp/proxy-lifecycle.js +11 -2
  144. package/dist/mcp/proxy-log.d.ts +63 -0
  145. package/dist/mcp/proxy-log.js +132 -0
  146. package/dist/mcp/server-instructions.d.ts +17 -0
  147. package/dist/mcp/server-instructions.js +56 -0
  148. package/dist/mcp/shared-params.d.ts +27 -0
  149. package/dist/mcp/shared-params.js +36 -0
  150. package/dist/mcp/sse-frame-parser.d.ts +19 -0
  151. package/dist/mcp/sse-frame-parser.js +41 -0
  152. package/dist/project/reticle-dir.d.ts +2 -0
  153. package/dist/project/reticle-dir.js +1 -0
  154. package/dist/session/connection-memory.d.ts +59 -0
  155. package/dist/session/connection-memory.js +108 -0
  156. package/dist/session/dev-command.d.ts +26 -0
  157. package/dist/session/dev-command.js +89 -0
  158. package/dist/session/dev-server-probe.d.ts +68 -0
  159. package/dist/session/dev-server-probe.js +102 -7
  160. package/dist/session/last-act.d.ts +26 -0
  161. package/dist/session/last-act.js +35 -1
  162. package/dist/session/live-control-tools.js +42 -5
  163. package/dist/session/no-session-diagnosis.d.ts +55 -2
  164. package/dist/session/no-session-diagnosis.js +199 -22
  165. package/dist/session/no-session-next-action.d.ts +46 -0
  166. package/dist/session/no-session-next-action.js +85 -0
  167. package/dist/session/no-session-watch.d.ts +40 -1
  168. package/dist/session/no-session-watch.js +172 -11
  169. package/dist/session/pending-commands.d.ts +12 -0
  170. package/dist/session/pending-commands.js +13 -1
  171. package/dist/session/session-manager.d.ts +4 -0
  172. package/dist/session/session-manager.js +35 -5
  173. package/dist/session/session-readiness.js +1 -1
  174. package/dist/session/session.d.ts +10 -1
  175. package/dist/session/session.js +146 -5
  176. package/dist/telemetry/app-instrumented.d.ts +8 -0
  177. package/dist/telemetry/app-instrumented.js +10 -0
  178. package/dist/telemetry/bug-found.d.ts +18 -0
  179. package/dist/telemetry/bug-found.js +14 -0
  180. package/dist/telemetry/daemon-telemetry.js +29 -0
  181. package/dist/telemetry/install-source.d.ts +20 -0
  182. package/dist/telemetry/install-source.js +34 -0
  183. package/dist/telemetry/instrumentation-stall.d.ts +57 -0
  184. package/dist/telemetry/instrumentation-stall.js +103 -0
  185. package/dist/telemetry/license-activation.d.ts +48 -0
  186. package/dist/telemetry/license-activation.js +57 -0
  187. package/dist/telemetry/session-metrics.d.ts +8 -0
  188. package/dist/telemetry/session-metrics.js +10 -0
  189. package/dist/telemetry/telemetry.d.ts +3 -1
  190. package/dist/telemetry/telemetry.js +23 -3
  191. package/dist/tools/act-tools.js +166 -29
  192. package/dist/tools/assert-verdict.js +32 -7
  193. package/dist/tools/browser-tools.js +10 -0
  194. package/dist/tools/dynamic-tools.js +17 -8
  195. package/dist/tools/error-recovery.js +10 -6
  196. package/dist/tools/feedback-tools.js +31 -6
  197. package/dist/tools/invoke-tool.d.ts +1 -1
  198. package/dist/tools/invoke-tool.js +30 -8
  199. package/dist/tools/lease-hint.d.ts +30 -8
  200. package/dist/tools/lease-hint.js +65 -14
  201. package/dist/tools/lease-tools.d.ts +11 -0
  202. package/dist/tools/lease-tools.js +141 -96
  203. package/dist/tools/observe-tools.js +9 -3
  204. package/dist/tools/react-grace.d.ts +4 -0
  205. package/dist/tools/react-grace.js +6 -3
  206. package/dist/tools/read-tools.js +47 -1
  207. package/dist/tools/resolve-target.d.ts +33 -0
  208. package/dist/tools/resolve-target.js +53 -0
  209. package/dist/tools/settle-in-flight.d.ts +15 -0
  210. package/dist/tools/settle-in-flight.js +42 -2
  211. package/dist/tools/snapshot-delta.d.ts +3 -0
  212. package/dist/tools/snapshot-delta.js +67 -6
  213. package/dist/tools/tool-kit.d.ts +38 -3
  214. package/dist/tools/tool-names.d.ts +2 -0
  215. package/dist/tools/tool-names.js +2 -0
  216. package/dist/tools/tool-surface.d.ts +60 -0
  217. package/dist/tools/tool-surface.js +84 -19
  218. package/dist/tools/tools.js +61 -5
  219. package/dist/tools/unadvertised-help.js +9 -6
  220. package/docs/README.md +1 -2
  221. package/docs/actions.mdx +41 -8
  222. package/docs/agent-cheatsheet.md +6 -4
  223. package/docs/architecture.md +6 -0
  224. package/docs/benchmarks.md +60 -12
  225. package/docs/best-practices.mdx +11 -6
  226. package/docs/capabilities.mdx +204 -57
  227. package/docs/cli/affected.mdx +6 -4
  228. package/docs/cli/capsules.mdx +9 -8
  229. package/docs/cli/cloud.mdx +30 -4
  230. package/docs/cli/doctor.mdx +41 -8
  231. package/docs/cli/drive.mdx +34 -15
  232. package/docs/cli/gate.mdx +6 -6
  233. package/docs/cli/help.mdx +8 -6
  234. package/docs/cli/hunt.mdx +20 -4
  235. package/docs/cli/identify.mdx +16 -2
  236. package/docs/cli/init.mdx +35 -8
  237. package/docs/cli/kill.mdx +5 -5
  238. package/docs/cli/license.mdx +1 -1
  239. package/docs/cli/open.mdx +6 -0
  240. package/docs/cli/restart.mdx +6 -6
  241. package/docs/cli/serve.mdx +10 -3
  242. package/docs/cli/status.mdx +29 -5
  243. package/docs/cli/stop.mdx +8 -0
  244. package/docs/cli/verify.mdx +20 -1
  245. package/docs/cli/version.mdx +1 -1
  246. package/docs/cli/watch.mdx +9 -2
  247. package/docs/cli.mdx +44 -22
  248. package/docs/debugging.md +3 -1
  249. package/docs/deploy-checks.md +31 -2
  250. package/docs/desktop.mdx +276 -80
  251. package/docs/docs.json +13 -12
  252. package/docs/enterprise.md +9 -3
  253. package/docs/faq.mdx +49 -35
  254. package/docs/fixtures.md +2 -0
  255. package/docs/flows.md +7 -5
  256. package/docs/for-agents.md +3 -3
  257. package/docs/frameworks.mdx +46 -7
  258. package/docs/gate-plan.md +4 -2
  259. package/docs/gates.md +8 -6
  260. package/docs/getting-started.md +53 -12
  261. package/docs/human-control.md +2 -0
  262. package/docs/index.mdx +43 -1
  263. package/docs/install-agentic.mdx +13 -4
  264. package/docs/install-manual.mdx +6 -4
  265. package/docs/instrumentation.mdx +7 -1
  266. package/docs/integration-patterns.md +3 -1
  267. package/docs/local-registry.md +4 -2
  268. package/docs/multi-agent-testing.md +3 -1
  269. package/docs/packages/babel-plugin.mdx +15 -6
  270. package/docs/packages/browser.mdx +2 -2
  271. package/docs/packages/core.mdx +26 -16
  272. package/docs/packages/electron.mdx +5 -7
  273. package/docs/packages/eslint-plugin.mdx +5 -3
  274. package/docs/packages/next.mdx +3 -5
  275. package/docs/packages/react.mdx +3 -3
  276. package/docs/packages/server.mdx +2 -2
  277. package/docs/packages/tauri.mdx +6 -6
  278. package/docs/packages/test.mdx +18 -7
  279. package/docs/packages/vite-plugin.mdx +4 -6
  280. package/docs/packages.mdx +14 -13
  281. package/docs/platform-integration.md +2 -0
  282. package/docs/predicates.mdx +76 -7
  283. package/docs/quickstart.mdx +51 -5
  284. package/docs/recipes.mdx +63 -23
  285. package/docs/skill-file.mdx +3 -1
  286. package/docs/state-management.mdx +2 -0
  287. package/docs/system-map.md +3 -1
  288. package/docs/telemetry-contract.md +33 -5
  289. package/docs/telemetry-events.mdx +63 -18
  290. package/docs/telemetry.md +8 -5
  291. package/docs/testing.md +35 -1
  292. package/docs/token-efficiency.md +8 -2
  293. package/docs/tools-act-and-wait.mdx +64 -14
  294. package/docs/tools-act-sequence.mdx +34 -17
  295. package/docs/tools-act.mdx +24 -11
  296. package/docs/tools-assert.mdx +69 -10
  297. package/docs/tools-console.mdx +11 -7
  298. package/docs/tools-inspect.mdx +11 -8
  299. package/docs/tools-navigate.mdx +41 -5
  300. package/docs/tools-network.mdx +42 -9
  301. package/docs/tools-observe.mdx +66 -10
  302. package/docs/tools-overview.mdx +26 -23
  303. package/docs/tools-query.mdx +52 -8
  304. package/docs/tools-session-and-feedback.mdx +35 -9
  305. package/docs/tools-sessions.mdx +39 -10
  306. package/docs/tools-snapshot.mdx +57 -3
  307. package/docs/tools-state.mdx +24 -1
  308. package/docs/tools-tools-and-run.mdx +55 -24
  309. package/docs/tools-wait-for.mdx +34 -15
  310. package/docs/troubleshooting.mdx +80 -10
  311. package/docs/usage.md +46 -24
  312. package/docs/vs-chrome-devtools-mcp.mdx +41 -0
  313. package/docs/vs-playwright-mcp.mdx +42 -2
  314. package/docs/vs-screenshots.mdx +42 -1
  315. package/docs/what-is-reticle.mdx +1 -1
  316. package/docs/why-reticle.mdx +3 -1
  317. package/package.json +5 -5
  318. package/docs/desktop-apps.md +0 -265
@@ -4,13 +4,15 @@ description: Block until a predicate holds. Or return immediately if it is alrea
4
4
  icon: hourglass
5
5
  ---
6
6
 
7
- `reticle_wait_for` takes the same predicate grammar as [`reticle_act_and_wait`](/tools-act-and-wait), without the action. Use it when the thing you are waiting for was triggered by something other than your own click. A timer, a websocket push, a background refresh.
7
+ `reticle_wait_for` blocks until a predicate holds, or returns straight away if it is already satisfied in the recent event buffer. Reach for it when you are waiting on something you did not cause. It reads and waits; it does not produce a verdict, so finish with [`reticle_assert`](/tools-assert).
8
+
9
+ It takes the same predicate grammar as [`reticle_act_and_wait`](/tools-act-and-wait), without the action. Use it when the thing you are waiting for was triggered by something other than your own click. A timer, a websocket push, a background refresh.
8
10
 
9
11
  ## Example
10
12
 
11
13
  ```json
12
14
  {
13
- "predicate": { "kind": "text", "value": "Invalid email or password" },
15
+ "predicate": { "kind": "text", "contains": "Invalid email or password" },
14
16
  "timeout_ms": 3000
15
17
  }
16
18
  ```
@@ -22,12 +24,12 @@ Real response:
22
24
  "pass": true,
23
25
  "evidence": [
24
26
  {
25
- "ref": "e104",
27
+ "ref": "e606",
26
28
  "role": "alert",
27
29
  "name": "Invalid email or password",
28
- "visible": false,
30
+ "visible": true,
29
31
  "source": "src/components/Login.tsx:72",
30
- "states": ["present", "hidden", "enabled"]
32
+ "states": ["present", "visible", "enabled"]
31
33
  }
32
34
  ]
33
35
  }
@@ -36,31 +38,48 @@ Real response:
36
38
  The evidence is the element itself, with the file and line that rendered it.
37
39
 
38
40
  <Note>
39
- Look at `visible: false` and `states: ["present", "hidden", "enabled"]`. The alert exists in the
40
- DOM and is not visible. `wait_for` on a `text` predicate matches presence, if you need it on
41
- screen, that is a different question, and [`reticle_inspect`](/tools-inspect) answers it.
41
+ A `text` predicate matches **presence** by default, so an element that is in the DOM but hidden
42
+ satisfies it and comes back with `visible: false` and `states: ["present", "hidden", "enabled"]`.
43
+ If you need it on screen, say so: `{ "kind": "text", "contains": "…", "visible": true }`.
42
44
  </Note>
43
45
 
46
+ The field is `contains`. `value` and `text` are accepted as aliases and rewritten to it, but `contains` is what the published schema declares.
47
+
44
48
  ## It checks the buffer first
45
49
 
46
50
  `wait_for` does not always wait. If the predicate is already satisfied in the recent event buffer, it returns immediately. This means you can act, then wait, without racing. The event that happened while you were composing the second call is not lost.
47
51
 
48
52
  ## Arguments
49
53
 
50
- | Argument | What it does |
51
- | --- | --- |
52
- | `predicate` (or `until`) | `{ kind, ...fields }`, see the [predicate grammar](/tools-act-and-wait) |
53
- | `timeout_ms` | Maximum wait |
54
- | `since` | Cursor from a prior act, to scope the wait |
54
+ | Argument | What it does |
55
+ | ------------------------ | ----------------------------------------------------------------- |
56
+ | `predicate` (or `until`) | `{ kind, ...fields }`, see the [predicate reference](/predicates) |
57
+ | `timeout_ms` | Maximum wait. Default 4000 |
58
+ | `since` | Cursor from a prior act, to widen or narrow the window explicitly |
59
+
60
+ By default the wait only counts events since your last act, so a signal that fired before the action cannot satisfy it. A miss returns a near-miss diagnosis rather than a bare `false`:
61
+
62
+ ```json
63
+ {
64
+ "pass": false,
65
+ "failureReason": "no element matched {\"text\":\"Deployments\"}",
66
+ "observed": "no matching element on the page",
67
+ "expected": "an element matching {\"text\":\"Deployments\"}",
68
+ "assertion": "element.present",
69
+ "evidence": { "presentTestids": ["login-email", "login-password", "login-submit"] }
70
+ }
71
+ ```
72
+
73
+ `presentTestids` there is the answer to the real question. The text was missing because the app had logged out, not because the render was slow.
55
74
 
56
75
  ## Prefer `act_and_wait` when you caused it
57
76
 
58
77
  If your own action is supposed to trigger the thing, put the predicate in the action:
59
78
 
60
79
  ```json
61
- { "ref": "e103", "action": "click", "until": { "kind": "signal", "name": "auth:granted" } }
80
+ { "ref": "e605", "action": "click", "until": { "kind": "signal", "name": "auth:granted" } }
62
81
  ```
63
82
 
64
- One call instead of two, no gap for an event to slip through, and. The part that matters. The expectation is recorded _before_ the action rather than chosen after it.
83
+ One call instead of two, no gap for an event to slip through, and, the part that actually matters, the expectation is recorded _before_ the action rather than chosen after it.
65
84
 
66
85
  Save `wait_for` for consequences you did not cause.
@@ -1,10 +1,14 @@
1
1
  ---
2
- title: Troubleshooting: it worked on my tab
2
+ title: 'Troubleshooting: it worked on my tab'
3
3
  sidebarTitle: Troubleshooting
4
4
  description: Nothing connected, the click did nothing, the verdict says unknown. The failures people actually hit, and the diagnostic for each.
5
5
  icon: stethoscope
6
6
  ---
7
7
 
8
+ **Run `npx @reticlehq/server doctor` first.** It checks Chromium, the daemon, the port and the connected sessions in one command, and it names the problem in most cases. If `sessions` reads zero, your app is not reaching the bridge, and a port mismatch is the usual reason.
9
+
10
+ Reticle is a verification layer that runs a dev-only SDK inside your running web app. Two halves have to be working: your agent needs the MCP server registered, and your app needs the SDK connected to the local daemon. Almost every failure below is one of those two halves being absent.
11
+
8
12
  Most of these were hit while writing these docs. The diagnostics quoted are real output.
9
13
 
10
14
  ## Start here
@@ -17,7 +21,7 @@ npx @reticlehq/server doctor
17
21
  reticle doctor
18
22
  node v22.14.0
19
23
  chromium ✓ installed
20
- daemon ✓ running on :4400 (pid 24735, v2.8.0)
24
+ daemon ✓ running on :4400 (pid 24735, v<the version you have>)
21
25
  sessions ✓ 1 page connected
22
26
  bridge port 4400 (your app must dial THIS port, not your dev-server port)
23
27
  daemon log /Users/you/.reticle/daemon-4400.log
@@ -35,30 +39,51 @@ By far the most common. Reticle's own diagnostic is unusually good here. It tell
35
39
  ```json
36
40
  {
37
41
  "sessionCount": 0,
38
- "why": "no browser session connected, but one WAS connected to this daemon earlier, so the wiring is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen the app (or run `reticle open`), or reload the tab. You do not have to wait for the human: reticle_lease {action:\"acquire\", url} opens a browser Reticle drives itself."
42
+ "why": "no browser session connected, but one WAS connected to this daemon earlier, so the wiring is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen the app (or run `reticle open`), or reload the tab. You do not have to wait for the human: reticle_lease {action:\"acquire\", url} opens a browser Reticle drives itself, and returns a sessionId you can use immediately (reach it with reticle_run {tool:\"reticle_lease\"} if it is not advertised directly; release it when you finish). Then call reticle_sessions again."
39
43
  }
40
44
  ```
41
45
 
42
- Read the `why`. "One WAS connected earlier" means stop debugging your config, just reopen the tab.
46
+ Read the `why`. If it says one **was** connected earlier, stop debugging your config and just reopen the tab. If it says nothing ever connected, the wiring itself is wrong, so work through the list below.
43
47
 
44
48
  If nothing ever connected:
45
49
 
46
- - Is the app running, and open in a browser?
50
+ - Is the app running, and open in a browser? If nothing is serving it, see [Who starts the dev server](#who-starts-the-dev-server): this is the agent's job, not a question to put to you.
47
51
  - Is `reticle.connect()` actually executing? Check the dev guard is true.
48
52
  - **Port mismatch.** This is the big one.
49
53
 
54
+ ### Who starts the dev server
55
+
56
+ **The agent does.** If no dev server is listening, the agent reads your project's own dev script out of `package.json`, runs it in the background, tells you in one line that it is running and how to stop it, and carries on. It used to be told the opposite, "Reticle never starts the dev server for you, that's your job", and agents obeyed it exactly: they read the rule, declined to start anything, and ended the turn with nothing verified.
57
+
58
+ The daemon still does not do it, on purpose. A build process started by a long-lived background daemon is invisible to whoever's machine it is running on, was never consented to, cannot easily be stopped, and orphans when the daemon exits. It would also mean a piece of standing infrastructure runs arbitrary `package.json` scripts. An agent already has shell access in your repo, already runs install and build commands, and runs inside a host that asks you before it does. A dev server the agent starts is in the transcript, attributable to a step you approved, and stoppable.
59
+
60
+ The agent does not have to work out the command. When `reticle_sessions` comes back with nothing connected, it carries a `next_action` naming which of four cases this is and, where one applies, the literal command and port: start the dev server, run `reticle init`, open the app, or reopen a tab that went away. The command is read from this project's own scripts, so a project with no recognisable dev script gets no command rather than a guess.
61
+
62
+ The guards, which apply wherever this instruction appears:
63
+
64
+ 1. **Never start a second one.** If something is already listening on the app's port, it is adopted.
65
+ 2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, the agent says so and stops rather than inventing one.
66
+ 3. **Never kill anything**: not a dev server, not a daemon, not a port holder, including one it started.
67
+ 4. **Background it, and say so.** You must know a server is running and how to stop it. A dev server you do not know about is the same failure one step later.
68
+ 5. **The permission prompt is your agent host's.** Nothing in Reticle bypasses or auto-approves it, and you can always decline.
69
+
70
+ The corollary matters as much: if a dev server IS already running and nothing connects, the cause is the SDK not loading in the page. An agent that answers this state by asking you to start a server you are already running has ended the conversation with nothing fixed. Work the checklist above instead.
71
+
50
72
  ### The port mismatch
51
73
 
52
74
  Your app dials a port; the daemon listens on one. When they disagree, the app runs perfectly and silently never connects. The browser console says so:
53
75
 
54
76
  ```
55
- [Reticle] could not reach the bridge at ws://localhost:4460/reticle after 3 attempts.
56
- Is the Reticle daemon running on that port? If your app runs in a container/devcontainer/WSL,
57
- the daemon is on a different host, so set the WS URL explicitly
58
- (Vite: VITE_RETICLE_WS_URL, or reticle.connect({ url })). Still retrying…
77
+ [Reticle] this page could not open a websocket to ws://localhost:4460/reticle. 3 attempts, all
78
+ failed. That is everything the page can see: from inside the browser it cannot tell a daemon that
79
+ is not there from one it is not allowed to reach, so this is not evidence about the daemon. What
80
+ answers it: run `npx @reticlehq/server status` to see whether one is listening and on which port.
81
+ If your app runs in a container, devcontainer or WSL, the daemon is on a different host, so set
82
+ the URL explicitly (Vite: VITE_RETICLE_WS_URL, or reticle.connect({ url })). An https page cannot
83
+ open a ws:// socket at all. Still retrying…
59
84
  ```
60
85
 
61
- That message names the exact port it tried. Compare it with `bridge port` from `doctor` and set the one that is wrong:
86
+ That message names the exact address it tried. Compare its port with `bridge port` from `doctor` and set whichever is wrong:
62
87
 
63
88
  ```bash
64
89
  VITE_RETICLE_WS_URL=ws://localhost:4400/reticle pnpm dev
@@ -188,6 +213,51 @@ Stale sessions pointing at dev servers you restarted an hour ago look completely
188
213
 
189
214
  `reticle_session { action: "end" }` when you are done, `{ action: "yield" }` when pausing. Both revive on your next action.
190
215
 
216
+ ## Frequently asked questions
217
+
218
+ <AccordionGroup>
219
+ <Accordion title="Why does reticle_sessions return nothing when my app is clearly running?">
220
+ Your app running is not the same as your app being connected. The SDK has to dial the daemon's
221
+ bridge port over a WebSocket. If `npx @reticlehq/server doctor` shows the daemon up and
222
+ `sessions` at zero, either `reticle.connect()` never executed (check the dev guard is actually
223
+ true) or the app dialled the wrong port. The browser console names the exact address it tried.
224
+ </Accordion>
225
+
226
+ <Accordion title="Which port should my app dial?">
227
+ The bridge port, which defaults to `4400`. It is **not** your dev-server port. A dev server on
228
+ 4312 and a daemon on 4400 is the normal, correct arrangement. `doctor` prints the bridge port
229
+ under `bridge port`.
230
+ </Accordion>
231
+
232
+ <Accordion title="I restarted my agent and the reticle tools still are not there.">
233
+ If you registered the MCP server while the agent was running, restart the agent process itself:
234
+ quit and reopen Claude Code, reload the window in Cursor, or press Start in `.vscode/mcp.json` in
235
+ VS Code. `/mcp` only manages servers that were already loaded, so it cannot discover a new one. If
236
+ the tools are still missing, run `claude mcp list` and check `reticle` is present.
237
+ </Accordion>
238
+
239
+ <Accordion title="Does a verdict of unknown mean the test failed?">
240
+ No, and it does not mean it passed either. `verified: "unknown"` means Reticle drove the app and
241
+ could not tell what happened, usually because the page never settled inside the observation
242
+ window. Report it as unknown. If you hit one on an unthrottled context and believe it is wrong,
243
+ that is a Reticle defect worth sending with `reticle_feedback`.
244
+ </Accordion>
245
+
246
+ <Accordion title="Is it safe to kill port 4400 to restart things?">
247
+ No. `lsof -ti tcp:4400 | xargs kill -9` matches your agent's MCP proxy as well as the daemon and
248
+ takes the connection down with it. Use `npx @reticlehq/server stop`, or at minimum add
249
+ `-sTCP:LISTEN` so only the listener matches.
250
+ </Accordion>
251
+
252
+ <Accordion title="Why does my agent click a button and nothing happens?">
253
+ Check the response for a `warning` about a throttled tab. A backgrounded browser tab has its
254
+ timers suppressed, so synthetic pointer gestures can silently no-op while the action still
255
+ reports success. Drive a lease instead, which is a context Reticle owns and never backgrounds.
256
+ If the tab is fine, check `occluded` on the element: a click landing on a modal overlay looks
257
+ identical from the outside.
258
+ </Accordion>
259
+ </AccordionGroup>
260
+
191
261
  <Card title="Still stuck?" icon="comments" href="/tools-session-and-feedback">
192
262
  `reticle feedback --agent` works even when nothing is installed and the daemon will not start.
193
263
  </Card>
package/docs/usage.md CHANGED
@@ -4,7 +4,9 @@ description: 'The full reference and cookbook: every tool, flag, and workflow, w
4
4
  icon: book
5
5
  ---
6
6
 
7
- The full reference and cookbook. If you haven't set up Reticle yet, start with [Getting Started](getting-started.md).
7
+ Every Reticle drive is the same four steps: **look** (`reticle_snapshot` / `reticle_query`), **act** (`reticle_act` / `reticle_act_sequence`), **observe** (`reticle_observe` / `reticle_network` / `reticle_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.
8
+
9
+ If you haven't set up Reticle yet, start with [Getting Started](getting-started.md).
8
10
 
9
11
  > **Start here instead, unless you want the long form.** This page predates the focused reference and still holds the fullest single narrative, but almost everything in it now has a page of its own that is shorter, searchable and verified against a running app:
10
12
  >
@@ -41,6 +43,9 @@ The full reference and cookbook. If you haven't set up Reticle yet, start with [
41
43
  13. [Best practices & gotchas](#13-best-practices--gotchas)
42
44
  14. [FAQ](#14-faq)
43
45
  15. [Security & privacy](#15-security--privacy)
46
+ 16. [Presenter mode, narration & fake clock](#16-presenter-mode-narration--fake-clock-watch--control)
47
+ 17. [Evidence-of-effect, act+await, state, capabilities, replay](#17-evidence-of-effect-actawait-state-capabilities-replay)
48
+ 18. [Real input mode: native hover & drag](#18-real-input-mode-native-hover--drag)
44
49
 
45
50
  ---
46
51
 
@@ -86,7 +91,7 @@ List connected tabs. → `{ sessions: [{ sessionId, url, title, lastSeenMs, hidd
86
91
  A semantic, accessibility-tree view of the page.
87
92
 
88
93
  - **args:** `mode?: 'full' | 'interactive' | 'status'` (default `full`), `scope?` (CSS selector or ref), `diff?: boolean`, `sessionId?`.
89
- - **returns:** `{ tree, status: { route, title, visibleDialogs }, nodes, truncated, cost: { bytes, tokens } }`.
94
+ - **returns:** `{ tree, status: { route, title, visibleDialogs, overlayHidingPage }, nodes, truncated, cost: { bytes, tokens } }`.
90
95
  - **`diff: true`** returns only what changed since your last snapshot of the same scope/mode: `{ mode: 'delta', delta: { added, removed, addedCount, removedCount } }` or `{ mode: 'unchanged' }` (no full tree). The first call (and any call after a route change) still returns the full tree. ~99% fewer tokens to re-look after an action; see [token-efficiency.md](token-efficiency.md).
91
96
  - **`cost`** is an estimated size of the result. Re-scope (`mode`/`scope`) before reading if large.
92
97
 
@@ -123,11 +128,11 @@ Deep detail on one element, including the signals a snapshot/a11y tree omits, so
123
128
 
124
129
  Perform one action / several in order.
125
130
 
126
- - **`reticle_act` args:** `ref`, `action`, `args?`, `refuseWhenThrottled?`, `sessionId?`. → `{ since, dispatched, settled, settleReason, result, session, warning? }` where `result = { ok, ref, action, dispatched, settled, settleReason, effect }`. The `session` block `{ lastSeenMs, throttled, focused }` (F2) reports tab health on every act; when `throttled` is true a `warning` string is also attached. Pass `refuseWhenThrottled: true` to hard-fail instead of warning (opt-in; default is warn-only so background testing never breaks).
131
+ - **`reticle_act` args:** `ref`, `action`, `args?`, `refuseWhenThrottled?`, `sessionId?`. → `{ since, dispatched, settled, settleReason, result, session, warning? }` where `result = { ok, ref, action, dispatched, settled, settleReason, effect }`. The `session` block `{ lastSeenMs, throttled, focused }` reports tab health on every act; when `throttled` is true a `warning` string is also attached. Pass `refuseWhenThrottled: true` to hard-fail instead of warning (opt-in; default is warn-only so background testing never breaks).
127
132
  - **`reticle_act_sequence` args:** `steps: [{ ref, action, args? }]`. → `{ since, dispatched, result }` where `result = { ok, count, effects: [...], steps: [...] }` (one `effect` per step; each step carries its own `dispatched`/`settled`/`settleReason`).
128
133
  - See [§5](#5-actions-full-list) for the action list.
129
134
 
130
- **Dispatch vs settle (F1).** The action is two phases: the **dispatch** (the synchronous click/fill, which is what can fail) and the **settle** (waiting one animation frame so React's commit lands before we return). The settle is **bounded** (~200ms): in a throttled/background tab `requestAnimationFrame` never fires, so Reticle falls back to a timer and resolves anyway. A settle timeout is therefore **never an error**: `reticle_act` resolves with `settled:false, settleReason:"timeout"` and the dispatch (the click) has still landed. Only a real dispatch failure (stale ref, wrong element type) throws.
135
+ **Dispatch vs settle.** The action is two phases: the **dispatch** (the synchronous click/fill, which is what can fail) and the **settle** (waiting one animation frame so React's commit lands before we return). The settle is **bounded** (~200ms): in a throttled/background tab `requestAnimationFrame` never fires, so Reticle falls back to a timer and resolves anyway. A settle timeout is therefore **never an error**: `reticle_act` resolves with `settled:false, settleReason:"timeout"` and the dispatch (the click) has still landed. Only a real dispatch failure (stale ref, wrong element type) throws.
131
136
 
132
137
  | top-level field | meaning |
133
138
  | --- | --- |
@@ -179,7 +184,7 @@ The timeline + summary of what happened.
179
184
  Act, then wait for a predicate: the whole act→observe→assert loop in one hop.
180
185
 
181
186
  - **args:** `ref`, `action`, `args?`, `until: <predicate>`, `timeout_ms?` (default 4000; 0 = evaluate once), `refuseWhenThrottled?`, `sessionId?`.
182
- - **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` (F2) 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.
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.
183
188
 
184
189
  ### `reticle_wait_for`
185
190
 
@@ -210,7 +215,7 @@ Compare what the API **returned** against what the page **renders**.
210
215
 
211
216
  Fast targeted lookups without a full timeline.
212
217
 
213
- - `reticle_network({ since?, method?, urlContains?, status? })` → `{ calls }`
218
+ - `reticle_network({ since?, method?, urlContains?, status?, bodies? })` → `{ calls }`. Pass `bodies: false` for a body-free listing (method/url/status/timing only); bodies dominate the payload, so the common "did POST /x return 200?" read gets much cheaper.
214
219
  - `reticle_console({ level?, since? })` → `{ logs }`
215
220
  - `reticle_animations()` → running/recent animations.
216
221
 
@@ -281,10 +286,10 @@ List interactive elements + console-error count for autonomous exploration. See
281
286
 
282
287
  ### Flows, recorder & self-healing (`.reticle/`)
283
288
 
284
- `reticle_contract_save`, `reticle_flow_save` / `reticle_flow_save_recorded` / `reticle_flow {action:"list"}` / `reticle_flow {action:"load"}` / `reticle_flow_replay` / `reticle_flow_verify`, `reticle_flow_heal`, `reticle_annotate`: record once, replay forever (anchored on testid/signal, or on an auto-derived component/source anchor when there's no testid), with legible drift + self-heal. Full guide: [Flows, the recorder & self-healing](flows.md).
289
+ `reticle_contract_save`, `reticle_flow_save` / `reticle_flow_save_recorded` / `reticle_flow {action:"list"}` / `reticle_flow {action:"load"}` / `reticle_flow_replay` / `reticle_verify {action:"flows"}`, `reticle_flow_heal`, `reticle_annotate`: record once, replay forever (anchored on testid/signal, or on an auto-derived component/source anchor when there's no testid), with legible drift + self-heal. Full guide: [Flows, the recorder & self-healing](flows.md).
285
290
 
286
- - **`reticle_flow_verify({ names?, sessionId? })`** is the regression-suite call: it replays EVERY saved flow (or a subset) deterministically and returns one verdict `{ status, passed, failed, failures: [{ flow, verdict, whatChanged, whereInSource, nextAction }] }`. Passing flows are counted; only failures carry detail. Run it after any change: one call, no LLM per flow.
287
- - **Decision envelope:** on a drift/fail, `reticle_flow_replay` (and each `reticle_flow_verify` failure) returns the actionable fix: `whatChanged`, `whereInSource` (`file:line`), and a one-line `nextAction` (e.g. "rebind the anchor to 'new-deploy', or update the flow if intended").
291
+ - **`reticle_verify({ action: "flows", names?, sessionId? })`** is the regression-suite call: it replays EVERY saved flow (or a subset) deterministically and returns one verdict `{ status, passed, failed, failures: [{ flow, verdict, whatChanged, whereInSource, nextAction }] }`. Passing flows are counted; only failures carry detail. Run it after any change: one call, no LLM per flow.
292
+ - **Decision envelope:** on a drift/fail, `reticle_flow_replay` (and each `reticle_verify {action:"flows"}` failure) returns the actionable fix: `whatChanged`, `whereInSource` (`file:line`), and a one-line `nextAction` (e.g. "rebind the anchor to 'new-deploy', or update the flow if intended").
288
293
 
289
294
  ### Human-in-the-loop control
290
295
 
@@ -344,11 +349,14 @@ A **predicate** declares what should be true. `reticle_assert` / `reticle_wait_f
344
349
  // An element exists / is in a state
345
350
  { "kind": "element", "query": { "role": "dialog", "name": "Confirm" }, "state": "visible" }
346
351
  // query supports: role, name, text, label, placeholder, testid, alt, scope
347
- // state: visible | hidden | enabled | disabled | checked | expanded | focused | present
352
+ // state: visible | hidden | enabled | disabled | checked | expanded | focused | present | inViewport
353
+ // inViewport asserts the element is in the viewport NOW (not just in the DOM), so a scrollIntoView is gradeable
348
354
  // add "absent": true to assert it is NOT there (regression / removal)
349
355
 
350
- // Visible text anywhere (optionally scoped via an element query instead)
356
+ // Visible text: page-wide, or `scope` it to a subtree (CSS selector or ref) so a match
357
+ // in a background tab can't satisfy an assertion about the dialog that just opened
351
358
  { "kind": "text", "contains": "Saved successfully", "visible": true }
359
+ { "kind": "text", "contains": "Floor 3", "scope": "[role=dialog]" }
352
360
 
353
361
  // A network call happened
354
362
  { "kind": "net", "method": "POST", "urlContains": "/api/order", "status": 200, "since": 1820 }
@@ -406,7 +414,7 @@ A `state` assertion is graded as a **consequence** (a wrong element or stale ren
406
414
  | `select` | `{ value }` | `<select>` option |
407
415
  | `check` / `uncheck` | n/a | checkbox/radio |
408
416
  | `submit` | n/a | submits the element's `<form>` |
409
- | `press` | `{ key }` | keydown/up (default `Enter`) |
417
+ | `press` | `{ key, modifiers? }` | keydown/up (default `Enter`); `modifiers`: an array of `Meta` / `Control` / `Shift` / `Alt` for Cmd+K-style shortcuts |
410
418
  | `scrollIntoView` | n/a | |
411
419
  | `upload` | `{ name, content?, type? }` | sets a file on `<input type=file>` |
412
420
  | `drag` | `{ toRef }` | pointer-based drag (dnd-kit / rbd) + HTML5 DnD |
@@ -483,7 +491,7 @@ reticle_assert({ timeout_ms: 3000, predicate: { kind: "element",
483
491
  query: { text: "Invoice #4821", scope: "[data-testid=item-list]" }, state: "visible" } })
484
492
  ```
485
493
 
486
- > Note: if your list is **virtualized** (react-window/virtuoso), off-screen rows aren't in the DOM yet. Scroll-to-find support is on the roadmap; for now scroll the container or assert against the data via an `reticle.signal`.
494
+ > Note: if your list is **virtualized** (react-window/virtuoso), an off-screen row is not in the DOM at all, so `reticle_query` correctly finds nothing. Use **`reticle_scroll_to`** to scroll the windowed container until the row renders, then query it: `reticle_scroll_to({ by: "text", value: "Invoice #4821", container: "[data-testid=item-list]" })`. Asserting against the data with `reticle.signal` or `reticle_state` also works and is cheaper.
487
495
 
488
496
  ### "Login form: does it actually authorize?"
489
497
 
@@ -560,7 +568,7 @@ reticle.describe({
560
568
  });
561
569
  ```
562
570
 
563
- The agent reads this back with `reticle_capabilities()`; see [§3](#3-tool-reference).
571
+ The agent reads this back with `reticle_capabilities()`; see [§3](#3-the-tools-full-reference).
564
572
 
565
573
  ```jsonc
566
574
  reticle_assert({ timeout_ms: 30000, predicate: {
@@ -569,7 +577,7 @@ reticle_assert({ timeout_ms: 30000, predicate: {
569
577
 
570
578
  #### Keeping signals from drifting (lint)
571
579
 
572
- Signals only help if you actually emit one whenever user-visible state changes. The `@reticlehq/eslint-plugin` package ships one rule, `reticle/require-signal-on-mutation`, that flags any function which calls a configured store **mutator** but never fires the **signal callee** in the same body, so the signal map can't silently fall behind the store.
580
+ Signals only help if you actually emit one whenever user-visible state changes. The `@reticlehq/eslint-plugin` package ships `reticle/require-signal-on-mutation`, which flags any function that calls a configured store **mutator** but never fires the **signal callee** in the same body, so the signal map can't silently fall behind the store. (The package also ships `reticle/no-internal-tags`; both are on in its `recommended` config.)
573
581
 
574
582
  ```js
575
583
  // eslint.config.mjs
@@ -705,7 +713,7 @@ Reticle is cheap by design ([benchmark](token-efficiency.md)), but keep it that
705
713
  - **Stable handles for controls.** Prefer `data-testid` over names that include dynamic counts (e.g. "Notifications (3)"); the count changes the accessible name.
706
714
  - **Always thread `since`.** Pass the cursor from `reticle_act` into `observe`/`assert` so you only consider what happened _after_ the action.
707
715
  - **Use `timeout_ms` for async.** Don't assert instantly on something that arrives over the network or after a re-render.
708
- - **Watch `session.throttled` (F2).** Background tabs throttle timers/rAF/pointer gestures, so an act can silently no-op. Every `reticle_act` / `reticle_assert` / `reticle_act_and_wait` result carries `session: { lastSeenMs, throttled, focused }` and, when throttled, a `warning`. Refocus the tab (or run it foregrounded) before driving; pass `refuseWhenThrottled: true` to hard-fail instead.
716
+ - **Watch `session.throttled`.** Background tabs throttle timers/rAF/pointer gestures, so an act can silently no-op. Every `reticle_act` / `reticle_assert` / `reticle_act_and_wait` result carries `session: { lastSeenMs, throttled, focused }` and, when throttled, a `warning`. Refocus the tab (or run it foregrounded) before driving; pass `refuseWhenThrottled: true` to hard-fail instead.
709
717
  - **Scope big pages.** On dashboards with hundreds of elements, scope queries to the panel you care about.
710
718
  - **Never breaks your app.** Observers are additive and reversible (`reticle.disconnect()` restores patched globals). It won't interfere with your app's behavior.
711
719
 
@@ -713,19 +721,33 @@ Reticle is cheap by design ([benchmark](token-efficiency.md)), but keep it that
713
721
 
714
722
  ## 14. FAQ
715
723
 
716
- **Does this run in production?** No. Keep `reticle.connect()` behind a dev guard. The SDK is side-effect-free and tree-shakes out of prod builds.
724
+ ### Does this run in production?
725
+
726
+ No. Keep `reticle.connect()` behind a dev guard. The SDK is side-effect-free and tree-shakes out of prod builds.
727
+
728
+ ### Do I have to change my components?
729
+
730
+ No, for basic look/act/observe. You'll get better results by adding `data-testid`s and labels where the agent needs precision.
731
+
732
+ ### Does it work without React?
733
+
734
+ 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).
735
+
736
+ ### Can it judge whether my UI looks good?
737
+
738
+ No. Reticle verifies behavior, not aesthetics. Visual/pixel correctness and "does it feel right" remain human (or a visual-diff tool).
717
739
 
718
- **Do I have to change my components?** No, for basic look/act/observe. You'll get better results by adding `data-testid`s and labels where the agent needs precision.
740
+ ### Does it replace Playwright/Cypress?
719
741
 
720
- **Does it work without React?** 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).
742
+ No. Those are your scripted CI suite. Reticle is for in-loop verification while the agent codes, and for the cases you never automated. They compose.
721
743
 
722
- **Can it judge whether my UI _looks_ good?** No. Reticle verifies behavior, not aesthetics. Visual/pixel correctness and "does it feel right" remain human (or a visual-diff tool).
744
+ ### How does it compare to Playwright MCP / Chrome DevTools MCP?
723
745
 
724
- **Does it replace Playwright/Cypress?** No. Those are your scripted CI suite. Reticle is for in-loop verification while the agent codes, and for the cases you never automated. They compose.
746
+ Those let an agent drive/ inspect a _separate_ browser; Reticle verifies your _own running app_ (real session/auth) with assertions + regression as first-class, far more cheaply. See [Reticle vs Playwright MCP](vs-playwright-mcp.mdx) and [Reticle vs Chrome DevTools MCP](vs-chrome-devtools-mcp.mdx).
725
747
 
726
- **How does it compare to Playwright MCP / Chrome DevTools MCP?** Those let an agent drive/ inspect a _separate_ browser; Reticle verifies your _own running app_ (real session/auth) with assertions + regression as first-class, far more cheaply. See the README comparison.
748
+ ### Multiple tabs/apps?
727
749
 
728
- **Multiple tabs/apps?** Each is a session; pass `sessionId` to any tool when more than one is connected (`reticle_sessions` lists them).
750
+ Each is a session; pass `sessionId` to any tool when more than one is connected (`reticle_sessions` lists them).
729
751
 
730
752
  ---
731
753
 
@@ -794,7 +816,7 @@ A session starts on the agent's first activity and must reliably end even when t
794
816
  - **Agent kills the Reticle server process** (so no push can arrive) → the SDK self-ends the session after it can't reach the bridge for `BRIDGE_LOST_MS` (~15s), showing "lost connection to Reticle."
795
817
  - **Slow-but-alive agent** → if it goes quiet long enough to auto-end and then acts again, the session **revives** automatically (an explicit `reticle_session {action:"end"}` stays terminal).
796
818
 
797
- Tune the idle window with `reticle_session({ idleEndMs })`; it updates both the browser timer and the server reaper. The human keeps the panel (with Copy/Export of the run) after any end.
819
+ Tune the idle window with `reticle_session({ action: "tune", idleEndMs })`; it updates both the browser timer and the server reaper. The human keeps the panel (with Copy/Export of the run) after any end.
798
820
 
799
821
  ### `reticle_session {action:"narrate"}`: show the agent's intent
800
822
 
@@ -80,3 +80,44 @@ Neither tool forbids the other. A reasonable setup:
80
80
  handlers, double submits, silent validation.
81
81
  </Card>
82
82
  </CardGroup>
83
+
84
+ ## FAQ
85
+
86
+ <AccordionGroup>
87
+
88
+ <Accordion title="Which two bugs did Chrome DevTools MCP miss?">
89
+ The ones where the page is fine and the program is not: a mutation that never reached the store,
90
+ and a success handler that fired for the wrong reason. DevTools MCP is DOM-and-network only, so it
91
+ can describe what the page looks like and what it requested, but it cannot ask the application
92
+ what it believes. There is nothing in the DOM or the network log that separates those cases from
93
+ working code.
94
+ </Accordion>
95
+
96
+ <Accordion title="If DevTools MCP is cheaper, why not just use it?">
97
+ Because it is cheaper partly by looking at less. Cost per look is half a metric; the other half is
98
+ whether the look catches the bug. Verification Efficiency combines them and gates on catching
99
+ every bug with zero false alarms, and on that measure Reticle leads 12.27 to 10.55. The 7%
100
+ per-look advantage also inverts on a real app: on the production dashboard measured here DevTools'
101
+ snapshot was 1,105 tokens against Reticle's 678.
102
+ </Accordion>
103
+
104
+ <Accordion title="Can I run both MCP servers at once?">
105
+ Yes. They are independent servers with distinct tool names. A reasonable split is DevTools MCP for
106
+ performance traces, deep Chrome internals and real screenshots, and Reticle for the verify loop
107
+ and the source pointer.
108
+ </Accordion>
109
+
110
+ <Accordion title="Do I have to instrument my app to use Reticle?">
111
+ You have to add the dev-only SDK, which is what `npx @reticlehq/server init` does. Beyond that,
112
+ look, act and observe work with zero component changes. Signals and registered stores are
113
+ optional, and they are what buys the assertions DevTools MCP structurally cannot make. If
114
+ instrumenting is off the table entirely, DevTools MCP is the correct choice.
115
+ </Accordion>
116
+
117
+ <Accordion title="Does Reticle give me performance traces?">
118
+ Not in the way Chrome's own tooling does, and it does not pretend to. It can tell you a React
119
+ render storm is happening (commit rate climbing with no DOM mutation), which DevTools MCP does not
120
+ surface. For flame charts and deep tracing, use Chrome's.
121
+ </Accordion>
122
+
123
+ </AccordionGroup>
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Reticle vs Playwright MCP: outside in, inside out
2
+ title: 'Reticle vs Playwright MCP: outside in, inside out'
3
3
  sidebarTitle: vs Playwright MCP
4
4
  description: Playwright MCP drives any website from the outside. Reticle verifies an app you own from the inside. They solve different problems, and they compose.
5
5
  icon: masks-theater
@@ -49,7 +49,7 @@ This is not a courtesy section. There are real cases where Playwright is the cor
49
49
  - **Assert on the app's own success signal.** When your app emits `auth:granted`, Reticle can require that exact signal. Playwright can check that a DOM element appeared, which a mock also does.
50
50
  - **Read application state.** `stateDiffs` shows `auth` moving from `null` to a real user. A POST that returns `200` without touching state is caught here and nowhere else.
51
51
  - **Map a DOM node to source.** `src/components/Login.tsx:81`, via the React fiber tree. Playwright can tell you the selector; it cannot tell you the file.
52
- - **Grade its own evidence.** Every verdict reports `honesty.grade`: `presence`, `state` or `signal`. Plus whether the capture was clean and complete.
52
+ - **Grade its own evidence.** Every verdict reports `honesty.grade`: `presence`, `state`, `net` or `signal`, weakest to strongest. Plus whether the capture was clean and complete.
53
53
  - **Say "unknown".** Reticle distinguishes "did not happen" from "I could not tell". That third state is the difference between a check and a coin flip.
54
54
 
55
55
  ## The false-green case
@@ -83,3 +83,43 @@ Reticle also has a driven mode (`reticle drive`) that provides real CDP input an
83
83
  that looks fine.
84
84
  </Card>
85
85
  </CardGroup>
86
+
87
+ ## FAQ
88
+
89
+ <AccordionGroup>
90
+
91
+ <Accordion title="Can I use Reticle and Playwright MCP at the same time?">
92
+ Yes, and it is a common setup. They are separate MCP servers with separate tool namespaces, so
93
+ nothing collides. Drive with Playwright where it is stronger (a third-party OAuth hop, a
94
+ cross-browser pass, a genuine pixel diff) and assert with Reticle where the question is "did my
95
+ app actually do the thing".
96
+ </Accordion>
97
+
98
+ <Accordion title="Does Reticle work on a site I do not own?">
99
+ No. Reticle reads the program from inside the page, which requires a dev-only SDK in the app. No
100
+ SDK, no Reticle. That is the single hardest boundary between these two tools, and it is why
101
+ Playwright MCP is the right answer for anything you cannot instrument.
102
+ </Accordion>
103
+
104
+ <Accordion title="Is Reticle actually cheaper, or only on the pages you chose?">
105
+ On a full-tree-vs-full-tree comparison the gap is modest, roughly 1.8x. The large gap comes from
106
+ not needing the full tree: Playwright MCP's primary perception primitive returns the accessibility
107
+ tree, Reticle's answers a specific question. On a small page both are cheap. On the real
108
+ production dashboard measured here Reticle was about 2.1x leaner. Force Reticle to dump the whole
109
+ page every step and you save very little.
110
+ </Accordion>
111
+
112
+ <Accordion title="Does Reticle support Firefox or WebKit?">
113
+ Not meaningfully. The always-on in-page SDK is browser-agnostic, but the driven paths that provide
114
+ real pointer input and screenshots go through CDP and are Chromium-oriented. If cross-browser is a
115
+ requirement, that is a Playwright job.
116
+ </Accordion>
117
+
118
+ <Accordion title="Do I have to give up my existing Playwright suite?">
119
+ No, and you should not. A scripted Playwright or Cypress suite is what gates your releases.
120
+ Reticle is the in-loop check while an agent is coding and the safety net for the flows nobody ever
121
+ got round to automating. Your existing `data-testid` attributes work in Reticle unchanged, so the
122
+ two share the same anchors.
123
+ </Accordion>
124
+
125
+ </AccordionGroup>
@@ -1,5 +1,5 @@
1
1
  ---
2
- title: Screenshots lie: why pixels cannot verify AI-generated code
2
+ title: 'Screenshots lie: why pixels cannot verify AI-generated code'
3
3
  sidebarTitle: vs screenshots
4
4
  description: The failures that AI coding agents actually produce are invisible in a rendered frame. Here is why a better vision model does not fix that.
5
5
  icon: image
@@ -86,6 +86,47 @@ We have not proven that Reticle makes agents _fix_ more bugs. A controlled test
86
86
 
87
87
  What is measured is detection: 10 of 10 injected regressions caught with zero false alarms, against 9 and 8 for the alternatives.
88
88
 
89
+ ## FAQ
90
+
91
+ <AccordionGroup>
92
+
93
+ <Accordion title="Would a better vision model fix this?">
94
+ No, and this is the single most expensive misconception on the page. Model quality improves the
95
+ _reading_ of evidence; it cannot create evidence that was never captured. A screenshot of a page
96
+ whose bug is non-visual contains no signal about that bug at any resolution, so a stronger model
97
+ produces a more confident version of the same wrong answer.
98
+ </Accordion>
99
+
100
+ <Accordion title="So screenshots are useless?">
101
+ No. They are the correct instrument for a specific class of bug: a font that failed to load, a GPU
102
+ or compositing glitch, layout that breaks only at one viewport, and anything where the rendered
103
+ frame _is_ the thing under test. Reticle ships `reticle_screenshot` and `reticle_visual_diff` for
104
+ exactly those. If pixel regression is your primary concern, a dedicated visual tool will serve you
105
+ better.
106
+ </Accordion>
107
+
108
+ <Accordion title="What does Reticle read instead of pixels?">
109
+ The program: the network log (did the request fire, how many times, what did it return), the
110
+ console, client-side routing, registered store state, and any signal the app itself emits. A
111
+ response carrying `net.total: 1` rules out a double submit; a non-empty `stateDiffs` rules out
112
+ mock data; `consoleErrors: 0` rules out "it worked but threw".
113
+ </Accordion>
114
+
115
+ <Accordion title="Does that mean Reticle catches every bug?">
116
+ No. It has not been shown to make agents _fix_ more bugs; a controlled fix-rate test did not show
117
+ an improvement and we consider that question open. What is measured is detection: 10 of 10
118
+ injected regressions caught with zero false alarms, against 9 and 8 for the alternatives. And a
119
+ purely visual regression still needs the opt-in visual diff.
120
+ </Accordion>
121
+
122
+ <Accordion title="Can I keep using screenshots alongside Reticle?">
123
+ Yes, and for a visual product you probably should. The argument here is not that screenshots are
124
+ bad, it is that a screenshot cannot answer "did the code work", which is the question an AI coding
125
+ agent needs answered on every change.
126
+ </Accordion>
127
+
128
+ </AccordionGroup>
129
+
89
130
  <CardGroup cols={2}>
90
131
  <Card title="See a real verdict" icon="rocket" href="/quickstart">
91
132
  Five minutes, and the output on that page was captured live.
@@ -12,7 +12,7 @@ It exists because an agent that writes code cannot otherwise tell whether that c
12
12
  ## How it works, in three parts
13
13
 
14
14
  1. **An SDK inside the app.** Dev-only, stripped from production builds, added by a build plugin. It instruments the DOM, network, routing, console, animations, and your state library.
15
- 2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine. Reticle never starts or manages your dev server; it attaches to the app you are already running.
15
+ 2. **A local bridge.** The SDK connects over a WebSocket to a daemon on your machine. The daemon never starts or manages your dev server. It attaches to the app that is running. If none is, the agent is the one told to start it, using your project's own dev script, in the background, and saying so.
16
16
  3. **An MCP server.** The daemon exposes the app to your agent as tools. `reticle_snapshot` to look, `reticle_act_and_wait` to act and prove in one call, `reticle_state` and `reticle_network` to observe, `reticle_assert` to check.
17
17
 
18
18
  Everything stays on your machine. No application code, DOM, network traffic, or console output leaves it.
@@ -1,10 +1,12 @@
1
1
  ---
2
- title: Why Reticle: seeing is not believing
2
+ title: 'Why Reticle: seeing is not believing'
3
3
  sidebarTitle: Why Reticle
4
4
  description: AI-written code fails by looking correct. Reticle reads the running program instead of the pixels, so a silent failure stops being invisible.
5
5
  icon: lightbulb
6
6
  ---
7
7
 
8
+ **Use Reticle because AI-written code fails by looking correct.** A screenshot cannot show you whether a request fired, what status it returned, whether application state changed, or whether one click sent one request or two. Reticle reads the running program instead of the pixels, so those silent failures stop being invisible.
9
+
8
10
  Software has always had bugs. What changed is the _kind_ of bug that reaches you.
9
11
 
10
12
  A human writing code by hand produces errors that announce themselves: a stack trace, a red screen, a test that goes red. An agent writing code produces something more dangerous: a change that looks completely correct, renders beautifully, and does nothing. The page is fine. The data never saved.