@reticlehq/server 2.7.0 → 2.9.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.
- package/README.md +14 -5
- package/SKILL.md +130 -778
- package/dist/bridge/bridge.d.ts +23 -0
- package/dist/bridge/bridge.js +80 -2
- package/dist/bridge/bridge.test-harness.d.ts +5 -0
- package/dist/bridge/bridge.test-harness.js +6 -4
- package/dist/capsule/causal-summary.d.ts +36 -1
- package/dist/capsule/causal-summary.js +33 -7
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +33 -5
- package/dist/cli/chromium-hint.js +82 -12
- package/dist/cli/cli-doctor.js +65 -34
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +32 -0
- package/dist/cli/cli-port.js +82 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +47 -3
- package/dist/cli/config-discovery.d.ts +35 -0
- package/dist/cli/config-discovery.js +163 -0
- package/dist/cli/doctor-daemon-line.js +4 -1
- package/dist/cli/doctor-project-line.d.ts +37 -0
- package/dist/cli/doctor-project-line.js +35 -0
- package/dist/cli/doctor-rows.d.ts +51 -0
- package/dist/cli/doctor-rows.js +54 -0
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +69 -0
- package/dist/cli/drive-attach.d.ts +99 -0
- package/dist/cli/drive-attach.js +203 -0
- package/dist/cli/drive-command.d.ts +13 -0
- package/dist/cli/drive-command.js +103 -0
- package/dist/cli/open-note.d.ts +20 -0
- package/dist/cli/open-note.js +37 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +119 -39
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/events/contradictions.d.ts +46 -0
- package/dist/events/contradictions.js +35 -22
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +8 -2
- package/dist/events/echo-mismatch.js +33 -5
- package/dist/events/event-filters.d.ts +1 -1
- package/dist/events/event-filters.js +7 -4
- package/dist/events/predicate-eval.d.ts +17 -72
- package/dist/events/predicate-eval.js +117 -236
- package/dist/events/predicate-parse.js +31 -3
- package/dist/events/predicate-route.d.ts +32 -0
- package/dist/events/predicate-route.js +102 -0
- package/dist/events/predicate-schema.d.ts +99 -0
- package/dist/events/predicate-schema.js +309 -0
- package/dist/events/predicate.d.ts +8 -0
- package/dist/events/predicate.js +296 -22
- package/dist/flows/annotate-tools.js +3 -0
- package/dist/flows/annotate.js +18 -4
- package/dist/flows/coverage.d.ts +38 -5
- package/dist/flows/coverage.js +30 -4
- package/dist/flows/decision.d.ts +15 -0
- package/dist/flows/decision.js +1 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +21 -5
- package/dist/flows/heal-run.js +1 -1
- package/dist/flows/replay.js +3 -0
- package/dist/honesty/accepted-write.d.ts +3 -0
- package/dist/honesty/accepted-write.js +8 -3
- package/dist/honesty/already-true.js +15 -0
- package/dist/honesty/blind-spots.js +10 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +1 -0
- package/dist/honesty/uncaptured-bodies.d.ts +4 -2
- package/dist/honesty/uncaptured-bodies.js +9 -2
- package/dist/honesty/unread-outcome.d.ts +8 -0
- package/dist/honesty/unread-outcome.js +36 -9
- package/dist/honesty/unsettled.d.ts +49 -0
- package/dist/honesty/unsettled.js +82 -0
- package/dist/honesty/verified.d.ts +36 -3
- package/dist/honesty/verified.js +54 -11
- package/dist/http-server.d.ts +19 -0
- package/dist/http-server.js +202 -4
- package/dist/impact/impact-recorder.d.ts +23 -0
- package/dist/impact/impact-recorder.js +76 -0
- package/dist/impact/impact-store.d.ts +47 -0
- package/dist/impact/impact-store.js +171 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +47 -10
- package/dist/init/agent-rules.d.ts +36 -0
- package/dist/init/agent-rules.js +132 -22
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +38 -6
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +114 -0
- package/dist/init/confirm.js +165 -0
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/csp-check.d.ts +38 -0
- package/dist/init/csp-check.js +92 -0
- package/dist/init/csp-doctor.d.ts +25 -0
- package/dist/init/csp-doctor.js +49 -0
- package/dist/init/cursor.js +6 -9
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +33 -3
- package/dist/init/mcp-clients.d.ts +1 -0
- package/dist/init/mcp-clients.js +51 -7
- package/dist/init/mcp.d.ts +15 -0
- package/dist/init/mcp.js +19 -0
- package/dist/init/non-js-project.js +29 -5
- package/dist/init/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +78 -16
- package/dist/init/plan.d.ts +47 -6
- package/dist/init/plan.js +228 -69
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +157 -45
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +54 -6
- package/dist/init/snippets.d.ts +45 -5
- package/dist/init/snippets.js +194 -42
- package/dist/init/vite-config.js +53 -5
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +37 -5
- package/dist/input/scroll-find.d.ts +6 -0
- package/dist/input/scroll-find.js +17 -2
- package/dist/input/scroll-tools.js +4 -0
- package/dist/journal/attach-journal.d.ts +9 -1
- package/dist/journal/attach-journal.js +13 -2
- package/dist/journal/journal-query.d.ts +13 -0
- package/dist/journal/journal-query.js +28 -3
- package/dist/journal/workspace-gitignore.d.ts +10 -0
- package/dist/journal/workspace-gitignore.js +59 -0
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +35 -30
- package/dist/mcp/mcp-proxy.js +306 -104
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +70 -34
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +11 -2
- package/dist/mcp/proxy-lifecycle.js +12 -3
- package/dist/mcp/proxy-log.d.ts +63 -0
- package/dist/mcp/proxy-log.js +132 -0
- package/dist/mcp/server-instructions.d.ts +17 -0
- package/dist/mcp/server-instructions.js +56 -0
- package/dist/mcp/shared-params.d.ts +27 -0
- package/dist/mcp/shared-params.js +36 -0
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/session/connection-memory.d.ts +59 -0
- package/dist/session/connection-memory.js +108 -0
- package/dist/session/dev-command.d.ts +26 -0
- package/dist/session/dev-command.js +89 -0
- package/dist/session/dev-server-probe.d.ts +68 -0
- package/dist/session/dev-server-probe.js +102 -7
- package/dist/session/last-act.d.ts +26 -0
- package/dist/session/last-act.js +35 -1
- package/dist/session/live-control-tools.js +42 -5
- package/dist/session/no-session-diagnosis.d.ts +67 -2
- package/dist/session/no-session-diagnosis.js +236 -27
- package/dist/session/no-session-next-action.d.ts +46 -0
- package/dist/session/no-session-next-action.js +85 -0
- package/dist/session/no-session-watch.d.ts +40 -1
- package/dist/session/no-session-watch.js +174 -8
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -1
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +4 -0
- package/dist/session/session-manager.js +35 -5
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.js +146 -5
- package/dist/telemetry/app-instrumented.d.ts +8 -0
- package/dist/telemetry/app-instrumented.js +10 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +15 -17
- package/dist/telemetry/bug-found.js +52 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +56 -0
- package/dist/telemetry/install-source.js +74 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +15 -0
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +26 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +46 -7
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +209 -28
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +49 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +73 -9
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +159 -26
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +40 -1
- package/dist/tools/lease-tools.js +181 -83
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +10 -4
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +52 -0
- package/dist/tools/react-grace.js +90 -0
- package/dist/tools/read-tools.js +67 -5
- package/dist/tools/resolve-target.d.ts +33 -0
- package/dist/tools/resolve-target.js +53 -0
- package/dist/tools/settle-in-flight.d.ts +94 -0
- package/dist/tools/settle-in-flight.js +146 -0
- package/dist/tools/snapshot-delta.d.ts +10 -0
- package/dist/tools/snapshot-delta.js +194 -9
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +86 -1
- package/dist/tools/tools.js +61 -8
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +55 -11
- package/docs/actions.mdx +120 -0
- package/docs/agent-cheatsheet.md +95 -29
- package/docs/architecture.md +52 -35
- package/docs/benchmarks.md +127 -75
- package/docs/best-practices.mdx +216 -0
- package/docs/capabilities.mdx +534 -0
- package/docs/cli/affected.mdx +53 -0
- package/docs/cli/capsules.mdx +70 -0
- package/docs/cli/cloud.mdx +153 -0
- package/docs/cli/doctor.mdx +109 -0
- package/docs/cli/drive.mdx +74 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +94 -0
- package/docs/cli/hunt.mdx +73 -0
- package/docs/cli/identify.mdx +76 -0
- package/docs/cli/init.mdx +121 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +74 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +69 -0
- package/docs/cli/status.mdx +88 -0
- package/docs/cli/stop.mdx +64 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +76 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +62 -0
- package/docs/cli.mdx +241 -0
- package/docs/debugging.md +16 -10
- package/docs/deploy-checks.md +45 -12
- package/docs/desktop.mdx +384 -0
- package/docs/docs.json +364 -0
- package/docs/enterprise.md +22 -16
- package/docs/faq.mdx +102 -0
- package/docs/fixtures.md +13 -7
- package/docs/flows.md +29 -23
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +238 -0
- package/docs/gate-plan.md +48 -42
- package/docs/gates.md +28 -22
- package/docs/getting-started.md +129 -77
- package/docs/human-control.md +24 -18
- package/docs/index.mdx +143 -0
- package/docs/install-agentic.mdx +224 -0
- package/docs/install-manual.mdx +196 -0
- package/docs/instrumentation.mdx +238 -0
- package/docs/integration-patterns.md +41 -35
- package/docs/local-registry.md +19 -13
- package/docs/multi-agent-testing.md +19 -13
- package/docs/packages/babel-plugin.mdx +79 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +116 -0
- package/docs/packages/electron.mdx +85 -0
- package/docs/packages/eslint-plugin.mdx +86 -0
- package/docs/packages/next.mdx +89 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +119 -0
- package/docs/packages/vite-plugin.mdx +94 -0
- package/docs/packages.mdx +218 -0
- package/docs/platform-integration.md +35 -26
- package/docs/predicates.mdx +248 -0
- package/docs/quickstart.mdx +257 -0
- package/docs/recipes.mdx +282 -0
- package/docs/skill-file.mdx +107 -0
- package/docs/state-management.mdx +199 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +50 -33
- package/docs/telemetry-contract.md +106 -52
- package/docs/telemetry-events.mdx +475 -0
- package/docs/telemetry.md +63 -55
- package/docs/testing.md +44 -6
- package/docs/token-efficiency.md +37 -27
- package/docs/tools-act-and-wait.mdx +173 -0
- package/docs/tools-act-sequence.mdx +137 -0
- package/docs/tools-act.mdx +95 -0
- package/docs/tools-assert.mdx +129 -0
- package/docs/tools-console.mdx +63 -0
- package/docs/tools-inspect.mdx +88 -0
- package/docs/tools-navigate.mdx +99 -0
- package/docs/tools-network.mdx +119 -0
- package/docs/tools-observe.mdx +145 -0
- package/docs/tools-overview.mdx +160 -0
- package/docs/tools-query.mdx +126 -0
- package/docs/tools-session-and-feedback.mdx +112 -0
- package/docs/tools-sessions.mdx +94 -0
- package/docs/tools-snapshot.mdx +129 -0
- package/docs/tools-state.mdx +104 -0
- package/docs/tools-tools-and-run.mdx +156 -0
- package/docs/tools-wait-for.mdx +85 -0
- package/docs/troubleshooting.mdx +263 -0
- package/docs/usage.md +209 -165
- package/docs/vs-chrome-devtools-mcp.mdx +123 -0
- package/docs/vs-playwright-mcp.mdx +125 -0
- package/docs/vs-screenshots.mdx +137 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +109 -0
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -256
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
package/docs/recipes.mdx
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'Recipes: copy, paste, verify'
|
|
3
|
+
sidebarTitle: Recipes
|
|
4
|
+
description: Worked examples for the situations you are actually in, with the real response each one returned.
|
|
5
|
+
icon: book-open
|
|
6
|
+
---
|
|
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.
|
|
9
|
+
|
|
10
|
+
## "Did the login actually work?"
|
|
11
|
+
|
|
12
|
+
One call, three conditions. The signal fired, exactly one request went out, and nothing was logged.
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"ref": "e605",
|
|
17
|
+
"action": "click",
|
|
18
|
+
"until": {
|
|
19
|
+
"kind": "allOf",
|
|
20
|
+
"predicates": [
|
|
21
|
+
{ "kind": "signal", "name": "auth:granted" },
|
|
22
|
+
{ "kind": "net", "method": "POST", "urlContains": "/api/login", "count": 1 },
|
|
23
|
+
{ "kind": "console", "level": "error", "absent": true }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"verified": "yes",
|
|
32
|
+
"because": "assertion held at signal grade over a clean capture with no channel disagreeing",
|
|
33
|
+
"verdict": {
|
|
34
|
+
"pass": true,
|
|
35
|
+
"evidence": [
|
|
36
|
+
{ "name": "auth:granted", "data": { "email": "admin@reticle.dev" } },
|
|
37
|
+
{ "matched": 1 },
|
|
38
|
+
{ "absent": true }
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"summary": {
|
|
42
|
+
"stateDiffs": [{ "path": "auth", "from": null, "to": "{…}" }],
|
|
43
|
+
"storageKeysChanged": ["reticle.bench.authToken", "reticle.bench.sessionId"]
|
|
44
|
+
},
|
|
45
|
+
"honesty": { "grade": "signal", "coverage": { "pct": 100, "partial": false } }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each child predicate reports its own evidence, in order. `count: 1` is the part that catches double-submit, and it costs nothing to ask for.
|
|
50
|
+
|
|
51
|
+
## "Did navigating actually change the view?"
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{ "ref": "e618", "action": "click", "until": { "kind": "signal", "name": "nav:changed" } }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"verified": "yes",
|
|
60
|
+
"verdict": {
|
|
61
|
+
"pass": true,
|
|
62
|
+
"evidence": { "name": "nav:changed", "data": { "view": "deployments" } }
|
|
63
|
+
},
|
|
64
|
+
"summary": {
|
|
65
|
+
"statePathsChanged": ["app"],
|
|
66
|
+
"stateDiffs": [{ "path": "view", "from": "overview", "to": "deployments", "atMs": 49084 }],
|
|
67
|
+
"signals": ["nav:changed"]
|
|
68
|
+
},
|
|
69
|
+
"honesty": { "grade": "signal", "coverage": { "pct": 100, "partial": false } }
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The signal carries the destination in its payload, so one predicate proves both that navigation happened and that it went to the right place.
|
|
74
|
+
|
|
75
|
+
## "Did the modal open?" and the lesson in getting it wrong
|
|
76
|
+
|
|
77
|
+
Here is a real failure, kept because it teaches more than another pass.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"ref": "e623",
|
|
82
|
+
"action": "click",
|
|
83
|
+
"args": { "confirmDangerous": true },
|
|
84
|
+
"until": {
|
|
85
|
+
"kind": "allOf",
|
|
86
|
+
"predicates": [
|
|
87
|
+
{ "kind": "element", "query": { "role": "dialog" }, "state": "visible" },
|
|
88
|
+
{ "kind": "console", "level": "error", "absent": true }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"verified": "no",
|
|
97
|
+
"verdict": {
|
|
98
|
+
"pass": false,
|
|
99
|
+
"evidence": [
|
|
100
|
+
{
|
|
101
|
+
"pass": false,
|
|
102
|
+
"expected": "an element matching {\"role\":\"dialog\"} in state 'visible'",
|
|
103
|
+
"observed": "no matching element on the page",
|
|
104
|
+
"assertion": "element.present",
|
|
105
|
+
"evidence": {
|
|
106
|
+
"presentTestids": ["deploy-table", "filter-search", "env-filter", "sign-out", "…"]
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
{ "pass": true, "evidence": { "absent": true } }
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"summary": {
|
|
113
|
+
"stateDiffs": [{ "path": "newDeployOpen", "from": false, "to": true, "atMs": 60862 }],
|
|
114
|
+
"signals": ["modal:opened"]
|
|
115
|
+
},
|
|
116
|
+
"capsule": { "blastRadius": ["state app", "signal modal:opened"], "firstDivergence": null }
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Read the `summary`. The modal **did** open. `newDeployOpen` went `false` to `true` and the app fired `modal:opened`. `capsule.blastRadius` says the same thing as a list: the click moved the `app` store and fired `modal:opened`, and nothing else.
|
|
121
|
+
|
|
122
|
+
The assertion was wrong, not the app. That modal is a `div` with no `role="dialog"`, so a role query could never match it. (Which is also an accessibility bug worth fixing, found for free.)
|
|
123
|
+
|
|
124
|
+
The right predicate was sitting in the response the whole time:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{ "until": { "kind": "signal", "name": "modal:opened" } }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
<Tip>
|
|
131
|
+
When an assertion fails, read `summary.signals` and `summary.stateDiffs` before you assume the
|
|
132
|
+
feature is broken. They tell you what actually happened, and usually hand you the predicate you
|
|
133
|
+
should have written.
|
|
134
|
+
</Tip>
|
|
135
|
+
|
|
136
|
+
## "Nothing matched, and I do not know why"
|
|
137
|
+
|
|
138
|
+
An empty `reticle_query` result tells you what _is_ there:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"count": 0,
|
|
143
|
+
"elements": [],
|
|
144
|
+
"hint": {
|
|
145
|
+
"route": "/hostile",
|
|
146
|
+
"knownEmptyState": true,
|
|
147
|
+
"presentTestids": ["nav-overview", "nav-deployments", "hostile-view", "hostile-ticker", "…"],
|
|
148
|
+
"presentRegions": [{ "role": "list", "childCount": 0, "name": "hostile-feed", "sample": [] }]
|
|
149
|
+
},
|
|
150
|
+
"cost": { "bytes": 402, "tokens": 101 }
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
The testid existed, on another route. `hint.route` says where you actually are and `presentTestids` says what is here instead, for 101 tokens and no snapshot. `presentRegions` is the other half: a list with `childCount: 0` is an empty state, which is a different bug from a list that failed to render at all.
|
|
155
|
+
|
|
156
|
+
## "Is this button destructive?"
|
|
157
|
+
|
|
158
|
+
Reticle refuses controls that look destructive, before acting:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"error": "potentially destructive action blocked; retry with args.confirmDangerous=true",
|
|
163
|
+
"recovery": "Reticle blocked a potentially destructive control (delete, remove, revoke…) on purpose. If you mean it, retry the same action with args.confirmDangerous set to true. This is a deliberate refusal, not a defect: there is nothing to report."
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Confirm it explicitly when you mean it:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{ "ref": "e623", "action": "click", "args": { "confirmDangerous": true }, "until": { … } }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
<Note>
|
|
174
|
+
The matcher is conservative and will occasionally stop something harmless. A button labelled "New
|
|
175
|
+
deploy" trips it. That is the trade: a false stop costs you one argument, and a false start costs
|
|
176
|
+
you a deleted record.
|
|
177
|
+
</Note>
|
|
178
|
+
|
|
179
|
+
## "Did my change break anything else?"
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{ "tool": "reticle_verify_change", "args": { "since": "HEAD~1" } }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Give it the files you edited, or a git ref. It works out which saved flows cover them and replays only those.
|
|
186
|
+
|
|
187
|
+
## "What is even testable in this app?"
|
|
188
|
+
|
|
189
|
+
The best first call on an unfamiliar codebase:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{ "tool": "reticle_capabilities" }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"testids": ["login-email", "login-submit", "new-deploy", "deploy-submit", "…"],
|
|
198
|
+
"signals": ["auth:granted", "auth:denied", "deploy:created", "modal:opened", "…"],
|
|
199
|
+
"stores": ["app"],
|
|
200
|
+
"flows": [
|
|
201
|
+
{
|
|
202
|
+
"name": "ship-a-deploy",
|
|
203
|
+
"steps": ["nav-deployments", "new-deploy", "deploy-name", "deploy-submit"]
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
"source": "live"
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
That is the app stating what it considers testable, which beats snapshotting the DOM and inferring it from element names.
|
|
211
|
+
|
|
212
|
+
## "The error state has never been tested"
|
|
213
|
+
|
|
214
|
+
Most error states have never run. Force one:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"tool": "reticle_network_mock",
|
|
219
|
+
"args": { "mocks": [{ "urlContains": "/api/deploys", "status": 500 }] }
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Then drive the flow and assert the error UI appears. Pass `clear: true` to turn mocking off.
|
|
224
|
+
|
|
225
|
+
<Warning>
|
|
226
|
+
This one needs a **Reticle-driven browser**, because mocks are applied through CDP and the always-on
|
|
227
|
+
SDK cannot do that. A pooled lease is **not** enough. Re-verified on 2026-08-16: calling it against a
|
|
228
|
+
tab from `reticle_lease` returns
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"applied": false,
|
|
233
|
+
"count": 0,
|
|
234
|
+
"ok": false,
|
|
235
|
+
"reason": "no-cdp-provider",
|
|
236
|
+
"recommendation": "this needs a Reticle-driven browser (it is applied through CDP, which the always-on SDK cannot do), start with `reticle drive <url>` or set RETICLE_CDP_URL"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Under a driven browser the same call answers `{ "applied": true, "count": 1 }`. The same rule applies to `reticle_screenshot`, `reticle_visual_diff` and `reticle_viewport`, which report `no-visual-provider` instead.
|
|
241
|
+
|
|
242
|
+
</Warning>
|
|
243
|
+
|
|
244
|
+
## "I want this to run on every PR"
|
|
245
|
+
|
|
246
|
+
Record it once, replay it forever:
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{ "tool": "reticle_flow_verify", "args": { "names": ["docs-check"] } }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Omit `names` to replay every saved flow. Either way you get one consolidated verdict, and only the failures carry detail:
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"status": "fail",
|
|
257
|
+
"total": 1,
|
|
258
|
+
"passed": 0,
|
|
259
|
+
"failed": 1,
|
|
260
|
+
"summary": "0/1 flows pass, 1 need attention: docs-check",
|
|
261
|
+
"failures": [
|
|
262
|
+
{
|
|
263
|
+
"flow": "docs-check",
|
|
264
|
+
"verdict": "drift",
|
|
265
|
+
"whatChanged": "testid \"login-submit\" not found",
|
|
266
|
+
"whereInSource": "src/components/Login.tsx:81",
|
|
267
|
+
"nextAction": "rebind the anchor to \"sign-out\" (closest survivor), or update the flow if the change was intended."
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
It also returns `flaky`, the flows it has seen both pass and fail on unchanged code. Treat that list as a bug in the flow, not in the app.
|
|
274
|
+
|
|
275
|
+
<CardGroup cols={2}>
|
|
276
|
+
<Card title="The predicate grammar" icon="filter" href="/predicates">
|
|
277
|
+
Every kind, every field, and which ones actually prove something.
|
|
278
|
+
</Card>
|
|
279
|
+
<Card title="Best practices" icon="star" href="/best-practices">
|
|
280
|
+
The habits behind these recipes, and the mistakes that produce a confident wrong pass.
|
|
281
|
+
</Card>
|
|
282
|
+
</CardGroup>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: 'The SKILL file: paste and go'
|
|
3
|
+
sidebarTitle: SKILL file
|
|
4
|
+
description: One URL you paste into any coding agent to teach it Reticle. How it works, what it contains, and when you need it at all.
|
|
5
|
+
icon: graduation-cap
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
Paste `https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md` into any coding agent and say "read this and set up Reticle". The document installs Reticle, wires it into your app, and then drives a proper verification loop, choosing setup mode or test mode itself based on whether `.reticle.json` already exists. You often do not need it: `npx @reticlehq/server init` configures Claude Code and Cursor directly.
|
|
9
|
+
|
|
10
|
+
`SKILL.md` is a single document you hand to a coding agent. It teaches the agent to install Reticle, wire it into your app, and then drive a verification loop properly. Without you explaining any of it.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
https://raw.githubusercontent.com/reticlehq/reticle/main/SKILL.md
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That URL is live and returns the current version. Paste it into your agent and say "read this and set up Reticle".
|
|
17
|
+
|
|
18
|
+
<Note>
|
|
19
|
+
You often do not need it. `npx @reticlehq/server init` writes a `/reticle` slash command for
|
|
20
|
+
Claude Code automatically, and installs agent rule files for Claude and Cursor. The SKILL file is
|
|
21
|
+
for agents that `init` cannot configure, for a from-scratch install, or when you want the agent to
|
|
22
|
+
have the full protocol in context rather than a summary.
|
|
23
|
+
</Note>
|
|
24
|
+
|
|
25
|
+
## It has two modes, and it picks one itself
|
|
26
|
+
|
|
27
|
+
The first thing the document tells an agent to do is check which situation it is in:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cat .reticle.json 2>/dev/null || echo "NOT_FOUND"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **Not found** → run **Setup mode**: register the MCP server, install the SDK, wire the entry point, validate the connection, and prove it on one flow while the user watches.
|
|
34
|
+
- **Found** → run **Test mode**: connect, orient, decide what to test, drive it, report.
|
|
35
|
+
|
|
36
|
+
One document, two jobs, no flag for you to remember. The second time you invoke it, it skips straight to the useful part.
|
|
37
|
+
|
|
38
|
+
## What Setup mode actually does
|
|
39
|
+
|
|
40
|
+
| Phase | What the agent does |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| Save the skill | Store it as `/reticle` so the loop is one word away from then on |
|
|
43
|
+
| Run `init` | Without asking you questions it can answer itself |
|
|
44
|
+
| Manual fallbacks | Per-agent MCP config, per-framework SDK wiring, for anything `init` marked `⚠` |
|
|
45
|
+
| Validate | Confirm a session actually connected. Not that a checklist went green |
|
|
46
|
+
| Prove it | Drive one real flow while you watch, so the install ends in evidence |
|
|
47
|
+
|
|
48
|
+
That last row is the one that matters. Plenty of installs "succeed" and then nothing works. The skill does not consider itself finished until a real flow has produced a real verdict.
|
|
49
|
+
|
|
50
|
+
## What Test mode actually does
|
|
51
|
+
|
|
52
|
+
Five phases: connect, orient, decide what to test, run the tests, report. The reporting format is specified, which matters more than it sounds. An agent left to summarise freely will summarise optimistically.
|
|
53
|
+
|
|
54
|
+
The rules it operates under are the same ones these docs keep repeating:
|
|
55
|
+
|
|
56
|
+
- Only `reticle_act_and_wait` and `reticle_assert` produce a verdict.
|
|
57
|
+
- `verified: "unknown"` is not a pass.
|
|
58
|
+
- Never weaken a check to make it pass.
|
|
59
|
+
|
|
60
|
+
## Feedback is the first section, on purpose
|
|
61
|
+
|
|
62
|
+
Before setup, before anything, the document tells the agent that reporting problems is part of the job. And that this applies **during** setup, which is where Reticle is most likely to break and least likely to hear about it.
|
|
63
|
+
|
|
64
|
+
It even covers the case where nothing is installed yet:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx @reticlehq/server feedback --agent --kind gap "init finished but no session ever connected"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That works with no daemon running, which is the entire point. The install that failed halfway is the one worth hearing about, and in that state the MCP tools do not exist.
|
|
71
|
+
|
|
72
|
+
<Tip>
|
|
73
|
+
It also draws a line that saves everyone time: report defects in **Reticle**. A bug found in the
|
|
74
|
+
app under test is Reticle working correctly, and belongs in the answer to the user.
|
|
75
|
+
</Tip>
|
|
76
|
+
|
|
77
|
+
## It ships inside the package too
|
|
78
|
+
|
|
79
|
+
`SKILL.md` is bundled into `@reticlehq/server` at publish time, alongside a copy of this entire documentation site. So an agent that has already installed Reticle can read it from disk without a network round trip:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
node_modules/@reticlehq/server/SKILL.md
|
|
83
|
+
node_modules/@reticlehq/server/docs/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Handy in a sandbox with no outbound network, which is an increasingly common place for an agent to live.
|
|
87
|
+
|
|
88
|
+
## Should you read it?
|
|
89
|
+
|
|
90
|
+
Probably not, unless you are curious. It is roughly 60 KB and written for an agent, which means it is exhaustive in the way a runbook is exhaustive. Every fallback, every framework, every failure mode spelled out rather than summarised.
|
|
91
|
+
|
|
92
|
+
If you want the human version of the same material, you are already in it:
|
|
93
|
+
|
|
94
|
+
<CardGroup cols={2}>
|
|
95
|
+
<Card title="Agentic install" icon="wand-magic-sparkles" href="/install-agentic">
|
|
96
|
+
What `reticle init` does, with real output.
|
|
97
|
+
</Card>
|
|
98
|
+
<Card title="Manual install" icon="screwdriver-wrench" href="/install-manual">
|
|
99
|
+
Every step `init` cannot automate.
|
|
100
|
+
</Card>
|
|
101
|
+
<Card title="Agent cheat sheet" icon="bolt" href="/agent-cheatsheet">
|
|
102
|
+
The one-screen version of the drive loop.
|
|
103
|
+
</Card>
|
|
104
|
+
<Card title="Best practices" icon="star" href="/best-practices">
|
|
105
|
+
The habits that make verdicts worth trusting.
|
|
106
|
+
</Card>
|
|
107
|
+
</CardGroup>
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: State management
|
|
3
|
+
description: Register any store so Reticle can read what your app believes. Zustand and Redux work natively, and eight more have adapters.
|
|
4
|
+
icon: database
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
**Reticle reads any store that has `getState()` and `subscribe()`, which covers Zustand and Redux with no adapter at all. TanStack Query, Pinia, Svelte stores, Jotai, Valtio, MobX, XState and Recoil each have a one-line adapter, and `pushStore` covers anything else.** Register one with `registerStore('app', myStore)` in your dev-only `src/reticle-dev.ts`.
|
|
8
|
+
|
|
9
|
+
Reading application state is what separates a verdict from a screenshot. A `200` proves the server was reachable. A rendered row proves React ran. Only state proves your app accepted the change.
|
|
10
|
+
|
|
11
|
+
Reticle needs one thing from your store: a way to read it, and a way to know when it changed.
|
|
12
|
+
|
|
13
|
+
## The contract
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface StoreLike {
|
|
17
|
+
getState(): unknown;
|
|
18
|
+
subscribe(listener: () => void): () => void;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Anything with that shape registers directly, with no adapter.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { registerStore } from '@reticlehq/browser';
|
|
26
|
+
|
|
27
|
+
registerStore('app', myStore);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
<Warning>
|
|
31
|
+
Pass the **store**, not `() => store.getState()`. The store form wires `subscribe`, so every mutation
|
|
32
|
+
emits a state diff. The getter form still works for compatibility but leaves the store on pull-only
|
|
33
|
+
reads: no change events, and an empty `stateDiffs` in every verdict.
|
|
34
|
+
|
|
35
|
+
That reads as "nothing changed" and actually means "I was never watching". It is the most common instrumentation mistake there is.
|
|
36
|
+
|
|
37
|
+
</Warning>
|
|
38
|
+
|
|
39
|
+
## Works natively
|
|
40
|
+
|
|
41
|
+
<CardGroup cols={2}>
|
|
42
|
+
<Card title="Zustand" icon="bolt">
|
|
43
|
+
A zustand hook already has `getState` and `subscribe`. Pass it straight in.
|
|
44
|
+
</Card>
|
|
45
|
+
<Card title="Redux" icon="boxes-stacked">
|
|
46
|
+
A Redux store is `StoreLike` by definition. Same one-liner.
|
|
47
|
+
</Card>
|
|
48
|
+
</CardGroup>
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
registerStore('app', useApp); // zustand
|
|
52
|
+
registerStore('app', store); // redux
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This is what the Reticle fixture app does, and it is why its verdicts carry `stateDiffs: [{ "path": "auth", "from": null, "to": "…" }]`.
|
|
56
|
+
|
|
57
|
+
## Adapters
|
|
58
|
+
|
|
59
|
+
Eight libraries do not fit `StoreLike` for a reason specific to each. Every adapter returns a `StoreLike` you hand straight to `registerStore`.
|
|
60
|
+
|
|
61
|
+
Each example below is the one documented in the adapter's own source.
|
|
62
|
+
|
|
63
|
+
### TanStack Query
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { tanstackQueryStore } from '@reticlehq/browser';
|
|
67
|
+
|
|
68
|
+
registerStore('queries', tanstackQueryStore(queryClient));
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
<Tip>
|
|
72
|
+
Register your server cache even if you already register a client store. "The screen is plausible
|
|
73
|
+
and the network is silent" is the normal failure with query caches: a mutation forgets to
|
|
74
|
+
invalidate, the UI keeps rendering a number that was true a minute ago, and nothing fires for
|
|
75
|
+
anyone to notice. The cache's freshness metadata is the only witness.
|
|
76
|
+
</Tip>
|
|
77
|
+
|
|
78
|
+
### Pinia
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { piniaStore } from '@reticlehq/browser';
|
|
82
|
+
|
|
83
|
+
registerStore('cart', piniaStore(useCartStore()));
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Svelte stores
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { svelteStore } from '@reticlehq/browser';
|
|
90
|
+
|
|
91
|
+
registerStore('cart', svelteStore(cartStore));
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Jotai
|
|
95
|
+
|
|
96
|
+
Atoms are not a store, so you name the ones you care about.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { jotaiStore } from '@reticlehq/browser';
|
|
100
|
+
import { getDefaultStore } from 'jotai';
|
|
101
|
+
|
|
102
|
+
registerStore('app', jotaiStore(getDefaultStore(), { cart: cartAtom, user: userAtom }));
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Valtio
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { valtioStore } from '@reticlehq/browser';
|
|
109
|
+
import { snapshot, subscribe } from 'valtio/vanilla';
|
|
110
|
+
|
|
111
|
+
registerStore('app', valtioStore(state, snapshot, subscribe));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### MobX
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { mobxStore } from '@reticlehq/browser';
|
|
118
|
+
import { reaction, toJS } from 'mobx';
|
|
119
|
+
|
|
120
|
+
registerStore('app', mobxStore(store, toJS, reaction));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
You pass `toJS` and `reaction` in rather than Reticle importing MobX. The SDK stays free of your state library, and your bundle stays free of a copy it did not ask for.
|
|
124
|
+
|
|
125
|
+
### XState
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { xstateStore } from '@reticlehq/browser';
|
|
129
|
+
|
|
130
|
+
registerStore('machine', xstateStore(actor));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
An XState actor's `subscribe` returns a subscription object rather than an unsubscribe function, which is the single reason it does not satisfy `StoreLike` already.
|
|
134
|
+
|
|
135
|
+
### Recoil
|
|
136
|
+
|
|
137
|
+
The most involved, because Recoil has no store object to hand over.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { recoilStore } from '@reticlehq/browser';
|
|
141
|
+
|
|
142
|
+
const latest = useRef(snapshot_UNSTABLE());
|
|
143
|
+
const listeners = useRef(new Set<() => void>()).current;
|
|
144
|
+
|
|
145
|
+
useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
|
|
146
|
+
latest.current = snapshot;
|
|
147
|
+
for (const l of listeners) l();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
registerStore(
|
|
151
|
+
'recoil',
|
|
152
|
+
recoilStore(
|
|
153
|
+
{ cart: cartAtom },
|
|
154
|
+
() => latest.current,
|
|
155
|
+
(l) => {
|
|
156
|
+
listeners.add(l);
|
|
157
|
+
return () => listeners.delete(l);
|
|
158
|
+
},
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Anything else
|
|
164
|
+
|
|
165
|
+
`pushStore` gives you a store plus the `push` that updates it, so you own the write side. Useful for state that lives somewhere Reticle cannot reach: a web worker, a websocket, a third-party SDK.
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { pushStore } from '@reticlehq/browser';
|
|
169
|
+
|
|
170
|
+
const { store, push } = pushStore({ status: 'idle' });
|
|
171
|
+
registerStore('worker', store);
|
|
172
|
+
|
|
173
|
+
worker.onmessage = (e) => push(e.data);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Reading it back
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{ "store": "app", "path": "cart.items.0.qty" }
|
|
180
|
+
```
|
|
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.
|
|
183
|
+
|
|
184
|
+
You can also assert on it directly, which is stronger than reading it afterwards:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{ "kind": "state", "store": "app", "path": "deployments.0.status", "equals": "live" }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
That catches a UI-versus-store desync, a deploy that only looks shipped, deterministically and with no model involved.
|
|
191
|
+
|
|
192
|
+
<CardGroup cols={2}>
|
|
193
|
+
<Card title="reticle_state" icon="database" href="/tools-state">
|
|
194
|
+
Arguments, depth control, and reading a component's hook state.
|
|
195
|
+
</Card>
|
|
196
|
+
<Card title="Instrument your app" icon="wrench" href="/instrumentation">
|
|
197
|
+
Stores are one of three things worth registering. The other two are signals and testids.
|
|
198
|
+
</Card>
|
|
199
|
+
</CardGroup>
|
package/docs/style.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* Any .css file at the docs root is auto-loaded by Mintlify; docs.json has no field for it.
|
|
2
|
+
Tagline under the footer logo: the footer schema is {socials, links} and takes no text. */
|
|
3
|
+
#footer a.select-none::after {
|
|
4
|
+
content: 'Your agent says it works. Reticle checks.';
|
|
5
|
+
display: block;
|
|
6
|
+
margin-top: 0.75rem;
|
|
7
|
+
font-size: 0.875rem;
|
|
8
|
+
line-height: 1.25rem;
|
|
9
|
+
color: rgb(var(--gray-500));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.dark #footer a.select-none::after {
|
|
13
|
+
color: rgb(var(--gray-400));
|
|
14
|
+
}
|