@reticlehq/init 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/detect/capabilities.d.ts +4 -1
  2. package/dist/detect/capabilities.js +45 -9
  3. package/dist/detect/detect.d.ts +45 -2
  4. package/dist/detect/detect.js +76 -5
  5. package/dist/detect/dev-script.d.ts +26 -2
  6. package/dist/detect/dev-script.js +80 -7
  7. package/dist/detect/non-js-project.d.ts +1 -1
  8. package/dist/detect/non-js-project.js +6 -1
  9. package/dist/detect/workspace-apps.d.ts +21 -0
  10. package/dist/detect/workspace-apps.js +111 -12
  11. package/dist/detect/workspace-redirect.js +28 -2
  12. package/dist/diagnose/containerised-dev-server.d.ts +12 -4
  13. package/dist/diagnose/containerised-dev-server.js +36 -4
  14. package/dist/diagnose/csp-check.d.ts +12 -6
  15. package/dist/diagnose/csp-check.js +61 -13
  16. package/dist/diagnose/csp-doctor.d.ts +13 -1
  17. package/dist/diagnose/csp-doctor.js +2 -2
  18. package/dist/diagnose/csp-step.d.ts +27 -0
  19. package/dist/diagnose/csp-step.js +86 -0
  20. package/dist/diagnose/desktop-doctor.js +8 -2
  21. package/dist/diagnose/init-failure.d.ts +7 -0
  22. package/dist/diagnose/init-failure.js +7 -0
  23. package/dist/diagnose/install-hint.d.ts +9 -1
  24. package/dist/diagnose/install-hint.js +17 -7
  25. package/dist/diagnose/install-retries.d.ts +16 -1
  26. package/dist/diagnose/install-retries.js +50 -3
  27. package/dist/diagnose/terminal-wrap.d.ts +18 -0
  28. package/dist/diagnose/terminal-wrap.js +63 -0
  29. package/dist/index.d.ts +6 -2
  30. package/dist/index.js +6 -2
  31. package/dist/no-package-json.d.ts +10 -0
  32. package/dist/no-package-json.js +131 -0
  33. package/dist/node-io.d.ts +12 -6
  34. package/dist/node-io.js +33 -11
  35. package/dist/patch/angular.d.ts +68 -0
  36. package/dist/patch/angular.js +216 -0
  37. package/dist/patch/astro-patch.d.ts +1 -1
  38. package/dist/patch/astro-patch.js +2 -2
  39. package/dist/patch/cra.d.ts +0 -2
  40. package/dist/patch/cra.js +0 -2
  41. package/dist/patch/electron-patch.d.ts +10 -1
  42. package/dist/patch/electron-patch.js +79 -27
  43. package/dist/patch/electron-vite-patch.js +1 -1
  44. package/dist/patch/nuxt-patch.d.ts +1 -1
  45. package/dist/patch/nuxt-patch.js +2 -2
  46. package/dist/patch/nuxt-snippets.js +6 -6
  47. package/dist/patch/remix.d.ts +24 -0
  48. package/dist/patch/remix.js +59 -0
  49. package/dist/patch/snippets.d.ts +57 -9
  50. package/dist/patch/snippets.js +119 -28
  51. package/dist/patch/static-page.d.ts +39 -0
  52. package/dist/patch/static-page.js +102 -0
  53. package/dist/patch/tanstack-start.d.ts +36 -8
  54. package/dist/patch/tanstack-start.js +118 -29
  55. package/dist/patch/vite-config.d.ts +5 -0
  56. package/dist/patch/vite-config.js +8 -0
  57. package/dist/patch/vite-owning-config.d.ts +34 -0
  58. package/dist/patch/vite-owning-config.js +63 -5
  59. package/dist/plan/connect-steps.d.ts +7 -1
  60. package/dist/plan/connect-steps.js +18 -1
  61. package/dist/plan/framework-adapter.d.ts +3 -1
  62. package/dist/plan/framework-adapter.js +99 -6
  63. package/dist/plan/notices.js +4 -3
  64. package/dist/plan/plan-angular.d.ts +6 -0
  65. package/dist/plan/plan-angular.js +89 -0
  66. package/dist/plan/plan-electron-vite.d.ts +6 -3
  67. package/dist/plan/plan-electron-vite.js +39 -8
  68. package/dist/plan/plan-framework.d.ts +8 -46
  69. package/dist/plan/plan-framework.js +81 -312
  70. package/dist/plan/plan-types.d.ts +29 -0
  71. package/dist/plan/plan-vite.d.ts +67 -0
  72. package/dist/plan/plan-vite.js +256 -0
  73. package/dist/plan/plan.js +38 -34
  74. package/dist/plan/port-steps.d.ts +45 -0
  75. package/dist/plan/port-steps.js +119 -0
  76. package/dist/plan/preflight.d.ts +17 -3
  77. package/dist/plan/preflight.js +53 -23
  78. package/dist/register/mcp-clients.js +5 -3
  79. package/dist/register/mcp.d.ts +18 -5
  80. package/dist/register/mcp.js +48 -8
  81. package/dist/run-types.d.ts +8 -0
  82. package/dist/run.js +118 -80
  83. package/package.json +3 -4
  84. package/NOTICE +0 -5
