@reactra/devtools 0.1.0-alpha.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -0
  3. package/dist/ValueTree.d.ts +38 -0
  4. package/dist/ValueTree.d.ts.map +1 -0
  5. package/dist/ValueTree.js +178 -0
  6. package/dist/ValueTree.js.map +1 -0
  7. package/dist/helpers.d.ts +59 -0
  8. package/dist/helpers.d.ts.map +1 -0
  9. package/dist/helpers.js +110 -0
  10. package/dist/helpers.js.map +1 -0
  11. package/dist/hookRecorder.d.ts +72 -0
  12. package/dist/hookRecorder.d.ts.map +1 -0
  13. package/dist/hookRecorder.js +142 -0
  14. package/dist/hookRecorder.js.map +1 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +16 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/invoke.d.ts +35 -0
  20. package/dist/invoke.d.ts.map +1 -0
  21. package/dist/invoke.js +67 -0
  22. package/dist/invoke.js.map +1 -0
  23. package/dist/model.d.ts +99 -0
  24. package/dist/model.d.ts.map +1 -0
  25. package/dist/model.js +160 -0
  26. package/dist/model.js.map +1 -0
  27. package/dist/panel.d.ts +17 -0
  28. package/dist/panel.d.ts.map +1 -0
  29. package/dist/panel.js +438 -0
  30. package/dist/panel.js.map +1 -0
  31. package/dist/routerWatcher.d.ts +47 -0
  32. package/dist/routerWatcher.d.ts.map +1 -0
  33. package/dist/routerWatcher.js +51 -0
  34. package/dist/routerWatcher.js.map +1 -0
  35. package/dist/serialize.d.ts +38 -0
  36. package/dist/serialize.d.ts.map +1 -0
  37. package/dist/serialize.js +217 -0
  38. package/dist/serialize.js.map +1 -0
  39. package/dist/storeRecorder.d.ts +25 -0
  40. package/dist/storeRecorder.d.ts.map +1 -0
  41. package/dist/storeRecorder.js +94 -0
  42. package/dist/storeRecorder.js.map +1 -0
  43. package/dist/storeWatcher.d.ts +26 -0
  44. package/dist/storeWatcher.d.ts.map +1 -0
  45. package/dist/storeWatcher.js +24 -0
  46. package/dist/storeWatcher.js.map +1 -0
  47. package/dist/styles.d.ts +5 -0
  48. package/dist/styles.d.ts.map +1 -0
  49. package/dist/styles.js +288 -0
  50. package/dist/styles.js.map +1 -0
  51. package/dist/tabs/router.d.ts +10 -0
  52. package/dist/tabs/router.d.ts.map +1 -0
  53. package/dist/tabs/router.js +48 -0
  54. package/dist/tabs/router.js.map +1 -0
  55. package/dist/tabs/stores.d.ts +14 -0
  56. package/dist/tabs/stores.d.ts.map +1 -0
  57. package/dist/tabs/stores.js +167 -0
  58. package/dist/tabs/stores.js.map +1 -0
  59. package/dist/tabs/timeTravel.d.ts +46 -0
  60. package/dist/tabs/timeTravel.d.ts.map +1 -0
  61. package/dist/tabs/timeTravel.js +322 -0
  62. package/dist/tabs/timeTravel.js.map +1 -0
  63. package/package.json +43 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Akhil Shastri and the Reactra contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @reactra/devtools
