@yoltra/devtools-ui 0.3.0 → 0.5.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/README.es.md +1 -1
- package/README.md +1 -1
- package/dist/devtools-ui.cjs +23 -0
- package/dist/devtools-ui.cjs.map +1 -0
- package/dist/devtools-ui.mjs +885 -0
- package/dist/devtools-ui.mjs.map +1 -0
- package/dist/types/context/HubContext.d.ts +1 -1
- package/dist/types/context/HubProvider.d.ts +2 -2
- package/dist/types/hooks/stateReplay.d.ts +53 -0
- package/dist/types/hooks/useEventLog.d.ts +1 -1
- package/dist/types/hooks/useEventReplay.d.ts +1 -1
- package/dist/types/hooks/useHubConnection.d.ts +1 -1
- package/dist/types/hooks/useStoreRegistry.d.ts +1 -1
- package/dist/types/hooks/useTimeTravel.d.ts +1 -1
- package/dist/types/index.d.ts +17 -17
- package/dist/types/types.d.ts +10 -0
- package/package.json +28 -15
- package/dist/devtools-ui.cjs.js +0 -7
- package/dist/devtools-ui.cjs.js.map +0 -1
- package/dist/devtools-ui.esm.js +0 -847
- package/dist/devtools-ui.esm.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtools-ui.mjs","sources":["../src/context/HubContext.ts","../../../common/temp/node_modules/.pnpm/react@19.1.1/node_modules/react/cjs/react-jsx-runtime.production.js","../../../common/temp/node_modules/.pnpm/react@19.1.1/node_modules/react/cjs/react-jsx-runtime.development.js","../../../common/temp/node_modules/.pnpm/react@19.1.1/node_modules/react/jsx-runtime.js","../src/context/HubProvider.tsx","../src/hooks/useHubConnection.ts","../src/hooks/useEventEmitter.ts","../src/hooks/useEventLog.ts","../src/hooks/useEventReplay.ts","../src/hooks/useStoreMetrics.ts","../src/hooks/useStoreRegistry.ts","../src/utils/apply-patch.ts","../src/hooks/snapshotRetry.ts","../src/hooks/useStoreState.ts","../src/hooks/useStoreSubscriptions.ts","../src/hooks/timeTravelNav.ts","../src/hooks/stateReplay.ts","../src/hooks/useTimeTravel.ts","../src/transport/loopback.ts"],"sourcesContent":["/**\n * React context that exposes the DevTools hub connection to the component tree.\n *\n * @remarks\n * This module defines {@link HubContext}, which holds the current\n * {@link HubContextValue}. It is populated by {@link HubProvider} and\n * consumed via the {@link useHubConnection} hook. Accessing the context\n * without a provider returns a safe no-op default.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { createContext } from \"react\";\nimport type { HubContextValue } from \"../types\";\n\nconst noop = () => {};\n\n/**\n * React context for the DevTools hub connection.\n *\n * @remarks\n * The context ships with an inert default value so that components rendered\n * outside of a {@link HubProvider} do not throw. Consumers should always\n * wrap their tree in a provider before relying on the context value.\n *\n * @example\n * ```tsx\n * import { useContext } from \"react\";\n * import { HubContext } from \"@yoltra/devtools-ui\";\n *\n * function StatusBadge() {\n * const { status } = useContext(HubContext);\n * return <span>{status}</span>;\n * }\n * ```\n *\n * @public\n */\nexport const HubContext = createContext<HubContextValue>({\n status: \"disconnected\",\n extensionId: \"\",\n send: noop,\n subscribe: () => noop,\n disconnect: noop,\n reconnect: noop,\n});\n","/**\n * @license React\n * react-jsx-runtime.production.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\nvar REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\");\nfunction jsxProd(type, config, maybeKey) {\n var key = null;\n void 0 !== maybeKey && (key = \"\" + maybeKey);\n void 0 !== config.key && (key = \"\" + config.key);\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n config = maybeKey.ref;\n return {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n ref: void 0 !== config ? config : null,\n props: maybeKey\n };\n}\nexports.Fragment = REACT_FRAGMENT_TYPE;\nexports.jsx = jsxProd;\nexports.jsxs = jsxProd;\n","/**\n * @license React\n * react-jsx-runtime.development.js\n *\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\"use strict\";\n\"production\" !== process.env.NODE_ENV &&\n (function () {\n function getComponentNameFromType(type) {\n if (null == type) return null;\n if (\"function\" === typeof type)\n return type.$$typeof === REACT_CLIENT_REFERENCE\n ? null\n : type.displayName || type.name || null;\n if (\"string\" === typeof type) return type;\n switch (type) {\n case REACT_FRAGMENT_TYPE:\n return \"Fragment\";\n case REACT_PROFILER_TYPE:\n return \"Profiler\";\n case REACT_STRICT_MODE_TYPE:\n return \"StrictMode\";\n case REACT_SUSPENSE_TYPE:\n return \"Suspense\";\n case REACT_SUSPENSE_LIST_TYPE:\n return \"SuspenseList\";\n case REACT_ACTIVITY_TYPE:\n return \"Activity\";\n }\n if (\"object\" === typeof type)\n switch (\n (\"number\" === typeof type.tag &&\n console.error(\n \"Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue.\"\n ),\n type.$$typeof)\n ) {\n case REACT_PORTAL_TYPE:\n return \"Portal\";\n case REACT_CONTEXT_TYPE:\n return (type.displayName || \"Context\") + \".Provider\";\n case REACT_CONSUMER_TYPE:\n return (type._context.displayName || \"Context\") + \".Consumer\";\n case REACT_FORWARD_REF_TYPE:\n var innerType = type.render;\n type = type.displayName;\n type ||\n ((type = innerType.displayName || innerType.name || \"\"),\n (type = \"\" !== type ? \"ForwardRef(\" + type + \")\" : \"ForwardRef\"));\n return type;\n case REACT_MEMO_TYPE:\n return (\n (innerType = type.displayName || null),\n null !== innerType\n ? innerType\n : getComponentNameFromType(type.type) || \"Memo\"\n );\n case REACT_LAZY_TYPE:\n innerType = type._payload;\n type = type._init;\n try {\n return getComponentNameFromType(type(innerType));\n } catch (x) {}\n }\n return null;\n }\n function testStringCoercion(value) {\n return \"\" + value;\n }\n function checkKeyStringCoercion(value) {\n try {\n testStringCoercion(value);\n var JSCompiler_inline_result = !1;\n } catch (e) {\n JSCompiler_inline_result = !0;\n }\n if (JSCompiler_inline_result) {\n JSCompiler_inline_result = console;\n var JSCompiler_temp_const = JSCompiler_inline_result.error;\n var JSCompiler_inline_result$jscomp$0 =\n (\"function\" === typeof Symbol &&\n Symbol.toStringTag &&\n value[Symbol.toStringTag]) ||\n value.constructor.name ||\n \"Object\";\n JSCompiler_temp_const.call(\n JSCompiler_inline_result,\n \"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.\",\n JSCompiler_inline_result$jscomp$0\n );\n return testStringCoercion(value);\n }\n }\n function getTaskName(type) {\n if (type === REACT_FRAGMENT_TYPE) return \"<>\";\n if (\n \"object\" === typeof type &&\n null !== type &&\n type.$$typeof === REACT_LAZY_TYPE\n )\n return \"<...>\";\n try {\n var name = getComponentNameFromType(type);\n return name ? \"<\" + name + \">\" : \"<...>\";\n } catch (x) {\n return \"<...>\";\n }\n }\n function getOwner() {\n var dispatcher = ReactSharedInternals.A;\n return null === dispatcher ? null : dispatcher.getOwner();\n }\n function UnknownOwner() {\n return Error(\"react-stack-top-frame\");\n }\n function hasValidKey(config) {\n if (hasOwnProperty.call(config, \"key\")) {\n var getter = Object.getOwnPropertyDescriptor(config, \"key\").get;\n if (getter && getter.isReactWarning) return !1;\n }\n return void 0 !== config.key;\n }\n function defineKeyPropWarningGetter(props, displayName) {\n function warnAboutAccessingKey() {\n specialPropKeyWarningShown ||\n ((specialPropKeyWarningShown = !0),\n console.error(\n \"%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)\",\n displayName\n ));\n }\n warnAboutAccessingKey.isReactWarning = !0;\n Object.defineProperty(props, \"key\", {\n get: warnAboutAccessingKey,\n configurable: !0\n });\n }\n function elementRefGetterWithDeprecationWarning() {\n var componentName = getComponentNameFromType(this.type);\n didWarnAboutElementRef[componentName] ||\n ((didWarnAboutElementRef[componentName] = !0),\n console.error(\n \"Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.\"\n ));\n componentName = this.props.ref;\n return void 0 !== componentName ? componentName : null;\n }\n function ReactElement(\n type,\n key,\n self,\n source,\n owner,\n props,\n debugStack,\n debugTask\n ) {\n self = props.ref;\n type = {\n $$typeof: REACT_ELEMENT_TYPE,\n type: type,\n key: key,\n props: props,\n _owner: owner\n };\n null !== (void 0 !== self ? self : null)\n ? Object.defineProperty(type, \"ref\", {\n enumerable: !1,\n get: elementRefGetterWithDeprecationWarning\n })\n : Object.defineProperty(type, \"ref\", { enumerable: !1, value: null });\n type._store = {};\n Object.defineProperty(type._store, \"validated\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: 0\n });\n Object.defineProperty(type, \"_debugInfo\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: null\n });\n Object.defineProperty(type, \"_debugStack\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: debugStack\n });\n Object.defineProperty(type, \"_debugTask\", {\n configurable: !1,\n enumerable: !1,\n writable: !0,\n value: debugTask\n });\n Object.freeze && (Object.freeze(type.props), Object.freeze(type));\n return type;\n }\n function jsxDEVImpl(\n type,\n config,\n maybeKey,\n isStaticChildren,\n source,\n self,\n debugStack,\n debugTask\n ) {\n var children = config.children;\n if (void 0 !== children)\n if (isStaticChildren)\n if (isArrayImpl(children)) {\n for (\n isStaticChildren = 0;\n isStaticChildren < children.length;\n isStaticChildren++\n )\n validateChildKeys(children[isStaticChildren]);\n Object.freeze && Object.freeze(children);\n } else\n console.error(\n \"React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.\"\n );\n else validateChildKeys(children);\n if (hasOwnProperty.call(config, \"key\")) {\n children = getComponentNameFromType(type);\n var keys = Object.keys(config).filter(function (k) {\n return \"key\" !== k;\n });\n isStaticChildren =\n 0 < keys.length\n ? \"{key: someKey, \" + keys.join(\": ..., \") + \": ...}\"\n : \"{key: someKey}\";\n didWarnAboutKeySpread[children + isStaticChildren] ||\n ((keys =\n 0 < keys.length ? \"{\" + keys.join(\": ..., \") + \": ...}\" : \"{}\"),\n console.error(\n 'A props object containing a \"key\" prop is being spread into JSX:\\n let props = %s;\\n <%s {...props} />\\nReact keys must be passed directly to JSX without using spread:\\n let props = %s;\\n <%s key={someKey} {...props} />',\n isStaticChildren,\n children,\n keys,\n children\n ),\n (didWarnAboutKeySpread[children + isStaticChildren] = !0));\n }\n children = null;\n void 0 !== maybeKey &&\n (checkKeyStringCoercion(maybeKey), (children = \"\" + maybeKey));\n hasValidKey(config) &&\n (checkKeyStringCoercion(config.key), (children = \"\" + config.key));\n if (\"key\" in config) {\n maybeKey = {};\n for (var propName in config)\n \"key\" !== propName && (maybeKey[propName] = config[propName]);\n } else maybeKey = config;\n children &&\n defineKeyPropWarningGetter(\n maybeKey,\n \"function\" === typeof type\n ? type.displayName || type.name || \"Unknown\"\n : type\n );\n return ReactElement(\n type,\n children,\n self,\n source,\n getOwner(),\n maybeKey,\n debugStack,\n debugTask\n );\n }\n function validateChildKeys(node) {\n \"object\" === typeof node &&\n null !== node &&\n node.$$typeof === REACT_ELEMENT_TYPE &&\n node._store &&\n (node._store.validated = 1);\n }\n var React = require(\"react\"),\n REACT_ELEMENT_TYPE = Symbol.for(\"react.transitional.element\"),\n REACT_PORTAL_TYPE = Symbol.for(\"react.portal\"),\n REACT_FRAGMENT_TYPE = Symbol.for(\"react.fragment\"),\n REACT_STRICT_MODE_TYPE = Symbol.for(\"react.strict_mode\"),\n REACT_PROFILER_TYPE = Symbol.for(\"react.profiler\");\n Symbol.for(\"react.provider\");\n var REACT_CONSUMER_TYPE = Symbol.for(\"react.consumer\"),\n REACT_CONTEXT_TYPE = Symbol.for(\"react.context\"),\n REACT_FORWARD_REF_TYPE = Symbol.for(\"react.forward_ref\"),\n REACT_SUSPENSE_TYPE = Symbol.for(\"react.suspense\"),\n REACT_SUSPENSE_LIST_TYPE = Symbol.for(\"react.suspense_list\"),\n REACT_MEMO_TYPE = Symbol.for(\"react.memo\"),\n REACT_LAZY_TYPE = Symbol.for(\"react.lazy\"),\n REACT_ACTIVITY_TYPE = Symbol.for(\"react.activity\"),\n REACT_CLIENT_REFERENCE = Symbol.for(\"react.client.reference\"),\n ReactSharedInternals =\n React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,\n hasOwnProperty = Object.prototype.hasOwnProperty,\n isArrayImpl = Array.isArray,\n createTask = console.createTask\n ? console.createTask\n : function () {\n return null;\n };\n React = {\n react_stack_bottom_frame: function (callStackForError) {\n return callStackForError();\n }\n };\n var specialPropKeyWarningShown;\n var didWarnAboutElementRef = {};\n var unknownOwnerDebugStack = React.react_stack_bottom_frame.bind(\n React,\n UnknownOwner\n )();\n var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner));\n var didWarnAboutKeySpread = {};\n exports.Fragment = REACT_FRAGMENT_TYPE;\n exports.jsx = function (type, config, maybeKey, source, self) {\n var trackActualOwner =\n 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;\n return jsxDEVImpl(\n type,\n config,\n maybeKey,\n !1,\n source,\n self,\n trackActualOwner\n ? Error(\"react-stack-top-frame\")\n : unknownOwnerDebugStack,\n trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask\n );\n };\n exports.jsxs = function (type, config, maybeKey, source, self) {\n var trackActualOwner =\n 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++;\n return jsxDEVImpl(\n type,\n config,\n maybeKey,\n !0,\n source,\n self,\n trackActualOwner\n ? Error(\"react-stack-top-frame\")\n : unknownOwnerDebugStack,\n trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask\n );\n };\n })();\n","'use strict';\n\nif (process.env.NODE_ENV === 'production') {\n module.exports = require('./cjs/react-jsx-runtime.production.js');\n} else {\n module.exports = require('./cjs/react-jsx-runtime.development.js');\n}\n","/**\n * Provider component that manages the WebSocket connection to the DevTools hub\n * and exposes it to the React tree via {@link HubContext}.\n *\n * @remarks\n * `HubProvider` owns the full connection lifecycle: initial handshake,\n * exponential-backoff reconnection, message fan-out to subscribers, and\n * graceful teardown on unmount. All DevTools UI hooks depend on this\n * provider being present in the ancestor tree.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport {\n DevtoolsRole,\n PROTOCOL_VERSION,\n type DevtoolsMessage,\n type ExtensionCapabilities,\n} from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from \"react\";\nimport type { HubConnectionConfig, HubConnectionStatus, HubContextValue } from \"../types\";\nimport { HubContext } from \"./HubContext\";\n\n/** @internal Default extension capabilities advertised during handshake. */\nconst DEFAULT_CAPABILITIES: ExtensionCapabilities = {\n timeTravel: true,\n eventReplay: true,\n stateExplorer: true,\n eventEmit: true,\n performanceMetrics: true,\n};\n\n/** @internal Base delay (ms) for exponential backoff reconnection. */\nconst RECONNECT_BASE_MS = 750;\n\n/** @internal Maximum delay (ms) cap for exponential backoff reconnection. */\nconst RECONNECT_MAX_MS = 30_000;\n\n/**\n * Provides hub connection context to all child hooks and components.\n *\n * @remarks\n * On mount the provider opens a WebSocket to the hub, performs the protocol\n * handshake, and begins dispatching incoming messages to all subscribers. If\n * the connection drops and `autoReconnect` is enabled (default), it\n * reconnects with exponential backoff up to `maxReconnectAttempts`.\n *\n * @example\n * ```tsx\n * import { HubProvider } from \"@yoltra/devtools-ui\";\n *\n * function App() {\n * return (\n * <HubProvider config={{ port: 8900 }}>\n * <DevToolsPanel />\n * </HubProvider>\n * );\n * }\n * ```\n *\n * @param props.config - Connection configuration (see {@link HubConnectionConfig}).\n * @param props.children - React children that will have access to the hub context.\n * @returns A React element wrapping children in {@link HubContext.Provider}.\n *\n * @public\n */\nexport function HubProvider({\n config,\n children,\n}: {\n config: HubConnectionConfig;\n children: ReactNode;\n}) {\n const [status, setStatus] = useState<HubConnectionStatus>(\"disconnected\");\n const wsRef = useRef<WebSocket | null>(null);\n const subscribersRef = useRef<Set<(msg: DevtoolsMessage) => void>>(new Set());\n const reconnectAttemptRef = useRef(0);\n const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const extensionIdRef = useRef(crypto.randomUUID());\n const configRef = useRef(config);\n configRef.current = config;\n\n const connect = useCallback(() => {\n const cfg = configRef.current;\n const host = cfg.host ?? \"localhost\";\n const url = `ws://${host}:${cfg.port}`;\n const WS =\n cfg.WebSocket ??\n (typeof globalThis.WebSocket !== \"undefined\" ? globalThis.WebSocket : undefined);\n\n if (!WS) {\n console.error(\n \"[Yoltra DevTools] No WebSocket implementation available. In Node.js, pass { WebSocket } from 'ws' via config.WebSocket.\",\n );\n return;\n }\n\n setStatus(\"connecting\");\n\n try {\n const ws = new WS(url);\n wsRef.current = ws;\n\n ws.onopen = () => {\n reconnectAttemptRef.current = 0;\n\n const handshake = JSON.stringify({\n type: \"HANDSHAKE_REQUEST\",\n protocolVersion: PROTOCOL_VERSION,\n role: DevtoolsRole.EXTENSION,\n extension: {\n id: extensionIdRef.current,\n name: cfg.extensionName ?? \"DevTools UI\",\n capabilities: DEFAULT_CAPABILITIES,\n },\n });\n ws.send(handshake);\n };\n\n ws.onmessage = (event) => {\n try {\n // Handle both string (browser) and Buffer (Node.js ws package) data\n const raw = typeof event.data === \"string\" ? event.data : String(event.data);\n const msg: DevtoolsMessage = JSON.parse(raw);\n\n if (msg.type === \"HANDSHAKE_RESPONSE\") {\n if (msg.success) {\n setStatus(\"connected\");\n } else {\n ws.close();\n }\n return;\n }\n\n subscribersRef.current.forEach((handler) => handler(msg));\n } catch {\n // Ignore malformed messages\n }\n };\n\n ws.onclose = () => {\n wsRef.current = null;\n setStatus(\"disconnected\");\n\n const autoReconnect = cfg.autoReconnect ?? true;\n const maxAttempts = cfg.maxReconnectAttempts ?? Infinity;\n\n if (autoReconnect && reconnectAttemptRef.current < maxAttempts) {\n const attempt = reconnectAttemptRef.current++;\n const delay = Math.min(\n RECONNECT_BASE_MS * Math.pow(2, attempt) + Math.random() * 500,\n RECONNECT_MAX_MS,\n );\n reconnectTimerRef.current = setTimeout(connect, delay);\n }\n };\n\n ws.onerror = () => {\n // onclose will fire after onerror\n };\n } catch {\n setStatus(\"disconnected\");\n }\n }, []);\n\n const disconnect = useCallback(() => {\n if (reconnectTimerRef.current) {\n clearTimeout(reconnectTimerRef.current);\n reconnectTimerRef.current = null;\n }\n reconnectAttemptRef.current = Infinity; // Prevent auto-reconnect\n wsRef.current?.close();\n wsRef.current = null;\n setStatus(\"disconnected\");\n }, []);\n\n const reconnect = useCallback(() => {\n reconnectAttemptRef.current = 0;\n wsRef.current?.close();\n wsRef.current = null;\n connect();\n }, [connect]);\n\n const send = useCallback((message: DevtoolsMessage) => {\n const ws = wsRef.current;\n if (ws && ws.readyState === 1 /* OPEN */) {\n ws.send(JSON.stringify(message));\n }\n }, []);\n\n const subscribe = useCallback((handler: (msg: DevtoolsMessage) => void) => {\n subscribersRef.current.add(handler);\n return () => {\n subscribersRef.current.delete(handler);\n };\n }, []);\n\n useEffect(() => {\n connect();\n return () => {\n if (reconnectTimerRef.current) {\n clearTimeout(reconnectTimerRef.current);\n }\n reconnectAttemptRef.current = Infinity;\n wsRef.current?.close();\n };\n }, [connect]);\n\n const value: HubContextValue = {\n status,\n extensionId: extensionIdRef.current,\n send,\n subscribe,\n disconnect,\n reconnect,\n };\n\n return <HubContext.Provider value={value}>{children}</HubContext.Provider>;\n}\n","/**\n * Hook for accessing the DevTools hub connection from any component.\n *\n * @remarks\n * Thin wrapper around `useContext(HubContext)`. It is the primary entry-point\n * for all other DevTools UI hooks that need to send or subscribe to hub\n * messages. Must be rendered inside a {@link HubProvider}.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { useContext } from \"react\";\nimport { HubContext } from \"../context/HubContext\";\nimport type { HubContextValue } from \"../types\";\n\n/**\n * Access the hub connection context.\n *\n * @remarks\n * Must be used within a {@link HubProvider}. If called outside a provider\n * the returned value contains safe no-op functions and a `\"disconnected\"`\n * status.\n *\n * @example\n * ```tsx\n * import { useHubConnection } from \"@yoltra/devtools-ui\";\n *\n * function ConnectionStatus() {\n * const { status, reconnect } = useHubConnection();\n * return (\n * <div>\n * <span>Status: {status}</span>\n * <button onClick={reconnect}>Reconnect</button>\n * </div>\n * );\n * }\n * ```\n *\n * @returns The hub connection context value (see {@link HubContextValue}).\n *\n * @public\n */\nexport function useHubConnection(): HubContextValue {\n return useContext(HubContext);\n}\n","/**\n * Hook that allows the DevTools extension to emit custom events to a\n * Yoltra store.\n *\n * @remarks\n * Wraps the `EMIT_TO_STORE` protocol message. The emitted event is\n * dispatched through the store's normal event pipeline (middleware,\n * reducers, subscribers) just as if it had been emitted from application\n * code. This is useful for manually triggering state transitions during\n * debugging.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { DevtoolsRole } from \"@yoltra/devtools-protocol\";\nimport { useCallback } from \"react\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Emits events to a store from the extension.\n *\n * @remarks\n * The returned `emit` function is referentially stable (memoised via\n * `useCallback`) and safe to include in dependency arrays. Calling `emit`\n * when `storeId` is `null` is a no-op.\n *\n * @example\n * ```tsx\n * import { useEventEmitter } from \"@yoltra/devtools-ui\";\n *\n * function EmitForm({ storeId }: { storeId: string }) {\n * const { emit } = useEventEmitter(storeId);\n *\n * const handleClick = () => {\n * emit(\"counter\", \"INCREMENT\", { amount: 1 });\n * };\n *\n * return <button onClick={handleClick}>Increment Counter</button>;\n * }\n * ```\n *\n * @param storeId - The store ID to emit events to, or `null` to disable.\n * @returns An object containing the `emit(channel, type, payload)` function.\n *\n * @public\n */\nexport function useEventEmitter(storeId: string | null): {\n emit: (channel: string, type: string, payload: unknown) => void;\n} {\n const { send, extensionId } = useHubConnection();\n\n const emit = useCallback(\n (channel: string, type: string, payload: unknown) => {\n if (!storeId) return;\n\n send({\n type: \"EMIT_TO_STORE\",\n storeId,\n event: { channel, type, payload },\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n },\n [storeId, send, extensionId],\n );\n\n return { emit };\n}\n","/**\n * Hook that accumulates a chronological log of store events for inspection.\n *\n * @remarks\n * Subscribes to `STORE_EVENT` messages for **all** stores and stores them in\n * a per-store map so that history is retained when the user switches between\n * store tabs. The `storeId` parameter controls which store's slice is\n * returned, but collection is never paused or reset when the selected store\n * changes.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport type { DevtoolsMessage } from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { EventLogEntry } from \"../types\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/** @internal Default upper bound on retained event log entries per store. */\nconst DEFAULT_MAX_ENTRIES = 2000;\n\n/**\n * Options for the {@link useEventLog} hook.\n *\n * @public\n */\nexport interface UseEventLogOptions {\n /** Maximum number of entries to keep per store. @defaultValue `2000` */\n maxEntries?: number;\n}\n\n/**\n * Maintains a chronological event log for a given store.\n *\n * @remarks\n * Events are accumulated for **all** stores simultaneously in a `Map` keyed\n * by store ID, so switching between store tabs does not discard existing\n * history. Passing `null` as `storeId` returns an empty array but collection\n * continues in the background.\n *\n * When the log for a given store exceeds `maxEntries` the oldest entries for\n * that store are discarded.\n *\n * @example\n * ```tsx\n * import { useEventLog } from \"@yoltra/devtools-ui\";\n *\n * function EventTimeline({ storeId }: { storeId: string }) {\n * const { entries, clear } = useEventLog(storeId, { maxEntries: 500 });\n * return (\n * <div>\n * <button onClick={clear}>Clear</button>\n * <ul>\n * {entries.map((e, i) => (\n * <li key={i}>{e.event.type} @ {e.timestamp}</li>\n * ))}\n * </ul>\n * </div>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID whose events to return, or `null` to get an\n * empty slice (collection still runs for all stores).\n * @param options - Optional configuration (see {@link UseEventLogOptions}).\n * @returns An object containing the `entries` array and a `clear` function.\n *\n * @public\n */\nexport function useEventLog(\n storeId: string | null,\n options?: UseEventLogOptions,\n): { entries: EventLogEntry[]; clear: () => void } {\n const { subscribe } = useHubConnection();\n const maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;\n\n /**\n * Per-store event log, keyed by storeId.\n * Stored in a ref so mutations don't trigger unnecessary re-renders; we\n * control re-renders manually via the `tick` counter below.\n */\n const allEntriesRef = useRef<Map<string, EventLogEntry[]>>(new Map());\n const maxRef = useRef(maxEntries);\n maxRef.current = maxEntries;\n\n /**\n * Incremented on every mutation to trigger a re-render so consumers see\n * the latest entries. The value itself is not used in the render output.\n */\n const [, setTick] = useState(0);\n\n useEffect(() => {\n // Subscribe once for the lifetime of the provider — collect events for\n // ALL stores so history persists regardless of which store tab is active.\n const unsub = subscribe((msg: DevtoolsMessage) => {\n if (msg.type !== \"STORE_EVENT\") return;\n\n const sid = msg.storeId;\n const entry: EventLogEntry = {\n event: msg.event,\n storeId: sid,\n patches: msg.patches,\n snapshotVersion: msg.snapshotVersion,\n committed: msg.committed,\n timestamp: msg.timestamp,\n };\n\n const prev = allEntriesRef.current.get(sid) ?? [];\n const next = [...prev, entry];\n allEntriesRef.current.set(\n sid,\n next.length > maxRef.current ? next.slice(next.length - maxRef.current) : next,\n );\n\n setTick((t) => t + 1);\n });\n\n return unsub;\n // subscribe is stable for the lifetime of HubProvider — intentionally\n // omit storeId so the subscription is never restarted on tab switch.\n }, [subscribe]);\n\n /** Clear the event log for the currently selected store only. */\n const clear = useCallback(() => {\n if (storeId) {\n allEntriesRef.current.delete(storeId);\n setTick((t) => t + 1);\n }\n }, [storeId]);\n\n const entries = storeId ? (allEntriesRef.current.get(storeId) ?? []) : [];\n\n return { entries, clear };\n}\n","/**\n * Hook that replays a sequence of events through a store's reducer pipeline\n * starting from a given snapshot.\n *\n * @remarks\n * Sends an `EVENT_REPLAY` message to the hub containing the base snapshot\n * and the ordered list of events to replay. The store re-processes each\n * event through its reducers and updates its state accordingly. This is\n * useful for \"what-if\" analysis and debugging reducer logic.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { DevtoolsRole } from \"@yoltra/devtools-protocol\";\nimport { useCallback } from \"react\";\nimport type { EventLogEntry } from \"../types\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Replays events through a store's reducers from a given snapshot.\n *\n * @remarks\n * The `replay` function accepts a base state snapshot and an array of\n * {@link EventLogEntry} objects. It serialises the events into the protocol\n * format and sends a single `EVENT_REPLAY` message to the hub. The store\n * is expected to process each event sequentially through its reducer\n * pipeline and emit the resulting state.\n *\n * @example\n * ```tsx\n * import { useEventLog, useStoreState, useEventReplay } from \"@yoltra/devtools-ui\";\n *\n * function ReplayButton({ storeId }: { storeId: string }) {\n * const { state } = useStoreState(storeId);\n * const { entries } = useEventLog(storeId);\n * const { replay } = useEventReplay(storeId);\n *\n * return (\n * <button onClick={() => replay(state, entries)}>\n * Replay All Events\n * </button>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID to replay events on, or `null` to disable.\n * @returns An object containing the `replay` function.\n *\n * @public\n */\nexport function useEventReplay(storeId: string | null): {\n replay: (snapshot: unknown, events: EventLogEntry[]) => void;\n} {\n const { send, extensionId } = useHubConnection();\n\n const replay = useCallback(\n (snapshot: unknown, events: EventLogEntry[]) => {\n if (!storeId) return;\n\n send({\n type: \"EVENT_REPLAY\",\n storeId,\n snapshot,\n events: events.map((e) => ({\n id: e.event.id,\n channel: e.event.channel,\n type: e.event.type,\n payload: e.event.payload,\n })),\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n },\n [storeId, send, extensionId],\n );\n\n return { replay };\n}\n","/**\n * Hook that fetches and auto-refreshes performance metrics for a Yoltra store.\n *\n * @remarks\n * Sends periodic `REQUEST_METRICS` messages to the hub and caches the\n * latest `STORE_METRICS` response. The polling interval is configurable\n * and can be disabled by setting `refreshIntervalMs` to `0`.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport {\n DevtoolsRole,\n type DevtoolsMessage,\n type StoreMetrics,\n} from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Metrics payload extracted from a `STORE_METRICS` message.\n *\n * @internal\n */\ntype MetricsData = StoreMetrics[\"metrics\"];\n\n/**\n * Options for the {@link useStoreMetrics} hook.\n *\n * @public\n */\nexport interface UseStoreMetricsOptions {\n /** Auto-refresh interval in milliseconds. Set to `0` to disable. @defaultValue `2000` */\n refreshIntervalMs?: number;\n}\n\n/**\n * Fetches and caches performance metrics for a store, with automatic\n * periodic refresh.\n *\n * @remarks\n * On mount the hook immediately requests metrics and starts an interval\n * timer that re-fetches every `refreshIntervalMs` milliseconds (default\n * 2 000). The timer is cleaned up on unmount or when the `storeId` changes.\n *\n * @example\n * ```tsx\n * import { useStoreMetrics } from \"@yoltra/devtools-ui\";\n *\n * function MetricsPanel({ storeId }: { storeId: string }) {\n * const { metrics, loading, refresh } = useStoreMetrics(storeId, {\n * refreshIntervalMs: 5000,\n * });\n * if (loading || !metrics) return <p>Loading...</p>;\n * return (\n * <div>\n * <pre>{JSON.stringify(metrics, null, 2)}</pre>\n * <button onClick={refresh}>Refresh Now</button>\n * </div>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID to query, or `null` to disable.\n * @param options - Optional configuration (see {@link UseStoreMetricsOptions}).\n * @returns An object with `metrics` ({@link MetricsData} or `null`),\n * `loading`, and `refresh`.\n *\n * @public\n */\nexport function useStoreMetrics(\n storeId: string | null,\n options?: UseStoreMetricsOptions,\n): {\n metrics: MetricsData | null;\n loading: boolean;\n refresh: () => void;\n} {\n const { send, subscribe, extensionId } = useHubConnection();\n const [metrics, setMetrics] = useState<MetricsData | null>(null);\n const [loading, setLoading] = useState(false);\n const intervalMs = options?.refreshIntervalMs ?? 2000;\n const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);\n\n const requestMetrics = useCallback(() => {\n if (!storeId) return;\n setLoading(true);\n send({\n type: \"REQUEST_METRICS\",\n storeId,\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n }, [storeId, send, extensionId]);\n\n useEffect(() => {\n if (!storeId) {\n setMetrics(null);\n return;\n }\n\n requestMetrics();\n\n const unsub = subscribe((msg: DevtoolsMessage) => {\n if (msg.type === \"STORE_METRICS\" && msg.storeId === storeId) {\n setMetrics(msg.metrics);\n setLoading(false);\n }\n });\n\n // Auto-refresh\n if (intervalMs > 0) {\n timerRef.current = setInterval(requestMetrics, intervalMs);\n }\n\n return () => {\n unsub();\n if (timerRef.current) {\n clearInterval(timerRef.current);\n timerRef.current = null;\n }\n };\n }, [storeId, subscribe, requestMetrics, intervalMs]);\n\n return { metrics, loading, refresh: requestMetrics };\n}\n","/**\n * Hook that maintains a live list of stores registered with the DevTools hub.\n *\n * @remarks\n * Subscribes to `STORE_REGISTRY`, `STORE_CONNECTED`, and\n * `STORE_DISCONNECTED` hub messages and keeps an up-to-date array of\n * {@link RegisteredStore} entries. Use this to build store selector UIs.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport type { DevtoolsMessage } from \"@yoltra/devtools-protocol\";\nimport { useEffect, useState } from \"react\";\nimport type { RegisteredStore } from \"../types\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Tracks connected stores from `STORE_REGISTRY` hub broadcasts.\n *\n * @remarks\n * The hook listens for three message types:\n * - `STORE_REGISTRY` -- replaces the full store list (sent on initial connect).\n * - `STORE_CONNECTED` -- adds or updates a single store entry.\n * - `STORE_DISCONNECTED` -- marks a store as `\"disconnected\"`.\n *\n * The returned array is referentially stable unless the underlying data changes.\n *\n * @example\n * ```tsx\n * import { useStoreRegistry } from \"@yoltra/devtools-ui\";\n *\n * function StoreList() {\n * const stores = useStoreRegistry();\n * return (\n * <ul>\n * {stores.map((s) => (\n * <li key={s.id}>{s.name} ({s.status})</li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n *\n * @returns The current list of registered stores (see {@link RegisteredStore}).\n *\n * @public\n */\nexport function useStoreRegistry(): RegisteredStore[] {\n const { subscribe } = useHubConnection();\n const [stores, setStores] = useState<RegisteredStore[]>([]);\n\n useEffect(() => {\n const unsub = subscribe((msg: DevtoolsMessage) => {\n switch (msg.type) {\n case \"STORE_REGISTRY\":\n setStores(msg.stores);\n break;\n case \"STORE_CONNECTED\":\n setStores((prev) => {\n const exists = prev.some((s) => s.id === msg.store.id);\n if (exists) {\n return prev.map((s) =>\n s.id === msg.store.id ? { ...s, status: \"connected\" as const } : s,\n );\n }\n return [\n ...prev,\n {\n id: msg.store.id,\n name: msg.store.name,\n status: \"connected\" as const,\n capabilities: msg.store.capabilities,\n connectedAt: msg.timestamp,\n },\n ];\n });\n break;\n case \"STORE_DISCONNECTED\":\n setStores((prev) =>\n prev.map((s) =>\n s.id === msg.storeId ? { ...s, status: \"disconnected\" as const } : s,\n ),\n );\n break;\n }\n });\n return unsub;\n }, [subscribe]);\n\n return stores;\n}\n","/**\n * Immutable RFC 6902 JSON Patch utility used by the DevTools UI state hooks.\n *\n * @remarks\n * This module provides a minimal, dependency-free implementation of the\n * `add`, `remove`, and `replace` JSON Patch operations. It is used\n * internally by {@link useStoreState} to apply incremental state patches\n * received from the hub without mutating the existing state tree.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport type { JsonPatch } from \"@yoltra/devtools-protocol\";\n\n/**\n * Apply an array of RFC 6902 JSON Patch operations to a value.\n * Returns a new object -- does not mutate the input.\n *\n * @remarks\n * Only the `add`, `remove`, and `replace` operations are implemented because\n * the v1 devtools protocol does not emit `move`, `copy`, or `test` patches.\n * Each operation is applied sequentially in array order. Path segments are\n * resolved according to RFC 6901 (JSON Pointer), including `~0` / `~1`\n * escape handling.\n *\n * @example\n * ```ts\n * import { applyPatches } from \"@yoltra/devtools-ui\";\n *\n * const next = applyPatches(\n * { counter: { value: 0 } },\n * [{ op: \"replace\", path: \"/counter/value\", value: 1 }],\n * );\n * // next => { counter: { value: 1 } }\n * ```\n *\n * @param target - The value to patch.\n * @param patches - RFC 6902 operations.\n * @returns A new patched value.\n *\n * @public\n */\nexport function applyPatches<T = unknown>(target: T, patches: JsonPatch[]): T {\n // No clone. `setAtPath` and `removeAtPath` already copy each node along the touched path and\n // never write into their input, so deep-cloning first duplicated the entire state tree only to\n // have the parts that matter copied again — turning an operation proportional to the change\n // into one proportional to the whole store, on every event.\n //\n // It also destroyed structural sharing: a fresh tree meant every subtree looked new to the\n // panel's memoization, so a one-field update re-rendered the whole state view.\n let result: any = target;\n\n for (const patch of patches) {\n const segments = parsePointer(patch.path);\n switch (patch.op) {\n case \"add\":\n // RFC 6902 `add` inserts into arrays (replace only for objects); `replace`\n // always overwrites. Threading the op keeps array indices correct.\n result = setAtPath(result, segments, patch.value, true);\n break;\n case \"replace\":\n result = setAtPath(result, segments, patch.value, false);\n break;\n case \"remove\":\n result = removeAtPath(result, segments);\n break;\n // move, copy, test — not used in v1 protocol\n }\n }\n\n return result;\n}\n\n/**\n * Parse a JSON Pointer (RFC 6901) into path segments.\n *\n * @remarks\n * Handles `~0` and `~1` escape sequences as specified by RFC 6901.\n * E.g., `\"/counter/value\"` becomes `[\"counter\", \"value\"]`.\n *\n * @param pointer - A JSON Pointer string (e.g. `\"/a/b/c\"`).\n * @returns An array of unescaped path segments.\n *\n * @internal\n */\nfunction parsePointer(pointer: string): string[] {\n if (pointer === \"\" || pointer === \"/\") return [];\n return pointer\n .slice(1) // Remove leading \"/\"\n .split(\"/\")\n .map((s) => s.replace(/~1/g, \"/\").replace(/~0/g, \"~\"));\n}\n\n/**\n * Immutably set a value at the given path segments within an object or array.\n *\n * @param obj - The root object.\n * @param segments - Path segments produced by {@link parsePointer}.\n * @param value - The value to set.\n * @returns A shallow-cloned tree with the value set.\n *\n * @internal\n */\n/**\n * Keys that must never be written from a JSON Pointer: assigning them walks the\n * prototype chain and pollutes `Object.prototype`. Patch contents originate from\n * a (possibly untrusted) peer store or hub, so the pointer is attacker-chosen.\n */\nconst FORBIDDEN_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\nfunction setAtPath(obj: any, segments: string[], value: unknown, insert: boolean): any {\n if (segments.length === 0) return value;\n\n const result = Array.isArray(obj) ? [...obj] : { ...obj };\n const [head, ...rest] = segments;\n\n // Prevent prototype pollution — ignore dangerous segments entirely.\n if (FORBIDDEN_KEYS.has(head)) return result;\n\n if (rest.length === 0) {\n if (insert && Array.isArray(result)) {\n // RFC 6902 `add` on an array: insert before `head` (shifting), or append\n // when `head` is the \"-\" end-of-array token.\n const index = head === \"-\" ? result.length : Number(head);\n if (Number.isInteger(index) && index >= 0 && index <= result.length) {\n result.splice(index, 0, value);\n } else {\n (result as any)[head] = value; // out-of-range / non-numeric: best-effort set\n }\n } else {\n result[head] = value;\n }\n } else {\n result[head] = setAtPath(result[head] ?? {}, rest, value, insert);\n }\n\n return result;\n}\n\n/**\n * Immutably remove the value at the given path segments within an object or array.\n *\n * @param obj - The root object.\n * @param segments - Path segments produced by {@link parsePointer}.\n * @returns A shallow-cloned tree with the value removed.\n *\n * @internal\n */\nfunction removeAtPath(obj: any, segments: string[]): any {\n if (segments.length === 0) return undefined;\n\n const result = Array.isArray(obj) ? [...obj] : { ...obj };\n const [head, ...rest] = segments;\n\n // Prevent prototype-chain traversal from attacker-controlled pointers.\n if (FORBIDDEN_KEYS.has(head)) return result;\n\n if (rest.length === 0) {\n if (Array.isArray(result)) {\n result.splice(Number(head), 1);\n } else {\n delete result[head];\n }\n } else {\n result[head] = removeAtPath(result[head], rest);\n }\n\n return result;\n}\n","/**\n * Framework-agnostic retry policy for one-shot request/response exchanges.\n *\n * @remarks\n * Some DevTools requests (e.g. `REQUEST_STATE`) expect a single response. If\n * that request races store registration or the hub handshake it can be\n * dropped, leaving the UI waiting forever. This helper re-issues the request\n * on a fixed interval until the response is observed, then stops. Extracted\n * from the hooks so the timing behaviour is unit-testable without React.\n *\n * @module @yoltra/devtools-ui\n */\n\n/**\n * Minimal timer surface, injectable so tests can drive it with fake timers.\n *\n * @internal\n */\nexport interface RetryScheduler {\n setInterval: (handler: () => void, ms: number) => ReturnType<typeof setInterval>;\n clearInterval: (id: ReturnType<typeof setInterval>) => void;\n}\n\n/**\n * Options for {@link startSnapshotRetry}.\n *\n * @internal\n */\nexport interface SnapshotRetryOptions {\n /** Re-issue the request. Called once per interval until settled. */\n request: () => void;\n /** Returns `true` once the awaited response has been observed. */\n isSettled: () => boolean;\n /** Interval between retries, in milliseconds. */\n intervalMs: number;\n /** Timer implementation. Defaults to the global timers. */\n scheduler?: RetryScheduler;\n}\n\n/**\n * Begin re-issuing `request` every `intervalMs` until `isSettled()` returns\n * `true`, at which point the retry stops on its own. Returns a cancel function\n * that stops it immediately (call on unmount / when the response arrives).\n *\n * @param options - See {@link SnapshotRetryOptions}.\n * @returns A function that cancels the retry loop.\n * @internal\n */\nexport function startSnapshotRetry(options: SnapshotRetryOptions): () => void {\n const scheduler: RetryScheduler = options.scheduler ?? {\n setInterval: (h, ms) => setInterval(h, ms),\n clearInterval: (id) => clearInterval(id),\n };\n\n let id: ReturnType<typeof setInterval> | null = scheduler.setInterval(() => {\n if (options.isSettled()) {\n cancel();\n return;\n }\n options.request();\n }, options.intervalMs);\n\n function cancel() {\n if (id !== null) {\n scheduler.clearInterval(id);\n id = null;\n }\n }\n\n return cancel;\n}\n","/**\n * Hook that provides a live, incrementally-patched view of a store's state.\n *\n * @remarks\n * On activation, the hook requests a full `STATE_SNAPSHOT` from the hub.\n * While waiting, any `STORE_EVENT` patches that arrive are buffered and\n * replayed once the snapshot lands. After that, each committed patch is\n * applied via {@link applyPatches} so the UI always reflects the latest\n * store state without requiring full snapshots.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { DevtoolsRole, type DevtoolsMessage, type JsonPatch } from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { applyPatches } from \"../utils/apply-patch\";\nimport { startSnapshotRetry } from \"./snapshotRetry\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * How often to re-send `REQUEST_STATE` until the first `STATE_SNAPSHOT`\n * arrives. A single request can be lost if it races store registration or\n * the hub handshake, which would otherwise hang the panel forever; retrying\n * until the baseline lands makes the State view self-heal.\n *\n * @internal\n */\nconst SNAPSHOT_RETRY_INTERVAL_MS = 1500;\n\n/**\n * Lazily fetches a store's full state and keeps it up-to-date via patches.\n *\n * @remarks\n * The state is initially `null` until a `STATE_SNAPSHOT` is received.\n * After that, incoming `STORE_EVENT` patches are applied incrementally\n * using {@link applyPatches}. Patches that arrive before the snapshot are\n * buffered and replayed in version order once the snapshot is available.\n *\n * Call `refresh()` to discard the current state and re-fetch from scratch.\n *\n * @example\n * ```tsx\n * import { useStoreState } from \"@yoltra/devtools-ui\";\n *\n * function StateInspector({ storeId }: { storeId: string }) {\n * const { state, version, loading, refresh } = useStoreState(storeId);\n * if (loading) return <p>Loading...</p>;\n * return (\n * <div>\n * <p>Version: {version}</p>\n * <pre>{JSON.stringify(state, null, 2)}</pre>\n * <button onClick={refresh}>Refresh</button>\n * </div>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID to track state for, or `null` to disable.\n * @returns An object with `state`, `version`, `loading`, and `refresh`.\n *\n * @public\n */\nexport function useStoreState(storeId: string | null): {\n state: unknown;\n version: number;\n loading: boolean;\n refresh: () => void;\n} {\n const { send, subscribe, extensionId } = useHubConnection();\n const [state, setState] = useState<unknown>(null);\n const [version, setVersion] = useState(0);\n const [loading, setLoading] = useState(false);\n const versionRef = useRef(0);\n const pendingPatchesRef = useRef<Array<{ patches: JsonPatch[]; version: number }>>([]);\n const cancelRetryRef = useRef<(() => void) | null>(null);\n\n const stopRetry = useCallback(() => {\n cancelRetryRef.current?.();\n cancelRetryRef.current = null;\n }, []);\n\n const requestState = useCallback(() => {\n if (!storeId) return;\n setLoading(true);\n send({\n type: \"REQUEST_STATE\",\n storeId,\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n }, [storeId, send, extensionId]);\n\n useEffect(() => {\n if (!storeId) {\n setState(null);\n setVersion(0);\n versionRef.current = 0;\n return;\n }\n\n requestState();\n\n // Keep re-requesting until the first snapshot lands. A single REQUEST_STATE\n // can be dropped if it races store registration / the handshake, which used\n // to hang the State (and time-travel baseline) view indefinitely. The\n // snapshot handler below cancels this the moment a snapshot arrives.\n cancelRetryRef.current = startSnapshotRetry({\n request: requestState,\n isSettled: () => versionRef.current !== 0,\n intervalMs: SNAPSHOT_RETRY_INTERVAL_MS,\n });\n\n const unsub = subscribe((msg: DevtoolsMessage) => {\n if (msg.type === \"STATE_SNAPSHOT\" && msg.storeId === storeId) {\n stopRetry();\n setState(msg.state);\n setVersion(msg.version);\n versionRef.current = msg.version;\n setLoading(false);\n\n // Apply any buffered patches that arrived after the snapshot version\n const pending = pendingPatchesRef.current\n .filter((p) => p.version > msg.version)\n .sort((a, b) => a.version - b.version);\n pendingPatchesRef.current = [];\n\n if (pending.length > 0) {\n setState((prev: any) => {\n let current = prev;\n for (const p of pending) {\n current = applyPatches(current, p.patches);\n }\n return current;\n });\n const lastVersion = pending[pending.length - 1].version;\n setVersion(lastVersion);\n versionRef.current = lastVersion;\n }\n return;\n }\n\n if (msg.type === \"STORE_EVENT\" && msg.storeId === storeId && msg.committed) {\n if (versionRef.current === 0) {\n // No snapshot yet — buffer patches\n pendingPatchesRef.current.push({\n patches: msg.patches,\n version: msg.snapshotVersion,\n });\n return;\n }\n\n if (msg.snapshotVersion <= versionRef.current) {\n // Already applied\n return;\n }\n\n setState((prev: any) => applyPatches(prev, msg.patches));\n setVersion(msg.snapshotVersion);\n versionRef.current = msg.snapshotVersion;\n }\n });\n\n return () => {\n unsub();\n stopRetry();\n pendingPatchesRef.current = [];\n };\n }, [storeId, subscribe, requestState, stopRetry]);\n\n return { state, version, loading, refresh: requestState };\n}\n","/**\n * Hook that fetches and caches subscription and consumer information for\n * a Yoltra store.\n *\n * @remarks\n * Sends a `REQUEST_SUBSCRIPTIONS` message to the hub and resolves when\n * the corresponding `STORE_SUBSCRIPTIONS` response arrives. The result\n * includes atomic, event, coarse, effect, middleware, and reducer\n * subscription breakdowns. Call `refresh()` to re-fetch.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport {\n DevtoolsRole,\n type DevtoolsMessage,\n type StoreSubscriptions,\n} from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Subscription data payload with protocol envelope fields stripped.\n *\n * @internal\n */\ntype SubscriptionData = Omit<\n StoreSubscriptions,\n \"type\" | \"timestamp\" | \"sourceId\" | \"sourceRole\" | \"storeId\"\n>;\n\n/**\n * Fetches and caches subscription/consumer info for a store.\n *\n * @remarks\n * On mount (or when `storeId` changes) the hook sends a\n * `REQUEST_SUBSCRIPTIONS` message and sets `loading` to `true`. When the\n * matching `STORE_SUBSCRIPTIONS` response arrives the data is cached and\n * `loading` flips to `false`.\n *\n * @example\n * ```tsx\n * import { useStoreSubscriptions } from \"@yoltra/devtools-ui\";\n *\n * function SubscriptionPanel({ storeId }: { storeId: string }) {\n * const { data, loading, refresh } = useStoreSubscriptions(storeId);\n * if (loading || !data) return <p>Loading...</p>;\n * return (\n * <div>\n * <p>Atomic: {data.atomic.length}</p>\n * <p>Event: {data.event.length}</p>\n * <button onClick={refresh}>Refresh</button>\n * </div>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID to query, or `null` to disable.\n * @returns An object with `data` ({@link SubscriptionData} or `null`),\n * `loading`, and `refresh`.\n *\n * @public\n */\nexport function useStoreSubscriptions(storeId: string | null): {\n data: SubscriptionData | null;\n loading: boolean;\n refresh: () => void;\n} {\n const { send, subscribe, extensionId } = useHubConnection();\n const [data, setData] = useState<SubscriptionData | null>(null);\n const [loading, setLoading] = useState(false);\n\n const requestSubscriptions = useCallback(() => {\n if (!storeId) return;\n setLoading(true);\n send({\n type: \"REQUEST_SUBSCRIPTIONS\",\n storeId,\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n }, [storeId, send, extensionId]);\n\n useEffect(() => {\n if (!storeId) {\n setData(null);\n return;\n }\n\n requestSubscriptions();\n\n const unsub = subscribe((msg: DevtoolsMessage) => {\n if (msg.type === \"STORE_SUBSCRIPTIONS\" && msg.storeId === storeId) {\n setData({\n atomic: msg.atomic,\n event: msg.event,\n coarse: msg.coarse,\n effects: msg.effects,\n middleware: msg.middleware,\n reducers: msg.reducers,\n });\n setLoading(false);\n }\n });\n\n return unsub;\n }, [storeId, subscribe, requestSubscriptions]);\n\n return { data, loading, refresh: requestSubscriptions };\n}\n","/**\n * Pure navigation math for time-travel stepping.\n *\n * @remarks\n * Kept separate from {@link useTimeTravel} so the boundary conditions — the\n * part that is easy to get wrong — are unit-testable without React. The hook\n * translates the returned action into `jumpTo` / `resume` calls.\n *\n * @module @yoltra/devtools-ui\n */\n\n/**\n * The action a step resolves to.\n *\n * @internal\n */\nexport type StepAction =\n | { kind: \"jump\"; index: number }\n | { kind: \"resume\" }\n | { kind: \"none\" };\n\n/**\n * Current navigation state.\n *\n * @internal\n */\nexport interface StepState {\n /** Whether a time-travel session is active. */\n isTimeTraveling: boolean;\n /** The viewed entry index while traveling (ignored when not). */\n currentIndex: number;\n /** Total number of recorded events. */\n entryCount: number;\n}\n\n/**\n * Resolve one `back` / `forward` step to a concrete action.\n *\n * - **back** — from live, start at the newest event; while traveling, move\n * relative to the current position. Steps to the previous event, or does\n * nothing at the very start.\n * - **forward** — only meaningful while traveling (nothing lies past live).\n * Advances one event, or resumes the live stream once the newest event is\n * reached so the store is resynced rather than left on a stale snapshot.\n *\n * @param direction - `\"back\"` or `\"forward\"`.\n * @param state - See {@link StepState}.\n * @returns The {@link StepAction} to apply.\n * @internal\n */\nexport function planStep(direction: \"back\" | \"forward\", state: StepState): StepAction {\n const { isTimeTraveling, currentIndex, entryCount } = state;\n const lastIndex = entryCount - 1;\n\n if (direction === \"back\") {\n const viewIndex = isTimeTraveling ? currentIndex : lastIndex;\n const target = viewIndex - 1;\n return target >= 0 ? { kind: \"jump\", index: target } : { kind: \"none\" };\n }\n\n // forward\n if (!isTimeTraveling) return { kind: \"none\" };\n const target = currentIndex + 1;\n // Step onto the newest event; only stepping *past* it returns to live so the\n // store is resynced rather than left on a stale reconstructed snapshot.\n return target > lastIndex ? { kind: \"resume\" } : { kind: \"jump\", index: target };\n}\n","/**\n * Forward state reconstruction from a baseline snapshot.\n *\n * @remarks\n * Extracted as a pure function for the same reason the step boundary maths was: it is the part\n * with the interesting failure modes, and inside a hook it can only be exercised through a\n * rendered component.\n *\n * The panel reconstructs state by replaying patches from the first snapshot forward. Doing that\n * from the beginning on every call made the cost of watching a store grow with the length of its\n * history — and live events arrive while the panel is open, each one triggering another full\n * replay. Carrying the previous result forward turns that back into the one entry that actually\n * arrived.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { applyPatches } from \"../utils/apply-patch\";\n\n/** Minimal shape of an event log entry needed to replay it. */\nexport interface ReplayableEntry {\n readonly snapshotVersion: number;\n readonly patches: Parameters<typeof applyPatches>[1];\n}\n\n/** The baseline a replay starts from. */\nexport interface ReplayBaseline {\n readonly state: unknown;\n readonly version: number;\n}\n\n/**\n * A previous reconstruction, reusable as the starting point for the next.\n *\n * @remarks\n * `marker` is the entry that occupied `index` when this state was built. It is what makes reuse\n * safe: the event log is capped and drops its oldest entries, which shifts every index left, so\n * a cache keyed on position alone would resume from an entry that is no longer the one it\n * recorded and produce a state that never existed.\n */\nexport interface ReplayCache {\n readonly index: number;\n readonly state: unknown;\n readonly marker: unknown;\n}\n\n/** Result of {@link replayState}: the state, and the cache to pass in next time. */\nexport interface ReplayResult {\n readonly state: unknown;\n readonly cache: ReplayCache | null;\n}\n\n/**\n * Rebuilds the state as of `index`, continuing from `cache` when it is still valid.\n *\n * @param args.baseline - First snapshot received, and the version it was taken at.\n * @param args.entries - Event log, oldest first.\n * @param args.index - Position to rebuild to. Negative means \"just the baseline\".\n * @param args.cache - Result of a previous call, or `null`.\n *\n * @returns The reconstructed state and the cache for the next call.\n *\n * @remarks\n * Only moves forward. Patches describe a change rather than its inverse, so scrubbing backwards\n * starts again from the baseline — a cost bounded by how fast somebody can drag a slider, which\n * is not the case that made the panel unusable.\n *\n * @public\n */\nexport function replayState(args: {\n baseline: ReplayBaseline | null;\n entries: readonly ReplayableEntry[];\n index: number;\n cache: ReplayCache | null;\n}): ReplayResult {\n const { baseline, entries, index, cache } = args;\n if (baseline === null) return { state: null, cache: null };\n\n let start = 0;\n let state = baseline.state;\n\n // Reusable only when it points at the same entry it was built against, and only for a\n // position at or after it.\n if (cache !== null && cache.index <= index && entries[cache.index] === cache.marker) {\n start = cache.index + 1;\n state = cache.state;\n }\n\n for (let i = start; i <= index; i++) {\n const entry = entries[i];\n if (entry === undefined) break;\n // Entries older than the baseline are already reflected in it.\n if (entry.snapshotVersion <= baseline.version) continue;\n state = applyPatches(state, entry.patches);\n }\n\n const marker = entries[index];\n return {\n state,\n cache: index >= 0 && marker !== undefined ? { index, state, marker } : null,\n };\n}\n","/**\n * Hook that enables time-travel debugging by navigating through an event log\n * and sending `TIME_TRAVEL` commands to a Yoltra store.\n *\n * @remarks\n * Works in concert with {@link useEventLog} -- pass its `entries` array to\n * this hook. Jumping to a specific index sends a `TIME_TRAVEL` message with\n * the corresponding snapshot version so the store can restore that point in\n * time. Call `resume()` to return to live state.\n *\n * @module @yoltra/devtools-ui\n */\n\nimport { DevtoolsRole, type DevtoolsMessage } from \"@yoltra/devtools-protocol\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { EventLogEntry } from \"../types\";\nimport { planStep } from \"./timeTravelNav\";\nimport { replayState, type ReplayCache } from \"./stateReplay\";\nimport { useHubConnection } from \"./useHubConnection\";\n\n/**\n * Provides time-travel navigation through the event log.\n *\n * @remarks\n * The hook tracks whether the user is actively time-traveling via\n * `isTimeTraveling`. While traveling, `currentIndex` indicates the position\n * within the `entries` array. The `stepBack` / `stepForward` helpers move\n * one entry at a time, while `jumpTo` allows arbitrary positioning. Calling\n * `resume()` exits time-travel mode, jumps to the latest entry, and resets\n * the index to `-1`.\n *\n * @example\n * ```tsx\n * import { useEventLog, useTimeTravel } from \"@yoltra/devtools-ui\";\n *\n * function TimeTravelControls({ storeId }: { storeId: string }) {\n * const { entries } = useEventLog(storeId);\n * const { currentIndex, isTimeTraveling, stepBack, stepForward, resume } =\n * useTimeTravel(storeId, entries);\n *\n * return (\n * <div>\n * <button onClick={stepBack} disabled={currentIndex <= 0}>Back</button>\n * <button onClick={stepForward}>Forward</button>\n * {isTimeTraveling && <button onClick={resume}>Resume Live</button>}\n * <span>Index: {currentIndex} / {entries.length - 1}</span>\n * </div>\n * );\n * }\n * ```\n *\n * @param storeId - The store ID to time-travel, or `null` to disable.\n * @param entries - The event log entries (typically from {@link useEventLog}).\n * @returns An object with `currentIndex`, `isTimeTraveling`, `jumpTo`,\n * `stepBack`, `stepForward`, and `resume`.\n *\n * @public\n */\nexport function useTimeTravel(\n storeId: string | null,\n entries: EventLogEntry[],\n /**\n * Whether the selected store advertised the `replay` capability. Time-travel\n * commands are gated on it (DEV-4): the agent/core enforce it too, but the UI\n * should not send commands that will be dropped. Defaults to `true`.\n */\n canReplay: boolean = true,\n): {\n currentIndex: number;\n isTimeTraveling: boolean;\n /**\n * The store state reconstructed at the currently-viewed position (the\n * scrubbed index while traveling, otherwise the latest entry). `null` until\n * the baseline snapshot has been captured. Lets consumers render a live\n * preview of the state at any point in history.\n */\n previewState: unknown;\n /**\n * The event count the timeline is measured against: frozen at travel-start\n * so a live-emitting store cannot shift the scrubber, or `null` when live\n * (use `entries.length`). Consumers should size the slider/label off\n * `frameCount ?? entries.length`.\n */\n frameCount: number | null;\n jumpTo: (index: number) => void;\n stepBack: () => void;\n stepForward: () => void;\n resume: () => void;\n} {\n const { send, subscribe, extensionId } = useHubConnection();\n const [currentIndex, setCurrentIndex] = useState(-1);\n const [isTimeTraveling, setIsTimeTraveling] = useState(false);\n\n // The event count frozen at the moment a travel session begins. While\n // traveling, the timeline (slider range, position label, forward boundary)\n // is measured against this frame instead of the live `entries.length`, so a\n // store that keeps emitting does not shift the scrubber under the user —\n // otherwise a fixed position appears to drift backward as the log grows.\n // `null` when live.\n const [frameCount, setFrameCount] = useState<number | null>(null);\n\n // The first STATE_SNAPSHOT received for this store, used as the baseline\n // for forward-replay state reconstruction. Only the first snapshot is\n // captured (the connect-time snapshot); time-travel response snapshots\n // sent back by the store are intentionally ignored so they don't overwrite\n // the baseline and corrupt future reconstructions.\n const baselineRef = useRef<{ state: unknown; version: number } | null>(null);\n\n /**\n * The most recent reconstruction, so the next one can continue from it.\n *\n * @remarks\n * `marker` is the entry object that occupied `index` when the state was computed. Comparing it\n * on reuse is what makes the cache safe against the event log dropping its oldest entries and\n * shifting every index underneath us.\n */\n const cacheRef = useRef<ReplayCache | null>(null);\n\n useEffect(() => {\n // Reset baseline whenever the target store changes so a fresh snapshot\n // is captured for the new store. The reconstruction cache goes with it: it holds a state\n // built from the previous store's baseline, and reusing that against a new one would\n // silently produce a state neither store ever had.\n baselineRef.current = null;\n cacheRef.current = null;\n setFrameCount(null);\n if (!storeId) return;\n\n return subscribe((msg: DevtoolsMessage) => {\n if (\n msg.type === \"STATE_SNAPSHOT\" &&\n msg.storeId === storeId &&\n baselineRef.current === null\n ) {\n baselineRef.current = { state: msg.state, version: msg.version };\n }\n });\n }, [storeId, subscribe]);\n\n // Reconstruct the store state at a specific entries index by applying\n // each entry's patches sequentially from the baseline forward.\n // Returns null when no baseline has been captured yet (devtools still\n // waiting for the first STATE_SNAPSHOT).\n const buildStateAt = useCallback(\n (index: number): unknown => {\n const { state, cache } = replayState({\n baseline: baselineRef.current,\n entries,\n index,\n cache: cacheRef.current,\n });\n cacheRef.current = cache;\n return state;\n },\n [entries],\n );\n\n const jumpTo = useCallback(\n (index: number) => {\n // Do not send if the store can't replay (DEV-4) — the command would be a no-op.\n if (!storeId || !canReplay || index < 0 || index >= entries.length) return;\n\n // Freeze the timeline frame the first time we leave live, so subsequent\n // live events don't grow the slider range under the user.\n setFrameCount((prev) => prev ?? entries.length);\n setCurrentIndex(index);\n setIsTimeTraveling(true);\n\n const entry = entries[index]!;\n send({\n type: \"TIME_TRAVEL\",\n storeId,\n state: buildStateAt(index),\n snapshotVersion: entry.snapshotVersion,\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n },\n [storeId, entries, send, buildStateAt, canReplay, extensionId],\n );\n\n const resume = useCallback(() => {\n setIsTimeTraveling(false);\n setCurrentIndex(-1);\n setFrameCount(null);\n // Restore the store to the latest known state by computing the state at\n // the last entry and sending a TIME_TRAVEL to snap it back (DEV-4: only if\n // the store can replay; the local reset above still runs regardless).\n if (canReplay && storeId && entries.length > 0) {\n const latestIndex = entries.length - 1;\n const latest = entries[latestIndex]!;\n send({\n type: \"TIME_TRAVEL\",\n storeId,\n state: buildStateAt(latestIndex),\n snapshotVersion: latest.snapshotVersion,\n timestamp: new Date().toISOString(),\n sourceId: extensionId,\n sourceRole: DevtoolsRole.EXTENSION,\n });\n }\n }, [storeId, entries, send, buildStateAt, canReplay, extensionId]);\n\n // While traveling the timeline is measured against the frozen frame, so\n // steps and boundaries ignore events that arrive mid-session.\n const timelineCount = frameCount ?? entries.length;\n\n const stepBack = useCallback(() => {\n const action = planStep(\"back\", {\n isTimeTraveling,\n currentIndex,\n entryCount: timelineCount,\n });\n if (action.kind === \"jump\") jumpTo(action.index);\n }, [isTimeTraveling, currentIndex, timelineCount, jumpTo]);\n\n const stepForward = useCallback(() => {\n const action = planStep(\"forward\", {\n isTimeTraveling,\n currentIndex,\n entryCount: timelineCount,\n });\n if (action.kind === \"jump\") jumpTo(action.index);\n else if (action.kind === \"resume\") resume();\n }, [isTimeTraveling, currentIndex, timelineCount, jumpTo, resume]);\n\n // The state at the currently-viewed position: the scrubbed index while\n // traveling, otherwise the newest entry. Recomputed from the baseline so the\n // preview follows both the slider and newly-arriving live events.\n const previewState = useMemo(\n () => buildStateAt(isTimeTraveling ? currentIndex : entries.length - 1),\n [buildStateAt, isTimeTraveling, currentIndex, entries.length],\n );\n\n return {\n currentIndex,\n isTimeTraveling,\n previewState,\n frameCount,\n jumpTo,\n stepBack,\n stepForward,\n resume,\n };\n}\n","/**\n * @module @yoltra/devtools-ui\n *\n * In-memory loopback transport: run the store agent, the hub, and the panel all\n * in one process (a browser tab or a test) with **no network socket**. This is\n * what powers the embeddable, zero-install DevTools demo — and it exercises the\n * exact same protocol the real WebSocket hub does, only over an in-page channel.\n */\n\nimport {\n DevtoolsRole,\n PROTOCOL_VERSION,\n type DevtoolsSocketCallbacks,\n type DevtoolsSocketFactory,\n type DevtoolsSocketHandle,\n} from \"@yoltra/devtools-protocol\";\n\n// Standard WebSocket readyState values.\nconst CONNECTING = 0;\nconst OPEN = 1;\nconst CLOSING = 2;\nconst CLOSED = 3;\n\n/** A party connected to the in-memory broker (agent or extension). */\ninterface Peer {\n role: DevtoolsRole | null; // resolved at handshake\n id: string | null;\n storeInfo?: { name: string; capabilities: unknown };\n deliver: (raw: string) => void; // push a message TO this peer's socket\n closed: boolean;\n}\n\n/** Hub-authored envelope fields (mirrors the real hub's `baseMsg`). */\nfunction hubMeta() {\n return {\n timestamp: new Date().toISOString(),\n sourceId: \"loopback-hub\",\n sourceRole: DevtoolsRole.HUB,\n };\n}\n\nfunction majorOf(version: string): string {\n return version.split(\".\")[0] ?? \"0\";\n}\n\n/**\n * A tiny in-process hub that speaks the DevTools protocol without any socket.\n * Stores fan their messages out to every extension; extension commands route to\n * the target store by `storeId`. Same routing contract as the real hub, minus\n * the WebSocket server (and its history buffer / origin checks, which are moot\n * in a single trusted process).\n */\nclass LoopbackBroker {\n private readonly peers = new Set<Peer>();\n\n add(deliver: (raw: string) => void): Peer {\n const peer: Peer = { role: null, id: null, deliver, closed: false };\n this.peers.add(peer);\n return peer;\n }\n\n remove(peer: Peer): void {\n if (!this.peers.delete(peer)) return;\n peer.closed = true;\n if (peer.role === DevtoolsRole.STORE && peer.id) {\n const msg = JSON.stringify({\n type: \"STORE_DISCONNECTED\",\n storeId: peer.id,\n reason: \"disconnected\",\n ...hubMeta(),\n });\n this.eachExtension((p) => p.deliver(msg));\n }\n }\n\n receive(peer: Peer, raw: string): void {\n if (peer.closed) return;\n let msg: Record<string, unknown> | null;\n try {\n msg = JSON.parse(raw);\n } catch {\n return;\n }\n if (msg === null || typeof msg !== \"object\" || typeof msg.type !== \"string\") return;\n\n if (peer.role === null) {\n this.handshake(peer, msg);\n return;\n }\n\n if (peer.role === DevtoolsRole.STORE) {\n // A store's messages fan out to every connected extension.\n this.eachExtension((p) => p.deliver(raw));\n } else if (typeof msg.storeId === \"string\") {\n // An extension's commands route to the target store.\n for (const p of this.peers) {\n if (p.role === DevtoolsRole.STORE && p.id === msg.storeId) p.deliver(raw);\n }\n }\n }\n\n private handshake(peer: Peer, msg: Record<string, any>): void {\n if (msg.type !== \"HANDSHAKE_REQUEST\") return;\n\n const role = msg.role as DevtoolsRole;\n const id =\n role === DevtoolsRole.STORE\n ? msg.store?.id\n : role === DevtoolsRole.EXTENSION\n ? msg.extension?.id\n : undefined;\n const compatible = majorOf(String(msg.protocolVersion ?? \"\")) === majorOf(PROTOCOL_VERSION);\n const success = compatible && typeof id === \"string\";\n\n peer.deliver(\n JSON.stringify({\n type: \"HANDSHAKE_RESPONSE\",\n success,\n // `negotiatedVersion`, the name the interface and the real hub use. This sent\n // `protocolVersion` instead — harmless only for as long as nothing reads it, and the\n // moment something did, the embedded panel would have failed where the real hub worked,\n // in the one path with no server to inspect.\n negotiatedVersion: PROTOCOL_VERSION,\n ...hubMeta(),\n ...(success ? {} : { error: \"Loopback handshake rejected (version or id mismatch)\" }),\n }),\n );\n if (!success) return;\n\n peer.role = role;\n peer.id = id as string;\n\n if (role === DevtoolsRole.STORE) {\n peer.storeInfo = { name: msg.store?.name ?? id, capabilities: msg.store?.capabilities };\n const connected = JSON.stringify({\n type: \"STORE_CONNECTED\",\n store: { id, name: peer.storeInfo.name, capabilities: peer.storeInfo.capabilities },\n ...hubMeta(),\n });\n this.eachExtension((p) => p.deliver(connected));\n } else {\n const stores = [...this.peers]\n .filter((p) => p.role === DevtoolsRole.STORE && p.storeInfo)\n .map((p) => ({\n id: p.id,\n name: p.storeInfo!.name,\n status: \"connected\" as const,\n capabilities: p.storeInfo!.capabilities,\n connectedAt: new Date().toISOString(),\n }));\n peer.deliver(JSON.stringify({ type: \"STORE_REGISTRY\", stores, ...hubMeta() }));\n }\n }\n\n private eachExtension(fn: (p: Peer) => void): void {\n for (const p of this.peers) if (p.role === DevtoolsRole.EXTENSION) fn(p);\n }\n}\n\n/**\n * A loopback hub instance. Wire the agent and the panel to the *same* instance.\n *\n * @public\n */\nexport interface LoopbackHub {\n /** Inject into the browser agent: `withDevtools(store, { socketFactory })`. */\n agentSocketFactory: DevtoolsSocketFactory;\n /** Pass to the DevTools UI as `config.WebSocket` (e.g. `<DevtoolsApp>`). */\n WebSocket: { new (url: string): WebSocket };\n}\n\n/**\n * Creates a self-contained in-memory DevTools hub plus the two client transports\n * that connect to it — a `socketFactory` for the store agent and a\n * `WebSocket`-compatible class for the panel UI. No ports, no server, no\n * extension: everything runs in the current process.\n *\n * @example\n * ```ts\n * const hub = createLoopbackHub();\n * withDevtools(store, { port: 0, socketFactory: hub.agentSocketFactory });\n * // <DevtoolsApp config={{ port: 0, WebSocket: hub.WebSocket }} />\n * ```\n *\n * @public\n */\nexport function createLoopbackHub(): LoopbackHub {\n const broker = new LoopbackBroker();\n\n const agentSocketFactory: DevtoolsSocketFactory = (\n _url: string,\n callbacks: DevtoolsSocketCallbacks,\n ): DevtoolsSocketHandle => {\n let readyState = CONNECTING;\n const peer = broker.add((raw) => callbacks.onMessage(raw));\n queueMicrotask(() => {\n readyState = OPEN;\n callbacks.onOpen();\n });\n return {\n get readyState() {\n return readyState;\n },\n send: (data) => broker.receive(peer, data),\n close: () => {\n if (readyState === CLOSED) return;\n readyState = CLOSED;\n broker.remove(peer);\n callbacks.onClose();\n },\n dispose: () => {\n peer.closed = true;\n },\n };\n };\n\n class LoopbackWebSocket {\n static readonly CONNECTING = CONNECTING;\n static readonly OPEN = OPEN;\n static readonly CLOSING = CLOSING;\n static readonly CLOSED = CLOSED;\n\n onopen: (() => void) | null = null;\n onmessage: ((ev: { data: string }) => void) | null = null;\n onclose: (() => void) | null = null;\n onerror: (() => void) | null = null;\n readyState = CONNECTING;\n\n private readonly peer: Peer;\n\n constructor(_url: string) {\n this.peer = broker.add((raw) => this.onmessage?.({ data: raw }));\n queueMicrotask(() => {\n this.readyState = OPEN;\n this.onopen?.();\n });\n }\n\n send(data: string): void {\n broker.receive(this.peer, data);\n }\n\n close(): void {\n if (this.readyState === CLOSED) return;\n this.readyState = CLOSED;\n broker.remove(this.peer);\n this.onclose?.();\n }\n }\n\n return {\n agentSocketFactory,\n WebSocket: LoopbackWebSocket as unknown as { new (url: string): WebSocket },\n };\n}\n"],"names":["noop","HubContext","createContext","REACT_ELEMENT_TYPE","REACT_FRAGMENT_TYPE","jsxProd","type","config","maybeKey","key","propName","reactJsxRuntime_production","getComponentNameFromType","REACT_CLIENT_REFERENCE","REACT_PROFILER_TYPE","REACT_STRICT_MODE_TYPE","REACT_SUSPENSE_TYPE","REACT_SUSPENSE_LIST_TYPE","REACT_ACTIVITY_TYPE","REACT_PORTAL_TYPE","REACT_CONTEXT_TYPE","REACT_CONSUMER_TYPE","REACT_FORWARD_REF_TYPE","innerType","REACT_MEMO_TYPE","REACT_LAZY_TYPE","testStringCoercion","value","checkKeyStringCoercion","JSCompiler_inline_result","JSCompiler_temp_const","JSCompiler_inline_result$jscomp$0","getTaskName","name","getOwner","dispatcher","ReactSharedInternals","UnknownOwner","hasValidKey","hasOwnProperty","getter","defineKeyPropWarningGetter","props","displayName","warnAboutAccessingKey","specialPropKeyWarningShown","elementRefGetterWithDeprecationWarning","componentName","didWarnAboutElementRef","ReactElement","self","source","owner","debugStack","debugTask","jsxDEVImpl","isStaticChildren","children","isArrayImpl","validateChildKeys","keys","k","didWarnAboutKeySpread","node","React","require$$0","createTask","callStackForError","unknownOwnerDebugStack","unknownOwnerDebugTask","reactJsxRuntime_development","trackActualOwner","jsxRuntimeModule","require$$1","DEFAULT_CAPABILITIES","RECONNECT_BASE_MS","RECONNECT_MAX_MS","HubProvider","status","setStatus","useState","wsRef","useRef","subscribersRef","reconnectAttemptRef","reconnectTimerRef","extensionIdRef","configRef","connect","useCallback","cfg","url","WS","ws","handshake","PROTOCOL_VERSION","DevtoolsRole","event","raw","msg","handler","autoReconnect","maxAttempts","attempt","delay","disconnect","reconnect","send","message","subscribe","useEffect","jsx","useHubConnection","useContext","useEventEmitter","storeId","extensionId","channel","payload","DEFAULT_MAX_ENTRIES","useEventLog","options","maxEntries","allEntriesRef","maxRef","setTick","sid","entry","next","t","clear","useEventReplay","snapshot","events","e","useStoreMetrics","metrics","setMetrics","loading","setLoading","intervalMs","timerRef","requestMetrics","unsub","useStoreRegistry","stores","setStores","prev","s","applyPatches","target","patches","result","patch","segments","parsePointer","setAtPath","removeAtPath","pointer","FORBIDDEN_KEYS","obj","insert","head","rest","index","startSnapshotRetry","scheduler","h","ms","id","cancel","SNAPSHOT_RETRY_INTERVAL_MS","useStoreState","state","setState","version","setVersion","versionRef","pendingPatchesRef","cancelRetryRef","stopRetry","requestState","pending","p","a","b","current","lastVersion","useStoreSubscriptions","data","setData","requestSubscriptions","planStep","direction","isTimeTraveling","currentIndex","entryCount","lastIndex","replayState","args","baseline","entries","cache","start","i","marker","useTimeTravel","canReplay","setCurrentIndex","setIsTimeTraveling","frameCount","setFrameCount","baselineRef","cacheRef","buildStateAt","jumpTo","resume","latestIndex","latest","timelineCount","stepBack","action","stepForward","previewState","useMemo","CONNECTING","OPEN","CLOSING","CLOSED","hubMeta","majorOf","LoopbackBroker","deliver","peer","role","success","connected","fn","createLoopbackHub","broker","agentSocketFactory","_url","callbacks","readyState","_LoopbackWebSocket","LoopbackWebSocket"],"mappings":"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","x_google_ignoreList":[1,2,3]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { HubConnectionConfig } from '../types';
|
|
2
|
+
import { HubConnectionConfig } from '../types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Provides hub connection context to all child hooks and components.
|
|
5
5
|
*
|
|
@@ -31,4 +31,4 @@ import { HubConnectionConfig } from '../types';
|
|
|
31
31
|
export declare function HubProvider({ config, children, }: {
|
|
32
32
|
config: HubConnectionConfig;
|
|
33
33
|
children: ReactNode;
|
|
34
|
-
}): import("react
|
|
34
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { applyPatches } from '../utils/apply-patch.js';
|
|
2
|
+
/** Minimal shape of an event log entry needed to replay it. */
|
|
3
|
+
export interface ReplayableEntry {
|
|
4
|
+
readonly snapshotVersion: number;
|
|
5
|
+
readonly patches: Parameters<typeof applyPatches>[1];
|
|
6
|
+
}
|
|
7
|
+
/** The baseline a replay starts from. */
|
|
8
|
+
export interface ReplayBaseline {
|
|
9
|
+
readonly state: unknown;
|
|
10
|
+
readonly version: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A previous reconstruction, reusable as the starting point for the next.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* `marker` is the entry that occupied `index` when this state was built. It is what makes reuse
|
|
17
|
+
* safe: the event log is capped and drops its oldest entries, which shifts every index left, so
|
|
18
|
+
* a cache keyed on position alone would resume from an entry that is no longer the one it
|
|
19
|
+
* recorded and produce a state that never existed.
|
|
20
|
+
*/
|
|
21
|
+
export interface ReplayCache {
|
|
22
|
+
readonly index: number;
|
|
23
|
+
readonly state: unknown;
|
|
24
|
+
readonly marker: unknown;
|
|
25
|
+
}
|
|
26
|
+
/** Result of {@link replayState}: the state, and the cache to pass in next time. */
|
|
27
|
+
export interface ReplayResult {
|
|
28
|
+
readonly state: unknown;
|
|
29
|
+
readonly cache: ReplayCache | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Rebuilds the state as of `index`, continuing from `cache` when it is still valid.
|
|
33
|
+
*
|
|
34
|
+
* @param args.baseline - First snapshot received, and the version it was taken at.
|
|
35
|
+
* @param args.entries - Event log, oldest first.
|
|
36
|
+
* @param args.index - Position to rebuild to. Negative means "just the baseline".
|
|
37
|
+
* @param args.cache - Result of a previous call, or `null`.
|
|
38
|
+
*
|
|
39
|
+
* @returns The reconstructed state and the cache for the next call.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Only moves forward. Patches describe a change rather than its inverse, so scrubbing backwards
|
|
43
|
+
* starts again from the baseline — a cost bounded by how fast somebody can drag a slider, which
|
|
44
|
+
* is not the case that made the panel unusable.
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
export declare function replayState(args: {
|
|
49
|
+
baseline: ReplayBaseline | null;
|
|
50
|
+
entries: readonly ReplayableEntry[];
|
|
51
|
+
index: number;
|
|
52
|
+
cache: ReplayCache | null;
|
|
53
|
+
}): ReplayResult;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
* Used by both `@yoltra/devtools-storeview` (React DOM) and `@yoltra/devtools-cli` (Ink).
|
|
6
6
|
* This package contains no UI components — only logic.
|
|
7
7
|
*/
|
|
8
|
-
export { HubContext } from './context/HubContext';
|
|
9
|
-
export { HubProvider } from './context/HubProvider';
|
|
10
|
-
export { useEventEmitter } from './hooks/useEventEmitter';
|
|
11
|
-
export { useEventLog } from './hooks/useEventLog';
|
|
12
|
-
export { useEventReplay } from './hooks/useEventReplay';
|
|
13
|
-
export { useHubConnection } from './hooks/useHubConnection';
|
|
14
|
-
export { useStoreMetrics } from './hooks/useStoreMetrics';
|
|
15
|
-
export { useStoreRegistry } from './hooks/useStoreRegistry';
|
|
16
|
-
export { useStoreState } from './hooks/useStoreState';
|
|
17
|
-
export { useStoreSubscriptions } from './hooks/useStoreSubscriptions';
|
|
18
|
-
export { useTimeTravel } from './hooks/useTimeTravel';
|
|
19
|
-
export { applyPatches } from './utils/apply-patch';
|
|
20
|
-
export { createLoopbackHub } from './transport/loopback';
|
|
21
|
-
export type { LoopbackHub } from './transport/loopback';
|
|
22
|
-
export type { EventLogEntry, HubConnectionConfig, HubConnectionStatus, HubContextValue, RegisteredStore, } from './types';
|
|
23
|
-
export type { UseEventLogOptions } from './hooks/useEventLog';
|
|
24
|
-
export type { UseStoreMetricsOptions } from './hooks/useStoreMetrics';
|
|
8
|
+
export { HubContext } from './context/HubContext.js';
|
|
9
|
+
export { HubProvider } from './context/HubProvider.js';
|
|
10
|
+
export { useEventEmitter } from './hooks/useEventEmitter.js';
|
|
11
|
+
export { useEventLog } from './hooks/useEventLog.js';
|
|
12
|
+
export { useEventReplay } from './hooks/useEventReplay.js';
|
|
13
|
+
export { useHubConnection } from './hooks/useHubConnection.js';
|
|
14
|
+
export { useStoreMetrics } from './hooks/useStoreMetrics.js';
|
|
15
|
+
export { useStoreRegistry } from './hooks/useStoreRegistry.js';
|
|
16
|
+
export { useStoreState } from './hooks/useStoreState.js';
|
|
17
|
+
export { useStoreSubscriptions } from './hooks/useStoreSubscriptions.js';
|
|
18
|
+
export { useTimeTravel } from './hooks/useTimeTravel.js';
|
|
19
|
+
export { applyPatches } from './utils/apply-patch.js';
|
|
20
|
+
export { createLoopbackHub } from './transport/loopback.js';
|
|
21
|
+
export type { LoopbackHub } from './transport/loopback.js';
|
|
22
|
+
export type { EventLogEntry, HubConnectionConfig, HubConnectionStatus, HubContextValue, RegisteredStore, } from './types.js';
|
|
23
|
+
export type { UseEventLogOptions } from './hooks/useEventLog.js';
|
|
24
|
+
export type { UseStoreMetricsOptions } from './hooks/useStoreMetrics.js';
|
package/dist/types/types.d.ts
CHANGED
|
@@ -120,6 +120,16 @@ export interface EventLogEntry {
|
|
|
120
120
|
* @public
|
|
121
121
|
*/
|
|
122
122
|
export interface HubContextValue {
|
|
123
|
+
/**
|
|
124
|
+
* This panel's identity, as given to the hub at handshake.
|
|
125
|
+
*
|
|
126
|
+
* @remarks
|
|
127
|
+
* Stamped on every message the panel sends. It used to send an empty string, so the hub could
|
|
128
|
+
* not tell one panel's commands from another's — with several open, or an authenticated hub
|
|
129
|
+
* auditing who drove a store, there was nothing to attribute a time-travel or an injected
|
|
130
|
+
* event to.
|
|
131
|
+
*/
|
|
132
|
+
extensionId: string;
|
|
123
133
|
/** Current connection status. */
|
|
124
134
|
status: HubConnectionStatus;
|
|
125
135
|
/** Send a protocol message to the hub. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoltra/devtools-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared React hooks and business logic for Yoltra DevTools UIs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"url": "https://github.com/yoltra/yoltra/issues"
|
|
24
24
|
},
|
|
25
25
|
"type": "module",
|
|
26
|
-
"main": "dist/devtools-ui.cjs
|
|
27
|
-
"module": "dist/devtools-ui.
|
|
26
|
+
"main": "dist/devtools-ui.cjs",
|
|
27
|
+
"module": "dist/devtools-ui.mjs",
|
|
28
28
|
"types": "dist/types/index.d.ts",
|
|
29
29
|
"exports": {
|
|
30
30
|
".": {
|
|
31
31
|
"types": "./dist/types/index.d.ts",
|
|
32
|
-
"import": "./dist/devtools-ui.
|
|
33
|
-
"require": "./dist/devtools-ui.cjs
|
|
32
|
+
"import": "./dist/devtools-ui.mjs",
|
|
33
|
+
"require": "./dist/devtools-ui.cjs"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
@@ -41,18 +41,19 @@
|
|
|
41
41
|
"react": "^18 || ^19"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@yoltra/devtools-protocol": "0.
|
|
44
|
+
"@yoltra/devtools-protocol": "0.5.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"react": "19.1.1",
|
|
48
47
|
"@types/react": "^19.1.8",
|
|
48
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
49
|
+
"react": "19.1.1",
|
|
49
50
|
"typedoc": "^0.28.13",
|
|
50
|
-
"typedoc-plugin-markdown": "4.9.0",
|
|
51
51
|
"typedoc-plugin-localization": "3.0.6",
|
|
52
|
+
"typedoc-plugin-markdown": "4.9.0",
|
|
52
53
|
"typescript": "5.9.3",
|
|
53
54
|
"vite": "^7.1.11",
|
|
54
|
-
"vite-plugin-dts": "^4.5.4",
|
|
55
55
|
"vite-plugin-banner": "0.8.1",
|
|
56
|
+
"vite-plugin-dts": "^4.5.4",
|
|
56
57
|
"vitest": "3.2.4"
|
|
57
58
|
},
|
|
58
59
|
"engines": {
|
|
@@ -61,13 +62,25 @@
|
|
|
61
62
|
"publishConfig": {
|
|
62
63
|
"access": "public"
|
|
63
64
|
},
|
|
65
|
+
"sizeLimit": [
|
|
66
|
+
{
|
|
67
|
+
"name": "barrel",
|
|
68
|
+
"limitKb": 8,
|
|
69
|
+
"external": [
|
|
70
|
+
"react",
|
|
71
|
+
"@yoltra/devtools-protocol"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
64
75
|
"scripts": {
|
|
65
|
-
"build": "vite build",
|
|
66
|
-
"test": "vitest --watch=false",
|
|
76
|
+
"build": "vite build && node ../../tools/repo-tools/bin/dts-extensions.mjs dist/types",
|
|
77
|
+
"test": "vitest --watch=false --coverage",
|
|
67
78
|
"lint": "node ../../tools/repo-tools/bin/repo-eslint.cjs --report-unused-disable-directives --max-warnings 0",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
69
|
-
"docs": "rushx docs:js && rushx docs:md",
|
|
70
|
-
"docs:
|
|
71
|
-
"docs:
|
|
79
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.vitest.json --noEmit",
|
|
80
|
+
"docs": "rushx docs:js && rushx docs:md && rushx docs:stamp",
|
|
81
|
+
"docs:stamp": "node ../../tools/repo-tools/bin/docs-stamp.mjs docs",
|
|
82
|
+
"docs:md": "typedoc --options ./typedoc.json",
|
|
83
|
+
"docs:js": "typedoc --options ./typedoc.json --json ./.typedoc/devtools-ui-en.json",
|
|
84
|
+
"size": "node ../../tools/repo-tools/bin/size-check.cjs"
|
|
72
85
|
}
|
|
73
86
|
}
|
package/dist/devtools-ui.cjs.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react"),b=require("@yoltra/devtools-protocol"),Y=()=>{},Q=a.createContext({status:"disconnected",send:Y,subscribe:()=>Y,disconnect:Y,reconnect:Y});var U={exports:{}},M={};var se;function pe(){if(se)return M;se=1;var e=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function n(o,r,c){var i=null;if(c!==void 0&&(i=""+c),r.key!==void 0&&(i=""+r.key),"key"in r){c={};for(var u in r)u!=="key"&&(c[u]=r[u])}else c=r;return r=c.ref,{$$typeof:e,type:o,key:i,ref:r!==void 0?r:null,props:c}}return M.Fragment=s,M.jsx=n,M.jsxs=n,M}var V={};var oe;function Re(){return oe||(oe=1,process.env.NODE_ENV!=="production"&&(function(){function e(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===de?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case p:return"Fragment";case N:return"Profiler";case v:return"StrictMode";case k:return"Suspense";case x:return"SuspenseList";case fe:return"Activity"}if(typeof t=="object")switch(typeof t.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),t.$$typeof){case C:return"Portal";case E:return(t.displayName||"Context")+".Provider";case w:return(t._context.displayName||"Context")+".Consumer";case m:var l=t.render;return t=t.displayName,t||(t=l.displayName||l.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case A:return l=t.displayName||null,l!==null?l:e(t.type)||"Memo";case D:l=t._payload,t=t._init;try{return e(t(l))}catch{}}return null}function s(t){return""+t}function n(t){try{s(t);var l=!1}catch{l=!0}if(l){l=console;var T=l.error,y=typeof Symbol=="function"&&Symbol.toStringTag&&t[Symbol.toStringTag]||t.constructor.name||"Object";return T.call(l,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",y),s(t)}}function o(t){if(t===p)return"<>";if(typeof t=="object"&&t!==null&&t.$$typeof===D)return"<...>";try{var l=e(t);return l?"<"+l+">":"<...>"}catch{return"<...>"}}function r(){var t=q.A;return t===null?null:t.getOwner()}function c(){return Error("react-stack-top-frame")}function i(t){if(K.call(t,"key")){var l=Object.getOwnPropertyDescriptor(t,"key").get;if(l&&l.isReactWarning)return!1}return t.key!==void 0}function u(t,l){function T(){Z||(Z=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",l))}T.isReactWarning=!0,Object.defineProperty(t,"key",{get:T,configurable:!0})}function d(){var t=e(this.type);return ee[t]||(ee[t]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),t=this.props.ref,t!==void 0?t:null}function f(t,l,T,y,g,I,$,J){return T=I.ref,t={$$typeof:O,type:t,key:l,props:I,_owner:g},(T!==void 0?T:null)!==null?Object.defineProperty(t,"ref",{enumerable:!1,get:d}):Object.defineProperty(t,"ref",{enumerable:!1,value:null}),t._store={},Object.defineProperty(t._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(t,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(t,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:$}),Object.defineProperty(t,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:J}),Object.freeze&&(Object.freeze(t.props),Object.freeze(t)),t}function S(t,l,T,y,g,I,$,J){var _=l.children;if(_!==void 0)if(y)if(Ee(_)){for(y=0;y<_.length;y++)h(_[y]);Object.freeze&&Object.freeze(_)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else h(_);if(K.call(l,"key")){_=e(t);var j=Object.keys(l).filter(function(Se){return Se!=="key"});y=0<j.length?"{key: someKey, "+j.join(": ..., ")+": ...}":"{key: someKey}",ne[_+y]||(j=0<j.length?"{"+j.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
2
|
-
let props = %s;
|
|
3
|
-
<%s {...props} />
|
|
4
|
-
React keys must be passed directly to JSX without using spread:
|
|
5
|
-
let props = %s;
|
|
6
|
-
<%s key={someKey} {...props} />`,y,_,j,_),ne[_+y]=!0)}if(_=null,T!==void 0&&(n(T),_=""+T),i(l)&&(n(l.key),_=""+l.key),"key"in l){T={};for(var W in l)W!=="key"&&(T[W]=l[W])}else T=l;return _&&u(T,typeof t=="function"?t.displayName||t.name||"Unknown":t),f(t,_,I,g,r(),T,$,J)}function h(t){typeof t=="object"&&t!==null&&t.$$typeof===O&&t._store&&(t._store.validated=1)}var R=a,O=Symbol.for("react.transitional.element"),C=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),v=Symbol.for("react.strict_mode"),N=Symbol.for("react.profiler"),w=Symbol.for("react.consumer"),E=Symbol.for("react.context"),m=Symbol.for("react.forward_ref"),k=Symbol.for("react.suspense"),x=Symbol.for("react.suspense_list"),A=Symbol.for("react.memo"),D=Symbol.for("react.lazy"),fe=Symbol.for("react.activity"),de=Symbol.for("react.client.reference"),q=R.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,K=Object.prototype.hasOwnProperty,Ee=Array.isArray,X=console.createTask?console.createTask:function(){return null};R={react_stack_bottom_frame:function(t){return t()}};var Z,ee={},te=R.react_stack_bottom_frame.bind(R,c)(),re=X(o(c)),ne={};V.Fragment=p,V.jsx=function(t,l,T,y,g){var I=1e4>q.recentlyCreatedOwnerStacks++;return S(t,l,T,!1,y,g,I?Error("react-stack-top-frame"):te,I?X(o(t)):re)},V.jsxs=function(t,l,T,y,g){var I=1e4>q.recentlyCreatedOwnerStacks++;return S(t,l,T,!0,y,g,I?Error("react-stack-top-frame"):te,I?X(o(t)):re)}})()),V}var ce;function Te(){return ce||(ce=1,process.env.NODE_ENV==="production"?U.exports=pe():U.exports=Re()),U.exports}var ve=Te();const be={timeTravel:!0,eventReplay:!0,stateExplorer:!0,eventEmit:!0,performanceMetrics:!0},he=750,me=3e4;function Oe({config:e,children:s}){const[n,o]=a.useState("disconnected"),r=a.useRef(null),c=a.useRef(new Set),i=a.useRef(0),u=a.useRef(null),d=a.useRef(crypto.randomUUID()),f=a.useRef(e);f.current=e;const S=a.useCallback(()=>{const v=f.current,w=`ws://${v.host??"localhost"}:${v.port}`,E=v.WebSocket??(typeof globalThis.WebSocket<"u"?globalThis.WebSocket:void 0);if(!E){console.error("[Yoltra DevTools] No WebSocket implementation available. In Node.js, pass { WebSocket } from 'ws' via config.WebSocket.");return}o("connecting");try{const m=new E(w);r.current=m,m.onopen=()=>{i.current=0;const k=JSON.stringify({type:"HANDSHAKE_REQUEST",protocolVersion:b.PROTOCOL_VERSION,role:b.DevtoolsRole.EXTENSION,extension:{id:d.current,name:v.extensionName??"DevTools UI",capabilities:be}});m.send(k)},m.onmessage=k=>{try{const x=typeof k.data=="string"?k.data:String(k.data),A=JSON.parse(x);if(A.type==="HANDSHAKE_RESPONSE"){A.success?o("connected"):m.close();return}c.current.forEach(D=>D(A))}catch{}},m.onclose=()=>{r.current=null,o("disconnected");const k=v.autoReconnect??!0,x=v.maxReconnectAttempts??1/0;if(k&&i.current<x){const A=i.current++,D=Math.min(he*Math.pow(2,A)+Math.random()*500,me);u.current=setTimeout(S,D)}},m.onerror=()=>{}}catch{o("disconnected")}},[]),h=a.useCallback(()=>{u.current&&(clearTimeout(u.current),u.current=null),i.current=1/0,r.current?.close(),r.current=null,o("disconnected")},[]),R=a.useCallback(()=>{i.current=0,r.current?.close(),r.current=null,S()},[S]),O=a.useCallback(v=>{const N=r.current;N&&N.readyState===1&&N.send(JSON.stringify(v))},[]),C=a.useCallback(v=>(c.current.add(v),()=>{c.current.delete(v)}),[]);a.useEffect(()=>(S(),()=>{u.current&&clearTimeout(u.current),i.current=1/0,r.current?.close()}),[S]);const p={status:n,send:O,subscribe:C,disconnect:h,reconnect:R};return ve.jsx(Q.Provider,{value:p,children:s})}function P(){return a.useContext(Q)}function ye(e){const{send:s}=P();return{emit:a.useCallback((o,r,c)=>{e&&s({type:"EMIT_TO_STORE",storeId:e,event:{channel:o,type:r,payload:c},timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION})},[e,s])}}const _e=2e3;function Ne(e,s){const{subscribe:n}=P(),o=s?.maxEntries??_e,r=a.useRef(new Map),c=a.useRef(o);c.current=o;const[,i]=a.useState(0);a.useEffect(()=>n(S=>{if(S.type!=="STORE_EVENT")return;const h=S.storeId,R={event:S.event,storeId:h,patches:S.patches,snapshotVersion:S.snapshotVersion,committed:S.committed,timestamp:S.timestamp},C=[...r.current.get(h)??[],R];r.current.set(h,C.length>c.current?C.slice(C.length-c.current):C),i(p=>p+1)}),[n]);const u=a.useCallback(()=>{e&&(r.current.delete(e),i(f=>f+1))},[e]);return{entries:e?r.current.get(e)??[]:[],clear:u}}function ke(e){const{send:s}=P();return{replay:a.useCallback((o,r)=>{e&&s({type:"EVENT_REPLAY",storeId:e,snapshot:o,events:r.map(c=>({id:c.event.id,channel:c.event.channel,type:c.event.type,payload:c.event.payload})),timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION})},[e,s])}}function Ce(e,s){const{send:n,subscribe:o}=P(),[r,c]=a.useState(null),[i,u]=a.useState(!1),d=s?.refreshIntervalMs??2e3,f=a.useRef(null),S=a.useCallback(()=>{e&&(u(!0),n({type:"REQUEST_METRICS",storeId:e,timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION}))},[e,n]);return a.useEffect(()=>{if(!e){c(null);return}S();const h=o(R=>{R.type==="STORE_METRICS"&&R.storeId===e&&(c(R.metrics),u(!1))});return d>0&&(f.current=setInterval(S,d)),()=>{h(),f.current&&(clearInterval(f.current),f.current=null)}},[e,o,S,d]),{metrics:r,loading:i,refresh:S}}function Ae(){const{subscribe:e}=P(),[s,n]=a.useState([]);return a.useEffect(()=>e(r=>{switch(r.type){case"STORE_REGISTRY":n(r.stores);break;case"STORE_CONNECTED":n(c=>c.some(u=>u.id===r.store.id)?c.map(u=>u.id===r.store.id?{...u,status:"connected"}:u):[...c,{id:r.store.id,name:r.store.name,status:"connected",capabilities:r.store.capabilities,connectedAt:r.timestamp}]);break;case"STORE_DISCONNECTED":n(c=>c.map(i=>i.id===r.storeId?{...i,status:"disconnected"}:i));break}}),[e]),s}function F(e,s){let n=structuredClone(e);for(const o of s){const r=Ie(o.path);switch(o.op){case"add":n=z(n,r,o.value,!0);break;case"replace":n=z(n,r,o.value,!1);break;case"remove":n=le(n,r);break}}return n}function Ie(e){return e===""||e==="/"?[]:e.slice(1).split("/").map(s=>s.replace(/~1/g,"/").replace(/~0/g,"~"))}const ie=new Set(["__proto__","constructor","prototype"]);function z(e,s,n,o){if(s.length===0)return n;const r=Array.isArray(e)?[...e]:{...e},[c,...i]=s;if(ie.has(c))return r;if(i.length===0)if(o&&Array.isArray(r)){const u=c==="-"?r.length:Number(c);Number.isInteger(u)&&u>=0&&u<=r.length?r.splice(u,0,n):r[c]=n}else r[c]=n;else r[c]=z(r[c]??{},i,n,o);return r}function le(e,s){if(s.length===0)return;const n=Array.isArray(e)?[...e]:{...e},[o,...r]=s;return ie.has(o)||(r.length===0?Array.isArray(n)?n.splice(Number(o),1):delete n[o]:n[o]=le(n[o],r)),n}function we(e){const s=e.scheduler??{setInterval:(r,c)=>setInterval(r,c),clearInterval:r=>clearInterval(r)};let n=s.setInterval(()=>{if(e.isSettled()){o();return}e.request()},e.intervalMs);function o(){n!==null&&(s.clearInterval(n),n=null)}return o}const xe=1500;function Pe(e){const{send:s,subscribe:n}=P(),[o,r]=a.useState(null),[c,i]=a.useState(0),[u,d]=a.useState(!1),f=a.useRef(0),S=a.useRef([]),h=a.useRef(null),R=a.useCallback(()=>{h.current?.(),h.current=null},[]),O=a.useCallback(()=>{e&&(d(!0),s({type:"REQUEST_STATE",storeId:e,timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION}))},[e,s]);return a.useEffect(()=>{if(!e){r(null),i(0),f.current=0;return}O(),h.current=we({request:O,isSettled:()=>f.current!==0,intervalMs:xe});const C=n(p=>{if(p.type==="STATE_SNAPSHOT"&&p.storeId===e){R(),r(p.state),i(p.version),f.current=p.version,d(!1);const v=S.current.filter(N=>N.version>p.version).sort((N,w)=>N.version-w.version);if(S.current=[],v.length>0){r(w=>{let E=w;for(const m of v)E=F(E,m.patches);return E});const N=v[v.length-1].version;i(N),f.current=N}return}if(p.type==="STORE_EVENT"&&p.storeId===e&&p.committed){if(f.current===0){S.current.push({patches:p.patches,version:p.snapshotVersion});return}if(p.snapshotVersion<=f.current)return;r(v=>F(v,p.patches)),i(p.snapshotVersion),f.current=p.snapshotVersion}});return()=>{C(),R(),S.current=[]}},[e,n,O,R]),{state:o,version:c,loading:u,refresh:O}}function ge(e){const{send:s,subscribe:n}=P(),[o,r]=a.useState(null),[c,i]=a.useState(!1),u=a.useCallback(()=>{e&&(i(!0),s({type:"REQUEST_SUBSCRIPTIONS",storeId:e,timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION}))},[e,s]);return a.useEffect(()=>{if(!e){r(null);return}return u(),n(f=>{f.type==="STORE_SUBSCRIPTIONS"&&f.storeId===e&&(r({atomic:f.atomic,event:f.event,coarse:f.coarse,effects:f.effects,middleware:f.middleware,reducers:f.reducers}),i(!1))})},[e,n,u]),{data:o,loading:c,refresh:u}}function ae(e,s){const{isTimeTraveling:n,currentIndex:o,entryCount:r}=s,c=r-1;if(e==="back"){const d=(n?o:c)-1;return d>=0?{kind:"jump",index:d}:{kind:"none"}}if(!n)return{kind:"none"};const i=o+1;return i>c?{kind:"resume"}:{kind:"jump",index:i}}function De(e,s,n=!0){const{send:o,subscribe:r}=P(),[c,i]=a.useState(-1),[u,d]=a.useState(!1),[f,S]=a.useState(null),h=a.useRef(null);a.useEffect(()=>{if(h.current=null,S(null),!!e)return r(E=>{E.type==="STATE_SNAPSHOT"&&E.storeId===e&&h.current===null&&(h.current={state:E.state,version:E.version})})},[e,r]);const R=a.useCallback(E=>{const m=h.current;if(!m)return null;let k=m.state;for(let x=0;x<=E;x++){const A=s[x];if(!A)break;A.snapshotVersion<=m.version||(k=F(k,A.patches))}return k},[s]),O=a.useCallback(E=>{if(!e||!n||E<0||E>=s.length)return;S(k=>k??s.length),i(E),d(!0);const m=s[E];o({type:"TIME_TRAVEL",storeId:e,state:R(E),snapshotVersion:m.snapshotVersion,timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION})},[e,s,o,R,n]),C=a.useCallback(()=>{if(d(!1),i(-1),S(null),n&&e&&s.length>0){const E=s.length-1,m=s[E];o({type:"TIME_TRAVEL",storeId:e,state:R(E),snapshotVersion:m.snapshotVersion,timestamp:new Date().toISOString(),sourceId:"",sourceRole:b.DevtoolsRole.EXTENSION})}},[e,s,o,R,n]),p=f??s.length,v=a.useCallback(()=>{const E=ae("back",{isTimeTraveling:u,currentIndex:c,entryCount:p});E.kind==="jump"&&O(E.index)},[u,c,p,O]),N=a.useCallback(()=>{const E=ae("forward",{isTimeTraveling:u,currentIndex:c,entryCount:p});E.kind==="jump"?O(E.index):E.kind==="resume"&&C()},[u,c,p,O,C]),w=a.useMemo(()=>R(u?c:s.length-1),[R,u,c,s.length]);return{currentIndex:c,isTimeTraveling:u,previewState:w,frameCount:f,jumpTo:O,stepBack:v,stepForward:N,resume:C}}const G=0,B=1,je=2,L=3;function H(){return{timestamp:new Date().toISOString(),sourceId:"loopback-hub",sourceRole:b.DevtoolsRole.HUB}}function ue(e){return e.split(".")[0]??"0"}class Me{constructor(){this.peers=new Set}add(s){const n={role:null,id:null,deliver:s,closed:!1};return this.peers.add(n),n}remove(s){if(this.peers.delete(s)&&(s.closed=!0,s.role===b.DevtoolsRole.STORE&&s.id)){const n=JSON.stringify({type:"STORE_DISCONNECTED",storeId:s.id,reason:"disconnected",...H()});this.eachExtension(o=>o.deliver(n))}}receive(s,n){if(s.closed)return;let o;try{o=JSON.parse(n)}catch{return}if(!(o===null||typeof o!="object"||typeof o.type!="string")){if(s.role===null){this.handshake(s,o);return}if(s.role===b.DevtoolsRole.STORE)this.eachExtension(r=>r.deliver(n));else if(typeof o.storeId=="string")for(const r of this.peers)r.role===b.DevtoolsRole.STORE&&r.id===o.storeId&&r.deliver(n)}}handshake(s,n){if(n.type!=="HANDSHAKE_REQUEST")return;const o=n.role,r=o===b.DevtoolsRole.STORE?n.store?.id:o===b.DevtoolsRole.EXTENSION?n.extension?.id:void 0,i=ue(String(n.protocolVersion??""))===ue(b.PROTOCOL_VERSION)&&typeof r=="string";if(s.deliver(JSON.stringify({type:"HANDSHAKE_RESPONSE",success:i,protocolVersion:b.PROTOCOL_VERSION,...H(),...i?{}:{error:"Loopback handshake rejected (version or id mismatch)"}})),!!i)if(s.role=o,s.id=r,o===b.DevtoolsRole.STORE){s.storeInfo={name:n.store?.name??r,capabilities:n.store?.capabilities};const u=JSON.stringify({type:"STORE_CONNECTED",store:{id:r,name:s.storeInfo.name,capabilities:s.storeInfo.capabilities},...H()});this.eachExtension(d=>d.deliver(u))}else{const u=[...this.peers].filter(d=>d.role===b.DevtoolsRole.STORE&&d.storeInfo).map(d=>({id:d.id,name:d.storeInfo.name,status:"connected",capabilities:d.storeInfo.capabilities,connectedAt:new Date().toISOString()}));s.deliver(JSON.stringify({type:"STORE_REGISTRY",stores:u,...H()}))}}eachExtension(s){for(const n of this.peers)n.role===b.DevtoolsRole.EXTENSION&&s(n)}}function Ve(){const e=new Me,s=(r,c)=>{let i=G;const u=e.add(d=>c.onMessage(d));return queueMicrotask(()=>{i=B,c.onOpen()}),{get readyState(){return i},send:d=>e.receive(u,d),close:()=>{i!==L&&(i=L,e.remove(u),c.onClose())},dispose:()=>{u.closed=!0}}},o=class o{constructor(c){this.onopen=null,this.onmessage=null,this.onclose=null,this.onerror=null,this.readyState=G,this.peer=e.add(i=>this.onmessage?.({data:i})),queueMicrotask(()=>{this.readyState=B,this.onopen?.()})}send(c){e.receive(this.peer,c)}close(){this.readyState!==L&&(this.readyState=L,e.remove(this.peer),this.onclose?.())}};o.CONNECTING=G,o.OPEN=B,o.CLOSING=je,o.CLOSED=L;let n=o;return{agentSocketFactory:s,WebSocket:n}}exports.HubContext=Q;exports.HubProvider=Oe;exports.applyPatches=F;exports.createLoopbackHub=Ve;exports.useEventEmitter=ye;exports.useEventLog=Ne;exports.useEventReplay=ke;exports.useHubConnection=P;exports.useStoreMetrics=Ce;exports.useStoreRegistry=Ae;exports.useStoreState=Pe;exports.useStoreSubscriptions=ge;exports.useTimeTravel=De;
|
|
7
|
-
//# sourceMappingURL=devtools-ui.cjs.js.map
|