use-everywhere 1.0.0 → 1.0.2

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.
@@ -1,408 +1,397 @@
1
1
  'use client';
2
- "use strict";
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
18
- };
19
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
-
21
- // src/devtools/index.ts
22
- var devtools_exports = {};
23
- __export(devtools_exports, {
24
- Inspector: () => Inspector
25
- });
26
- module.exports = __toCommonJS(devtools_exports);
27
-
28
- // src/devtools/inspector.tsx
29
- var import_react3 = require("react");
30
- var import_react_dom = require("react-dom");
31
- var import_core3 = require("@use-everywhere/core");
32
-
33
- // src/devtools/panel.tsx
34
- var import_react2 = require("react");
35
- var import_core2 = require("@use-everywhere/core");
36
-
37
- // src/registry.ts
38
- var import_core = require("@use-everywhere/core");
39
-
40
- // src/server-stubs.ts
41
- var SERVER_CLIENT_ID = "";
42
- var EMPTY = Object.freeze({});
43
- var NO_PEERS = Object.freeze([]);
44
- var noop = () => {
45
- };
46
- var unsubscribe = () => noop;
47
- var INERT = Object.freeze({
48
- clientId: SERVER_CLIENT_ID,
49
- // store
50
- //
51
- // Resolved, unlike the leader's `waitForLeadership` below. A server render
52
- // has nothing to restore and is documented to render defaults, so `await
53
- // store.hydrated` must proceed — a never-settling promise would hang the
54
- // render rather than describe it honestly.
55
- hydrated: Promise.resolve(),
56
- state: EMPTY,
57
- getVersions: () => EMPTY,
58
- set: noop,
59
- subscribeKey: unsubscribe,
60
- registerKey: noop,
61
- // store + leader share getSnapshot; presence has getPeers
62
- getSnapshot: () => EMPTY,
63
- getPeers: () => NO_PEERS,
64
- // presence
65
- setMetadata: noop,
66
- // leader
67
- resign: noop,
68
- setEligible: noop,
69
- // common
70
- subscribe: unsubscribe,
71
- close: noop
72
- });
73
- var createServerStore = () => INERT;
74
- var createServerPresence = () => INERT;
75
- var NO_LEADER = Object.freeze({ leaderId: null, isLeader: false });
76
-
77
- // src/registry.ts
78
- var stores = /* @__PURE__ */ new Map();
79
- var presences = /* @__PURE__ */ new Map();
80
- var storeConfig = /* @__PURE__ */ new Map();
81
- var isServer = () => typeof window === "undefined";
82
- var scopeOptions = {
83
- everywhere: {},
84
- tabs: { accept: (meta) => meta.kind !== "worker" },
85
- tab: { transport: () => new import_core.NoopTransport() }
86
- };
87
- function getSharedStore(name = import_core.DEFAULT_NAME, scope = "everywhere") {
88
- return getStore(name, scope);
89
- }
90
- function getStore(name, scope = "everywhere") {
91
- const key = `${scope} ${name}`;
92
- let store = stores.get(key);
93
- if (!store) {
94
- store = isServer() ? createServerStore() : (0, import_core.createSharedStore)(name, {}, { ...scopeOptions[scope], ...storeConfig.get(key) });
95
- stores.set(key, store);
96
- }
97
- return store;
98
- }
99
- function getPresence(name, includeSelf = false) {
100
- const key = includeSelf ? `self ${name}` : name;
101
- let presence = presences.get(key);
102
- if (!presence) {
103
- presence = isServer() ? createServerPresence() : (0, import_core.createPresence)(name, { includeSelf });
104
- presences.set(key, presence);
105
- }
106
- return presence;
107
- }
108
-
109
- // src/use-peers.ts
110
- var import_react = require("react");
111
- var NO_PEERS2 = Object.freeze([]);
112
- function usePeers(options) {
113
- const presence = getPresence(options?.name ?? import_core.DEFAULT_NAME, options?.includeSelf ?? false);
114
- return (0, import_react.useSyncExternalStore)(
115
- (0, import_react.useCallback)((onChange) => presence.subscribe(onChange), [presence]),
116
- () => presence.getPeers(),
117
- () => NO_PEERS2
118
- );
119
- }
120
-
121
- // src/devtools/panel.tsx
122
- var import_jsx_runtime = require("react/jsx-runtime");
123
- var short = (id) => id.slice(0, 6);
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_use_peers = require("../use-peers-RhIB77TV.cjs");
4
+ let react = require("react");
5
+ let _use_everywhere_core = require("@use-everywhere/core");
6
+ let react_dom = require("react-dom");
7
+ let react_jsx_runtime = require("react/jsx-runtime");
8
+ //#region src/devtools/panel.tsx
9
+ const short = (id) => id.slice(0, 6);
124
10
  function wireLabel(wire) {
125
- return `${wire.scope}/${wire.type}`;
11
+ return `${wire.scope}/${wire.type}`;
126
12
  }
