@korajs/devtools 1.0.0-beta.12 → 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 +23 -32
- package/dist/{chunk-JTHYT3EM.js → chunk-LQFL4TWM.js} +49 -1
- package/dist/chunk-LQFL4TWM.js.map +1 -0
- package/dist/extension/panel.cjs +48 -0
- package/dist/extension/panel.cjs.map +1 -1
- package/dist/extension/panel.js +48 -0
- package/dist/extension/panel.js.map +1 -1
- package/dist/index.cjs +58 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay.cjs +48 -0
- package/dist/overlay.cjs.map +1 -1
- package/dist/overlay.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-JTHYT3EM.js.map +0 -1
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
|
-
|
|
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:
|
|
24
|
+
devtools: import.meta.env.DEV,
|
|
21
25
|
})
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
@@ -31,20 +37,30 @@ var EVENT_TYPE_CATEGORIES = {
|
|
|
31
37
|
"query:subscribed": "query",
|
|
32
38
|
"query:invalidated": "query",
|
|
33
39
|
"query:executed": "query",
|
|
40
|
+
"query:error": "query",
|
|
41
|
+
"storage:persistence": "connection",
|
|
34
42
|
"connection:quality": "connection",
|
|
35
43
|
"sync:diagnostics": "sync",
|
|
36
44
|
"sync:bandwidth": "sync",
|
|
37
45
|
"sync:initial-sync-progress": "sync",
|
|
38
46
|
"sync:delivery-gap": "sync",
|
|
39
47
|
"sync:delivery-stalled": "sync",
|
|
48
|
+
"sync:durability-degraded": "connection",
|
|
49
|
+
"sync:durability-restored": "connection",
|
|
40
50
|
"awareness:updated": "sync",
|
|
41
51
|
"state-machine:transition": "operation",
|
|
42
52
|
"state-machine:rejected": "operation",
|
|
43
53
|
"store:persistence-error": "connection",
|
|
44
54
|
"store:quota-exceeded": "connection",
|
|
55
|
+
"store:log-integrity": "operation",
|
|
56
|
+
"store:rematerialized": "operation",
|
|
45
57
|
"store:storage-fallback": "connection",
|
|
46
58
|
"store:opfs-unavailable": "connection",
|
|
59
|
+
"store:durability-lost": "connection",
|
|
47
60
|
"store:db-name-collision": "connection",
|
|
61
|
+
"store:storage-blocked": "connection",
|
|
62
|
+
"store:storage-migrated": "connection",
|
|
63
|
+
"store:schema-ahead": "connection",
|
|
48
64
|
"replay:completed": "operation"
|
|
49
65
|
};
|
|
50
66
|
function eventTypeToCategory(type) {
|
|
@@ -315,12 +331,24 @@ function timelineLabel(event) {
|
|
|
315
331
|
return "sync auth failed";
|
|
316
332
|
case "sync:suspended":
|
|
317
333
|
return `sync suspended (${event.reason})`;
|
|
334
|
+
case "encryption:status":
|
|
335
|
+
return `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ""}`;
|
|
318
336
|
case "sync:clock-skew":
|
|
319
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}`;
|
|
320
342
|
case "sync:clock-rebase":
|
|
321
343
|
return `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1e3)}s ahead)`;
|
|
322
344
|
case "sync:schema-mismatch":
|
|
323
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})`;
|
|
324
352
|
case "sync:apply-failed":
|
|
325
353
|
return `sync apply failed ${event.operationId}`;
|
|
326
354
|
case "sync:apply-blocked":
|
|
@@ -347,6 +375,10 @@ function timelineLabel(event) {
|
|
|
347
375
|
return `query invalidated ${event.queryId}`;
|
|
348
376
|
case "query:executed":
|
|
349
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"}`;
|
|
350
382
|
case "connection:quality":
|
|
351
383
|
return `connection ${event.quality}`;
|
|
352
384
|
case "sync:diagnostics":
|
|
@@ -359,6 +391,10 @@ function timelineLabel(event) {
|
|
|
359
391
|
return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
|
|
360
392
|
case "sync:delivery-stalled":
|
|
361
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";
|
|
362
398
|
case "awareness:updated":
|
|
363
399
|
return `awareness ${event.states.size} peers`;
|
|
364
400
|
case "state-machine:transition":
|
|
@@ -369,12 +405,24 @@ function timelineLabel(event) {
|
|
|
369
405
|
return `store persistence error ${event.message}`;
|
|
370
406
|
case "store:quota-exceeded":
|
|
371
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`;
|
|
372
412
|
case "store:storage-fallback":
|
|
373
413
|
return `store fallback ${event.from} \u2192 ${event.to}`;
|
|
374
414
|
case "store:opfs-unavailable":
|
|
375
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`;
|
|
376
418
|
case "store:db-name-collision":
|
|
377
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}`;
|
|
378
426
|
case "replay:completed":
|
|
379
427
|
return `replay ${event.operationsApplied} ops`;
|
|
380
428
|
}
|
|
@@ -948,4 +996,4 @@ export {
|
|
|
948
996
|
renderDevtoolsPanel,
|
|
949
997
|
mountKoraDevtoolsOverlay
|
|
950
998
|
};
|
|
951
|
-
//# sourceMappingURL=chunk-
|
|
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"]}
|
package/dist/extension/panel.cjs
CHANGED
|
@@ -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",
|
|
@@ -447,20 +453,30 @@ var EVENT_TYPE_CATEGORIES = {
|
|
|
447
453
|
"query:subscribed": "query",
|
|
448
454
|
"query:invalidated": "query",
|
|
449
455
|
"query:executed": "query",
|
|
456
|
+
"query:error": "query",
|
|
457
|
+
"storage:persistence": "connection",
|
|
450
458
|
"connection:quality": "connection",
|
|
451
459
|
"sync:diagnostics": "sync",
|
|
452
460
|
"sync:bandwidth": "sync",
|
|
453
461
|
"sync:initial-sync-progress": "sync",
|
|
454
462
|
"sync:delivery-gap": "sync",
|
|
455
463
|
"sync:delivery-stalled": "sync",
|
|
464
|
+
"sync:durability-degraded": "connection",
|
|
465
|
+
"sync:durability-restored": "connection",
|
|
456
466
|
"awareness:updated": "sync",
|
|
457
467
|
"state-machine:transition": "operation",
|
|
458
468
|
"state-machine:rejected": "operation",
|
|
459
469
|
"store:persistence-error": "connection",
|
|
460
470
|
"store:quota-exceeded": "connection",
|
|
471
|
+
"store:log-integrity": "operation",
|
|
472
|
+
"store:rematerialized": "operation",
|
|
461
473
|
"store:storage-fallback": "connection",
|
|
462
474
|
"store:opfs-unavailable": "connection",
|
|
475
|
+
"store:durability-lost": "connection",
|
|
463
476
|
"store:db-name-collision": "connection",
|
|
477
|
+
"store:storage-blocked": "connection",
|
|
478
|
+
"store:storage-migrated": "connection",
|
|
479
|
+
"store:schema-ahead": "connection",
|
|
464
480
|
"replay:completed": "operation"
|
|
465
481
|
};
|
|
466
482
|
function eventTypeToCategory(type) {
|
|
@@ -728,12 +744,24 @@ function timelineLabel(event) {
|
|
|
728
744
|
return "sync auth failed";
|
|
729
745
|
case "sync:suspended":
|
|
730
746
|
return `sync suspended (${event.reason})`;
|
|
747
|
+
case "encryption:status":
|
|
748
|
+
return `encryption ${event.status.state}${event.status.code ? ` (${event.status.code})` : ""}`;
|
|
731
749
|
case "sync:clock-skew":
|
|
732
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}`;
|
|
733
755
|
case "sync:clock-rebase":
|
|
734
756
|
return `clock rebase ${event.rebasedCount} ops (${Math.round(event.maxSkewMs / 1e3)}s ahead)`;
|
|
735
757
|
case "sync:schema-mismatch":
|
|
736
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})`;
|
|
737
765
|
case "sync:apply-failed":
|
|
738
766
|
return `sync apply failed ${event.operationId}`;
|
|
739
767
|
case "sync:apply-blocked":
|
|
@@ -760,6 +788,10 @@ function timelineLabel(event) {
|
|
|
760
788
|
return `query invalidated ${event.queryId}`;
|
|
761
789
|
case "query:executed":
|
|
762
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"}`;
|
|
763
795
|
case "connection:quality":
|
|
764
796
|
return `connection ${event.quality}`;
|
|
765
797
|
case "sync:diagnostics":
|
|
@@ -772,6 +804,10 @@ function timelineLabel(event) {
|
|
|
772
804
|
return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
|
|
773
805
|
case "sync:delivery-stalled":
|
|
774
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";
|
|
775
811
|
case "awareness:updated":
|
|
776
812
|
return `awareness ${event.states.size} peers`;
|
|
777
813
|
case "state-machine:transition":
|
|
@@ -782,12 +818,24 @@ function timelineLabel(event) {
|
|
|
782
818
|
return `store persistence error ${event.message}`;
|
|
783
819
|
case "store:quota-exceeded":
|
|
784
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`;
|
|
785
825
|
case "store:storage-fallback":
|
|
786
826
|
return `store fallback ${event.from} \u2192 ${event.to}`;
|
|
787
827
|
case "store:opfs-unavailable":
|
|
788
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`;
|
|
789
831
|
case "store:db-name-collision":
|
|
790
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}`;
|
|
791
839
|
case "replay:completed":
|
|
792
840
|
return `replay ${event.operationsApplied} ops`;
|
|
793
841
|
}
|