@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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/ValueTree.d.ts +38 -0
- package/dist/ValueTree.d.ts.map +1 -0
- package/dist/ValueTree.js +178 -0
- package/dist/ValueTree.js.map +1 -0
- package/dist/helpers.d.ts +59 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +110 -0
- package/dist/helpers.js.map +1 -0
- package/dist/hookRecorder.d.ts +72 -0
- package/dist/hookRecorder.d.ts.map +1 -0
- package/dist/hookRecorder.js +142 -0
- package/dist/hookRecorder.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/invoke.d.ts +35 -0
- package/dist/invoke.d.ts.map +1 -0
- package/dist/invoke.js +67 -0
- package/dist/invoke.js.map +1 -0
- package/dist/model.d.ts +99 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +160 -0
- package/dist/model.js.map +1 -0
- package/dist/panel.d.ts +17 -0
- package/dist/panel.d.ts.map +1 -0
- package/dist/panel.js +438 -0
- package/dist/panel.js.map +1 -0
- package/dist/routerWatcher.d.ts +47 -0
- package/dist/routerWatcher.d.ts.map +1 -0
- package/dist/routerWatcher.js +51 -0
- package/dist/routerWatcher.js.map +1 -0
- package/dist/serialize.d.ts +38 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +217 -0
- package/dist/serialize.js.map +1 -0
- package/dist/storeRecorder.d.ts +25 -0
- package/dist/storeRecorder.d.ts.map +1 -0
- package/dist/storeRecorder.js +94 -0
- package/dist/storeRecorder.js.map +1 -0
- package/dist/storeWatcher.d.ts +26 -0
- package/dist/storeWatcher.d.ts.map +1 -0
- package/dist/storeWatcher.js +24 -0
- package/dist/storeWatcher.js.map +1 -0
- package/dist/styles.d.ts +5 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +288 -0
- package/dist/styles.js.map +1 -0
- package/dist/tabs/router.d.ts +10 -0
- package/dist/tabs/router.d.ts.map +1 -0
- package/dist/tabs/router.js +48 -0
- package/dist/tabs/router.js.map +1 -0
- package/dist/tabs/stores.d.ts +14 -0
- package/dist/tabs/stores.d.ts.map +1 -0
- package/dist/tabs/stores.js +167 -0
- package/dist/tabs/stores.js.map +1 -0
- package/dist/tabs/timeTravel.d.ts +46 -0
- package/dist/tabs/timeTravel.d.ts.map +1 -0
- package/dist/tabs/timeTravel.js +322 -0
- package/dist/tabs/timeTravel.js.map +1 -0
- 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"}
|
package/dist/helpers.js
ADDED
|
@@ -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"}
|