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