@flemo/devtools 0.4.0 → 0.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.
@@ -0,0 +1,4 @@
1
+ import { FlemoDevtoolsProps } from './react';
2
+ export type { FlemoDevtoolsProps } from './react';
3
+ export declare function FlemoDevtools(_props?: FlemoDevtoolsProps): null;
4
+ export default FlemoDevtools;
@@ -0,0 +1,6 @@
1
+ //#region src/reactNoop.ts
2
+ function e(e = {}) {
3
+ return null;
4
+ }
5
+ //#endregion
6
+ export { e as FlemoDevtools, e as default };
@@ -1,11 +1,13 @@
1
+ import { clearTrace } from './persistence';
1
2
  import { FlemoReport, FlightRecord, FlightRecorderHandle, FlightRecorderOptions } from './types';
2
- export declare const REPORT_SCHEMA_VERSION = "2";
3
+ export declare const REPORT_SCHEMA_VERSION = "3";
3
4
  /** The API installed at window.flemo (guarded — see attachFlightRecorder). */
4
5
  export interface FlemoGlobal {
5
6
  /** Marker distinguishing this recorder's global from foreign occupants. */
6
7
  __flemoDevtools: true;
7
8
  report: () => FlemoReport;
8
9
  flights: () => FlightRecord[];
10
+ mark: (bucket: string | null) => string | null;
9
11
  detach: () => void;
10
12
  }
11
13
  /**
@@ -15,3 +17,4 @@ export interface FlemoGlobal {
15
17
  * the schema constants.
16
18
  */
17
19
  export declare const attachFlightRecorder: (options?: FlightRecorderOptions) => FlightRecorderHandle;
20
+ export { clearTrace };
@@ -1,4 +1,5 @@
1
- import { FlightDriver, FlightKind, FramePhaseStats, FrameSampleStats, PlayerGapStats } from './types';
1
+ import { DriverEvidence } from './frameProbe';
2
+ import { FlightDriver, FlightKind, FramePhaseStats, FrameSampleStats } from './types';
2
3
  export declare const computePhaseStats: (gaps: readonly number[]) => FramePhaseStats;
3
4
  /**
4
5
  * Combined frame stats: `heldGaps` are frames sampled while any transitional
@@ -7,16 +8,13 @@ export declare const computePhaseStats: (gaps: readonly number[]) => FramePhaseS
7
8
  * ordered gap list is their concatenation.
8
9
  */
9
10
  export declare const computeFrameStats: (heldGaps: readonly number[], releasedGaps?: readonly number[]) => FrameSampleStats;
10
- export declare const computePlayerGapStats: (gaps: readonly number[]) => PlayerGapStats | null;
11
- /** Driver evidence gathered by the rAF sampler during a flight. */
12
- export interface DriverEvidence {
13
- /** A running CSSAnimation named flemo-* was observed on a participant. */
14
- compiledAnimation: boolean;
15
- /** A participant carried inline `animation` suppression (player stake). */
16
- playerSuppression: boolean;
17
- /** Inline transform/opacity advanced between sampled frames. */
18
- playerAdvance: boolean;
19
- }
11
+ /**
12
+ * Which tier drove the flight.
13
+ *
14
+ * The library compiles every animation, so `inline` never comes from flemo:
15
+ * it means SOMETHING ELSE was writing frames onto a participant, which is
16
+ * worth knowing and is why the signature is still watched for.
17
+ */
20
18
  export declare const classifyDriver: (evidence: DriverEvidence) => FlightDriver;
21
19
  export declare const kindFromStatus: (status: string) => FlightKind | null;