@@ -2,8 +2,34 @@
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 { UiLibrary } from '../detect/detect.js';
5
+ import { Framework, UiLibrary } from '../detect/detect.js';
6
6
  import type { FoundStore } from '../detect/capabilities.js';
7
+ /**
8
+ * The SDK as one import a plain page can actually resolve.
9
+ *
10
+ * `init` used to tell static-HTML users that a page with no build step could not load the SDK, and
11
+ * to go and stand up a bundler. That is true of a BARE specifier and false of a URL, and the
12
+ * difference was the whole road for every server-rendered app we hear from: FastAPI, Flask, Django,
13
+ * Streamlit, Rails. Proven end to end before this shipped, on a page served by `python3 -m
14
+ * http.server`: a session connected, a snapshot returned, and two act_and_wait calls came back
15
+ * `verified: "yes"`.
16
+ *
17
+ * jsDelivr rather than esm.sh, measured: a third of the requests and a third of the bytes for the
18
+ * same result. `/+esm` is what makes it work, and the bare package URL does NOT: every file in
19
+ * `dist` still carries bare workspace specifiers such as `@reticlehq/core`, so an unbundled entry
20
+ * point dies on the first import. That is also why adding `unpkg`/`jsdelivr` fields to package.json
21
+ * would not help.
22
+ *
23
+ * PINNED to this server's version on purpose. A floating import upgrades the page SDK underneath a
24
+ * daemon that did not move, which is `version_skew` arriving by a route nothing checks.
25
+ */
26
+ /**
27
+ * Where a generated file sends someone to learn `registerStore`. A URL, not a path into
28
+ * `node_modules/@reticlehq/server`: the server is never installed into the app, so the path the
29
+ * generated files used to name did not exist in any project they were written into.
30
+ */
31
+ export declare const STATE_DOCS_URL = "https://docs.reticle.sh/state-management";
32
+ export declare const CDN_SDK_URL: string;
7
33
  /**
8
34
  * The connect argument literal: a non-default port adds a `url`, and a projectId is always passed
9
35
  * (so the app is identifiable across port changes). Empty string only when neither applies.
@@ -22,6 +48,7 @@ export declare function connectArg(port: number | undefined, projectId?: string)
22
48
  * one that does hold a token.
23
49
  */
24
50
  export declare function connectArgWithToken(port: number | undefined, projectId: string | undefined, pairingToken: string | undefined): string;
51
+ export declare function usesReactKit(uiLibrary: UiLibrary, framework?: Framework): boolean;
25
52
  /**
26
53
  * Which SDK package the GENERATED code should import, and whether `install()` applies.
27
54
  *
@@ -33,7 +60,7 @@ export declare function connectArgWithToken(port: number | undefined, projectId:
33
60
  * `install()` is the React adapter's, not the sensor's: `@reticlehq/browser` exports `reticle` and no
34
61
  * `install`, so swapping the specifier alone would trade a missing module for a missing export.
35
62
  */
