@korajs/devtools 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ __export(src_exports, {
38
38
  computeStatistics: () => computeStatistics,
39
39
  filterEvents: () => filterEvents,
40
40
  getEventCategory: () => getEventCategory,
41
+ mountKoraDevtoolsOverlay: () => mountKoraDevtoolsOverlay,
41
42
  renderDevtoolsPanel: () => renderDevtoolsPanel
42
43
  });
43
44
  module.exports = __toCommonJS(src_exports);
@@ -188,9 +189,18 @@ var ALL_EVENT_TYPES = [
188
189
  "constraint:violated",
189
190
  "sync:connected",
190
191
  "sync:disconnected",
192
+ "sync:schema-mismatch",
193
+ "sync:auth-failed",
194
+ "sync:apply-failed",
191
195
  "sync:sent",
192
196
  "sync:received",
193
197
  "sync:acknowledged",
198
+ "sync:diagnostics",
199
+ "sync:bandwidth",
200
+ "sync:initial-sync-progress",
201
+ "store:persistence-error",
202
+ "store:quota-exceeded",
203
+ "replay:completed",
194
204
  "query:subscribed",
195
205
  "query:invalidated",
196
206
  "query:executed",
@@ -277,13 +287,24 @@ var EVENT_TYPE_CATEGORIES = {
277
287
  "sync:connected": "sync",
278
288
  "sync:disconnected": "sync",
279
289
  "sync:auth-failed": "sync",
290
+ "sync:schema-mismatch": "sync",
291
+ "sync:apply-failed": "sync",
280
292
  "sync:sent": "sync",
281
293
  "sync:received": "sync",
282
294
  "sync:acknowledged": "sync",
283
295
  "query:subscribed": "query",
284
296
  "query:invalidated": "query",
285
297
  "query:executed": "query",
286
- "connection:quality": "connection"
298
+ "connection:quality": "connection",
299
+ "sync:diagnostics": "sync",
300
+ "sync:bandwidth": "sync",
301
+ "sync:initial-sync-progress": "sync",
302
+ "awareness:updated": "sync",
303
+ "state-machine:transition": "operation",
304
+ "state-machine:rejected": "operation",
305
+ "store:persistence-error": "connection",
306
+ "store:quota-exceeded": "connection",
307
+ "replay:completed": "operation"
287
308
  };
288
309
  function eventTypeToCategory(type) {
289
310
  return EVENT_TYPE_CATEGORIES[type];
@@ -528,9 +549,13 @@ function timelineLabel(event) {
528
549
  case "sync:connected":
529
550
  return `sync connected ${event.nodeId}`;
530
551
  case "sync:disconnected":
531
- return `sync disconnected`;
552
+ return "sync disconnected";
532
553
  case "sync:auth-failed":
533
- return `sync auth failed`;
554
+ return "sync auth failed";
555
+ case "sync:schema-mismatch":
556
+ return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
557
+ case "sync:apply-failed":
558
+ return `sync apply failed ${event.operationId}`;
534
559
  case "sync:sent":
535
560
  return `sync sent ${event.batchSize}`;
536
561
  case "sync:received":
@@ -545,6 +570,24 @@ function timelineLabel(event) {
545
570
  return `query executed ${event.queryId}`;
546
571
  case "connection:quality":
547
572
  return `connection ${event.quality}`;
573
+ case "sync:diagnostics":
574
+ return `sync diagnostics ${event.diagnostics.quality}`;
575
+ case "sync:bandwidth":
576
+ return `bandwidth ${event.direction} ${event.bytesPerSecond}B/s`;
577
+ case "sync:initial-sync-progress":
578
+ return `initial sync ${Math.round(event.progress * 100)}%`;
579
+ case "awareness:updated":
580
+ return `awareness ${event.states.size} peers`;
581
+ case "state-machine:transition":
582
+ return `transition ${event.collection} ${event.from} \u2192 ${event.to}`;
583
+ case "state-machine:rejected":
584
+ return `transition rejected ${event.collection} ${event.from} \u2192 ${event.to}`;
585
+ case "store:persistence-error":
586
+ return `store persistence error ${event.message}`;
587
+ case "store:quota-exceeded":
588
+ return "store quota exceeded";
589
+ case "replay:completed":
590
+ return `replay ${event.operationsApplied} ops`;
548
591
  }
549
592
  }
550
593
  function timelineColor(type) {
@@ -571,9 +614,9 @@ function extractOperation(event) {
571
614
  }
572
615
 
573
616
  // src/ui/panel.ts
617
+ var import_htm = __toESM(require("htm"), 1);
574
618
  var import_preact = require("preact");
575
619
  var import_hooks = require("preact/hooks");
576
- var import_htm = __toESM(require("htm"), 1);
577
620
 
578
621
  // src/ui/components.ts
579
622
  function formatTime(timestamp) {
@@ -586,7 +629,7 @@ function formatTime(timestamp) {
586
629
  }
587
630
  function truncate(str, max) {
588
631
  if (str.length <= max) return str;
589
- return str.slice(0, max - 1) + "\u2026";
632
+ return `${str.slice(0, max - 1)}\u2026`;
590
633
  }
591
634
  function formatValue(value) {
592
635
  if (value === null || value === void 0) return "null";
@@ -601,33 +644,25 @@ var ALL_CATEGORIES = ["operation", "merge", "sync", "query", "connection"];
601
644
  function DevToolsPanel({ events }) {
602
645
  const [activeTab, setActiveTab] = (0, import_hooks.useState)("timeline");
603
646
  const [search, setSearch] = (0, import_hooks.useState)("");
604
- const [categories, setCategories] = (0, import_hooks.useState)(
605
- () => new Set(ALL_CATEGORIES)
606
- );
647
+ const [categories, setCategories] = (0, import_hooks.useState)(() => new Set(ALL_CATEGORIES));
607
648
  const [expanded, setExpanded] = (0, import_hooks.useState)(() => /* @__PURE__ */ new Set());
608
649
  const [paused, setPaused] = (0, import_hooks.useState)(false);
609
- const toggleCategory = (0, import_hooks.useCallback)(
610
- (cat) => {
611
- setCategories((prev) => {
612
- const next = new Set(prev);
613
- if (next.has(cat)) next.delete(cat);
614
- else next.add(cat);
615
- return next;
616
- });
617
- },
618
- []
619
- );
620
- const toggleExpand = (0, import_hooks.useCallback)(
621
- (id) => {
622
- setExpanded((prev) => {
623
- const next = new Set(prev);
624
- if (next.has(id)) next.delete(id);
625
- else next.add(id);
626
- return next;
627
- });
628
- },
629
- []
630
- );
650
+ const toggleCategory = (0, import_hooks.useCallback)((cat) => {
651
+ setCategories((prev) => {
652
+ const next = new Set(prev);
653
+ if (next.has(cat)) next.delete(cat);
654
+ else next.add(cat);
655
+ return next;
656
+ });
657
+ }, []);
658
+ const toggleExpand = (0, import_hooks.useCallback)((id) => {
659
+ setExpanded((prev) => {
660
+ const next = new Set(prev);
661
+ if (next.has(id)) next.delete(id);
662
+ else next.add(id);
663
+ return next;
664
+ });
665
+ }, []);
631
666
  const filtered = (0, import_hooks.useMemo)(
632
667
  () => filterEvents(events, { categories: [...categories] }),
633
668
  [events, categories]
@@ -1029,6 +1064,65 @@ function renderDevtoolsPanel(target, events) {
1029
1064
  (0, import_preact.render)(html`<${DevToolsPanel} events=${events} />`, target);
1030
1065
  }
1031
1066
 
1067
+ // src/ui/panel-styles.ts
1068
+ 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 }";
1069
+
1070
+ // src/overlay/embed-overlay.ts
1071
+ var OVERLAY_HOST_ID = "kora-devtools-overlay-host";
1072
+ function mountKoraDevtoolsOverlay(instrumenter) {
1073
+ if (typeof document === "undefined") {
1074
+ return () => {
1075
+ };
1076
+ }
1077
+ const existing = document.getElementById(OVERLAY_HOST_ID);
1078
+ if (existing) {
1079
+ existing.remove();
1080
+ }
1081
+ const host = document.createElement("div");
1082
+ host.id = OVERLAY_HOST_ID;
1083
+ host.style.cssText = "position:fixed;inset:auto 12px 12px 12px;height:42vh;z-index:2147483646;display:none;";
1084
+ const shadow = host.attachShadow({ mode: "open" });
1085
+ const style = document.createElement("style");
1086
+ style.textContent = KORA_DEVTOOLS_STYLES;
1087
+ shadow.appendChild(style);
1088
+ const panelRoot = document.createElement("div");
1089
+ panelRoot.id = "kora-devtools-root";
1090
+ panelRoot.style.height = "100%";
1091
+ shadow.appendChild(panelRoot);
1092
+ const hint = document.createElement("div");
1093
+ hint.textContent = "Kora DevTools (Ctrl+Shift+K)";
1094
+ hint.style.cssText = "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;";
1095
+ document.body.appendChild(hint);
1096
+ document.body.appendChild(host);
1097
+ let visible = false;
1098
+ const setVisible = (next) => {
1099
+ visible = next;
1100
+ host.style.display = visible ? "block" : "none";
1101
+ hint.style.display = visible ? "none" : "block";
1102
+ };
1103
+ const refresh = () => {
1104
+ if (!visible) return;
1105
+ renderDevtoolsPanel(panelRoot, instrumenter.getBuffer().getAll());
1106
+ };
1107
+ const intervalId = window.setInterval(refresh, 300);
1108
+ const onKeyDown = (event) => {
1109
+ const isToggle = event.key === "K" && event.shiftKey && (event.ctrlKey || event.metaKey);
1110
+ if (!isToggle) return;
1111
+ event.preventDefault();
1112
+ setVisible(!visible);
1113
+ if (visible) {
1114
+ refresh();
1115
+ }
1116
+ };
1117
+ window.addEventListener("keydown", onKeyDown);
1118
+ return () => {
1119
+ window.clearInterval(intervalId);
1120
+ window.removeEventListener("keydown", onKeyDown);
1121
+ host.remove();
1122
+ hint.remove();
1123
+ };
1124
+ }
1125
+
1032
1126
  // src/extension/port-router.ts
1033
1127
  var PortRouter = class {
1034
1128
  panelClients = /* @__PURE__ */ new Map();
@@ -1092,6 +1186,7 @@ function isContentEventMessage(value) {
1092
1186
  computeStatistics,
1093
1187
  filterEvents,
1094
1188
  getEventCategory,
1189
+ mountKoraDevtoolsOverlay,
1095
1190
  renderDevtoolsPanel
1096
1191
  });
1097
1192
  //# sourceMappingURL=index.cjs.map