127
- var SCOPES = ["all", "state", "leader", "presence", "channel"];
128
- function Panel({
129
- name,
130
- limit,
131
- leaseMs,
132
- position,
133
- defaultOpen
134
- }) {
135
- const [open, setOpen] = (0, import_react2.useState)(defaultOpen);
136
- const [wires, setWires] = (0, import_react2.useState)([]);
137
- const [crown, setCrown] = (0, import_react2.useState)(null);
138
- const [paused, setPaused] = (0, import_react2.useState)(false);
139
- const [filter, setFilter] = (0, import_react2.useState)("");
140
- const [scopeView, setScopeView] = (0, import_react2.useState)("all");
141
- const [editing, setEditing] = (0, import_react2.useState)(null);
142
- const [frames, setFrames] = (0, import_react2.useState)([]);
143
- const nextId = (0, import_react2.useRef)(0);
144
- const crownAt = (0, import_react2.useRef)(0);
145
- const pausedRef = (0, import_react2.useRef)(false);
146
- (0, import_react2.useEffect)(() => {
147
- pausedRef.current = paused;
148
- }, [paused]);
149
- const peers = usePeers({ name });
150
- const store = getSharedStore(name);
151
- const subscribe = (0, import_react2.useCallback)((onChange) => store.subscribe(onChange), [store]);
152
- const snapshot = (0, import_react2.useSyncExternalStore)(
153
- subscribe,
154
- () => store.getSnapshot(),
155
- () => store.getSnapshot()
156
- );
157
- const versions = (0, import_react2.useSyncExternalStore)(
158
- subscribe,
159
- () => store.getVersions(),
160
- () => store.getVersions()
161
- );
162
- (0, import_react2.useEffect)(() => {
163
- return (0, import_core2.observeBus)(name, (event) => {
164
- const { wire, direction } = event;
165
- if (wire.scope === "leader") {
166
- if (wire.type === "resign") {
167
- setCrown(null);
168
- crownAt.current = 0;
169
- } else if (wire.type === "claim" || wire.type === "heartbeat") {
170
- setCrown(wire.clientId);
171
- crownAt.current = Date.now();
172
- }
173
- }
174
- if (pausedRef.current) return;
175
- const id = nextId.current++;
176
- const label = wireLabel(wire);
177
- setWires(
178
- (prev) => [
179
- ...prev.slice(-(limit - 1)),
180
- { id, direction, scope: wire.scope, label, from: short(wire.clientId) }
181
- ].slice(-limit)
182
- );
183
- if (wire.scope === "state") {
184
- setFrames(
185
- (prev) => [...prev, { wireId: id, label, snapshot: { ...store.getSnapshot() } }].slice(-limit)
186
- );
187
- }
188
- });
189
- }, [name, limit, store]);
190
- (0, import_react2.useEffect)(() => {
191
- const timer = setInterval(
192
- () => {
193
- if (crownAt.current && Date.now() - crownAt.current > leaseMs) {
194
- setCrown(null);
195
- crownAt.current = 0;
196
- }
197
- },
198
- Math.max(50, Math.floor(leaseMs / 4))
199
- );
200
- return () => clearInterval(timer);
201
- }, [leaseMs]);
202
- const selfId = store.clientId;
203
- const entries = Object.entries(versions);
204
- const needle = filter.trim().toLowerCase();
205
- const shown = wires.filter((wire) => {
206
- if (scopeView !== "all" && wire.scope !== scopeView) return false;
207
- if (!needle) return true;
208
- return wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle);
209
- });
210
- const commit = (key, draft) => {
211
- let value;
212
- try {
213
- value = JSON.parse(draft);
214
- } catch {
215
- return;
216
- }
217
- store.set(key, value);
218
- setEditing(null);
219
- };
220
- const draftIsValid = (draft) => {
221
- try {
222
- JSON.parse(draft);
223
- return true;
224
- } catch {
225
- return false;
226
- }
227
- };
228
- const restore = (frame) => {
229
- for (const [key, value] of Object.entries(frame.snapshot)) {
230
- if (Object.is(store.getSnapshot()[key], value)) continue;
231
- store.set(key, value);
232
- }
233
- };
234
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `ue-ins ue-ins--${position}`, "data-testid": "ue-inspector", children: [
235
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
236
- "button",
237
- {
238
- type: "button",
239
- className: "ue-ins__bar",
240
- onClick: () => setOpen((v) => !v),
241
- "aria-expanded": open,
242
- children: [
243
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__dot" }),
244
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__title", children: "use-everywhere" }),
245
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__muted", children: name }),
246
- crown ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ue-ins__crown", "data-testid": "ue-crown", children: [
247
- "\u2654 ",
248
- crown === selfId ? "this tab" : short(crown)
249
- ] }) : null
250
- ]
251
- }
252
- ),
253
- open ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__body", children: [
254
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
255
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__h", children: "This tab" }),
256
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__row", children: [
257
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__k", children: short(selfId) }),
258
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__v", children: crown === selfId ? "leader" : crown ? "follower" : "no leader" })
259
- ] })
260
- ] }),
261
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
262
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__h", children: [
263
- "Peers (",
264
- peers.length,
265
- ")"
266
- ] }),
267
- peers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", children: "nobody else here" }) : peers.map((peer) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__row", children: [
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__k", children: short(peer.id) }),
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__v", children: peer.kind })
270
- ] }, peer.id))
271
- ] }),
272
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
273
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__h", children: [
274
- "State (",
275
- entries.length,
276
- ")"
277
- ] }),
278
- entries.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", children: "no keys yet" }) : entries.map(([key, version]) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__row", children: [
279
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__k", children: key }),
280
- editing?.key === key ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
281
- "input",
282
- {
283
- className: `ue-ins__edit${draftIsValid(editing.draft) ? "" : " ue-ins__v--invalid"}`,
284
- value: editing.draft,
285
- autoFocus: true,
286
- "aria-label": `Value of ${key}`,
287
- onChange: (event) => setEditing({ key, draft: event.target.value }),
288
- onKeyDown: (event) => {
289
- if (event.key === "Enter") commit(key, editing.draft);
290
- if (event.key === "Escape") setEditing(null);
291
- },
292
- onBlur: () => setEditing(null),
293
- "data-testid": `ue-edit-${key}`
294
- }
295
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
296
- "button",
297
- {
298
- type: "button",
299
- className: "ue-ins__v ue-ins__v--editable",
300
- onClick: () => setEditing({ key, draft: JSON.stringify(snapshot[key]) ?? "" }),
301
- "data-testid": `ue-value-${key}`,
302
- children: JSON.stringify(snapshot[key])
303
- }
304
- ),
305
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ue-ins__ver", children: [
306
- version[0],
307
- "\xB7",
308
- short(version[1])
309
- ] })
310
- ] }, key))
311
- ] }),
312
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
313
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__h", children: [
314
- "Wires (",
315
- shown.length,
316
- shown.length === wires.length ? "" : ` of ${wires.length}`,
317
- ")",
318
- paused ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__paused", children: " \xB7 paused" }) : null
319
- ] }),
320
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__tools", children: [
321
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
322
- "button",
323
- {
324
- type: "button",
325
- className: "ue-ins__btn",
326
- "aria-pressed": paused,
327
- onClick: () => setPaused((value) => !value),
328
- "data-testid": "ue-pause",
329
- children: paused ? "resume" : "pause"
330
- }
331
- ),
332
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
333
- "button",
334
- {
335
- type: "button",
336
- className: "ue-ins__btn",
337
- onClick: () => {
338
- setWires([]);
339
- setFrames([]);
340
- },
341
- "data-testid": "ue-clear",
342
- children: "clear"
343
- }
344
- ),
345
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
346
- "input",
347
- {
348
- className: "ue-ins__filter",
349
- value: filter,
350
- placeholder: "filter",
351
- "aria-label": "Filter wires",
352
- onChange: (event) => setFilter(event.target.value),
353
- "data-testid": "ue-filter"
354
- }
355
- )
356
- ] }),
357
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__tools", role: "group", "aria-label": "Scope", children: SCOPES.map((scope) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
358
- "button",
359
- {
360
- type: "button",
361
- className: "ue-ins__btn",
362
- "aria-pressed": scopeView === scope,
363
- onClick: () => setScopeView(scope),
364
- "data-testid": `ue-scope-${scope}`,
365
- children: scope
366
- },
367
- scope
368
- )) }),
369
- shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", "data-testid": "ue-wires", children: wires.length === 0 ? "nothing yet" : "no matches" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__log", "data-testid": "ue-wires", children: shown.map((wire) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__wire", children: [
370
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `ue-ins__dir ue-ins__dir--${wire.direction}`, children: wire.direction === "out" ? "\u2192" : "\u2190" }),
371
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__scope", children: wire.label }),
372
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__from", children: wire.from })
373
- ] }, wire.id)) })
374
- ] }),
375
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__section", children: [
376
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__h", children: [
377
- "Timeline (",
378
- frames.length,
379
- ")"
380
- ] }),
381
- frames.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__empty", children: "no state on the wire yet" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "ue-ins__log", children: frames.map((frame) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "ue-ins__wire", children: [
382
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
383
- "button",
384
- {
385
- type: "button",
386
- className: "ue-ins__btn",
387
- onClick: () => restore(frame),
388
- "data-testid": `ue-restore-${String(frame.wireId)}`,
389
- children: "restore"
390
- }
391
- ),
392
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ue-ins__scope", children: frame.label }),
393
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "ue-ins__from", children: [
394
- Object.keys(frame.snapshot).length,
395
- " key",
396
- Object.keys(frame.snapshot).length === 1 ? "" : "s"
397
- ] })
398
- ] }, frame.wireId)) })
399
- ] })
400
- ] }) : null
401
- ] });
13
+ /** The scopes worth separating. `all` is not a scope; it is the absence of one. */
14
+ const SCOPES = [
15
+ "all",
16
+ "state",
17
+ "leader",
18
+ "presence",
19
+ "channel"
20
+ ];
21
+ /**
22
+ * The panel's contents. Split out of `Inspector` because the Inspector is now
23
+ * the shadow host and this is what gets portalled into it — keeping them in one
24
+ * component would mean the hooks re-run whenever the host re-renders for
25
+ * reasons that have nothing to do with what is on the wire.
26
+ */
27
+ function Panel({ name, limit, leaseMs, position, defaultOpen }) {
28
+ const [open, setOpen] = (0, react.useState)(defaultOpen);
29
+ const [wires, setWires] = (0, react.useState)([]);
30
+ const [crown, setCrown] = (0, react.useState)(null);
31
+ const [paused, setPaused] = (0, react.useState)(false);
32
+ const [filter, setFilter] = (0, react.useState)("");
33
+ const [scopeView, setScopeView] = (0, react.useState)("all");
34
+ const [editing, setEditing] = (0, react.useState)(null);
35
+ const [frames, setFrames] = (0, react.useState)([]);
36
+ const nextId = (0, react.useRef)(0);
37
+ const crownAt = (0, react.useRef)(0);
38
+ const pausedRef = (0, react.useRef)(false);
39
+ (0, react.useEffect)(() => {
40
+ pausedRef.current = paused;
41
+ }, [paused]);
42
+ const peers = require_use_peers.usePeers({ name });
43
+ const store = require_use_peers.getSharedStore(name);
44
+ const subscribe = (0, react.useCallback)((onChange) => store.subscribe(onChange), [store]);
45
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, () => store.getSnapshot(), () => store.getSnapshot());
46
+ const versions = (0, react.useSyncExternalStore)(subscribe, () => store.getVersions(), () => store.getVersions());
47
+ (0, react.useEffect)(() => {
48
+ return (0, _use_everywhere_core.observeBus)(name, (event) => {
49
+ const { wire, direction } = event;
50
+ if (wire.scope === "leader") {
51
+ if (wire.type === "resign") {
52
+ setCrown(null);
53
+ crownAt.current = 0;
54
+ } else if (wire.type === "claim" || wire.type === "heartbeat") {
55
+ setCrown(wire.clientId);
56
+ crownAt.current = Date.now();
57
+ }
58
+ }
59
+ if (pausedRef.current) return;
60
+ const id = nextId.current++;
61
+ const label = wireLabel(wire);
62
+ setWires((prev) => [...prev.slice(-(limit - 1)), {
63
+ id,
64
+ direction,
65
+ scope: wire.scope,
66
+ label,
67
+ from: short(wire.clientId)
68
+ }].slice(-limit));
69
+ if (wire.scope === "state") setFrames((prev) => [...prev, {
70
+ wireId: id,
71
+ label,
72
+ snapshot: { ...store.getSnapshot() }
73
+ }].slice(-limit));
74
+ });
75
+ }, [
76
+ name,
77
+ limit,
78
+ store
79
+ ]);
80
+ (0, react.useEffect)(() => {
81
+ const timer = setInterval(() => {
82
+ if (crownAt.current && Date.now() - crownAt.current > leaseMs) {
83
+ setCrown(null);
84
+ crownAt.current = 0;
85
+ }
86
+ }, Math.max(50, Math.floor(leaseMs / 4)));
87
+ return () => clearInterval(timer);
88
+ }, [leaseMs]);
89
+ const selfId = store.clientId;
90
+ const entries = Object.entries(versions);
91
+ const needle = filter.trim().toLowerCase();
92
+ const shown = wires.filter((wire) => {
93
+ if (scopeView !== "all" && wire.scope !== scopeView) return false;
94
+ if (!needle) return true;
95
+ return wire.label.toLowerCase().includes(needle) || wire.from.toLowerCase().includes(needle);
96
+ });
97
+ const commit = (key, draft) => {
98
+ let value;
99
+ try {
100
+ value = JSON.parse(draft);
101
+ } catch {
102
+ return;
103
+ }
104
+ store.set(key, value);
105
+ setEditing(null);
106
+ };
107
+ const draftIsValid = (draft) => {
108
+ try {
109
+ JSON.parse(draft);
110
+ return true;
111
+ } catch {
112
+ return false;
113
+ }
114
+ };
115
+ /**
116
+ * Put a recorded snapshot back.
117
+ *
118
+ * Restoring is a **write**, not a rewind: every key that differs is set
119
+ * through the store, takes a fresh version, and goes on the wire, so peers
120
+ * converge on the restored value instead of quietly disagreeing with the tab
121
+ * that time-travelled. Nothing about history is undone — this is the honest
122
+ * version of the feature, and the label says "restore" rather than "undo" for
123
+ * that reason.
124
+ *
125
+ * Keys added after the frame was taken are left alone rather than deleted:
126
+ * removing a key another tab is using is a bigger claim than a devtool should
127
+ * make from a picture of the past.
128
+ */
129
+ const restore = (frame) => {
130
+ for (const [key, value] of Object.entries(frame.snapshot)) {
131
+ if (Object.is(store.getSnapshot()[key], value)) continue;
132
+ store.set(key, value);
133
+ }
134
+ };
135
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
136
+ className: `ue-ins ue-ins--${position}`,
137
+ "data-testid": "ue-inspector",
138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
139
+ type: "button",
140
+ className: "ue-ins__bar",
141
+ onClick: () => setOpen((v) => !v),
142
+ "aria-expanded": open,
143
+ children: [
144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "ue-ins__dot" }),
145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
146
+ className: "ue-ins__title",
147
+ children: "use-everywhere"
148
+ }),
149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
150
+ className: "ue-ins__muted",
151
+ children: name
152
+ }),
153
+ crown ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
154
+ className: "ue-ins__crown",
155
+ "data-testid": "ue-crown",
156
+ children: ["♔ ", crown === selfId ? "this tab" : short(crown)]
157
+ }) : null
158
+ ]
159
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
160
+ className: "ue-ins__body",
161
+ children: [
162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
163
+ className: "ue-ins__section",
164
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
165
+ className: "ue-ins__h",
166
+ children: "This tab"
167
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
168
+ className: "ue-ins__row",
169
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
170
+ className: "ue-ins__k",
171
+ children: short(selfId)
172
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
173
+ className: "ue-ins__v",
174
+ children: crown === selfId ? "leader" : crown ? "follower" : "no leader"
175
+ })]
176
+ })]
177
+ }),
178
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
179
+ className: "ue-ins__section",
180
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
181
+ className: "ue-ins__h",
182
+ children: [
183
+ "Peers (",
184
+ peers.length,
185
+ ")"
186
+ ]
187
+ }), peers.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
188
+ className: "ue-ins__empty",
189
+ children: "nobody else here"
190
+ }) : peers.map((peer) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
191
+ className: "ue-ins__row",
192
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
193
+ className: "ue-ins__k",
194
+ children: short(peer.id)
195
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
196
+ className: "ue-ins__v",
197
+ children: peer.kind
198
+ })]
199
+ }, peer.id))]
200
+ }),
201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
202
+ className: "ue-ins__section",
203
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
204
+ className: "ue-ins__h",
205
+ children: [
206
+ "State (",
207
+ entries.length,
208
+ ")"
209
+ ]
210
+ }), entries.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
211
+ className: "ue-ins__empty",
212
+ children: "no keys yet"
213
+ }) : entries.map(([key, version]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
214
+ className: "ue-ins__row",
215
+ children: [
216
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
217
+ className: "ue-ins__k",
218
+ children: key
219
+ }),
220
+ editing?.key === key ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
221
+ className: `ue-ins__edit${draftIsValid(editing.draft) ? "" : " ue-ins__v--invalid"}`,
222
+ value: editing.draft,
223
+ autoFocus: true,
224
+ "aria-label": `Value of ${key}`,
225
+ onChange: (event) => setEditing({
226
+ key,
227
+ draft: event.target.value
228
+ }),
229
+ onKeyDown: (event) => {
230
+ if (event.key === "Enter") commit(key, editing.draft);
231
+ if (event.key === "Escape") setEditing(null);
232
+ },
233
+ onBlur: () => setEditing(null),
234
+ "data-testid": `ue-edit-${key}`
235
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
236
+ type: "button",
237
+ className: "ue-ins__v ue-ins__v--editable",
238
+ onClick: () => setEditing({
239
+ key,
240
+ draft: JSON.stringify(snapshot[key]) ?? ""
241
+ }),
242
+ "data-testid": `ue-value-${key}`,
243
+ children: JSON.stringify(snapshot[key])
244
+ }),
245
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
246
+ className: "ue-ins__ver",
247
+ children: [
248
+ version[0],
249
+ "·",
250
+ short(version[1])
251
+ ]
252
+ })
253
+ ]
254
+ }, key))]
255
+ }),
256
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
257
+ className: "ue-ins__section",
258
+ children: [
259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
260
+ className: "ue-ins__h",
261
+ children: [
262
+ "Wires (",
263
+ shown.length,
264
+ shown.length === wires.length ? "" : ` of ${wires.length}`,
265
+ ")",
266
+ paused ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
267
+ className: "ue-ins__paused",
268
+ children: " · paused"
269
+ }) : null
270
+ ]
271
+ }),
272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
273
+ className: "ue-ins__tools",
274
+ children: [
275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
276
+ type: "button",
277
+ className: "ue-ins__btn",
278
+ "aria-pressed": paused,
279
+ onClick: () => setPaused((value) => !value),
280
+ "data-testid": "ue-pause",
281
+ children: paused ? "resume" : "pause"
282
+ }),
283
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
284
+ type: "button",
285
+ className: "ue-ins__btn",
286
+ onClick: () => {
287
+ setWires([]);
288
+ setFrames([]);
289
+ },
290
+ "data-testid": "ue-clear",
291
+ children: "clear"
292
+ }),
293
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
294
+ className: "ue-ins__filter",
295
+ value: filter,
296
+ placeholder: "filter",
297
+ "aria-label": "Filter wires",
298
+ onChange: (event) => setFilter(event.target.value),
299
+ "data-testid": "ue-filter"
300
+ })
301
+ ]
302
+ }),
303
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
304
+ className: "ue-ins__tools",
305
+ role: "group",
306
+ "aria-label": "Scope",
307
+ children: SCOPES.map((scope) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
308
+ type: "button",
309
+ className: "ue-ins__btn",
310
+ "aria-pressed": scopeView === scope,
311
+ onClick: () => setScopeView(scope),
312
+ "data-testid": `ue-scope-${scope}`,
313
+ children: scope
314
+ }, scope))
315
+ }),
316
+ shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
317
+ className: "ue-ins__empty",
318
+ "data-testid": "ue-wires",
319
+ children: wires.length === 0 ? "nothing yet" : "no matches"
320
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
321
+ className: "ue-ins__log",
322
+ "data-testid": "ue-wires",
323
+ children: shown.map((wire) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
324
+ className: "ue-ins__wire",
325
+ children: [
326
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
327
+ className: `ue-ins__dir ue-ins__dir--${wire.direction}`,
328
+ children: wire.direction === "out" ? "→" : "←"
329
+ }),
330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
331
+ className: "ue-ins__scope",
332
+ children: wire.label
333
+ }),
334
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
335
+ className: "ue-ins__from",
336
+ children: wire.from
337
+ })
338
+ ]
339
+ }, wire.id))
340
+ })
341
+ ]
342
+ }),
343
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
344
+ className: "ue-ins__section",
345
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
346
+ className: "ue-ins__h",
347
+ children: [
348
+ "Timeline (",
349
+ frames.length,
350
+ ")"
351
+ ]
352
+ }), frames.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
353
+ className: "ue-ins__empty",
354
+ children: "no state on the wire yet"
355
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
356
+ className: "ue-ins__log",
357
+ children: frames.map((frame) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
358
+ className: "ue-ins__wire",
359
+ children: [
360
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
361
+ type: "button",
362
+ className: "ue-ins__btn",
363
+ onClick: () => restore(frame),
364
+ "data-testid": `ue-restore-${String(frame.wireId)}`,
365
+ children: "restore"
366
+ }),
367
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
368
+ className: "ue-ins__scope",
369
+ children: frame.label
370
+ }),
371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
372
+ className: "ue-ins__from",
373
+ children: [
374
+ Object.keys(frame.snapshot).length,
375
+ " key",
376
+ Object.keys(frame.snapshot).length === 1 ? "" : "s"
377
+ ]
378
+ })
379
+ ]
380
+ }, frame.wireId))
381
+ })]
382
+ })
383
+ ]
384
+ }) : null]
385
+ });
402
386
  }