22
20
  /**
@@ -0,0 +1,30 @@
1
+ import { FlightRecorderHandle } from './types';
2
+ /** Any screen mid-transition: while this matches, every surface stays frozen. */
3
+ export declare const IN_FLIGHT_SELECTOR: string;
4
+ export declare const flightInProgress: () => boolean;
5
+ export interface ShadowHost {
6
+ host: HTMLElement;
7
+ root: HTMLElement;
8
+ }
9
+ /**
10
+ * A fixed, zero-sized host with an open shadow root.
11
+ *
12
+ * Zero-sized so it participates in no layout, and its fixed children position
13
+ * against the viewport instead. It carries the devtools marker and NO
14
+ * `data-flemo-*` screen attribute, so the recorder can never mistake its own
15
+ * surface for a flight participant.
16
+ */
17
+ export declare const createShadowHost: (css: string) => ShadowHost;
18
+ /**
19
+ * Which recorder a surface reads.
20
+ *
21
+ * Three cases, and the ownership question is the point of each: a recorder
22
+ * handed in is the caller's and is never detached here; this package's own
23
+ * `window.flemo` (usually an app's own recorder) is read but never taken down;
24
+ * anything else means the surface attaches one and owns it. Getting this wrong
25
+ * detaches the app's recorder when a panel closes, which loses the trace.
26
+ */
27
+ export declare const resolveRecorder: (provided?: FlightRecorderHandle) => {
28
+ recorder: FlightRecorderHandle;
29
+ ownsRecorder: boolean;
30
+ };
@@ -0,0 +1,37 @@
1
+ import { InputEvidence, TripwireHit } from './types';
2
+ /** How long before a flight opens an input event still counts as its cause. */
3
+ export declare const INPUT_WINDOW_MS = 2000;
4
+ export interface TripwireHandle {
5
+ detach: () => void;
6
+ /** True once any flemo-named CSS animation event has been observed. */
7
+ sawAnimationEvent: () => boolean;
8
+ /** Input observed in [from - INPUT_WINDOW_MS, to]. */
9
+ inputBetween: (fromMs: number, toMs: number) => InputEvidence;
10
+ }
11
+ export interface TripwireOptions {
12
+ /**
13
+ * Called with each hit, on the frame it happened. `atMs` is
14
+ * `performance.now()`, absolute — the recorder makes it flight-relative,
15
+ * because a hit can land while no flight is open and must not be silently
16
+ * attributed to the previous one.
17
+ */
18
+ onHit: (hit: {
19
+ kind: TripwireHit["kind"];
20
+ detail: string;
21
+ atMs: number;
22
+ }) => void;
23
+ /** Called with the moment the first flemo animation of a flight started. */
24
+ onAnimationStart: (atMs: number) => void;
25
+ }
26
+ /**
27
+ * Wire the tripwires onto the document.
28
+ *
29
+ * Returns an inert handle where there is no document to wire onto, so a caller
30
+ * never has to branch on the environment.
31
+ */
32
+ export declare const attachTripwires: (options: TripwireOptions) => TripwireHandle;
33
+ export declare const relativeHit: (hit: {
34
+ kind: TripwireHit["kind"];
35
+ detail: string;
36
+ atMs: number;
37
+ }, t0Ms: number) => TripwireHit;
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** How a flight's motion was driven, judged from DOM signatures alone. */
2
- export type FlightDriver = "player" | "compiled" | "mixed" | "unknown";
2
+ export type FlightDriver = "inline" | "compiled" | "mixed" | "unknown";
3
3
  /** Navigation kind, from the transitional `data-flemo-status` value. */
4
4
  export type FlightKind = "PUSH" | "POP" | "REPLACE";
5
5
  /** A single moment on both clocks: monotonic performance.now + wall clock. */
@@ -53,11 +53,6 @@ export interface FrameSampleStats {
53
53
  /** Frames after every hold released — the phase the eye watches. */
54
54
  released: FramePhaseStats;
55
55
  }
56
- /** Stats over the transition player's own gap mirror (__flemoPlayerGaps). */
57
- export interface PlayerGapStats {
58
- maxMs: number;
59
- over30Count: number;
60
- }
61
56
  /** A PerformanceObserver("longtask") entry overlapping the flight window. */
