@korajs/devtools 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-N64DMGUU.js +856 -0
- package/dist/chunk-N64DMGUU.js.map +1 -0
- package/dist/embed-overlay-Dwys5PYM.d.cts +170 -0
- package/dist/embed-overlay-Dwys5PYM.d.ts +170 -0
- package/dist/extension/panel.cjs +16 -1
- package/dist/extension/panel.cjs.map +1 -1
- package/dist/extension/panel.js +16 -1
- package/dist/extension/panel.js.map +1 -1
- package/dist/index.cjs +86 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -161
- package/dist/index.d.ts +4 -161
- package/dist/index.js +19 -773
- package/dist/index.js.map +1 -1
- package/dist/overlay.cjs +884 -0
- package/dist/overlay.cjs.map +1 -0
- package/dist/overlay.d.cts +2 -0
- package/dist/overlay.d.ts +2 -0
- package/dist/overlay.js +7 -0
- package/dist/overlay.js.map +1 -0
- package/package.json +12 -2
package/dist/overlay.cjs
ADDED
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/overlay/embed-overlay.ts
|
|
31
|
+
var embed_overlay_exports = {};
|
|
32
|
+
__export(embed_overlay_exports, {
|
|
33
|
+
mountKoraDevtoolsOverlay: () => mountKoraDevtoolsOverlay
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(embed_overlay_exports);
|
|
36
|
+
|
|
37
|
+
// src/ui/panel.ts
|
|
38
|
+
var import_htm = __toESM(require("htm"), 1);
|
|
39
|
+
var import_preact = require("preact");
|
|
40
|
+
var import_hooks = require("preact/hooks");
|
|
41
|
+
|
|
42
|
+
// src/types.ts
|
|
43
|
+
var EVENT_TYPE_CATEGORIES = {
|
|
44
|
+
"operation:created": "operation",
|
|
45
|
+
"operation:applied": "operation",
|
|
46
|
+
"merge:started": "merge",
|
|
47
|
+
"merge:completed": "merge",
|
|
48
|
+
"merge:conflict": "merge",
|
|
49
|
+
"constraint:violated": "merge",
|
|
50
|
+
"sync:connected": "sync",
|
|
51
|
+
"sync:disconnected": "sync",
|
|
52
|
+
"sync:auth-failed": "sync",
|
|
53
|
+
"sync:schema-mismatch": "sync",
|
|
54
|
+
"sync:apply-failed": "sync",
|
|
55
|
+
"sync:sent": "sync",
|
|
56
|
+
"sync:received": "sync",
|
|
57
|
+
"sync:acknowledged": "sync",
|
|
58
|
+
"query:subscribed": "query",
|
|
59
|
+
"query:invalidated": "query",
|
|
60
|
+
"query:executed": "query",
|
|
61
|
+
"connection:quality": "connection",
|
|
62
|
+
"sync:diagnostics": "sync",
|
|
63
|
+
"sync:bandwidth": "sync",
|
|
64
|
+
"sync:initial-sync-progress": "sync",
|
|
65
|
+
"awareness:updated": "sync",
|
|
66
|
+
"state-machine:transition": "operation",
|
|
67
|
+
"state-machine:rejected": "operation",
|
|
68
|
+
"store:persistence-error": "connection",
|
|
69
|
+
"store:quota-exceeded": "connection",
|
|
70
|
+
"replay:completed": "operation"
|
|
71
|
+
};
|
|
72
|
+
function eventTypeToCategory(type) {
|
|
73
|
+
return EVENT_TYPE_CATEGORIES[type];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// src/filter/event-filter.ts
|
|
77
|
+
function extractCollection(event) {
|
|
78
|
+
const e = event.event;
|
|
79
|
+
switch (e.type) {
|
|
80
|
+
case "operation:created":
|
|
81
|
+
case "operation:applied":
|
|
82
|
+
return e.operation.collection;
|
|
83
|
+
case "merge:started":
|
|
84
|
+
return e.operationA.collection;
|
|
85
|
+
case "merge:completed":
|
|
86
|
+
case "merge:conflict":
|
|
87
|
+
return e.trace.operationA.collection;
|
|
88
|
+
case "constraint:violated":
|
|
89
|
+
return e.trace.operationA.collection;
|
|
90
|
+
case "query:subscribed":
|
|
91
|
+
return e.collection;
|
|
92
|
+
case "query:invalidated":
|
|
93
|
+
return e.trigger.collection;
|
|
94
|
+
case "sync:sent":
|
|
95
|
+
return e.operations[0]?.collection ?? null;
|
|
96
|
+
case "sync:received":
|
|
97
|
+
return e.operations[0]?.collection ?? null;
|
|
98
|
+
default:
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function filterEvents(events, criteria) {
|
|
103
|
+
const { categories, types, timeRange, collection } = criteria;
|
|
104
|
+
if (!categories && !types && !timeRange && !collection) {
|
|
105
|
+
return events;
|
|
106
|
+
}
|
|
107
|
+
const categorySet = categories ? new Set(categories) : null;
|
|
108
|
+
const typeSet = types ? new Set(types) : null;
|
|
109
|
+
return events.filter((event) => {
|
|
110
|
+
if (categorySet) {
|
|
111
|
+
const cat = eventTypeToCategory(event.event.type);
|
|
112
|
+
if (!categorySet.has(cat)) return false;
|
|
113
|
+
}
|
|
114
|
+
if (typeSet) {
|
|
115
|
+
if (!typeSet.has(event.event.type)) return false;
|
|
116
|
+
}
|
|
117
|
+
if (timeRange) {
|
|
118
|
+
if (event.receivedAt < timeRange.start || event.receivedAt > timeRange.end) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (collection) {
|
|
123
|
+
const eventCollection = extractCollection(event);
|
|
124
|
+
if (eventCollection !== collection) return false;
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/stats/event-stats.ts
|
|
131
|
+
function computeStatistics(events) {
|
|
132
|
+
const eventsByCategory = {
|
|
133
|
+
operation: 0,
|
|
134
|
+
merge: 0,
|
|
135
|
+
sync: 0,
|
|
136
|
+
query: 0,
|
|
137
|
+
connection: 0
|
|
138
|
+
};
|
|
139
|
+
const eventsByType = {};
|
|
140
|
+
let mergeConflicts = 0;
|
|
141
|
+
let constraintViolations = 0;
|
|
142
|
+
let mergeDurationSum = 0;
|
|
143
|
+
let mergeDurationCount = 0;
|
|
144
|
+
let queryDurationSum = 0;
|
|
145
|
+
let queryDurationCount = 0;
|
|
146
|
+
let syncOperationsSent = 0;
|
|
147
|
+
let syncOperationsReceived = 0;
|
|
148
|
+
for (const timestamped of events) {
|
|
149
|
+
const e = timestamped.event;
|
|
150
|
+
const category = eventTypeToCategory(e.type);
|
|
151
|
+
eventsByCategory[category]++;
|
|
152
|
+
eventsByType[e.type] = (eventsByType[e.type] ?? 0) + 1;
|
|
153
|
+
switch (e.type) {
|
|
154
|
+
case "merge:completed":
|
|
155
|
+
mergeDurationSum += e.trace.duration;
|
|
156
|
+
mergeDurationCount++;
|
|
157
|
+
break;
|
|
158
|
+
case "merge:conflict":
|
|
159
|
+
mergeConflicts++;
|
|
160
|
+
mergeDurationSum += e.trace.duration;
|
|
161
|
+
mergeDurationCount++;
|
|
162
|
+
break;
|
|
163
|
+
case "constraint:violated":
|
|
164
|
+
constraintViolations++;
|
|
165
|
+
break;
|
|
166
|
+
case "query:executed":
|
|
167
|
+
queryDurationSum += e.duration;
|
|
168
|
+
queryDurationCount++;
|
|
169
|
+
break;
|
|
170
|
+
case "sync:sent":
|
|
171
|
+
syncOperationsSent += e.batchSize;
|
|
172
|
+
break;
|
|
173
|
+
case "sync:received":
|
|
174
|
+
syncOperationsReceived += e.batchSize;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
totalEvents: events.length,
|
|
180
|
+
eventsByCategory,
|
|
181
|
+
eventsByType,
|
|
182
|
+
mergeConflicts,
|
|
183
|
+
constraintViolations,
|
|
184
|
+
avgMergeDuration: mergeDurationCount > 0 ? mergeDurationSum / mergeDurationCount : null,
|
|
185
|
+
avgQueryDuration: queryDurationCount > 0 ? queryDurationSum / queryDurationCount : null,
|
|
186
|
+
syncOperationsSent,
|
|
187
|
+
syncOperationsReceived
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/ui/components.ts
|
|
192
|
+
function formatTime(timestamp) {
|
|
193
|
+
const d = new Date(timestamp);
|
|
194
|
+
const h2 = String(d.getHours()).padStart(2, "0");
|
|
195
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
196
|
+
const s = String(d.getSeconds()).padStart(2, "0");
|
|
197
|
+
const ms = String(d.getMilliseconds()).padStart(3, "0");
|
|
198
|
+
return `${h2}:${m}:${s}.${ms}`;
|
|
199
|
+
}
|
|
200
|
+
function truncate(str, max) {
|
|
201
|
+
if (str.length <= max) return str;
|
|
202
|
+
return `${str.slice(0, max - 1)}\u2026`;
|
|
203
|
+
}
|
|
204
|
+
function formatValue(value) {
|
|
205
|
+
if (value === null || value === void 0) return "null";
|
|
206
|
+
if (typeof value === "string") return `"${value}"`;
|
|
207
|
+
if (typeof value === "object") return JSON.stringify(value, null, 2);
|
|
208
|
+
return String(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/ui/panel-state.ts
|
|
212
|
+
function buildPanelModel(events) {
|
|
213
|
+
const timeline = events.map((entry) => ({
|
|
214
|
+
id: entry.id,
|
|
215
|
+
type: entry.event.type,
|
|
216
|
+
label: timelineLabel(entry.event),
|
|
217
|
+
color: timelineColor(entry.event.type),
|
|
218
|
+
receivedAt: entry.receivedAt,
|
|
219
|
+
dependsOn: extractCausalDependencies(entry.event)
|
|
220
|
+
}));
|
|
221
|
+
const conflicts = events.flatMap((entry) => {
|
|
222
|
+
if (entry.event.type !== "merge:completed" && entry.event.type !== "merge:conflict" && entry.event.type !== "constraint:violated") {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const trace = entry.event.trace;
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
id: entry.id,
|
|
229
|
+
timestamp: entry.receivedAt,
|
|
230
|
+
collection: trace.operationA.collection,
|
|
231
|
+
field: trace.field,
|
|
232
|
+
strategy: trace.strategy,
|
|
233
|
+
tier: trace.tier,
|
|
234
|
+
inputA: trace.inputA,
|
|
235
|
+
inputB: trace.inputB,
|
|
236
|
+
output: trace.output,
|
|
237
|
+
constraintViolated: trace.constraintViolated
|
|
238
|
+
}
|
|
239
|
+
];
|
|
240
|
+
});
|
|
241
|
+
const operations = events.map((entry) => {
|
|
242
|
+
const operation = extractOperation(entry.event);
|
|
243
|
+
if (!operation) return null;
|
|
244
|
+
return {
|
|
245
|
+
id: entry.id,
|
|
246
|
+
timestamp: entry.receivedAt,
|
|
247
|
+
operationId: operation.id,
|
|
248
|
+
collection: operation.collection,
|
|
249
|
+
recordId: operation.recordId,
|
|
250
|
+
opType: operation.type,
|
|
251
|
+
data: operation.data,
|
|
252
|
+
causalDeps: operation.causalDeps,
|
|
253
|
+
nodeId: operation.nodeId,
|
|
254
|
+
sequenceNumber: operation.sequenceNumber
|
|
255
|
+
};
|
|
256
|
+
}).filter((item) => item !== null);
|
|
257
|
+
const network = buildNetworkStatus(events, operations);
|
|
258
|
+
return {
|
|
259
|
+
timeline,
|
|
260
|
+
conflicts,
|
|
261
|
+
operations,
|
|
262
|
+
network
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function buildNetworkStatus(events, operations) {
|
|
266
|
+
let connected = false;
|
|
267
|
+
let quality = null;
|
|
268
|
+
let pendingAcks = 0;
|
|
269
|
+
let lastSyncAt = null;
|
|
270
|
+
let sentOps = 0;
|
|
271
|
+
let receivedOps = 0;
|
|
272
|
+
for (const entry of events) {
|
|
273
|
+
switch (entry.event.type) {
|
|
274
|
+
case "sync:connected":
|
|
275
|
+
connected = true;
|
|
276
|
+
lastSyncAt = entry.receivedAt;
|
|
277
|
+
break;
|
|
278
|
+
case "sync:disconnected":
|
|
279
|
+
connected = false;
|
|
280
|
+
break;
|
|
281
|
+
case "connection:quality":
|
|
282
|
+
quality = entry.event.quality;
|
|
283
|
+
break;
|
|
284
|
+
case "sync:sent":
|
|
285
|
+
sentOps += entry.event.operations.length;
|
|
286
|
+
pendingAcks += entry.event.operations.length;
|
|
287
|
+
lastSyncAt = entry.receivedAt;
|
|
288
|
+
break;
|
|
289
|
+
case "sync:received":
|
|
290
|
+
receivedOps += entry.event.operations.length;
|
|
291
|
+
lastSyncAt = entry.receivedAt;
|
|
292
|
+
break;
|
|
293
|
+
case "sync:acknowledged":
|
|
294
|
+
pendingAcks = Math.max(0, pendingAcks - 1);
|
|
295
|
+
lastSyncAt = entry.receivedAt;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const vector = /* @__PURE__ */ new Map();
|
|
300
|
+
for (const operation of operations) {
|
|
301
|
+
const current = vector.get(operation.nodeId) ?? 0;
|
|
302
|
+
if (operation.sequenceNumber > current) {
|
|
303
|
+
vector.set(operation.nodeId, operation.sequenceNumber);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
connected,
|
|
308
|
+
quality,
|
|
309
|
+
pendingAcks,
|
|
310
|
+
lastSyncAt,
|
|
311
|
+
sentOps,
|
|
312
|
+
receivedOps,
|
|
313
|
+
versionVector: [...vector.entries()].map(([nodeId, sequenceNumber]) => ({ nodeId, sequenceNumber })).sort((left, right) => left.nodeId.localeCompare(right.nodeId))
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function timelineLabel(event) {
|
|
317
|
+
switch (event.type) {
|
|
318
|
+
case "operation:created":
|
|
319
|
+
case "operation:applied":
|
|
320
|
+
return `${event.operation.type} ${event.operation.collection}/${event.operation.recordId}`;
|
|
321
|
+
case "merge:started":
|
|
322
|
+
return `merge start ${event.operationA.collection}`;
|
|
323
|
+
case "merge:completed":
|
|
324
|
+
return `merge complete ${event.trace.field}`;
|
|
325
|
+
case "merge:conflict":
|
|
326
|
+
return `merge conflict ${event.trace.field}`;
|
|
327
|
+
case "constraint:violated":
|
|
328
|
+
return `constraint ${event.constraint}`;
|
|
329
|
+
case "sync:connected":
|
|
330
|
+
return `sync connected ${event.nodeId}`;
|
|
331
|
+
case "sync:disconnected":
|
|
332
|
+
return "sync disconnected";
|
|
333
|
+
case "sync:auth-failed":
|
|
334
|
+
return "sync auth failed";
|
|
335
|
+
case "sync:schema-mismatch":
|
|
336
|
+
return `schema mismatch client ${event.clientSchemaVersion} server ${event.serverSchemaVersion}`;
|
|
337
|
+
case "sync:apply-failed":
|
|
338
|
+
return `sync apply failed ${event.operationId}`;
|
|
339
|
+
case "sync:sent":
|
|
340
|
+
return `sync sent ${event.batchSize}`;
|
|
341
|
+
case "sync:received":
|
|
342
|
+
return `sync received ${event.batchSize}`;
|
|
343
|
+
case "sync:acknowledged":
|
|
344
|
+
return `sync ack ${event.sequenceNumber}`;
|
|
345
|
+
case "query:subscribed":
|
|
346
|
+
return `query subscribed ${event.collection}`;
|
|
347
|
+
case "query:invalidated":
|
|
348
|
+
return `query invalidated ${event.queryId}`;
|
|
349
|
+
case "query:executed":
|
|
350
|
+
return `query executed ${event.queryId}`;
|
|
351
|
+
case "connection:quality":
|
|
352
|
+
return `connection ${event.quality}`;
|
|
353
|
+
case "sync:diagnostics":
|
|
354
|
+
return `sync diagnostics ${event.diagnostics.quality}`;
|
|
355
|
+
case "sync:bandwidth":
|
|
356
|
+
return `bandwidth ${event.direction} ${event.bytesPerSecond}B/s`;
|
|
357
|
+
case "sync:initial-sync-progress":
|
|
358
|
+
return `initial sync ${Math.round(event.progress * 100)}%`;
|
|
359
|
+
case "awareness:updated":
|
|
360
|
+
return `awareness ${event.states.size} peers`;
|
|
361
|
+
case "state-machine:transition":
|
|
362
|
+
return `transition ${event.collection} ${event.from} \u2192 ${event.to}`;
|
|
363
|
+
case "state-machine:rejected":
|
|
364
|
+
return `transition rejected ${event.collection} ${event.from} \u2192 ${event.to}`;
|
|
365
|
+
case "store:persistence-error":
|
|
366
|
+
return `store persistence error ${event.message}`;
|
|
367
|
+
case "store:quota-exceeded":
|
|
368
|
+
return "store quota exceeded";
|
|
369
|
+
case "replay:completed":
|
|
370
|
+
return `replay ${event.operationsApplied} ops`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function timelineColor(type) {
|
|
374
|
+
if (type.startsWith("operation:")) return "#22c55e";
|
|
375
|
+
if (type.startsWith("sync:")) return "#a855f7";
|
|
376
|
+
if (type.startsWith("merge:") || type.startsWith("constraint:")) return "#f59e0b";
|
|
377
|
+
if (type.startsWith("query:")) return "#0ea5e9";
|
|
378
|
+
return "#64748b";
|
|
379
|
+
}
|
|
380
|
+
function extractCausalDependencies(event) {
|
|
381
|
+
const operation = extractOperation(event);
|
|
382
|
+
return operation?.causalDeps ?? [];
|
|
383
|
+
}
|
|
384
|
+
function extractOperation(event) {
|
|
385
|
+
switch (event.type) {
|
|
386
|
+
case "operation:created":
|
|
387
|
+
case "operation:applied":
|
|
388
|
+
return event.operation;
|
|
389
|
+
case "query:invalidated":
|
|
390
|
+
return event.trigger;
|
|
391
|
+
default:
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/ui/panel.ts
|
|
397
|
+
var html = import_htm.default.bind(import_preact.h);
|
|
398
|
+
var ALL_CATEGORIES = ["operation", "merge", "sync", "query", "connection"];
|
|
399
|
+
function DevToolsPanel({ events }) {
|
|
400
|
+
const [activeTab, setActiveTab] = (0, import_hooks.useState)("timeline");
|
|
401
|
+
const [search, setSearch] = (0, import_hooks.useState)("");
|
|
402
|
+
const [categories, setCategories] = (0, import_hooks.useState)(() => new Set(ALL_CATEGORIES));
|
|
403
|
+
const [expanded, setExpanded] = (0, import_hooks.useState)(() => /* @__PURE__ */ new Set());
|
|
404
|
+
const [paused, setPaused] = (0, import_hooks.useState)(false);
|
|
405
|
+
const toggleCategory = (0, import_hooks.useCallback)((cat) => {
|
|
406
|
+
setCategories((prev) => {
|
|
407
|
+
const next = new Set(prev);
|
|
408
|
+
if (next.has(cat)) next.delete(cat);
|
|
409
|
+
else next.add(cat);
|
|
410
|
+
return next;
|
|
411
|
+
});
|
|
412
|
+
}, []);
|
|
413
|
+
const toggleExpand = (0, import_hooks.useCallback)((id) => {
|
|
414
|
+
setExpanded((prev) => {
|
|
415
|
+
const next = new Set(prev);
|
|
416
|
+
if (next.has(id)) next.delete(id);
|
|
417
|
+
else next.add(id);
|
|
418
|
+
return next;
|
|
419
|
+
});
|
|
420
|
+
}, []);
|
|
421
|
+
const filtered = (0, import_hooks.useMemo)(
|
|
422
|
+
() => filterEvents(events, { categories: [...categories] }),
|
|
423
|
+
[events, categories]
|
|
424
|
+
);
|
|
425
|
+
const model = (0, import_hooks.useMemo)(() => buildPanelModel(filtered), [filtered]);
|
|
426
|
+
const stats = (0, import_hooks.useMemo)(() => computeStatistics(events), [events]);
|
|
427
|
+
return html`
|
|
428
|
+
<div class="kora-devtools">
|
|
429
|
+
<${Toolbar}
|
|
430
|
+
activeTab=${activeTab}
|
|
431
|
+
onTabChange=${setActiveTab}
|
|
432
|
+
totalEvents=${events.length}
|
|
433
|
+
conflicts=${stats.mergeConflicts}
|
|
434
|
+
search=${search}
|
|
435
|
+
onSearchChange=${setSearch}
|
|
436
|
+
paused=${paused}
|
|
437
|
+
onPauseToggle=${() => setPaused((p) => !p)}
|
|
438
|
+
onClear=${() => setExpanded(/* @__PURE__ */ new Set())}
|
|
439
|
+
categories=${categories}
|
|
440
|
+
onToggleCategory=${toggleCategory}
|
|
441
|
+
/>
|
|
442
|
+
<div class="kora-content">
|
|
443
|
+
${activeTab === "timeline" && html`<${TimelinePanel}
|
|
444
|
+
items=${model.timeline}
|
|
445
|
+
search=${search}
|
|
446
|
+
expanded=${expanded}
|
|
447
|
+
onToggle=${toggleExpand}
|
|
448
|
+
/>`}
|
|
449
|
+
${activeTab === "conflicts" && html`<${ConflictsPanel}
|
|
450
|
+
items=${model.conflicts}
|
|
451
|
+
search=${search}
|
|
452
|
+
expanded=${expanded}
|
|
453
|
+
onToggle=${toggleExpand}
|
|
454
|
+
/>`}
|
|
455
|
+
${activeTab === "operations" && html`<${OperationsPanel}
|
|
456
|
+
items=${model.operations}
|
|
457
|
+
search=${search}
|
|
458
|
+
expanded=${expanded}
|
|
459
|
+
onToggle=${toggleExpand}
|
|
460
|
+
/>`}
|
|
461
|
+
${activeTab === "network" && html`<${NetworkPanel} network=${model.network} events=${events} />`}
|
|
462
|
+
</div>
|
|
463
|
+
</div>
|
|
464
|
+
`;
|
|
465
|
+
}
|
|
466
|
+
function Toolbar({
|
|
467
|
+
activeTab,
|
|
468
|
+
onTabChange,
|
|
469
|
+
totalEvents,
|
|
470
|
+
conflicts,
|
|
471
|
+
search,
|
|
472
|
+
onSearchChange,
|
|
473
|
+
paused,
|
|
474
|
+
onPauseToggle,
|
|
475
|
+
onClear,
|
|
476
|
+
categories,
|
|
477
|
+
onToggleCategory
|
|
478
|
+
}) {
|
|
479
|
+
const tabs = [
|
|
480
|
+
{ id: "timeline", label: "Timeline", badge: totalEvents },
|
|
481
|
+
{ id: "conflicts", label: "Conflicts", badge: conflicts },
|
|
482
|
+
{ id: "operations", label: "Operations" },
|
|
483
|
+
{ id: "network", label: "Network" }
|
|
484
|
+
];
|
|
485
|
+
return html`
|
|
486
|
+
<div class="kora-toolbar">
|
|
487
|
+
<div class="kora-tabs">
|
|
488
|
+
${tabs.map(
|
|
489
|
+
(tab) => html`
|
|
490
|
+
<button
|
|
491
|
+
class="kora-tab${activeTab === tab.id ? " active" : ""}"
|
|
492
|
+
onClick=${() => onTabChange(tab.id)}
|
|
493
|
+
>
|
|
494
|
+
${tab.label}
|
|
495
|
+
${tab.badge != null && tab.badge > 0 ? html`<span class="kora-badge">${tab.badge}</span>` : null}
|
|
496
|
+
</button>
|
|
497
|
+
`
|
|
498
|
+
)}
|
|
499
|
+
</div>
|
|
500
|
+
<div class="kora-controls">
|
|
501
|
+
<input
|
|
502
|
+
type="text"
|
|
503
|
+
class="kora-search"
|
|
504
|
+
placeholder="Filter..."
|
|
505
|
+
value=${search}
|
|
506
|
+
onInput=${(e) => onSearchChange(e.target.value)}
|
|
507
|
+
/>
|
|
508
|
+
<button class="kora-btn" onClick=${onPauseToggle}>
|
|
509
|
+
${paused ? "Resume" : "Pause"}
|
|
510
|
+
</button>
|
|
511
|
+
<button class="kora-btn" onClick=${onClear}>Clear</button>
|
|
512
|
+
${ALL_CATEGORIES.map(
|
|
513
|
+
(cat) => html`
|
|
514
|
+
<button
|
|
515
|
+
class="kora-cat-toggle${categories.has(cat) ? " active" : ""}"
|
|
516
|
+
onClick=${() => onToggleCategory(cat)}
|
|
517
|
+
>
|
|
518
|
+
${cat}
|
|
519
|
+
</button>
|
|
520
|
+
`
|
|
521
|
+
)}
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
`;
|
|
525
|
+
}
|
|
526
|
+
function TimelinePanel({
|
|
527
|
+
items,
|
|
528
|
+
search,
|
|
529
|
+
expanded,
|
|
530
|
+
onToggle
|
|
531
|
+
}) {
|
|
532
|
+
const lowerSearch = search.toLowerCase();
|
|
533
|
+
const filtered = lowerSearch ? items.filter(
|
|
534
|
+
(i) => i.label.toLowerCase().includes(lowerSearch) || i.type.toLowerCase().includes(lowerSearch)
|
|
535
|
+
) : items;
|
|
536
|
+
const visible = filtered.slice(-200).reverse();
|
|
537
|
+
if (visible.length === 0) {
|
|
538
|
+
return html`<div class="kora-panel"><div class="kora-empty">No events recorded yet.</div></div>`;
|
|
539
|
+
}
|
|
540
|
+
return html`
|
|
541
|
+
<div class="kora-panel kora-timeline">
|
|
542
|
+
<div class="kora-summary">Showing ${visible.length} of ${items.length} events</div>
|
|
543
|
+
<div class="kora-event-list">
|
|
544
|
+
${visible.map(
|
|
545
|
+
(item) => html`
|
|
546
|
+
<div
|
|
547
|
+
key=${item.id}
|
|
548
|
+
class="kora-row${expanded.has(item.id) ? " expanded" : ""}"
|
|
549
|
+
onClick=${() => onToggle(item.id)}
|
|
550
|
+
>
|
|
551
|
+
<span class="kora-time">${formatTime(item.receivedAt)}</span>
|
|
552
|
+
<span class="kora-dot" style="background:${item.color}"></span>
|
|
553
|
+
<span class="kora-type">${item.type}</span>
|
|
554
|
+
<span class="kora-label">${item.label}</span>
|
|
555
|
+
${item.dependsOn.length > 0 ? html`<span class="kora-deps">deps: ${item.dependsOn.length}</span>` : null}
|
|
556
|
+
</div>
|
|
557
|
+
${expanded.has(item.id) ? html`
|
|
558
|
+
<div class="kora-detail">
|
|
559
|
+
<div>
|
|
560
|
+
<span class="kora-detail-label">Event ID:</span> ${item.id}
|
|
561
|
+
</div>
|
|
562
|
+
<div>
|
|
563
|
+
<span class="kora-detail-label">Received:</span>
|
|
564
|
+
${new Date(item.receivedAt).toISOString()}
|
|
565
|
+
</div>
|
|
566
|
+
${item.dependsOn.length > 0 ? html`<div>
|
|
567
|
+
<span class="kora-detail-label">Causal deps:</span>
|
|
568
|
+
${item.dependsOn.join(", ")}
|
|
569
|
+
</div>` : null}
|
|
570
|
+
</div>
|
|
571
|
+
` : null}
|
|
572
|
+
`
|
|
573
|
+
)}
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
function ConflictsPanel({
|
|
579
|
+
items,
|
|
580
|
+
search,
|
|
581
|
+
expanded,
|
|
582
|
+
onToggle
|
|
583
|
+
}) {
|
|
584
|
+
const lowerSearch = search.toLowerCase();
|
|
585
|
+
const filtered = lowerSearch ? items.filter(
|
|
586
|
+
(i) => i.collection.toLowerCase().includes(lowerSearch) || i.field.toLowerCase().includes(lowerSearch) || i.strategy.toLowerCase().includes(lowerSearch)
|
|
587
|
+
) : items;
|
|
588
|
+
const visible = filtered.slice(-100).reverse();
|
|
589
|
+
if (visible.length === 0) {
|
|
590
|
+
return html`<div class="kora-panel"><div class="kora-empty">No merge conflicts detected.</div></div>`;
|
|
591
|
+
}
|
|
592
|
+
return html`
|
|
593
|
+
<div class="kora-panel kora-conflicts">
|
|
594
|
+
<div class="kora-summary">
|
|
595
|
+
${items.length} conflict${items.length !== 1 ? "s" : ""}
|
|
596
|
+
</div>
|
|
597
|
+
<table class="kora-table">
|
|
598
|
+
<thead>
|
|
599
|
+
<tr>
|
|
600
|
+
<th>Time</th>
|
|
601
|
+
<th>Collection</th>
|
|
602
|
+
<th>Field</th>
|
|
603
|
+
<th>Strategy</th>
|
|
604
|
+
<th>Tier</th>
|
|
605
|
+
<th>Result</th>
|
|
606
|
+
</tr>
|
|
607
|
+
</thead>
|
|
608
|
+
<tbody>
|
|
609
|
+
${visible.map(
|
|
610
|
+
(item) => html`
|
|
611
|
+
<tr
|
|
612
|
+
key=${item.id}
|
|
613
|
+
class="kora-conflict-row${item.constraintViolated ? " violated" : ""}${expanded.has(item.id) ? " expanded" : ""}"
|
|
614
|
+
onClick=${() => onToggle(item.id)}
|
|
615
|
+
>
|
|
616
|
+
<td>${formatTime(item.timestamp)}</td>
|
|
617
|
+
<td>${item.collection}</td>
|
|
618
|
+
<td class="kora-mono">${item.field}</td>
|
|
619
|
+
<td>
|
|
620
|
+
<span class="kora-strategy tier-${item.tier}">${item.strategy}</span>
|
|
621
|
+
</td>
|
|
622
|
+
<td>${item.tier}</td>
|
|
623
|
+
<td class="kora-mono">${truncate(String(item.output), 30)}</td>
|
|
624
|
+
</tr>
|
|
625
|
+
${expanded.has(item.id) ? html`
|
|
626
|
+
<tr class="kora-detail-row">
|
|
627
|
+
<td colspan="6">
|
|
628
|
+
<div class="kora-conflict-detail">
|
|
629
|
+
<div class="kora-comparison">
|
|
630
|
+
<div class="kora-value-box">
|
|
631
|
+
<div class="kora-value-label">Input A (local)</div>
|
|
632
|
+
<pre class="kora-value-code">
|
|
633
|
+
${formatValue(item.inputA)}</pre
|
|
634
|
+
>
|
|
635
|
+
</div>
|
|
636
|
+
<div class="kora-value-box">
|
|
637
|
+
<div class="kora-value-label">Input B (remote)</div>
|
|
638
|
+
<pre class="kora-value-code">
|
|
639
|
+
${formatValue(item.inputB)}</pre
|
|
640
|
+
>
|
|
641
|
+
</div>
|
|
642
|
+
<div class="kora-value-box result">
|
|
643
|
+
<div class="kora-value-label">Result</div>
|
|
644
|
+
<pre class="kora-value-code">
|
|
645
|
+
${formatValue(item.output)}</pre
|
|
646
|
+
>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
${item.constraintViolated ? html`<div class="kora-constraint-warning">
|
|
650
|
+
Constraint violated: ${item.constraintViolated}
|
|
651
|
+
</div>` : null}
|
|
652
|
+
</div>
|
|
653
|
+
</td>
|
|
654
|
+
</tr>
|
|
655
|
+
` : null}
|
|
656
|
+
`
|
|
657
|
+
)}
|
|
658
|
+
</tbody>
|
|
659
|
+
</table>
|
|
660
|
+
</div>
|
|
661
|
+
`;
|
|
662
|
+
}
|
|
663
|
+
function OperationsPanel({
|
|
664
|
+
items,
|
|
665
|
+
search,
|
|
666
|
+
expanded,
|
|
667
|
+
onToggle
|
|
668
|
+
}) {
|
|
669
|
+
const lowerSearch = search.toLowerCase();
|
|
670
|
+
const filtered = lowerSearch ? items.filter(
|
|
671
|
+
(i) => i.collection.toLowerCase().includes(lowerSearch) || i.recordId.toLowerCase().includes(lowerSearch) || i.operationId.toLowerCase().includes(lowerSearch) || i.opType.toLowerCase().includes(lowerSearch)
|
|
672
|
+
) : items;
|
|
673
|
+
const visible = filtered.slice(-200).reverse();
|
|
674
|
+
if (visible.length === 0) {
|
|
675
|
+
return html`<div class="kora-panel"><div class="kora-empty">No operations recorded yet.</div></div>`;
|
|
676
|
+
}
|
|
677
|
+
return html`
|
|
678
|
+
<div class="kora-panel kora-operations">
|
|
679
|
+
<div class="kora-summary">
|
|
680
|
+
Showing ${visible.length} of ${items.length} operations
|
|
681
|
+
</div>
|
|
682
|
+
<div class="kora-op-list">
|
|
683
|
+
${visible.map(
|
|
684
|
+
(item) => html`
|
|
685
|
+
<div
|
|
686
|
+
key=${item.id}
|
|
687
|
+
class="kora-op-row${expanded.has(item.id) ? " expanded" : ""}"
|
|
688
|
+
onClick=${() => onToggle(item.id)}
|
|
689
|
+
>
|
|
690
|
+
<span class="kora-time">${formatTime(item.timestamp)}</span>
|
|
691
|
+
<span class="kora-op-type op-${item.opType}">${item.opType}</span>
|
|
692
|
+
<span class="kora-op-collection">${item.collection}</span>
|
|
693
|
+
<span class="kora-op-record kora-mono"
|
|
694
|
+
>${truncate(item.recordId, 12)}</span
|
|
695
|
+
>
|
|
696
|
+
<span class="kora-op-node">node:${truncate(item.nodeId, 8)}</span>
|
|
697
|
+
<span class="kora-op-seq">#${item.sequenceNumber}</span>
|
|
698
|
+
</div>
|
|
699
|
+
${expanded.has(item.id) ? html`
|
|
700
|
+
<div class="kora-op-detail">
|
|
701
|
+
<div>
|
|
702
|
+
<span class="kora-detail-label">Operation ID:</span>
|
|
703
|
+
<span class="kora-mono">${item.operationId}</span>
|
|
704
|
+
</div>
|
|
705
|
+
<div>
|
|
706
|
+
<span class="kora-detail-label">Node:</span>
|
|
707
|
+
<span class="kora-mono">${item.nodeId}</span>
|
|
708
|
+
</div>
|
|
709
|
+
<div>
|
|
710
|
+
<span class="kora-detail-label">Sequence:</span>
|
|
711
|
+
${item.sequenceNumber}
|
|
712
|
+
</div>
|
|
713
|
+
${item.causalDeps.length > 0 ? html`<div>
|
|
714
|
+
<span class="kora-detail-label">Causal deps:</span>
|
|
715
|
+
<span class="kora-mono"
|
|
716
|
+
>${item.causalDeps.join(", ")}</span
|
|
717
|
+
>
|
|
718
|
+
</div>` : null}
|
|
719
|
+
${item.data ? html`<div>
|
|
720
|
+
<div class="kora-detail-label">Data:</div>
|
|
721
|
+
<pre class="kora-value-code">
|
|
722
|
+
${JSON.stringify(item.data, null, 2)}</pre
|
|
723
|
+
>
|
|
724
|
+
</div>` : null}
|
|
725
|
+
</div>
|
|
726
|
+
` : null}
|
|
727
|
+
`
|
|
728
|
+
)}
|
|
729
|
+
</div>
|
|
730
|
+
</div>
|
|
731
|
+
`;
|
|
732
|
+
}
|
|
733
|
+
function NetworkPanel({
|
|
734
|
+
network,
|
|
735
|
+
events
|
|
736
|
+
}) {
|
|
737
|
+
const statusClass = network.connected ? "connected" : "disconnected";
|
|
738
|
+
const statusText = network.connected ? network.quality ? `Connected (${network.quality})` : "Connected" : "Disconnected";
|
|
739
|
+
const syncEvents = events.filter((e) => {
|
|
740
|
+
const cat = eventTypeToCategory(e.event.type);
|
|
741
|
+
return cat === "sync" || cat === "connection";
|
|
742
|
+
}).slice(-50).reverse();
|
|
743
|
+
return html`
|
|
744
|
+
<div class="kora-panel kora-network">
|
|
745
|
+
<div class="kora-network-status">
|
|
746
|
+
<div class="kora-status-indicator ${statusClass}">
|
|
747
|
+
<span class="kora-status-dot"></span>
|
|
748
|
+
<span class="kora-status-text">${statusText}</span>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
752
|
+
<div class="kora-stats-grid">
|
|
753
|
+
<${StatCard} label="Sent" value=${String(network.sentOps)} unit="ops" />
|
|
754
|
+
<${StatCard} label="Received" value=${String(network.receivedOps)} unit="ops" />
|
|
755
|
+
<${StatCard} label="Pending" value=${String(network.pendingAcks)} unit="acks" />
|
|
756
|
+
<${StatCard}
|
|
757
|
+
label="Last Sync"
|
|
758
|
+
value=${network.lastSyncAt ? formatTime(network.lastSyncAt) : "never"}
|
|
759
|
+
/>
|
|
760
|
+
</div>
|
|
761
|
+
|
|
762
|
+
${network.versionVector.length > 0 ? html`
|
|
763
|
+
<div class="kora-vv-section">
|
|
764
|
+
<h3>Version Vector</h3>
|
|
765
|
+
<table class="kora-table kora-vv-table">
|
|
766
|
+
<thead>
|
|
767
|
+
<tr>
|
|
768
|
+
<th>Node</th>
|
|
769
|
+
<th>Sequence</th>
|
|
770
|
+
</tr>
|
|
771
|
+
</thead>
|
|
772
|
+
<tbody>
|
|
773
|
+
${network.versionVector.map(
|
|
774
|
+
(entry) => html`
|
|
775
|
+
<tr key=${entry.nodeId}>
|
|
776
|
+
<td class="kora-mono">${truncate(entry.nodeId, 16)}</td>
|
|
777
|
+
<td>${entry.sequenceNumber}</td>
|
|
778
|
+
</tr>
|
|
779
|
+
`
|
|
780
|
+
)}
|
|
781
|
+
</tbody>
|
|
782
|
+
</table>
|
|
783
|
+
</div>
|
|
784
|
+
` : null}
|
|
785
|
+
|
|
786
|
+
${syncEvents.length > 0 ? html`
|
|
787
|
+
<div class="kora-sync-log">
|
|
788
|
+
<h3>Recent Sync Activity</h3>
|
|
789
|
+
<div class="kora-event-list">
|
|
790
|
+
${syncEvents.map(
|
|
791
|
+
(entry) => html`
|
|
792
|
+
<div key=${entry.id} class="kora-row compact">
|
|
793
|
+
<span class="kora-time"
|
|
794
|
+
>${formatTime(entry.receivedAt)}</span
|
|
795
|
+
>
|
|
796
|
+
<span class="kora-type">${entry.event.type}</span>
|
|
797
|
+
</div>
|
|
798
|
+
`
|
|
799
|
+
)}
|
|
800
|
+
</div>
|
|
801
|
+
</div>
|
|
802
|
+
` : null}
|
|
803
|
+
</div>
|
|
804
|
+
`;
|
|
805
|
+
}
|
|
806
|
+
function StatCard({
|
|
807
|
+
label,
|
|
808
|
+
value,
|
|
809
|
+
unit
|
|
810
|
+
}) {
|
|
811
|
+
return html`
|
|
812
|
+
<div class="kora-stat-card">
|
|
813
|
+
<div class="kora-stat-value">${value}</div>
|
|
814
|
+
<div class="kora-stat-label">${label}${unit ? ` (${unit})` : ""}</div>
|
|
815
|
+
</div>
|
|
816
|
+
`;
|
|
817
|
+
}
|
|
818
|
+
function renderDevtoolsPanel(target, events) {
|
|
819
|
+
(0, import_preact.render)(html`<${DevToolsPanel} events=${events} />`, target);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// src/ui/panel-styles.ts
|
|
823
|
+
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 }";
|
|
824
|
+
|
|
825
|
+
// src/overlay/embed-overlay.ts
|
|
826
|
+
var OVERLAY_HOST_ID = "kora-devtools-overlay-host";
|
|
827
|
+
function mountKoraDevtoolsOverlay(instrumenter) {
|
|
828
|
+
if (typeof document === "undefined") {
|
|
829
|
+
return () => {
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const existing = document.getElementById(OVERLAY_HOST_ID);
|
|
833
|
+
if (existing) {
|
|
834
|
+
existing.remove();
|
|
835
|
+
}
|
|
836
|
+
const host = document.createElement("div");
|
|
837
|
+
host.id = OVERLAY_HOST_ID;
|
|
838
|
+
host.style.cssText = "position:fixed;inset:auto 12px 12px 12px;height:42vh;z-index:2147483646;display:none;";
|
|
839
|
+
const shadow = host.attachShadow({ mode: "open" });
|
|
840
|
+
const style = document.createElement("style");
|
|
841
|
+
style.textContent = KORA_DEVTOOLS_STYLES;
|
|
842
|
+
shadow.appendChild(style);
|
|
843
|
+
const panelRoot = document.createElement("div");
|
|
844
|
+
panelRoot.id = "kora-devtools-root";
|
|
845
|
+
panelRoot.style.height = "100%";
|
|
846
|
+
shadow.appendChild(panelRoot);
|
|
847
|
+
const hint = document.createElement("div");
|
|
848
|
+
hint.textContent = "Kora DevTools (Ctrl+Shift+K)";
|
|
849
|
+
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;";
|
|
850
|
+
document.body.appendChild(hint);
|
|
851
|
+
document.body.appendChild(host);
|
|
852
|
+
let visible = false;
|
|
853
|
+
const setVisible = (next) => {
|
|
854
|
+
visible = next;
|
|
855
|
+
host.style.display = visible ? "block" : "none";
|
|
856
|
+
hint.style.display = visible ? "none" : "block";
|
|
857
|
+
};
|
|
858
|
+
const refresh = () => {
|
|
859
|
+
if (!visible) return;
|
|
860
|
+
renderDevtoolsPanel(panelRoot, instrumenter.getBuffer().getAll());
|
|
861
|
+
};
|
|
862
|
+
const intervalId = window.setInterval(refresh, 300);
|
|
863
|
+
const onKeyDown = (event) => {
|
|
864
|
+
const isToggle = event.key === "K" && event.shiftKey && (event.ctrlKey || event.metaKey);
|
|
865
|
+
if (!isToggle) return;
|
|
866
|
+
event.preventDefault();
|
|
867
|
+
setVisible(!visible);
|
|
868
|
+
if (visible) {
|
|
869
|
+
refresh();
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
window.addEventListener("keydown", onKeyDown);
|
|
873
|
+
return () => {
|
|
874
|
+
window.clearInterval(intervalId);
|
|
875
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
876
|
+
host.remove();
|
|
877
|
+
hint.remove();
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
881
|
+
0 && (module.exports = {
|
|
882
|
+
mountKoraDevtoolsOverlay
|
|
883
|
+
});
|
|
884
|
+
//# sourceMappingURL=overlay.cjs.map
|