36
- export declare function sdkImport(uiLibrary: UiLibrary): {
63
+ export declare function sdkImport(uiLibrary: UiLibrary, framework?: Framework): {
37
64
  specifier: string;
38
65
  usesInstall: boolean;
39
66
  };
@@ -42,8 +69,11 @@ export declare function viteManual(port: number | undefined, uiLibrary?: UiLibra
42
69
  /**
43
70
  * The electron-vite recipe: the plugin belongs in `renderer`, never in `main` or `preload`.
44
71
  *
45
- * `desktop: true` is load-bearing. Without it the plugin is serve-only, so a packaged renderer
46
- * (a production build with no dev server) ships with no connect() at all.
72
+ * `desktop: true` is load-bearing: it injects connect() into the entry module and passes
73
+ * `allowInProduction`, because a packaged renderer has no dev server and reports
74
+ * `NODE_ENV=production`, so without it the SDK's production backstop refuses to start. It does not
75
+ * instrument a release — a production-mode `electron-vite build` is stubbed like a web bundle — so
76
+ * a packaged renderer that should be driven is built with `--mode development`.
47
77
  */
48
78
  export declare function electronViteManual(port: number | undefined, uiLibrary?: UiLibrary): string;
49
79
  /** Next.js config wrap — always printed (we never auto-rewrite next.config). */
@@ -89,9 +119,14 @@ export declare function registerCapabilitiesCall(testids: readonly string[], ind
89
119
  * breaks the module everything else hangs off. So the store lines are generated COMMENTED, naming
90
120
  * the libraries actually found in package.json, with the exact call to uncomment.
91
121
  */
92
- export declare function viteDevModuleFile(testids: readonly string[], stores: readonly string[], found?: readonly FoundStore[], uiLibrary?: UiLibrary): string;
122
+ export declare function viteDevModuleFile(testids: readonly string[], stores: readonly string[], found?: readonly FoundStore[], uiLibrary?: UiLibrary, framework?: Framework): string;
93
123
  /** Where that module goes. Matches @reticlehq/vite-plugin's convention list. */
94
124
  export declare const VITE_DEV_MODULE_PATH = "src/reticle-dev.ts";
125
+ /**
126
+ * The dev module as a relative import from `fromFile` — for the frameworks that connect themselves
127
+ * and so have to load it themselves (see svelteKitHooksFile, tanstackStartConnectFile).
128
+ */
129
+ export declare function devModuleSpecifier(fromFile: string): string;
95
130
  /**
96
131
  * electron-vite's renderer Vite root is `src/renderer`, so the plugin looks for
97
132
  * `src/renderer/src/reticle-dev.ts` — not the package-root path a plain Vite app uses.
@@ -137,7 +172,7 @@ export declare function streamlitPageSnippet(connectArgLiteral: string): string;
137
172
  * `<head>`, so the SDK sees a parsed document.
138
173
  */
139
174
  export declare function djangoMiddlewareSnippet(connectArgLiteral: string): string;
140
- export declare function htmlManual(port: number | undefined, projectId?: string, pairingToken?: string): string;
175
+ export declare function htmlManual(port: number | undefined, projectId?: string, pairingToken?: string, uiLibrary?: UiLibrary): string;
141
176
  export declare const NEXT_RETICLE_DEV_PATH = "app/reticle-dev.tsx";
142
177
  export declare const SVELTEKIT_HOOKS_PATH = "src/hooks.client.ts";
143
178
  /**
@@ -146,9 +181,9 @@ export declare const SVELTEKIT_HOOKS_PATH = "src/hooks.client.ts";
146
181
  * component names and source mapping do not, and no CI gate covers this stack. Saying so beats
147
182
  * reporting all-green.
148
183
  */
149
- export declare function unverifiedUiLibraryNote(library: string): string;
150
- export declare const UNVERIFIED_FRAMEWORK_NOTE = "Reticle has no SvelteKit app and no CI gate for one, so this wiring is untested \u2014 it may work, but nothing proves it and nothing will tell us if it breaks. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the hook does not register a session, please open an issue.";
151
- export declare const UNVERIFIED_TANSTACK_START_NOTE = "Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested \u2014 it may work, but nothing proves it and nothing will tell us if it breaks. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the client effect does not register a session, please open an issue.";
184
+ export declare function unverifiedUiLibraryNote(library: string, installGated?: boolean): string;
185
+ export declare 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.";
186
+ export declare const UNVERIFIED_TANSTACK_START_NOTE = "Reticle has no TanStack Start app and no CI gate for one, so this wiring is untested \u2014 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.";
152
187
  /**
153
188
  * Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
154
189
  * canvas is none of those. Without this line, init succeeds, sessions connect, and surrounding UI
@@ -181,6 +216,19 @@ export declare const WEBPACK4_REACT_SCRIPTS_MAJOR = 5;
181
216
  export declare function webpack4TranspileNote(reactScriptsMajor: number): string;
182
217
  /** React Router's client-entry override point, in framework mode. */
183
218
  export declare const REACT_ROUTER_ENTRY_PATH = "app/entry.client.tsx";
219
+ /**
220
+ * The one line that puts Reticle in a React Router client entry — and in a Remix one.
221
+ *
222
+ * The specifier is BUILT, not written as a literal. `import('/@reticle-connect')` is a module only
223
+ * the Vite dev server can answer, so `tsc` cannot resolve it: a fresh framework-mode scaffold went
224
+ * red on `npm run typecheck` (`TS2307: Cannot find module '/@reticle-connect'`) the moment `init`
225
+ * wrote it. A computed specifier is typed `Promise<any>` and never resolved, so no declaration, no
226
+ * suppression comment and no tsconfig edit is needed. The browser still asks the dev server for the
227
+ * same URL, which the plugin serves; `BASE_URL` keeps it right under a non-root `base`. `@vite-ignore`
228
+ * is Vite's own marker for a dynamic import it should not try to analyse, not a lint suppression. In
229
+ * a production build `import.meta.env.DEV` is false and the whole line is dropped.
230
+ */
231
+ export declare const REACT_ROUTER_CONNECT_LINE = "if (import.meta.env.DEV) void import(/* @vite-ignore */ `${import.meta.env.BASE_URL}@reticle-connect`);";
184
232
  /**
185
233
  * The client entry `init` writes when React Router framework mode has none.
186
234
  *
@@ -3,9 +3,10 @@
3
3
  * so the runner never inlines free strings.
4
4
  */
5
5
  import { RETICLE_DEFAULT_PORT, bridgeWsUrl, RETICLE_CLIENT_HOST, RETICLE_WS_PATH, } from '@reticlehq/core';
6
- import { UiLibrary } from '../detect/detect.js';
6
+ import { Framework, UiLibrary } from '../detect/detect.js';
7
7
  import { RETICLE_VERSION } from '../version.js';
8
8
  import { VITE_ENV_DEV_DECLARATION } from './vite-env-types.js';
9
+ import { posix } from 'node:path';
9
10
  /**
10
11
  * The SDK as one import a plain page can actually resolve.
11
12
  *
@@ -25,7 +26,13 @@ import { VITE_ENV_DEV_DECLARATION } from './vite-env-types.js';
25
26
  * PINNED to this server's version on purpose. A floating import upgrades the page SDK underneath a
26
27
  * daemon that did not move, which is `version_skew` arriving by a route nothing checks.
27
28
  */
28
- const CDN_SDK_URL = `https://cdn.jsdelivr.net/npm/@reticlehq/browser@${RETICLE_VERSION}/+esm`;
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`;
29
36
  /**
30
37
  * The connect argument literal: a non-default port adds a `url`, and a projectId is always passed
31
38
  * (so the app is identifiable across port changes). Empty string only when neither applies.
@@ -58,6 +65,35 @@ export function connectArgWithToken(port, projectId, pairingToken) {
58
65
  const inner = base.length > 0 ? base.slice(1, -1).trim() : '';
59
66
  return `{ ${[inner, `token: '${pairingToken}'`].filter((p) => p.length > 0).join(', ')} }`;
60
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
+ }
61
97
  /**
62
98
  * Which SDK package the GENERATED code should import, and whether `install()` applies.
63
99
  *
@@ -69,9 +105,8 @@ export function connectArgWithToken(port, projectId, pairingToken) {
69
105
  * `install()` is the React adapter's, not the sensor's: `@reticlehq/browser` exports `reticle` and no
70
106
  * `install`, so swapping the specifier alone would trade a missing module for a missing export.
71
107
  */
72
- export function sdkImport(uiLibrary) {
73
- const react = uiLibrary !== UiLibrary.VUE && uiLibrary !== UiLibrary.SVELTE;
74
- return react
108
+ export function sdkImport(uiLibrary, framework) {
109
+ return usesReactKit(uiLibrary, framework)
75
110
  ? { specifier: '@reticlehq/react', usesInstall: true }
76
111
  : { specifier: '@reticlehq/browser', usesInstall: false };
77
112
  }
@@ -96,6 +131,15 @@ function frameworkPluginExample(uiLibrary) {
96
131
  return '/* your existing plugins */';
97
132
  }
98
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
+ };
99
143
  /** The Vite-config snippet printed when we can't safely auto-patch the config. */
100
144
  export function viteManual(port, uiLibrary = UiLibrary.UNKNOWN, inject = true, sourceMapping = true) {
101
145
  const options = [
@@ -111,6 +155,7 @@ lowercase JSX tag is not an element. Stamping one crashes the app at commit time
111
155
  pointers, not the app.`;
112
156
  return `Add the Reticle plugin to your Vite config:
113
157
 
158
+ ${DEFINE_CONFIG_IMPORT.VITE}
114
159
  import { reticle } from '@reticlehq/vite-plugin';
115
160
 
116
161
  export default defineConfig({
@@ -125,8 +170,11 @@ from \`vite build\`.${note}`;
125
170
  /**
126
171
  * The electron-vite recipe: the plugin belongs in `renderer`, never in `main` or `preload`.
127
172
  *
128
- * `desktop: true` is load-bearing. Without it the plugin is serve-only, so a packaged renderer
129
- * (a production build with no dev server) ships with no connect() at all.
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`.
130
178
  */
131
179
  export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
132
180
  const extras = port === undefined
@@ -134,6 +182,7 @@ export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
134
182
  : `desktop: true, port: ${String(port)}, captureNetworkBodies: true`;
135
183
  return `Add the Reticle plugin to the \`renderer\` block of electron.vite.config, not \`main\`:
136
184
 
185
+ ${DEFINE_CONFIG_IMPORT.ELECTRON_VITE}
137
186
  import { reticle } from '@reticlehq/vite-plugin';
138
187
 
139
188
  export default defineConfig({
@@ -144,8 +193,9 @@ export function electronViteManual(port, uiLibrary = UiLibrary.UNKNOWN) {
144
193
  },
145
194
  });
146
195
 
147
- \`desktop: true\` is required: a packaged renderer is a production build with no dev server, so the
148
- default serve-only plugin is dropped from \`vite build\` and the shipped app never connects.
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\`.
149
199
 
150
200
  Also add \`import '@reticlehq/electron/preload'\` as the first line of your preload, and
151
201
  \`installReticleCapture(win)\` in main after you construct the BrowserWindow.`;
@@ -183,7 +233,7 @@ export function nextReticleDevFile(port, projectId, testids = [], stores = [], f
183
233
  const storeBlock = found.length > 0
184
234
  ? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
185
235
  : 0 === stores.length
186
- ? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
236
+ ? ` // No state library detected. If you add one, register it here — see ${STATE_DOCS_URL}`
187
237
  : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
188
238
  const registerNames = found.length > 0 ? ', registerStore' : '';
189
239
  // Multiline connect + single blank after imports (#684): the one-line connect and the double blank
@@ -398,7 +448,7 @@ ${indent}});`;
398
448
  * breaks the module everything else hangs off. So the store lines are generated COMMENTED, naming
399
449
  * the libraries actually found in package.json, with the exact call to uncomment.
400
450
  */
401
- export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLibrary.REACT) {
451
+ export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLibrary.REACT, framework) {
402
452
  // The specifier has to be the package `frameworkPackages` installed. This file is the Vite path —
403
453
  // the commonest install there is — and it was hardcoded to `@reticlehq/react` while a Vue or
404
454
  // Svelte app was being given `@reticlehq/browser`, so the generated file imported something that
@@ -406,7 +456,7 @@ export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLib
406
456
  //
407
457
  // The sensor exports `registerCapabilities` and `registerStore` (and the store adapters) just as
408
458
  // the React kit does; the only thing it lacks is `install()`, which this file never called.
409
- const sdk = sdkImport(uiLibrary);
459
+ const sdk = sdkImport(uiLibrary, framework);
410
460
  const ids = testids.map((t) => `'${t}'`).join(', ');
411
461
  // A store we FOUND is imported and registered outright — the whole point of the file. The hints
412
462
  // stay only for the libraries we can name but not wire (they need an argument we cannot infer).
@@ -414,10 +464,11 @@ export function viteDevModuleFile(testids, stores, found = [], uiLibrary = UiLib
414
464
  const storeBlock = found.length > 0
415
465
  ? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
416
466
  : 0 === stores.length
417
- ? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
467
+ ? ` // No state library detected. If you add one, register it here — see ${STATE_DOCS_URL}`
418
468
  : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
419
469
  const registerImport = found.length > 0 ? 'registerCapabilities, registerStore' : 'registerCapabilities';
420
- return `// Dev-only. Imported automatically by @reticlehq/vite-plugin, so you do not need to import it.
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.
421
472
  // Self-guards on import.meta.env.DEV, so it is a no-op in a production build.
422
473
  import { ${registerImport} } from '${sdk.specifier}';
423
474
  ${storeImports.length > 0 ? `${storeImports}\n` : ''}
@@ -444,6 +495,14 @@ ${VITE_ENV_DEV_DECLARATION}`;
444
495
  }
445
496
  /** Where that module goes. Matches @reticlehq/vite-plugin's convention list. */
446
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
+ }
447
506
  /**
448
507
  * electron-vite's renderer Vite root is `src/renderer`, so the plugin looks for
449
508
  * `src/renderer/src/reticle-dev.ts` — not the package-root path a plain Vite app uses.
@@ -556,8 +615,21 @@ class ReticleDevMiddleware:
556
615
  return response
557
616
  `;
558
617
  }
559
- 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) {
560
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
+ });`;
561
633
  const tokenNote = pairingToken === undefined || 0 === pairingToken.length
562
634
  ? ''
563
635
  : `\n\n The \`token\` is this machine's pairing token, read from ~/.reticle/pairing-token. Keep it: the
@@ -566,13 +638,10 @@ export function htmlManual(port, projectId, pairingToken) {
566
638
  return `No Vite/Next plugin detected — wire the dev-only connect by hand. Pick the form for your setup:
567
639
 
568
640
  • Bundled app (Create React App, webpack, Parcel, Vue/Svelte CLI, etc.) — add to your ENTRY module
569
- (e.g. src/index.js or src/main.js), where '@reticlehq/react' resolves through your bundler:
641
+ (e.g. src/index.js or src/main.js), where '${sdk.specifier}' resolves through your bundler:
570
642
 
571
643
  if (process.env.NODE_ENV !== 'production') {
572
- void import('@reticlehq/react').then(({ reticle, install }) => {
573
- install();
574
- reticle.connect(${withToken});
575
- });
644
+ ${bundledImport}
576
645
  }${tokenNote}
577
646
 
578
647
  • Plain HTML with NO build step (FastAPI, Flask, Django, Rails, Streamlit, a hand-written page) —
@@ -597,7 +666,7 @@ export const SVELTEKIT_HOOKS_PATH = 'src/hooks.client.ts';
597
666
  * component names and source mapping do not, and no CI gate covers this stack. Saying so beats
598
667
  * reporting all-green.
599
668
  */
