@korajs/devtools 1.0.0-beta.1 → 1.0.0-beta.10

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/dist/index.cjs CHANGED
@@ -200,6 +200,9 @@ var ALL_EVENT_TYPES = [
200
200
  "sync:initial-sync-progress",
201
201
  "store:persistence-error",
202
202
  "store:quota-exceeded",
203
+ "store:storage-fallback",
204
+ "store:opfs-unavailable",
205
+ "store:db-name-collision",
203
206
  "replay:completed",
204
207
  "query:subscribed",
205
208
  "query:invalidated",
@@ -291,6 +294,7 @@ var EVENT_TYPE_CATEGORIES = {
291
294
  "sync:clock-rebase": "sync",
292
295
  "sync:schema-mismatch": "sync",
293
296
  "sync:apply-failed": "sync",
297
+ "sync:operation-rejected": "sync",
294
298
  "sync:sent": "sync",
295
299
  "sync:received": "sync",
296
300
  "sync:acknowledged": "sync",
@@ -301,11 +305,16 @@ var EVENT_TYPE_CATEGORIES = {
301
305
  "sync:diagnostics": "sync",
302
306
  "sync:bandwidth": "sync",
303
307
  "sync:initial-sync-progress": "sync",
308
+ "sync:delivery-gap": "sync",
309
+ "sync:delivery-stalled": "sync",
304
310
  "awareness:updated": "sync",
305
311
  "state-machine:transition": "operation",
306
312
  "state-machine:rejected": "operation",
307
313
  "store:persistence-error": "connection",
308
314
  "store:quota-exceeded": "connection",
315
+ "store:storage-fallback": "connection",
316
+ "store:opfs-unavailable": "connection",
317
+ "store:db-name-collision": "connection",
309
318
  "replay:completed": "operation"
310
319
  };
311
320
  function eventTypeToCategory(type) {
@@ -562,6 +571,8 @@ function timelineLabel(event) {
562
571
  return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
563
572
  case "sync:apply-failed":
564
573
  return `sync apply failed ${event.operationId}`;
574
+ case "sync:operation-rejected":
575
+ return `sync op rejected ${event.collection}/${event.recordId} (${event.code})`;
565
576
  case "sync:sent":
566
577
  return `sync sent ${event.batchSize}`;
567
578
  case "sync:received":
@@ -582,6 +593,10 @@ function timelineLabel(event) {
582
593
  return `bandwidth ${event.direction} ${event.bytesPerSecond}B/s`;
583
594
  case "sync:initial-sync-progress":
584
595
  return `initial sync ${Math.round(event.progress * 100)}%`;
596
+ case "sync:delivery-gap":
597
+ return `delivery gap expected ${event.expectedBase} received ${event.receivedBase}`;
598
+ case "sync:delivery-stalled":
599
+ return `delivery stalled at ${event.watermark} (${event.repeatCount} repeats)`;
585
600
  case "awareness:updated":
586
601
  return `awareness ${event.states.size} peers`;
587
602
  case "state-machine:transition":
@@ -592,6 +607,12 @@ function timelineLabel(event) {
592
607
  return `store persistence error ${event.message}`;
593
608
  case "store:quota-exceeded":
594
609
  return "store quota exceeded";
610
+ case "store:storage-fallback":
611
+ return `store fallback ${event.from} \u2192 ${event.to}`;
612
+ case "store:opfs-unavailable":
613
+ return `store OPFS unavailable (${event.reason}), running in memory`;
614
+ case "store:db-name-collision":
615
+ return `store db-name collision ${event.dbName}`;
595
616
  case "replay:completed":
596
617
  return `replay ${event.operationsApplied} ops`;
597
618
  }
@@ -1071,7 +1092,7 @@ function renderDevtoolsPanel(target, events) {
1071
1092
  }
1072
1093
 
1073
1094
  // src/ui/panel-styles.ts
1074
- var KORA_DEVTOOLS_STYLES = "/* ================================================================\n Kora DevTools \u2014 Dark theme optimized for Chrome DevTools panels\n ================================================================ */\n\n :root {\n --bg-primary: #1e1e2e;\n --bg-secondary: #181825;\n --bg-surface: #313244;\n --bg-hover: #45475a;\n --bg-selected: #585b70;\n --text-primary: #cdd6f4;\n --text-secondary: #a6adc8;\n --text-muted: #6c7086;\n --border: #45475a;\n\n --green: #a6e3a1;\n --red: #f38ba8;\n --yellow: #f9e2af;\n --blue: #89b4fa;\n --purple: #cba6f7;\n --teal: #94e2d5;\n --peach: #fab387;\n\n --font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', Consolas, monospace;\n --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --font-size: 12px;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n body {\n background: var(--bg-primary);\n color: var(--text-primary);\n font-family: var(--font-sans);\n font-size: var(--font-size);\n line-height: 1.5;\n overflow: hidden;\n }\n\n #kora-devtools-root {\n width: 100vw;\n height: 100vh;\n display: flex;\n flex-direction: column;\n }\n\n .kora-devtools {\n display: flex;\n flex-direction: column;\n height: 100%;\n }\n\n /* ---- Toolbar ---- */\n\n .kora-toolbar {\n background: var(--bg-secondary);\n border-bottom: 1px solid var(--border);\n padding: 4px 8px;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n }\n\n .kora-tabs {\n display: flex;\n gap: 2px;\n }\n\n .kora-tab {\n background: transparent;\n color: var(--text-secondary);\n border: none;\n padding: 4px 12px;\n border-radius: 4px;\n cursor: pointer;\n font-size: var(--font-size);\n font-family: var(--font-sans);\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .kora-tab:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .kora-tab.active {\n background: var(--bg-surface);\n color: var(--text-primary);\n font-weight: 600;\n }\n\n .kora-badge {\n background: var(--bg-hover);\n color: var(--text-muted);\n padding: 0 6px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 500;\n }\n\n .kora-tab.active .kora-badge {\n background: var(--blue);\n color: var(--bg-primary);\n }\n\n .kora-controls {\n display: flex;\n gap: 4px;\n align-items: center;\n margin-left: auto;\n flex-wrap: wrap;\n }\n\n .kora-search {\n background: var(--bg-surface);\n color: var(--text-primary);\n border: 1px solid var(--border);\n padding: 3px 8px;\n border-radius: 4px;\n font-size: var(--font-size);\n font-family: var(--font-sans);\n width: 140px;\n outline: none;\n }\n\n .kora-search:focus {\n border-color: var(--blue);\n }\n\n .kora-btn {\n background: var(--bg-surface);\n color: var(--text-secondary);\n border: 1px solid var(--border);\n padding: 3px 8px;\n border-radius: 4px;\n cursor: pointer;\n font-size: 11px;\n font-family: var(--font-sans);\n }\n\n .kora-btn:hover {\n background: var(--bg-hover);\n color: var(--text-primary);\n }\n\n .kora-cat-toggle {\n background: transparent;\n color: var(--text-muted);\n border: 1px solid transparent;\n padding: 2px 6px;\n border-radius: 3px;\n cursor: pointer;\n font-size: 10px;\n font-family: var(--font-sans);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n }\n\n .kora-cat-toggle:hover {\n color: var(--text-secondary);\n }\n\n .kora-cat-toggle.active {\n background: var(--bg-surface);\n color: var(--text-primary);\n border-color: var(--border);\n }\n\n /* ---- Content ---- */\n\n .kora-content {\n flex: 1;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n }\n\n .kora-panel {\n flex: 1;\n overflow-y: auto;\n padding: 8px;\n }\n\n .kora-empty {\n color: var(--text-muted);\n text-align: center;\n padding: 40px;\n font-style: italic;\n }\n\n .kora-summary {\n color: var(--text-muted);\n font-size: 11px;\n padding: 4px 0 8px;\n border-bottom: 1px solid var(--border);\n margin-bottom: 4px;\n }\n\n /* ---- Event List (Timeline) ---- */\n\n .kora-event-list {\n display: flex;\n flex-direction: column;\n }\n\n .kora-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 6px;\n border-radius: 3px;\n cursor: pointer;\n min-height: 24px;\n }\n\n .kora-row:hover {\n background: var(--bg-hover);\n }\n\n .kora-row.expanded,\n .kora-row.selected {\n background: var(--bg-surface);\n }\n\n .kora-row.compact {\n padding: 2px 6px;\n font-size: 11px;\n }\n\n .kora-time {\n color: var(--text-muted);\n font-family: var(--font-mono);\n font-size: 11px;\n flex-shrink: 0;\n width: 90px;\n }\n\n .kora-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n }\n\n .kora-type {\n color: var(--text-secondary);\n font-size: 11px;\n flex-shrink: 0;\n width: 140px;\n }\n\n .kora-label {\n color: var(--text-primary);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .kora-deps {\n color: var(--peach);\n font-size: 10px;\n flex-shrink: 0;\n }\n\n /* ---- Detail / Expandable ---- */\n\n .kora-detail,\n .kora-op-detail {\n background: var(--bg-secondary);\n border: 1px solid var(--border);\n border-radius: 4px;\n padding: 8px 12px;\n margin: 2px 0 6px 28px;\n font-size: 11px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n\n .kora-detail-label {\n color: var(--text-muted);\n font-weight: 500;\n margin-right: 4px;\n }\n\n .kora-mono {\n font-family: var(--font-mono);\n font-size: 11px;\n }\n\n /* ---- Conflicts Table ---- */\n\n .kora-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 11px;\n }\n\n .kora-table th {\n text-align: left;\n color: var(--text-muted);\n font-weight: 500;\n padding: 6px 8px;\n border-bottom: 1px solid var(--border);\n text-transform: uppercase;\n font-size: 10px;\n letter-spacing: 0.5px;\n }\n\n .kora-table td {\n padding: 5px 8px;\n border-bottom: 1px solid var(--bg-surface);\n }\n\n .kora-conflict-row {\n cursor: pointer;\n }\n\n .kora-conflict-row:hover td {\n background: var(--bg-hover);\n }\n\n .kora-conflict-row.expanded td {\n background: var(--bg-surface);\n }\n\n .kora-conflict-row.violated td {\n border-left: 3px solid var(--red);\n }\n\n .kora-strategy {\n padding: 1px 6px;\n border-radius: 3px;\n font-size: 10px;\n font-weight: 500;\n }\n\n .kora-strategy.tier-1 {\n background: rgba(166, 227, 161, 0.15);\n color: var(--green);\n }\n\n .kora-strategy.tier-2 {\n background: rgba(249, 226, 175, 0.15);\n color: var(--yellow);\n }\n\n .kora-strategy.tier-3 {\n background: rgba(203, 166, 247, 0.15);\n color: var(--purple);\n }\n\n .kora-detail-row td {\n padding: 0;\n }\n\n .kora-conflict-detail {\n padding: 12px;\n }\n\n .kora-comparison {\n display: grid;\n grid-template-columns: 1fr 1fr 1fr;\n gap: 8px;\n }\n\n .kora-value-box {\n background: var(--bg-primary);\n border: 1px solid var(--border);\n border-radius: 4px;\n padding: 8px;\n }\n\n .kora-value-box.result {\n border-color: var(--green);\n }\n\n .kora-value-label {\n color: var(--text-muted);\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-bottom: 4px;\n }\n\n .kora-value-code {\n font-family: var(--font-mono);\n font-size: 11px;\n color: var(--text-primary);\n white-space: pre-wrap;\n word-break: break-all;\n margin: 0;\n }\n\n .kora-constraint-warning {\n margin-top: 8px;\n padding: 6px 10px;\n background: rgba(243, 139, 168, 0.1);\n border: 1px solid var(--red);\n border-radius: 4px;\n color: var(--red);\n font-size: 11px;\n }\n\n /* ---- Operations ---- */\n\n .kora-op-list {\n display: flex;\n flex-direction: column;\n }\n\n .kora-op-row {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 3px 6px;\n border-radius: 3px;\n cursor: pointer;\n }\n\n .kora-op-row:hover {\n background: var(--bg-hover);\n }\n\n .kora-op-row.expanded {\n background: var(--bg-surface);\n }\n\n .kora-op-type {\n padding: 1px 6px;\n border-radius: 3px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n flex-shrink: 0;\n }\n\n .op-insert {\n background: rgba(166, 227, 161, 0.15);\n color: var(--green);\n }\n\n .op-update {\n background: rgba(137, 180, 250, 0.15);\n color: var(--blue);\n }\n\n .op-delete {\n background: rgba(243, 139, 168, 0.15);\n color: var(--red);\n }\n\n .kora-op-collection {\n color: var(--teal);\n font-weight: 500;\n flex-shrink: 0;\n }\n\n .kora-op-record {\n color: var(--text-secondary);\n flex-shrink: 0;\n }\n\n .kora-op-node {\n color: var(--text-muted);\n font-size: 10px;\n flex-shrink: 0;\n }\n\n .kora-op-seq {\n color: var(--text-muted);\n font-size: 10px;\n flex-shrink: 0;\n }\n\n /* ---- Network ---- */\n\n .kora-network-status {\n padding: 12px 0;\n }\n\n .kora-status-indicator {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 14px;\n font-weight: 600;\n }\n\n .kora-status-dot {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n }\n\n .connected .kora-status-dot {\n background: var(--green);\n box-shadow: 0 0 8px rgba(166, 227, 161, 0.5);\n }\n\n .disconnected .kora-status-dot {\n background: var(--red);\n box-shadow: 0 0 8px rgba(243, 139, 168, 0.3);\n }\n\n .connected .kora-status-text {\n color: var(--green);\n }\n\n .disconnected .kora-status-text {\n color: var(--red);\n }\n\n .kora-stats-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 8px;\n margin: 12px 0;\n }\n\n .kora-stat-card {\n background: var(--bg-surface);\n border: 1px solid var(--border);\n border-radius: 6px;\n padding: 12px;\n text-align: center;\n }\n\n .kora-stat-value {\n font-size: 20px;\n font-weight: 700;\n color: var(--text-primary);\n font-family: var(--font-mono);\n }\n\n .kora-stat-label {\n color: var(--text-muted);\n font-size: 10px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n margin-top: 2px;\n }\n\n .kora-vv-section,\n .kora-sync-log {\n margin-top: 16px;\n }\n\n .kora-vv-section h3,\n .kora-sync-log h3 {\n color: var(--text-secondary);\n font-size: 12px;\n font-weight: 600;\n margin-bottom: 8px;\n }\n\n .kora-vv-table {\n max-width: 400px;\n }\n\n /* ---- Scrollbar ---- */\n\n ::-webkit-scrollbar {\n width: 8px;\n }\n\n ::-webkit-scrollbar-track {\n background: var(--bg-primary);\n }\n\n ::-webkit-scrollbar-thumb {\n background: var(--bg-hover);\n border-radius: 4px;\n }\n\n ::-webkit-scrollbar-thumb:hover {\n background: var(--bg-selected);\n }";
1095
+ var KORA_DEVTOOLS_STYLES = "/* ================================================================\n Kora DevTools \u2014 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 }";
1075
1096
 
1076
1097
  // src/overlay/embed-overlay.ts
1077
1098
  var OVERLAY_HOST_ID = "kora-devtools-overlay-host";
@@ -1089,8 +1110,41 @@ function mountKoraDevtoolsOverlay(instrumenter) {
1089
1110
  host.style.cssText = "position:fixed;inset:auto 12px 12px 12px;height:42vh;z-index:2147483646;display:none;";
1090
1111
  const shadow = host.attachShadow({ mode: "open" });
1091
1112
  const style = document.createElement("style");
1092
- style.textContent = KORA_DEVTOOLS_STYLES;
1113
+ style.textContent = `${KORA_DEVTOOLS_STYLES}
1114
+ :host {
1115
+ color-scheme: dark;
1116
+ background: #1e1e2e;
1117
+ color: #cdd6f4;
1118
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1119
+ overflow: hidden;
1120
+ border: 1px solid #45475a;
1121
+ box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
1122
+ }
1123
+ .kora-overlay-close {
1124
+ position: absolute;
1125
+ top: 6px;
1126
+ right: 8px;
1127
+ z-index: 1;
1128
+ background: #313244;
1129
+ color: #cdd6f4;
1130
+ border: 1px solid #45475a;
1131
+ border-radius: 4px;
1132
+ cursor: pointer;
1133
+ font: 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
1134
+ line-height: 1;
1135
+ padding: 4px 8px;
1136
+ }
1137
+ .kora-overlay-close:hover {
1138
+ background: #45475a;
1139
+ }
1140
+ `;
1093
1141
  shadow.appendChild(style);
1142
+ const closeButton = document.createElement("button");
1143
+ closeButton.type = "button";
1144
+ closeButton.className = "kora-overlay-close";
1145
+ closeButton.textContent = "Close";
1146
+ closeButton.setAttribute("aria-label", "Close Kora DevTools");
1147
+ shadow.appendChild(closeButton);
1094
1148
  const panelRoot = document.createElement("div");
1095
1149
  panelRoot.id = "kora-devtools-root";
1096
1150
  panelRoot.style.height = "100%";
@@ -1104,7 +1158,8 @@ function mountKoraDevtoolsOverlay(instrumenter) {
1104
1158
  const setVisible = (next) => {
1105
1159
  visible = next;
1106
1160
  host.style.display = visible ? "block" : "none";
1107
- hint.style.display = visible ? "none" : "block";
1161
+ hint.textContent = visible ? "Hide Kora DevTools (Esc)" : "Kora DevTools (Ctrl+Shift+K)";
1162
+ hint.style.display = "block";
1108
1163
  };
1109
1164
  const refresh = () => {
1110
1165
  if (!visible) return;
@@ -1113,17 +1168,36 @@ function mountKoraDevtoolsOverlay(instrumenter) {
1113
1168
  const intervalId = window.setInterval(refresh, 300);
1114
1169
  const onKeyDown = (event) => {
1115
1170
  const isToggle = event.key === "K" && event.shiftKey && (event.ctrlKey || event.metaKey);
1116
- if (!isToggle) return;
1117
- event.preventDefault();
1171
+ if (isToggle) {
1172
+ event.preventDefault();
1173
+ setVisible(!visible);
1174
+ if (visible) {
1175
+ refresh();
1176
+ }
1177
+ return;
1178
+ }
1179
+ if (event.key === "Escape" && visible) {
1180
+ event.preventDefault();
1181
+ setVisible(false);
1182
+ }
1183
+ };
1184
+ const onHintClick = () => {
1118
1185
  setVisible(!visible);
1119
1186
  if (visible) {
1120
1187
  refresh();
1121
1188
  }
1122
1189
  };
1190
+ const onCloseClick = () => {
1191
+ setVisible(false);
1192
+ };
1123
1193
  window.addEventListener("keydown", onKeyDown);
1194
+ hint.addEventListener("click", onHintClick);
1195
+ closeButton.addEventListener("click", onCloseClick);
1124
1196
  return () => {
1125
1197
  window.clearInterval(intervalId);
1126
1198
  window.removeEventListener("keydown", onKeyDown);
1199
+ hint.removeEventListener("click", onHintClick);
1200
+ closeButton.removeEventListener("click", onCloseClick);
1127
1201
  host.remove();
1128
1202
  hint.remove();
1129
1203
  };