403
-
404
- // src/devtools/styles.ts
405
- var STYLES = `
387
+ //#endregion
388
+ //#region src/devtools/styles.ts
389
+ /**
390
+ * Shipped as a <style> element inside the panel rather than a CSS file: no
391
+ * import for consumers to wire up, no document.head mutation, and it renders
392
+ * fine on the server. Every rule is scoped under .ue-ins.
393
+ */
394
+ const STYLES = `
406
395
  .ue-ins {
407
396
  position: fixed;
408
397
  z-index: 2147483000;
@@ -505,39 +494,50 @@ button.ue-ins__v {
505
494
  .ue-ins__v--invalid { color: #f85149; }
506
495
  .ue-ins__paused { color: #d29922; }
507
496
  `;
508
-
509
- // src/devtools/inspector.tsx
510
- var import_jsx_runtime2 = require("react/jsx-runtime");
511
- function Inspector({
512
- name = import_core3.DEFAULT_NAME,
513
- position = "bottom-right",
514
- limit = 50,
515
- defaultOpen = false,
516
- leaseMs = 3e3
517
- } = {}) {
518
- const [root, setRoot] = (0, import_react3.useState)(null);
519
- const attach = (host) => {
520
- if (!host || root) return;
521
- setRoot(host.shadowRoot ?? host.attachShadow({ mode: "open" }));
522
- };
523
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ref: attach, "data-testid": "ue-inspector-host", children: root ? (0, import_react_dom.createPortal)(
524
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
525
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: STYLES }),
526
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
527
- Panel,
528
- {
529
- name,
530
- position,
531
- limit,
532
- defaultOpen,
533
- leaseMs
534
- }
535
- )
536
- ] }),
537
- root
538
- ) : null });
497
+ //#endregion
498
+ //#region src/devtools/inspector.tsx
499
+ /**
500
+ * A floating panel showing what this tab is saying and hearing on the bus:
501
+ * peers, the leader, store keys with their version clocks, a live wire log in
502
+ * both directions, and a timeline of the state each wire produced.
503
+ *
504
+ * It deliberately does **not** create a Leader. Under dynamic eligibility,
505
+ * mounting one with `eligible: false` would disable candidacy for the whole
506
+ * tab, and mounting a plain one would enrol a tab that never asked to be a
507
+ * candidate — a devtool must not change what it measures. Instead it reads the
508
+ * crown out of the wire log, which it already sees in both directions.
509
+ *
510
+ * Presence is fine to use: the bus heartbeats regardless of whether anything
511
+ * created a Presence, so usePeers observes rather than perturbs.
512
+ *
513
+ * **The panel lives in a shadow root.** Its styles used to be a `<style>` tag
514
+ * in the page, which is only ever half true: `.ue-ins` could not leak out, but
515
+ * everything in the host document leaked *in* — a `* { box-sizing }` reset, an
516
+ * app-wide `button { text-transform: uppercase }`, a Tailwind preflight, any
517
+ * `!important`. A devtool that renders differently depending on whose app it is
518
+ * mounted in is a devtool you cannot trust when it looks wrong. Inside a shadow
519
+ * root, page CSS does not apply and the panel's own CSS cannot escape.
520
+ *
521
+ * A consequence worth knowing when testing an app that mounts it: the panel is
522
+ * **not** in `document`. Reach it through `host.shadowRoot`.
523
+ */
524
+ function Inspector({ name = _use_everywhere_core.DEFAULT_NAME, position = "bottom-right", limit = 50, defaultOpen = false, leaseMs = 3e3 } = {}) {
525
+ const [root, setRoot] = (0, react.useState)(null);
526
+ const attach = (host) => {
527
+ if (!host || root) return;
528
+ setRoot(host.shadowRoot ?? host.attachShadow({ mode: "open" }));
529
+ };
530
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
531
+ ref: attach,
532
+ "data-testid": "ue-inspector-host",
533
+ children: root ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: STYLES }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Panel, {
534
+ name,
535
+ position,
536
+ limit,
537
+ defaultOpen,
538
+ leaseMs
539
+ })] }), root) : null
540
+ });
539
541
  }
540
- // Annotate the CommonJS export names for ESM import in node:
541
- 0 && (module.exports = {
542
- Inspector
543
- });
542
+ //#endregion
543
+ exports.Inspector = Inspector;