600
- export function unverifiedUiLibraryNote(library) {
669
+ export function unverifiedUiLibraryNote(library, installGated = false) {
601
670
  // Preact is not in the same position as Vue or Svelte and must not be told it is. The React
602
671
  // adapter reaches Preact through `preact/compat`, which is what `docs/frameworks.mdx` has always
603
672
  // said, so telling a Preact reader they get no component identity contradicts our own docs and
@@ -606,10 +675,10 @@ export function unverifiedUiLibraryNote(library) {
606
675
  const identity = 'preact' === library
607
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.'
608
677
  : 'What `@reticlehq/react` adds and you will NOT get is React component identity: component names and component stacks.';
609
- 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} ${'vue' === library ? 'The install gate scaffolds a Vue app from scratch on every change, so this SETUP is proven; no gate drives a Vue app to a verdict, so the drive is not.' : `No CI gate covers ${library}.`} Driven on every change: Vite + React, Next.js, Remix, Astro. If something doesn't work, please open an issue.`;
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.`;
610
679
  }
611
- export const UNVERIFIED_FRAMEWORK_NOTE = 'Reticle has no SvelteKit 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. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the hook does not register a session, please open an issue.';
612
- 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. Supported and gated today: Vite + React, Next.js, Remix and Astro. If the client effect does not register a session, please open an issue.';
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.';
613
682
  /**
614
683
  * Printed when init detects react-three-fiber. Reticle's model is DOM + store + network; a WebGL
615
684
  * canvas is none of those. Without this line, init succeeds, sessions connect, and surrounding UI
@@ -648,6 +717,9 @@ if (import.meta.env.DEV) {
648
717
  ...(sdkVersion.length > 0 ? { sdkVersion } : {}),
649
718
  });
650
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)}');
651
723
  });
652
724
  }
653
725
 
@@ -697,10 +769,26 @@ export function webpack4TranspileNote(reactScriptsMajor) {
697
769
  }
698
770
  /** React Router's client-entry override point, in framework mode. */
699
771
  export const REACT_ROUTER_ENTRY_PATH = 'app/entry.client.tsx';
700
- /** The dev-only module @reticlehq/vite-plugin serves: connect() with the token already in it. */
701
- const RETICLE_CONNECT_MODULE = '/@reticle-connect';
702
- /** The one line that puts Reticle in a React Router client entry. */
703
- const REACT_ROUTER_CONNECT_LINE = `if (import.meta.env.DEV) void import('/@reticle-connect');`;
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');";
704
792
  /**
705
793
  * The client entry `init` writes when React Router framework mode has none.
706
794
  *
@@ -742,6 +830,9 @@ startTransition(() => {
742
830
  * still be evaluated first, and this is a side-effect import.
743
831
  */
744
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
+ }
745
836
  if (source.includes(RETICLE_CONNECT_MODULE))
746
837
  return null;
747
838
  const lines = source.split('\n');
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The connect snippet, written INTO a static page rather than printed beside it.
3
+ *
4
+ * A page with no package.json has no build, so the pasted `<script type="module">` is the whole
5
+ * install — proven end to end on a page served by `python3 -m http.server`. `init` used to print it
6
+ * and exit 1 with `no_package_json`, which made the one path that works read as a failure, and left
7
+ * the step to a human who then had to be told where to put it.
8
+ *
9
+ * Two things differ from the printed recipe, both because the file is the deployable artifact:
10
+ *
11
+ * - It runs only on a loopback host. A static file has no development build to strip it, so the
12
+ * host is the only line between this snippet and a published page; guarded, a published copy
13
+ * does not even fetch the SDK. The printed bundled-app recipe deliberately does NOT do this
14
+ * (a hosts-file dev alias defeats it, see html-no-build.test.ts) — that recipe has NODE_ENV.
15
+ * - It is marked, so a re-run recognises its own block and never writes a second one.
16
+ * - It carries no pairing token. The token used to be a literal in the page, so committing or
17
+ * publishing index.html published it; it now sits in a module beside the page that `init`
18
+ * gitignores, which the block imports and any static server serves. Absent — a teammate's
19
+ * checkout, a published copy — the connect goes without one, and the bridge says why it refused.
20
+ */
21
+ /** The page a static site serves at its root, and the one file `init` will write the snippet into. */
22
+ export declare const HTML_INDEX_PATH = "index.html";
23
+ /** The gitignored module beside the page that holds this machine's pairing token. */
24
+ export declare const STATIC_TOKEN_MODULE = "reticle.local.js";
25
+ /** Opens the block `init` writes; a re-run looks for it. */
26
+ export declare const STATIC_SNIPPET_MARKER = "<!-- reticle:connect (dev only, written by reticle init) -->";
27
+ /** Whether this page already carries a Reticle connect. */
28
+ export declare function hasStaticSnippet(html: string): boolean;
29
+ /** The token module's content. Rewritten on every run, so a re-minted token is picked up. */
30
+ export declare function staticTokenModule(pairingToken: string): string;
31
+ /** The files to write so the token module exists and git ignores it; only what actually changes. */
32
+ export declare function staticTokenFiles(pairingToken: string | undefined, readFile: (path: string) => string | null): Record<string, string>;
33
+ /**
34
+ * The page with the snippet added, or null when it already connects.
35
+ *
36
+ * Before `</body>` so the SDK starts on a parsed document; before `</head>` for a page with no body
37
+ * tag, and at the end for a fragment with neither.
38
+ */
39
+ export declare function withStaticSnippet(html: string, connectArgLiteral: string): string | null;
@@ -0,0 +1,102 @@
1
+ /**
2
+ * The connect snippet, written INTO a static page rather than printed beside it.
3
+ *
4
+ * A page with no package.json has no build, so the pasted `<script type="module">` is the whole
5
+ * install — proven end to end on a page served by `python3 -m http.server`. `init` used to print it
6
+ * and exit 1 with `no_package_json`, which made the one path that works read as a failure, and left
7
+ * the step to a human who then had to be told where to put it.
8
+ *
9
+ * Two things differ from the printed recipe, both because the file is the deployable artifact:
10
+ *
11
+ * - It runs only on a loopback host. A static file has no development build to strip it, so the
12
+ * host is the only line between this snippet and a published page; guarded, a published copy
13
+ * does not even fetch the SDK. The printed bundled-app recipe deliberately does NOT do this
14
+ * (a hosts-file dev alias defeats it, see html-no-build.test.ts) — that recipe has NODE_ENV.
15
+ * - It is marked, so a re-run recognises its own block and never writes a second one.
16
+ * - It carries no pairing token. The token used to be a literal in the page, so committing or
17
+ * publishing index.html published it; it now sits in a module beside the page that `init`
18
+ * gitignores, which the block imports and any static server serves. Absent — a teammate's
19
+ * checkout, a published copy — the connect goes without one, and the bridge says why it refused.
20
+ */
21
+ import { CDN_SDK_URL } from './snippets.js';
22
+ /** The page a static site serves at its root, and the one file `init` will write the snippet into. */
23
+ export const HTML_INDEX_PATH = 'index.html';
24
+ /** The gitignored module beside the page that holds this machine's pairing token. */
25
+ export const STATIC_TOKEN_MODULE = 'reticle.local.js';
26
+ const GITIGNORE_PATH = '.gitignore';
27
+ /** Opens the block `init` writes; a re-run looks for it. */
28
+ export const STATIC_SNIPPET_MARKER = '<!-- reticle:connect (dev only, written by reticle init) -->';
29
+ const STATIC_SNIPPET_END = '<!-- /reticle:connect -->';
30
+ /** Hostnames a developer's own machine serves a page on. */
31
+ const LOOPBACK_HOSTS = ['localhost', '127.0.0.1', '[::1]'];
32
+ /**
33
+ * Signs that the page already connects, whoever put the snippet there — the printed recipe pasted
34
+ * by hand counts as much as our own block.
35
+ */
36
+ const ALREADY_CONNECTS = [STATIC_SNIPPET_MARKER, '@reticlehq/browser'];
37
+ const BODY_CLOSE = /<\/body>/i;
38
+ const HEAD_CLOSE = /<\/head>/i;
39
+ /** Whether this page already carries a Reticle connect. */
40
+ export function hasStaticSnippet(html) {
41
+ return ALREADY_CONNECTS.some((sign) => html.includes(sign));
42
+ }
43
+ function block(connectArgLiteral) {
44
+ const hosts = LOOPBACK_HOSTS.map((h) => `'${h}'`).join(', ');
45
+ const arg = '' === connectArgLiteral
46
+ ? '{ token: local.token }'
47
+ : `{ ...${connectArgLiteral}, token: local.token }`;
48
+ return ` ${STATIC_SNIPPET_MARKER}
49
+ <script type="module">
50
+ if ([${hosts}].includes(location.hostname)) {
51
+ Promise.all([
52
+ import('${CDN_SDK_URL}'),
53
+ import('./${STATIC_TOKEN_MODULE}').catch(() => ({})),
54
+ ]).then(([{ reticle }, local]) => reticle.connect(${arg}));
55
+ }
56
+ </script>
57
+ ${STATIC_SNIPPET_END}
58
+ `;
59
+ }
60
+ /** The token module's content. Rewritten on every run, so a re-minted token is picked up. */
61
+ export function staticTokenModule(pairingToken) {
62
+ return (`// This machine's Reticle pairing token, written by \`reticle init\`. Gitignored: never commit\n` +
63
+ `// or publish it — each machine runs \`reticle init\` for its own.\n` +
64
+ `export const token = '${pairingToken}';\n`);
65
+ }
66
+ /** The files to write so the token module exists and git ignores it; only what actually changes. */
67
+ export function staticTokenFiles(pairingToken, readFile) {
68
+ if (pairingToken === undefined || 0 === pairingToken.length)
69
+ return {};
70
+ const files = {};
71
+ const module = staticTokenModule(pairingToken);
72
+ if (readFile(STATIC_TOKEN_MODULE) !== module)
73
+ files[STATIC_TOKEN_MODULE] = module;
74
+ const ignore = readFile(GITIGNORE_PATH) ?? '';
75
+ const covered = ignore
76
+ .split('\n')
77
+ .some((l) => [STATIC_TOKEN_MODULE, `/${STATIC_TOKEN_MODULE}`].includes(l.trim()));
78
+ if (!covered) {
79
+ files[GITIGNORE_PATH] =
80
+ `${'' === ignore ? '' : ignore.replace(/\n*$/, '\n')}${STATIC_TOKEN_MODULE}\n`;
81
+ }
82
+ return files;
83
+ }
84
+ /**
85
+ * The page with the snippet added, or null when it already connects.
86
+ *
87
+ * Before `</body>` so the SDK starts on a parsed document; before `</head>` for a page with no body
88
+ * tag, and at the end for a fragment with neither.
89
+ */
90
+ export function withStaticSnippet(html, connectArgLiteral) {
91
+ if (hasStaticSnippet(html))
92
+ return null;
93
+ const snippet = block(connectArgLiteral);
94
+ const anchor = BODY_CLOSE.exec(html) ?? HEAD_CLOSE.exec(html);
95
+ if (null === anchor)
96
+ return `${html.trimEnd()}\n${snippet}`;
97
+ // At the start of the closing tag's own line when it sits alone on one, so its indentation stays
98
+ // with it rather than moving onto the first line of the snippet.
99
+ const lineStart = html.lastIndexOf('\n', anchor.index - 1) + 1;
100
+ const at = '' === html.slice(lineStart, anchor.index).trim() ? lineStart : anchor.index;
101
+ return `${html.slice(0, at)}${snippet}${html.slice(at)}`;
102
+ }
@@ -1,17 +1,45 @@
1
1
  /**
2
- * The TanStack Start connect recipe.
2
+ * The TanStack Start connect: a client-only component, rendered in the document the root route SSRs.
3
3
  *
4
- * Its own module because Start is the one framework whose document module `init` deliberately
5
- * refuses to write, so the recipe and the reasoning behind that refusal are a unit.
4
+ * Its own module because Start is the one framework whose document module is ALSO the server
5
+ * render, so what may be written into it — and what must stay out — is a unit.
6
6
  */
