@reticlehq/init 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/detect/capabilities.d.ts +4 -1
- package/dist/detect/capabilities.js +45 -9
- package/dist/detect/detect.d.ts +58 -4
- package/dist/detect/detect.js +117 -7
- package/dist/detect/dev-script.d.ts +26 -2
- package/dist/detect/dev-script.js +80 -7
- package/dist/detect/non-js-project.d.ts +1 -1
- package/dist/detect/non-js-project.js +6 -1
- package/dist/detect/workspace-apps.d.ts +21 -0
- package/dist/detect/workspace-apps.js +111 -12
- package/dist/detect/workspace-redirect.d.ts +2 -1
- package/dist/detect/workspace-redirect.js +28 -2
- package/dist/diagnose/containerised-dev-server.d.ts +12 -4
- package/dist/diagnose/containerised-dev-server.js +36 -4
- package/dist/diagnose/csp-check.d.ts +12 -6
- package/dist/diagnose/csp-check.js +61 -13
- package/dist/diagnose/csp-doctor.d.ts +33 -2
- package/dist/diagnose/csp-doctor.js +103 -6
- package/dist/diagnose/csp-step.d.ts +27 -0
- package/dist/diagnose/csp-step.js +86 -0
- package/dist/diagnose/desktop-doctor.d.ts +1 -1
- package/dist/diagnose/desktop-doctor.js +8 -2
- package/dist/diagnose/install-hint.d.ts +9 -1
- package/dist/diagnose/install-hint.js +17 -7
- package/dist/diagnose/install-retries.d.ts +18 -2
- package/dist/diagnose/install-retries.js +50 -3
- package/dist/diagnose/terminal-wrap.d.ts +18 -0
- package/dist/diagnose/terminal-wrap.js +63 -0
- package/dist/host.d.ts +6 -0
- package/dist/host.js +3 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +8 -3
- package/dist/no-package-json.d.ts +10 -0
- package/dist/no-package-json.js +121 -0
- package/dist/node-io.d.ts +9 -5
- package/dist/node-io.js +25 -7
- package/dist/patch/angular.d.ts +68 -0
- package/dist/patch/angular.js +216 -0
- package/dist/patch/astro-patch.d.ts +48 -13
- package/dist/patch/astro-patch.js +151 -43
- package/dist/patch/cra.d.ts +0 -2
- package/dist/patch/cra.js +0 -2
- package/dist/patch/electron-patch.d.ts +10 -1
- package/dist/patch/electron-patch.js +79 -27
- package/dist/patch/electron-vite-patch.js +1 -1
- package/dist/patch/nuxt-patch.d.ts +1 -1
- package/dist/patch/nuxt-patch.js +2 -2
- package/dist/patch/nuxt-snippets.d.ts +44 -0
- package/dist/patch/nuxt-snippets.js +135 -0
- package/dist/patch/remix.d.ts +24 -0
- package/dist/patch/remix.js +59 -0
- package/dist/patch/snippets.d.ts +58 -57
- package/dist/patch/snippets.js +181 -202
- package/dist/patch/static-page.d.ts +39 -0
- package/dist/patch/static-page.js +102 -0
- package/dist/patch/tanstack-start.d.ts +36 -8
- package/dist/patch/tanstack-start.js +118 -29
- package/dist/patch/vite-config.d.ts +5 -0
- package/dist/patch/vite-config.js +8 -0
- package/dist/patch/vite-env-types.d.ts +9 -0
- package/dist/patch/vite-env-types.js +19 -0
- package/dist/patch/vite-owning-config.d.ts +47 -0
- package/dist/patch/vite-owning-config.js +104 -19
- package/dist/plan/connect-steps.d.ts +10 -3
- package/dist/plan/connect-steps.js +22 -3
- package/dist/plan/framework-adapter.d.ts +5 -6
- package/dist/plan/framework-adapter.js +105 -11
- package/dist/plan/notices.js +4 -3
- package/dist/plan/plan-angular.d.ts +6 -0
- package/dist/plan/plan-angular.js +89 -0
- package/dist/plan/plan-electron-vite.d.ts +6 -3
- package/dist/plan/plan-electron-vite.js +39 -8
- package/dist/plan/plan-framework.d.ts +9 -47
- package/dist/plan/plan-framework.js +114 -323
- package/dist/plan/plan-types.d.ts +47 -2
- package/dist/plan/plan-vite.d.ts +67 -0
- package/dist/plan/plan-vite.js +256 -0
- package/dist/plan/plan.js +155 -69
- package/dist/plan/port-steps.d.ts +45 -0
- package/dist/plan/port-steps.js +119 -0
- package/dist/plan/preflight.d.ts +17 -3
- package/dist/plan/preflight.js +53 -23
- package/dist/plan/stop-hook-step.d.ts +4 -0
- package/dist/plan/stop-hook-step.js +49 -0
- package/dist/project/agent-rules.js +2 -0
- package/dist/register/mcp-clients.d.ts +13 -0
- package/dist/register/mcp-clients.js +22 -0
- package/dist/register/mcp.d.ts +26 -5
- package/dist/register/mcp.js +64 -8
- package/dist/run-types.d.ts +10 -0
- package/dist/run.js +132 -78
- package/package.json +3 -4
- package/NOTICE +0 -5
package/dist/patch/snippets.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
* Generated file contents and copy-paste snippets for `reticle init`. Kept as named constants/builders
|
|
3
3
|
* so the runner never inlines free strings.
|
|
4
4
|
*/
|
|
5
|
-
import { RETICLE_DEFAULT_PORT,
|
|
6
|
-
import { UiLibrary } from '../detect/detect.js';
|
|
5
|
+
import { RETICLE_DEFAULT_PORT, bridgeWsUrl, RETICLE_CLIENT_HOST, RETICLE_WS_PATH, } from '@reticlehq/core';
|
|
6
|
+
import { Framework, UiLibrary } from '../detect/detect.js';
|
|
7
7
|
import { RETICLE_VERSION } from '../version.js';
|
|
8
|
+
import { VITE_ENV_DEV_DECLARATION } from './vite-env-types.js';
|
|
9
|
+
import { posix } from 'node:path';
|
|
8
10
|
/**
|
|
9
11
|
* The SDK as one import a plain page can actually resolve.
|
|
10
12
|
*
|
|
@@ -24,7 +26,13 @@ import { RETICLE_VERSION } from '../version.js';
|
|
|
24
26
|
* PINNED to this server's version on purpose. A floating import upgrades the page SDK underneath a
|
|
25
27
|
* daemon that did not move, which is `version_skew` arriving by a route nothing checks.
|
|
26
28
|
*/
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Where a generated file sends someone to learn `registerStore`. A URL, not a path into
|
|
31
|
+
* `node_modules/@reticlehq/server`: the server is never installed into the app, so the path the
|
|
32
|
+
* generated files used to name did not exist in any project they were written into.
|
|
33
|
+
*/
|
|
34
|
+
export const STATE_DOCS_URL = 'https://docs.reticle.sh/state-management';
|
|
35
|
+
export const CDN_SDK_URL = `https://cdn.jsdelivr.net/npm/@reticlehq/browser@${RETICLE_VERSION}/+esm`;
|
|
28
36
|
/**
|
|
29
37
|
* The connect argument literal: a non-default port adds a `url`, and a projectId is always passed
|
|
30
38
|
* (so the app is identifiable across port changes). Empty string only when neither applies.
|
|
@@ -57,6 +65,35 @@ export function connectArgWithToken(port, projectId, pairingToken) {
|
|
|
57
65
|
const inner = base.length > 0 ? base.slice(1, -1).trim() : '';
|
|
58
66
|
return `{ ${[inner, `token: '${pairingToken}'`].filter((p) => p.length > 0).join(', ')} }`;
|
|
59
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Does this codebase want the React kit, or the framework-neutral sensor? The ONE answer, read by
|
|
70
|
+
* both `frameworkPackages` (what is installed) and `sdkImport` (what generated code imports).
|
|
71
|
+
*
|
|
72
|
+
* The kit is what adds component identity — component names and stacks — and it is worth having
|
|
73
|
+
* wherever React or Preact is rendering (the adapter reaches Preact through `preact/compat`).
|
|
74
|
+
* Everywhere else it is a package named `@reticlehq/react`, carrying `react` in its peer
|
|
75
|
+
* dependencies, being installed into a codebase that has no React in it.
|
|
76
|
+
*
|
|
77
|
+
* UNKNOWN keeps the kit deliberately. Absence of evidence is not evidence of Vue, and guessing
|
|
78
|
+
* "sensor" on no information silently drops component identity from apps that should have it.
|
|
79
|
+
*
|
|
80
|
+
* Except where the absence IS the evidence: on these stacks nothing renders React but a `react` the
|
|
81
|
+
* app depends on itself, so no renderer found is a vanilla, Lit, Solid or Angular app.
|
|
82
|
+
*/
|
|
83
|
+
const RENDERER_IS_DECLARED = new Set([
|
|
84
|
+
Framework.VITE,
|
|
85
|
+
// A package.json with no UI library and no bundler Reticle recognises. It was handed
|
|
86
|
+
// `@reticlehq/react` and `react` — a React install into a page with no React in it.
|
|
87
|
+
Framework.HTML,
|
|
88
|
+
// Forge's Vite template is vanilla TypeScript; a React renderer declares `react` like any app.
|
|
89
|
+
Framework.ELECTRON_FORGE,
|
|
90
|
+
Framework.ANGULAR,
|
|
91
|
+
]);
|
|
92
|
+
export function usesReactKit(uiLibrary, framework) {
|
|
93
|
+
if (UiLibrary.UNKNOWN === uiLibrary)
|
|
94
|
+
return !RENDERER_IS_DECLARED.has(framework);
|
|
95
|
+
return uiLibrary !== UiLibrary.VUE && uiLibrary !== UiLibrary.SVELTE;
|
|
96
|
+
}
|
|
60
97
|
/**
|
|
61
98
|
* Which SDK package the GENERATED code should import, and whether `install()` applies.
|
|
62
99
|
*
|
|
@@ -68,9 +105,8 @@ export function connectArgWithToken(port, projectId, pairingToken) {
|
|
|
68
105
|
* `install()` is the React adapter's, not the sensor's: `@reticlehq/browser` exports `reticle` and no
|
|
69
106
|
* `install`, so swapping the specifier alone would trade a missing module for a missing export.
|
|
70
107
|
*/
|
|
71
|
-
export function sdkImport(uiLibrary) {
|
|
72
|
-
|
|
73
|
-
return react
|
|
108
|
+
export function sdkImport(uiLibrary, framework) {
|
|
109
|
+
return usesReactKit(uiLibrary, framework)
|
|
74
110
|
? { specifier: '@reticlehq/react', usesInstall: true }
|
|
75
111
|
: { specifier: '@reticlehq/browser', usesInstall: false };
|
|
76
112
|
}
|
|
@@ -95,6 +131,15 @@ function frameworkPluginExample(uiLibrary) {
|
|
|
95
131
|
return '/* your existing plugins */';
|
|
96
132
|
}
|
|
97
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The `defineConfig` import each printed config needs. The snippets call `defineConfig`, and one
|
|
136
|
+
* pasted as printed into an empty file used to die with `ReferenceError: defineConfig is not
|
|
137
|
+
* defined` because the import was left out.
|
|
138
|
+
*/
|
|
139
|
+
const DEFINE_CONFIG_IMPORT = {
|
|
140
|
+
VITE: "import { defineConfig } from 'vite';",
|
|
141
|
+
ELECTRON_VITE: "import { defineConfig } from 'electron-vite';",
|
|
142
|
+
};
|
|
98
143
|
/** The Vite-config snippet printed when we can't safely auto-patch the config. */
|
|
99
144
|
export function viteManual(port, uiLibrary = UiLibrary.UNKNOWN, inject = true, sourceMapping = true) {
|
|
100
145
|
const options = [
|
|
@@ -110,6 +155,7 @@ lowercase JSX tag is not an element. Stamping one crashes the app at commit time
|
|
|
110
155
|
pointers, not the app.`;
|
|
111
156
|
return `Add the Reticle plugin to your Vite config:
|
|
112
157
|
|
|
158
|
+
${DEFINE_CONFIG_IMPORT.VITE}
|
|
113
159
|
import { reticle } from '@reticlehq/vite-plugin';
|
|
114
160
|
|
|
115
161
|
export default defineConfig({
|
|
@@ -124,8 +170,11 @@ from \`vite build\`.${note}`;
|
|
|
124
170
|
/**
|
|
125
171
|
* The electron-vite recipe: the plugin belongs in `renderer`, never in `main` or `preload`.
|
|
126
172
|
*
|
|
127
|
-
* `desktop: true` is load-bearing
|
|
128
|
-
*
|
|
173
|
+
* `desktop: true` is load-bearing: it injects connect() into the entry module and passes
|
|
174
|
+
* `allowInProduction`, because a packaged renderer has no dev server and reports
|
|
175
|
+
* `NODE_ENV=production`, so without it the SDK's production backstop refuses to start. It does not
|
|
176
|
+
* instrument a release — a production-mode `electron-vite build` is stubbed like a web bundle — so
|
|
177
|
+
* a packaged renderer that should be driven is built with `--mode development`.
|
|
129
178
|
*/
|
|
130
179
|
export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
|
|
131
180
|
const extras = port === undefined
|
|
@@ -133,6 +182,7 @@ export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
|
|
|
133
182
|
: `desktop: true, port: ${String(port)}, captureNetworkBodies: true`;
|
|
134
183
|
return `Add the Reticle plugin to the \`renderer\` block of electron.vite.config, not \`main\`:
|
|
135
184
|
|
|
185
|
+
${DEFINE_CONFIG_IMPORT.ELECTRON_VITE}
|
|
136
186
|
import { reticle } from '@reticlehq/vite-plugin';
|
|
137
187
|
|
|
138
188
|
export default defineConfig({
|
|
@@ -143,8 +193,9 @@ export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
|
|
|
143
193
|
},
|
|
144
194
|
});
|
|
145
195
|
|
|
146
|
-
\`desktop: true\` is required: a packaged renderer
|
|
147
|
-
|
|
196
|
+
\`desktop: true\` is required: a packaged renderer has no dev server and reports production, and
|
|
197
|
+
without it the SDK refuses to start there. A production-mode build still ships no Reticle code; to drive a
|
|
198
|
+
packaged renderer with no dev server, build it with \`--mode development\`.
|
|
148
199
|
|
|
149
200
|
Also add \`import '@reticlehq/electron/preload'\` as the first line of your preload, and
|
|
150
201
|
\`installReticleCapture(win)\` in main after you construct the BrowserWindow.`;
|
|
@@ -182,7 +233,7 @@ export function nextReticleDevFile(port, projectId, testids = [], stores = [], f
|
|
|
182
233
|
const storeBlock = found.length > 0
|
|
183
234
|
? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
|
|
184
235
|
: 0 === stores.length
|
|
185
|
-
?
|
|
236
|
+
? ` // No state library detected. If you add one, register it here — see ${STATE_DOCS_URL}`
|
|
186
237
|
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
187
238
|
const registerNames = found.length > 0 ? ', registerStore' : '';
|
|
188
239
|
// Multiline connect + single blank after imports (#684): the one-line connect and the double blank
|
|
@@ -204,10 +255,13 @@ export function ReticleDev() {
|
|
|
204
255
|
// withReticle() finds the daemon serving this project on every dev-server start. It wins over
|
|
205
256
|
// any port written into this file at install time, so moving the daemon needs no edit here.
|
|
206
257
|
const url = process.env.NEXT_PUBLIC_RETICLE_URL;
|
|
258
|
+
// So a version-skewed pair names the SDK's version instead of "an unknown older wire".
|
|
259
|
+
const sdkVersion = process.env.NEXT_PUBLIC_RETICLE_SDK_VERSION;
|
|
207
260
|
reticle.connect({
|
|
208
261
|
${fields}...(url ? { url } : {}),
|
|
209
262
|
...(token ? { token } : {}),
|
|
210
263
|
...(root ? { root } : {}),
|
|
264
|
+
...(sdkVersion ? { sdkVersion } : {}),
|
|
211
265
|
});
|
|
212
266
|
|
|
213
267
|
// ── Start with ONE flow. ────────────────────────────────────────────────────────────────
|
|
@@ -263,10 +317,9 @@ layoutPath) {
|
|
|
263
317
|
? `\n url: '${bridgeWsUrl(port)}',`
|
|
264
318
|
: '';
|
|
265
319
|
const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
|
|
266
|
-
// Astro owns its Vite instance, so
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
// moment the daemon moves; this does not.
|
|
320
|
+
// Astro owns its Vite instance, so the layout can resolve the daemon URL in frontmatter on every
|
|
321
|
+
// `astro dev` and put it on a <meta> the client script reads. The port above is written once at
|
|
322
|
+
// install time and goes stale the moment the daemon moves; this does not.
|
|
270
323
|
//
|
|
271
324
|
// Needs the projectId to know WHICH daemon is ours. Without one there is nothing to match on, and
|
|
272
325
|
// adopting a daemon serving another project would report its state as this app's, so the whole
|
|
@@ -276,10 +329,8 @@ layoutPath) {
|
|
|
276
329
|
// `bridgeWsUrl` builds, and a generator that spells it out by hand is exactly how the four call
|
|
277
330
|
// sites that constant exists to unify drifted apart in the first place.
|
|
278
331
|
const RETICLE_CLIENT_HOST_LITERAL = `ws://${RETICLE_CLIENT_HOST}:`;
|
|
279
|
-
const
|
|
332
|
+
const urlFrontmatter = canDiscover
|
|
280
333
|
? `
|
|
281
|
-
// The live daemon serving THIS project, re-read on every \`astro dev\`. Same rule as the Vite and
|
|
282
|
-
// Next plugins: skip dead daemons, match on projectId, lowest port wins, '' when none matches.
|
|
283
334
|
function reticleUrl() {
|
|
284
335
|
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
285
336
|
let best;
|
|
@@ -295,63 +346,77 @@ layoutPath) {
|
|
|
295
346
|
} catch { return ''; }
|
|
296
347
|
return best === undefined ? '' : '${RETICLE_CLIENT_HOST_LITERAL}' + best + '${RETICLE_WS_PATH}';
|
|
297
348
|
}
|
|
349
|
+
const pairingUrl = import.meta.env.DEV ? reticleUrl() : '';
|
|
298
350
|
`
|
|
299
351
|
: '';
|
|
300
|
-
const
|
|
352
|
+
const urlMeta = canDiscover ? `\n <meta name="reticle-pairing-url" content={pairingUrl} />` : '';
|
|
301
353
|
const urlRead = canDiscover
|
|
302
|
-
? `\n
|
|
354
|
+
? `\n const url = document.querySelector('meta[name="reticle-pairing-url"]')?.getAttribute('content') ?? '';`
|
|
303
355
|
: '';
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
return `Astro renders its own HTML, so the connect goes in a page <script> and the pairing token is inlined by the config.
|
|
356
|
+
const urlSpread = canDiscover ? `\n ...(url.length > 0 ? { url } : {}),` : '';
|
|
357
|
+
return `Astro renders its own HTML, so the connect goes in a local module the page statically imports. Do not put the pairing token through \`vite.define\`: on Astro 7.2+ that substitution never reaches the client, the identifier stays literal, and the bridge refuses the dial. Read the token in frontmatter and put it on a <meta> the module can query. Reading it per request also means a token written after the dev server started is picked up on the next load instead of needing a restart.
|
|
307
358
|
|
|
308
|
-
1. In astro.config.mjs
|
|
359
|
+
1. In astro.config.mjs, raise the build target. The token does NOT belong here:
|
|
309
360
|
|
|
310
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
311
|
-
import { homedir } from 'node:os';
|
|
312
|
-
import { join } from 'node:path';
|
|
313
|
-
|
|
314
|
-
function reticleToken() {
|
|
315
|
-
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
316
|
-
try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
|
|
317
|
-
}
|
|
318
|
-
${urlHelper}
|
|
319
361
|
export default defineConfig({
|
|
320
362
|
vite: {
|
|
321
363
|
// Astro's default target down-levels the modern SDK bundle and fails on a destructuring transform.
|
|
322
364
|
build: { target: 'es2022' },
|
|
323
|
-
optimizeDeps: {
|
|
324
|
-
define: {
|
|
325
|
-
__RETICLE_TOKEN__: JSON.stringify(reticleToken()),${urlDefine}
|
|
326
|
-
// Without this, source pointers come back as absolute paths from YOUR machine — useless in a
|
|
327
|
-
// report. Every other framework gets it from its build plugin; Astro owns its Vite instance.
|
|
328
|
-
__RETICLE_ROOT__: JSON.stringify(process.cwd()),
|
|
329
|
-
},
|
|
365
|
+
optimizeDeps: { include: ['@reticlehq/react'] },
|
|
330
366
|
},
|
|
331
367
|
});
|
|
332
368
|
|
|
333
369
|
${layoutHost(layoutPath)}
|
|
334
370
|
|
|
371
|
+
---
|
|
372
|
+
import { readFileSync${canDiscover ? ', readdirSync' : ''} } from 'node:fs';
|
|
373
|
+
import { homedir } from 'node:os';
|
|
374
|
+
import { join } from 'node:path';
|
|
375
|
+
const pairingToken = (() => {
|
|
376
|
+
if (!import.meta.env.DEV) return '';
|
|
377
|
+
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
378
|
+
try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
|
|
379
|
+
})();
|
|
380
|
+
const pairingRoot = import.meta.env.DEV ? process.cwd() : '';${urlFrontmatter} ---
|
|
381
|
+
<meta name="reticle-pairing-token" content={pairingToken} />
|
|
382
|
+
<meta name="reticle-pairing-root" content={pairingRoot} />${urlMeta}
|
|
335
383
|
<script>
|
|
384
|
+
import connectReticle from '../components/ReticleDev';
|
|
336
385
|
if (import.meta.env.DEV) {
|
|
337
|
-
|
|
338
|
-
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';${urlRead}
|
|
339
|
-
const { reticle, install } = await import('@reticlehq/react');
|
|
340
|
-
install();
|
|
341
|
-
reticle.connect({${id}${extra}${urlSpread}
|
|
342
|
-
...(token.length > 0 ? { token } : {}),
|
|
343
|
-
...(root.length > 0 ? { root } : {}),
|
|
344
|
-
});
|
|
386
|
+
connectReticle();
|
|
345
387
|
}
|
|
346
388
|
</script>
|
|
347
389
|
|
|
348
|
-
|
|
349
|
-
|
|
390
|
+
The <script> tag must not sit inside a conditional. Astro hoists them statically, so
|
|
391
|
+
\`{isDev && (<script />)}\` is never hoisted and the module 404s.
|
|
392
|
+
The dev gate belongs in the imported module.
|
|
393
|
+
|
|
394
|
+
3. Create src/components/ReticleDev.ts — Vite owns this file, so the SDK is a static import:
|
|
395
|
+
|
|
396
|
+
import { reticle, install } from '@reticlehq/react';
|
|
397
|
+
export default function connectReticle() {
|
|
398
|
+
const token = document.querySelector('meta[name="reticle-pairing-token"]')?.getAttribute('content') ?? '';
|
|
399
|
+
const root = document.querySelector('meta[name="reticle-pairing-root"]')?.getAttribute('content') ?? '';${urlRead}
|
|
400
|
+
if (token.length === 0) {
|
|
401
|
+
console.warn('[reticle] no pairing token was available when this page rendered, so the app will connect and be refused — you will see NO SESSION even though the SDK loads and the socket opens. The token is written by the Reticle daemon: start it and reload this page.');
|
|
402
|
+
}
|
|
403
|
+
install();
|
|
404
|
+
reticle.connect({${id}${extra}${urlSpread}
|
|
405
|
+
...(token.length > 0 ? { token } : {}),
|
|
406
|
+
...(root.length > 0 ? { root } : {}),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
4. In src/env.d.ts — declare the window names so \`astro check\` can see them (create-astro's
|
|
411
|
+
default build runs check first):
|
|
350
412
|
|
|
351
|
-
|
|
352
|
-
|
|
413
|
+
interface Window {
|
|
414
|
+
__RETICLE_TOKEN__?: string;
|
|
415
|
+
__RETICLE_ROOT__?: string;
|
|
416
|
+
}
|
|
353
417
|
|
|
354
|
-
Start the daemon
|
|
418
|
+
Start the daemon, then load the page. The token is read when the page renders, so a reload is enough
|
|
419
|
+
once the token file exists — you do not have to restart \`astro dev\`.`;
|
|
355
420
|
}
|
|
356
421
|
/**
|
|
357
422
|
* The `registerCapabilities` call every generated connect carries.
|
|
@@ -383,7 +448,7 @@ ${indent}});`;
|
|
|
383
448
|
* breaks the module everything else hangs off. So the store lines are generated COMMENTED, naming
|
|
384
449
|
* the libraries actually found in package.json, with the exact call to uncomment.
|
|
385
450
|
*/
|
|
386
|
-
export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLibrary.REACT) {
|
|
451
|
+
export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLibrary.REACT, framework) {
|
|
387
452
|
// The specifier has to be the package `frameworkPackages` installed. This file is the Vite path —
|
|
388
453
|
// the commonest install there is — and it was hardcoded to `@reticlehq/react` while a Vue or
|
|
389
454
|
// Svelte app was being given `@reticlehq/browser`, so the generated file imported something that
|
|
@@ -391,7 +456,7 @@ export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLib
|
|
|
391
456
|
//
|
|
392
457
|
// The sensor exports `registerCapabilities` and `registerStore` (and the store adapters) just as
|
|
393
458
|
// the React kit does; the only thing it lacks is `install()`, which this file never called.
|
|
394
|
-
const sdk = sdkImport(uiLibrary);
|
|
459
|
+
const sdk = sdkImport(uiLibrary, framework);
|
|
395
460
|
const ids = testids.map((t) => `'${t}'`).join(', ');
|
|
396
461
|
// A store we FOUND is imported and registered outright — the whole point of the file. The hints
|
|
397
462
|
// stay only for the libraries we can name but not wire (they need an argument we cannot infer).
|
|
@@ -399,10 +464,11 @@ export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLib
|
|
|
399
464
|
const storeBlock = found.length > 0
|
|
400
465
|
? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
|
|
401
466
|
: 0 === stores.length
|
|
402
|
-
?
|
|
467
|
+
? ` // No state library detected. If you add one, register it here — see ${STATE_DOCS_URL}`
|
|
403
468
|
: stores.map((h) => ` // import your store, then: ${h}`).join('\n');
|
|
404
469
|
const registerImport = found.length > 0 ? 'registerCapabilities, registerStore' : 'registerCapabilities';
|
|
405
|
-
return `// Dev-only.
|
|
470
|
+
return `// Dev-only. Loaded for you after connect() — by @reticlehq/vite-plugin's injected connect, or by
|
|
471
|
+
// the connect file init wrote for a framework that connects itself — so you do not import it.
|
|
406
472
|
// Self-guards on import.meta.env.DEV, so it is a no-op in a production build.
|
|
407
473
|
import { ${registerImport} } from '${sdk.specifier}';
|
|
408
474
|
${storeImports.length > 0 ? `${storeImports}\n` : ''}
|
|
@@ -424,10 +490,19 @@ ${storeBlock}
|
|
|
424
490
|
stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
|
|
425
491
|
});
|
|
426
492
|
}
|
|
427
|
-
|
|
493
|
+
|
|
494
|
+
${VITE_ENV_DEV_DECLARATION}`;
|
|
428
495
|
}
|
|
429
496
|
/** Where that module goes. Matches @reticlehq/vite-plugin's convention list. */
|
|
430
497
|
export const VITE_DEV_MODULE_PATH = 'src/reticle-dev.ts';
|
|
498
|
+
/**
|
|
499
|
+
* The dev module as a relative import from `fromFile` — for the frameworks that connect themselves
|
|
500
|
+
* and so have to load it themselves (see svelteKitHooksFile, tanstackStartConnectFile).
|
|
501
|
+
*/
|
|
502
|
+
export function devModuleSpecifier(fromFile) {
|
|
503
|
+
const rel = posix.relative(posix.dirname(fromFile), VITE_DEV_MODULE_PATH.replace(/\.ts$/, ''));
|
|
504
|
+
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
505
|
+
}
|
|
431
506
|
/**
|
|
432
507
|
* electron-vite's renderer Vite root is `src/renderer`, so the plugin looks for
|
|
433
508
|
* `src/renderer/src/reticle-dev.ts` — not the package-root path a plain Vite app uses.
|
|
@@ -504,7 +579,7 @@ st.html(
|
|
|
504
579
|
* `<head>`, so the SDK sees a parsed document.
|
|
505
580
|
*/
|
|
506
581
|
export function djangoMiddlewareSnippet(connectArgLiteral) {
|
|
507
|
-
return `# reticle_dev.py
|
|
582
|
+
return `# reticle_dev.py: add to MIDDLEWARE while DEBUG is on:
|
|
508
583
|
# MIDDLEWARE = [..., "reticle_dev.ReticleDevMiddleware"]
|
|
509
584
|
from django.conf import settings
|
|
510
585
|
|
|
@@ -540,8 +615,21 @@ class ReticleDevMiddleware:
|
|
|
540
615
|
return response
|
|
541
616
|
`;
|
|
542
617
|
}
|
|
543
|
-
export function htmlManual(port, projectId, pairingToken
|
|
618
|
+
export function htmlManual(port, projectId, pairingToken,
|
|
619
|
+
// Which SDK the bundled form imports must be the one init INSTALLED. A page with no UI library
|
|
620
|
+
// now gets the sensor, and a snippet still naming `@reticlehq/react` would import a package that
|
|
621
|
+
// is not there.
|
|
622
|
+
uiLibrary = UiLibrary.UNKNOWN) {
|
|
544
623
|
const withToken = connectArgWithToken(port, projectId, pairingToken);
|
|
624
|
+
const sdk = sdkImport(uiLibrary, Framework.HTML);
|
|
625
|
+
const bundledImport = sdk.usesInstall
|
|
626
|
+
? `void import('${sdk.specifier}').then(({ reticle, install }) => {
|
|
627
|
+
install();
|
|
628
|
+
reticle.connect(${withToken});
|
|
629
|
+
});`
|
|
630
|
+
: `void import('${sdk.specifier}').then(({ reticle }) => {
|
|
631
|
+
reticle.connect(${withToken});
|
|
632
|
+
});`;
|
|
545
633
|
const tokenNote = pairingToken === undefined || 0 === pairingToken.length
|
|
546
634
|
? ''
|
|
547
635
|
: `\n\n The \`token\` is this machine's pairing token, read from ~/.reticle/pairing-token. Keep it: the
|
|
@@ -550,13 +638,10 @@ export function htmlManual(port, projectId, pairingToken) {
|
|
|
550
638
|
return `No Vite/Next plugin detected — wire the dev-only connect by hand. Pick the form for your setup:
|
|
551
639
|
|
|
552
640
|
• Bundled app (Create React App, webpack, Parcel, Vue/Svelte CLI, etc.) — add to your ENTRY module
|
|
553
|
-
(e.g. src/index.js or src/main.js), where '
|
|
641
|
+
(e.g. src/index.js or src/main.js), where '${sdk.specifier}' resolves through your bundler:
|
|
554
642
|
|
|
555
643
|
if (process.env.NODE_ENV !== 'production') {
|
|
556
|
-
|
|
557
|
-
install();
|
|
558
|
-
reticle.connect(${withToken});
|
|
559
|
-
});
|
|
644
|
+
${bundledImport}
|
|
560
645
|
}${tokenNote}
|
|
561
646
|
|
|
562
647
|
• Plain HTML with NO build step (FastAPI, Flask, Django, Rails, Streamlit, a hand-written page) —
|
|
@@ -581,7 +666,7 @@ export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
|
|
|
581
666
|
* component names and source mapping do not, and no CI gate covers this stack. Saying so beats
|
|
582
667
|
* reporting all-green.
|
|
583
668
|
*/
|
|
584
|
-
export function unverifiedUiLibraryNote(library) {
|
|
669
|
+
export function unverifiedUiLibraryNote(library, installGated = false) {
|
|
585
670
|
// Preact is not in the same position as Vue or Svelte and must not be told it is. The React
|
|
586
671
|
// adapter reaches Preact through `preact/compat`, which is what `docs/frameworks.mdx` has always
|
|
587
672
|
// said, so telling a Preact reader they get no component identity contradicts our own docs and
|
|
@@ -590,10 +675,10 @@ export function unverifiedUiLibraryNote(library) {
|
|
|
590
675
|
const identity = 'preact' === library
|
|
591
676
|
? 'React component identity — component names and stacks — comes from `@reticlehq/react`, which reaches Preact through `preact/compat`. That path is not covered by a CI gate here, so treat it as expected-to-work rather than proven.'
|
|
592
677
|
: 'What `@reticlehq/react` adds and you will NOT get is React component identity: component names and component stacks.';
|
|
593
|
-
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${
|
|
678
|
+
return `Detected a ${library} app. Reticle's DOM, network, console and state tools work here. ${'vue' === library ? 'Source `file:line` does NOT come through: the build plugin stamps JSX and, separately, Svelte components, and a Vue single-file component is neither — measured, a Svelte counter reports `src/lib/Counter.svelte:5` and the same drive on Vue reports no source at all.' : 'Source `file:line` does too — the build plugin stamps it for this library (measured on preact and svelte).'} ${identity} ${installGated ? `The install gate scaffolds this ${library} stack from scratch on every change, so this SETUP is proven; no gate drives it to a verdict, so the drive is not.` : `No CI gate covers ${library}.`} Driven on every change: Vite + React, Next.js, Astro. If something doesn't work, please open an issue.`;
|
|
594
679
|
}
|
|
595
|
-
export const
|
|
596
|
-
export const UNVERIFIED_TANSTACK_START_NOTE = 'Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks.
|
|
680
|
+
export const SVELTEKIT_SETUP_GATED_NOTE = 'The install gate scaffolds a SvelteKit app on every release and requires this hook to connect a session, so the wiring is proven; driving flows on SvelteKit to a verdict is not. If the hook does not register a session, please open an issue.';
|
|
681
|
+
export const UNVERIFIED_TANSTACK_START_NOTE = 'Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested — it may work, but nothing proves it and nothing will tell us if it breaks. Gated today: Vite (React, Vue), Next.js, Nuxt, React Router, Remix, Astro, SvelteKit, Create React App and Angular. If the client effect does not register a session, please open an issue.';
|
|
597
682
|
/**
|
|
598
683
|
* Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
|
|
599
684
|
* canvas is none of those. Without this line, init succeeds, sessions connect, and surrounding UI
|
|
@@ -632,6 +717,9 @@ if (import.meta.env.DEV) {
|
|
|
632
717
|
...(sdkVersion.length > 0 ? { sdkVersion } : {}),
|
|
633
718
|
});
|
|
634
719
|
${registerCapabilitiesCall(testids, ' ')}
|
|
720
|
+
// Your stores and capabilities live in ${VITE_DEV_MODULE_PATH}. Only the plugin's injected
|
|
721
|
+
// connect imports that file, and SvelteKit never runs it, so this hook loads it instead.
|
|
722
|
+
void import('${devModuleSpecifier(SVELTEKIT_HOOKS_PATH)}');
|
|
635
723
|
});
|
|
636
724
|
}
|
|
637
725
|
|
|
@@ -681,10 +769,26 @@ export function webpack4TranspileNote(reactScriptsMajor) {
|
|
|
681
769
|
}
|
|
682
770
|
/** React Router's client-entry override point, in framework mode. */
|
|
683
771
|
export const REACT_ROUTER_ENTRY_PATH = 'app/entry.client.tsx';
|
|
684
|
-
/**
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
772
|
+
/**
|
|
773
|
+
* The dev-only module @reticlehq/vite-plugin serves: connect() with the token already in it. Named
|
|
774
|
+
* without its leading slash, because that is the part both the current line and the old one carry.
|
|
775
|
+
*/
|
|
776
|
+
const RETICLE_CONNECT_MODULE = '@reticle-connect';
|
|
777
|
+
/**
|
|
778
|
+
* The one line that puts Reticle in a React Router client entry — and in a Remix one.
|
|
779
|
+
*
|
|
780
|
+
* The specifier is BUILT, not written as a literal. `import('/@reticle-connect')` is a module only
|
|
781
|
+
* the Vite dev server can answer, so `tsc` cannot resolve it: a fresh framework-mode scaffold went
|
|
782
|
+
* red on `npm run typecheck` (`TS2307: Cannot find module '/@reticle-connect'`) the moment `init`
|
|
783
|
+
* wrote it. A computed specifier is typed `Promise<any>` and never resolved, so no declaration, no
|
|
784
|
+
* suppression comment and no tsconfig edit is needed. The browser still asks the dev server for the
|
|
785
|
+
* same URL, which the plugin serves; `BASE_URL` keeps it right under a non-root `base`. `@vite-ignore`
|
|
786
|
+
* is Vite's own marker for a dynamic import it should not try to analyse, not a lint suppression. In
|
|
787
|
+
* a production build `import.meta.env.DEV` is false and the whole line is dropped.
|
|
788
|
+
*/
|
|
789
|
+
export const REACT_ROUTER_CONNECT_LINE = 'if (import.meta.env.DEV) void import(/* @vite-ignore */ `${import.meta.env.BASE_URL}@reticle-connect`);';
|
|
790
|
+
/** The line `init` used to write, which fails `tsc`; replaced in place on a re-run. */
|
|
791
|
+
const REACT_ROUTER_CONNECT_LINE_UNTYPED = "if (import.meta.env.DEV) void import('/@reticle-connect');";
|
|
688
792
|
/**
|
|
689
793
|
* The client entry `init` writes when React Router framework mode has none.
|
|
690
794
|
*
|
|
@@ -726,6 +830,9 @@ startTransition(() => {
|
|
|
726
830
|
* still be evaluated first, and this is a side-effect import.
|
|
727
831
|
*/
|
|
728
832
|
export function reactRouterEntryPatch(source) {
|
|
833
|
+
if (source.includes(REACT_ROUTER_CONNECT_LINE_UNTYPED)) {
|
|
834
|
+
return source.replace(REACT_ROUTER_CONNECT_LINE_UNTYPED, REACT_ROUTER_CONNECT_LINE);
|
|
835
|
+
}
|
|
729
836
|
if (source.includes(RETICLE_CONNECT_MODULE))
|
|
730
837
|
return null;
|
|
731
838
|
const lines = source.split('\n');
|
|
@@ -737,135 +844,7 @@ export function reactRouterEntryPatch(source) {
|
|
|
737
844
|
lines.splice(lastImport + 1, 0, REACT_ROUTER_CONNECT_LINE);
|
|
738
845
|
return lines.join('\n');
|
|
739
846
|
}
|
|
740
|
-
|
|
741
|
-
export const NUXT_PLUGIN_PATH = 'app/plugins/reticle.client.ts';
|
|
742
|
-
/**
|
|
743
|
-
* The plugin directory Nuxt actually scans, which is not the same on 3 and 4.
|
|
744
|
-
*
|
|
745
|
-
* Nuxt 4's default `srcDir` is `app/`, so plugins live in `app/plugins/`. Nuxt 3's is the project
|
|
746
|
-
* root, so they live in `plugins/`. Writing to the wrong one is the silent failure this whole change
|
|
747
|
-
* exists to remove: the file is on disk, `init` reports it green, Nuxt never scans that directory,
|
|
748
|
-
* and no plugin is ever registered. Keyed on whether the app HAS an `app/` directory, which is the
|
|
749
|
-
* same signal Nuxt itself uses to pick its srcDir.
|
|
750
|
-
*/
|
|
751
|
-
export function nuxtPluginPath(hasAppDir) {
|
|
752
|
-
return hasAppDir ? NUXT_PLUGIN_PATH : 'plugins/reticle.client.ts';
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* The dev-only Nuxt client plugin `init` writes.
|
|
756
|
-
*
|
|
757
|
-
* Every trap in it is one somebody actually hit. `import.meta.dev` rather than a hostname check: the
|
|
758
|
-
* reported failure was a snippet guarded on `window.location.hostname === 'localhost'`, which is
|
|
759
|
-
* false on any hosts-file alias or LAN address and throws during SSR, so the connect never ran with
|
|
760
|
-
* no error and no log line. `.client.ts` rather than an SSR guard, because that suffix is what keeps
|
|
761
|
-
* this out of the server bundle. `@reticlehq/browser` rather than the React kit, because installing
|
|
762
|
-
* a package named `@reticlehq/react` into a Vue codebase is the single thing most likely to make
|
|
763
|
-
* somebody abandon the setup.
|
|
764
|
-
*
|
|
765
|
-
* The token is inlined by `nuxt.config`'s `vite.define` (see nuxt-patch.ts). The bridge requires it
|
|
766
|
-
* even on localhost, and nothing in a browser can read the file it lives in.
|
|
767
|
-
*/
|
|
768
|
-
export function nuxtPluginFile(port, projectId, testids = []) {
|
|
769
|
-
const base = connectArg(port, projectId);
|
|
770
|
-
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
771
|
-
return `// Dev-only: connect Reticle from a Nuxt client plugin. Nuxt owns its own Vite instance and
|
|
772
|
-
// renders its own HTML, so the Vite plugin's index.html injection never fires — a .client plugin is
|
|
773
|
-
// the path that can register a session at all.
|
|
774
|
-
export default defineNuxtPlugin(() => {
|
|
775
|
-
// import.meta.dev is resolved at BUILD time, so it does not care what hostname you develop on.
|
|
776
|
-
// Do NOT guard on window.location.hostname === 'localhost': that is false on any hosts-file alias
|
|
777
|
-
// or LAN address, and window does not exist during SSR.
|
|
778
|
-
if (!import.meta.dev) return;
|
|
779
|
-
void import('@reticlehq/browser').then(({ reticle, registerCapabilities }) => {
|
|
780
|
-
// Both inlined by nuxt.config's vite.define, which \`reticle init\` wrote. The bridge refuses a
|
|
781
|
-
// connect with no token even on localhost; the root makes source paths repo-relative.
|
|
782
|
-
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
783
|
-
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
784
|
-
reticle.connect({
|
|
785
|
-
${fields}...(token.length > 0 ? { token } : {}),
|
|
786
|
-
...(root.length > 0 ? { root } : {}),
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
// What the agent can drive without guessing. Add a store here too — registerStore('cart',
|
|
790
|
-
// piniaStore(useCartStore())) — and the agent can check what the app BELIEVES, not just what it
|
|
791
|
-
// rendered. See node_modules/@reticlehq/server/docs/usage.md.
|
|
792
|
-
${registerCapabilitiesCall(testids, ' ')}
|
|
793
|
-
});
|
|
794
|
-
});
|
|
795
|
-
|
|
796
|
-
declare const __RETICLE_TOKEN__: string | undefined;
|
|
797
|
-
declare const __RETICLE_ROOT__: string | undefined;
|
|
798
|
-
`;
|
|
799
|
-
}
|
|
800
|
-
/**
|
|
801
|
-
* Said as its own NOTICE beside the write, not inside it: a running dev server does not pick up a
|
|
802
|
-
* new plugin, and off localhost the connect needs two more arguments.
|
|
803
|
-
*
|
|
804
|
-
* A ✓ line is one SKILL.md tells the reader to skip, so a caveat carried on the ✓ is a caveat nobody
|
|
805
|
-
* reads — and both of these end with an app that boots, looks correct and never pairs.
|
|
806
|
-
*/
|
|
807
|
-
export const NUXT_PLUGIN_NOTICE = 'Restart the dev server: one that is already running does not pick up a new plugin — it will not ' +
|
|
808
|
-
'appear in .nuxt/plugins/client.mjs and the app comes up with no SDK at all. And if your dev host ' +
|
|
809
|
-
'is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that connect call ' +
|
|
810
|
-
'needs TWO additions, not one: allowNonLocalhost: true AND the token from ~/.reticle/pairing-token. ' +
|
|
811
|
-
'The flag alone is NOT sufficient off localhost.';
|
|
812
|
-
/**
|
|
813
|
-
* The Nuxt recipe, written out in full because each trap in it is one a Nuxt 4 app actually hit:
|
|
814
|
-
* a Vue codebase classified as `html` and handed `@reticlehq/react`, a connect guarded on
|
|
815
|
-
* `window.location.hostname === 'localhost'` (false on any hosts-file alias, and `window` does not
|
|
816
|
-
* exist during SSR), and a running dev server that never picked up the new plugin.
|
|
817
|
-
*
|
|
818
|
-
* So: `import.meta.dev` instead of a hostname check, `.client.ts` instead of an SSR guard, the
|
|
819
|
-
* framework-neutral sensor instead of the React kit, the non-localhost flag named up front, and the
|
|
820
|
-
* restart said out loud.
|
|
821
|
-
*/
|
|
822
|
-
export function nuxtManual(port, projectId) {
|
|
823
|
-
const base = connectArg(port, projectId);
|
|
824
|
-
const fields = '' === base ? '' : base.slice(1, -1).trim();
|
|
825
|
-
const connect = '' === fields ? 'reticle.connect()' : `reticle.connect({ ${fields} })`;
|
|
826
|
-
return `Nuxt owns its own Vite instance and renders its own HTML, so there is no vite.config to patch
|
|
827
|
-
and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which is Nuxt's own idiom:
|
|
828
|
-
|
|
829
|
-
1. Create ${NUXT_PLUGIN_PATH}:
|
|
830
|
-
|
|
831
|
-
export default defineNuxtPlugin(() => {
|
|
832
|
-
// import.meta.dev is the correct guard: it is resolved at build time, so it does not care
|
|
833
|
-
// what hostname you develop on. Do NOT guard on window.location.hostname === 'localhost' —
|
|
834
|
-
// that is false on any hosts-file alias or LAN address, and window does not exist in SSR.
|
|
835
|
-
if (!import.meta.dev) return
|
|
836
|
-
void import('@reticlehq/browser').then(({ reticle }) => {
|
|
837
|
-
${connect}
|
|
838
|
-
})
|
|
839
|
-
})
|
|
840
|
-
|
|
841
|
-
The .client.ts suffix is load-bearing: it is what keeps this out of the server bundle.
|
|
842
|
-
|
|
843
|
-
2. Restart the dev server. A dev server that is already running does not pick up a new plugin —
|
|
844
|
-
it will not appear in .nuxt/plugins/client.mjs, and the app will come up with no SDK at all.
|
|
845
|
-
|
|
846
|
-
3. If your dev host is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that
|
|
847
|
-
connect call needs TWO additions, not one: allowNonLocalhost: true AND a pairing token passed as
|
|
848
|
-
token. The flag alone is NOT sufficient off localhost. The token is the one in
|
|
849
|
-
~/.reticle/pairing-token. Without both, the SDK loads and then refuses, and the only sign is one
|
|
850
|
-
line in the browser console.
|
|
851
|
-
|
|
852
|
-
4. Add this to nuxt.config, so the dev server does not watch Reticle's own journal:
|
|
853
|
-
|
|
854
|
-
vite: { server: { watch: { ignored: [/(^|[\\\\/])\\.${ReticleDir.ROOT.slice(1)}([\\\\/]|$)/] } } }
|
|
855
|
-
|
|
856
|
-
Reticle journals every session into ${ReticleDir.ROOT}/ in your project root, rewriting one file
|
|
857
|
-
in it continuously while a session is live. Nuxt's dev server watches that root, so without this
|
|
858
|
-
it sees each write as a project file changing and full-reloads the page — which reconnects the
|
|
859
|
-
SDK, which produces the next write. The loop runs several times a second and looks like anything
|
|
860
|
-
except what it is: refs go stale, actions die mid-flight, and the session appears to flap.
|
|
861
|
-
It is a RegExp rather than a glob on purpose — chokidar dropped glob support in v4, so a
|
|
862
|
-
double-star pattern here is accepted and matches nothing.
|
|
863
|
-
|
|
864
|
-
The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
|
|
865
|
-
source file:line all work in Vue. What you do not get is React component identity, which is the only
|
|
866
|
-
thing the React adapter adds. There is no Nuxt app in this project's CI, so this path is UNVERIFIED:
|
|
867
|
-
if something does not work, please open an issue.`;
|
|
868
|
-
}
|
|
847
|
+
export { nuxtPluginPath, nuxtPluginFile, NUXT_PLUGIN_NOTICE, nuxtManual } from './nuxt-snippets.js';
|
|
869
848
|
/**
|
|
870
849
|
* Root-level project config for Reticle. Written by `reticle init`; read by `reticle mcp` for the port
|
|
871
850
|
* and by tooling for the stable projectId (the app's identity across port changes).
|