@omniaura/solid-pulse 0.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/README.md +128 -0
- package/dist/bridge.d.ts +202 -0
- package/dist/bridge.js +10 -0
- package/dist/bridge.js.map +1 -0
- package/dist/chunk-4QA2G6S3.js +15 -0
- package/dist/chunk-4QA2G6S3.js.map +1 -0
- package/dist/chunk-5FYH2KEZ.js +66 -0
- package/dist/chunk-5FYH2KEZ.js.map +1 -0
- package/dist/chunk-C72EYM65.js +462 -0
- package/dist/chunk-C72EYM65.js.map +1 -0
- package/dist/chunk-IXNWEUNF.js +114 -0
- package/dist/chunk-IXNWEUNF.js.map +1 -0
- package/dist/chunk-TVSI7G5S.js +414 -0
- package/dist/chunk-TVSI7G5S.js.map +1 -0
- package/dist/chunk-WIMCBTHZ.js +21 -0
- package/dist/chunk-WIMCBTHZ.js.map +1 -0
- package/dist/cli.js +229 -0
- package/dist/cli.js.map +1 -0
- package/dist/controller-3akN6Qi0.d.ts +210 -0
- package/dist/core.d.ts +76 -0
- package/dist/core.js +41 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +175 -0
- package/dist/index.js +987 -0
- package/dist/index.js.map +1 -0
- package/dist/panel.d.ts +34 -0
- package/dist/panel.js +408 -0
- package/dist/panel.js.map +1 -0
- package/dist/query.d.ts +93 -0
- package/dist/query.js +164 -0
- package/dist/query.js.map +1 -0
- package/dist/vite.d.ts +41 -0
- package/dist/vite.js +76 -0
- package/dist/vite.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/solid/instrument.ts","../src/solid/dom.ts","../src/solid/network.ts","../src/bridge/client.ts","../src/index.ts"],"sourcesContent":["/**\n * Solid instrumentation through the official dev hooks (`DEV.hooks`), which\n * exist only in Solid's development build. In a production build `DEV` is\n * undefined and this module becomes a no-op — nothing is patched.\n *\n * What we see, precisely:\n * - `afterCreateOwner(owner)` fires for every root, computation and (in dev)\n * every component: dev components are computations carrying `.component`.\n * - We wrap each non-component computation's `fn` so we know when a memo /\n * effect / render-effect actually re-runs. Solid never re-runs component\n * bodies, so there is no \"rerender\" to report — only these.\n * - `afterUpdate()` marks the end of a synchronous update; together with a\n * microtask fallback it closes a \"flush\" group used to attribute DOM\n * mutations to the computations that produced them.\n * - `sharedConfig.context` is set while hydrating, so mounts during hydration\n * are labelled as such.\n */\n\nimport { DEV, getOwner, sharedConfig } from \"solid-js\";\nimport type { PulseController } from \"../core/controller.js\";\nimport type { ComponentRef, Rect } from \"../core/events.js\";\n\ntype Fn = (...args: unknown[]) => unknown;\n\ninterface OwnerLike {\n fn?: Fn;\n component?: Fn;\n name?: string;\n pure?: boolean;\n user?: boolean;\n comparator?: unknown;\n owner?: OwnerLike | null;\n cleanups?: (() => void)[] | null;\n}\n\nexport interface ComponentInfo {\n id: number;\n name: string;\n parent: number | null;\n hydrated: boolean;\n mountedAt: number;\n mountedWall: number;\n flush: number;\n disposedAt: number | null;\n}\n\nexport type ComputationKind = \"memo\" | \"computed\" | \"effect\" | \"render\";\n\nexport interface FlushSummary {\n id: number;\n computations: number;\n byKind: Record<ComputationKind, number>;\n byComponent: Record<string, number>;\n components: ComponentRef[];\n}\n\nexport interface SolidInstrumentation {\n readonly available: boolean;\n flushId(): number;\n /** Components whose computations ran in the most recently closed flush. */\n lastFlushComponents(): ComponentRef[];\n componentFor(owner: unknown): ComponentRef | null;\n currentComponent(): ComponentRef | null;\n components(): ComponentInfo[];\n attachElement(componentId: number, el: Element): void;\n rectFor(component: ComponentRef): Rect | null;\n elementsFor(componentId: number): Element[];\n dispose(): void;\n}\n\nconst MAX_RECENT_DISPOSED = 64;\nconst MAX_ELEMENTS_PER_COMPONENT = 8;\n\nexport function installSolid(controller: PulseController): SolidInstrumentation {\n const bus = controller.bus;\n const hooks = (DEV as { hooks?: Record<string, Fn | null> } | undefined)?.hooks;\n\n const compByOwner = new WeakMap<object, ComponentInfo>();\n const live = new Map<number, ComponentInfo>();\n const elements = new Map<number, WeakRef<Element>[]>();\n const recentDisposed = new Map<string, { t: number; flush: number }>();\n let nextId = 1;\n let flushId = 0;\n let flushOpen = false;\n let runs = 0;\n let byKind: Record<ComputationKind, number> = { memo: 0, computed: 0, effect: 0, render: 0 };\n let byComponent = new Map<string, number>();\n let componentsRan = new Map<number, ComponentRef>();\n let lastClosed: ComponentRef[] = [];\n let roots = 0;\n\n const now = () => performance.now();\n\n function toRef(info: ComponentInfo | null): ComponentRef | null {\n if (!info) return null;\n const chain: string[] = [];\n let cur: ComponentInfo | undefined = info;\n while (cur && chain.length < 8) {\n chain.push(cur.name);\n cur = cur.parent === null ? undefined : live.get(cur.parent);\n }\n return { id: info.id, name: info.name, chain };\n }\n\n function componentInfoFor(owner: unknown): ComponentInfo | null {\n let cur = owner as OwnerLike | null | undefined;\n let hops = 0;\n while (cur && hops++ < 200) {\n const info = compByOwner.get(cur as object);\n if (info) return info;\n cur = cur.owner;\n }\n return null;\n }\n\n function ensureFlush() {\n if (flushOpen) return;\n flushOpen = true;\n flushId++;\n queueMicrotask(closeFlush);\n }\n\n function closeFlush() {\n if (!flushOpen) return;\n flushOpen = false;\n lastClosed = [...componentsRan.values()];\n if (runs > 0 || componentsRan.size > 0) {\n const topComponents: Record<string, number> = {};\n const sorted = [...byComponent.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);\n for (const [name, n] of sorted) topComponents[name] = n;\n bus.emit(\n \"solid.flush\",\n {\n computations: runs,\n byKind: { ...byKind },\n byComponent: topComponents,\n components: lastClosed.map((c) => c.name),\n },\n { flush: flushId, component: lastClosed.length === 1 ? lastClosed[0] : null },\n );\n }\n runs = 0;\n byKind = { memo: 0, computed: 0, effect: 0, render: 0 };\n byComponent = new Map();\n componentsRan = new Map();\n }\n\n function kindOf(owner: OwnerLike): ComputationKind {\n if (owner.pure) return owner.comparator !== undefined ? \"memo\" : \"computed\";\n return owner.user ? \"effect\" : \"render\";\n }\n\n function wrapComputation(owner: OwnerLike) {\n const orig = owner.fn;\n if (typeof orig !== \"function\") return;\n // `comparator` (memos) and `user` (effects) are assigned by their creators\n // after createComputation returns, so the kind is settled at the first run.\n let kind: ComputationKind = \"render\";\n let first = true;\n let isComponent = false;\n owner.fn = function pulseWrapped(this: unknown, ...args: unknown[]) {\n if (first) {\n first = false;\n // Dev components are computations too: `devComponent` creates the\n // computation (our hook fires here, before `.component` is assigned)\n // and then runs it once with `.component` set. So the creation run is\n // where a component reveals itself — that run is its mount. For every\n // other computation the creation run is a mount as well, not an update.\n if (owner.component) {\n isComponent = true;\n registerComponent(owner);\n } else {\n kind = kindOf(owner);\n }\n return orig.apply(this, args);\n }\n if (isComponent) {\n // Solid never re-runs a component body; if it ever did we would still\n // not call it a rerender — report it for what it is.\n if (controller.isOn(\"solid\")) bus.emit(\"pulse.note\", { note: `component body re-executed: ${owner.name ?? \"?\"}` });\n return orig.apply(this, args);\n }\n if (controller.isOn(\"solid\")) {\n ensureFlush();\n runs++;\n byKind[kind]++;\n const info = componentInfoFor(owner);\n if (info) {\n byComponent.set(info.name, (byComponent.get(info.name) ?? 0) + 1);\n if (!componentsRan.has(info.id)) componentsRan.set(info.id, toRef(info)!);\n }\n if (controller.isOn(\"verboseComputations\")) {\n bus.emit(\"solid.computation\", { kind, name: owner.name ?? null }, { flush: flushId, component: toRef(info) });\n }\n }\n return orig.apply(this, args);\n };\n }\n\n function registerComponent(owner: OwnerLike) {\n const name = owner.name || owner.component?.name || \"Anonymous\";\n const parent = componentInfoFor(owner.owner);\n const t = now();\n const info: ComponentInfo = {\n id: nextId++,\n name,\n parent: parent?.id ?? null,\n hydrated: Boolean((sharedConfig as { context?: unknown }).context),\n mountedAt: t,\n mountedWall: Date.now(),\n flush: flushId,\n disposedAt: null,\n };\n compByOwner.set(owner as object, info);\n live.set(info.id, info);\n const key = `${name}|${parent?.name ?? \"\"}`;\n // Open the flush first so `flushId` is the one this mount belongs to.\n if (controller.isOn(\"solid\")) ensureFlush();\n const disposed = recentDisposed.get(key);\n const remount = disposed !== undefined && flushId - disposed.flush <= 1 && t - disposed.t < 250;\n if (disposed) recentDisposed.delete(key);\n if (controller.isOn(\"solid\")) {\n ensureFlush();\n const ref = toRef(info)!;\n componentsRan.set(info.id, ref);\n bus.emit(\n remount ? \"solid.component.remount\" : \"solid.component.mount\",\n {\n name,\n parent: parent?.name ?? null,\n hydrated: info.hydrated,\n id: info.id,\n ...(remount ? { gapMs: Math.round((t - disposed!.t) * 100) / 100 } : {}),\n },\n { flush: flushId, component: ref },\n );\n }\n (owner.cleanups ||= []).push(() => {\n const d = now();\n if (controller.isOn(\"solid\")) ensureFlush();\n info.disposedAt = d;\n live.delete(info.id);\n elements.delete(info.id);\n recentDisposed.set(key, { t: d, flush: flushId });\n while (recentDisposed.size > MAX_RECENT_DISPOSED) {\n const oldest = recentDisposed.keys().next().value;\n if (oldest === undefined) break;\n recentDisposed.delete(oldest);\n }\n if (controller.isOn(\"solid\")) {\n ensureFlush();\n bus.emit(\n \"solid.component.dispose\",\n { name, parent: parent?.name ?? null, id: info.id, lifetimeMs: Math.round((d - info.mountedAt) * 100) / 100 },\n { flush: flushId, component: toRef(info) },\n );\n }\n });\n }\n\n let prevAfterCreateOwner: Fn | null = null;\n let prevAfterUpdate: Fn | null = null;\n const available = Boolean(hooks);\n\n if (hooks) {\n prevAfterCreateOwner = hooks.afterCreateOwner ?? null;\n prevAfterUpdate = hooks.afterUpdate ?? null;\n hooks.afterCreateOwner = ((owner: OwnerLike) => {\n prevAfterCreateOwner?.(owner);\n try {\n if (typeof owner.fn === \"function\") wrapComputation(owner);\n else if (!owner.owner) {\n roots++;\n if (controller.isOn(\"solid\")) bus.emit(\"solid.root\", { roots });\n }\n } catch (err) {\n console.warn(\"[solid-pulse] instrumentation error\", err);\n }\n }) as Fn;\n hooks.afterUpdate = () => {\n prevAfterUpdate?.();\n closeFlush();\n };\n }\n\n const api: SolidInstrumentation = {\n available,\n flushId: () => flushId,\n lastFlushComponents: () => (flushOpen ? [...componentsRan.values()] : lastClosed),\n componentFor: (owner) => toRef(componentInfoFor(owner)),\n currentComponent: () => toRef(componentInfoFor(getOwner())),\n components: () => [...live.values()],\n attachElement(componentId, el) {\n const list = elements.get(componentId) ?? [];\n if (list.some((r) => r.deref() === el)) return;\n list.push(new WeakRef(el));\n while (list.length > MAX_ELEMENTS_PER_COMPONENT) list.shift();\n elements.set(componentId, list);\n },\n elementsFor(componentId) {\n const out: Element[] = [];\n for (const ref of elements.get(componentId) ?? []) {\n const el = ref.deref();\n if (el && el.isConnected) out.push(el);\n }\n return out;\n },\n rectFor(component) {\n let els = api.elementsFor(component.id);\n if (els.length === 0 && typeof document !== \"undefined\") {\n // Fallback: solid-grab's build-time attribute, when present.\n const escaped = component.name.replace(/[\"\\\\]/g, \"\\\\$&\");\n els = [...document.querySelectorAll(`[data-solid-component=\"${escaped}\"]`)].slice(0, 4);\n }\n let x1 = Infinity, y1 = Infinity, x2 = -Infinity, y2 = -Infinity;\n for (const el of els) {\n const r = el.getBoundingClientRect();\n if (r.width === 0 && r.height === 0) continue;\n x1 = Math.min(x1, r.left);\n y1 = Math.min(y1, r.top);\n x2 = Math.max(x2, r.right);\n y2 = Math.max(y2, r.bottom);\n }\n if (!Number.isFinite(x1)) return null;\n return { x: x1, y: y1, w: x2 - x1, h: y2 - y1 };\n },\n dispose() {\n if (hooks) {\n hooks.afterCreateOwner = prevAfterCreateOwner;\n hooks.afterUpdate = prevAfterUpdate;\n }\n },\n };\n\n controller.register(\n { name: \"inspect.components\", summary: \"Live component instances (name, parent, hydrated, age).\", args: { name: \"substring filter\" }, ui: \"Pulse tab › Components\" },\n (a) => {\n const needle = a.name === undefined ? \"\" : String(a.name).toLowerCase();\n const t = now();\n return api\n .components()\n .filter((c) => !needle || c.name.toLowerCase().includes(needle))\n .map((c) => ({ id: c.id, name: c.name, parent: c.parent === null ? null : live.get(c.parent)?.name ?? null, hydrated: c.hydrated, ageMs: Math.round(t - c.mountedAt), elements: api.elementsFor(c.id).length }));\n },\n );\n controller.register(\n { name: \"inspect.solid\", summary: \"Solid dev-hook availability, root count, live component count, current flush id.\" },\n () => ({ available, roots, liveComponents: live.size, flush: flushId }),\n );\n\n if (!available) {\n bus.emit(\"pulse.note\", { note: \"solid-js DEV hooks unavailable (production build?) — Solid instrumentation disabled\" });\n }\n\n return api;\n}\n","/**\n * DOM observation. A MutationObserver tells us what actually changed on\n * screen; nothing here infers \"rerenders\". Beyond plain mutations it detects\n * the pattern that costs people days: a subtree is removed and the *same node\n * instance* is re-inserted moments later (a Suspense boundary flipping to its\n * fallback and back, a keyed <Show>/<Switch> toggling). That detach/reattach\n * silently resets scroll positions to 0 and drops focus to <body>, with no\n * component cleanup running — so we record scrollTop and focus at detach time\n * and compare after reattach.\n */\n\nimport type { PulseController } from \"../core/controller.js\";\nimport type { ComponentRef, ElementRef, Rect } from \"../core/events.js\";\nimport { FlashOverlay, OWN_ATTR } from \"../overlay/flash.js\";\nimport type { SolidInstrumentation } from \"./instrument.js\";\n\nconst MAX_TARGETS_PER_BATCH = 40;\nconst MAX_TARGETS_IN_EVENT = 25;\nconst MAX_TRACKED_SCROLLERS = 50;\nconst MAX_DETACHED = 200;\nconst DETACHED_TTL_MS = 3000;\n\ninterface DetachRecord {\n t: number;\n desc: ElementRef;\n scrollers: { el: Element; desc: ElementRef; scrollTop: number }[];\n hadFocus: boolean;\n focused: ElementRef | null;\n component: ComponentRef | null;\n}\n\nexport function describeElement(el: Element, withRect = true): ElementRef {\n const out: ElementRef = { tag: el.tagName.toLowerCase() };\n if (el.id) out.id = el.id;\n const testId = el.getAttribute(\"data-testid\");\n if (testId) out.testId = testId;\n const cls = typeof el.className === \"string\" ? el.className.trim() : \"\";\n if (cls) out.classes = cls.length > 80 ? cls.slice(0, 77) + \"...\" : cls;\n const comp = el.closest(\"[data-solid-component]\");\n out.component = comp ? comp.getAttribute(\"data-solid-component\") : null;\n const src = el.closest(\"[data-solid-source]\");\n out.source = src ? src.getAttribute(\"data-solid-source\") : null;\n if (withRect) {\n const r = el.getBoundingClientRect();\n out.rect = { x: r.left, y: r.top, w: r.width, h: r.height };\n }\n return out;\n}\n\nexport function toSelector(el: Element): string {\n if (el.id) return `#${cssEscape(el.id)}`;\n const testId = el.getAttribute(\"data-testid\");\n if (testId) return `[data-testid=\"${testId.replace(/\"/g, '\\\\\"')}\"]`;\n const parts: string[] = [];\n let cur: Element | null = el;\n while (cur && parts.length < 5 && cur !== document.documentElement) {\n const parent: Element | null = cur.parentElement;\n let part = cur.tagName.toLowerCase();\n if (parent) {\n const siblings = [...parent.children].filter((c) => c.tagName === cur!.tagName);\n if (siblings.length > 1) part += `:nth-of-type(${siblings.indexOf(cur) + 1})`;\n }\n parts.unshift(part);\n if (cur.id) {\n parts[0] = `#${cssEscape(cur.id)}`;\n break;\n }\n cur = parent;\n }\n return parts.join(\" > \");\n}\n\nfunction cssEscape(s: string) {\n return typeof CSS !== \"undefined\" && CSS.escape ? CSS.escape(s) : s.replace(/[^a-zA-Z0-9_-]/g, \"\\\\$&\");\n}\n\nexport interface DomInstrumentation {\n dispose(): void;\n}\n\n/**\n * Run after the next paint — or after 50 ms if no frame comes (hidden tabs and\n * headless runs throttle requestAnimationFrame, and a reattach report must not\n * wait for the tab to be foregrounded).\n */\nfunction nextFrame(fn: () => void) {\n let done = false;\n const run = () => {\n if (done) return;\n done = true;\n fn();\n };\n if (typeof requestAnimationFrame === \"function\") requestAnimationFrame(run);\n setTimeout(run, 50);\n}\n\nexport function installDom(controller: PulseController, solid: SolidInstrumentation | null, overlay: FlashOverlay | null): DomInstrumentation {\n const bus = controller.bus;\n const scrollTops = new Map<Element, number>();\n let lastFocused: Element | null = null;\n const detached = new Map<Node, DetachRecord>();\n const now = () => performance.now();\n\n const isOwn = (n: Node): boolean => {\n const el = n instanceof Element ? n : n.parentElement;\n return el ? el.closest(`[${OWN_ATTR}]`) !== null : false;\n };\n\n const onScroll = (e: Event) => {\n const t = e.target;\n if (!(t instanceof Element)) return;\n scrollTops.delete(t);\n scrollTops.set(t, t.scrollTop);\n if (scrollTops.size > MAX_TRACKED_SCROLLERS) {\n const oldest = scrollTops.keys().next().value;\n if (oldest) scrollTops.delete(oldest);\n }\n };\n const onFocusIn = (e: FocusEvent) => {\n if (e.target instanceof Element && !isOwn(e.target)) lastFocused = e.target;\n };\n document.addEventListener(\"scroll\", onScroll, { capture: true, passive: true });\n document.addEventListener(\"focusin\", onFocusIn, true);\n // Headless/background pages often do not dispatch focusin for programmatic\n // `el.focus()` (the window has no system focus), so also sample the active\n // element cheaply; agent-driven QA relies on this.\n const focusPoll = setInterval(() => {\n const active = document.activeElement;\n if (active && active !== document.body && active !== document.documentElement && !isOwn(active)) lastFocused = active;\n }, 200);\n\n function attributionFor(target: Element, flushComps: ComponentRef[]): ComponentRef | null {\n if (flushComps.length === 1) return flushComps[0]!;\n const named = target.closest(\"[data-solid-component]\")?.getAttribute(\"data-solid-component\");\n if (named) {\n const match = flushComps.find((c) => c.name === named);\n if (match) return match;\n return { id: -1, name: named };\n }\n return flushComps.length > 1 ? null : null;\n }\n\n function pruneDetached(t: number) {\n for (const [node, rec] of detached) {\n if (t - rec.t > DETACHED_TTL_MS) detached.delete(node);\n else break;\n }\n while (detached.size > MAX_DETACHED) {\n const oldest = detached.keys().next().value;\n if (oldest === undefined) break;\n detached.delete(oldest);\n }\n }\n\n const observer = new MutationObserver((records) => {\n if ((globalThis as { __PULSE_DEBUG?: boolean }).__PULSE_DEBUG) console.error(\"DBG-MO\", records.map((r) => `${r.type}:${(r.target as Element).tagName ?? \"?\"}:+${r.addedNodes.length}/-${r.removedNodes.length}`).join(\" \"), \"dom on:\", controller.isOn(\"dom\"));\n if (!controller.isOn(\"dom\")) return;\n const t = now();\n const flush = solid?.flushId() ?? 0;\n const flushComps = solid?.lastFlushComponents() ?? [];\n const targets = new Map<Element, { types: Set<string>; attrs: Set<string>; added: number; removed: number }>();\n const detachedNow: DetachRecord[] = [];\n const reattached: { node: Element; rec: DetachRecord }[] = [];\n\n for (const r of records) {\n const target = r.target instanceof Element ? r.target : r.target.parentElement;\n if (!target || isOwn(target)) continue;\n let info = targets.get(target);\n if (!info) {\n if (targets.size >= MAX_TARGETS_PER_BATCH) continue;\n info = { types: new Set(), attrs: new Set(), added: 0, removed: 0 };\n targets.set(target, info);\n }\n info.types.add(r.type);\n if (r.type === \"attributes\" && r.attributeName) info.attrs.add(r.attributeName);\n info.added += r.addedNodes.length;\n info.removed += r.removedNodes.length;\n\n for (const n of r.removedNodes) {\n if (!(n instanceof Element) || isOwn(n)) continue;\n const scrollers: DetachRecord[\"scrollers\"] = [];\n for (const [el, top] of scrollTops) {\n if (top > 0 && (n === el || n.contains(el))) scrollers.push({ el, desc: describeElement(el, false), scrollTop: top });\n }\n const hadFocus = lastFocused !== null && (n === lastFocused || n.contains(lastFocused));\n const rec: DetachRecord = {\n t,\n desc: describeElement(n, false),\n scrollers,\n hadFocus,\n focused: hadFocus && lastFocused ? describeElement(lastFocused, false) : null,\n component: attributionFor(n, flushComps),\n };\n detached.set(n, rec);\n if (scrollers.length || hadFocus) detachedNow.push(rec);\n }\n for (const n of r.addedNodes) {\n if (!(n instanceof Element)) continue;\n const rec = detached.get(n);\n if (rec) {\n detached.delete(n);\n reattached.push({ node: n, rec });\n }\n }\n }\n pruneDetached(t);\n\n if (targets.size > 0) {\n const wantRects = Boolean(overlay && controller.isOn(\"flash\"));\n const rects: Rect[] = [];\n const summary: Array<ElementRef & { types: string[]; attrs?: string[]; added?: number; removed?: number }> = [];\n let attributed: ComponentRef | null = null;\n let i = 0;\n for (const [el, info] of targets) {\n if (wantRects || i < MAX_TARGETS_IN_EVENT) {\n const desc = describeElement(el, wantRects);\n if (wantRects && desc.rect && el.isConnected) rects.push(desc.rect);\n if (i < MAX_TARGETS_IN_EVENT) {\n summary.push({\n ...desc,\n types: [...info.types],\n ...(info.attrs.size ? { attrs: [...info.attrs] } : {}),\n ...(info.added ? { added: info.added } : {}),\n ...(info.removed ? { removed: info.removed } : {}),\n });\n }\n }\n const comp = attributionFor(el, flushComps);\n if (comp && !attributed) attributed = comp;\n if (solid && comp && comp.id > 0 && el.isConnected) solid.attachElement(comp.id, el);\n i++;\n }\n bus.emit(\n \"dom.mutation\",\n {\n records: records.length,\n targets: targets.size,\n summary,\n attributedTo: flushComps.length === 1 ? \"single-component-flush\" : flushComps.length > 1 ? \"multi-component-flush\" : \"outside-solid-flush\",\n },\n { flush, component: attributed },\n );\n if (wantRects && rects.length) overlay!.flash(rects, \"dom\", { label: attributed?.name });\n }\n\n for (const rec of detachedNow) {\n bus.emit(\n \"dom.detach\",\n {\n element: rec.desc,\n scrollers: rec.scrollers.map((s) => ({ element: s.desc, scrollTop: s.scrollTop })),\n hadFocus: rec.hadFocus,\n focused: rec.focused,\n },\n { flush, component: rec.component },\n );\n }\n\n if (reattached.length) {\n nextFrame(() => {\n const t2 = now();\n for (const { node, rec } of reattached) {\n const scrollReset = rec.scrollers.map((s) => ({\n element: s.desc,\n before: s.scrollTop,\n after: s.el.scrollTop,\n reset: s.scrollTop > 0 && s.el.scrollTop === 0,\n }));\n const focusLost = rec.hadFocus && !node.contains(document.activeElement);\n const chain = rec.component?.chain ?? [];\n const data = {\n element: rec.desc,\n gapMs: Math.round((t2 - rec.t) * 100) / 100,\n scrollReset,\n focusLost,\n suspenseInChain: chain.includes(\"Suspense\"),\n selector: toSelector(node),\n };\n bus.emit(\"dom.reattach\", data, { flush, component: rec.component });\n if (overlay && controller.isOn(\"flash\") && node.isConnected) {\n const r = node.getBoundingClientRect();\n overlay.flash([{ x: r.left, y: r.top, w: r.width, h: r.height }], \"reattach\", {\n ms: 900,\n label: `reattach ${scrollReset.some((s) => s.reset) ? \"· scroll reset\" : \"\"}${focusLost ? \" · focus lost\" : \"\"}`.trim(),\n });\n }\n }\n });\n }\n\n if (lastFocused && !lastFocused.isConnected) {\n const active = document.activeElement;\n if (!active || active === document.body) {\n bus.emit(\"focus.lost\", { element: describeElement(lastFocused, false), cause: \"element removed from document\" }, { flush });\n }\n lastFocused = null;\n }\n });\n\n observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });\n\n // ── commands ─────────────────────────────────────────────────────\n\n function grabContext(el: Element) {\n const grab = (window as unknown as { __SOLID_GRAB__?: { inspect?: (el: HTMLElement) => { formatted: string; elementSource: unknown; components: unknown } } }).__SOLID_GRAB__;\n const desc = describeElement(el);\n const ctx = grab?.inspect && el instanceof HTMLElement ? grab.inspect(el) : null;\n return {\n element: desc,\n selector: toSelector(el),\n pulseComponent: solid ? (() => {\n for (const c of solid.components()) if (solid.elementsFor(c.id).includes(el)) return { id: c.id, name: c.name };\n return null;\n })() : null,\n grab: ctx ? { formatted: ctx.formatted, elementSource: ctx.elementSource, components: ctx.components } : null,\n html: el.outerHTML.length > 500 ? el.outerHTML.slice(0, 500) + \"...\" : el.outerHTML,\n };\n }\n\n controller.register(\n {\n name: \"inspect.element\",\n summary: \"Element → source context: data-solid-source/component, solid-grab formatted context when installed, pulse component attribution.\",\n args: { selector: \"CSS selector (first match)\", x: \"viewport x (with y, instead of selector)\", y: \"viewport y\" },\n ui: \"Grab tab › Pick element (Alt+click via solid-grab)\",\n },\n (a) => {\n let el: Element | null = null;\n if (a.selector !== undefined) el = document.querySelector(String(a.selector));\n else if (a.x !== undefined && a.y !== undefined) {\n const hit = document.elementsFromPoint(Number(a.x), Number(a.y)).find((e) => !isOwn(e));\n el = hit ?? null;\n }\n if (!el) throw new Error(\"no element matched\");\n return grabContext(el);\n },\n );\n controller.register(\n { name: \"dom.highlight\", summary: \"Flash an outline around matching elements so a human can see what an agent is looking at.\", args: { selector: \"CSS selector\", ms: \"duration (default 1200)\", all: \"true = every match (max 20)\" }, ui: \"Grab tab › Highlight\" },\n (a) => {\n if (!overlay) throw new Error(\"overlay not mounted\");\n const all = a.all === true || a.all === \"true\";\n const nodes = all ? [...document.querySelectorAll(String(a.selector))].slice(0, 20) : [document.querySelector(String(a.selector))].filter(Boolean) as Element[];\n const rects = nodes.map((n) => {\n const r = n.getBoundingClientRect();\n return { x: r.left, y: r.top, w: r.width, h: r.height };\n });\n overlay.flash(rects, \"highlight\", { ms: a.ms === undefined ? 1200 : Number(a.ms), label: String(a.selector) });\n return { matched: nodes.length, rects };\n },\n );\n controller.register(\n { name: \"inspect.focus\", summary: \"Active element and the last element that had focus.\", ui: \"Pulse tab › footer\" },\n () => ({\n active: document.activeElement && document.activeElement !== document.body ? describeElement(document.activeElement) : null,\n lastFocused: lastFocused ? describeElement(lastFocused) : null,\n }),\n );\n controller.register(\n { name: \"inspect.scrollers\", summary: \"Elements that have scrolled recently with their last scrollTop.\", ui: \"Pulse tab › footer\" },\n () => [...scrollTops].map(([el, top]) => ({ element: describeElement(el, false), selector: toSelector(el), scrollTop: top, connected: el.isConnected })),\n );\n\n return {\n dispose() {\n observer.disconnect();\n clearInterval(focusPoll);\n document.removeEventListener(\"scroll\", onScroll, true);\n document.removeEventListener(\"focusin\", onFocusIn, true);\n controller.unregister(\"inspect.element\");\n controller.unregister(\"dom.highlight\");\n controller.unregister(\"inspect.focus\");\n controller.unregister(\"inspect.scrollers\");\n },\n };\n}\n","/**\n * Network lifecycle. `fetch`, `WebSocket` and `EventSource` are wrapped so we\n * see start/end/error, streaming responses (SSE detected by content-type and\n * counted frame by frame), socket open/message/close, and aborts. URLs are\n * redacted; bodies are never captured unless the `captureBodies` feature is on,\n * and even then are truncated.\n *\n * If an in-page simulator also patches these globals, install it *before*\n * pulse so pulse wraps the simulator and still sees app-level calls.\n */\n\nimport type { PulseController } from \"../core/controller.js\";\nimport { redactText, redactUrl } from \"../core/redact.js\";\nimport type { SolidInstrumentation } from \"./instrument.js\";\n\nconst MAX_BODY_CHARS = 2000;\nconst MAX_INDIVIDUAL_STREAM_MESSAGES = 200;\n\nexport interface NetworkInstrumentation {\n dispose(): void;\n}\n\nfunction bodySize(body: unknown): number | null {\n if (body == null) return 0;\n if (typeof body === \"string\") return body.length;\n if (body instanceof ArrayBuffer) return body.byteLength;\n if (ArrayBuffer.isView(body)) return body.byteLength;\n if (typeof Blob !== \"undefined\" && body instanceof Blob) return body.size;\n if (typeof URLSearchParams !== \"undefined\" && body instanceof URLSearchParams) return body.toString().length;\n return null;\n}\n\nfunction messageType(data: unknown): string | null {\n if (typeof data !== \"string\" || data.length > 65536 || data[0] !== \"{\") return null;\n const m = /\"type\"\\s*:\\s*\"([^\"]{1,80})\"/.exec(data);\n return m ? m[1]! : null;\n}\n\nfunction preview(data: unknown, on: boolean): string | undefined {\n if (!on) return undefined;\n if (typeof data === \"string\") return redactText(data.length > MAX_BODY_CHARS ? data.slice(0, MAX_BODY_CHARS) + \"…\" : data);\n return undefined;\n}\n\nexport interface NetworkOptions {\n /** URLs to leave untraced (e.g. the devtools' own bridge). */\n ignoreUrl?: (url: string) => boolean;\n}\n\nexport function installNetwork(controller: PulseController, solid: SolidInstrumentation | null, options: NetworkOptions = {}): NetworkInstrumentation {\n const bus = controller.bus;\n const ignored = (url: string) => options.ignoreUrl?.(url) === true;\n let nextId = 1;\n const g = globalThis as unknown as { fetch: typeof fetch; WebSocket: typeof WebSocket; EventSource?: typeof EventSource };\n const origFetch = g.fetch;\n const NativeWebSocket = g.WebSocket;\n const NativeEventSource = g.EventSource;\n\n // ── fetch ────────────────────────────────────────────────────────\n\n function wrapSse(res: Response, id: number, url: string): Response {\n if (!res.body) return res;\n let count = 0;\n let carry = \"\";\n const decoder = new TextDecoder();\n const startedAt = performance.now();\n const reader = res.body.getReader();\n bus.emit(\"net.sse.open\", { id, url, transport: \"fetch\" });\n const scan = (chunk: Uint8Array) => {\n carry += decoder.decode(chunk, { stream: true });\n let idx: number;\n while ((idx = carry.search(/\\r?\\n\\r?\\n/)) >= 0) {\n const frame = carry.slice(0, idx);\n carry = carry.slice(idx).replace(/^\\r?\\n\\r?\\n/, \"\");\n if (!frame.trim() || frame.startsWith(\":\")) continue;\n count++;\n const evt = /^event:\\s?(.*)$/m.exec(frame)?.[1] ?? \"message\";\n if (count <= MAX_INDIVIDUAL_STREAM_MESSAGES || count % 50 === 0) {\n bus.emit(\"net.sse.message\", { id, url, event: evt, n: count, bytes: frame.length, transport: \"fetch\", preview: preview(frame, controller.isOn(\"captureBodies\")) });\n }\n }\n };\n const done = () => bus.emit(\"net.sse.close\", { id, url, messages: count, ms: Math.round(performance.now() - startedAt), transport: \"fetch\" });\n // A manual pump (not pipeThrough) so the same bytes flow to the app\n // unchanged and no TransformStream implementation mismatch can bite.\n const body = new ReadableStream<Uint8Array>({\n async pull(ctl) {\n const { value, done: finished } = await reader.read();\n if (finished) {\n done();\n ctl.close();\n return;\n }\n scan(value);\n ctl.enqueue(value);\n },\n cancel(reason) {\n done();\n return reader.cancel(reason);\n },\n });\n return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });\n }\n\n const pulseFetch = function pulseFetch(this: unknown, input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n if (!controller.isOn(\"network\")) return origFetch.call(this, input, init);\n const req = typeof Request !== \"undefined\" && input instanceof Request ? input : null;\n const rawUrl = req ? req.url : input instanceof URL ? input.href : String(input);\n if (ignored(rawUrl)) return origFetch.call(this, input, init);\n const id = nextId++;\n const url = redactUrl(rawUrl);\n const method = (init?.method ?? req?.method ?? \"GET\").toUpperCase();\n const start = performance.now();\n const component = solid?.currentComponent() ?? null;\n let aborted = false;\n const signal = init?.signal ?? req?.signal;\n if (signal) {\n if (signal.aborted) aborted = true;\n else signal.addEventListener(\"abort\", () => (aborted = true), { once: true });\n }\n bus.emit(\"net.fetch.start\", { id, method, url, bodyBytes: bodySize(init?.body), preview: preview(init?.body, controller.isOn(\"captureBodies\")) }, { component });\n return origFetch.call(this, input, init).then(\n (res: Response) => {\n const ms = Math.round((performance.now() - start) * 100) / 100;\n const ct = res.headers.get(\"content-type\") ?? \"\";\n const sse = ct.includes(\"text/event-stream\");\n bus.emit(\n \"net.fetch.end\",\n { id, method, url, status: res.status, ok: res.ok, ms, contentType: ct, contentLength: res.headers.get(\"content-length\"), sse, streaming: sse || (!res.headers.has(\"content-length\") && res.body !== null) },\n { component },\n );\n return sse ? wrapSse(res, id, url) : res;\n },\n (err: unknown) => {\n const ms = Math.round((performance.now() - start) * 100) / 100;\n const e = err as { name?: string; message?: string } | null;\n bus.emit(\"net.fetch.error\", { id, method, url, ms, name: e?.name ?? \"Error\", message: redactText(String(e?.message ?? err)), aborted: aborted || e?.name === \"AbortError\" }, { component });\n throw err;\n },\n );\n } as typeof fetch & { __solidPulse?: true };\n // Marker so cooperating shims (e.g. @omniaura/scenario-sim/browser) can tell\n // they are wrapping pulse — and must report to pulse's bus themselves.\n pulseFetch.__solidPulse = true;\n g.fetch = pulseFetch;\n\n // ── WebSocket ────────────────────────────────────────────────────\n\n class PulseWebSocket extends NativeWebSocket {\n constructor(url: string | URL, protocols?: string | string[]) {\n super(url, protocols);\n if (ignored(typeof url === \"string\" ? url : url.href)) return;\n const id = nextId++;\n const safeUrl = redactUrl(typeof url === \"string\" ? url : url.href);\n const openedAt = performance.now();\n let inbound = 0;\n let outbound = 0;\n const component = solid?.currentComponent() ?? null;\n bus.emit(\"net.ws.open\", { id, url: safeUrl, protocols: protocols ? ([] as string[]).concat(protocols) : [], state: \"connecting\" }, { component });\n this.addEventListener(\"open\", () => {\n bus.emit(\"net.ws.open\", { id, url: safeUrl, protocol: this.protocol, state: \"open\", ms: Math.round(performance.now() - openedAt) }, { component });\n });\n this.addEventListener(\"message\", (ev) => {\n inbound++;\n if (!controller.isOn(\"network\")) return;\n if (inbound <= MAX_INDIVIDUAL_STREAM_MESSAGES || inbound % 50 === 0) {\n const data = (ev as MessageEvent).data;\n bus.emit(\"net.ws.message\", { id, url: safeUrl, dir: \"in\", n: inbound, bytes: bodySize(data), type: messageType(data), preview: preview(data, controller.isOn(\"captureBodies\")) });\n }\n });\n this.addEventListener(\"close\", (ev) => {\n const e = ev as CloseEvent;\n bus.emit(\"net.ws.close\", { id, url: safeUrl, code: e.code, reason: redactText(e.reason), wasClean: e.wasClean, inbound, outbound, ms: Math.round(performance.now() - openedAt) });\n });\n this.addEventListener(\"error\", () => {\n bus.emit(\"net.ws.error\", { id, url: safeUrl, readyState: this.readyState });\n });\n const origSend = this.send.bind(this);\n this.send = (data: string | ArrayBufferLike | Blob | ArrayBufferView) => {\n outbound++;\n if (controller.isOn(\"network\") && (outbound <= MAX_INDIVIDUAL_STREAM_MESSAGES || outbound % 50 === 0)) {\n bus.emit(\"net.ws.message\", { id, url: safeUrl, dir: \"out\", n: outbound, bytes: bodySize(data), type: messageType(data), preview: preview(data, controller.isOn(\"captureBodies\")) });\n }\n return origSend(data as never);\n };\n }\n }\n (PulseWebSocket as unknown as { __solidPulse?: true }).__solidPulse = true;\n g.WebSocket = PulseWebSocket as unknown as typeof WebSocket;\n\n // ── EventSource ──────────────────────────────────────────────────\n\n if (NativeEventSource) {\n class PulseEventSource extends NativeEventSource {\n constructor(url: string | URL, init?: EventSourceInit) {\n super(url, init);\n if (ignored(typeof url === \"string\" ? url : url.href)) return;\n const id = nextId++;\n const safeUrl = redactUrl(typeof url === \"string\" ? url : url.href);\n const openedAt = performance.now();\n let count = 0;\n const seen = new Set<string>();\n const component = solid?.currentComponent() ?? null;\n const countType = (type: string) => {\n if (seen.has(type)) return;\n seen.add(type);\n super.addEventListener(type, (ev) => {\n count++;\n if (!controller.isOn(\"network\")) return;\n if (count <= MAX_INDIVIDUAL_STREAM_MESSAGES || count % 50 === 0) {\n const data = (ev as MessageEvent).data;\n bus.emit(\"net.sse.message\", { id, url: safeUrl, event: type, n: count, bytes: bodySize(data), transport: \"EventSource\", preview: preview(data, controller.isOn(\"captureBodies\")) });\n }\n });\n };\n countType(\"message\");\n this.addEventListener(\"open\", () => bus.emit(\"net.sse.open\", { id, url: safeUrl, transport: \"EventSource\", ms: Math.round(performance.now() - openedAt) }, { component }));\n this.addEventListener(\"error\", () => bus.emit(\"net.sse.error\", { id, url: safeUrl, readyState: this.readyState, transport: \"EventSource\" }));\n const origAdd = this.addEventListener.bind(this);\n this.addEventListener = ((type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions) => {\n if (type !== \"open\" && type !== \"error\") countType(type);\n return (origAdd as (t: string, l: EventListenerOrEventListenerObject | null, o?: boolean | AddEventListenerOptions) => void)(type, listener, options);\n }) as typeof this.addEventListener;\n const origClose = this.close.bind(this);\n this.close = () => {\n bus.emit(\"net.sse.close\", { id, url: safeUrl, messages: count, ms: Math.round(performance.now() - openedAt), transport: \"EventSource\" });\n origClose();\n };\n }\n }\n (PulseEventSource as unknown as { __solidPulse?: true }).__solidPulse = true;\n g.EventSource = PulseEventSource as unknown as typeof EventSource;\n }\n\n return {\n dispose() {\n g.fetch = origFetch;\n g.WebSocket = NativeWebSocket;\n if (NativeEventSource) g.EventSource = NativeEventSource;\n },\n };\n}\n","/**\n * Page-side bridge transport. Streams events to the bridge server in ≤50 ms\n * batches and executes commands the server relays from the CLI/agents. Any\n * command is just `controller.run(name, args)` — the same call the panel makes.\n */\n\nimport type { PulseController } from \"../core/controller.js\";\nimport { DEFAULT_PATH, PROTOCOL_VERSION, isServerFrame, type HelloFrame, type PageFrame } from \"../core/protocol.js\";\nimport type { PulseEvent } from \"../core/events.js\";\n\nexport interface BridgeClientOptions {\n /** ws(s):// URL. Default: same origin + /__pulse/ws. */\n url?: string;\n /** Reconnect delay in ms (default 2000). */\n reconnectMs?: number;\n /** Stable client id (default: random per page load, persisted in sessionStorage). */\n clientId?: string;\n}\n\nfunction defaultUrl(): string {\n const proto = location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return `${proto}//${location.host}${DEFAULT_PATH}/ws`;\n}\n\nfunction clientIdFor(explicit?: string): string {\n if (explicit) return explicit;\n try {\n const existing = sessionStorage.getItem(\"solid-pulse:clientId\");\n if (existing) return existing;\n const id = `tab-${Math.random().toString(36).slice(2, 8)}`;\n sessionStorage.setItem(\"solid-pulse:clientId\", id);\n return id;\n } catch {\n return `tab-${Math.random().toString(36).slice(2, 8)}`;\n }\n}\n\nexport class BridgeClient {\n private ws: WebSocket | null = null;\n private timer: ReturnType<typeof setTimeout> | null = null;\n private queue: PulseEvent[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: (() => void) | null = null;\n private closed = false;\n private NativeWebSocket: typeof WebSocket;\n readonly url: string;\n readonly clientId: string;\n connected = false;\n\n constructor(private controller: PulseController, private options: BridgeClientOptions = {}) {\n this.url = options.url ?? defaultUrl();\n this.clientId = clientIdFor(options.clientId);\n // Grab the native constructor now: network instrumentation may wrap the\n // global later and we must not trace our own transport.\n this.NativeWebSocket = WebSocket;\n }\n\n connect() {\n if (this.ws || this.closed) return;\n try {\n const ws = new this.NativeWebSocket(this.url);\n this.ws = ws;\n ws.onopen = () => {\n this.connected = true;\n const hello: HelloFrame = {\n type: \"hello\",\n protocol: PROTOCOL_VERSION,\n clientId: this.clientId,\n url: location.href,\n title: document.title,\n userAgent: navigator.userAgent,\n commands: this.controller.describe(),\n startedWall: this.controller.startedWall,\n };\n ws.send(JSON.stringify(hello));\n // Replay the buffer so a CLI that connects late still sees history.\n this.queue = this.controller.bus.list({ limit: 2000 });\n this.scheduleFlush();\n this.unsubscribe?.();\n this.unsubscribe = this.controller.bus.subscribe((e) => {\n this.queue.push(e);\n this.scheduleFlush();\n });\n this.controller.bus.emit(\"pulse.note\", { note: `bridge connected ${this.url}` });\n };\n ws.onmessage = (ev) => void this.onMessage(ev.data);\n ws.onclose = () => {\n this.connected = false;\n this.ws = null;\n this.unsubscribe?.();\n this.unsubscribe = null;\n if (!this.closed) this.timer = setTimeout(() => this.connect(), this.options.reconnectMs ?? 2000);\n };\n ws.onerror = () => ws.close();\n } catch {\n this.timer = setTimeout(() => this.connect(), this.options.reconnectMs ?? 2000);\n }\n }\n\n disconnect() {\n this.closed = true;\n if (this.timer) clearTimeout(this.timer);\n if (this.flushTimer) clearTimeout(this.flushTimer);\n this.unsubscribe?.();\n this.ws?.close();\n this.ws = null;\n this.connected = false;\n }\n\n private scheduleFlush() {\n if (this.flushTimer) return;\n this.flushTimer = setTimeout(() => {\n this.flushTimer = null;\n this.flush();\n }, 50);\n }\n\n private flush() {\n if (!this.ws || this.ws.readyState !== this.NativeWebSocket.OPEN || this.queue.length === 0) return;\n // Send in chunks so a replay of 2000 events never builds one huge frame.\n while (this.queue.length) {\n const chunk = this.queue.splice(0, 200);\n this.send({ type: \"events\", events: chunk });\n }\n }\n\n private send(frame: PageFrame) {\n if (!this.ws || this.ws.readyState !== this.NativeWebSocket.OPEN) return;\n this.ws.send(JSON.stringify(frame));\n }\n\n private async onMessage(raw: unknown) {\n let frame: unknown;\n try {\n frame = JSON.parse(String(raw));\n } catch {\n return;\n }\n if (!isServerFrame(frame)) return;\n if (frame.type === \"command\") {\n const result = await this.controller.run(frame.name, frame.args ?? {});\n this.send({ type: \"result\", id: frame.id, result });\n }\n }\n}\n","/**\n * @omniaura/solid-pulse — runtime entry.\n *\n * import { initPulse } from \"@omniaura/solid-pulse\";\n * if (import.meta.env.DEV) initPulse({ bridge: true });\n *\n * or let `@omniaura/solid-pulse/vite` auto-import it in dev. Never ship it in\n * production: the Vite plugin is `apply: \"serve\"` and `initPulse` refuses to\n * run twice. Instrumentation is dev-only and everything is bounded (ring\n * buffer, per-frame flash caps, per-stream message caps).\n */\n\nimport { PulseController, type Feature } from \"./core/controller.js\";\nimport { EventBus } from \"./core/bus.js\";\nimport { FlashOverlay } from \"./overlay/flash.js\";\nimport { installSolid, type SolidInstrumentation } from \"./solid/instrument.js\";\nimport { installDom, type DomInstrumentation } from \"./solid/dom.js\";\nimport { installNetwork, type NetworkInstrumentation } from \"./solid/network.js\";\nimport { BridgeClient, type BridgeClientOptions } from \"./bridge/client.js\";\n\nexport type { PulseEvent, PulseEventKind, ComponentRef, ElementRef, Rect } from \"./core/events.js\";\nexport type { CommandSpec, CommandResult, Feature, Filters } from \"./core/controller.js\";\nexport { PulseController } from \"./core/controller.js\";\nexport { EventBus } from \"./core/bus.js\";\nexport { FlashOverlay } from \"./overlay/flash.js\";\nexport type { SolidInstrumentation } from \"./solid/instrument.js\";\nexport { describeElement, toSelector } from \"./solid/dom.js\";\n\nexport interface PulseOptions {\n /** Ring-buffer capacity (default 2000 events). */\n bufferSize?: number;\n /** Initial feature flags. */\n features?: Partial<Record<Feature, boolean>>;\n /** Mount the flash/badge overlay (default true). */\n overlay?: boolean;\n /**\n * Connect to a bridge. `true` uses the same origin at `/__pulse/ws` (the\n * Vite plugin mounts one there); a string is an explicit ws:// URL.\n */\n bridge?: boolean | string | BridgeClientOptions;\n /** Print a console banner (default true). */\n banner?: boolean;\n}\n\nexport interface Pulse {\n controller: PulseController;\n bus: EventBus;\n overlay: FlashOverlay | null;\n solid: SolidInstrumentation | null;\n bridge: BridgeClient | null;\n /** Run a command exactly as the CLI would. */\n run: PulseController[\"run\"];\n destroy(): void;\n}\n\nlet instance: Pulse | null = null;\n\nexport function getPulse(): Pulse | null {\n return instance;\n}\n\nexport function initPulse(options: PulseOptions = {}): Pulse {\n if (instance) return instance;\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n throw new Error(\"solid-pulse runs in the browser only\");\n }\n const bus = new EventBus(options.bufferSize ?? 2000);\n const controller = new PulseController(bus, options.features);\n const overlay = options.overlay === false ? null : new FlashOverlay();\n let solid: SolidInstrumentation | null = null;\n let dom: DomInstrumentation | null = null;\n let net: NetworkInstrumentation | null = null;\n let bridge: BridgeClient | null = null;\n\n const boot = () => {\n overlay?.mount();\n solid = installSolid(controller);\n dom = installDom(controller, solid, overlay);\n // The bridge grabs the native WebSocket before network instrumentation\n // wraps the global, so our own transport never appears in the events.\n if (options.bridge) {\n const opts: BridgeClientOptions =\n options.bridge === true ? {} : typeof options.bridge === \"string\" ? { url: options.bridge } : options.bridge;\n bridge = new BridgeClient(controller, opts);\n }\n net = installNetwork(controller, solid, { ignoreUrl: (url) => url.includes(\"/__pulse/\") });\n bridge?.connect();\n if (options.banner !== false) {\n console.log(\n \"%c◉ solid-pulse%c dev instrumentation on · window.__SOLID_PULSE__.run(cmd) · CLI: solid-pulse commands\",\n \"color:#f59e0b;font-weight:bold\",\n \"color:inherit\",\n );\n }\n };\n\n const pulse: Pulse = {\n controller,\n bus,\n overlay,\n get solid() {\n return solid;\n },\n get bridge() {\n return bridge;\n },\n run: (name, args) => controller.run(name, args),\n destroy() {\n bridge?.disconnect();\n net?.dispose();\n dom?.dispose();\n solid?.dispose();\n overlay?.unmount();\n instance = null;\n delete (window as unknown as { __SOLID_PULSE__?: unknown }).__SOLID_PULSE__;\n },\n };\n instance = pulse;\n (window as unknown as { __SOLID_PULSE__: Pulse }).__SOLID_PULSE__ = pulse;\n\n // Solid hooks must be installed before the app's first render to see the\n // initial mounts; the Vite plugin imports us first for that reason. DOM\n // observation needs a document element, which exists at script time.\n boot();\n return pulse;\n}\n\ndeclare global {\n interface Window {\n __SOLID_PULSE__?: Pulse;\n }\n}\n"],"mappings":"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","names":["pulseFetch","options"]}
|
package/dist/panel.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Pulse } from './index.js';
|
|
2
|
+
import './controller-3akN6Qi0.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The panel: a raw-DOM floating drawer (no Solid, so it never shows up in the
|
|
6
|
+
* events it displays). Every control carries `data-command="<name>"` and calls
|
|
7
|
+
* `controller.run(name, args)` — exactly what the CLI does. The parity test
|
|
8
|
+
* asserts that every command that declares a `ui` location has a control here
|
|
9
|
+
* and that every control maps to a registered command.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
interface PanelTab {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
/** Called once when the tab body is created; return a cleanup. */
|
|
16
|
+
mount: (el: HTMLElement, pulse: Pulse) => void | (() => void);
|
|
17
|
+
}
|
|
18
|
+
interface PanelOptions {
|
|
19
|
+
tabs?: PanelTab[];
|
|
20
|
+
/** Start open (default false). Opening never moves focus. */
|
|
21
|
+
open?: boolean;
|
|
22
|
+
position?: "bottom-left" | "bottom-right";
|
|
23
|
+
/** Max rows in the live list (default 150). */
|
|
24
|
+
rows?: number;
|
|
25
|
+
}
|
|
26
|
+
declare function mountPanel(pulse: Pulse, options?: PanelOptions): {
|
|
27
|
+
root: HTMLDivElement;
|
|
28
|
+
querySlot: HTMLDivElement;
|
|
29
|
+
open: () => void;
|
|
30
|
+
close: () => void;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export { type PanelOptions, type PanelTab, mountPanel };
|
package/dist/panel.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OWN_ATTR
|
|
3
|
+
} from "./chunk-IXNWEUNF.js";
|
|
4
|
+
import {
|
|
5
|
+
FEATURES
|
|
6
|
+
} from "./chunk-C72EYM65.js";
|
|
7
|
+
|
|
8
|
+
// src/panel/index.ts
|
|
9
|
+
function h(tag, attrs = {}, ...children) {
|
|
10
|
+
const el = document.createElement(tag);
|
|
11
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
12
|
+
if (v === void 0 || v === false) continue;
|
|
13
|
+
if (typeof v === "function") el.addEventListener(k.replace(/^on/, "").toLowerCase(), v);
|
|
14
|
+
else if (k === "class") el.className = String(v);
|
|
15
|
+
else if (k === "text") el.textContent = String(v);
|
|
16
|
+
else el.setAttribute(k, v === true ? "" : String(v));
|
|
17
|
+
}
|
|
18
|
+
for (const c of children) if (c != null) el.append(c);
|
|
19
|
+
return el;
|
|
20
|
+
}
|
|
21
|
+
var CSS = `
|
|
22
|
+
[${OWN_ATTR}="panel-root"]{position:fixed;z-index:2147483647;font:12px/1.35 ui-monospace,SFMono-Regular,Menlo,monospace;color:#e5e7eb;pointer-events:none}
|
|
23
|
+
[${OWN_ATTR}="panel-root"] *{box-sizing:border-box}
|
|
24
|
+
.sp-fab{pointer-events:auto;position:fixed;bottom:12px;width:auto;padding:6px 10px;border-radius:999px;border:1px solid #f59e0b;background:rgba(17,24,39,.92);color:#fbbf24;cursor:pointer;font:inherit;box-shadow:0 4px 14px rgba(0,0,0,.35)}
|
|
25
|
+
.sp-fab[data-rec="1"]{border-color:#ef4444;color:#fca5a5}
|
|
26
|
+
.sp-drawer{pointer-events:auto;position:fixed;bottom:0;left:0;right:0;height:42vh;min-height:220px;background:rgba(17,24,39,.97);border-top:1px solid #374151;display:flex;flex-direction:column;box-shadow:0 -8px 30px rgba(0,0,0,.4)}
|
|
27
|
+
.sp-head{display:flex;align-items:center;gap:8px;padding:6px 10px;border-bottom:1px solid #374151}
|
|
28
|
+
.sp-head b{color:#fbbf24}
|
|
29
|
+
.sp-tabs{display:flex;gap:2px}
|
|
30
|
+
.sp-tab{background:transparent;border:1px solid transparent;color:#9ca3af;padding:3px 8px;border-radius:6px;cursor:pointer;font:inherit}
|
|
31
|
+
.sp-tab[aria-selected="true"]{color:#fff;border-color:#4b5563;background:#1f2937}
|
|
32
|
+
.sp-body{flex:1;overflow:auto;padding:8px 10px}
|
|
33
|
+
.sp-body[hidden]{display:none}
|
|
34
|
+
.sp-row{display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center;margin-bottom:6px}
|
|
35
|
+
.sp-btn{background:#1f2937;border:1px solid #4b5563;color:#e5e7eb;padding:3px 8px;border-radius:6px;cursor:pointer;font:inherit}
|
|
36
|
+
.sp-btn:hover{border-color:#9ca3af}
|
|
37
|
+
.sp-in{background:#111827;border:1px solid #4b5563;color:#e5e7eb;padding:3px 6px;border-radius:6px;font:inherit;min-width:120px}
|
|
38
|
+
.sp-list{font-size:11px;white-space:pre-wrap;word-break:break-word}
|
|
39
|
+
.sp-ev{padding:2px 4px;border-left:3px solid transparent;cursor:pointer}
|
|
40
|
+
.sp-ev:hover{background:#1f2937}
|
|
41
|
+
.sp-ev[data-g="solid"]{border-color:#22c55e}.sp-ev[data-g="dom"]{border-color:#f59e0b}.sp-ev[data-g="net"]{border-color:#a78bfa}.sp-ev[data-g="query"]{border-color:#3b82f6}.sp-ev[data-g="pulse"]{border-color:#6b7280}
|
|
42
|
+
.sp-ev[data-warn="1"]{background:rgba(239,68,68,.15)}
|
|
43
|
+
.sp-ev pre{margin:4px 0 6px 10px;color:#9ca3af;max-height:220px;overflow:auto}
|
|
44
|
+
.sp-dim{color:#9ca3af}
|
|
45
|
+
.sp-status{margin-left:auto;display:flex;gap:8px;color:#9ca3af}
|
|
46
|
+
.sp-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:#6b7280;margin-right:4px}
|
|
47
|
+
.sp-dot[data-on="1"]{background:#22c55e}
|
|
48
|
+
`;
|
|
49
|
+
function group(kind) {
|
|
50
|
+
return kind.split(".")[0] ?? "pulse";
|
|
51
|
+
}
|
|
52
|
+
function isWarn(e) {
|
|
53
|
+
if (e.kind === "dom.reattach") {
|
|
54
|
+
const d = e.data;
|
|
55
|
+
return Boolean(d.focusLost || d.scrollReset?.some((s) => s.reset));
|
|
56
|
+
}
|
|
57
|
+
return e.kind === "focus.lost" || e.kind.endsWith(".error") || e.kind === "solid.component.remount";
|
|
58
|
+
}
|
|
59
|
+
function oneLine(e) {
|
|
60
|
+
const d = e.data;
|
|
61
|
+
const comp = e.component?.name ? `<${e.component.name}> ` : "";
|
|
62
|
+
switch (e.kind) {
|
|
63
|
+
case "solid.flush":
|
|
64
|
+
return `${comp}${d.computations} computations ${JSON.stringify(d.byKind)}`;
|
|
65
|
+
case "solid.component.mount":
|
|
66
|
+
case "solid.component.remount":
|
|
67
|
+
case "solid.component.dispose":
|
|
68
|
+
return `${d.name}${d.hydrated ? " (hydrated)" : ""}${d.gapMs !== void 0 ? ` gap ${d.gapMs}ms` : ""}${d.lifetimeMs !== void 0 ? ` lived ${d.lifetimeMs}ms` : ""}`;
|
|
69
|
+
case "dom.mutation":
|
|
70
|
+
return `${comp}${d.targets} targets \xB7 ${d.summary.slice(0, 3).map((s) => `${s.tag}(${s.types.join("+")})`).join(" ")} \xB7 ${d.attributedTo}`;
|
|
71
|
+
case "dom.detach":
|
|
72
|
+
return `${comp}${d.element.tag} detached \xB7 ${d.scrollers.length} scrollers${d.hadFocus ? " \xB7 had focus" : ""}`;
|
|
73
|
+
case "dom.reattach": {
|
|
74
|
+
const resets = d.scrollReset.filter((s) => s.reset);
|
|
75
|
+
return `${comp}${d.element.tag} reattached after ${d.gapMs}ms${resets.length ? ` \xB7 SCROLL RESET ${resets.map((r) => `${r.before}\u2192${r.after}`).join(",")}` : ""}${d.focusLost ? " \xB7 FOCUS LOST" : ""}${d.suspenseInChain ? " \xB7 Suspense" : ""}`;
|
|
76
|
+
}
|
|
77
|
+
case "focus.lost":
|
|
78
|
+
return `${d.element.tag} \u2014 ${d.cause}`;
|
|
79
|
+
default:
|
|
80
|
+
if (e.kind.startsWith("net.")) return `${comp}${d.method ?? ""} ${d.url ?? ""} ${d.status ?? d.state ?? d.dir ?? d.event ?? ""}${d.ms !== void 0 ? ` ${d.ms}ms` : ""}${d.message ? ` \u2717 ${d.message}` : ""}`.trim();
|
|
81
|
+
if (e.kind.startsWith("query") || e.kind.startsWith("mutation")) return `${comp}${d.label ?? ""} ${d.role ?? d.trigger ?? d.action ?? ""}${d.ms != null ? ` ${d.ms}ms` : ""}${d.message ? ` \u2717 ${d.message}` : ""}`.trim();
|
|
82
|
+
return JSON.stringify(d).slice(0, 140);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function mountPanel(pulse, options = {}) {
|
|
86
|
+
const { controller } = pulse;
|
|
87
|
+
const rows = options.rows ?? 150;
|
|
88
|
+
const root = h("div", { [OWN_ATTR]: "panel-root" });
|
|
89
|
+
const style = h("style");
|
|
90
|
+
style.textContent = CSS;
|
|
91
|
+
root.append(style);
|
|
92
|
+
const fab = h("button", { class: "sp-fab", type: "button", title: "solid-pulse panel (also: solid-pulse panel.toggle)", "data-command": "panel.toggle", onclick: () => void controller.run("panel.toggle") }, "\u25C9 pulse");
|
|
93
|
+
fab.style[options.position === "bottom-right" ? "right" : "left"] = "12px";
|
|
94
|
+
root.append(fab);
|
|
95
|
+
let open = false;
|
|
96
|
+
let drawer = null;
|
|
97
|
+
let activeTab = "pulse";
|
|
98
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
99
|
+
const tabButtons = /* @__PURE__ */ new Map();
|
|
100
|
+
const cleanups = [];
|
|
101
|
+
const run = (name, args = {}) => controller.run(name, args);
|
|
102
|
+
const val = (r) => r.ok ? r.value : { error: r.error };
|
|
103
|
+
const pulseBody = h("div", { class: "sp-body" });
|
|
104
|
+
const toggles = h("div", { class: "sp-row", "data-command": "features.list" });
|
|
105
|
+
const featureBoxes = /* @__PURE__ */ new Map();
|
|
106
|
+
for (const f of FEATURES) {
|
|
107
|
+
const box = h("input", { type: "checkbox", "data-command": "features.set", "data-feature": f, onchange: (e) => void run("features.set", { name: f, on: e.target.checked }) });
|
|
108
|
+
box.checked = controller.isOn(f);
|
|
109
|
+
featureBoxes.set(f, box);
|
|
110
|
+
toggles.append(h("label", {}, box, ` ${f}`));
|
|
111
|
+
}
|
|
112
|
+
const kindsIn = h("input", { class: "sp-in", placeholder: "kinds: dom,query,net.ws.*", "data-command": "filters.set", "data-arg": "kinds", onchange: (e) => void run("filters.set", { kinds: e.target.value }) });
|
|
113
|
+
const compIn = h("input", { class: "sp-in", placeholder: "component contains\u2026", "data-command": "filters.set", "data-arg": "component", onchange: (e) => void run("filters.set", { component: e.target.value }) });
|
|
114
|
+
const textIn = h("input", { class: "sp-in", placeholder: "text contains\u2026", "data-command": "filters.set", "data-arg": "text", onchange: (e) => void run("filters.set", { text: e.target.value }) });
|
|
115
|
+
const pauseBtn = h("button", { class: "sp-btn", type: "button", "data-command": "events.pause", onclick: () => void run(controller.bus.paused ? "events.resume" : "events.pause").then(refreshStatus) }, "Pause");
|
|
116
|
+
const list = h("div", { class: "sp-list" });
|
|
117
|
+
pulseBody.append(
|
|
118
|
+
toggles,
|
|
119
|
+
h(
|
|
120
|
+
"div",
|
|
121
|
+
{ class: "sp-row" },
|
|
122
|
+
kindsIn,
|
|
123
|
+
compIn,
|
|
124
|
+
textIn,
|
|
125
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "filters.get", onclick: () => void run("filters.get").then((r) => showResult(pulseBody, val(r))) }, "Filters"),
|
|
126
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "events.clear", onclick: () => void run("events.clear").then(() => list.textContent = "") }, "Clear"),
|
|
127
|
+
pauseBtn,
|
|
128
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.components", onclick: () => void run("inspect.components").then((r) => showResult(pulseBody, val(r))) }, "Components"),
|
|
129
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.solid", onclick: () => void run("inspect.solid").then((r) => showResult(pulseBody, val(r))) }, "Solid"),
|
|
130
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.focus", onclick: () => void run("inspect.focus").then((r) => showResult(pulseBody, val(r))) }, "Focus"),
|
|
131
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.scrollers", onclick: () => void run("inspect.scrollers").then((r) => showResult(pulseBody, val(r))) }, "Scrollers"),
|
|
132
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "events.list", onclick: () => void run("events.list", { limit: rows }).then((r) => renderList(r.ok ? r.value : [])) }, "Reload"),
|
|
133
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "status", onclick: () => void run("status").then((r) => showResult(pulseBody, val(r))) }, "Status"),
|
|
134
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "commands", onclick: () => void run("commands").then((r) => showResult(pulseBody, val(r))) }, "Commands")
|
|
135
|
+
),
|
|
136
|
+
list
|
|
137
|
+
);
|
|
138
|
+
bodies.set("pulse", pulseBody);
|
|
139
|
+
const resultBoxes = /* @__PURE__ */ new WeakMap();
|
|
140
|
+
function showResult(body, value) {
|
|
141
|
+
let pre = resultBoxes.get(body);
|
|
142
|
+
if (!pre) {
|
|
143
|
+
pre = h("pre", { class: "sp-dim" });
|
|
144
|
+
pre.style.cssText = "max-height:40%;overflow:auto;margin:0 0 6px;border:1px solid #374151;padding:6px;border-radius:6px";
|
|
145
|
+
body.insertBefore(pre, body.children[body === pulseBody ? 2 : 1] ?? null);
|
|
146
|
+
resultBoxes.set(body, pre);
|
|
147
|
+
}
|
|
148
|
+
pre.textContent = JSON.stringify(value, null, 2);
|
|
149
|
+
}
|
|
150
|
+
function rowFor(e) {
|
|
151
|
+
const row = h("div", { class: "sp-ev", "data-g": group(e.kind), "data-warn": isWarn(e) ? "1" : void 0, "data-seq": String(e.seq) });
|
|
152
|
+
row.append(h("span", { class: "sp-dim" }, `${(e.t / 1e3).toFixed(3)}s `), h("span", {}, `${e.kind} `), h("span", { class: "sp-dim" }, oneLine(e)));
|
|
153
|
+
row.addEventListener("click", () => {
|
|
154
|
+
const existing = row.querySelector("pre");
|
|
155
|
+
if (existing) existing.remove();
|
|
156
|
+
else row.append(h("pre", {}, JSON.stringify(e, null, 2)));
|
|
157
|
+
});
|
|
158
|
+
return row;
|
|
159
|
+
}
|
|
160
|
+
function renderList(events) {
|
|
161
|
+
list.textContent = "";
|
|
162
|
+
for (const e of events.slice(-rows)) list.append(rowFor(e));
|
|
163
|
+
list.lastElementChild?.scrollIntoView({ block: "nearest" });
|
|
164
|
+
}
|
|
165
|
+
let autoScroll = true;
|
|
166
|
+
list.addEventListener("scroll", () => {
|
|
167
|
+
autoScroll = list.scrollTop + list.clientHeight >= list.scrollHeight - 24;
|
|
168
|
+
});
|
|
169
|
+
cleanups.push(
|
|
170
|
+
controller.bus.subscribe((e) => {
|
|
171
|
+
if (!open || activeTab !== "pulse") return;
|
|
172
|
+
if (!controller.matchesFilters(e)) return;
|
|
173
|
+
list.append(rowFor(e));
|
|
174
|
+
while (list.children.length > rows) list.firstElementChild?.remove();
|
|
175
|
+
if (autoScroll) list.scrollTop = list.scrollHeight;
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
cleanups.push(controller.onFeature((f, on) => {
|
|
179
|
+
const box = featureBoxes.get(f);
|
|
180
|
+
if (box) box.checked = on;
|
|
181
|
+
}));
|
|
182
|
+
cleanups.push(controller.onFilters((f) => {
|
|
183
|
+
kindsIn.value = f.kinds.join(",");
|
|
184
|
+
compIn.value = f.component;
|
|
185
|
+
textIn.value = f.text;
|
|
186
|
+
}));
|
|
187
|
+
const queryBody = h("div", { class: "sp-body" });
|
|
188
|
+
const querySlot = h("div", { "data-slot": "query-devtools" });
|
|
189
|
+
const queryHint = h("span", { class: "sp-dim" });
|
|
190
|
+
const refreshQueryHint = () => {
|
|
191
|
+
queryHint.textContent = controller.has("inspect.queries") ? "solid-query adapter attached" : "attach with attachQueryClient(pulse, queryClient) to enable";
|
|
192
|
+
};
|
|
193
|
+
refreshQueryHint();
|
|
194
|
+
queryBody.append(
|
|
195
|
+
h(
|
|
196
|
+
"div",
|
|
197
|
+
{ class: "sp-row" },
|
|
198
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.queries", onclick: () => void run("inspect.queries", { active: true }).then((r) => showResult(queryBody, r.ok ? r.value : r.error)) }, "Active queries"),
|
|
199
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "query.invalidate", onclick: () => void run("query.invalidate").then((r) => showResult(queryBody, r.ok ? r.value : r.error)) }, "Invalidate all"),
|
|
200
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "query.reset", onclick: () => void run("query.reset").then((r) => showResult(queryBody, r.ok ? r.value : r.error)) }, "Reset all"),
|
|
201
|
+
queryHint
|
|
202
|
+
),
|
|
203
|
+
querySlot
|
|
204
|
+
);
|
|
205
|
+
bodies.set("query", queryBody);
|
|
206
|
+
const grabBody = h("div", { class: "sp-body" });
|
|
207
|
+
const selIn = h("input", { class: "sp-in", placeholder: "CSS selector", "data-arg": "selector" });
|
|
208
|
+
const hasGrab = Boolean(window.__SOLID_GRAB__);
|
|
209
|
+
grabBody.append(
|
|
210
|
+
h(
|
|
211
|
+
"div",
|
|
212
|
+
{ class: "sp-row" },
|
|
213
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.element", onclick: () => pickElement() }, "Pick element (click)"),
|
|
214
|
+
selIn,
|
|
215
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "inspect.element", "data-arg": "selector", onclick: () => void run("inspect.element", { selector: selIn.value }).then((r) => showResult(grabBody, r.ok ? r.value : r.error)) }, "Inspect selector"),
|
|
216
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "dom.highlight", onclick: () => void run("dom.highlight", { selector: selIn.value, all: true }).then((r) => showResult(grabBody, r.ok ? r.value : r.error)) }, "Highlight"),
|
|
217
|
+
h("span", { class: "sp-dim" }, hasGrab ? "solid-grab detected: Alt+click anywhere copies source context" : "solid-grab not detected (source attributes need its Vite plugin)")
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
bodies.set("grab", grabBody);
|
|
221
|
+
function pickElement() {
|
|
222
|
+
const onClick = (ev) => {
|
|
223
|
+
ev.preventDefault();
|
|
224
|
+
ev.stopPropagation();
|
|
225
|
+
document.removeEventListener("click", onClick, true);
|
|
226
|
+
void run("inspect.element", { x: ev.clientX, y: ev.clientY }).then((r) => {
|
|
227
|
+
showResult(grabBody, r.ok ? r.value : r.error);
|
|
228
|
+
if (r.ok) void run("dom.highlight", { selector: r.value.selector });
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
document.addEventListener("click", onClick, true);
|
|
232
|
+
}
|
|
233
|
+
const scenBody = h("div", { class: "sp-body" });
|
|
234
|
+
const scenSelect = h("select", { class: "sp-in", "data-command": "scenario.select", onchange: (e) => void run("scenario.select", { name: e.target.value }).then(refreshScenario) });
|
|
235
|
+
const seedIn = h("input", { class: "sp-in", placeholder: "seed (optional)", "data-arg": "seed" });
|
|
236
|
+
const stepIn = h("input", { class: "sp-in", placeholder: "step ms (default 1000)", "data-arg": "ms" });
|
|
237
|
+
const scenStatus = h("pre", { class: "sp-dim" });
|
|
238
|
+
scenBody.append(
|
|
239
|
+
h(
|
|
240
|
+
"div",
|
|
241
|
+
{ class: "sp-row" },
|
|
242
|
+
h("span", {}, "scenario"),
|
|
243
|
+
scenSelect,
|
|
244
|
+
seedIn,
|
|
245
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.reset", onclick: () => void run("scenario.reset", { seed: seedIn.value || void 0 }).then(refreshScenario) }, "Reset"),
|
|
246
|
+
stepIn,
|
|
247
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.step", onclick: () => void run("scenario.step", { ms: stepIn.value ? Number(stepIn.value) : 1e3 }).then(refreshScenario) }, "Step clock"),
|
|
248
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.status", onclick: () => void refreshScenario() }, "Status"),
|
|
249
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.state", onclick: () => void run("scenario.state").then((r) => showResult(scenBody, r.ok ? r.value : r.error)) }, "State"),
|
|
250
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.events", onclick: () => void run("scenario.events", { limit: 100 }).then((r) => showResult(scenBody, r.ok ? r.value : r.error)) }, "Sim events"),
|
|
251
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.streams", onclick: () => void run("scenario.streams").then((r) => showResult(scenBody, r.ok ? r.value : r.error)) }, "Streams"),
|
|
252
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "scenario.list", onclick: () => void refreshScenario() }, "Reload list")
|
|
253
|
+
),
|
|
254
|
+
scenStatus
|
|
255
|
+
);
|
|
256
|
+
bodies.set("scenarios", scenBody);
|
|
257
|
+
async function refreshScenario() {
|
|
258
|
+
if (!controller.has("scenario.list")) {
|
|
259
|
+
scenStatus.textContent = "no scenario simulator attached (attach @omniaura/scenario-sim's pulse adapter)";
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const listRes = await run("scenario.list");
|
|
263
|
+
const status = await run("scenario.status");
|
|
264
|
+
if (listRes.ok) {
|
|
265
|
+
const scenarios = listRes.value;
|
|
266
|
+
const current = status.ok ? status.value.scenario : void 0;
|
|
267
|
+
scenSelect.textContent = "";
|
|
268
|
+
for (const s of scenarios) {
|
|
269
|
+
const opt = h("option", { value: s.name }, s.label ? `${s.name} \u2014 ${s.label}` : s.name);
|
|
270
|
+
if (s.name === current) opt.selected = true;
|
|
271
|
+
scenSelect.append(opt);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
scenStatus.textContent = JSON.stringify(status.ok ? status.value : status.error, null, 2);
|
|
275
|
+
}
|
|
276
|
+
const recBody = h("div", { class: "sp-body" });
|
|
277
|
+
const noteIn = h("input", { class: "sp-in", placeholder: "note text", "data-arg": "text" });
|
|
278
|
+
const recList = h("pre", { class: "sp-dim" });
|
|
279
|
+
recBody.append(
|
|
280
|
+
h(
|
|
281
|
+
"div",
|
|
282
|
+
{ class: "sp-row" },
|
|
283
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "record.start", onclick: () => void run("record.start").then(() => refreshRecordings()) }, "Start recording"),
|
|
284
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "record.stop", onclick: () => void run("record.stop").then(() => refreshRecordings()) }, "Stop"),
|
|
285
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "record.list", onclick: () => void refreshRecordings() }, "List"),
|
|
286
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "export", onclick: () => void exportJson() }, "Export JSON"),
|
|
287
|
+
noteIn,
|
|
288
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "note", onclick: () => void run("note", { text: noteIn.value }).then(() => noteIn.value = "") }, "Add note"),
|
|
289
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "events.resume", onclick: () => void run("events.resume").then(refreshStatus) }, "Resume")
|
|
290
|
+
),
|
|
291
|
+
recList
|
|
292
|
+
);
|
|
293
|
+
bodies.set("record", recBody);
|
|
294
|
+
async function refreshRecordings() {
|
|
295
|
+
const r = await run("record.list");
|
|
296
|
+
recList.textContent = JSON.stringify(val(r), null, 2);
|
|
297
|
+
refreshStatus();
|
|
298
|
+
}
|
|
299
|
+
async function exportJson() {
|
|
300
|
+
const active = controller.bus.currentRecording();
|
|
301
|
+
const last = controller.bus.listRecordings().filter((r2) => !r2.active).at(-1);
|
|
302
|
+
const r = await run("export", active || !last ? { filtered: false } : { recording: last.id });
|
|
303
|
+
if (!r.ok) return showResult(recBody, r.error);
|
|
304
|
+
const blob = new Blob([JSON.stringify(r.value)], { type: "application/json" });
|
|
305
|
+
const a = h("a", { href: URL.createObjectURL(blob), download: `solid-pulse-${Date.now()}.json` });
|
|
306
|
+
a.click();
|
|
307
|
+
setTimeout(() => URL.revokeObjectURL(a.href), 1e3);
|
|
308
|
+
}
|
|
309
|
+
for (const tab of options.tabs ?? []) {
|
|
310
|
+
const body = h("div", { class: "sp-body" });
|
|
311
|
+
const cleanup = tab.mount(body, pulse);
|
|
312
|
+
if (cleanup) cleanups.push(cleanup);
|
|
313
|
+
bodies.set(tab.id, body);
|
|
314
|
+
}
|
|
315
|
+
const bridgeDot = h("span", { class: "sp-dot" });
|
|
316
|
+
const recDot = h("span", { class: "sp-dot" });
|
|
317
|
+
const statusText = h("span", { "data-command": "panel.status" });
|
|
318
|
+
function refreshStatus() {
|
|
319
|
+
bridgeDot.dataset.on = pulse.bridge?.connected ? "1" : "0";
|
|
320
|
+
recDot.dataset.on = controller.bus.currentRecording() ? "1" : "0";
|
|
321
|
+
fab.dataset.rec = controller.bus.currentRecording() ? "1" : "0";
|
|
322
|
+
pauseBtn.textContent = controller.bus.paused ? "Resume" : "Pause";
|
|
323
|
+
statusText.textContent = `${controller.bus.buffer.size}/${controller.bus.buffer.capacity} buffered \xB7 ${controller.bus.buffer.dropped} dropped${pulse.bridge ? ` \xB7 ${pulse.bridge.clientId}` : ""}`;
|
|
324
|
+
}
|
|
325
|
+
const statusTimer = setInterval(() => open && refreshStatus(), 1e3);
|
|
326
|
+
cleanups.push(() => clearInterval(statusTimer));
|
|
327
|
+
function buildDrawer() {
|
|
328
|
+
const tabs = h("div", { class: "sp-tabs", role: "tablist" });
|
|
329
|
+
const titles = { pulse: "Pulse", query: "Query", grab: "Grab", scenarios: "Scenarios", record: "Record" };
|
|
330
|
+
for (const t of options.tabs ?? []) titles[t.id] = t.title;
|
|
331
|
+
for (const [id, body] of bodies) {
|
|
332
|
+
const btn = h("button", { class: "sp-tab", type: "button", role: "tab", "data-command": "panel.tab", "data-tab": id, onclick: () => void run("panel.tab", { name: id }) }, titles[id] ?? id);
|
|
333
|
+
tabButtons.set(id, btn);
|
|
334
|
+
tabs.append(btn);
|
|
335
|
+
body.hidden = id !== activeTab;
|
|
336
|
+
}
|
|
337
|
+
const head = h(
|
|
338
|
+
"div",
|
|
339
|
+
{ class: "sp-head" },
|
|
340
|
+
h("b", {}, "\u25C9 solid-pulse"),
|
|
341
|
+
tabs,
|
|
342
|
+
h("span", { class: "sp-status" }, h("span", {}, bridgeDot, "bridge"), h("span", {}, recDot, "rec"), statusText),
|
|
343
|
+
h("button", { class: "sp-btn", type: "button", "data-command": "panel.close", onclick: () => void run("panel.close") }, "\u2715")
|
|
344
|
+
);
|
|
345
|
+
const d = h("div", { class: "sp-drawer", role: "region", "aria-label": "solid-pulse" }, head, ...bodies.values());
|
|
346
|
+
return d;
|
|
347
|
+
}
|
|
348
|
+
function setTab(id) {
|
|
349
|
+
if (!bodies.has(id)) throw new Error(`unknown tab: ${id} (${[...bodies.keys()].join(", ")})`);
|
|
350
|
+
activeTab = id;
|
|
351
|
+
for (const [tid, body] of bodies) body.hidden = tid !== id;
|
|
352
|
+
for (const [tid, btn] of tabButtons) btn.setAttribute("aria-selected", tid === id ? "true" : "false");
|
|
353
|
+
if (id === "pulse") void run("events.list", { limit: rows }).then((r) => renderList(r.ok ? r.value : []));
|
|
354
|
+
if (id === "query") refreshQueryHint();
|
|
355
|
+
if (id === "scenarios") void refreshScenario();
|
|
356
|
+
if (id === "record") void refreshRecordings();
|
|
357
|
+
}
|
|
358
|
+
function setOpen(next) {
|
|
359
|
+
if (next === open) return;
|
|
360
|
+
open = next;
|
|
361
|
+
if (open) {
|
|
362
|
+
drawer ??= buildDrawer();
|
|
363
|
+
root.append(drawer);
|
|
364
|
+
fab.hidden = true;
|
|
365
|
+
setTab(activeTab);
|
|
366
|
+
refreshStatus();
|
|
367
|
+
} else {
|
|
368
|
+
drawer?.remove();
|
|
369
|
+
fab.hidden = false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
controller.register({ name: "panel.open", summary: "Open the panel (never moves focus). Alias of panel.toggle/panel.tab for agents.", args: { tab: "pulse|query|grab|scenarios|record|<custom>" } }, (a) => {
|
|
373
|
+
setOpen(true);
|
|
374
|
+
if (a.tab !== void 0) setTab(String(a.tab));
|
|
375
|
+
return { open: true, tab: activeTab };
|
|
376
|
+
});
|
|
377
|
+
controller.register({ name: "panel.close", summary: "Close the panel.", ui: "\u2715" }, () => {
|
|
378
|
+
setOpen(false);
|
|
379
|
+
return { open: false };
|
|
380
|
+
});
|
|
381
|
+
controller.register({ name: "panel.toggle", summary: "Toggle the panel.", ui: "\u25C9 pulse button" }, () => {
|
|
382
|
+
setOpen(!open);
|
|
383
|
+
return { open, tab: activeTab };
|
|
384
|
+
});
|
|
385
|
+
controller.register({ name: "panel.tab", summary: "Switch the panel tab.", args: { name: "tab id" }, ui: "tab strip" }, (a) => {
|
|
386
|
+
setOpen(true);
|
|
387
|
+
setTab(String(a.name ?? "pulse"));
|
|
388
|
+
return { open, tab: activeTab };
|
|
389
|
+
});
|
|
390
|
+
controller.register({ name: "panel.status", summary: "Is the panel open, which tab, which tabs exist.", ui: "panel header" }, () => ({ open, tab: activeTab, tabs: [...bodies.keys()] }));
|
|
391
|
+
(document.body ?? document.documentElement).append(root);
|
|
392
|
+
if (options.open) setOpen(true);
|
|
393
|
+
return {
|
|
394
|
+
root,
|
|
395
|
+
querySlot,
|
|
396
|
+
open: () => setOpen(true),
|
|
397
|
+
close: () => setOpen(false),
|
|
398
|
+
destroy() {
|
|
399
|
+
for (const c of cleanups) c();
|
|
400
|
+
for (const n of ["panel.open", "panel.close", "panel.toggle", "panel.tab", "panel.status"]) controller.unregister(n);
|
|
401
|
+
root.remove();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
export {
|
|
406
|
+
mountPanel
|
|
407
|
+
};
|
|
408
|
+
//# sourceMappingURL=panel.js.map
|