2
+
3
+ The Reactra **developer panel** — a drop-in devtools drawer for any Reactra
4
+ app. Mount it once in your root layout behind a dev guard:
5
+
6
+ ```tsx
7
+ import { ReactraDevtools } from "@reactra/devtools"
8
+
9
+ // in _layout.tsx (or your root wrapper):
10
+ {import.meta.env.DEV && <ReactraDevtools defaultOpen />}
11
+ ```
12
+
13
+ It is a **UI companion** package (Runtime spec §2 / §3): hand-mounted, never
14
+ imported by compiler-emitted code — an app that doesn't mount it ships none
15
+ of it.
16
+
17
+ ## What you get
18
+
19
+ - **Components** — live hook-slot snapshot per component: state, derived,
20
+ service, action, resource binding groups; changed-key flash on each commit;
21
+ `[▶ run]` rows to invoke actions directly (0-arg and multi-arg JSON);
22
+ inline DVT004 error on bad JSON or action throw.
23
+ - **Stores** — all registered stores with kind chips (export / session / route);
24
+ live snapshots that update in real time; idle/live lifecycle badge for route
25
+ stores; `preserved` badge on Store §6 preserved-state fields; `[▶ run]`
26
+ rows for store actions.
27
+ - **Router** — current-route card (id / pattern / pathname / raw + coerced
28
+ params + query); full route manifest with highlight on the active route;
29
+ timestamped nav log of every transition.
30
+ - **⏪ Time Travel** — scrub the app's passive commit history; REPLAYING banner
31
+ while traveling; ⏮/⏵/▶ transport; `⟲ live` badge on components that
32
+ support live re-drive, `(view)` on commit-only components; ⏎ live exits
33
+ replay mode and re-arms recording.
34
+
35
+ To unlock **live re-drive** on a component, add `uses replayable` to it
36
+ (Replay spec §4). Without it the Time Travel tab is view-only (DVT003 teaching
37
+ chip shows when no replayable component is mounted on the current route).
38
+
39
+ ## Props (`ReactraDevtoolsProps`)
40
+
41
+ | Prop | Type | Default | Purpose |
42
+ |---|---|---|---|
43
+ | `defaultOpen` | `boolean` | `false` | Open the drawer on mount (collapsed pill otherwise) |
44
+ | `historyLimit` | `number` | `5000` | Passive-history ring capacity in commits; DVT005 alert fires on eviction |
45
+ | `record` | `boolean` | `true` | Arm the passive recorder at mount; set `false` to start paused |
46
+
47
+ ## DVT001 degradation
48
+
49
+ If `globalThis.__REACTRA_TEST__` is already installed by another consumer
50
+ (e.g. `@reactra/testing`'s `renderReactra`) when the panel mounts, the panel
51
+ degrades gracefully: the **Components** tab and passive **Time Travel** ring
52
+ are disabled (DVT001 warning shown); the **Stores** and **Router** tabs
53
+ continue to work normally. The occupant is never overwritten.
54
+
55
+ ## Hybrid Time Travel
56
+
57
+ The Time Travel tab uses the app's passive commit ring (recorded via the
58
+ `__REACTRA_TEST__` hook) as its timeline source. Live re-drive is capability-
59
+ based: the panel calls `applyReplayState` on each scrub stop, and components
60
+ that carry `uses replayable` apply the state to their live React hooks (⟲ live
61
+ badge); components that don't are shown as `(view)`. Both modes coexist in the
62
+ same scrub session — you can have one replayable page component and several
63
+ view-only child components.
64
+
65
+ See `examples/devtools-tour` for a full tour of all four tabs.
66
+
67
+ ## Spec references
68
+
69
+ - Devtools spec (`reactra-devtools-spec.md`) — DVT001–DVT005, four-tab design,
70
+ popup-out, hybrid time travel
71
+ - Runtime spec §2 (UI-companion tier) / §3 (hook-slot data sources)
72
+
73
+ ## Node-portability
74
+
75
+ No JSX in source (written against `React.createElement`), no `.css` imports
76
+ (one `<style>` tag injected behind a `typeof document` guard), browser globals
77
+ only inside handlers. `import("@reactra/devtools")` works under plain
78
+ Node ≥ 22.18.
@@ -0,0 +1,38 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Serialized } from "./serialize.ts";
3
+ /**
4
+ * Props for a single value-tree node.
5
+ * `changed` is passed through from the parent to highlight top-level keys.
6
+ */
7
+ export interface ValueNodeProps {
8
+ /** The label/key name for this node (undefined for top-level roots). */
9
+ label?: string;
10
+ /** The serialized value to render. */
11
+ value: Serialized;
12
+ /** Whether this node's value has changed since the last render. */
13
+ changed?: boolean;
14
+ /** Nesting depth — used to limit initial expansion. */
15
+ depth?: number;
16
+ }
17
+ /** A single tree node — may recurse for objects/arrays. */
18
+ export declare const ValueNode: (props: ValueNodeProps) => ReactNode;
19
+ /**
20
+ * Render a Record<string, Serialized> as a value tree — the top-level
21
+ * replacement for the flat key-value list in Components + Stores + Router
22
+ * + Time Travel cards.
23
+ *
24
+ * `changed` is a Set of top-level key names that changed since the last
25
+ * render (drives the yellow highlight / fade animation).
26
+ */
27
+ export interface ValueTreeProps {
28
+ /** The data to display (output of serialize.ts). */
29
+ data: Record<string, Serialized>;
30
+ /** Keys that changed since the last render (optional). */
31
+ changed?: ReadonlySet<string>;
32
+ }
33
+ /**
34
+ * Value tree root — renders each key as a collapsible ValueNode. Replaces
35
+ * the flat `JSON.stringify(v)` row list in all four tabs.
36
+ */
37
+ export declare const ValueTree: (props: ValueTreeProps) => ReactNode;
38
+ //# sourceMappingURL=ValueTree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ValueTree.d.ts","sourceRoot":"","sources":["../src/ValueTree.ts"],"names":[],"mappings":"AAWA,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACpE,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,gBAAgB,CAAA;AA2B1E;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,KAAK,EAAE,UAAU,CAAA;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AA6BD,2DAA2D;AAC3D,eAAO,MAAM,SAAS,GAAI,OAAO,cAAc,KAAG,SA2HjD,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAChC,0DAA0D;IAC1D,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC9B;AAED;;;GAGG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,cAAc,KAAG,SAiBjD,CAAA"}
@@ -0,0 +1,178 @@
1
+ // @reactra/devtools — recursive value-tree renderer (T1.2).
2
+ //
3
+ // Owner: reactra-devtools-spec.md §4 (binding cards). Renders a serialized
4
+ // value (from serialize.ts) as collapsible tree nodes: objects → `{N keys}`
5
+ // summary, arrays → `[N items]`, primitives inline. The tree replaces
6
+ // the flat JSON.stringify(v) rendering in Components / Stores / Router /
7
+ // Time Travel cards.
8
+ //
9
+ // UI-companion tier rules: createElement ONLY (no JSX); browser globals
10
+ // (clipboard) only inside handlers.
11
+ import { createElement as h, useState } from "react";
12
+ /** Whether a serialized value is a resource-status projection. */
13
+ const isResourceProj = (v) => v !== null &&
14
+ typeof v === "object" &&
15
+ !Array.isArray(v) &&
16
+ typeof v.status === "string" &&
17
+ ["pending", "resolved", "error"].includes(v.status);
18
+ /** Determine if a value should render as a collapsible container. */
19
+ const isExpandable = (v) => {
20
+ if (v === null || v === undefined)
21
+ return false;
22
+ if (isResourceProj(v))
23
+ return v.status === "resolved" && "value" in v;
24
+ if (Array.isArray(v))
25
+ return v.length > 0;
26
+ if (typeof v === "object")
27
+ return Object.keys(v).length > 0;
28
+ return false;
29
+ };
30
+ /** Inline summary for collapsed containers. */
31
+ const summary = (v) => {
32
+ if (Array.isArray(v))
33
+ return `[${v.length}]`;
34
+ if (isResourceProj(v))
35
+ return `(${v.status})`;
36
+ if (typeof v === "object" && v !== null)
37
+ return `{${Object.keys(v).length}}`;
38
+ return String(v);
39
+ };
40
+ /** The copy-to-clipboard affordance (UX#6). */
41
+ const CopyButton = ({ value }) => {
42
+ const [copied, setCopied] = useState(false);
43
+ const copy = () => {
44
+ // navigator.clipboard is only available in secure contexts + browser.
45
+ // Guarded per plan constraint: browser globals only in handlers.
46
+ if (typeof navigator === "undefined" || !navigator.clipboard)
47
+ return;
48
+ const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
49
+ navigator.clipboard.writeText(text).then(() => {
50
+ setCopied(true);
51
+ setTimeout(() => setCopied(false), 1200);
52
+ }).catch(() => { });
53
+ };
54
+ return h("button", {
55
+ className: "rdt-copy",
56
+ onClick: copy,
57
+ title: "Copy value",
58
+ "aria-label": "Copy value to clipboard",
59
+ }, copied ? "✓" : "⎘");
60
+ };
61
+ /** A single tree node — may recurse for objects/arrays. */
62
+ export const ValueNode = (props) => {
63
+ const { label, value, changed = false, depth = 0 } = props;
64
+ // Auto-expand the first level; deeper levels start collapsed.
65
+ const [expanded, setExpanded] = useState(depth < 1);
66
+ const expandable = isExpandable(value);
67
+ // ---- Primitive / leaf rendering ----------------------------------------
68
+ if (!expandable) {
69
+ let valClass = "rdt-tree-val";
70
+ let displayVal;
71
+ if (value === null) {
72
+ valClass += " rdt-tree-val--null";
73
+ displayVal = "null";
74
+ }
75
+ else if (value === undefined) {
76
+ valClass += " rdt-tree-val--null";
77
+ displayVal = "undefined";
78
+ }
79
+ else if (typeof value === "string") {
80
+ if (value === "[unserializable]") {
81
+ valClass += " rdt-tree-val--sentinel";
82
+ displayVal = value;
83
+ }
84
+ else {
85
+ valClass += " rdt-tree-val--str";
86
+ displayVal = JSON.stringify(value);
87
+ }
88
+ }
89
+ else if (typeof value === "number") {
90
+ valClass += " rdt-tree-val--num";
91
+ displayVal = String(value);
92
+ }
93
+ else if (typeof value === "boolean") {
94
+ valClass += " rdt-tree-val--bool";
95
+ displayVal = String(value);
96
+ }
97
+ else if (isResourceProj(value)) {
98
+ valClass += " rdt-tree-val--resource";
99
+ displayVal = `(${value.status})`;
100
+ }
101
+ else if (Array.isArray(value)) {
102
+ // Empty array (non-empty arrays are expandable) — render `[]`, not "".
103
+ displayVal = "[]";
104
+ }
105
+ else if (typeof value === "object" && value !== null) {
106
+ // Empty object (non-empty objects are expandable) — render `{}`, not
107
+ // String({}) === "[object Object]".
108
+ displayVal = "{}";
109
+ }
110
+ else {
111
+ displayVal = String(value);
112
+ }
113
+ const changedClass = changed ? " rdt-v--changed" : "";
114
+ return h("div", { className: "rdt-tree-node" }, label !== undefined && h("span", { className: "rdt-tree-key" }, `${label}: `), h("span", { className: `${valClass}${changedClass}` }, displayVal), h(CopyButton, { value }));
115
+ }
116
+ // ---- Expandable container rendering ------------------------------------
117
+ const toggleLabel = expanded ? "▾" : "▸";
118
+ // Build children based on value type.
119
+ const children = [];
120
+ if (Array.isArray(value)) {
121
+ ;
122
+ value.forEach((item, i) => {
123
+ children.push(h(ValueNode, {
124
+ key: String(i),
125
+ label: String(i),
126
+ value: item,
127
+ depth: depth + 1,
128
+ }));
129
+ });
130
+ }
131
+ else if (isResourceProj(value) && value.status === "resolved") {
132
+ const rp = value;
133
+ if ("value" in rp) {
134
+ children.push(h(ValueNode, {
135
+ key: "value",
136
+ label: "value",
137
+ value: rp.value,
138
+ depth: depth + 1,
139
+ }));
140
+ }
141
+ }
142
+ else if (typeof value === "object" && value !== null) {
143
+ for (const [k, v] of Object.entries(value)) {
144
+ children.push(h(ValueNode, {
145
+ key: k,
146
+ label: k,
147
+ value: v,
148
+ depth: depth + 1,
149
+ }));
150
+ }
151
+ }
152
+ const changedClass = changed ? " rdt-v--changed" : "";
153
+ const sumText = summary(value);
154
+ return h("div", { className: "rdt-tree-node" }, h("span", { style: { display: "inline-flex", alignItems: "center", gap: 2 } }, h("button", {
155
+ className: "rdt-tree-toggle",
156
+ onClick: () => setExpanded((e) => !e),
157
+ "aria-expanded": expanded,
158
+ "aria-label": expanded ? "Collapse" : "Expand",
159
+ }, toggleLabel), label !== undefined && h("span", { className: "rdt-tree-key" }, `${label}: `), h("span", { className: `rdt-tree-val${changedClass}` }, sumText), h(CopyButton, { value })), expanded && h("div", { className: "rdt-tree-children" }, ...children));
160
+ };
161
+ /**
162
+ * Value tree root — renders each key as a collapsible ValueNode. Replaces
163
+ * the flat `JSON.stringify(v)` row list in all four tabs.
164
+ */
165
+ export const ValueTree = (props) => {
166
+ const { data, changed = new Set() } = props;
167
+ const entries = Object.entries(data);
168
+ if (entries.length === 0)
169
+ return null;
170
+ return h("div", { className: "rdt-tree" }, ...entries.map(([k, v]) => h(ValueNode, {
171
+ key: k,
172
+ label: k,
173
+ value: v,
174
+ changed: changed.has(k),
175
+ depth: 0,
176
+ })));
177
+ };
178
+ //# sourceMappingURL=ValueTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ValueTree.js","sourceRoot":"","sources":["../src/ValueTree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import type { SessionBundle } from "@reactra/behaviours/replayable";
2
+ /**
3
+ * The data-valued projection of a hook bindings object (Devtools §7):
4
+ * function values are dropped (actions/setters aren't state). Values are
5
+ * depth/size-capped (serialize.ts) and resource handles emit a
6
+ * status-projection (DVT-LIM-04 resolution). One bad binding never poisons
7
+ * a commit — unserializable values become the `"[unserializable]"` sentinel.
8
+ */
9
+ export declare const dataBindings: (bindings: Record<string, unknown>) => Record<string, unknown>;
10
+ /** Names of the function-valued bindings — the §4 `▶ run` candidates. */
11
+ export declare const callableNames: (bindings: Record<string, unknown>) => string[];
12
+ /**
13
+ * Grouped bindings — separates data members from callable members (T1.3).
14
+ * Preserves the Testing §2 normative order within each bucket:
15
+ * data: props, state, derived, behaviour (non-callable by nature), resource
16
+ * actions: action functions
17
+ *
18
+ * The split is structural: function-valued keys go to `actions`, everything
19
+ * else to `data`. This matches the §4 intent (data section above actions).
20
+ */
21
+ export declare const groupBindings: (bindings: Record<string, unknown>) => {
22
+ data: Record<string, unknown>;
23
+ actions: Record<string, unknown>;
24
+ };
25
+ /**
26
+ * RELATIVE staleness heuristic (DVT-LIM-01; T2.2 re-disposition): an entry
27
+ * is stale ONLY if it missed the latest commit wave while other components
28
+ * committed in that wave. This prevents static pages (where nothing commits)
29
+ * from incorrectly flagging everything as stale.
30
+ *
31
+ * `lastSeen` — wall-clock time of the component's last commit.
32
+ * `latestWaveTime` — wall-clock time of the most recent commit across ALL
33
+ * tracked components (the "wave front"). Pass 0 / undefined to disable
34
+ * relative staleness (nothing is stale when no new commits arrived).
35
+ * `thresholdMs` — a component is stale if it's older than `latestWaveTime`
36
+ * by at least this many ms AND another component committed recently (default 2000ms).
37
+ *
38
+ * Back-compat: the two-argument form `isStale(lastSeen, now)` still works —
39
+ * it passes `now` as `latestWaveTime` so the absolute-threshold behavior is
40
+ * preserved for callers that haven't migrated yet.
41
+ */
42
+ export declare const isStale: (lastSeen: number, latestWaveTime: number, thresholdMs?: number) => boolean;
43
+ /**
44
+ * Parse the action-invoke args field (Devtools §4): empty → no args; a JSON
45
+ * array → the args spread. Anything else is a user error surfaced inline —
46
+ * never thrown (the DVT004 path covers the call itself).
47
+ */
48
+ export declare const parseInvokeArgs: (text: string) => {
49
+ args: unknown[];
50
+ } | {
51
+ error: string;
52
+ };
53
+ /**
54
+ * Wrap a passive-history event ring into a Replay §3 `SessionBundle` —
55
+ * `"2.0"` / `"state-snapshot"`, snapshots + mount markers only (Devtools §7;
56
+ * format cited, not extended). `null` when nothing was recorded.
57
+ */
58
+ export declare const passiveBundle: (events: ReadonlyArray<SessionBundle["events"][number]>, sessionId?: string) => SessionBundle | null;
59
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAGnE;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAIxB,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,aAAa,GAAI,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,EACA,CAAA;AAExE;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC;IAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAWnE,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,MAAM,EAChB,gBAAgB,MAAM,EACtB,oBAAkB,KACjB,OAOF,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,MAAM,MAAM,KACX;IAAE,IAAI,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAUvC,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,aAAa,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,EACtD,kBAA8B,KAC7B,aAAa,GAAG,IAclB,CAAA"}
@@ -0,0 +1,110 @@
1
+ // @reactra/devtools — pure panel logic (no React, no DOM).
2
+ //
3
+ // Owner: reactra-devtools-spec.md (internal tier — exported for tests).
4
+ // The recorded structures are Replay spec §3 types — consumed via
5
+ // `@reactra/behaviours/replayable`, never redefined or extended
6
+ // (architect condition 5).
7
+ import { serializeBindings } from "./serialize.js";
8
+ /**
9
+ * The data-valued projection of a hook bindings object (Devtools §7):
10
+ * function values are dropped (actions/setters aren't state). Values are
11
+ * depth/size-capped (serialize.ts) and resource handles emit a
12
+ * status-projection (DVT-LIM-04 resolution). One bad binding never poisons
13
+ * a commit — unserializable values become the `"[unserializable]"` sentinel.
14
+ */
15
+ export const dataBindings = (bindings) => {
16
+ // Delegate to the shared serializer; the return type is Record<string,
17
+ // Serialized> which is structurally assignable to Record<string, unknown>.
18
+ return serializeBindings(bindings);
19
+ };
20
+ /** Names of the function-valued bindings — the §4 `▶ run` candidates. */
21
+ export const callableNames = (bindings) => Object.keys(bindings).filter((k) => typeof bindings[k] === "function");
22
+ /**
23
+ * Grouped bindings — separates data members from callable members (T1.3).
24
+ * Preserves the Testing §2 normative order within each bucket:
25
+ * data: props, state, derived, behaviour (non-callable by nature), resource
26
+ * actions: action functions
27
+ *
28
+ * The split is structural: function-valued keys go to `actions`, everything
29
+ * else to `data`. This matches the §4 intent (data section above actions).
30
+ */
31
+ export const groupBindings = (bindings) => {
32
+ const data = {};
33
+ const actions = {};
34
+ for (const [k, v] of Object.entries(bindings)) {
35
+ if (typeof v === "function") {
36
+ actions[k] = v;
37
+ }
38
+ else {
39
+ data[k] = v;
40
+ }
41
+ }
42
+ return { data, actions };
43
+ };
44
+ /**
45
+ * RELATIVE staleness heuristic (DVT-LIM-01; T2.2 re-disposition): an entry
46
+ * is stale ONLY if it missed the latest commit wave while other components
47
+ * committed in that wave. This prevents static pages (where nothing commits)
48
+ * from incorrectly flagging everything as stale.
49
+ *
50
+ * `lastSeen` — wall-clock time of the component's last commit.
51
+ * `latestWaveTime` — wall-clock time of the most recent commit across ALL
52
+ * tracked components (the "wave front"). Pass 0 / undefined to disable
53
+ * relative staleness (nothing is stale when no new commits arrived).
54
+ * `thresholdMs` — a component is stale if it's older than `latestWaveTime`
55
+ * by at least this many ms AND another component committed recently (default 2000ms).
56
+ *
57
+ * Back-compat: the two-argument form `isStale(lastSeen, now)` still works —
58
+ * it passes `now` as `latestWaveTime` so the absolute-threshold behavior is
59
+ * preserved for callers that haven't migrated yet.
60
+ */
61
+ export const isStale = (lastSeen, latestWaveTime, thresholdMs = 2000) => {
62
+ // If nothing has committed recently (latestWaveTime is 0 or old), nothing
63
+ // is stale — a static page should not dim all components.
64
+ const waveAge = Date.now() - latestWaveTime;
65
+ if (waveAge > thresholdMs)
66
+ return false;
67
+ // Relative check: stale if this component missed the latest wave.
68
+ return latestWaveTime - lastSeen > thresholdMs;
69
+ };
70
+ /**
71
+ * Parse the action-invoke args field (Devtools §4): empty → no args; a JSON
72
+ * array → the args spread. Anything else is a user error surfaced inline —
73
+ * never thrown (the DVT004 path covers the call itself).
74
+ */
75
+ export const parseInvokeArgs = (text) => {
76
+ const trimmed = text.trim();
77
+ if (trimmed === "")
78
+ return { args: [] };
79
+ try {
80
+ const parsed = JSON.parse(trimmed);
81
+ if (!Array.isArray(parsed))
82
+ return { error: "args must be a JSON array, e.g. [\"x\", 1]" };
83
+ return { args: parsed };
84
+ }
85
+ catch (err) {
86
+ return { error: `invalid JSON: ${String(err)}` };
87
+ }
88
+ };
89
+ /**
90
+ * Wrap a passive-history event ring into a Replay §3 `SessionBundle` —
91
+ * `"2.0"` / `"state-snapshot"`, snapshots + mount markers only (Devtools §7;
92
+ * format cited, not extended). `null` when nothing was recorded.
93
+ */
94
+ export const passiveBundle = (events, sessionId = "devtools-passive") => {
95
+ if (events.length === 0)
96
+ return null;
97
+ const startTime = events[0].timestamp;
98
+ const endTime = events[events.length - 1].timestamp;
99
+ const components = [...new Set(events.map((e) => e.componentId.split("#")[0] ?? e.componentId))];
100
+ return {
101
+ version: "2.0",
102
+ mode: "state-snapshot",
103
+ startTime,
104
+ duration: endTime - startTime,
105
+ sessionId,
106
+ components,
107
+ events: [...events],
108
+ };
109
+ };
110
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ import type { ReplayEvent } from "@reactra/behaviours/replayable";
2
+ /** The Testing §2 hook interface (structural — cited, not re-owned). */
3
+ export interface ReactraTestHook {
4
+ update(componentName: string, bindings: Record<string, unknown>): void;
5
+ }
6
+ /** Latest-commit info per component name (Components tab rows). */
7
+ export interface CommitInfo {
8
+ /** The raw latest bindings — function values included (action invoke). */
9
+ bindings: Record<string, unknown>;
10
+ /** Wall-clock time of the last commit (staleness dimming, DVT-LIM-01). */
11
+ lastSeen: number;
12
+ /** Commits observed since the recorder installed. */
13
+ commits: number;
14
+ }
15
+ /** What `install()` found in the global slot. */
16
+ export type InstallResult = "installed" | "occupied";
17
+ export interface HookRecorderOptions {
18
+ /** Passive-history ring capacity in commits (Devtools §2; default 5000). */
19
+ historyLimit?: number;
20
+ /** Clock injection for tests. */
21
+ now?: () => number;
22
+ /** Global object carrying the `__REACTRA_TEST__` slot (tests pass a stub). */
23
+ globalObject?: Record<string, unknown>;
24
+ }
25
+ /** The recorder handle — see `createHookRecorder`. */
26
+ export interface HookRecorder {
27
+ /**
28
+ * Install into the `__REACTRA_TEST__` slot. `"occupied"` = DVT001: the
29
+ * occupant is NEVER overwritten or wrapped; the caller degrades the
30
+ * hook-dependent surfaces (Devtools §3).
31
+ */
32
+ install(): InstallResult;
33
+ /** Clear the slot — only if it still holds this recorder's own hook. */
34
+ uninstall(): void;
35
+ /** Subscribe to commit notifications (panel re-render). */
36
+ subscribe(fn: () => void): () => void;
37
+ /** Latest commit per component name, first-seen order. */
38
+ components(): ReadonlyMap<string, CommitInfo>;
39
+ /**
40
+ * Record a synthetic snapshot into the ring under a caller-chosen
41
+ * `componentId` WITHOUT adding it to the Components-tab map. Used to fold
42
+ * non-component sources (e.g. route changes — `"Route#1"`) into the same
43
+ * passive time-travel timeline. Respects the ⏺ arm state and eviction, and
44
+ * notifies subscribers. The `componentId` should follow the `Name#N`
45
+ * convention (Replay §4.3) so the Time Travel cards strip it consistently.
46
+ */
47
+ recordSnapshot(componentId: string, state: Record<string, unknown>): void;
48
+ /**
49
+ * The RAW (un-serialized) store snapshot folded under `componentId` at or
50
+ * before `atTime`, or undefined if none. Stores re-drive from this — NOT from
51
+ * the ring's `state`, which is depth/size-capped + type-projected by the
52
+ * display serializer (a `Map`/`Date`/`>50`-key value would be written back as
53
+ * a lossy string/truncation, corrupting the live store). Only `store:` ids are
54
+ * stashed (re-drive targets; components self-heal on next commit).
55
+ */
56
+ rawStoreStateAt(componentId: string, atTime: number): Record<string, unknown> | undefined;
57
+ /** Arm / disarm the passive ring (the ⏺ toggle). */
58
+ setRecording(on: boolean): void;
59
+ recording(): boolean;
60
+ /** The passive ring — Replay §3 events, oldest first. */
61
+ events(): readonly ReplayEvent[];
62
+ /** True once eviction has occurred (the DVT005 one-time notice). */
63
+ evicted(): boolean;
64
+ /** Drop the ring + the eviction flag (a fresh ⏺ arm). */
65
+ clearHistory(): void;
66
+ }
67
+ /**
68
+ * Create the devtools hook recorder. Pure logic — no DOM, no React — so the
69
+ * DT-01…03 scenarios run renderer-free.
70
+ */
71
+ export declare const createHookRecorder: (opts?: HookRecorderOptions) => HookRecorder;
72
+ //# sourceMappingURL=hookRecorder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hookRecorder.d.ts","sourceRoot":"","sources":["../src/hookRecorder.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAA;AAGjE,wEAAwE;AACxE,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACvE;AAED,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,UAAU,CAAA;AAEpD,MAAM,WAAW,mBAAmB;IAClC,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACvC;AAED,sDAAsD;AACtD,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,OAAO,IAAI,aAAa,CAAA;IACxB,wEAAwE;IACxE,SAAS,IAAI,IAAI,CAAA;IACjB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;IACrC,0DAA0D;IAC1D,UAAU,IAAI,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC7C;;;;;;;OAOG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACzE;;;;;;;OAOG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACzF,oDAAoD;IACpD,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/B,SAAS,IAAI,OAAO,CAAA;IACpB,yDAAyD;IACzD,MAAM,IAAI,SAAS,WAAW,EAAE,CAAA;IAChC,oEAAoE;IACpE,OAAO,IAAI,OAAO,CAAA;IAClB,yDAAyD;IACzD,YAAY,IAAI,IAAI,CAAA;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,OAAM,mBAAwB,KAAG,YA+HnE,CAAA"}