62
57
  export interface LongTaskSpan {
63
58
  startMs: number;
@@ -71,8 +66,8 @@ export interface LongTaskSpan {
71
66
  * running flight paused the animation for ~250ms while rAF kept ticking at a
72
67
  * perfect 16.7ms — every timing metric clean, the screen frozen. The decisive
73
68
  * instrument was a pose encoder, so the recorder carries one: for a compiled
74
- * flight it reads the animation's own clock, for a player flight the inline
75
- * pose it writes. Neither forces a style flush.
69
+ * flight it reads the animation's own clock, for an inline-driven one the pose
70
+ * being written. Neither forces a style flush.
76
71
  */
77
72
  export interface MotionProgress {
78
73
  /** Frames sampled during the RELEASED (visible-motion) phase. */
@@ -94,6 +89,16 @@ export interface MotionProgress {
94
89
  * flights, always exactly 3 frames).
95
90
  */
96
91
  tailFrames: number;
92
+ /**
93
+ * When the first flemo keyframe actually STARTED, relative to t0 — reported
94
+ * by the browser's own `animationstart`, not sampled.
95
+ *
96
+ * The status flip and the first moving frame are different moments: a React
97
+ * commit, a style recalculation and a present sit between them, and on a
98
+ * phone that gap has measured 90-165ms while every other number stayed
99
+ * clean. Null means no flemo animation reported a start for this flight.
100
+ */
101
+ firstAnimationAtMs: number | null;
97
102
  }
98
103
  /**
99
104
  * Images inside the flight's participants. A still-loading <img> that
@@ -123,6 +128,100 @@ export interface ImageActivity {
123
128
  */
124
129
  completedUnheld: number;
125
130
  }
131
+ /**
132
+ * The shared elements on this flight, and whether they actually flew.
133
+ *
134
+ * WHY THIS IS ITS OWN SECTION. A morph that does not pair produces no error,
135
+ * no attribute, no animation and no console line: the element simply appears
136
+ * where it belongs and the navigation looks like one without a shared element
137
+ * at all. Four separate investigations began by hand-building a private tracer
138
+ * to answer the one question "did these two ends find each other", and each
139
+ * one was deleted when it was over. The runtime writes the pairing key onto
140
+ * every registered morph (`data-flemo-morph-id`) precisely so this section can
141
+ * answer it from outside, permanently.
142
+ */
143
+ export interface MorphActivity {
144
+ /** Registered morphs seen anywhere in the document as the flight opened. */
145
+ registered: number;
146
+ /**
147
+ * Pairing keys carried by ends in TWO different screens: a pair that had
148
+ * everything it needs to fly.
149
+ */
150
+ pairable: string[];
151
+ /** Pairing keys whose end was stamped with a flight role (it flew). */
152
+ flew: string[];
153
+ /**
154
+ * Pairable keys that never took a role. This is the morph-skip signature —
155
+ * the pair existed and the flight did not happen.
156
+ */
157
+ skipped: string[];
158
+ /** A screen was driven as a camera (`carry: "screen"`) on this flight. */
159
+ camera: boolean;
160
+ /** Ghosts (copies of the replaced element) seen during the flight. */
161
+ ghosts: number;
162
+ /**
163
+ * Morph elements still stamped with a role once the flight landed. A role
164
+ * outliving its flight is the stranded-participant class: it stays in the
165
+ * layer and poisons the NEXT pairing, which is how one interrupted swipe
166
+ * turned into every later pop losing its camera.
167
+ */
168
+ strandedRoles: number;
169
+ /** Stand-ins left in the layout at rest — a hole where the element belongs. */
170
+ strandedStandIns: number;
171
+ /** Ghosts left in the document at rest. */
172
+ strandedGhosts: number;
173
+ /**
174
+ * Morph keyframe rules left in the per-flight sheet at rest, over what the
175
+ * flight started with. One `<style>` element holds them all and outlives
176
+ * every flight, so the rules are the leak, not the element.
177
+ */
178
+ leakedSheetRules: number;
179
+ /**
180
+ * Pairing keys used by more than one end inside a SINGLE screen. Not a
181
+ * runtime failure: two ends under one screen are not a pair, so one of them
182
+ * can never fly. Reported because the symptom (an element that morphs only
183
+ * sometimes) reads exactly like a library defect.
184
+ */
185
+ duplicatedKeys: string[];
186
+ /** Elements left inside a flight layer at rest (the corpse class). */
187
+ layerResidue: number;
188
+ }
189
+ /**
190
+ * A tripwire hit: something the recorder was TOLD about rather than something
191
+ * it sampled.
192
+ *
193
+ * The distinction is the whole reason this exists. Three of this project's
194
+ * hardest defects lasted exactly one frame — a false `animationend` carrying
195
+ * `elapsedTime` 0, an `animationcancel` from a re-parent that let a negative
196
+ * delay overwrite the authored one, a ghost cut a frame before its fade — and
197
+ * a sampler that looks three times a second sees none of them. These are
198
+ * event listeners: they cost nothing while nothing happens, and they cannot
199
+ * miss the frame when it does.
200
+ */
201
+ export interface TripwireHit {
202
+ kind: "animation-cancel" | "zero-length-animation-end" | "hold-reassert" | "ghost-cut";
203
+ /** Offset from the flight's t0, in ms. */
204
+ atMs: number;
205
+ /** The animation or element involved, and what the hit means. */
206
+ detail: string;
207
+ }
208
+ /**
209
+ * What drove this navigation, as the browser reports it.
210
+ *
211
+ * `isTrusted` and `pointerType` are cheap and decisive. A build whose touch
212
+ * path was broken outright once passed every automated layer green because
213
+ * every probe drove it with a mouse, and synthetic dispatch never fires
214
+ * `pointerdown` at all — so a session that only ever saw untrusted or
215
+ * mouse-only input has not tested what a phone does, however clean it reads.
216
+ */
217
+ export interface InputEvidence {
218
+ /** Trusted pointer/click events observed shortly before the flight opened. */
219
+ trusted: number;
220
+ /** Untrusted (script-dispatched) ones. */
221
+ synthetic: number;
222
+ /** Distinct `pointerType` values seen ("touch", "mouse", "pen"). */
223
+ pointerTypes: string[];
224
+ }
126
225
  /** Post-landing residue audit, taken 2 rAF after the flight completed. */
127
226
  export interface LandingAudit {
128
227
  /**
@@ -152,6 +251,8 @@ export interface FlightRecord {
152
251
  id: string;
153
252
  /** data-flemo-router of the first participating screen, if stamped. */
154
253
  routerId?: string;
254
+ /** The comparison bucket armed when this flight ran (see `mark`). */
255
+ bucket?: string;
155
256
  kind: FlightKind;
156
257
  t0: FlightTimestamp;
157
258
  t1: FlightTimestamp;
@@ -164,8 +265,12 @@ export interface FlightRecord {
164
265
  motion: MotionProgress;
165
266
  /** Image load/hold activity inside the participants during the flight. */
166
267
  images: ImageActivity;
167
- /** Present only when the player's gap mirror grew during the flight. */
168
- playerGaps?: PlayerGapStats;
268
+ /** Shared elements: which paired, which flew, what they left behind. */
269
+ morphs: MorphActivity;
270
+ /** One-frame events the recorder was notified of rather than sampled. */
271
+ tripwires: TripwireHit[];
272
+ /** What drove the navigation (trusted finger, mouse, or a script). */
273
+ input: InputEvidence;
169
274
  /**
170
275
  * Long tasks intersecting the RELEASED phase (visible motion) — these
171
276
  * drive the anomaly rules.
@@ -191,8 +296,16 @@ export interface ObservationCapabilities {
191
296
  longTasks: boolean;
192
297
  /** Element.getAnimations available — compiled-tier detection is direct. */
193
298
  elementAnimations: boolean;
194
- /** window.__flemoPlayerGaps present (the player has driven >= 1 flight). */
195
- playerGapMirror: boolean;
299
+ /**
300
+ * A flemo-named CSS animation event reached the tripwires at least once.
301
+ *
302
+ * This is the instrument checking ITSELF. A probe that never fires reads
303
+ * exactly like a page with nothing to report, and a build whose probe was
304
+ * silently broken once passed every layer green. If flights were recorded
305
+ * and this is false, the animation channel saw nothing — treat every
306
+ * animation-derived field in this report as unmeasured, not as clean.
307
+ */
308
+ animationEvents: boolean;
196
309
  }
197
310
  export interface EnvironmentFingerprint {
198
311
  userAgent: string;
@@ -202,6 +315,8 @@ export interface EnvironmentFingerprint {
202
315
  platform: string;
203
316
  maxTouchPoints: number;
204
317
  devicePixelRatio: number;
318
+ /** navigator.hardwareConcurrency, for reading a contention number in scale. */
319
+ hardwareConcurrency: number;
205
320
  screen: {
206
321
  width: number;
207
322
  height: number;
@@ -217,6 +332,14 @@ export interface EnvironmentFingerprint {
217
332
  sampleCount: number;
218
333
  };
219
334
  reducedMotion: boolean;
335
+ /**
336
+ * Development-server globals found on `window` (HMR clients, framework dev
337
+ * hooks). A development build is a different program: unminified, double-
338
+ * invoking, hot-reload-instrumented. A verdict taken on one says nothing
339
+ * about what ships, and a whole day of "regression ladder" measurements
340
+ * once turned out to be measuring the build itself.
341
+ */
342
+ developmentHints: string[];
220
343
  /**
221
344
  * DevTools device-emulation signature: Blink + desktop platform + touch
222
345
  * points. Emulation composites the page to a scaled surface, so VISUAL
@@ -227,6 +350,22 @@ export interface EnvironmentFingerprint {
227
350
  emulationSuspected: boolean;
228
351
  observation: ObservationCapabilities;
229
352
  }
353
+ /** Whether one judging precondition held, as far as the page can tell. */
354
+ export type PreconditionStatus = "ok" | "violated" | "unknown";
355
+ /**
356
+ * One precondition of a motion verdict, and what the page could observe about
357
+ * it. `unknown` is a first-class answer: several of the traps that cost this
358
+ * project weeks are not visible from inside a page at all, and saying so is
359
+ * the honest report — an agent must then confirm them with the user rather
360
+ * than read silence as consent.
361
+ */
362
+ export interface Precondition {
363
+ id: string;
364
+ status: PreconditionStatus;
365
+ detail: string;
366
+ /** Numbers behind the verdict, when there are any. */
367
+ metrics?: Record<string, number>;
368
+ }
230
369
  export interface OverridesSection {
231
370
  /**
232
371
  * Every `flemo:*` storage key found (sessionStorage + localStorage),
@@ -238,13 +377,59 @@ export interface OverridesSection {
238
377
  /** Derived, prominent warnings — read these before trusting anything. */
239
378
  warnings: string[];
240
379
  }
380
+ /**
381
+ * One comparison bucket: every flight recorded while that label was armed.
382
+ *
383
+ * The A/B ladder is this project's standard move and it has been run by hand
384
+ * every time — navigate five times, read five numbers off a console, change
385
+ * one thing, repeat. Doing it in the recorder removes the two ways that went
386
+ * wrong: numbers copied out of order, and a "candidate fix" build that changed
387
+ * more than one thing at once and made the whole judgement void.
388
+ */
389
+ export interface BucketSummary {
390
+ bucket: string;
391
+ flights: number;
392
+ medianDurationMs: number;
393
+ medianReleasedGapMs: number;
394
+ worstReleasedGapMs: number;
395
+ longGapCount: number;
396
+ anomalyCount: number;
397
+ /** Flights whose motion stalled at least once (see MotionProgress). */
398
+ stalledFlights: number;
399
+ }
400
+ /**
401
+ * Flights carried over from before the last full page load.
402
+ *
403
+ * A development session reloads constantly — HMR, a rebuild, a hard refresh to
404
+ * clear state — and each reload used to take the trace with it, including the
405
+ * one flight the user had just seen go wrong. Kept apart from the live flights
406
+ * rather than merged: they came from a different page instance, possibly a
407
+ * different build.
408
+ */
409
+ export interface PreviousSession {
410
+ savedAt: string;
411
+ flights: FlightRecord[];
412
+ note: string;
413
+ }
241
414
  export interface FlemoReport {
242
415
  generatedAt: string;
243
416
  /** Report schema version (not the package version). */
244
417
  version: string;
418
+ /**
419
+ * The recorder's own reading of the session, most important first, in plain
420
+ * sentences. Read this before anything else: it says whether this session
421
+ * can be used as evidence at all, and then what it found.
422
+ */
423
+ verdict: string[];
245
424
  environment: EnvironmentFingerprint;
425
+ /** The observable half of the judging protocol, checked. */
426
+ preconditions: Precondition[];
246
427
  overrides: OverridesSection;
247
428
  flights: FlightRecord[];
429
+ /** Per-bucket summaries; empty unless `mark()` armed at least one. */
430
+ comparison: BucketSummary[];
431
+ /** Flights restored from the previous page instance, or null. */
432
+ previousSession: PreviousSession | null;
248
433
  /** Session-level findings (observation traps, active pins, stuck flights). */
249
434
  anomalies: string[];
250
435
  /**
@@ -255,9 +440,9 @@ export interface FlemoReport {
255
440
  blindSpots: string[];
256
441
  /**
257
442
  * Constant list of preconditions a motion verdict is only valid under (see
258
- * judging.ts). The report cannot verify them from inside the page — an
259
- * agent must confirm them with the user before trusting any judgement,
260
- * including a clean one.
443
+ * judging.ts). The ones the page CAN check appear in `preconditions`; these
444
+ * are the rest, which an agent must confirm with the user before trusting
445
+ * any judgement, including a clean one.
261
446
  */
262
447
  judgingProtocol: string[];
263
448
  }
@@ -266,10 +451,22 @@ export interface FlightRecorderOptions {
266
451
  maxFlights?: number;
267
452
  /** console.info a one-line summary per completed flight. Default false. */
268
453
  log?: boolean;
269
- /** Install window.flemo = { report, flights, detach }. Default true. */
454
+ /** Install window.flemo = { report, flights, mark, detach }. Default true. */
270
455
  installGlobal?: boolean;
456
+ /**
457
+ * Carry flights across a full page load through sessionStorage. Default
458
+ * true — a development session reloads constantly and the flight worth
459
+ * reading is usually the one before the reload. Written only while no
460
+ * flight is running.
461
+ */
462
+ persist?: boolean;
271
463
  }
272
464
  export interface FlightRecorderHandle {
273
465
  detach: () => void;
274
466
  report: () => FlemoReport;
467
+ /**
468
+ * Arm a comparison bucket. Every flight recorded from here on carries the
469
+ * label until it is changed; `null` clears it. Returns the label in force.
470
+ */
471
+ mark: (bucket: string | null) => string | null;
275
472
  }
@@ -0,0 +1,7 @@
1
+ import { FlightRecord, ObservationCapabilities, Precondition } from './types';
2
+ export interface VerdictInput {
3
+ preconditions: readonly Precondition[];
4
+ flights: readonly FlightRecord[];
5
+ observation: ObservationCapabilities;
6
+ }
7
+ export declare const deriveVerdict: (input: VerdictInput) => string[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flemo/devtools",
3
- "version": "0.4.0",
4
- "description": "Zero-config flight recorder for flemo screen transitions: captures per-flight driver routing, frame pacing, long tasks, landing residues, active debug overrides, and environment/observation-trap fingerprints into one JSON report readable by humans and coding agents.",
3
+ "version": "0.6.0",
4
+ "description": "Zero-config flight recorder, on-device readout and visual panel for flemo screen transitions: captures per-flight driver routing, frame pacing, long tasks, shared-element pairing, landing residues and judging preconditions into one JSON report readable by humans and coding agents.",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
@@ -34,13 +34,17 @@
34
34
  "homepage": "https://flemo.dev",
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
- "@flemo/core": "2.2.0",
37
+ "@flemo/core": "2.3.1",
38
38
  "@flemo/eslint-config": "0.0.0",
39
39
  "@flemo/tsconfig": "0.0.0",
40
+ "@testing-library/react": "^16.3.3",
40
41
  "@types/node": "^24.13.1",
42
+ "@types/react": "^19.2.18",
41
43
  "@vitest/coverage-v8": "^4.1.11",
42
44
  "eslint": "^9.39.5",
43
45
  "jsdom": "^29.1.1",
46
+ "react": "^19.2.8",
47
+ "react-dom": "^19.2.8",
44
48
  "typescript": "^6.0.3",
45
49
  "vite": "^8.2.2",
46
50
  "vite-plugin-dts": "^5.0.3",
@@ -56,16 +60,30 @@
56
60
  "production": "./dist/noop.mjs",
57
61
  "default": "./dist/index.mjs"
58
62
  },
63
+ "./react": {
64
+ "types": "./dist/react.d.ts",
65
+ "development": "./dist/react.mjs",
66
+ "production": "./dist/reactNoop.mjs",
67
+ "default": "./dist/react.mjs"
68
+ },
59
69
  "./force": {
60
70
  "types": "./dist/index.d.ts",
61
71
  "default": "./dist/index.mjs"
62
72
  },
63
73
  "./package.json": "./package.json"
64
74
  },
75
+ "peerDependencies": {
76
+ "react": "^19.2.8"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "react": {
80
+ "optional": true
81
+ }
82
+ },
65
83
  "scripts": {
66
- "build": "vite build && DEVTOOLS_ENTRY=noop vite build",
67
- "watch": "vite build --watch",
68
- "dev": "vite build --watch",
84
+ "build": "rm -rf dist && vite build && DEVTOOLS_ENTRY=noop vite build && DEVTOOLS_ENTRY=react vite build && DEVTOOLS_ENTRY=reactNoop vite build",
85
+ "watch": "vite build --watch & DEVTOOLS_ENTRY=noop vite build --watch & DEVTOOLS_ENTRY=react vite build --watch & DEVTOOLS_ENTRY=reactNoop vite build --watch & wait",
86
+ "dev": "vite build --watch & DEVTOOLS_ENTRY=noop vite build --watch & DEVTOOLS_ENTRY=react vite build --watch & DEVTOOLS_ENTRY=reactNoop vite build --watch & wait",
69
87
  "lint": "eslint \"**/*.{js,mjs,ts,jsx,tsx,mts}\"",
70
88
  "typecheck": "tsc --noEmit",
71
89
  "test": "vitest run",