@korajs/devtools 1.0.0-beta.11 → 1.0.0-beta.13

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 CHANGED
@@ -4,55 +4,46 @@ Browser DevTools extension for Kora.js. Inspect operations, trace conflict resol
4
4
 
5
5
  ## Install
6
6
 
7
+ `korajs` already depends on `@korajs/devtools`; apps only turn it on. Install it directly to use
8
+ the building blocks (`Instrumenter`, `EventBuffer`, the panel renderer) in your own tooling:
9
+
7
10
  ```bash
8
- pnpm add -D @korajs/devtools
11
+ pnpm add -D @korajs/devtools@beta
9
12
  ```
10
13
 
11
14
  ## Enable
12
15
 
13
- Add `devtools: true` to your app config:
14
-
16
+ <!-- docs-check: standalone -->
15
17
  ```typescript
16
- import { createApp } from 'korajs'
18
+ import { createApp, defineSchema, t } from 'korajs'
19
+
20
+ const schema = defineSchema({ version: 1, collections: { todos: { fields: { title: t.string() } } } })
17
21
 
18
22
  const app = createApp({
19
23
  schema,
20
- devtools: process.env.NODE_ENV === 'development',
24
+ devtools: import.meta.env.DEV,
21
25
  })
22
26
  ```
23
27
 
24
- When enabled, the Kora DevTools panel appears in your browser's developer tools.
25
-
26
- ## Panels
27
-
28
- ### Sync Timeline
29
- Horizontal timeline of operations and sync events, color-coded by type. Click any event to inspect it. Causal arrows show dependencies between operations.
28
+ With `devtools: true`, Kora records instrumentation events and:
30
29
 
31
- ### Conflict Inspector
32
- Table of all merge events, filterable by collection, tier, and strategy. Expand any row to see the full `MergeTrace` -- input values, base value, resolved output, and which tier handled it.
30
+ - mounts an in-page overlay, toggled with `Ctrl+Shift+K` (`Cmd+Shift+K` on macOS);
31
+ - forwards events to the browser extension. The extension is not published to a store yet: build
32
+ it with `pnpm --filter @korajs/devtools build` in the Kora repository and load
33
+ `packages/devtools/dist/extension` as an unpacked extension in a Chromium browser.
33
34
 
34
- ### Operation Log
35
- Searchable list of all operations. Click an operation to view its full payload, causal dependencies, and the database state at that point in time.
36
-
37
- ### Network Status
38
- Real-time connection quality indicator, pending operation count, bandwidth graph, and last sync timestamp.
39
-
40
- ## Instrumentation Events
41
-
42
- The DevTools extension listens for events emitted by the Kora runtime:
43
-
44
- - `operation:created` / `operation:applied`
45
- - `merge:started` / `merge:completed` / `merge:conflict`
46
- - `sync:connected` / `sync:disconnected` / `sync:sent` / `sync:received`
47
- - `query:subscribed` / `query:invalidated` / `query:executed`
48
- - `connection:quality`
35
+ ## Panels
49
36
 
50
- ## Keyboard Shortcut
37
+ - **Timeline**: recorded events in order, color-coded by type.
38
+ - **Conflicts**: every merge conflict with its strategy, tier, inputs and result.
39
+ - **Operations**: operations created on this device and applied from sync, with payload, node,
40
+ sequence number and causal dependencies.
41
+ - **Network**: connection state and quality, operations sent and received, last sync, and a
42
+ version vector derived from the recorded operations.
51
43
 
