sygnal 5.4.0 → 6.1.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/CHANGELOG.md +716 -0
- package/README.md +24 -9
- package/dist/ai.cjs.js +115 -0
- package/dist/ai.cjs.js.map +1 -0
- package/dist/ai.esm.js +2 -0
- package/dist/ai.esm.js.map +1 -0
- package/dist/astro/client.cjs.js +16 -9
- package/dist/astro/client.cjs.js.map +1 -1
- package/dist/astro/client.mjs +16 -9
- package/dist/astro/client.mjs.map +1 -1
- package/dist/astro/index.cjs.js +958 -92
- package/dist/astro/index.cjs.js.map +1 -1
- package/dist/astro/index.mjs +958 -93
- package/dist/astro/index.mjs.map +1 -1
- package/dist/astro/server.cjs.js +3396 -165
- package/dist/astro/server.cjs.js.map +1 -1
- package/dist/astro/server.mjs +3396 -165
- package/dist/astro/server.mjs.map +1 -1
- package/dist/devtools.cjs.js +1840 -0
- package/dist/devtools.cjs.js.map +1 -0
- package/dist/devtools.esm.js +1827 -0
- package/dist/devtools.esm.js.map +1 -0
- package/dist/diagnostics.cjs.js +3190 -296
- package/dist/diagnostics.cjs.js.map +1 -1
- package/dist/diagnostics.esm.js +3190 -296
- package/dist/diagnostics.esm.js.map +1 -1
- package/dist/element.cjs.js +230 -0
- package/dist/element.cjs.js.map +1 -0
- package/dist/element.esm.js +228 -0
- package/dist/element.esm.js.map +1 -0
- package/dist/guide/accessibility.md +310 -0
- package/dist/guide/adapters.md +221 -0
- package/dist/guide/agent.md +588 -0
- package/dist/guide/ai-chat.md +693 -0
- package/dist/guide/ai-decisions.md +330 -0
- package/dist/guide/behaviors.md +312 -0
- package/dist/guide/browser-sources.md +203 -0
- package/dist/guide/drag-and-drop.md +342 -0
- package/dist/guide/element-commands.md +306 -0
- package/dist/guide/error-boundaries.md +169 -0
- package/dist/guide/forms-reference.md +409 -0
- package/dist/guide/forms.md +251 -0
- package/dist/guide/http.md +301 -0
- package/dist/guide/inputs.md +173 -0
- package/dist/guide/mcp-apps.md +272 -0
- package/dist/guide/persistence.md +245 -0
- package/dist/guide/recipes/ai-form-fill.md +163 -0
- package/dist/guide/recipes/ai-summarize.md +155 -0
- package/dist/guide/recipes/ai-support-inbox.md +190 -0
- package/dist/guide/recipes/carousel.md +155 -0
- package/dist/guide/recipes/charts.md +177 -0
- package/dist/guide/recipes/code-editor.md +135 -0
- package/dist/guide/recipes/data-grid.md +155 -0
- package/dist/guide/recipes/data-table.md +196 -0
- package/dist/guide/recipes/i18n.md +222 -0
- package/dist/guide/recipes/icons.md +115 -0
- package/dist/guide/recipes/overview.md +43 -0
- package/dist/guide/recipes/rich-text.md +154 -0
- package/dist/guide/resources.md +471 -0
- package/dist/guide/ssr.md +255 -0
- package/dist/guide/timers.md +178 -0
- package/dist/guide/ui/accordion.md +93 -0
- package/dist/guide/ui/combobox.md +144 -0
- package/dist/guide/ui/dialog.md +124 -0
- package/dist/guide/ui/disclosure.md +60 -0
- package/dist/guide/ui/menu.md +125 -0
- package/dist/guide/ui/overview.md +166 -0
- package/dist/guide/ui/popover.md +101 -0
- package/dist/guide/ui/select.md +114 -0
- package/dist/guide/ui/tabs.md +161 -0
- package/dist/guide/ui/toaster.md +152 -0
- package/dist/guide/ui/tooltip.md +103 -0
- package/dist/guide/undo.md +178 -0
- package/dist/guide/virtual-collections.md +183 -0
- package/dist/guide/web-components.md +179 -0
- package/dist/guide/webmcp.md +226 -0
- package/dist/guide/widgets.md +217 -0
- package/dist/index.cjs.js +19288 -7408
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +4466 -552
- package/dist/index.esm.js +19274 -7461
- package/dist/index.esm.js.map +1 -1
- package/dist/jsx-dev-runtime.cjs.js +26 -334
- package/dist/jsx-dev-runtime.cjs.js.map +1 -1
- package/dist/jsx-dev-runtime.esm.js +19 -327
- package/dist/jsx-dev-runtime.esm.js.map +1 -1
- package/dist/jsx-runtime.cjs.js +26 -334
- package/dist/jsx-runtime.cjs.js.map +1 -1
- package/dist/jsx-runtime.esm.js +19 -327
- package/dist/jsx-runtime.esm.js.map +1 -1
- package/dist/jsx.cjs.js +10 -314
- package/dist/jsx.cjs.js.map +1 -1
- package/dist/jsx.esm.js +8 -315
- package/dist/jsx.esm.js.map +1 -1
- package/dist/react.cjs.js +117 -0
- package/dist/react.cjs.js.map +1 -0
- package/dist/react.esm.js +115 -0
- package/dist/react.esm.js.map +1 -0
- package/dist/shims/globalthis.cjs +20 -0
- package/dist/sygnal.min.js +1 -1
- package/dist/sygnal.min.js.map +1 -1
- package/dist/ui-combobox.cjs.js +169 -0
- package/dist/ui-combobox.cjs.js.map +1 -0
- package/dist/ui-combobox.esm.js +148 -0
- package/dist/ui-combobox.esm.js.map +1 -0
- package/dist/ui-menu.cjs.js +87 -0
- package/dist/ui-menu.cjs.js.map +1 -0
- package/dist/ui-menu.esm.js +66 -0
- package/dist/ui-menu.esm.js.map +1 -0
- package/dist/ui-select.cjs.js +104 -0
- package/dist/ui-select.cjs.js.map +1 -0
- package/dist/ui-select.esm.js +83 -0
- package/dist/ui-select.esm.js.map +1 -0
- package/dist/ui.cjs.js +738 -0
- package/dist/ui.cjs.js.map +1 -0
- package/dist/ui.esm.js +727 -0
- package/dist/ui.esm.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +4 -14
- package/dist/vike/ClientOnly.cjs.js.map +1 -1
- package/dist/vike/ClientOnly.mjs +4 -14
- package/dist/vike/ClientOnly.mjs.map +1 -1
- package/dist/vike/{+config.js → config/+config.js} +9 -1
- package/dist/vike/config/+config.js.map +1 -0
- package/dist/vike/config/package.json +6 -0
- package/dist/vike/onRenderClient.cjs.js +165 -95
- package/dist/vike/onRenderClient.cjs.js.map +1 -1
- package/dist/vike/onRenderClient.mjs +165 -95
- package/dist/vike/onRenderClient.mjs.map +1 -1
- package/dist/vike/onRenderHtml.cjs.js +59 -24
- package/dist/vike/onRenderHtml.cjs.js.map +1 -1
- package/dist/vike/onRenderHtml.mjs +60 -25
- package/dist/vike/onRenderHtml.mjs.map +1 -1
- package/dist/vite/plugin.cjs.js +919 -89
- package/dist/vite/plugin.cjs.js.map +1 -1
- package/dist/vite/plugin.mjs +919 -90
- package/dist/vite/plugin.mjs.map +1 -1
- package/dist/zag.cjs.js +408 -0
- package/dist/zag.cjs.js.map +1 -0
- package/dist/zag.esm.js +405 -0
- package/dist/zag.esm.js.map +1 -0
- package/llms.txt +172 -103
- package/package.json +112 -14
- package/src/ai.d.ts +1186 -0
- package/src/ai.ts +23 -0
- package/src/astro/client.ts +27 -16
- package/src/astro/index.d.ts +24 -0
- package/src/astro/index.ts +69 -3
- package/src/astro/server.ts +8 -2
- package/src/collection.ts +6 -93
- package/src/core/actions.ts +134 -0
- package/src/core/cell.ts +202 -0
- package/src/core/debug.ts +23 -0
- package/src/core/define.ts +242 -0
- package/src/core/hooks.ts +314 -0
- package/src/core/hosts/collection.ts +325 -0
- package/src/core/hosts/switchable.ts +146 -0
- package/src/core/instance.ts +592 -0
- package/src/core/markers/clientonly.ts +13 -0
- package/src/core/markers/lazy.ts +40 -0
- package/src/core/markers/portal.ts +96 -0
- package/src/core/markers/suspense.ts +41 -0
- package/src/core/markers/transition.ts +70 -0
- package/src/core/registry.ts +25 -0
- package/src/core/runtime.ts +573 -0
- package/src/core/statics.ts +104 -0
- package/src/core/teardown.ts +60 -0
- package/src/core/view.ts +37 -0
- package/src/cycle/dom/DocumentDOMSource.ts +13 -8
- package/src/cycle/dom/ElementFinder.ts +7 -9
- package/src/cycle/dom/EventDelegator.ts +174 -223
- package/src/cycle/dom/IsolateModule.ts +65 -35
- package/src/cycle/dom/MainDOMSource.ts +9 -2
- package/src/cycle/dom/PriorityQueue.ts +4 -2
- package/src/cycle/dom/SymbolTree.ts +18 -47
- package/src/cycle/dom/classNameModule.ts +9 -7
- package/src/cycle/dom/controlledInputModule.ts +23 -6
- package/src/cycle/dom/enrichEventStream.ts +25 -47
- package/src/cycle/dom/fragment.ts +7 -0
- package/src/cycle/dom/isolate.ts +30 -27
- package/src/cycle/dom/makeDOMDriver.ts +115 -56
- package/src/cycle/dom/mockDOMSource.ts +21 -1
- package/src/cycle/dom/modules.ts +16 -3
- package/src/cycle/dom/propsModule.ts +65 -0
- package/src/cycle/dom/selectModule.ts +22 -17
- package/src/cycle/dom/snabbdom.ts +1 -6
- package/src/cycle/dom/thunk.ts +3 -0
- package/src/cycle/dom/utils.ts +98 -0
- package/src/cycle/dom/viewTransition.ts +43 -0
- package/src/cycle/state/StateSource.ts +19 -3
- package/src/cycle/state/objIsEqual.ts +50 -0
- package/src/cycle/state/types.ts +0 -10
- package/src/defineComponent.ts +34 -0
- package/src/devtools.d.ts +170 -0
- package/src/devtools.ts +37 -0
- package/src/element.d.ts +59 -0
- package/src/element.ts +235 -0
- package/src/extra/ai/agent/index.ts +484 -0
- package/src/extra/ai/answers.ts +115 -0
- package/src/extra/ai/chat/behavior.ts +336 -0
- package/src/extra/ai/chat/driver.ts +271 -0
- package/src/extra/ai/chat/memoryTransport.ts +61 -0
- package/src/extra/ai/chat/output.ts +37 -0
- package/src/extra/ai/commandBar.ts +281 -0
- package/src/extra/ai/decide.ts +104 -0
- package/src/extra/ai/index.ts +42 -0
- package/src/extra/ai/link.ts +90 -0
- package/src/extra/ai/mcpApp.ts +314 -0
- package/src/extra/ai/messages.ts +51 -0
- package/src/extra/ai/schema/index.ts +166 -0
- package/src/extra/ai/schema/jsonSchema.ts +72 -0
- package/src/extra/ai/schema/strict.ts +152 -0
- package/src/extra/ai/transports/agui.ts +177 -0
- package/src/extra/ai/transports/anthropicMessages.ts +175 -0
- package/src/extra/ai/transports/chatCompletions.ts +105 -0
- package/src/extra/ai/transports/chromePrompt.ts +77 -0
- package/src/extra/ai/transports/encodeOpenResponses.ts +87 -0
- package/src/extra/ai/transports/fromAISDK.ts +126 -0
- package/src/extra/ai/transports/openResponses.ts +105 -0
- package/src/extra/ai/transports/shared.ts +125 -0
- package/src/extra/ai/transports/tools.ts +59 -0
- package/src/extra/ai/transports/uiMessageStream.ts +107 -0
- package/src/extra/ai/webmcp.ts +260 -0
- package/src/extra/backoff.ts +9 -0
- package/src/extra/behaviors.ts +169 -0
- package/src/extra/browserSignals.ts +23 -0
- package/src/extra/browserSources.ts +326 -0
- package/src/extra/command.ts +3 -3
- package/src/extra/controls.ts +47 -0
- package/src/extra/copyAsTest.ts +286 -0
- package/src/extra/devMcp.ts +386 -0
- package/src/extra/devtools.ts +101 -31
- package/src/extra/devtoolsActions.ts +379 -0
- package/src/extra/devtoolsHook.ts +9 -0
- package/src/extra/devtoolsNext.ts +83 -0
- package/src/extra/diagnostics/checks/actionLog.ts +108 -0
- package/src/extra/diagnostics/checks/behaviors.ts +49 -0
- package/src/extra/diagnostics/checks/browserSources.ts +96 -0
- package/src/extra/diagnostics/checks/chat.ts +71 -0
- package/src/extra/diagnostics/checks/collections.ts +1 -1
- package/src/extra/diagnostics/checks/controls.ts +148 -0
- package/src/extra/diagnostics/checks/dataset.ts +57 -0
- package/src/extra/diagnostics/checks/dom.ts +20 -11
- package/src/extra/diagnostics/checks/elementCommands.ts +175 -0
- package/src/extra/diagnostics/checks/events.ts +17 -2
- package/src/extra/diagnostics/checks/fetch.ts +135 -0
- package/src/extra/diagnostics/checks/forms.ts +171 -0
- package/src/extra/diagnostics/checks/index.ts +103 -4
- package/src/extra/diagnostics/checks/inspect.ts +140 -32
- package/src/extra/diagnostics/checks/next.ts +417 -0
- package/src/extra/diagnostics/checks/persist.ts +54 -0
- package/src/extra/diagnostics/checks/props.ts +10 -5
- package/src/extra/diagnostics/checks/public.d.ts +115 -7
- package/src/extra/diagnostics/checks/replies.ts +128 -0
- package/src/extra/diagnostics/checks/router.ts +100 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +1 -1
- package/src/extra/diagnostics/checks/shared.ts +86 -2
- package/src/extra/diagnostics/checks/shorthand.ts +115 -0
- package/src/extra/diagnostics/checks/sortable.ts +129 -0
- package/src/extra/diagnostics/checks/state.ts +100 -7
- package/src/extra/diagnostics/checks/statics.ts +48 -0
- package/src/extra/diagnostics/checks/strict.ts +10 -67
- package/src/extra/diagnostics/checks/timers.ts +80 -0
- package/src/extra/diagnostics/checks/viewTransitions.ts +47 -0
- package/src/extra/diagnostics/checks/virtual.ts +73 -0
- package/src/extra/diagnostics/checks/widgets.ts +109 -0
- package/src/extra/diagnostics/checks/wiring.ts +69 -10
- package/src/extra/diagnostics/codes.ts +335 -23
- package/src/extra/diagnostics/index.ts +34 -9
- package/src/extra/diagnostics/legacy.ts +17 -0
- package/src/extra/driverFactories.ts +83 -19
- package/src/extra/elementCommands.ts +53 -0
- package/src/extra/fetchDriver.ts +519 -0
- package/src/extra/focusWithin.ts +27 -0
- package/src/extra/form.ts +284 -0
- package/src/extra/formHelpers.ts +139 -0
- package/src/extra/formTool.ts +150 -0
- package/src/extra/head.ts +106 -0
- package/src/extra/hmr.ts +2 -3
- package/src/extra/owned.ts +24 -0
- package/src/extra/pager.ts +50 -0
- package/src/extra/persist.ts +133 -0
- package/src/extra/pwa.ts +1 -1
- package/src/extra/queryCache.ts +107 -0
- package/src/extra/reducers.ts +1 -1
- package/src/extra/reduxDevtools.ts +92 -0
- package/src/extra/replies.ts +54 -0
- package/src/extra/router.ts +323 -0
- package/src/extra/run.ts +90 -217
- package/src/extra/selection.ts +76 -0
- package/src/extra/socketDriver.ts +265 -0
- package/src/extra/sortable.ts +377 -0
- package/src/extra/ssr.ts +427 -160
- package/src/extra/standardSchema.ts +27 -0
- package/src/extra/testing.ts +2550 -175
- package/src/extra/timers.ts +95 -0
- package/src/extra/undo.ts +261 -0
- package/src/extra/viewTransitions.ts +38 -0
- package/src/extra/virtual.ts +513 -0
- package/src/extra/widget.ts +201 -0
- package/src/index.d.ts +2754 -127
- package/src/index.ts +45 -4
- package/src/jsx-runtime.ts +15 -1
- package/src/jsx.ts +2 -1
- package/src/lazy.ts +50 -7
- package/src/portal.ts +3 -1
- package/src/pragma/index.ts +256 -133
- package/src/react-peers.d.ts +5 -0
- package/src/react.d.ts +43 -0
- package/src/react.ts +110 -0
- package/src/shared.ts +57 -0
- package/src/slot.ts +1 -1
- package/src/suspense.ts +3 -1
- package/src/switchable.ts +6 -121
- package/src/transition.ts +3 -1
- package/src/ui/accordion.ts +64 -0
- package/src/ui/dialog.ts +129 -0
- package/src/ui/disclosure.ts +35 -0
- package/src/ui/popover.ts +45 -0
- package/src/ui/shared.ts +94 -0
- package/src/ui/tabs.ts +82 -0
- package/src/ui/toaster.ts +198 -0
- package/src/ui/tooltip.ts +70 -0
- package/src/ui/zag/combobox.ts +115 -0
- package/src/ui/zag/menu.ts +40 -0
- package/src/ui/zag/select.ts +54 -0
- package/src/ui/zag/shared.ts +43 -0
- package/src/ui-combobox.d.ts +20 -0
- package/src/ui-combobox.ts +6 -0
- package/src/ui-menu.d.ts +30 -0
- package/src/ui-menu.ts +6 -0
- package/src/ui-select.d.ts +17 -0
- package/src/ui-select.ts +6 -0
- package/src/ui-zag-types.d.ts +42 -0
- package/src/ui.d.ts +208 -0
- package/src/ui.ts +15 -0
- package/src/vike/+config.ts +9 -1
- package/src/vike/ClientOnly.ts +1 -1
- package/src/vike/onRenderClient.ts +144 -96
- package/src/vike/onRenderHtml.ts +69 -26
- package/src/vike/types.ts +7 -1
- package/src/vite/globalthis-shim.ts +18 -0
- package/src/vite/globalthis.ts +56 -0
- package/src/vite/mcp.ts +568 -0
- package/src/vite/plugin.d.ts +47 -3
- package/src/vite/plugin.ts +262 -25
- package/src/zag.d.ts +64 -0
- package/src/zag.ts +238 -0
- package/dist/vike/+config.cjs.js +0 -66
- package/dist/vike/+config.cjs.js.map +0 -1
- package/dist/vike/+config.js.map +0 -1
- package/src/component.ts +0 -2301
- package/src/cycle/isolate/index.ts +0 -196
- package/src/cycle/run/index.ts +0 -151
- package/src/cycle/run/internals.ts +0 -143
- package/src/cycle/state/Collection.ts +0 -184
- package/src/cycle/state/index.ts +0 -5
- package/src/cycle/state/pickCombine.ts +0 -201
- package/src/cycle/state/pickMerge.ts +0 -122
- package/src/cycle/state/withState.ts +0 -47
- package/src/pragma/fn.ts +0 -55
package/src/extra/devtools.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import {getDiagnostics} from './diagnostics/index';
|
|
2
2
|
import type {Diagnostic} from './diagnostics/index';
|
|
3
|
+
import {recordActions, clearActions, getActions, onAction, getSession, preview} from './devtoolsActions';
|
|
4
|
+
import type {DevtoolsAction, SessionRecording} from './devtoolsActions';
|
|
5
|
+
import {sessionToTest} from './copyAsTest';
|
|
6
|
+
import type {CopyAsTestOptions, CopyAsTestResult} from './copyAsTest';
|
|
7
|
+
|
|
8
|
+
import {addLayer, devtoolsHooks} from './devtoolsNext';
|
|
3
9
|
|
|
4
10
|
const DEVTOOLS_SOURCE = '__SYGNAL_DEVTOOLS_PAGE__';
|
|
5
11
|
const EXTENSION_SOURCE = '__SYGNAL_DEVTOOLS_EXTENSION__';
|
|
@@ -9,13 +15,10 @@ declare global {
|
|
|
9
15
|
interface Window {
|
|
10
16
|
__SYGNAL_DEVTOOLS__?: SygnalDevTools;
|
|
11
17
|
__SYGNAL_DEVTOOLS_APP__?: {
|
|
12
|
-
|
|
18
|
+
__runtime?: { setState: (id: any, fn: (s: any) => any) => void };
|
|
13
19
|
};
|
|
14
20
|
__SYGNAL_DEVTOOLS_PAGE__?: boolean;
|
|
15
21
|
SYGNAL_DEBUG?: string | false;
|
|
16
|
-
__SYGNAL_HMR_UPDATING?: boolean;
|
|
17
|
-
__SYGNAL_HMR_STATE?: unknown;
|
|
18
|
-
__SYGNAL_HMR_PERSISTED_STATE?: unknown;
|
|
19
22
|
__SYGNAL_HMR_LAST_CAPTURED_STATE?: unknown;
|
|
20
23
|
Cyclejs?: { sinks?: any };
|
|
21
24
|
CyclejsDevTool_startGraphSerializer?: (sinks: any) => void;
|
|
@@ -58,11 +61,16 @@ interface ExtensionMessage {
|
|
|
58
61
|
source?: string;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
class SygnalDevTools {
|
|
64
|
+
export class SygnalDevTools {
|
|
62
65
|
_connected: boolean;
|
|
66
|
+
_initialized = false;
|
|
63
67
|
_components: Map<number, ComponentMeta>;
|
|
64
68
|
_stateHistory: StateHistoryEntry[];
|
|
65
69
|
_maxHistory: number;
|
|
70
|
+
/** PLAN-4 3-E: actions changed since the last ACTIONS post (flushed on a timeout) */
|
|
71
|
+
_dirty = new Set<DevtoolsAction>();
|
|
72
|
+
_flush: any = null;
|
|
73
|
+
_copyOptions: CopyAsTestOptions = {};
|
|
66
74
|
|
|
67
75
|
constructor() {
|
|
68
76
|
this._connected = false;
|
|
@@ -80,11 +88,24 @@ class SygnalDevTools {
|
|
|
80
88
|
return this._connected && typeof window !== 'undefined';
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
/** Publish the bridge on window and listen for the extension. Idempotent (D77: called once, by 'sygnal/devtools'). */
|
|
83
92
|
init(): void {
|
|
84
|
-
if (typeof window === 'undefined') return;
|
|
93
|
+
if (typeof window === 'undefined' || this._initialized) return;
|
|
94
|
+
this._initialized = true;
|
|
85
95
|
|
|
86
96
|
window.__SYGNAL_DEVTOOLS__ = this;
|
|
87
97
|
|
|
98
|
+
// PLAN-4 3-E (GS-10): the action log (recorded from now on, connected or not)
|
|
99
|
+
recordActions();
|
|
100
|
+
// PLAN-4.6 R4: the apps read this bridge's hooks (devtoolsNext.ts)
|
|
101
|
+
addLayer((api: any) => devtoolsHooks(this, api));
|
|
102
|
+
onAction((a, kind) => {
|
|
103
|
+
if (!this.connected) return;
|
|
104
|
+
if (kind == 'reset') { this._dirty.clear(); this._post('ACTIONS_RESET', {actions: []}); return; }
|
|
105
|
+
this._dirty.add(a!);
|
|
106
|
+
if (this._flush === null) this._flush = setTimeout(() => this._flushActions(), 0);
|
|
107
|
+
});
|
|
108
|
+
|
|
88
109
|
window.addEventListener('message', (event: MessageEvent) => {
|
|
89
110
|
if (event.source !== window) return;
|
|
90
111
|
if (event.data?.source === EXTENSION_SOURCE) {
|
|
@@ -99,6 +120,13 @@ class SygnalDevTools {
|
|
|
99
120
|
this._connected = true;
|
|
100
121
|
if (msg.payload?.maxHistory) this._maxHistory = msg.payload.maxHistory;
|
|
101
122
|
this._sendFullTree();
|
|
123
|
+
this._post('ACTIONS_RESET', {actions: getActions().slice(-500).map(a => this._serializeAction(a))});
|
|
124
|
+
break;
|
|
125
|
+
case 'CLEAR_ACTIONS':
|
|
126
|
+
clearActions();
|
|
127
|
+
break;
|
|
128
|
+
case 'COPY_AS_TEST':
|
|
129
|
+
this._post('COPY_AS_TEST_RESULT', this._copyAsTest(msg.payload?.instance));
|
|
102
130
|
break;
|
|
103
131
|
case 'DISCONNECT':
|
|
104
132
|
this._connected = false;
|
|
@@ -130,7 +158,7 @@ class SygnalDevTools {
|
|
|
130
158
|
hasIntent: !!instance.intent,
|
|
131
159
|
hasContext: !!instance.context,
|
|
132
160
|
hasCalculated: !!instance.calculated,
|
|
133
|
-
components:
|
|
161
|
+
components: [],
|
|
134
162
|
parentId: null,
|
|
135
163
|
children: [],
|
|
136
164
|
debug: instance._debug,
|
|
@@ -283,6 +311,42 @@ class SygnalDevTools {
|
|
|
283
311
|
});
|
|
284
312
|
}
|
|
285
313
|
|
|
314
|
+
/** PLAN-4 3-E: post the actions added or changed since the last flush */
|
|
315
|
+
_flushActions(): void {
|
|
316
|
+
this._flush = null;
|
|
317
|
+
if (!this.connected || !this._dirty.size) { this._dirty.clear(); return; }
|
|
318
|
+
const list = [...this._dirty].sort((a, b) => a.seq - b.seq).map(a => this._serializeAction(a));
|
|
319
|
+
this._dirty.clear();
|
|
320
|
+
this._post('ACTIONS', {actions: list});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
_serializeAction(a: DevtoolsAction): any {
|
|
324
|
+
return {
|
|
325
|
+
seq: a.seq, type: a.type, data: preview(a.data), component: a.component, instance: a.instance, parent: a.parent,
|
|
326
|
+
sinks: [...a.sinks], cause: a.cause, at: a.at,
|
|
327
|
+
...('after' in a ? {before: this._safeClone(a.before), after: this._safeClone(a.after)} : {}),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** "Copy as test" for the panel: the session of `instance` (default: the root) */
|
|
332
|
+
_copyAsTest(instance?: string | number): CopyAsTestResult | {error: string} {
|
|
333
|
+
try {
|
|
334
|
+
return sessionToTest(getSession(instance ?? undefined), this._copyOptions);
|
|
335
|
+
} catch (e: any) {
|
|
336
|
+
return {error: String(e?.message || e)};
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Defaults for "Copy as test" from the panel (componentImport, drivers, imports, ...) */
|
|
341
|
+
configureCopyAsTest(options: CopyAsTestOptions): void {
|
|
342
|
+
this._copyOptions = {...this._copyOptions, ...options};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** The recorded session of an instance (see sygnal/devtools getSession) */
|
|
346
|
+
getSession(target?: any): SessionRecording {
|
|
347
|
+
return getSession(target);
|
|
348
|
+
}
|
|
349
|
+
|
|
286
350
|
_setDebug({componentId, enabled}: {componentId?: number; enabled: boolean}): void {
|
|
287
351
|
if (typeof componentId === 'undefined' || componentId === null) {
|
|
288
352
|
if (typeof window !== 'undefined') window.SYGNAL_DEBUG = enabled ? 'true' : false;
|
|
@@ -294,7 +358,8 @@ class SygnalDevTools {
|
|
|
294
358
|
if (meta && meta._instanceRef) {
|
|
295
359
|
const instance = meta._instanceRef.deref();
|
|
296
360
|
if (instance) {
|
|
297
|
-
|
|
361
|
+
// (through the app's runtime API)
|
|
362
|
+
instance.__api?.setDebug(componentId, enabled);
|
|
298
363
|
meta.debug = enabled;
|
|
299
364
|
this._post('DEBUG_TOGGLED', {componentId, enabled});
|
|
300
365
|
}
|
|
@@ -311,40 +376,27 @@ class SygnalDevTools {
|
|
|
311
376
|
|
|
312
377
|
const newState = this._safeClone(state);
|
|
313
378
|
|
|
314
|
-
//
|
|
379
|
+
// Per-component time travel through the app's runtime API
|
|
315
380
|
const meta = this._components.get(componentId);
|
|
316
381
|
if (meta) {
|
|
317
382
|
const instance = meta._instanceRef?.deref();
|
|
318
383
|
if (!instance) {
|
|
319
384
|
console.warn(`[Sygnal DevTools] _timeTravel: WeakRef for component #${componentId} (${componentName}) has been GC'd`);
|
|
320
|
-
} else {
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
stateSink.shamefullySendNext(() => ({...newState}));
|
|
326
|
-
this._post('TIME_TRAVEL_APPLIED', {
|
|
327
|
-
componentId,
|
|
328
|
-
componentName,
|
|
329
|
-
state: newState,
|
|
330
|
-
});
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
console.warn(`[Sygnal DevTools] _timeTravel: component #${componentId} (${componentName}) has no STATE sink with shamefullySendNext. sinkName=${stateSinkName}, hasSinks=${!!instance.sinks}, sinkKeys=${instance.sinks ? Object.keys(instance.sinks).join(',') : 'none'}`);
|
|
385
|
+
} else if (instance.__api) {
|
|
386
|
+
// the runtime's setState (a queued action, one patch)
|
|
387
|
+
instance.__api.setState(componentId, () => ({...newState}));
|
|
388
|
+
this._post('TIME_TRAVEL_APPLIED', {componentId, componentName, state: newState});
|
|
389
|
+
return;
|
|
334
390
|
}
|
|
335
391
|
} else {
|
|
336
392
|
console.warn(`[Sygnal DevTools] _timeTravel: no meta for componentId ${componentId}`);
|
|
337
393
|
}
|
|
338
394
|
|
|
339
|
-
// Fall back to
|
|
395
|
+
// Fall back to the root's state
|
|
340
396
|
const app = window.__SYGNAL_DEVTOOLS_APP__;
|
|
341
|
-
if (app?.
|
|
342
|
-
app.
|
|
343
|
-
this._post('TIME_TRAVEL_APPLIED', {
|
|
344
|
-
componentId,
|
|
345
|
-
componentName,
|
|
346
|
-
state: newState,
|
|
347
|
-
});
|
|
397
|
+
if (app?.__runtime) {
|
|
398
|
+
app.__runtime.setState('root', () => ({...newState}));
|
|
399
|
+
this._post('TIME_TRAVEL_APPLIED', {componentId, componentName, state: newState});
|
|
348
400
|
} else {
|
|
349
401
|
console.warn(`[Sygnal DevTools] _timeTravel: no fallback root STATE sink available`);
|
|
350
402
|
}
|
|
@@ -506,9 +558,27 @@ class SygnalDevTools {
|
|
|
506
558
|
|
|
507
559
|
let instance: SygnalDevTools | null = null;
|
|
508
560
|
|
|
561
|
+
/**
|
|
562
|
+
* The bridge singleton of the 'sygnal/devtools' entry. Not part of the core
|
|
563
|
+
* (D77): the core only calls the hooks of window.__SYGNAL_DEVTOOLS__ when it exists.
|
|
564
|
+
*/
|
|
509
565
|
export function getDevTools(): SygnalDevTools {
|
|
510
566
|
if (!instance) instance = new SygnalDevTools();
|
|
511
567
|
return instance;
|
|
512
568
|
}
|
|
513
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Install the bridge (what run() did before 6.0): window.__SYGNAL_DEVTOOLS__ is
|
|
572
|
+
* set and the extension can connect. A bridge already on window (e.g. a second
|
|
573
|
+
* copy of this entry) is kept. Nothing happens outside a browser.
|
|
574
|
+
*/
|
|
575
|
+
export function installDevTools(): SygnalDevTools | undefined {
|
|
576
|
+
if (typeof window === 'undefined') return undefined;
|
|
577
|
+
const existing = window.__SYGNAL_DEVTOOLS__;
|
|
578
|
+
if (existing) return existing;
|
|
579
|
+
const dt = getDevTools();
|
|
580
|
+
dt.init();
|
|
581
|
+
return dt;
|
|
582
|
+
}
|
|
583
|
+
|
|
514
584
|
export default getDevTools;
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PLAN-4 3-E (GS-10): the DevTools action log, in the dev-only 'sygnal/devtools' entry (0 B in
|
|
3
|
+
* apps: nothing in the core calls it).
|
|
4
|
+
*
|
|
5
|
+
* It records every action of every component instance through 2-C's recorder
|
|
6
|
+
* (./diagnostics/checks/actionLog: actionHooks), a hook layer every app reads from the core's
|
|
7
|
+
* diagnostics bridge (globalThis.__SYGNAL_DIAGNOSTICS__.layers), so it records with diagnostics
|
|
8
|
+
* off too. On top of 2-C's record it keeps:
|
|
9
|
+
* seq, at a sequence number, ms since the session started
|
|
10
|
+
* parent the parent instance id (null: a root)
|
|
11
|
+
* before, after the instance's state around the action's STATE reducer (onReducer), when it
|
|
12
|
+
* changed the state (`after` without calculated fields)
|
|
13
|
+
* replySink for a reply action (and RESOURCE): the source that delivered it, and
|
|
14
|
+
* replyKind 'fetch' when that source is makeFetchDriver's (renderComponent fakes it)
|
|
15
|
+
*
|
|
16
|
+
* A session runs from the recorder's start (or the last clearActions()) to now. getSession()
|
|
17
|
+
* turns it into a SessionRecording for one instance (./copyAsTest generates the test).
|
|
18
|
+
*/
|
|
19
|
+
import {clockNow, actionHooks} from './diagnostics/checks/actionLog'
|
|
20
|
+
import {addLayer, devFacade} from './devtoolsNext'
|
|
21
|
+
import type {ActionListener, ActionRecord, ActionCause} from './diagnostics/checks/actionLog'
|
|
22
|
+
|
|
23
|
+
export type {ActionCause}
|
|
24
|
+
|
|
25
|
+
export interface DevtoolsAction {
|
|
26
|
+
seq: number
|
|
27
|
+
type: string
|
|
28
|
+
data: any
|
|
29
|
+
component: string
|
|
30
|
+
instance: string
|
|
31
|
+
parent: string | null
|
|
32
|
+
/** live: fills in as the action's reducers run */
|
|
33
|
+
sinks: string[]
|
|
34
|
+
cause: ActionCause
|
|
35
|
+
/** ms since the session started */
|
|
36
|
+
at: number
|
|
37
|
+
before?: any
|
|
38
|
+
after?: any
|
|
39
|
+
replySink?: string
|
|
40
|
+
replyKind?: 'fetch' | 'other'
|
|
41
|
+
/** an intent action whose data is the instance's own state (an intent over STATE.stream) */
|
|
42
|
+
echo?: true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SessionAction {
|
|
46
|
+
type: string
|
|
47
|
+
data: any
|
|
48
|
+
cause: ActionCause
|
|
49
|
+
sinks: string[]
|
|
50
|
+
at: number
|
|
51
|
+
replySink?: string
|
|
52
|
+
replyKind?: 'fetch' | 'other'
|
|
53
|
+
echo?: true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One instance's session: what copyAsTest() needs (plain data, raw action data) */
|
|
57
|
+
export interface SessionRecording {
|
|
58
|
+
version: 1
|
|
59
|
+
component: string
|
|
60
|
+
instance: string
|
|
61
|
+
/** the state when the session started for this instance (undefined: unknown) */
|
|
62
|
+
initialState?: any
|
|
63
|
+
/** the component's own `initialState` (renderComponent's default) */
|
|
64
|
+
definitionInitialState?: any
|
|
65
|
+
finalState: any
|
|
66
|
+
/** the action names the component can be sent (model keys, behavior actions); undefined: any */
|
|
67
|
+
actionNames?: string[]
|
|
68
|
+
/** source names beyond DOM / EVENTS / STATE / LOG / CHILD / PARENT / READY */
|
|
69
|
+
drivers: string[]
|
|
70
|
+
/** those of `drivers` renderComponent fakes (makeFetchDriver sources: t.respond / t.fail) */
|
|
71
|
+
fakeable: string[]
|
|
72
|
+
/** the instance's own actions, in order */
|
|
73
|
+
actions: SessionAction[]
|
|
74
|
+
/** state changes in descendant instances a root-level replay can't reproduce */
|
|
75
|
+
foreign: Array<{type: string; component: string; instance: string; cause: ActionCause}>
|
|
76
|
+
/** the session lost its oldest actions (more than the recorder keeps) */
|
|
77
|
+
truncated?: boolean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ActionFilter {
|
|
81
|
+
component?: string
|
|
82
|
+
instance?: string | number
|
|
83
|
+
type?: string | RegExp
|
|
84
|
+
cause?: ActionCause | ActionCause[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface Inst {
|
|
88
|
+
c: any
|
|
89
|
+
id: string
|
|
90
|
+
name: string
|
|
91
|
+
parent: string | null
|
|
92
|
+
stateName: string
|
|
93
|
+
/** created after the session started */
|
|
94
|
+
inSession: boolean
|
|
95
|
+
hasInitial: boolean
|
|
96
|
+
initial?: any
|
|
97
|
+
disposed: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const MAX = 5000
|
|
101
|
+
const STANDARD = /^(DOM|EVENTS|LOG|CHILD|PARENT|READY|props\$|children\$|dispose\$)$/
|
|
102
|
+
|
|
103
|
+
let actions: DevtoolsAction[] = []
|
|
104
|
+
let seq = 0
|
|
105
|
+
let epoch = clockNow()
|
|
106
|
+
let truncated = false
|
|
107
|
+
let created = 0
|
|
108
|
+
const insts = new Map<string, Inst>()
|
|
109
|
+
const byRec = new WeakMap<ActionRecord, DevtoolsAction>()
|
|
110
|
+
/** instance -> the action whose STATE reducer just produced a value (its onReducer follows) */
|
|
111
|
+
const pendingState = new WeakMap<any, DevtoolsAction>()
|
|
112
|
+
const subscribers = new Set<(a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => void>()
|
|
113
|
+
|
|
114
|
+
const notify = (a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => {
|
|
115
|
+
for (const s of subscribers) try { s(a, kind) } catch (_) { /* a subscriber never breaks the app */ }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const idOf = (c: any) => String(c && c._componentNumber)
|
|
119
|
+
|
|
120
|
+
function ensure(c: any): Inst {
|
|
121
|
+
const id = idOf(c)
|
|
122
|
+
let i = insts.get(id)
|
|
123
|
+
if (!i || i.c !== c) {
|
|
124
|
+
const p = c && c.__next && c.__next.parentId
|
|
125
|
+
i = {c, id, name: c && c.name, parent: typeof p == 'number' ? String(p) : null, stateName: (c && c.stateSourceName) || 'STATE',
|
|
126
|
+
inSession: true, hasInitial: false, disposed: false}
|
|
127
|
+
;(i as any).order = ++created
|
|
128
|
+
insts.set(id, i)
|
|
129
|
+
}
|
|
130
|
+
return i
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function clean(c: any, s: any): any {
|
|
134
|
+
try { return typeof c.cleanupCalculated == 'function' ? c.cleanupCalculated(s) : s } catch (_) { return s }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const listener: ActionListener = {
|
|
138
|
+
action(r, c) {
|
|
139
|
+
const info = ensure(c)
|
|
140
|
+
// the instance's state when its session started: INITIALIZE's data (its initial state), else
|
|
141
|
+
// the state it had at its first action
|
|
142
|
+
if (r.type == 'INITIALIZE' && info.inSession) { info.initial = r.data; info.hasInitial = true }
|
|
143
|
+
else if (!info.hasInitial) { info.initial = c.currentState; info.hasInitial = true }
|
|
144
|
+
const a: DevtoolsAction = {seq: ++seq, type: r.type, data: r.data, component: r.component, instance: r.instance,
|
|
145
|
+
parent: info.parent, sinks: r.sinks, cause: r.cause, at: r.time - epoch}
|
|
146
|
+
const slot = (r as any).source
|
|
147
|
+
if (slot !== undefined && (r.cause == 'reply' || r.type == 'RESOURCE')) {
|
|
148
|
+
a.replySink = slot
|
|
149
|
+
a.replyKind = c.sources && c.sources[slot] && c.sources[slot].__sygnalStatic === 'resources' ? 'fetch' : 'other'
|
|
150
|
+
}
|
|
151
|
+
if (r.cause == 'intent' && isStateEcho(r.data, c.currentState)) a.echo = true
|
|
152
|
+
byRec.set(r, a)
|
|
153
|
+
actions.push(a)
|
|
154
|
+
if (actions.length > MAX) { actions.shift(); truncated = true }
|
|
155
|
+
notify(a, 'add')
|
|
156
|
+
},
|
|
157
|
+
sink(r, sink) {
|
|
158
|
+
const a = byRec.get(r)
|
|
159
|
+
if (!a) return
|
|
160
|
+
const info = insts.get(r.instance)
|
|
161
|
+
if (info && info.c && sink == info.stateName) pendingState.set(info.c, a)
|
|
162
|
+
notify(a, 'update')
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function onReducer(c: any, name: string, prev: any, next: any): void {
|
|
167
|
+
const a = pendingState.get(c)
|
|
168
|
+
if (!a || a.type !== name || 'after' in a) return
|
|
169
|
+
pendingState.delete(c)
|
|
170
|
+
a.before = prev
|
|
171
|
+
a.after = clean(c, next)
|
|
172
|
+
notify(a, 'update')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function onDispose(c: any): void {
|
|
176
|
+
const i = insts.get(idOf(c))
|
|
177
|
+
if (i && i.c === c) { i.disposed = true; i.c = undefined }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let unregister: (() => void) | undefined
|
|
181
|
+
|
|
182
|
+
/** PLAN-4.6 R4: the same recorder on the core, from its hooks (per app; a facade per instance) */
|
|
183
|
+
function nextLayer(api: any): any {
|
|
184
|
+
const L: ActionListener = {
|
|
185
|
+
action: (r, iv) => listener.action(r, devFacade(iv, api)),
|
|
186
|
+
sink: (r, sink, reducer) => listener.sink!(r, sink, reducer),
|
|
187
|
+
}
|
|
188
|
+
const log = actionHooks(L)
|
|
189
|
+
return {
|
|
190
|
+
...log,
|
|
191
|
+
onCreate(iv: any) { ensure(devFacade(iv, api)); log.onCreate(iv) },
|
|
192
|
+
onReducer(iv: any, name: string, prev: any, next: any) { onReducer(devFacade(iv, api), name, prev, next) },
|
|
193
|
+
onDispose(iv: any) { onDispose(devFacade(iv, api)) },
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Start recording actions (idempotent; 'sygnal/devtools' does it on import in a browser). Only
|
|
199
|
+
* components created afterwards are recorded. Returns a function that stops recording.
|
|
200
|
+
*/
|
|
201
|
+
export function recordActions(): () => void {
|
|
202
|
+
if (!unregister) {
|
|
203
|
+
const core = (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
204
|
+
if (!core) return () => {}
|
|
205
|
+
unregister = addLayer(nextLayer)
|
|
206
|
+
}
|
|
207
|
+
return stopRecording
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function stopRecording(): void {
|
|
211
|
+
if (unregister) { unregister(); unregister = undefined }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export const isRecording = (): boolean => !!unregister
|
|
215
|
+
|
|
216
|
+
/** Start a new session: forget the recorded actions; each live instance's state is its new initial state. */
|
|
217
|
+
export function clearActions(): void {
|
|
218
|
+
actions = []
|
|
219
|
+
epoch = clockNow()
|
|
220
|
+
truncated = false
|
|
221
|
+
for (const [id, i] of insts) {
|
|
222
|
+
if (i.disposed) { insts.delete(id); continue }
|
|
223
|
+
i.inSession = false
|
|
224
|
+
i.initial = i.c && i.c.currentState
|
|
225
|
+
i.hasInitial = true
|
|
226
|
+
}
|
|
227
|
+
notify(null, 'reset')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const matches = (a: DevtoolsAction, f: ActionFilter) =>
|
|
231
|
+
(f.component === undefined || a.component === f.component) &&
|
|
232
|
+
(f.instance === undefined || a.instance === String(f.instance)) &&
|
|
233
|
+
(f.type === undefined || (typeof f.type == 'string' ? a.type === f.type : f.type.test(a.type))) &&
|
|
234
|
+
(f.cause === undefined || ([] as ActionCause[]).concat(f.cause).includes(a.cause))
|
|
235
|
+
|
|
236
|
+
/** The session's actions (oldest first), optionally filtered by component, instance, type or cause. */
|
|
237
|
+
export function getActions(filter?: ActionFilter): DevtoolsAction[] {
|
|
238
|
+
return filter ? actions.filter(a => matches(a, filter)) : actions.slice()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Subscribe to the log: (action, 'add' | 'update') and (null, 'reset'). Returns unsubscribe. */
|
|
242
|
+
export function onAction(fn: (a: DevtoolsAction | null, kind: 'add' | 'update' | 'reset') => void): () => void {
|
|
243
|
+
subscribers.add(fn)
|
|
244
|
+
return () => { subscribers.delete(fn) }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** the live instances, newest first */
|
|
248
|
+
const live = () => [...insts.values()].filter(i => !i.disposed && i.c).sort((a, b) => (b as any).order - (a as any).order)
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The instance a session target names: undefined (the newest live root), an instance id, a
|
|
252
|
+
* component function (its newest live instance) or run()'s result (its root, by DOM source).
|
|
253
|
+
*/
|
|
254
|
+
export function resolveInstance(target?: any): Inst | undefined {
|
|
255
|
+
const all = live()
|
|
256
|
+
if (target === undefined || target === null) return all.find(i => i.parent === null)
|
|
257
|
+
if (typeof target == 'string' || typeof target == 'number') return insts.get(String(target))
|
|
258
|
+
if (typeof target == 'function') return all.find(i => i.c.view === target)
|
|
259
|
+
if (typeof target == 'object') {
|
|
260
|
+
const s = target.sources
|
|
261
|
+
if (s && typeof s == 'object') {
|
|
262
|
+
return all.find(i => i.parent === null && Object.keys(s).some(k => k != 'STATE' && s[k] && i.c.sources && i.c.sources[k] === s[k])) ||
|
|
263
|
+
all.find(i => i.parent === null)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function descendants(id: string): Set<string> {
|
|
270
|
+
const out = new Set<string>()
|
|
271
|
+
let grew = true
|
|
272
|
+
while (grew) {
|
|
273
|
+
grew = false
|
|
274
|
+
for (const i of insts.values()) if (i.parent !== null && (i.parent === id || out.has(i.parent)) && !out.has(i.id)) { out.add(i.id); grew = true }
|
|
275
|
+
}
|
|
276
|
+
// instances already pruned: their actions still name their parent
|
|
277
|
+
for (const a of actions) if (a.parent !== null && (a.parent === id || out.has(a.parent))) out.add(a.instance)
|
|
278
|
+
return out
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const REPLAYED = new Set<ActionCause>(['intent', 'simulateAction', 'behavior', 'reply', 'agent'])
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The session of one instance (see resolveInstance for `target`) as plain data: its initial
|
|
285
|
+
* and final state, its own actions, and the state changes of its descendants (a replay at this
|
|
286
|
+
* instance can't reproduce them).
|
|
287
|
+
*/
|
|
288
|
+
export function getSession(target?: any): SessionRecording {
|
|
289
|
+
const info = resolveInstance(target)
|
|
290
|
+
if (!info) throw new Error('[Sygnal DevTools] getSession: no live component instance matches' + (target === undefined ? ' (no root recorded: import sygnal/devtools before run())' : ''))
|
|
291
|
+
const c = info.c
|
|
292
|
+
const desc = descendants(info.id)
|
|
293
|
+
const stateNameOf = (id: string) => insts.get(id)?.stateName || 'STATE'
|
|
294
|
+
const model = c.model && typeof c.model == 'object' ? Object.keys(c.model).map(k => k.split('|')[0].trim()) : undefined
|
|
295
|
+
const behaviors = c._behaviorActions && typeof c._behaviorActions == 'object' ? Object.keys(c._behaviorActions) : []
|
|
296
|
+
const sources = c.sources || {}
|
|
297
|
+
const drivers = (c.sourceNames || Object.keys(sources)).filter((n: string) =>
|
|
298
|
+
!STANDARD.test(n) && n !== info.stateName && n != 'STATE' && n != 'state' && !n.startsWith('__'))
|
|
299
|
+
return {
|
|
300
|
+
version: 1,
|
|
301
|
+
component: info.name,
|
|
302
|
+
instance: info.id,
|
|
303
|
+
initialState: info.hasInitial ? info.initial : undefined,
|
|
304
|
+
definitionInitialState: c.view ? c.view.initialState : undefined,
|
|
305
|
+
finalState: c.currentState,
|
|
306
|
+
actionNames: model ? [...model, ...behaviors] : undefined,
|
|
307
|
+
drivers,
|
|
308
|
+
fakeable: drivers.filter((n: string) => sources[n] && sources[n].__sygnalStatic === 'resources'),
|
|
309
|
+
actions: actions.filter(a => a.instance === info.id).map(a => ({
|
|
310
|
+
type: a.type, data: a.data, cause: a.cause, sinks: [...a.sinks], at: a.at,
|
|
311
|
+
...(a.replySink !== undefined ? {replySink: a.replySink, replyKind: a.replyKind} : {}),
|
|
312
|
+
...(a.echo ? {echo: true as const} : {}),
|
|
313
|
+
})),
|
|
314
|
+
foreign: actions.filter(a => desc.has(a.instance) && REPLAYED.has(a.cause) && a.sinks.includes(stateNameOf(a.instance)))
|
|
315
|
+
.map(a => ({type: a.type, component: a.component, instance: a.instance, cause: a.cause})),
|
|
316
|
+
...(truncated ? {truncated: true} : {}),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** A JSON-safe copy for the panel: DOM elements and events become short descriptions. */
|
|
321
|
+
export function preview(v: any): any {
|
|
322
|
+
if (v === undefined) return undefined
|
|
323
|
+
const seen = new WeakSet<object>()
|
|
324
|
+
try {
|
|
325
|
+
return JSON.parse(JSON.stringify(v, (_k, x) => {
|
|
326
|
+
if (typeof x == 'function') return `[Function ${x.name || 'anonymous'}]`
|
|
327
|
+
if (typeof x == 'symbol') return String(x)
|
|
328
|
+
if (typeof x == 'bigint') return `${x}n`
|
|
329
|
+
if (x && typeof x == 'object') {
|
|
330
|
+
if (isElement(x)) return describeElement(x)
|
|
331
|
+
if (isEvent(x)) return `[${x.constructor && x.constructor.name || 'Event'} ${x.type}]`
|
|
332
|
+
if (x instanceof Map) return {'[Map]': [...x.entries()]}
|
|
333
|
+
if (x instanceof Set) return {'[Set]': [...x.values()]}
|
|
334
|
+
if (seen.has(x)) return '[Circular]'
|
|
335
|
+
seen.add(x)
|
|
336
|
+
}
|
|
337
|
+
return x
|
|
338
|
+
}))
|
|
339
|
+
} catch (_) {
|
|
340
|
+
return '[unserializable]'
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const isPlain = (x: any) => !!x && typeof x == 'object' && (Object.getPrototypeOf(x) === Object.prototype || Object.getPrototypeOf(x) === null)
|
|
345
|
+
|
|
346
|
+
export function deepEqual(a: any, b: any): boolean {
|
|
347
|
+
if (Object.is(a, b)) return true
|
|
348
|
+
if (!a || !b || typeof a != 'object' || typeof b != 'object') return false
|
|
349
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false
|
|
350
|
+
const ka = Object.keys(a).filter(k => a[k] !== undefined), kb = Object.keys(b).filter(k => b[k] !== undefined)
|
|
351
|
+
return ka.length == kb.length && ka.every(k => deepEqual(a[k], b[k]))
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* The data is the state itself (STATE.stream in the intent; calculated fields may be added): the
|
|
356
|
+
* replay sends it again by itself, so it isn't replayed.
|
|
357
|
+
*/
|
|
358
|
+
function isStateEcho(data: any, state: any): boolean {
|
|
359
|
+
if (!isPlain(data) || !isPlain(state)) return false
|
|
360
|
+
const keys = Object.keys(state)
|
|
361
|
+
return keys.length > 0 && (data === state || keys.every(k => deepEqual(data[k], state[k])))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export const isElement = (x: any): boolean => !!x && typeof x == 'object' && x.nodeType === 1 && typeof x.tagName == 'string'
|
|
365
|
+
export const isEvent = (x: any): boolean => !!x && typeof x == 'object' && typeof x.type == 'string' && typeof x.preventDefault == 'function' && 'target' in x
|
|
366
|
+
|
|
367
|
+
const describeElement = (el: any) => {
|
|
368
|
+
const cls = typeof el.className == 'string' && el.className.trim() ? '.' + el.className.trim().split(/\s+/).join('.') : ''
|
|
369
|
+
return `<${String(el.tagName).toLowerCase()}${el.id ? '#' + el.id : ''}${cls}>`
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** test seam: forget everything (sessions, instances); recording stays as it is */
|
|
373
|
+
export function _resetActionLog(): void {
|
|
374
|
+
actions = []
|
|
375
|
+
seq = 0
|
|
376
|
+
epoch = clockNow()
|
|
377
|
+
truncated = false
|
|
378
|
+
insts.clear()
|
|
379
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {SygnalDevTools} from './devtools';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The installed DevTools bridge, or undefined (D77: the bridge itself lives in the
|
|
5
|
+
* 'sygnal/devtools' dev entry, which sygnal/vite injects in dev).
|
|
6
|
+
*/
|
|
7
|
+
export function getDevTools(): SygnalDevTools | undefined {
|
|
8
|
+
return (globalThis as any).__SYGNAL_DEVTOOLS__;
|
|
9
|
+
}
|