@reticlehq/vite-plugin 3.5.0 → 3.6.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.
@@ -6,6 +6,7 @@
6
6
  * of the plugin's options the PAGE needs to be told about.
7
7
  */
8
8
  import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
9
+ const PRESENT_OPTION = 'present';
9
10
  /** Build the `reticle.connect` argument literal — only includes keys the user set. */
10
11
  export function connectArgs(options) {
11
12
  const args = {};
@@ -18,6 +19,9 @@ export function connectArgs(options) {
18
19
  args['projectId'] = options.projectId;
19
20
  if (options.token !== undefined)
20
21
  args['token'] = options.token;
22
+ // Only the opt-out is needed; the SDK defaults to showing its presenter.
23
+ if (false === options.present)
24
+ args[PRESENT_OPTION] = false;
21
25
  // Passed as connect ARGUMENTS, not as a `define`. A define substitutes a bare identifier in the
22
26
  // source it transforms; the SDK reads these as `globalThis[NAME]`, a dynamic lookup no define can
23
27
  // ever reach — so defining them looked right, shipped, and did nothing. Baking them into the
@@ -67,5 +71,8 @@ export function connectArgs(options) {
67
71
  '1' === process.env['VITE_RETICLE_ALLOW_NON_LOCALHOST']) {
68
72
  args['allowNonLocalhost'] = true;
69
73
  }
74
+ if (options.testIdAttribute !== undefined && options.testIdAttribute.length > 0) {
75
+ args['testIdAttribute'] = options.testIdAttribute;
76
+ }
70
77
  return Object.keys(args).length > 0 ? JSON.stringify(args) : '';
71
78
  }
package/dist/index.cjs CHANGED
@@ -4243,6 +4243,9 @@ function bridgeWsUrl(port = RETICLE_DEFAULT_PORT, host = RETICLE_CLIENT_HOST) {
4243
4243
  return `ws://${host}:${String(port)}${RETICLE_WS_PATH}`;
4244
4244
  }
4245
4245
  var ReticleEnv = {
4246
+ /** "0"/"false"/"off" turns off telemetry and every optional outbound call; DO_NOT_TRACK too. */
4247
+ TELEMETRY: "RETICLE_TELEMETRY",
4248
+ DO_NOT_TRACK: "DO_NOT_TRACK",
4246
4249
  /** Shared-secret the browser SDK must present in HELLO; absent ⇒ loopback-trust only. */
4247
4250
  TOKEN: "RETICLE_TOKEN",
4248
4251
  /** Bridge bind host. Defaults to loopback; setting anything else is opt-in remote exposure. */
@@ -4257,6 +4260,8 @@ var ReticleEnv = {
4257
4260
  STATE_DIR: "RETICLE_STATE_DIR",
4258
4261
  /** Attach to an already-running browser over CDP instead of launching one. */
4259
4262
  CDP_URL: "RETICLE_CDP_URL",
4263
+ /** The Chromium executable to launch, in place of Playwright's own build or an installed Chrome. */
4264
+ CHROMIUM_PATH: "RETICLE_CHROMIUM_PATH",
4260
4265
  /** Max simultaneous leased headless contexts in the browser pool (resource cap). */
4261
4266
  MAX_CONTEXTS: "RETICLE_MAX_CONTEXTS",
4262
4267
  /**
@@ -4437,8 +4442,9 @@ var ReticleDir = {
4437
4442
  /**
4438
4443
  * the user's own record of what Reticle has done for them — .reticle/impact.json.
4439
4444
  *
4440
- * Local only, never uploaded, and deliberately NOT part of telemetry: telemetry answers questions
4441
- * about the product; this answers the user's question about their own work.
4445
+ * NOT telemetry: telemetry answers questions about the product; this answers the user's question
4446
+ * about their own work. A linked project's copy syncs to the user's own dashboard (the sync cycle's
4447
+ * `impact` kind); the machine-wide copy in `~/.reticle` never leaves the machine.
4442
4448
  */
4443
4449
  IMPACT_FILE: "impact.json",
4444
4450
  /** what changes were SUPPOSED to make true —.reticle/intent.json (git-checked, reviewed) */
@@ -4453,14 +4459,6 @@ var ReticleDir = {
4453
4459
  * classified nor ignored.
4454
4460
  */
4455
4461
  INTENT_SUBDIR: "intent",
4456
- /**
4457
- * what a drive types into a field, keyed by the field's label —.reticle/fill-values.json.
4458
- *
4459
- * Git-checked on purpose. A generated value is paid for once and then belongs to the project: the
4460
- * next drive reuses it for free, a replay sends exactly what the recording sent, and a human who
4461
- * dislikes one can edit the file rather than argue with a model.
4462
- */
4463
- FILL_VALUES_FILE: "fill-values.json",
4464
4462
  /** opt-in pixel baselines —.reticle/visual/<name>.png + <name>.diff.png. */
4465
4463
  VISUAL_SUBDIR: "visual",
4466
4464
  /** verification-run artifacts —.reticle/runs/<runId>.json (the OEM/CI-consumable verdict). */
@@ -4475,6 +4473,8 @@ var ReticleDir = {
4475
4473
  * in three separate files, so the guard could not see it and nobody was ever asked.
4476
4474
  */
4477
4475
  FEEDBACK_SUBDIR: "feedback",
4476
+ /** Every Harness drive plan, and how each journey went — .reticle/plans/<when>-<planId>.json. */
4477
+ PLANS_SUBDIR: "plans",
4478
4478
  /** durable causal journal —.reticle/sessions/<id>/{events,actions}.jsonl (the substrate). */
4479
4479
  SESSIONS_SUBDIR: "sessions",
4480
4480
  /** append-only event ledger inside a session dir (one ReticleEvent per line). */
@@ -4498,6 +4498,12 @@ var ReticleDir = {
4498
4498
  FLAKE_FILE: "flake.json",
4499
4499
  /** The app-wide coverage ledger — see server features/exhaust/ledger.ts. */
4500
4500
  COVERAGE_FILE: "coverage.json",
4501
+ /** Human review marks (HUD notes), pending and resolved — see server portal/session/human. */
4502
+ NOTES_FILE: "notes.json",
4503
+ /** The user's latest request, as the agent relayed it. Local: it is the user's own words. */
4504
+ REQUEST_FILE: "request.json",
4505
+ PLATFORM_MOMENTS_FILE: "platform-moments.json",
4506
+ // moments already said, so each is said once
4501
4507
  /**
4502
4508
  * the project's cloud binding — .reticle/cloud.json, written by `reticle link`. Git-checked and
4503
4509
  * non-secret: the project id, the API origin, and where its dashboard lives. The KEY lives in
@@ -4557,6 +4563,9 @@ var RETICLE_ROOT_GLOBAL = "__RETICLE_ROOT__";
4557
4563
  var RETICLE_SDK_VERSION_GLOBAL = "__RETICLE_SDK_VERSION__";
4558
4564
  var RETICLE_IGNORE_MARKER = "@reticle-ignore";
4559
4565
 
4566
+ // ../../../core/dist/identity/testid-attribute.js
4567
+ var TESTID_ATTR_PATTERN = /^[a-zA-Z_][\w-]*$/;
4568
+
4560
4569
  // ../../../core/dist/identity/project-id.js
4561
4570
  var PROJECT_ID_HASH_LENGTH = 8;
4562
4571
  var FALLBACK_PROJECT_BASE = "app";
@@ -4707,6 +4716,16 @@ function readConfiguredPort(startDir, readFile = readFileOrThrow) {
4707
4716
  tcpPort
4708
4717
  );
4709
4718
  }
4719
+ function readConfiguredTestIdAttribute(startDir, readFile = readFileOrThrow) {
4720
+ return readNearestField(
4721
+ startDir,
4722
+ RETICLE_CONFIG_BASENAME,
4723
+ "testIdAttribute",
4724
+ readFile,
4725
+ MAX_CONFIG_SEARCH_DEPTH,
4726
+ (value) => "string" === typeof value && TESTID_ATTR_PATTERN.test(value) ? value : void 0
4727
+ );
4728
+ }
4710
4729
  function resolveProjectId(explicit, cwd, readPkgName = readNearestPackageName, readConfiguredId = readConfiguredProjectId) {
4711
4730
  if (explicit !== void 0 && explicit.length > 0) return explicit;
4712
4731
  const configured = readConfiguredId(cwd);
@@ -5230,6 +5249,7 @@ export function resolveMarkAnchor() {
5230
5249
  `;
5231
5250
 
5232
5251
  // src/connect-args.ts
5252
+ var PRESENT_OPTION = "present";
5233
5253
  function connectArgs(options) {
5234
5254
  const args = {};
5235
5255
  const port = options.port ?? RETICLE_DEFAULT_PORT;
@@ -5237,6 +5257,7 @@ function connectArgs(options) {
5237
5257
  if (options.session !== void 0) args["session"] = options.session;
5238
5258
  if (options.projectId !== void 0) args["projectId"] = options.projectId;
5239
5259
  if (options.token !== void 0) args["token"] = options.token;
5260
+ if (false === options.present) args[PRESENT_OPTION] = false;
5240
5261
  if (options.root !== void 0 && options.root.length > 0) args["root"] = options.root;
5241
5262
  if (options.sdkVersion !== void 0 && options.sdkVersion.length > 0) {
5242
5263
  args["sdkVersion"] = options.sdkVersion;
@@ -5260,6 +5281,9 @@ function connectArgs(options) {
5260
5281
  if (true === options.allowNonLocalhost || "1" === process.env["VITE_RETICLE_ALLOW_NON_LOCALHOST"]) {
5261
5282
  args["allowNonLocalhost"] = true;
5262
5283
  }
5284
+ if (options.testIdAttribute !== void 0 && options.testIdAttribute.length > 0) {
5285
+ args["testIdAttribute"] = options.testIdAttribute;
5286
+ }
5263
5287
  return Object.keys(args).length > 0 ? JSON.stringify(args) : "";
5264
5288
  }
5265
5289
 
@@ -5445,7 +5469,13 @@ function reticle(options = {}) {
5445
5469
  const withToken = token !== void 0 ? { ...withPort, token } : withPort;
5446
5470
  const appRoot = withToken.root ?? root ?? process.cwd();
5447
5471
  const sdkVersion = withToken.sdkVersion ?? sdkPackageVersion(appRoot);
5448
- return { ...withToken, root: appRoot, sdkVersion };
5472
+ const testIdAttribute = withToken.testIdAttribute ?? readConfiguredTestIdAttribute(appRoot);
5473
+ return {
5474
+ ...withToken,
5475
+ root: appRoot,
5476
+ sdkVersion,
5477
+ ...testIdAttribute === void 0 ? {} : { testIdAttribute }
5478
+ };
5449
5479
  };
5450
5480
  const currentConnectSource = () => connectModuleSource(resolveLazy(), root === void 0 ? null : findDevModule(root, import_node_fs6.existsSync));
5451
5481
  let lastServedConnectSource;
package/dist/index.d.cts CHANGED
@@ -64,6 +64,8 @@ export interface ReticleVitePluginOptions {
64
64
  projectId?: string;
65
65
  /** Auth token forwarded to connect when the bridge requires one. */
66
66
  token?: string;
67
+ /** Show Reticle's presenter. Defaults to true; set false to keep the agent connection without the panel. */
68
+ present?: boolean;
67
69
  /** Stamp data-reticle-source for React 19 source mapping. Default true (harmless on React <=18). */
68
70
  sourceMapping?: boolean;
69
71
  /** Auto-inject the dev-gated reticle.connect call. Default true. */
@@ -109,6 +111,8 @@ export interface ReticleVitePluginOptions {
109
111
  * vite.config.
110
112
  */
111
113
  networkBodyMaxChars?: number;
114
+ /** Attribute this app marks controls with instead of `data-testid`. Defaults to `.reticle.json`'s `testIdAttribute`. */
115
+ testIdAttribute?: string;
112
116
  /**
113
117
  * Retain a FAILED request's response body even with `captureNetworkBodies` off. Default true.
114
118
  *
package/dist/index.d.ts CHANGED
@@ -64,6 +64,8 @@ export interface ReticleVitePluginOptions {
64
64
  projectId?: string;
65
65
  /** Auth token forwarded to connect when the bridge requires one. */
66
66
  token?: string;
67
+ /** Show Reticle's presenter. Defaults to true; set false to keep the agent connection without the panel. */
68
+ present?: boolean;
67
69
  /** Stamp data-reticle-source for React 19 source mapping. Default true (harmless on React <=18). */
68
70
  sourceMapping?: boolean;
69
71
  /** Auto-inject the dev-gated reticle.connect call. Default true. */
@@ -109,6 +111,8 @@ export interface ReticleVitePluginOptions {
109
111
  * vite.config.
110
112
  */
111
113
  networkBodyMaxChars?: number;
114
+ /** Attribute this app marks controls with instead of `data-testid`. Defaults to `.reticle.json`'s `testIdAttribute`. */
115
+ testIdAttribute?: string;
112
116
  /**
113
117
  * Retain a FAILED request's response body even with `captureNetworkBodies` off. Default true.
114
118
  *
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { ensurePairingToken } from './token/ensure-token.js';
4
4
  import { homedir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { RETICLE_RENDER_PREHOOK, ReticleDir, ReticleEnv, RETICLE_ROOT_GLOBAL, RETICLE_SDK_VERSION_GLOBAL, } from '@reticlehq/core';
7
- import { resolveProjectId } from './project-id.js';
7
+ import { readConfiguredTestIdAttribute, resolveProjectId } from './project-id.js';
8
8
  import { resolveDaemonPort } from './discover-port.js';
9
9
  import { announceDevServer } from './announce.js';
10
10
  import { stampSvelte } from './svelte-source.js';
@@ -327,7 +327,15 @@ export function reticle(options = {}) {
327
327
  // tree, and they travel in the generated connect call rather than through a `define`.
328
328
  const appRoot = withToken.root ?? root ?? process.cwd();
329
329
  const sdkVersion = withToken.sdkVersion ?? sdkPackageVersion(appRoot);
330
- return { ...withToken, root: appRoot, sdkVersion };
330
+ // `.reticle.json` is where a project names its test-id attribute, read per call like the port so
331
+ // an edit takes effect on the next page load; the option, when given, wins.
332
+ const testIdAttribute = withToken.testIdAttribute ?? readConfiguredTestIdAttribute(appRoot);
333
+ return {
334
+ ...withToken,
335
+ root: appRoot,
336
+ sdkVersion,
337
+ ...(testIdAttribute === undefined ? {} : { testIdAttribute }),
338
+ };
331
339
  };
332
340
  /**
333
341
  * The connect module's source as it would be served RIGHT NOW. Recomputed rather than cached: the
@@ -34,6 +34,13 @@ export declare function readConfiguredProjectId(startDir: string, readFile?: Rea
34
34
  * project's daemon — and the bridge refused it.
35
35
  */
36
36
  export declare function readConfiguredPort(startDir: string, readFile?: ReadFile): number | undefined;
37
+ /**
38
+ * Read `testIdAttribute` from the nearest `.reticle.json`, or undefined: the attribute this project
39
+ * marks controls with when it is not `data-testid` (`data-test-subj`, `data-cy`, ...). The page needs
40
+ * it to resolve `{ testid }`, and the file is the one place the CLI, the daemon and the plugin
41
+ * already agree on, so the plugin forwards it to `connect()` rather than asking for it twice.
42
+ */
43
+ export declare function readConfiguredTestIdAttribute(startDir: string, readFile?: ReadFile): string | undefined;
37
44
  /**
38
45
  * Resolve the projectId for a plugin instance, most-local statement of intent first: an explicit
39
46
  * option, then the id `reticle init` recorded in `.reticle.json`, then derivation.
@@ -9,7 +9,7 @@
9
9
  import { createHash } from 'node:crypto';
10
10
  import { dirname, join } from 'node:path';
11
11
  import { readFileSync } from 'node:fs';
12
- import { PROJECT_ID_HASH_LENGTH, projectIdFrom } from '@reticlehq/core';
12
+ import { PROJECT_ID_HASH_LENGTH, TESTID_ATTR_PATTERN, projectIdFrom } from '@reticlehq/core';
13
13
  /** The project config `reticle init` writes, and the id of record it carries. */
14
14
  const RETICLE_CONFIG_BASENAME = '.reticle.json';
15
15
  export { slugifyPackageName } from '@reticlehq/core';
@@ -99,6 +99,15 @@ export function readConfiguredProjectId(startDir, readFile = readFileOrThrow) {
99
99
  export function readConfiguredPort(startDir, readFile = readFileOrThrow) {
100
100
  return readNearestField(startDir, RETICLE_CONFIG_BASENAME, 'port', readFile, MAX_CONFIG_SEARCH_DEPTH, tcpPort);
101
101
  }
102
+ /**
103
+ * Read `testIdAttribute` from the nearest `.reticle.json`, or undefined: the attribute this project
104
+ * marks controls with when it is not `data-testid` (`data-test-subj`, `data-cy`, ...). The page needs
105
+ * it to resolve `{ testid }`, and the file is the one place the CLI, the daemon and the plugin
106
+ * already agree on, so the plugin forwards it to `connect()` rather than asking for it twice.
107
+ */
108
+ export function readConfiguredTestIdAttribute(startDir, readFile = readFileOrThrow) {
109
+ return readNearestField(startDir, RETICLE_CONFIG_BASENAME, 'testIdAttribute', readFile, MAX_CONFIG_SEARCH_DEPTH, (value) => ('string' === typeof value && TESTID_ATTR_PATTERN.test(value) ? value : undefined));
110
+ }
102
111
  /**
103
112
  * Resolve the projectId for a plugin instance, most-local statement of intent first: an explicit
104
113
  * option, then the id `reticle init` recorded in `.reticle.json`, then derivation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/vite-plugin",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "Vite plugin for Reticle: dev-only source-map stamping plus auto-injected reticle.connect(). apply:'serve' guarantees it never ships to production.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -41,15 +41,15 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@babel/core": "^7.26.0",
44
- "@reticlehq/babel-plugin": "3.5.0",
45
- "@reticlehq/core": "3.5.0"
44
+ "@reticlehq/babel-plugin": "3.6.0",
45
+ "@reticlehq/core": "3.6.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/babel__core": "^7.20.5",
49
49
  "esbuild": "^0.28.2",
50
50
  "svelte": "^5.57.1",
51
51
  "vite": "^8",
52
- "@reticlehq/react": "3.5.0"
52
+ "@reticlehq/react": "3.6.0"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "vite": ">=4"