52
- In development mode, press `Ctrl+Shift+K` (or `Cmd+Shift+K` on macOS) to toggle the embedded DevTools overlay.
44
+ The panels show events recorded since the page loaded. The complete event catalog is in the
45
+ [DevTools API reference](https://korajs.dev/api/devtools#events).
53
46
 
54
47
  ## License
55
48
 
56
49
  MIT
57
-
58
- See the [full documentation](https://github.com/ehoneahobed/kora) for guides, API reference, and examples.
@@ -16,8 +16,14 @@ var EVENT_TYPE_CATEGORIES = {
16
16
  "sync:auth-failed": "sync",
17
17
  "sync:suspended": "sync",
18
18
  "sync:clock-skew": "sync",
19
+ "encryption:status": "sync",
20
+ "sync:node-id-rotated": "sync",
21
+ "sync:local-node": "sync",
19
22
  "sync:clock-rebase": "sync",
20
23
  "sync:schema-mismatch": "sync",
24
+ "sync:protocol-deprecated": "sync",
25
+ "sync:unverified-legacy-operation": "sync",
26
+ "sync:forged-duplicate": "sync",
21
27
  "sync:apply-failed": "sync",
22
28
  "sync:apply-blocked": "sync",
23
29
  "sync:apply-retrying": "sync",
@@ -27,23 +33,34 @@ var EVENT_TYPE_CATEGORIES = {
27
33
  "sync:sent": "sync",
28
34
  "sync:received": "sync",
29
35
  "sync:acknowledged": "sync",
36
+ "sync:scope-retracted": "sync",
30
37
  "query:subscribed": "query",
31
38
  "query:invalidated": "query",
32
39
  "query:executed": "query",
40
+ "query:error": "query",
41
+ "storage:persistence": "connection",
33
42
  "connection:quality": "connection",
34
43
  "sync:diagnostics": "sync",
35
44
  "sync:bandwidth": "sync",
36
45
  "sync:initial-sync-progress": "sync",
37
46
  "sync:delivery-gap": "sync",
38
47
  "sync:delivery-stalled": "sync",
48
+ "sync:durability-degraded": "connection",
49
+ "sync:durability-restored": "connection",
39
50
  "awareness:updated": "sync",
40
51
  "state-machine:transition": "operation",
41
52
  "state-machine:rejected": "operation",
42
53
  "store:persistence-error": "connection",
43
54
  "store:quota-exceeded": "connection",
55
+ "store:log-integrity": "operation",
56
+ "store:rematerialized": "operation",
44
57
  "store:storage-fallback": "connection",
45
58
  "store:opfs-unavailable": "connection",
59
+ "store:durability-lost": "connection",
46
60
  "store:db-name-collision": "connection",
61
+ "store:storage-blocked": "connection",
62
+ "store:storage-migrated": "connection",
63
+ "store:schema-ahead": "connection",
47
64
  "replay:completed": "operation"
48
65
  };
49
66
  function eventTypeToCategory(type) {
@@ -314,12 +331,24 @@ function timelineLabel(event) {
314
331
  return "sync auth failed";
315
332
  case "sync:suspended":
316
333
  return `sync suspended (${event.reason})`;
334
+ case "encryption:status":
335
+ return `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ""}`;
317
336
  case "sync:clock-skew":
318
337
  return `clock skew ${Math.round(event.skewMs / 1e3)}s (${event.severity})`;
338
+ case "sync:node-id-rotated":
339
+ return `node id rotated (${event.reenqueuedCount} ops re-queued)`;
340
+ case "sync:local-node":
341
+ return `local node ${event.nodeId} ${event.action}`;
319
342
  case "sync:clock-rebase":
320
343
  return `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1e3)}s ahead)`;
321
344
  case "sync:schema-mismatch":
322
345
  return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
346
+ case "sync:protocol-deprecated":
347
+ return `protocol v${event.clientProtocolVersion} deprecated (node ${event.nodeId})`;
348
+ case "sync:unverified-legacy-operation":
349
+ return `unverified legacy op ${event.operationId} (node ${event.nodeId})`;
350
+ case "sync:forged-duplicate":
351
+ return `forged duplicate refused ${event.operationId} (node ${event.nodeId})`;
323
352
  case "sync:apply-failed":
324
353
  return `sync apply failed ${event.operationId}`;
325
354
  case "sync:apply-blocked":
@@ -338,12 +367,18 @@ function timelineLabel(event) {
338
367
  return `sync received ${event.batchSize}`;
339
368
  case "sync:acknowledged":
340
369
  return `sync ack ${event.sequenceNumber}`;
370
+ case "sync:scope-retracted":
371
+ return `scope retracted ${event.collection}/${event.recordId}`;
341
372
  case "query:subscribed":
342
373
  return `query subscribed ${event.collection}`;
343
374
  case "query:invalidated":
344
375
  return `query invalidated ${event.queryId}`;
345
376
  case "query:executed":
346
377
  return `query executed ${event.queryId}`;
378
+ case "query:error":
379
+ return `query error ${event.collection} (${event.phase}): ${event.message}`;
380
+ case "storage:persistence":
381
+ return `storage persistence ${event.state}: ${event.persisted ? "persisted" : "best-effort"}`;
347
382
  case "connection:quality":
348
383
  return `connection ${event.quality}`;
349
384
  case "sync:diagnostics":
@@ -355,7 +390,11 @@ function timelineLabel(event) {
355
390
  case "sync:delivery-gap":
356
391
  return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
357
392
  case "sync:delivery-stalled":
358
- return `delivery stalled at ${event.watermark} (${event.repeatCount} repeats)`;
393
+ return `delivery unacknowledged ${event.watermark} \u2192 ${event.outstandingMaxDeliverySequence} (${event.repeatCount} repeats)`;
394
+ case "sync:durability-degraded":
395
+ return `local durability degraded after ${event.failedAttempts} failed barriers: ${event.message}`;
396
+ case "sync:durability-restored":
397
+ return "local durability restored";
359
398
  case "awareness:updated":
360
399
  return `awareness ${event.states.size} peers`;
361
400
  case "state-machine:transition":
@@ -366,12 +405,24 @@ function timelineLabel(event) {
366
405
  return `store persistence error ${event.message}`;
367
406
  case "store:quota-exceeded":
368
407
  return "store quota exceeded";
408
+ case "store:log-integrity":
409
+ return `log integrity: ${event.repaired} repaired, ${event.quarantined} quarantined`;
410
+ case "store:rematerialized":
411
+ return `re-materialized ${event.records} records (${event.mode}), ${event.changedRows} changed`;
369
412
  case "store:storage-fallback":
370
413
  return `store fallback ${event.from} \u2192 ${event.to}`;
371
414
  case "store:opfs-unavailable":
372
415
  return `store OPFS unavailable (${event.reason}), running in memory`;
416
+ case "store:durability-lost":
417
+ return `store durability lost (${event.reason} during ${event.phase}), writes refused`;
373
418
  case "store:db-name-collision":
374
419
  return `store db-name collision ${event.dbName}`;
420
+ case "store:storage-blocked":
421
+ return `store ${event.resource} ${event.state === "waiting" ? "blocked by another holder" : "unblocked"}`;
422
+ case "store:storage-migrated":
423
+ return `store migrated ${event.from} \u2192 ${event.to}`;
424
+ case "store:schema-ahead":
425
+ return `store refused: database at schema v${event.storedVersion}, code at v${event.codeVersion}`;
375
426
  case "replay:completed":
376
427
  return `replay ${event.operationsApplied} ops`;
377
428
  }
@@ -945,4 +996,4 @@ export {
945
996
  renderDevtoolsPanel,
946
997
  mountKoraDevtoolsOverlay
947
998
  };
948
- //# sourceMappingURL=chunk-ZWXYLFF7.js.map
999
+ //# sourceMappingURL=chunk-LQFL4TWM.js.map
@@ -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:suspended': 'sync',\n\t'sync:clock-skew': 'sync',\n\t'encryption:status': 'sync',\n\t'sync:node-id-rotated': 'sync',\n\t'sync:local-node': 'sync',\n\t'sync:clock-rebase': 'sync',\n\t'sync:schema-mismatch': 'sync',\n\t'sync:protocol-deprecated': 'sync',\n\t'sync:unverified-legacy-operation': 'sync',\n\t'sync:forged-duplicate': 'sync',\n\t'sync:apply-failed': 'sync',\n\t'sync:apply-blocked': 'sync',\n\t'sync:apply-retrying': 'sync',\n\t'sync:apply-recovered': 'sync',\n\t'sync:apply-abandoned': 'sync',\n\t'sync:operation-rejected': 'sync',\n\t'sync:sent': 'sync',\n\t'sync:received': 'sync',\n\t'sync:acknowledged': 'sync',\n\t'sync:scope-retracted': 'sync',\n\t'query:subscribed': 'query',\n\t'query:invalidated': 'query',\n\t'query:executed': 'query',\n\t'query:error': 'query',\n\t'storage:persistence': 'connection',\n\t'connection:quality': 'connection',\n\t'sync:diagnostics': 'sync',\n\t'sync:bandwidth': 'sync',\n\t'sync:initial-sync-progress': 'sync',\n\t'sync:delivery-gap': 'sync',\n\t'sync:delivery-stalled': 'sync',\n\t'sync:durability-degraded': 'connection',\n\t'sync:durability-restored': 'connection',\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'store:log-integrity': 'operation',\n\t'store:rematerialized': 'operation',\n\t'store:storage-fallback': 'connection',\n\t'store:opfs-unavailable': 'connection',\n\t'store:durability-lost': 'connection',\n\t'store:db-name-collision': 'connection',\n\t'store:storage-blocked': 'connection',\n\t'store:storage-migrated': 'connection',\n\t'store:schema-ahead': '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:suspended':\n\t\t\treturn `sync suspended (${event.reason})`\n\t\tcase 'encryption:status':\n\t\t\treturn `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ''}`\n\t\tcase 'sync:clock-skew':\n\t\t\treturn `clock skew ${Math.round(event.skewMs / 1000)}s (${event.severity})`\n\t\tcase 'sync:node-id-rotated':\n\t\t\treturn `node id rotated (${event.reenqueuedCount} ops re-queued)`\n\t\tcase 'sync:local-node':\n\t\t\treturn `local node ${event.nodeId} ${event.action}`\n\t\tcase 'sync:clock-rebase':\n\t\t\treturn `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1000)}s ahead)`\n\t\tcase 'sync:schema-mismatch':\n\t\t\treturn `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`\n\t\tcase 'sync:protocol-deprecated':\n\t\t\treturn `protocol v${event.clientProtocolVersion} deprecated (node ${event.nodeId})`\n\t\tcase 'sync:unverified-legacy-operation':\n\t\t\treturn `unverified legacy op ${event.operationId} (node ${event.nodeId})`\n\t\tcase 'sync:forged-duplicate':\n\t\t\treturn `forged duplicate refused ${event.operationId} (node ${event.nodeId})`\n\t\tcase 'sync:apply-failed':\n\t\t\treturn `sync apply failed ${event.operationId}`\n\t\tcase 'sync:apply-blocked':\n\t\t\treturn `sync apply blocked ${event.failure.collection}/${event.failure.recordId}`\n\t\tcase 'sync:apply-retrying':\n\t\t\treturn `sync apply retry ${event.failure.collection}/${event.failure.recordId}`\n\t\tcase 'sync:apply-recovered':\n\t\t\treturn `sync apply recovered ${event.failure.collection}/${event.failure.recordId}`\n\t\tcase 'sync:apply-abandoned':\n\t\t\treturn `sync apply abandoned ${event.failure.collection}/${event.failure.recordId}`\n\t\tcase 'sync:operation-rejected':\n\t\t\treturn `sync op rejected ${event.collection}/${event.recordId} (${event.code})`\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 'sync:scope-retracted':\n\t\t\treturn `scope retracted ${event.collection}/${event.recordId}`\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 'query:error':\n\t\t\treturn `query error ${event.collection} (${event.phase}): ${event.message}`\n\t\tcase 'storage:persistence':\n\t\t\treturn `storage persistence ${event.state}: ${event.persisted ? 'persisted' : 'best-effort'}`\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 'sync:delivery-gap':\n\t\t\treturn `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`\n\t\tcase 'sync:delivery-stalled':\n\t\t\treturn `delivery unacknowledged ${event.watermark} → ${event.outstandingMaxDeliverySequence} (${event.repeatCount} repeats)`\n\t\tcase 'sync:durability-degraded':\n\t\t\treturn `local durability degraded after ${event.failedAttempts} failed barriers: ${event.message}`\n\t\tcase 'sync:durability-restored':\n\t\t\treturn 'local durability restored'\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 'store:log-integrity':\n\t\t\treturn `log integrity: ${event.repaired} repaired, ${event.quarantined} quarantined`\n\t\tcase 'store:rematerialized':\n\t\t\treturn `re-materialized ${event.records} records (${event.mode}), ${event.changedRows} changed`\n\t\tcase 'store:storage-fallback':\n\t\t\treturn `store fallback ${event.from} → ${event.to}`\n\t\tcase 'store:opfs-unavailable':\n\t\t\treturn `store OPFS unavailable (${event.reason}), running in memory`\n\t\tcase 'store:durability-lost':\n\t\t\treturn `store durability lost (${event.reason} during ${event.phase}), writes refused`\n\t\tcase 'store:db-name-collision':\n\t\t\treturn `store db-name collision ${event.dbName}`\n\t\tcase 'store:storage-blocked':\n\t\t\treturn `store ${event.resource} ${event.state === 'waiting' ? 'blocked by another holder' : 'unblocked'}`\n\t\tcase 'store:storage-migrated':\n\t\t\treturn `store migrated ${event.from} → ${event.to}`\n\t\tcase 'store:schema-ahead':\n\t\t\treturn `store refused: database at schema v${event.storedVersion}, code at v${event.codeVersion}`\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 :host,\\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 :host,\\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: 100%;\\n height: 100%;\\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\t\t:host {\n\t\t\tcolor-scheme: dark;\n\t\t\tbackground: #1e1e2e;\n\t\t\tcolor: #cdd6f4;\n\t\t\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n\t\t\toverflow: hidden;\n\t\t\tborder: 1px solid #45475a;\n\t\t\tbox-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);\n\t\t}\n\t\t.kora-overlay-close {\n\t\t\tposition: absolute;\n\t\t\ttop: 6px;\n\t\t\tright: 8px;\n\t\t\tz-index: 1;\n\t\t\tbackground: #313244;\n\t\t\tcolor: #cdd6f4;\n\t\t\tborder: 1px solid #45475a;\n\t\t\tborder-radius: 4px;\n\t\t\tcursor: pointer;\n\t\t\tfont: 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n\t\t\tline-height: 1;\n\t\t\tpadding: 4px 8px;\n\t\t}\n\t\t.kora-overlay-close:hover {\n\t\t\tbackground: #45475a;\n\t\t}\n\t`\n\tshadow.appendChild(style)\n\n\tconst closeButton = document.createElement('button')\n\tcloseButton.type = 'button'\n\tcloseButton.className = 'kora-overlay-close'\n\tcloseButton.textContent = 'Close'\n\tcloseButton.setAttribute('aria-label', 'Close Kora DevTools')\n\tshadow.appendChild(closeButton)\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.textContent = visible ? 'Hide Kora DevTools (Esc)' : 'Kora DevTools (Ctrl+Shift+K)'\n\t\thint.style.display = '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) {\n\t\t\tevent.preventDefault()\n\t\t\tsetVisible(!visible)\n\t\t\tif (visible) {\n\t\t\t\trefresh()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif (event.key === 'Escape' && visible) {\n\t\t\tevent.preventDefault()\n\t\t\tsetVisible(false)\n\t\t}\n\t}\n\n\tconst onHintClick = (): void => {\n\t\tsetVisible(!visible)\n\t\tif (visible) {\n\t\t\trefresh()\n\t\t}\n\t}\n\n\tconst onCloseClick = (): void => {\n\t\tsetVisible(false)\n\t}\n\n\twindow.addEventListener('keydown', onKeyDown)\n\thint.addEventListener('click', onHintClick)\n\tcloseButton.addEventListener('click', onCloseClick)\n\n\treturn () => {\n\t\twindow.clearInterval(intervalId)\n\t\twindow.removeEventListener('keydown', onKeyDown)\n\t\thint.removeEventListener('click', onHintClick)\n\t\tcloseButton.removeEventListener('click', onCloseClick)\n\t\thost.remove()\n\t\thint.remove()\n\t}\n}\n"],"mappings":"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","names":["h"]}
@@ -432,8 +432,14 @@ var EVENT_TYPE_CATEGORIES = {
432
432
  "sync:auth-failed": "sync",
433
433
  "sync:suspended": "sync",
434
434
  "sync:clock-skew": "sync",
435
+ "encryption:status": "sync",
436
+ "sync:node-id-rotated": "sync",
437
+ "sync:local-node": "sync",
435
438
  "sync:clock-rebase": "sync",
436
439
  "sync:schema-mismatch": "sync",
440
+ "sync:protocol-deprecated": "sync",
441
+ "sync:unverified-legacy-operation": "sync",
442
+ "sync:forged-duplicate": "sync",
437
443
  "sync:apply-failed": "sync",
438
444
  "sync:apply-blocked": "sync",
439
445
  "sync:apply-retrying": "sync",
@@ -443,23 +449,34 @@ var EVENT_TYPE_CATEGORIES = {
443
449
  "sync:sent": "sync",
444
450
  "sync:received": "sync",
445
451
  "sync:acknowledged": "sync",
452
+ "sync:scope-retracted": "sync",
446
453
  "query:subscribed": "query",
447
454
  "query:invalidated": "query",
448
455
  "query:executed": "query",
456
+ "query:error": "query",
457
+ "storage:persistence": "connection",
449
458
  "connection:quality": "connection",
450
459
  "sync:diagnostics": "sync",
451
460
  "sync:bandwidth": "sync",
452
461
  "sync:initial-sync-progress": "sync",
453
462
  "sync:delivery-gap": "sync",
454
463
  "sync:delivery-stalled": "sync",
464
+ "sync:durability-degraded": "connection",
465
+ "sync:durability-restored": "connection",
455
466
  "awareness:updated": "sync",
456
467
  "state-machine:transition": "operation",
457
468
  "state-machine:rejected": "operation",
458
469
  "store:persistence-error": "connection",
459
470
  "store:quota-exceeded": "connection",
471
+ "store:log-integrity": "operation",
472
+ "store:rematerialized": "operation",
460
473
  "store:storage-fallback": "connection",
461
474
  "store:opfs-unavailable": "connection",
475
+ "store:durability-lost": "connection",
462
476
  "store:db-name-collision": "connection",
477
+ "store:storage-blocked": "connection",
478
+ "store:storage-migrated": "connection",
479
+ "store:schema-ahead": "connection",
463
480
  "replay:completed": "operation"
464
481
  };
465
482
  function eventTypeToCategory(type) {
@@ -727,12 +744,24 @@ function timelineLabel(event) {
727
744
  return "sync auth failed";
728
745
  case "sync:suspended":
729
746
  return `sync suspended (${event.reason})`;
747
+ case "encryption:status":
748
+ return `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ""}`;
730
749
  case "sync:clock-skew":
731
750
  return `clock skew ${Math.round(event.skewMs / 1e3)}s (${event.severity})`;
751
+ case "sync:node-id-rotated":
752
+ return `node id rotated (${event.reenqueuedCount} ops re-queued)`;
753
+ case "sync:local-node":
754
+ return `local node ${event.nodeId} ${event.action}`;
732
755
  case "sync:clock-rebase":
733
756
  return `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1e3)}s ahead)`;
734
757
  case "sync:schema-mismatch":
735
758
  return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
759
+ case "sync:protocol-deprecated":
760
+ return `protocol v${event.clientProtocolVersion} deprecated (node ${event.nodeId})`;
761
+ case "sync:unverified-legacy-operation":
762
+ return `unverified legacy op ${event.operationId} (node ${event.nodeId})`;
763
+ case "sync:forged-duplicate":
764
+ return `forged duplicate refused ${event.operationId} (node ${event.nodeId})`;
736
765
  case "sync:apply-failed":
737
766
  return `sync apply failed ${event.operationId}`;
738
767
  case "sync:apply-blocked":
@@ -751,12 +780,18 @@ function timelineLabel(event) {
751
780
  return `sync received ${event.batchSize}`;
752
781
  case "sync:acknowledged":
753
782
  return `sync ack ${event.sequenceNumber}`;
783
+ case "sync:scope-retracted":
784
+ return `scope retracted ${event.collection}/${event.recordId}`;
754
785
  case "query:subscribed":
755
786
  return `query subscribed ${event.collection}`;
756
787
  case "query:invalidated":
757
788
  return `query invalidated ${event.queryId}`;
758
789
  case "query:executed":
759
790
  return `query executed ${event.queryId}`;
791
+ case "query:error":
792
+ return `query error ${event.collection} (${event.phase}): ${event.message}`;
793
+ case "storage:persistence":
794
+ return `storage persistence ${event.state}: ${event.persisted ? "persisted" : "best-effort"}`;
760
795
  case "connection:quality":
761
796
  return `connection ${event.quality}`;
762
797
  case "sync:diagnostics":
@@ -768,7 +803,11 @@ function timelineLabel(event) {
768
803
  case "sync:delivery-gap":
769
804
  return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
770
805
  case "sync:delivery-stalled":
771
- return `delivery stalled at ${event.watermark} (${event.repeatCount} repeats)`;
806
+ return `delivery unacknowledged ${event.watermark} \u2192 ${event.outstandingMaxDeliverySequence} (${event.repeatCount} repeats)`;
807
+ case "sync:durability-degraded":
808
+ return `local durability degraded after ${event.failedAttempts} failed barriers: ${event.message}`;
809
+ case "sync:durability-restored":
810
+ return "local durability restored";
772
811
  case "awareness:updated":
773
812
  return `awareness ${event.states.size} peers`;
774
813
  case "state-machine:transition":
@@ -779,12 +818,24 @@ function timelineLabel(event) {
779
818
  return `store persistence error ${event.message}`;
780
819
  case "store:quota-exceeded":
781
820
  return "store quota exceeded";
821
+ case "store:log-integrity":
822
+ return `log integrity: ${event.repaired} repaired, ${event.quarantined} quarantined`;
823
+ case "store:rematerialized":
824
+ return `re-materialized ${event.records} records (${event.mode}), ${event.changedRows} changed`;
782
825
  case "store:storage-fallback":
783
826
  return `store fallback ${event.from} \u2192 ${event.to}`;
784
827
  case "store:opfs-unavailable":
785
828
  return `store OPFS unavailable (${event.reason}), running in memory`;
829
+ case "store:durability-lost":
830
+ return `store durability lost (${event.reason} during ${event.phase}), writes refused`;
786
831
  case "store:db-name-collision":
787
832
  return `store db-name collision ${event.dbName}`;
833
+ case "store:storage-blocked":
834
+ return `store ${event.resource} ${event.state === "waiting" ? "blocked by another holder" : "unblocked"}`;
835
+ case "store:storage-migrated":
836
+ return `store migrated ${event.from} \u2192 ${event.to}`;
837
+ case "store:schema-ahead":
838
+ return `store refused: database at schema v${event.storedVersion}, code at v${event.codeVersion}`;
788
839
  case "replay:completed":
789
840
  return `replay ${event.operationsApplied} ops`;
790
841
  }