@korajs/devtools 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/panel.ts","../src/types.ts","../src/filter/event-filter.ts","../src/stats/event-stats.ts","../src/ui/components.ts","../src/ui/panel-state.ts","../src/ui/panel-styles.ts","../src/overlay/embed-overlay.ts"],"sourcesContent":["import htm from 'htm'\nimport { h, render } from 'preact'\nimport { useCallback, useMemo, useState } from 'preact/hooks'\n\nimport { filterEvents } from '../filter/event-filter'\nimport { computeStatistics } from '../stats/event-stats'\nimport type { EventCategory, TimestampedEvent } from '../types'\nimport { eventTypeToCategory } from '../types'\nimport { formatTime, formatValue, truncate } from './components'\nimport { buildPanelModel } from './panel-state'\nimport type {\n\tConflictItem,\n\tDevtoolsPanelModel,\n\tNetworkStatusModel,\n\tOperationItem,\n\tTimelineItem,\n} from './panel-state'\n\nconst html = htm.bind(h)\n\n// ============================================================================\n// Types\n// ============================================================================\n\ntype ActiveTab = 'timeline' | 'conflicts' | 'operations' | 'network'\n\nconst ALL_CATEGORIES: EventCategory[] = ['operation', 'merge', 'sync', 'query', 'connection']\n\n// ============================================================================\n// Main Panel Component\n// ============================================================================\n\nfunction DevToolsPanel({ events }: { events: readonly TimestampedEvent[] }) {\n\tconst [activeTab, setActiveTab] = useState<ActiveTab>('timeline')\n\tconst [search, setSearch] = useState('')\n\tconst [categories, setCategories] = useState<Set<EventCategory>>(() => new Set(ALL_CATEGORIES))\n\tconst [expanded, setExpanded] = useState<Set<number>>(() => new Set())\n\tconst [paused, setPaused] = useState(false)\n\n\tconst toggleCategory = useCallback((cat: EventCategory) => {\n\t\tsetCategories((prev) => {\n\t\t\tconst next = new Set(prev)\n\t\t\tif (next.has(cat)) next.delete(cat)\n\t\t\telse next.add(cat)\n\t\t\treturn next\n\t\t})\n\t}, [])\n\n\tconst toggleExpand = useCallback((id: number) => {\n\t\tsetExpanded((prev) => {\n\t\t\tconst next = new Set(prev)\n\t\t\tif (next.has(id)) next.delete(id)\n\t\t\telse next.add(id)\n\t\t\treturn next\n\t\t})\n\t}, [])\n\n\tconst filtered = useMemo(\n\t\t() => filterEvents(events, { categories: [...categories] }),\n\t\t[events, categories],\n\t)\n\n\tconst model = useMemo(() => buildPanelModel(filtered), [filtered])\n\tconst stats = useMemo(() => computeStatistics(events), [events])\n\n\treturn html`\n\t\t<div class=\"kora-devtools\">\n\t\t\t<${Toolbar}\n\t\t\t\tactiveTab=${activeTab}\n\t\t\t\tonTabChange=${setActiveTab}\n\t\t\t\ttotalEvents=${events.length}\n\t\t\t\tconflicts=${stats.mergeConflicts}\n\t\t\t\tsearch=${search}\n\t\t\t\tonSearchChange=${setSearch}\n\t\t\t\tpaused=${paused}\n\t\t\t\tonPauseToggle=${() => setPaused((p: boolean) => !p)}\n\t\t\t\tonClear=${() => setExpanded(new Set())}\n\t\t\t\tcategories=${categories}\n\t\t\t\tonToggleCategory=${toggleCategory}\n\t\t\t/>\n\t\t\t<div class=\"kora-content\">\n\t\t\t\t${\n\t\t\t\t\tactiveTab === 'timeline' &&\n\t\t\t\t\thtml`<${TimelinePanel}\n\t\t\t\t\titems=${model.timeline}\n\t\t\t\t\tsearch=${search}\n\t\t\t\t\texpanded=${expanded}\n\t\t\t\t\tonToggle=${toggleExpand}\n\t\t\t\t/>`\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\tactiveTab === 'conflicts' &&\n\t\t\t\t\thtml`<${ConflictsPanel}\n\t\t\t\t\titems=${model.conflicts}\n\t\t\t\t\tsearch=${search}\n\t\t\t\t\texpanded=${expanded}\n\t\t\t\t\tonToggle=${toggleExpand}\n\t\t\t\t/>`\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\tactiveTab === 'operations' &&\n\t\t\t\t\thtml`<${OperationsPanel}\n\t\t\t\t\titems=${model.operations}\n\t\t\t\t\tsearch=${search}\n\t\t\t\t\texpanded=${expanded}\n\t\t\t\t\tonToggle=${toggleExpand}\n\t\t\t\t/>`\n\t\t\t\t}\n\t\t\t\t${\n\t\t\t\t\tactiveTab === 'network' &&\n\t\t\t\t\thtml`<${NetworkPanel} network=${model.network} events=${events} />`\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Toolbar\n// ============================================================================\n\nfunction Toolbar({\n\tactiveTab,\n\tonTabChange,\n\ttotalEvents,\n\tconflicts,\n\tsearch,\n\tonSearchChange,\n\tpaused,\n\tonPauseToggle,\n\tonClear,\n\tcategories,\n\tonToggleCategory,\n}: {\n\tactiveTab: ActiveTab\n\tonTabChange: (tab: ActiveTab) => void\n\ttotalEvents: number\n\tconflicts: number\n\tsearch: string\n\tonSearchChange: (s: string) => void\n\tpaused: boolean\n\tonPauseToggle: () => void\n\tonClear: () => void\n\tcategories: Set<EventCategory>\n\tonToggleCategory: (cat: EventCategory) => void\n}) {\n\tconst tabs: Array<{ id: ActiveTab; label: string; badge?: number }> = [\n\t\t{ id: 'timeline', label: 'Timeline', badge: totalEvents },\n\t\t{ id: 'conflicts', label: 'Conflicts', badge: conflicts },\n\t\t{ id: 'operations', label: 'Operations' },\n\t\t{ id: 'network', label: 'Network' },\n\t]\n\n\treturn html`\n\t\t<div class=\"kora-toolbar\">\n\t\t\t<div class=\"kora-tabs\">\n\t\t\t\t${tabs.map(\n\t\t\t\t\t(tab) => html`\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclass=\"kora-tab${activeTab === tab.id ? ' active' : ''}\"\n\t\t\t\t\t\t\tonClick=${() => onTabChange(tab.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t${tab.label}\n\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\ttab.badge != null && tab.badge > 0\n\t\t\t\t\t\t\t\t\t? html`<span class=\"kora-badge\">${tab.badge}</span>`\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t`,\n\t\t\t\t)}\n\t\t\t</div>\n\t\t\t<div class=\"kora-controls\">\n\t\t\t\t<input\n\t\t\t\t\ttype=\"text\"\n\t\t\t\t\tclass=\"kora-search\"\n\t\t\t\t\tplaceholder=\"Filter...\"\n\t\t\t\t\tvalue=${search}\n\t\t\t\t\tonInput=${(e: Event) => onSearchChange((e.target as HTMLInputElement).value)}\n\t\t\t\t/>\n\t\t\t\t<button class=\"kora-btn\" onClick=${onPauseToggle}>\n\t\t\t\t\t${paused ? 'Resume' : 'Pause'}\n\t\t\t\t</button>\n\t\t\t\t<button class=\"kora-btn\" onClick=${onClear}>Clear</button>\n\t\t\t\t${ALL_CATEGORIES.map(\n\t\t\t\t\t(cat) => html`\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tclass=\"kora-cat-toggle${categories.has(cat) ? ' active' : ''}\"\n\t\t\t\t\t\t\tonClick=${() => onToggleCategory(cat)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t${cat}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t`,\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Timeline Panel\n// ============================================================================\n\nfunction TimelinePanel({\n\titems,\n\tsearch,\n\texpanded,\n\tonToggle,\n}: {\n\titems: TimelineItem[]\n\tsearch: string\n\texpanded: Set<number>\n\tonToggle: (id: number) => void\n}) {\n\tconst lowerSearch = search.toLowerCase()\n\tconst filtered = lowerSearch\n\t\t? items.filter(\n\t\t\t\t(i) =>\n\t\t\t\t\ti.label.toLowerCase().includes(lowerSearch) || i.type.toLowerCase().includes(lowerSearch),\n\t\t\t)\n\t\t: items\n\n\tconst visible = filtered.slice(-200).reverse()\n\n\tif (visible.length === 0) {\n\t\treturn html`<div class=\"kora-panel\"><div class=\"kora-empty\">No events recorded yet.</div></div>`\n\t}\n\n\treturn html`\n\t\t<div class=\"kora-panel kora-timeline\">\n\t\t\t<div class=\"kora-summary\">Showing ${visible.length} of ${items.length} events</div>\n\t\t\t<div class=\"kora-event-list\">\n\t\t\t\t${visible.map(\n\t\t\t\t\t(item) => html`\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey=${item.id}\n\t\t\t\t\t\t\tclass=\"kora-row${expanded.has(item.id) ? ' expanded' : ''}\"\n\t\t\t\t\t\t\tonClick=${() => onToggle(item.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"kora-time\">${formatTime(item.receivedAt)}</span>\n\t\t\t\t\t\t\t<span class=\"kora-dot\" style=\"background:${item.color}\"></span>\n\t\t\t\t\t\t\t<span class=\"kora-type\">${item.type}</span>\n\t\t\t\t\t\t\t<span class=\"kora-label\">${item.label}</span>\n\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\titem.dependsOn.length > 0\n\t\t\t\t\t\t\t\t\t? html`<span class=\"kora-deps\">deps: ${item.dependsOn.length}</span>`\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\texpanded.has(item.id)\n\t\t\t\t\t\t\t\t? html`\n\t\t\t\t\t\t\t\t\t<div class=\"kora-detail\">\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Event ID:</span> ${item.id}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Received:</span>\n\t\t\t\t\t\t\t\t\t\t\t${new Date(item.receivedAt).toISOString()}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\t\t\t\titem.dependsOn.length > 0\n\t\t\t\t\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Causal deps:</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t${item.dependsOn.join(', ')}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t}\n\t\t\t\t\t`,\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Conflicts Panel\n// ============================================================================\n\nfunction ConflictsPanel({\n\titems,\n\tsearch,\n\texpanded,\n\tonToggle,\n}: {\n\titems: ConflictItem[]\n\tsearch: string\n\texpanded: Set<number>\n\tonToggle: (id: number) => void\n}) {\n\tconst lowerSearch = search.toLowerCase()\n\tconst filtered = lowerSearch\n\t\t? items.filter(\n\t\t\t\t(i) =>\n\t\t\t\t\ti.collection.toLowerCase().includes(lowerSearch) ||\n\t\t\t\t\ti.field.toLowerCase().includes(lowerSearch) ||\n\t\t\t\t\ti.strategy.toLowerCase().includes(lowerSearch),\n\t\t\t)\n\t\t: items\n\n\tconst visible = filtered.slice(-100).reverse()\n\n\tif (visible.length === 0) {\n\t\treturn html`<div class=\"kora-panel\"><div class=\"kora-empty\">No merge conflicts detected.</div></div>`\n\t}\n\n\treturn html`\n\t\t<div class=\"kora-panel kora-conflicts\">\n\t\t\t<div class=\"kora-summary\">\n\t\t\t\t${items.length} conflict${items.length !== 1 ? 's' : ''}\n\t\t\t</div>\n\t\t\t<table class=\"kora-table\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr>\n\t\t\t\t\t\t<th>Time</th>\n\t\t\t\t\t\t<th>Collection</th>\n\t\t\t\t\t\t<th>Field</th>\n\t\t\t\t\t\t<th>Strategy</th>\n\t\t\t\t\t\t<th>Tier</th>\n\t\t\t\t\t\t<th>Result</th>\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t${visible.map(\n\t\t\t\t\t\t(item) => html`\n\t\t\t\t\t\t\t<tr\n\t\t\t\t\t\t\t\tkey=${item.id}\n\t\t\t\t\t\t\t\tclass=\"kora-conflict-row${item.constraintViolated ? ' violated' : ''}${expanded.has(item.id) ? ' expanded' : ''}\"\n\t\t\t\t\t\t\t\tonClick=${() => onToggle(item.id)}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<td>${formatTime(item.timestamp)}</td>\n\t\t\t\t\t\t\t\t<td>${item.collection}</td>\n\t\t\t\t\t\t\t\t<td class=\"kora-mono\">${item.field}</td>\n\t\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t\t<span class=\"kora-strategy tier-${item.tier}\">${item.strategy}</span>\n\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t<td>${item.tier}</td>\n\t\t\t\t\t\t\t\t<td class=\"kora-mono\">${truncate(String(item.output), 30)}</td>\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\texpanded.has(item.id)\n\t\t\t\t\t\t\t\t\t? html`\n\t\t\t\t\t\t\t\t\t\t<tr class=\"kora-detail-row\">\n\t\t\t\t\t\t\t\t\t\t\t<td colspan=\"6\">\n\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-conflict-detail\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-comparison\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-box\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-label\">Input A (local)</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<pre class=\"kora-value-code\">\n${formatValue(item.inputA)}</pre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-box\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-label\">Input B (remote)</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<pre class=\"kora-value-code\">\n${formatValue(item.inputB)}</pre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-box result\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-value-label\">Result</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<pre class=\"kora-value-code\">\n${formatValue(item.output)}</pre\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\t\t\t\t\t\t\titem.constraintViolated\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t? html`<div class=\"kora-constraint-warning\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tConstraint violated: ${item.constraintViolated}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t`,\n\t\t\t\t\t)}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Operations Panel\n// ============================================================================\n\nfunction OperationsPanel({\n\titems,\n\tsearch,\n\texpanded,\n\tonToggle,\n}: {\n\titems: OperationItem[]\n\tsearch: string\n\texpanded: Set<number>\n\tonToggle: (id: number) => void\n}) {\n\tconst lowerSearch = search.toLowerCase()\n\tconst filtered = lowerSearch\n\t\t? items.filter(\n\t\t\t\t(i) =>\n\t\t\t\t\ti.collection.toLowerCase().includes(lowerSearch) ||\n\t\t\t\t\ti.recordId.toLowerCase().includes(lowerSearch) ||\n\t\t\t\t\ti.operationId.toLowerCase().includes(lowerSearch) ||\n\t\t\t\t\ti.opType.toLowerCase().includes(lowerSearch),\n\t\t\t)\n\t\t: items\n\n\tconst visible = filtered.slice(-200).reverse()\n\n\tif (visible.length === 0) {\n\t\treturn html`<div class=\"kora-panel\"><div class=\"kora-empty\">No operations recorded yet.</div></div>`\n\t}\n\n\treturn html`\n\t\t<div class=\"kora-panel kora-operations\">\n\t\t\t<div class=\"kora-summary\">\n\t\t\t\tShowing ${visible.length} of ${items.length} operations\n\t\t\t</div>\n\t\t\t<div class=\"kora-op-list\">\n\t\t\t\t${visible.map(\n\t\t\t\t\t(item) => html`\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tkey=${item.id}\n\t\t\t\t\t\t\tclass=\"kora-op-row${expanded.has(item.id) ? ' expanded' : ''}\"\n\t\t\t\t\t\t\tonClick=${() => onToggle(item.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"kora-time\">${formatTime(item.timestamp)}</span>\n\t\t\t\t\t\t\t<span class=\"kora-op-type op-${item.opType}\">${item.opType}</span>\n\t\t\t\t\t\t\t<span class=\"kora-op-collection\">${item.collection}</span>\n\t\t\t\t\t\t\t<span class=\"kora-op-record kora-mono\"\n\t\t\t\t\t\t\t\t>${truncate(item.recordId, 12)}</span\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<span class=\"kora-op-node\">node:${truncate(item.nodeId, 8)}</span>\n\t\t\t\t\t\t\t<span class=\"kora-op-seq\">#${item.sequenceNumber}</span>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t${\n\t\t\t\t\t\t\texpanded.has(item.id)\n\t\t\t\t\t\t\t\t? html`\n\t\t\t\t\t\t\t\t\t<div class=\"kora-op-detail\">\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Operation ID:</span>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-mono\">${item.operationId}</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Node:</span>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-mono\">${item.nodeId}</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Sequence:</span>\n\t\t\t\t\t\t\t\t\t\t\t${item.sequenceNumber}\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\t\t\t\titem.causalDeps.length > 0\n\t\t\t\t\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-detail-label\">Causal deps:</span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-mono\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>${item.causalDeps.join(', ')}</span\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t${\n\t\t\t\t\t\t\t\t\t\t\titem.data\n\t\t\t\t\t\t\t\t\t\t\t\t? html`<div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<div class=\"kora-detail-label\">Data:</div>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<pre class=\"kora-value-code\">\n${JSON.stringify(item.data, null, 2)}</pre\n\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t</div>`\n\t\t\t\t\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t`\n\t\t\t\t\t\t\t\t: null\n\t\t\t\t\t\t}\n\t\t\t\t\t`,\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Network Panel\n// ============================================================================\n\nfunction NetworkPanel({\n\tnetwork,\n\tevents,\n}: {\n\tnetwork: NetworkStatusModel\n\tevents: readonly TimestampedEvent[]\n}) {\n\tconst statusClass = network.connected ? 'connected' : 'disconnected'\n\tconst statusText = network.connected\n\t\t? network.quality\n\t\t\t? `Connected (${network.quality})`\n\t\t\t: 'Connected'\n\t\t: 'Disconnected'\n\n\tconst syncEvents = events\n\t\t.filter((e) => {\n\t\t\tconst cat = eventTypeToCategory(e.event.type)\n\t\t\treturn cat === 'sync' || cat === 'connection'\n\t\t})\n\t\t.slice(-50)\n\t\t.reverse()\n\n\treturn html`\n\t\t<div class=\"kora-panel kora-network\">\n\t\t\t<div class=\"kora-network-status\">\n\t\t\t\t<div class=\"kora-status-indicator ${statusClass}\">\n\t\t\t\t\t<span class=\"kora-status-dot\"></span>\n\t\t\t\t\t<span class=\"kora-status-text\">${statusText}</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<div class=\"kora-stats-grid\">\n\t\t\t\t<${StatCard} label=\"Sent\" value=${String(network.sentOps)} unit=\"ops\" />\n\t\t\t\t<${StatCard} label=\"Received\" value=${String(network.receivedOps)} unit=\"ops\" />\n\t\t\t\t<${StatCard} label=\"Pending\" value=${String(network.pendingAcks)} unit=\"acks\" />\n\t\t\t\t<${StatCard}\n\t\t\t\t\tlabel=\"Last Sync\"\n\t\t\t\t\tvalue=${network.lastSyncAt ? formatTime(network.lastSyncAt) : 'never'}\n\t\t\t\t/>\n\t\t\t</div>\n\n\t\t\t${\n\t\t\t\tnetwork.versionVector.length > 0\n\t\t\t\t\t? html`\n\t\t\t\t\t\t<div class=\"kora-vv-section\">\n\t\t\t\t\t\t\t<h3>Version Vector</h3>\n\t\t\t\t\t\t\t<table class=\"kora-table kora-vv-table\">\n\t\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t\t<th>Node</th>\n\t\t\t\t\t\t\t\t\t\t<th>Sequence</th>\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t\t${network.versionVector.map(\n\t\t\t\t\t\t\t\t\t\t(entry) => html`\n\t\t\t\t\t\t\t\t\t\t\t<tr key=${entry.nodeId}>\n\t\t\t\t\t\t\t\t\t\t\t\t<td class=\"kora-mono\">${truncate(entry.nodeId, 16)}</td>\n\t\t\t\t\t\t\t\t\t\t\t\t<td>${entry.sequenceNumber}</td>\n\t\t\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t\t</table>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t`\n\t\t\t\t\t: null\n\t\t\t}\n\n\t\t\t${\n\t\t\t\tsyncEvents.length > 0\n\t\t\t\t\t? html`\n\t\t\t\t\t\t<div class=\"kora-sync-log\">\n\t\t\t\t\t\t\t<h3>Recent Sync Activity</h3>\n\t\t\t\t\t\t\t<div class=\"kora-event-list\">\n\t\t\t\t\t\t\t\t${syncEvents.map(\n\t\t\t\t\t\t\t\t\t(entry) => html`\n\t\t\t\t\t\t\t\t\t\t<div key=${entry.id} class=\"kora-row compact\">\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-time\"\n\t\t\t\t\t\t\t\t\t\t\t\t>${formatTime(entry.receivedAt)}</span\n\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<span class=\"kora-type\">${entry.event.type}</span>\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t`,\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t`\n\t\t\t\t\t: null\n\t\t\t}\n\t\t</div>\n\t`\n}\n\nfunction StatCard({\n\tlabel,\n\tvalue,\n\tunit,\n}: {\n\tlabel: string\n\tvalue: string\n\tunit?: string\n}) {\n\treturn html`\n\t\t<div class=\"kora-stat-card\">\n\t\t\t<div class=\"kora-stat-value\">${value}</div>\n\t\t\t<div class=\"kora-stat-label\">${label}${unit ? ` (${unit})` : ''}</div>\n\t\t</div>\n\t`\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\n/**\n * Render the DevTools panel into the target element using Preact.\n * Efficient re-renders via virtual DOM diffing.\n */\nexport function renderDevtoolsPanel(\n\ttarget: HTMLElement,\n\tevents: readonly TimestampedEvent[],\n): void {\n\trender(html`<${DevToolsPanel} events=${events} />`, target)\n}\n","import type { KoraEvent, KoraEventType } from '@korajs/core'\n\n/** A KoraEvent wrapped with a reception timestamp and sequential ID */\nexport interface TimestampedEvent {\n\t/** Auto-incrementing sequential ID */\n\tid: number\n\t/** The original framework event */\n\tevent: KoraEvent\n\t/** Date.now() when the instrumenter captured the event */\n\treceivedAt: number\n}\n\n/** Event categories for filtering and grouping */\nexport type EventCategory = 'operation' | 'merge' | 'sync' | 'query' | 'connection'\n\n/** Maps each KoraEventType to its category */\nconst EVENT_TYPE_CATEGORIES: Record<KoraEventType, EventCategory> = {\n\t'operation:created': 'operation',\n\t'operation:applied': 'operation',\n\t'merge:started': 'merge',\n\t'merge:completed': 'merge',\n\t'merge:conflict': 'merge',\n\t'constraint:violated': 'merge',\n\t'sync:connected': 'sync',\n\t'sync:disconnected': 'sync',\n\t'sync:auth-failed': 'sync',\n\t'sync:schema-mismatch': 'sync',\n\t'sync:apply-failed': 'sync',\n\t'sync:sent': 'sync',\n\t'sync:received': 'sync',\n\t'sync:acknowledged': 'sync',\n\t'query:subscribed': 'query',\n\t'query:invalidated': 'query',\n\t'query:executed': 'query',\n\t'connection:quality': 'connection',\n\t'sync:diagnostics': 'sync',\n\t'sync:bandwidth': 'sync',\n\t'sync:initial-sync-progress': 'sync',\n\t'awareness:updated': 'sync',\n\t'state-machine:transition': 'operation',\n\t'state-machine:rejected': 'operation',\n\t'store:persistence-error': 'connection',\n\t'store:quota-exceeded': 'connection',\n\t'replay:completed': 'operation',\n}\n\n/** Look up the category for a given event type */\nexport function eventTypeToCategory(type: KoraEventType): EventCategory {\n\treturn EVENT_TYPE_CATEGORIES[type]\n}\n\n/** All event categories */\nexport const EVENT_CATEGORIES = ['operation', 'merge', 'sync', 'query', 'connection'] as const\n\n/** Configuration for the Instrumenter */\nexport interface DevtoolsConfig {\n\t/** Max events in the ring buffer (default: 10000) */\n\tbufferSize?: number\n\t/** Enable message bridge for DevTools panel communication (default: true) */\n\tbridgeEnabled?: boolean\n\t/** Custom message channel name (default: 'kora-devtools') */\n\tchannelName?: string\n}\n\n/** Filter criteria for querying events */\nexport interface EventFilterCriteria {\n\t/** Filter by event categories */\n\tcategories?: EventCategory[]\n\t/** Filter by specific event types */\n\ttypes?: KoraEventType[]\n\t/** Filter by reception time range */\n\ttimeRange?: { start: number; end: number }\n\t/** Filter by collection name (extracted from operation events) */\n\tcollection?: string\n}\n\n/** Aggregated statistics computed from a set of events */\nexport interface EventStatistics {\n\ttotalEvents: number\n\teventsByCategory: Record<EventCategory, number>\n\teventsByType: Partial<Record<KoraEventType, number>>\n\tmergeConflicts: number\n\tconstraintViolations: number\n\tavgMergeDuration: number | null\n\tavgQueryDuration: number | null\n\tsyncOperationsSent: number\n\tsyncOperationsReceived: number\n}\n","import type { KoraEventType } from '@korajs/core'\nimport type { EventCategory, EventFilterCriteria, TimestampedEvent } from '../types'\nimport { eventTypeToCategory } from '../types'\n\n/**\n * Maps a KoraEventType to its EventCategory.\n * Re-exported from types for public API convenience.\n */\nexport function getEventCategory(type: KoraEventType): EventCategory {\n\treturn eventTypeToCategory(type)\n}\n\n/**\n * Extracts the collection name from an event, if present.\n * Only operation events and query:subscribed carry a collection field directly.\n * For other event types that embed operations, we inspect the nested operation.\n */\nfunction extractCollection(event: TimestampedEvent): string | null {\n\tconst e = event.event\n\tswitch (e.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn e.operation.collection\n\t\tcase 'merge:started':\n\t\t\treturn e.operationA.collection\n\t\tcase 'merge:completed':\n\t\tcase 'merge:conflict':\n\t\t\treturn e.trace.operationA.collection\n\t\tcase 'constraint:violated':\n\t\t\treturn e.trace.operationA.collection\n\t\tcase 'query:subscribed':\n\t\t\treturn e.collection\n\t\tcase 'query:invalidated':\n\t\t\treturn e.trigger.collection\n\t\tcase 'sync:sent':\n\t\t\treturn e.operations[0]?.collection ?? null\n\t\tcase 'sync:received':\n\t\t\treturn e.operations[0]?.collection ?? null\n\t\tdefault:\n\t\t\treturn null\n\t}\n}\n\n/**\n * Filters a list of timestamped events by the given criteria.\n * All criteria are combined with AND logic: an event must match all specified criteria.\n * Returns all events if no criteria are specified.\n */\nexport function filterEvents(\n\tevents: readonly TimestampedEvent[],\n\tcriteria: EventFilterCriteria,\n): readonly TimestampedEvent[] {\n\tconst { categories, types, timeRange, collection } = criteria\n\n\t// Fast path: no filters\n\tif (!categories && !types && !timeRange && !collection) {\n\t\treturn events\n\t}\n\n\t// Pre-compute the set of allowed types from categories for efficient lookup\n\tconst categorySet = categories ? new Set<EventCategory>(categories) : null\n\tconst typeSet = types ? new Set<KoraEventType>(types) : null\n\n\treturn events.filter((event) => {\n\t\t// Category filter\n\t\tif (categorySet) {\n\t\t\tconst cat = eventTypeToCategory(event.event.type)\n\t\t\tif (!categorySet.has(cat)) return false\n\t\t}\n\n\t\t// Specific type filter\n\t\tif (typeSet) {\n\t\t\tif (!typeSet.has(event.event.type)) return false\n\t\t}\n\n\t\t// Time range filter (on receivedAt)\n\t\tif (timeRange) {\n\t\t\tif (event.receivedAt < timeRange.start || event.receivedAt > timeRange.end) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\n\t\t// Collection filter\n\t\tif (collection) {\n\t\t\tconst eventCollection = extractCollection(event)\n\t\t\tif (eventCollection !== collection) return false\n\t\t}\n\n\t\treturn true\n\t})\n}\n","import type { KoraEventType } from '@korajs/core'\nimport type { EventCategory, EventStatistics, TimestampedEvent } from '../types'\nimport { EVENT_CATEGORIES, eventTypeToCategory } from '../types'\n\n/**\n * Computes aggregate statistics from a collection of timestamped events.\n * Processes the event list in a single pass for efficiency.\n */\nexport function computeStatistics(events: readonly TimestampedEvent[]): EventStatistics {\n\tconst eventsByCategory: Record<EventCategory, number> = {\n\t\toperation: 0,\n\t\tmerge: 0,\n\t\tsync: 0,\n\t\tquery: 0,\n\t\tconnection: 0,\n\t}\n\tconst eventsByType: Partial<Record<KoraEventType, number>> = {}\n\n\tlet mergeConflicts = 0\n\tlet constraintViolations = 0\n\tlet mergeDurationSum = 0\n\tlet mergeDurationCount = 0\n\tlet queryDurationSum = 0\n\tlet queryDurationCount = 0\n\tlet syncOperationsSent = 0\n\tlet syncOperationsReceived = 0\n\n\tfor (const timestamped of events) {\n\t\tconst e = timestamped.event\n\n\t\t// Category count\n\t\tconst category = eventTypeToCategory(e.type)\n\t\teventsByCategory[category]++\n\n\t\t// Type count\n\t\teventsByType[e.type] = (eventsByType[e.type] ?? 0) + 1\n\n\t\t// Type-specific aggregations\n\t\tswitch (e.type) {\n\t\t\tcase 'merge:completed':\n\t\t\t\tmergeDurationSum += e.trace.duration\n\t\t\t\tmergeDurationCount++\n\t\t\t\tbreak\n\t\t\tcase 'merge:conflict':\n\t\t\t\tmergeConflicts++\n\t\t\t\tmergeDurationSum += e.trace.duration\n\t\t\t\tmergeDurationCount++\n\t\t\t\tbreak\n\t\t\tcase 'constraint:violated':\n\t\t\t\tconstraintViolations++\n\t\t\t\tbreak\n\t\t\tcase 'query:executed':\n\t\t\t\tqueryDurationSum += e.duration\n\t\t\t\tqueryDurationCount++\n\t\t\t\tbreak\n\t\t\tcase 'sync:sent':\n\t\t\t\tsyncOperationsSent += e.batchSize\n\t\t\t\tbreak\n\t\t\tcase 'sync:received':\n\t\t\t\tsyncOperationsReceived += e.batchSize\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\treturn {\n\t\ttotalEvents: events.length,\n\t\teventsByCategory,\n\t\teventsByType,\n\t\tmergeConflicts,\n\t\tconstraintViolations,\n\t\tavgMergeDuration: mergeDurationCount > 0 ? mergeDurationSum / mergeDurationCount : null,\n\t\tavgQueryDuration: queryDurationCount > 0 ? queryDurationSum / queryDurationCount : null,\n\t\tsyncOperationsSent,\n\t\tsyncOperationsReceived,\n\t}\n}\n","/**\n * Shared utility functions for DevTools panel components.\n */\n\nexport function formatTime(timestamp: number): string {\n\tconst d = new Date(timestamp)\n\tconst h = String(d.getHours()).padStart(2, '0')\n\tconst m = String(d.getMinutes()).padStart(2, '0')\n\tconst s = String(d.getSeconds()).padStart(2, '0')\n\tconst ms = String(d.getMilliseconds()).padStart(3, '0')\n\treturn `${h}:${m}:${s}.${ms}`\n}\n\nexport function formatDuration(ms: number): string {\n\tif (ms < 1) return `${(ms * 1000).toFixed(0)}µs`\n\tif (ms < 1000) return `${ms.toFixed(1)}ms`\n\treturn `${(ms / 1000).toFixed(2)}s`\n}\n\nexport function truncate(str: string, max: number): string {\n\tif (str.length <= max) return str\n\treturn `${str.slice(0, max - 1)}…`\n}\n\nexport function formatValue(value: unknown): string {\n\tif (value === null || value === undefined) return 'null'\n\tif (typeof value === 'string') return `\"${value}\"`\n\tif (typeof value === 'object') return JSON.stringify(value, null, 2)\n\treturn String(value)\n}\n","import type { KoraEvent, Operation } from '@korajs/core'\nimport type { TimestampedEvent } from '../types'\n\nexport interface TimelineItem {\n\tid: number\n\ttype: KoraEvent['type']\n\tlabel: string\n\tcolor: string\n\treceivedAt: number\n\tdependsOn: string[]\n}\n\nexport interface ConflictItem {\n\tid: number\n\ttimestamp: number\n\tcollection: string\n\tfield: string\n\tstrategy: string\n\ttier: 1 | 2 | 3\n\tinputA: unknown\n\tinputB: unknown\n\toutput: unknown\n\tconstraintViolated: string | null\n}\n\nexport interface OperationItem {\n\tid: number\n\ttimestamp: number\n\toperationId: string\n\tcollection: string\n\trecordId: string\n\topType: Operation['type']\n\tdata: Record<string, unknown> | null\n\tcausalDeps: string[]\n\tnodeId: string\n\tsequenceNumber: number\n}\n\nexport interface NetworkStatusModel {\n\tconnected: boolean\n\tquality: string | null\n\tpendingAcks: number\n\tlastSyncAt: number | null\n\tsentOps: number\n\treceivedOps: number\n\tversionVector: Array<{ nodeId: string; sequenceNumber: number }>\n}\n\nexport interface DevtoolsPanelModel {\n\ttimeline: TimelineItem[]\n\tconflicts: ConflictItem[]\n\toperations: OperationItem[]\n\tnetwork: NetworkStatusModel\n}\n\nexport function buildPanelModel(events: readonly TimestampedEvent[]): DevtoolsPanelModel {\n\tconst timeline = events.map((entry) => ({\n\t\tid: entry.id,\n\t\ttype: entry.event.type,\n\t\tlabel: timelineLabel(entry.event),\n\t\tcolor: timelineColor(entry.event.type),\n\t\treceivedAt: entry.receivedAt,\n\t\tdependsOn: extractCausalDependencies(entry.event),\n\t}))\n\n\tconst conflicts = events.flatMap((entry) => {\n\t\tif (\n\t\t\tentry.event.type !== 'merge:completed' &&\n\t\t\tentry.event.type !== 'merge:conflict' &&\n\t\t\tentry.event.type !== 'constraint:violated'\n\t\t) {\n\t\t\treturn []\n\t\t}\n\n\t\tconst trace = entry.event.trace\n\t\treturn [\n\t\t\t{\n\t\t\t\tid: entry.id,\n\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\tcollection: trace.operationA.collection,\n\t\t\t\tfield: trace.field,\n\t\t\t\tstrategy: trace.strategy,\n\t\t\t\ttier: trace.tier,\n\t\t\t\tinputA: trace.inputA,\n\t\t\t\tinputB: trace.inputB,\n\t\t\t\toutput: trace.output,\n\t\t\t\tconstraintViolated: trace.constraintViolated,\n\t\t\t},\n\t\t]\n\t})\n\n\tconst operations = events\n\t\t.map((entry) => {\n\t\t\tconst operation = extractOperation(entry.event)\n\t\t\tif (!operation) return null\n\n\t\t\treturn {\n\t\t\t\tid: entry.id,\n\t\t\t\ttimestamp: entry.receivedAt,\n\t\t\t\toperationId: operation.id,\n\t\t\t\tcollection: operation.collection,\n\t\t\t\trecordId: operation.recordId,\n\t\t\t\topType: operation.type,\n\t\t\t\tdata: operation.data,\n\t\t\t\tcausalDeps: operation.causalDeps,\n\t\t\t\tnodeId: operation.nodeId,\n\t\t\t\tsequenceNumber: operation.sequenceNumber,\n\t\t\t}\n\t\t})\n\t\t.filter((item): item is OperationItem => item !== null)\n\n\tconst network = buildNetworkStatus(events, operations)\n\n\treturn {\n\t\ttimeline,\n\t\tconflicts,\n\t\toperations,\n\t\tnetwork,\n\t}\n}\n\nfunction buildNetworkStatus(\n\tevents: readonly TimestampedEvent[],\n\toperations: readonly OperationItem[],\n): NetworkStatusModel {\n\tlet connected = false\n\tlet quality: string | null = null\n\tlet pendingAcks = 0\n\tlet lastSyncAt: number | null = null\n\tlet sentOps = 0\n\tlet receivedOps = 0\n\n\tfor (const entry of events) {\n\t\tswitch (entry.event.type) {\n\t\t\tcase 'sync:connected':\n\t\t\t\tconnected = true\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:disconnected':\n\t\t\t\tconnected = false\n\t\t\t\tbreak\n\t\t\tcase 'connection:quality':\n\t\t\t\tquality = entry.event.quality\n\t\t\t\tbreak\n\t\t\tcase 'sync:sent':\n\t\t\t\tsentOps += entry.event.operations.length\n\t\t\t\tpendingAcks += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:received':\n\t\t\t\treceivedOps += entry.event.operations.length\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t\tcase 'sync:acknowledged':\n\t\t\t\tpendingAcks = Math.max(0, pendingAcks - 1)\n\t\t\t\tlastSyncAt = entry.receivedAt\n\t\t\t\tbreak\n\t\t}\n\t}\n\n\tconst vector = new Map<string, number>()\n\tfor (const operation of operations) {\n\t\tconst current = vector.get(operation.nodeId) ?? 0\n\t\tif (operation.sequenceNumber > current) {\n\t\t\tvector.set(operation.nodeId, operation.sequenceNumber)\n\t\t}\n\t}\n\n\treturn {\n\t\tconnected,\n\t\tquality,\n\t\tpendingAcks,\n\t\tlastSyncAt,\n\t\tsentOps,\n\t\treceivedOps,\n\t\tversionVector: [...vector.entries()]\n\t\t\t.map(([nodeId, sequenceNumber]) => ({ nodeId, sequenceNumber }))\n\t\t\t.sort((left, right) => left.nodeId.localeCompare(right.nodeId)),\n\t}\n}\n\nfunction timelineLabel(event: KoraEvent): string {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn `${event.operation.type} ${event.operation.collection}/${event.operation.recordId}`\n\t\tcase 'merge:started':\n\t\t\treturn `merge start ${event.operationA.collection}`\n\t\tcase 'merge:completed':\n\t\t\treturn `merge complete ${event.trace.field}`\n\t\tcase 'merge:conflict':\n\t\t\treturn `merge conflict ${event.trace.field}`\n\t\tcase 'constraint:violated':\n\t\t\treturn `constraint ${event.constraint}`\n\t\tcase 'sync:connected':\n\t\t\treturn `sync connected ${event.nodeId}`\n\t\tcase 'sync:disconnected':\n\t\t\treturn 'sync disconnected'\n\t\tcase 'sync:auth-failed':\n\t\t\treturn 'sync auth failed'\n\t\tcase 'sync:schema-mismatch':\n\t\t\treturn `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`\n\t\tcase 'sync:apply-failed':\n\t\t\treturn `sync apply failed ${event.operationId}`\n\t\tcase 'sync:sent':\n\t\t\treturn `sync sent ${event.batchSize}`\n\t\tcase 'sync:received':\n\t\t\treturn `sync received ${event.batchSize}`\n\t\tcase 'sync:acknowledged':\n\t\t\treturn `sync ack ${event.sequenceNumber}`\n\t\tcase 'query:subscribed':\n\t\t\treturn `query subscribed ${event.collection}`\n\t\tcase 'query:invalidated':\n\t\t\treturn `query invalidated ${event.queryId}`\n\t\tcase 'query:executed':\n\t\t\treturn `query executed ${event.queryId}`\n\t\tcase 'connection:quality':\n\t\t\treturn `connection ${event.quality}`\n\t\tcase 'sync:diagnostics':\n\t\t\treturn `sync diagnostics ${event.diagnostics.quality}`\n\t\tcase 'sync:bandwidth':\n\t\t\treturn `bandwidth ${event.direction} ${event.bytesPerSecond}B/s`\n\t\tcase 'sync:initial-sync-progress':\n\t\t\treturn `initial sync ${Math.round(event.progress * 100)}%`\n\t\tcase 'awareness:updated':\n\t\t\treturn `awareness ${event.states.size} peers`\n\t\tcase 'state-machine:transition':\n\t\t\treturn `transition ${event.collection} ${event.from} → ${event.to}`\n\t\tcase 'state-machine:rejected':\n\t\t\treturn `transition rejected ${event.collection} ${event.from} → ${event.to}`\n\t\tcase 'store:persistence-error':\n\t\t\treturn `store persistence error ${event.message}`\n\t\tcase 'store:quota-exceeded':\n\t\t\treturn 'store quota exceeded'\n\t\tcase 'replay:completed':\n\t\t\treturn `replay ${event.operationsApplied} ops`\n\t}\n}\n\nfunction timelineColor(type: KoraEvent['type']): string {\n\tif (type.startsWith('operation:')) return '#22c55e'\n\tif (type.startsWith('sync:')) return '#a855f7'\n\tif (type.startsWith('merge:') || type.startsWith('constraint:')) return '#f59e0b'\n\tif (type.startsWith('query:')) return '#0ea5e9'\n\treturn '#64748b'\n}\n\nfunction extractCausalDependencies(event: KoraEvent): string[] {\n\tconst operation = extractOperation(event)\n\treturn operation?.causalDeps ?? []\n}\n\nfunction extractOperation(event: KoraEvent): Operation | null {\n\tswitch (event.type) {\n\t\tcase 'operation:created':\n\t\tcase 'operation:applied':\n\t\t\treturn event.operation\n\t\tcase 'query:invalidated':\n\t\t\treturn event.trigger\n\t\tdefault:\n\t\t\treturn null\n\t}\n}\n","/** DevTools panel styles (shared by extension and embedded overlay). */\nexport const KORA_DEVTOOLS_STYLES =\n\t\"/* ================================================================\\n Kora DevTools — Dark theme optimized for Chrome DevTools panels\\n ================================================================ */\\n\\n :root {\\n --bg-primary: #1e1e2e;\\n --bg-secondary: #181825;\\n --bg-surface: #313244;\\n --bg-hover: #45475a;\\n --bg-selected: #585b70;\\n --text-primary: #cdd6f4;\\n --text-secondary: #a6adc8;\\n --text-muted: #6c7086;\\n --border: #45475a;\\n\\n --green: #a6e3a1;\\n --red: #f38ba8;\\n --yellow: #f9e2af;\\n --blue: #89b4fa;\\n --purple: #cba6f7;\\n --teal: #94e2d5;\\n --peach: #fab387;\\n\\n --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;\\n --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\\n --font-size: 12px;\\n }\\n\\n * {\\n box-sizing: border-box;\\n margin: 0;\\n padding: 0;\\n }\\n\\n body {\\n background: var(--bg-primary);\\n color: var(--text-primary);\\n font-family: var(--font-sans);\\n font-size: var(--font-size);\\n line-height: 1.5;\\n overflow: hidden;\\n }\\n\\n #kora-devtools-root {\\n width: 100vw;\\n height: 100vh;\\n display: flex;\\n flex-direction: column;\\n }\\n\\n .kora-devtools {\\n display: flex;\\n flex-direction: column;\\n height: 100%;\\n }\\n\\n /* ---- Toolbar ---- */\\n\\n .kora-toolbar {\\n background: var(--bg-secondary);\\n border-bottom: 1px solid var(--border);\\n padding: 4px 8px;\\n display: flex;\\n flex-wrap: wrap;\\n align-items: center;\\n gap: 8px;\\n flex-shrink: 0;\\n }\\n\\n .kora-tabs {\\n display: flex;\\n gap: 2px;\\n }\\n\\n .kora-tab {\\n background: transparent;\\n color: var(--text-secondary);\\n border: none;\\n padding: 4px 12px;\\n border-radius: 4px;\\n cursor: pointer;\\n font-size: var(--font-size);\\n font-family: var(--font-sans);\\n display: flex;\\n align-items: center;\\n gap: 6px;\\n }\\n\\n .kora-tab:hover {\\n background: var(--bg-hover);\\n color: var(--text-primary);\\n }\\n\\n .kora-tab.active {\\n background: var(--bg-surface);\\n color: var(--text-primary);\\n font-weight: 600;\\n }\\n\\n .kora-badge {\\n background: var(--bg-hover);\\n color: var(--text-muted);\\n padding: 0 6px;\\n border-radius: 10px;\\n font-size: 10px;\\n font-weight: 500;\\n }\\n\\n .kora-tab.active .kora-badge {\\n background: var(--blue);\\n color: var(--bg-primary);\\n }\\n\\n .kora-controls {\\n display: flex;\\n gap: 4px;\\n align-items: center;\\n margin-left: auto;\\n flex-wrap: wrap;\\n }\\n\\n .kora-search {\\n background: var(--bg-surface);\\n color: var(--text-primary);\\n border: 1px solid var(--border);\\n padding: 3px 8px;\\n border-radius: 4px;\\n font-size: var(--font-size);\\n font-family: var(--font-sans);\\n width: 140px;\\n outline: none;\\n }\\n\\n .kora-search:focus {\\n border-color: var(--blue);\\n }\\n\\n .kora-btn {\\n background: var(--bg-surface);\\n color: var(--text-secondary);\\n border: 1px solid var(--border);\\n padding: 3px 8px;\\n border-radius: 4px;\\n cursor: pointer;\\n font-size: 11px;\\n font-family: var(--font-sans);\\n }\\n\\n .kora-btn:hover {\\n background: var(--bg-hover);\\n color: var(--text-primary);\\n }\\n\\n .kora-cat-toggle {\\n background: transparent;\\n color: var(--text-muted);\\n border: 1px solid transparent;\\n padding: 2px 6px;\\n border-radius: 3px;\\n cursor: pointer;\\n font-size: 10px;\\n font-family: var(--font-sans);\\n text-transform: uppercase;\\n letter-spacing: 0.5px;\\n }\\n\\n .kora-cat-toggle:hover {\\n color: var(--text-secondary);\\n }\\n\\n .kora-cat-toggle.active {\\n background: var(--bg-surface);\\n color: var(--text-primary);\\n border-color: var(--border);\\n }\\n\\n /* ---- Content ---- */\\n\\n .kora-content {\\n flex: 1;\\n overflow: hidden;\\n display: flex;\\n flex-direction: column;\\n }\\n\\n .kora-panel {\\n flex: 1;\\n overflow-y: auto;\\n padding: 8px;\\n }\\n\\n .kora-empty {\\n color: var(--text-muted);\\n text-align: center;\\n padding: 40px;\\n font-style: italic;\\n }\\n\\n .kora-summary {\\n color: var(--text-muted);\\n font-size: 11px;\\n padding: 4px 0 8px;\\n border-bottom: 1px solid var(--border);\\n margin-bottom: 4px;\\n }\\n\\n /* ---- Event List (Timeline) ---- */\\n\\n .kora-event-list {\\n display: flex;\\n flex-direction: column;\\n }\\n\\n .kora-row {\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n padding: 3px 6px;\\n border-radius: 3px;\\n cursor: pointer;\\n min-height: 24px;\\n }\\n\\n .kora-row:hover {\\n background: var(--bg-hover);\\n }\\n\\n .kora-row.expanded,\\n .kora-row.selected {\\n background: var(--bg-surface);\\n }\\n\\n .kora-row.compact {\\n padding: 2px 6px;\\n font-size: 11px;\\n }\\n\\n .kora-time {\\n color: var(--text-muted);\\n font-family: var(--font-mono);\\n font-size: 11px;\\n flex-shrink: 0;\\n width: 90px;\\n }\\n\\n .kora-dot {\\n width: 8px;\\n height: 8px;\\n border-radius: 50%;\\n flex-shrink: 0;\\n }\\n\\n .kora-type {\\n color: var(--text-secondary);\\n font-size: 11px;\\n flex-shrink: 0;\\n width: 140px;\\n }\\n\\n .kora-label {\\n color: var(--text-primary);\\n flex: 1;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n }\\n\\n .kora-deps {\\n color: var(--peach);\\n font-size: 10px;\\n flex-shrink: 0;\\n }\\n\\n /* ---- Detail / Expandable ---- */\\n\\n .kora-detail,\\n .kora-op-detail {\\n background: var(--bg-secondary);\\n border: 1px solid var(--border);\\n border-radius: 4px;\\n padding: 8px 12px;\\n margin: 2px 0 6px 28px;\\n font-size: 11px;\\n display: flex;\\n flex-direction: column;\\n gap: 4px;\\n }\\n\\n .kora-detail-label {\\n color: var(--text-muted);\\n font-weight: 500;\\n margin-right: 4px;\\n }\\n\\n .kora-mono {\\n font-family: var(--font-mono);\\n font-size: 11px;\\n }\\n\\n /* ---- Conflicts Table ---- */\\n\\n .kora-table {\\n width: 100%;\\n border-collapse: collapse;\\n font-size: 11px;\\n }\\n\\n .kora-table th {\\n text-align: left;\\n color: var(--text-muted);\\n font-weight: 500;\\n padding: 6px 8px;\\n border-bottom: 1px solid var(--border);\\n text-transform: uppercase;\\n font-size: 10px;\\n letter-spacing: 0.5px;\\n }\\n\\n .kora-table td {\\n padding: 5px 8px;\\n border-bottom: 1px solid var(--bg-surface);\\n }\\n\\n .kora-conflict-row {\\n cursor: pointer;\\n }\\n\\n .kora-conflict-row:hover td {\\n background: var(--bg-hover);\\n }\\n\\n .kora-conflict-row.expanded td {\\n background: var(--bg-surface);\\n }\\n\\n .kora-conflict-row.violated td {\\n border-left: 3px solid var(--red);\\n }\\n\\n .kora-strategy {\\n padding: 1px 6px;\\n border-radius: 3px;\\n font-size: 10px;\\n font-weight: 500;\\n }\\n\\n .kora-strategy.tier-1 {\\n background: rgba(166, 227, 161, 0.15);\\n color: var(--green);\\n }\\n\\n .kora-strategy.tier-2 {\\n background: rgba(249, 226, 175, 0.15);\\n color: var(--yellow);\\n }\\n\\n .kora-strategy.tier-3 {\\n background: rgba(203, 166, 247, 0.15);\\n color: var(--purple);\\n }\\n\\n .kora-detail-row td {\\n padding: 0;\\n }\\n\\n .kora-conflict-detail {\\n padding: 12px;\\n }\\n\\n .kora-comparison {\\n display: grid;\\n grid-template-columns: 1fr 1fr 1fr;\\n gap: 8px;\\n }\\n\\n .kora-value-box {\\n background: var(--bg-primary);\\n border: 1px solid var(--border);\\n border-radius: 4px;\\n padding: 8px;\\n }\\n\\n .kora-value-box.result {\\n border-color: var(--green);\\n }\\n\\n .kora-value-label {\\n color: var(--text-muted);\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.5px;\\n margin-bottom: 4px;\\n }\\n\\n .kora-value-code {\\n font-family: var(--font-mono);\\n font-size: 11px;\\n color: var(--text-primary);\\n white-space: pre-wrap;\\n word-break: break-all;\\n margin: 0;\\n }\\n\\n .kora-constraint-warning {\\n margin-top: 8px;\\n padding: 6px 10px;\\n background: rgba(243, 139, 168, 0.1);\\n border: 1px solid var(--red);\\n border-radius: 4px;\\n color: var(--red);\\n font-size: 11px;\\n }\\n\\n /* ---- Operations ---- */\\n\\n .kora-op-list {\\n display: flex;\\n flex-direction: column;\\n }\\n\\n .kora-op-row {\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n padding: 3px 6px;\\n border-radius: 3px;\\n cursor: pointer;\\n }\\n\\n .kora-op-row:hover {\\n background: var(--bg-hover);\\n }\\n\\n .kora-op-row.expanded {\\n background: var(--bg-surface);\\n }\\n\\n .kora-op-type {\\n padding: 1px 6px;\\n border-radius: 3px;\\n font-size: 10px;\\n font-weight: 600;\\n text-transform: uppercase;\\n flex-shrink: 0;\\n }\\n\\n .op-insert {\\n background: rgba(166, 227, 161, 0.15);\\n color: var(--green);\\n }\\n\\n .op-update {\\n background: rgba(137, 180, 250, 0.15);\\n color: var(--blue);\\n }\\n\\n .op-delete {\\n background: rgba(243, 139, 168, 0.15);\\n color: var(--red);\\n }\\n\\n .kora-op-collection {\\n color: var(--teal);\\n font-weight: 500;\\n flex-shrink: 0;\\n }\\n\\n .kora-op-record {\\n color: var(--text-secondary);\\n flex-shrink: 0;\\n }\\n\\n .kora-op-node {\\n color: var(--text-muted);\\n font-size: 10px;\\n flex-shrink: 0;\\n }\\n\\n .kora-op-seq {\\n color: var(--text-muted);\\n font-size: 10px;\\n flex-shrink: 0;\\n }\\n\\n /* ---- Network ---- */\\n\\n .kora-network-status {\\n padding: 12px 0;\\n }\\n\\n .kora-status-indicator {\\n display: flex;\\n align-items: center;\\n gap: 8px;\\n font-size: 14px;\\n font-weight: 600;\\n }\\n\\n .kora-status-dot {\\n width: 12px;\\n height: 12px;\\n border-radius: 50%;\\n }\\n\\n .connected .kora-status-dot {\\n background: var(--green);\\n box-shadow: 0 0 8px rgba(166, 227, 161, 0.5);\\n }\\n\\n .disconnected .kora-status-dot {\\n background: var(--red);\\n box-shadow: 0 0 8px rgba(243, 139, 168, 0.3);\\n }\\n\\n .connected .kora-status-text {\\n color: var(--green);\\n }\\n\\n .disconnected .kora-status-text {\\n color: var(--red);\\n }\\n\\n .kora-stats-grid {\\n display: grid;\\n grid-template-columns: repeat(4, 1fr);\\n gap: 8px;\\n margin: 12px 0;\\n }\\n\\n .kora-stat-card {\\n background: var(--bg-surface);\\n border: 1px solid var(--border);\\n border-radius: 6px;\\n padding: 12px;\\n text-align: center;\\n }\\n\\n .kora-stat-value {\\n font-size: 20px;\\n font-weight: 700;\\n color: var(--text-primary);\\n font-family: var(--font-mono);\\n }\\n\\n .kora-stat-label {\\n color: var(--text-muted);\\n font-size: 10px;\\n text-transform: uppercase;\\n letter-spacing: 0.5px;\\n margin-top: 2px;\\n }\\n\\n .kora-vv-section,\\n .kora-sync-log {\\n margin-top: 16px;\\n }\\n\\n .kora-vv-section h3,\\n .kora-sync-log h3 {\\n color: var(--text-secondary);\\n font-size: 12px;\\n font-weight: 600;\\n margin-bottom: 8px;\\n }\\n\\n .kora-vv-table {\\n max-width: 400px;\\n }\\n\\n /* ---- Scrollbar ---- */\\n\\n ::-webkit-scrollbar {\\n width: 8px;\\n }\\n\\n ::-webkit-scrollbar-track {\\n background: var(--bg-primary);\\n }\\n\\n ::-webkit-scrollbar-thumb {\\n background: var(--bg-hover);\\n border-radius: 4px;\\n }\\n\\n ::-webkit-scrollbar-thumb:hover {\\n background: var(--bg-selected);\\n }\"\n","import type { Instrumenter } from '../instrumenter/instrumenter'\nimport { renderDevtoolsPanel } from '../ui/panel'\nimport { KORA_DEVTOOLS_STYLES } from '../ui/panel-styles'\n\nconst OVERLAY_HOST_ID = 'kora-devtools-overlay-host'\n\n/**\n * Mount an in-page DevTools panel toggled with Ctrl+Shift+K (Cmd+Shift+K on macOS).\n *\n * @param instrumenter - Active instrumenter from createApp({ devtools: true })\n * @returns Teardown function (also called when overlay is destroyed)\n */\nexport function mountKoraDevtoolsOverlay(instrumenter: Instrumenter): () => void {\n\tif (typeof document === 'undefined') {\n\t\treturn () => {}\n\t}\n\n\tconst existing = document.getElementById(OVERLAY_HOST_ID)\n\tif (existing) {\n\t\texisting.remove()\n\t}\n\n\tconst host = document.createElement('div')\n\thost.id = OVERLAY_HOST_ID\n\thost.style.cssText =\n\t\t'position:fixed;inset:auto 12px 12px 12px;height:42vh;z-index:2147483646;display:none;'\n\n\tconst shadow = host.attachShadow({ mode: 'open' })\n\n\tconst style = document.createElement('style')\n\tstyle.textContent = KORA_DEVTOOLS_STYLES\n\tshadow.appendChild(style)\n\n\tconst panelRoot = document.createElement('div')\n\tpanelRoot.id = 'kora-devtools-root'\n\tpanelRoot.style.height = '100%'\n\tshadow.appendChild(panelRoot)\n\n\tconst hint = document.createElement('div')\n\thint.textContent = 'Kora DevTools (Ctrl+Shift+K)'\n\thint.style.cssText =\n\t\t'position:fixed;bottom:8px;right:12px;z-index:2147483645;font:11px sans-serif;color:#6c7086;background:#1e1e2e;padding:4px 8px;border-radius:4px;opacity:0.85;'\n\tdocument.body.appendChild(hint)\n\n\tdocument.body.appendChild(host)\n\n\tlet visible = false\n\tconst setVisible = (next: boolean): void => {\n\t\tvisible = next\n\t\thost.style.display = visible ? 'block' : 'none'\n\t\thint.style.display = visible ? 'none' : 'block'\n\t}\n\n\tconst refresh = (): void => {\n\t\tif (!visible) return\n\t\trenderDevtoolsPanel(panelRoot, instrumenter.getBuffer().getAll())\n\t}\n\n\tconst intervalId = window.setInterval(refresh, 300)\n\n\tconst onKeyDown = (event: KeyboardEvent): void => {\n\t\tconst isToggle = event.key === 'K' && event.shiftKey && (event.ctrlKey || event.metaKey)\n\t\tif (!isToggle) return\n\t\tevent.preventDefault()\n\t\tsetVisible(!visible)\n\t\tif (visible) {\n\t\t\trefresh()\n\t\t}\n\t}\n\n\twindow.addEventListener('keydown', onKeyDown)\n\n\treturn () => {\n\t\twindow.clearInterval(intervalId)\n\t\twindow.removeEventListener('keydown', onKeyDown)\n\t\thost.remove()\n\t\thint.remove()\n\t}\n}\n"],"mappings":"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","names":["h"]}
@@ -0,0 +1,170 @@
1
+ import { KoraEvent, KoraEventType, KoraEventEmitter } from '@korajs/core';
2
+
3
+ /** A KoraEvent wrapped with a reception timestamp and sequential ID */
4
+ interface TimestampedEvent {
5
+ /** Auto-incrementing sequential ID */
6
+ id: number;
7
+ /** The original framework event */
8
+ event: KoraEvent;
9
+ /** Date.now() when the instrumenter captured the event */
10
+ receivedAt: number;
11
+ }
12
+ /** Event categories for filtering and grouping */
13
+ type EventCategory = 'operation' | 'merge' | 'sync' | 'query' | 'connection';
14
+ /** Configuration for the Instrumenter */
15
+ interface DevtoolsConfig {
16
+ /** Max events in the ring buffer (default: 10000) */
17
+ bufferSize?: number;
18
+ /** Enable message bridge for DevTools panel communication (default: true) */
19
+ bridgeEnabled?: boolean;
20
+ /** Custom message channel name (default: 'kora-devtools') */
21
+ channelName?: string;
22
+ }
23
+ /** Filter criteria for querying events */
24
+ interface EventFilterCriteria {
25
+ /** Filter by event categories */
26
+ categories?: EventCategory[];
27
+ /** Filter by specific event types */
28
+ types?: KoraEventType[];
29
+ /** Filter by reception time range */
30
+ timeRange?: {
31
+ start: number;
32
+ end: number;
33
+ };
34
+ /** Filter by collection name (extracted from operation events) */
35
+ collection?: string;
36
+ }
37
+ /** Aggregated statistics computed from a set of events */
38
+ interface EventStatistics {
39
+ totalEvents: number;
40
+ eventsByCategory: Record<EventCategory, number>;
41
+ eventsByType: Partial<Record<KoraEventType, number>>;
42
+ mergeConflicts: number;
43
+ constraintViolations: number;
44
+ avgMergeDuration: number | null;
45
+ avgQueryDuration: number | null;
46
+ syncOperationsSent: number;
47
+ syncOperationsReceived: number;
48
+ }
49
+
50
+ /**
51
+ * Communicates between the page context and a DevTools panel via window.postMessage.
52
+ * All messages are namespaced with a `source` field to avoid collisions with other
53
+ * postMessage consumers on the page.
54
+ *
55
+ * Safe to instantiate in non-browser environments (SSR/Node) — all operations
56
+ * become no-ops when `window` is not available.
57
+ */
58
+ declare class MessageBridge {
59
+ private readonly channelName;
60
+ private readonly listeners;
61
+ private readonly messageHandler;
62
+ private destroyed;
63
+ constructor(channelName?: string);
64
+ /**
65
+ * Post a timestamped event through the bridge.
66
+ * No-op if window is not available or the bridge has been destroyed.
67
+ */
68
+ send(event: TimestampedEvent): void;
69
+ /**
70
+ * Register a callback for events received through the bridge.
71
+ * Returns an unsubscribe function.
72
+ */
73
+ onReceive(callback: (event: TimestampedEvent) => void): () => void;
74
+ /**
75
+ * Remove all listeners and detach from window.
76
+ * After calling destroy, all operations become no-ops.
77
+ */
78
+ destroy(): void;
79
+ }
80
+
81
+ /**
82
+ * Fixed-capacity ring buffer for storing timestamped events.
83
+ * When the buffer is full, the oldest events are evicted to make room for new ones.
84
+ * This prevents unbounded memory growth when events accumulate fast.
85
+ */
86
+ declare class EventBuffer {
87
+ private readonly _capacity;
88
+ private readonly buffer;
89
+ /** Index where the next event will be written */
90
+ private head;
91
+ /** Total number of events ever pushed (used to compute readable range) */
92
+ private _totalPushed;
93
+ constructor(capacity?: number);
94
+ /** Maximum number of events the buffer can hold */
95
+ get capacity(): number;
96
+ /** Current number of events in the buffer */
97
+ get size(): number;
98
+ /**
99
+ * Append an event to the buffer.
100
+ * If the buffer is at capacity, the oldest event is evicted.
101
+ */
102
+ push(event: TimestampedEvent): void;
103
+ /**
104
+ * Returns all events in insertion order (oldest first).
105
+ */
106
+ getAll(): readonly TimestampedEvent[];
107
+ /**
108
+ * Returns events whose sequential IDs fall within [start, end] (inclusive).
109
+ */
110
+ getRange(start: number, end: number): readonly TimestampedEvent[];
111
+ /**
112
+ * Returns events matching a specific KoraEventType.
113
+ */
114
+ getByType(type: KoraEventType): readonly TimestampedEvent[];
115
+ /** Remove all events from the buffer */
116
+ clear(): void;
117
+ }
118
+
119
+ /**
120
+ * Core orchestrator for Kora DevTools instrumentation.
121
+ *
122
+ * Attaches to a KoraEventEmitter, records all emitted events into a ring buffer
123
+ * with sequential IDs and reception timestamps, and optionally forwards them
124
+ * through a MessageBridge for consumption by a DevTools panel.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const instrumenter = new Instrumenter(app.emitter, { bufferSize: 5000 })
129
+ * const buffer = instrumenter.getBuffer()
130
+ * // ... later
131
+ * instrumenter.destroy()
132
+ * ```
133
+ */
134
+ declare class Instrumenter {
135
+ private readonly emitter;
136
+ private readonly buffer;
137
+ private readonly bridge;
138
+ private readonly unsubscribers;
139
+ private nextId;
140
+ private paused;
141
+ private destroyed;
142
+ constructor(emitter: KoraEventEmitter, config?: DevtoolsConfig);
143
+ /** Access the underlying event buffer */
144
+ getBuffer(): EventBuffer;
145
+ /** Access the message bridge, or null if bridge is disabled */
146
+ getBridge(): MessageBridge | null;
147
+ /** Temporarily stop recording events. Events emitted while paused are dropped. */
148
+ pause(): void;
149
+ /** Resume recording events after a pause. */
150
+ resume(): void;
151
+ /** Whether the instrumenter is currently paused */
152
+ isPaused(): boolean;
153
+ /**
154
+ * Detach all listeners from the emitter and destroy the bridge.
155
+ * After calling destroy, the instrumenter is inert.
156
+ */
157
+ destroy(): void;
158
+ private attachListeners;
159
+ private handleEvent;
160
+ }
161
+
162
+ /**
163
+ * Mount an in-page DevTools panel toggled with Ctrl+Shift+K (Cmd+Shift+K on macOS).
164
+ *
165
+ * @param instrumenter - Active instrumenter from createApp({ devtools: true })
166
+ * @returns Teardown function (also called when overlay is destroyed)
167
+ */
168
+ declare function mountKoraDevtoolsOverlay(instrumenter: Instrumenter): () => void;
169
+
170
+ export { type DevtoolsConfig as D, type EventFilterCriteria as E, Instrumenter as I, MessageBridge as M, type TimestampedEvent as T, type EventCategory as a, type EventStatistics as b, EventBuffer as c, mountKoraDevtoolsOverlay as m };
@@ -0,0 +1,170 @@
1
+ import { KoraEvent, KoraEventType, KoraEventEmitter } from '@korajs/core';
2
+
3
+ /** A KoraEvent wrapped with a reception timestamp and sequential ID */
4
+ interface TimestampedEvent {
5
+ /** Auto-incrementing sequential ID */
6
+ id: number;
7
+ /** The original framework event */
8
+ event: KoraEvent;
9
+ /** Date.now() when the instrumenter captured the event */
10
+ receivedAt: number;
11
+ }
12
+ /** Event categories for filtering and grouping */
13
+ type EventCategory = 'operation' | 'merge' | 'sync' | 'query' | 'connection';
14
+ /** Configuration for the Instrumenter */
15
+ interface DevtoolsConfig {
16
+ /** Max events in the ring buffer (default: 10000) */
17
+ bufferSize?: number;
18
+ /** Enable message bridge for DevTools panel communication (default: true) */
19
+ bridgeEnabled?: boolean;
20
+ /** Custom message channel name (default: 'kora-devtools') */
21
+ channelName?: string;
22
+ }
23
+ /** Filter criteria for querying events */
24
+ interface EventFilterCriteria {
25
+ /** Filter by event categories */
26
+ categories?: EventCategory[];
27
+ /** Filter by specific event types */
28
+ types?: KoraEventType[];
29
+ /** Filter by reception time range */
30
+ timeRange?: {
31
+ start: number;
32
+ end: number;
33
+ };
34
+ /** Filter by collection name (extracted from operation events) */
35
+ collection?: string;
36
+ }
37
+ /** Aggregated statistics computed from a set of events */
38
+ interface EventStatistics {
39
+ totalEvents: number;
40
+ eventsByCategory: Record<EventCategory, number>;
41
+ eventsByType: Partial<Record<KoraEventType, number>>;
42
+ mergeConflicts: number;
43
+ constraintViolations: number;
44
+ avgMergeDuration: number | null;
45
+ avgQueryDuration: number | null;
46
+ syncOperationsSent: number;
47
+ syncOperationsReceived: number;
48
+ }
49
+
50
+ /**
51
+ * Communicates between the page context and a DevTools panel via window.postMessage.
52
+ * All messages are namespaced with a `source` field to avoid collisions with other
53
+ * postMessage consumers on the page.
54
+ *
55
+ * Safe to instantiate in non-browser environments (SSR/Node) — all operations
56
+ * become no-ops when `window` is not available.
57
+ */
58
+ declare class MessageBridge {
59
+ private readonly channelName;
60
+ private readonly listeners;
61
+ private readonly messageHandler;
62
+ private destroyed;
63
+ constructor(channelName?: string);
64
+ /**
65
+ * Post a timestamped event through the bridge.
66
+ * No-op if window is not available or the bridge has been destroyed.
67
+ */
68
+ send(event: TimestampedEvent): void;
69
+ /**
70
+ * Register a callback for events received through the bridge.
71
+ * Returns an unsubscribe function.
72
+ */
73
+ onReceive(callback: (event: TimestampedEvent) => void): () => void;
74
+ /**
75
+ * Remove all listeners and detach from window.
76
+ * After calling destroy, all operations become no-ops.
77
+ */
78
+ destroy(): void;
79
+ }
80
+
81
+ /**
82
+ * Fixed-capacity ring buffer for storing timestamped events.
83
+ * When the buffer is full, the oldest events are evicted to make room for new ones.
84
+ * This prevents unbounded memory growth when events accumulate fast.
85
+ */
86
+ declare class EventBuffer {
87
+ private readonly _capacity;
88
+ private readonly buffer;
89
+ /** Index where the next event will be written */
90
+ private head;
91
+ /** Total number of events ever pushed (used to compute readable range) */
92
+ private _totalPushed;
93
+ constructor(capacity?: number);
94
+ /** Maximum number of events the buffer can hold */
95
+ get capacity(): number;
96
+ /** Current number of events in the buffer */
97
+ get size(): number;
98
+ /**
99
+ * Append an event to the buffer.
100
+ * If the buffer is at capacity, the oldest event is evicted.
101
+ */
102
+ push(event: TimestampedEvent): void;
103
+ /**
104
+ * Returns all events in insertion order (oldest first).
105
+ */
106
+ getAll(): readonly TimestampedEvent[];
107
+ /**
108
+ * Returns events whose sequential IDs fall within [start, end] (inclusive).
109
+ */
110
+ getRange(start: number, end: number): readonly TimestampedEvent[];
111
+ /**
112
+ * Returns events matching a specific KoraEventType.
113
+ */
114
+ getByType(type: KoraEventType): readonly TimestampedEvent[];
115
+ /** Remove all events from the buffer */
116
+ clear(): void;
117
+ }
118
+
119
+ /**
120
+ * Core orchestrator for Kora DevTools instrumentation.
121
+ *
122
+ * Attaches to a KoraEventEmitter, records all emitted events into a ring buffer
123
+ * with sequential IDs and reception timestamps, and optionally forwards them
124
+ * through a MessageBridge for consumption by a DevTools panel.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const instrumenter = new Instrumenter(app.emitter, { bufferSize: 5000 })
129
+ * const buffer = instrumenter.getBuffer()
130
+ * // ... later
131
+ * instrumenter.destroy()
132
+ * ```
133
+ */
134
+ declare class Instrumenter {
135
+ private readonly emitter;
136
+ private readonly buffer;
137
+ private readonly bridge;
138
+ private readonly unsubscribers;
139
+ private nextId;
140
+ private paused;
141
+ private destroyed;
142
+ constructor(emitter: KoraEventEmitter, config?: DevtoolsConfig);
143
+ /** Access the underlying event buffer */
144
+ getBuffer(): EventBuffer;
145
+ /** Access the message bridge, or null if bridge is disabled */
146
+ getBridge(): MessageBridge | null;
147
+ /** Temporarily stop recording events. Events emitted while paused are dropped. */
148
+ pause(): void;
149
+ /** Resume recording events after a pause. */
150
+ resume(): void;
151
+ /** Whether the instrumenter is currently paused */
152
+ isPaused(): boolean;
153
+ /**
154
+ * Detach all listeners from the emitter and destroy the bridge.
155
+ * After calling destroy, the instrumenter is inert.
156
+ */
157
+ destroy(): void;
158
+ private attachListeners;
159
+ private handleEvent;
160
+ }
161
+
162
+ /**
163
+ * Mount an in-page DevTools panel toggled with Ctrl+Shift+K (Cmd+Shift+K on macOS).
164
+ *
165
+ * @param instrumenter - Active instrumenter from createApp({ devtools: true })
166
+ * @returns Teardown function (also called when overlay is destroyed)
167
+ */
168
+ declare function mountKoraDevtoolsOverlay(instrumenter: Instrumenter): () => void;
169
+
170
+ export { type DevtoolsConfig as D, type EventFilterCriteria as E, Instrumenter as I, MessageBridge as M, type TimestampedEvent as T, type EventCategory as a, type EventStatistics as b, EventBuffer as c, mountKoraDevtoolsOverlay as m };
@@ -430,6 +430,8 @@ var EVENT_TYPE_CATEGORIES = {
430
430
  "sync:connected": "sync",
431
431
  "sync:disconnected": "sync",
432
432
  "sync:auth-failed": "sync",
433
+ "sync:schema-mismatch": "sync",
434
+ "sync:apply-failed": "sync",
433
435
  "sync:sent": "sync",
434
436
  "sync:received": "sync",
435
437
  "sync:acknowledged": "sync",
@@ -442,7 +444,10 @@ var EVENT_TYPE_CATEGORIES = {
442
444
  "sync:initial-sync-progress": "sync",
443
445
  "awareness:updated": "sync",
444
446
  "state-machine:transition": "operation",
445
- "state-machine:rejected": "operation"
447
+ "state-machine:rejected": "operation",
448
+ "store:persistence-error": "connection",
449
+ "store:quota-exceeded": "connection",
450
+ "replay:completed": "operation"
446
451
  };
447
452
  function eventTypeToCategory(type) {
448
453
  return EVENT_TYPE_CATEGORIES[type];
@@ -707,6 +712,10 @@ function timelineLabel(event) {
707
712
  return "sync disconnected";
708
713
  case "sync:auth-failed":
709
714
  return "sync auth failed";
715
+ case "sync:schema-mismatch":
716
+ return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
717
+ case "sync:apply-failed":
718
+ return `sync apply failed ${event.operationId}`;
710
719
  case "sync:sent":
711
720
  return `sync sent ${event.batchSize}`;
712
721
  case "sync:received":
@@ -733,6 +742,12 @@ function timelineLabel(event) {
733
742
  return `transition ${event.collection} ${event.from} \u2192 ${event.to}`;
734
743
  case "state-machine:rejected":
735
744
  return `transition rejected ${event.collection} ${event.from} \u2192 ${event.to}`;
745
+ case "store:persistence-error":
746
+ return `store persistence error ${event.message}`;
747
+ case "store:quota-exceeded":
748
+ return "store quota exceeded";
749
+ case "replay:completed":
750
+ return `replay ${event.operationsApplied} ops`;
736
751
  }
737
752
  }
738
753
  function timelineColor(type) {