7
+ import { type SourcePatch } from './patch-kind.js';
7
8
  /** TanStack Start's document module — the file that SSRs `<html>`. */
8
9
  export declare const TANSTACK_START_ROOT_PATH = "src/routes/__root.tsx";
9
10
  /**
10
- * The TanStack Start recipe, printed rather than written.
11
+ * Where the connect component goes: beside the `routes/` directory, never inside it — a file under
12
+ * `routes/` is a route to Start's file-based router.
13
+ */
14
+ export declare function tanstackStartConnectPath(rootPath?: string): string;
15
+ /**
16
+ * The connect component.
17
+ *
18
+ * A dynamic SDK import inside `useEffect`, because a static import on anything the root document
19
+ * imports is evaluated during the server render and 500s the page. Effects never run on the server.
20
+ * The pairing token is the one the Vite plugin inlines as `__RETICLE_TOKEN__`, declared here so the
21
+ * file passes the scaffold's strict `tsc` — the recipe this replaced used the name undeclared and
22
+ * failed with `TS2304: Cannot find name '__RETICLE_TOKEN__'`.
23
+ *
24
+ * It also loads the app's `src/reticle-dev.ts` after connecting. Start is written `inject: false`,
25
+ * and the plugin's injected connect is the only other thing that imports that file, so without this
26
+ * line every store and capability registered there silently never ran.
27
+ */
28
+ export declare function tanstackStartConnectFile(port: number | undefined, projectId?: string, rootPath?: string): string;
29
+ /**
30
+ * Render the component in the root document, just before `<Scripts />`.
31
+ *
32
+ * `<Scripts />` is the anchor because every Start root has to render it — it is how the client
33
+ * bundle reaches the page — whatever the component around it is called (`RootDocument` as the
34
+ * route's `shellComponent` in the current template; `RootComponent` in older ones). A root without
35
+ * one is a shape we do not recognise, and a half-edited document is worse than a documented step.
36
+ */
37
+ export declare function patchTanstackStartRoot(source: string, rootPath: string): SourcePatch;
38
+ /**
39
+ * The recipe, for a root `patchTanstackStartRoot` would not edit.
11
40
  *
12
- * `__root.tsx` is the document. A static import of the SDK on that module SSRs and 500s, so `init`
13
- * does not write one. The connect has to be a client-only dynamic import inside `useEffect`, with
14
- * the pairing token the plugin inlines as `__RETICLE_TOKEN__`. Same judgement React Router already
15
- * makes about `app/entry.client.tsx`: a half-written document is worse than a documented manual step.
41
+ * The same two edits it makes, stated as edits: create the component, render it before
42
+ * `<Scripts />`. It used to say "add this useEffect to the App component", naming a component the
43
+ * current template does not have, and its snippet used `__RETICLE_TOKEN__` undeclared.
16
44
  */
17
45
  export declare function tanstackStartManual(port: number | undefined, projectId?: string, rootPath?: string): string;