devlog-ui 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -109
- package/dist/index.cjs +66 -24
- package/dist/index.js +199 -157
- package/dist/noop.cjs +0 -1
- package/dist/noop.js +0 -1
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/noop.cjs.map +0 -1
- package/dist/noop.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Devlog-UI
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A feature-rich, browser-based dev logger with a beautiful debug UI. Zero dependencies, framework-agnostic, production-safe.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -28,16 +28,16 @@ npm install devlogger
|
|
|
28
28
|
## Quick Start
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
import { logger, DevLoggerUI } from
|
|
31
|
+
import { logger, DevLoggerUI } from "devlogger";
|
|
32
32
|
|
|
33
33
|
// Initialize the UI (once, at app start)
|
|
34
34
|
DevLoggerUI.init();
|
|
35
35
|
|
|
36
36
|
// Log messages with automatic source tracking
|
|
37
|
-
logger.info(
|
|
38
|
-
logger.debug(
|
|
39
|
-
logger.warn(
|
|
40
|
-
logger.error(
|
|
37
|
+
logger.info("App started");
|
|
38
|
+
logger.debug("Loading config", { theme: "dark" });
|
|
39
|
+
logger.warn("Cache miss", { key: "user_prefs" });
|
|
40
|
+
logger.error("API failed", new Error("Network timeout"));
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## API Reference
|
|
@@ -64,9 +64,9 @@ logger.error(message: string, ...data: unknown[]): void
|
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
66
|
logger.configure({
|
|
67
|
-
maxLogs: 1000,
|
|
68
|
-
minLevel:
|
|
69
|
-
enabled: true,
|
|
67
|
+
maxLogs: 1000, // Max logs in memory (FIFO rotation)
|
|
68
|
+
minLevel: "debug", // Minimum level: 'debug' | 'info' | 'warn' | 'error'
|
|
69
|
+
enabled: true, // Enable/disable logging
|
|
70
70
|
});
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -81,7 +81,7 @@ logger.clear();
|
|
|
81
81
|
|
|
82
82
|
// Subscribe to new logs
|
|
83
83
|
const unsubscribe = logger.subscribe((log: LogEvent) => {
|
|
84
|
-
console.log(
|
|
84
|
+
console.log("New log:", log);
|
|
85
85
|
});
|
|
86
86
|
unsubscribe(); // Stop receiving logs
|
|
87
87
|
|
|
@@ -98,29 +98,29 @@ Group related logs together with timing and status:
|
|
|
98
98
|
|
|
99
99
|
```typescript
|
|
100
100
|
// Create a span for an operation
|
|
101
|
-
const span = logger.span(
|
|
102
|
-
span.info(
|
|
103
|
-
span.debug(
|
|
101
|
+
const span = logger.span("Load user profile");
|
|
102
|
+
span.info("Fetching from API...");
|
|
103
|
+
span.debug("Request payload", { userId: 123 });
|
|
104
104
|
|
|
105
105
|
// End successfully
|
|
106
106
|
span.end(); // status: 'success', duration calculated
|
|
107
107
|
|
|
108
108
|
// Or end with error
|
|
109
|
-
span.fail(
|
|
110
|
-
span.fail(new Error(
|
|
109
|
+
span.fail("Network timeout"); // status: 'error'
|
|
110
|
+
span.fail(new Error("Timeout")); // also logs the error
|
|
111
111
|
```
|
|
112
112
|
|
|
113
113
|
#### Nested Spans
|
|
114
114
|
|
|
115
115
|
```typescript
|
|
116
|
-
const requestSpan = logger.span(
|
|
116
|
+
const requestSpan = logger.span("HTTP Request", { requestId: "abc-123" });
|
|
117
117
|
|
|
118
|
-
const fetchSpan = requestSpan.span(
|
|
119
|
-
fetchSpan.info(
|
|
118
|
+
const fetchSpan = requestSpan.span("Fetch Data");
|
|
119
|
+
fetchSpan.info("Fetching...");
|
|
120
120
|
fetchSpan.end();
|
|
121
121
|
|
|
122
|
-
const processSpan = requestSpan.span(
|
|
123
|
-
processSpan.info(
|
|
122
|
+
const processSpan = requestSpan.span("Process Data");
|
|
123
|
+
processSpan.info("Processing...");
|
|
124
124
|
processSpan.end();
|
|
125
125
|
|
|
126
126
|
requestSpan.end(); // Parent span ends after children
|
|
@@ -140,7 +140,7 @@ const spanLogs = logger.getSpanLogs(spanId);
|
|
|
140
140
|
|
|
141
141
|
// Subscribe to span events
|
|
142
142
|
const unsub = logger.subscribeSpans((span) => {
|
|
143
|
-
if (span.status ===
|
|
143
|
+
if (span.status === "error") {
|
|
144
144
|
console.log(`Span ${span.name} failed after ${span.duration}ms`);
|
|
145
145
|
}
|
|
146
146
|
});
|
|
@@ -152,10 +152,10 @@ Attach contextual information to logs for filtering and correlation:
|
|
|
152
152
|
|
|
153
153
|
```typescript
|
|
154
154
|
// Set global context (attached to ALL logs)
|
|
155
|
-
logger.setGlobalContext({ env:
|
|
155
|
+
logger.setGlobalContext({ env: "development", build: "1.2.3" });
|
|
156
156
|
|
|
157
157
|
// Update global context
|
|
158
|
-
logger.updateGlobalContext({ userId:
|
|
158
|
+
logger.updateGlobalContext({ userId: "user-456" });
|
|
159
159
|
|
|
160
160
|
// Clear global context
|
|
161
161
|
logger.clearGlobalContext();
|
|
@@ -165,16 +165,16 @@ logger.clearGlobalContext();
|
|
|
165
165
|
|
|
166
166
|
```typescript
|
|
167
167
|
// Create a logger with specific context
|
|
168
|
-
const reqLogger = logger.withContext({ requestId:
|
|
169
|
-
reqLogger.info(
|
|
168
|
+
const reqLogger = logger.withContext({ requestId: "req-123" });
|
|
169
|
+
reqLogger.info("Request started"); // includes requestId
|
|
170
170
|
|
|
171
171
|
// Chain contexts
|
|
172
|
-
const userLogger = reqLogger.withContext({ userId:
|
|
173
|
-
userLogger.info(
|
|
172
|
+
const userLogger = reqLogger.withContext({ userId: "user-456" });
|
|
173
|
+
userLogger.info("User action"); // includes both requestId and userId
|
|
174
174
|
|
|
175
175
|
// Context loggers can also create spans
|
|
176
|
-
const span = reqLogger.span(
|
|
177
|
-
span.info(
|
|
176
|
+
const span = reqLogger.span("Process Request");
|
|
177
|
+
span.info("Processing..."); // inherits requestId
|
|
178
178
|
span.end();
|
|
179
179
|
```
|
|
180
180
|
|
|
@@ -184,26 +184,26 @@ Export logs for sharing, bug reports, or analysis:
|
|
|
184
184
|
|
|
185
185
|
```typescript
|
|
186
186
|
// Export as JSON (pretty printed)
|
|
187
|
-
const json = logger.exportLogs({ format:
|
|
187
|
+
const json = logger.exportLogs({ format: "json" });
|
|
188
188
|
|
|
189
189
|
// Export as compact JSON
|
|
190
|
-
const compact = logger.exportLogs({ format:
|
|
190
|
+
const compact = logger.exportLogs({ format: "json", pretty: false });
|
|
191
191
|
|
|
192
192
|
// Export as human-readable text
|
|
193
|
-
const text = logger.exportLogs({ format:
|
|
193
|
+
const text = logger.exportLogs({ format: "text" });
|
|
194
194
|
|
|
195
195
|
// Filter exports
|
|
196
196
|
const filtered = logger.exportLogs({
|
|
197
|
-
format:
|
|
198
|
-
levels: [
|
|
199
|
-
lastMs: 30000,
|
|
200
|
-
search:
|
|
197
|
+
format: "json",
|
|
198
|
+
levels: ["warn", "error"], // Only warnings and errors
|
|
199
|
+
lastMs: 30000, // Last 30 seconds
|
|
200
|
+
search: "user", // Contains "user"
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
// Copy to clipboard
|
|
204
|
-
const success = await logger.copyLogs({ format:
|
|
204
|
+
const success = await logger.copyLogs({ format: "json" });
|
|
205
205
|
if (success) {
|
|
206
|
-
console.log(
|
|
206
|
+
console.log("Logs copied!");
|
|
207
207
|
}
|
|
208
208
|
```
|
|
209
209
|
|
|
@@ -213,29 +213,34 @@ Compare objects and log changes with color-coded visualization:
|
|
|
213
213
|
|
|
214
214
|
```typescript
|
|
215
215
|
// Log a diff with automatic change detection
|
|
216
|
-
const oldConfig = { theme:
|
|
217
|
-
const newConfig = { theme:
|
|
216
|
+
const oldConfig = { theme: "light", fontSize: 14 };
|
|
217
|
+
const newConfig = { theme: "dark", fontSize: 14, language: "en" };
|
|
218
218
|
|
|
219
|
-
const diff = logger.diff(
|
|
219
|
+
const diff = logger.diff("Config updated", oldConfig, newConfig);
|
|
220
220
|
// Logs with visual diff: +1 added, ~1 changed
|
|
221
221
|
|
|
222
222
|
console.log(diff.summary);
|
|
223
223
|
// { added: 1, removed: 0, changed: 1, unchanged: 1 }
|
|
224
224
|
|
|
225
225
|
// Specify log level
|
|
226
|
-
logger.diff(
|
|
226
|
+
logger.diff("Breaking change", oldApi, newApi, "warn");
|
|
227
227
|
|
|
228
228
|
// Compute diff without logging
|
|
229
229
|
const result = logger.computeDiff(objA, objB);
|
|
230
230
|
if (result.summary.changed > 0) {
|
|
231
|
-
logger.warn(
|
|
231
|
+
logger.warn("Objects differ!", result.changes);
|
|
232
232
|
}
|
|
233
233
|
```
|
|
234
234
|
|
|
235
235
|
#### Diff Utilities
|
|
236
236
|
|
|
237
237
|
```typescript
|
|
238
|
-
import {
|
|
238
|
+
import {
|
|
239
|
+
computeDiff,
|
|
240
|
+
createDiffResult,
|
|
241
|
+
hasChanges,
|
|
242
|
+
formatValue,
|
|
243
|
+
} from "devlogger";
|
|
239
244
|
|
|
240
245
|
// Low-level diff computation
|
|
241
246
|
const changes = computeDiff(oldObj, newObj);
|
|
@@ -247,7 +252,7 @@ const result = createDiffResult(oldObj, newObj);
|
|
|
247
252
|
|
|
248
253
|
// Quick check for any changes
|
|
249
254
|
if (hasChanges(result)) {
|
|
250
|
-
console.log(
|
|
255
|
+
console.log("Objects are different");
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
// Format values for display
|
|
@@ -260,32 +265,33 @@ formatValue([1, 2, 3, 4, 5]); // "[5 items]"
|
|
|
260
265
|
Automatically track Fetch and XHR requests with spans:
|
|
261
266
|
|
|
262
267
|
```typescript
|
|
263
|
-
import { NetworkCapture } from
|
|
268
|
+
import { NetworkCapture } from "devlogger";
|
|
264
269
|
|
|
265
270
|
// Install at app start
|
|
266
271
|
NetworkCapture.install();
|
|
267
272
|
|
|
268
273
|
// All fetch calls are now automatically logged
|
|
269
|
-
await fetch(
|
|
274
|
+
await fetch("/api/users"); // Creates a span with timing
|
|
270
275
|
|
|
271
276
|
// With configuration
|
|
272
277
|
NetworkCapture.install({
|
|
273
|
-
captureFetch: true,
|
|
274
|
-
captureXHR: true,
|
|
275
|
-
includeHeaders: true,
|
|
276
|
-
includeBody: true,
|
|
277
|
-
includeResponse: true,
|
|
278
|
-
maxResponseLength: 5000,
|
|
279
|
-
ignorePatterns: [
|
|
280
|
-
|
|
278
|
+
captureFetch: true, // Hook into fetch (default: true)
|
|
279
|
+
captureXHR: true, // Hook into XHR (default: true)
|
|
280
|
+
includeHeaders: true, // Log request headers (default: false)
|
|
281
|
+
includeBody: true, // Log request body (default: false)
|
|
282
|
+
includeResponse: true, // Log response body (default: false)
|
|
283
|
+
maxResponseLength: 5000, // Max response chars to capture
|
|
284
|
+
ignorePatterns: [
|
|
285
|
+
// URLs to ignore
|
|
286
|
+
"/analytics",
|
|
281
287
|
/\.hot-update\./,
|
|
282
288
|
/sockjs/,
|
|
283
289
|
],
|
|
284
|
-
context: { service:
|
|
290
|
+
context: { service: "api" }, // Context for all network logs
|
|
285
291
|
});
|
|
286
292
|
|
|
287
293
|
// Add ignore patterns dynamically
|
|
288
|
-
NetworkCapture.addIgnorePattern(
|
|
294
|
+
NetworkCapture.addIgnorePattern("/health");
|
|
289
295
|
|
|
290
296
|
// Check status
|
|
291
297
|
NetworkCapture.isActive();
|
|
@@ -296,6 +302,7 @@ NetworkCapture.uninstall();
|
|
|
296
302
|
```
|
|
297
303
|
|
|
298
304
|
Network requests create spans automatically:
|
|
305
|
+
|
|
299
306
|
```
|
|
300
307
|
[info] GET /api/users
|
|
301
308
|
└─ span: "GET /api/users" (234ms, success)
|
|
@@ -309,16 +316,16 @@ Network requests create spans automatically:
|
|
|
309
316
|
Visualize logs and spans on a canvas-based timeline:
|
|
310
317
|
|
|
311
318
|
```typescript
|
|
312
|
-
import { createTimeline, Timeline } from
|
|
319
|
+
import { createTimeline, Timeline } from "devlogger";
|
|
313
320
|
|
|
314
321
|
// Create timeline in a container
|
|
315
322
|
const timeline = createTimeline({
|
|
316
|
-
container:
|
|
317
|
-
timeWindow: 60000,
|
|
318
|
-
refreshInterval: 100,
|
|
319
|
-
showSpans: true,
|
|
320
|
-
showLogs: true,
|
|
321
|
-
height: 200,
|
|
323
|
+
container: "#timeline-container", // CSS selector or HTMLElement
|
|
324
|
+
timeWindow: 60000, // Show last 60 seconds
|
|
325
|
+
refreshInterval: 100, // Refresh rate in ms
|
|
326
|
+
showSpans: true, // Display span bars
|
|
327
|
+
showLogs: true, // Display log markers
|
|
328
|
+
height: 200, // Canvas height in pixels
|
|
322
329
|
});
|
|
323
330
|
|
|
324
331
|
// Update time window
|
|
@@ -329,6 +336,7 @@ timeline.destroy();
|
|
|
329
336
|
```
|
|
330
337
|
|
|
331
338
|
Timeline features:
|
|
339
|
+
|
|
332
340
|
- Color-coded log markers (debug/info/warn/error)
|
|
333
341
|
- Span bars with duration and nesting
|
|
334
342
|
- Hover tooltips with details
|
|
@@ -355,9 +363,9 @@ DevLoggerUI.isPopoutOpen();
|
|
|
355
363
|
|
|
356
364
|
// Filter logs programmatically
|
|
357
365
|
DevLoggerUI.setFilter({
|
|
358
|
-
levels: new Set([
|
|
359
|
-
search:
|
|
360
|
-
file:
|
|
366
|
+
levels: new Set(["warn", "error"]), // Show only warnings and errors
|
|
367
|
+
search: "api", // Text search
|
|
368
|
+
file: "utils", // Filter by file name
|
|
361
369
|
});
|
|
362
370
|
DevLoggerUI.getFilter();
|
|
363
371
|
DevLoggerUI.clearFilter();
|
|
@@ -379,17 +387,17 @@ Press `Ctrl+Shift+L` to toggle the debug panel.
|
|
|
379
387
|
Automatically capture uncaught errors and unhandled promise rejections:
|
|
380
388
|
|
|
381
389
|
```typescript
|
|
382
|
-
import { ErrorCapture } from
|
|
390
|
+
import { ErrorCapture } from "devlogger";
|
|
383
391
|
|
|
384
392
|
// Install at app start
|
|
385
393
|
ErrorCapture.install();
|
|
386
394
|
|
|
387
395
|
// With custom configuration
|
|
388
396
|
ErrorCapture.install({
|
|
389
|
-
captureErrors: true,
|
|
390
|
-
captureRejections: true,
|
|
391
|
-
errorPrefix:
|
|
392
|
-
rejectionPrefix:
|
|
397
|
+
captureErrors: true, // Capture window.onerror (default: true)
|
|
398
|
+
captureRejections: true, // Capture unhandledrejection (default: true)
|
|
399
|
+
errorPrefix: "[ERROR]", // Prefix for error messages
|
|
400
|
+
rejectionPrefix: "[REJECT]", // Prefix for rejection messages
|
|
393
401
|
});
|
|
394
402
|
|
|
395
403
|
// Check if active
|
|
@@ -409,7 +417,7 @@ All captured errors are automatically logged as `error` level with full stack tr
|
|
|
409
417
|
Persist logs to survive page crashes and enable crash recovery:
|
|
410
418
|
|
|
411
419
|
```typescript
|
|
412
|
-
import { LogPersistence, logger } from
|
|
420
|
+
import { LogPersistence, logger } from "devlogger";
|
|
413
421
|
|
|
414
422
|
// Enable persistence at app start
|
|
415
423
|
LogPersistence.enable();
|
|
@@ -422,9 +430,9 @@ if (LogPersistence.hadCrash()) {
|
|
|
422
430
|
|
|
423
431
|
// With custom configuration
|
|
424
432
|
LogPersistence.enable({
|
|
425
|
-
storage:
|
|
426
|
-
maxPersisted: 500,
|
|
427
|
-
debounceMs: 100
|
|
433
|
+
storage: "session", // 'session' (sessionStorage) or 'local' (localStorage)
|
|
434
|
+
maxPersisted: 500, // Max logs to persist
|
|
435
|
+
debounceMs: 100, // Debounce writes for performance
|
|
428
436
|
});
|
|
429
437
|
|
|
430
438
|
// Check if active
|
|
@@ -453,11 +461,10 @@ For production, import from `devlogger/noop` to completely eliminate logging cod
|
|
|
453
461
|
export default defineConfig({
|
|
454
462
|
resolve: {
|
|
455
463
|
alias: {
|
|
456
|
-
|
|
457
|
-
?
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
464
|
+
devlogger:
|
|
465
|
+
process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
|
|
466
|
+
},
|
|
467
|
+
},
|
|
461
468
|
});
|
|
462
469
|
```
|
|
463
470
|
|
|
@@ -468,11 +475,10 @@ export default defineConfig({
|
|
|
468
475
|
module.exports = {
|
|
469
476
|
resolve: {
|
|
470
477
|
alias: {
|
|
471
|
-
|
|
472
|
-
?
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
478
|
+
devlogger:
|
|
479
|
+
process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
|
|
480
|
+
},
|
|
481
|
+
},
|
|
476
482
|
};
|
|
477
483
|
```
|
|
478
484
|
|
|
@@ -480,12 +486,11 @@ module.exports = {
|
|
|
480
486
|
|
|
481
487
|
```javascript
|
|
482
488
|
// build.js
|
|
483
|
-
require(
|
|
489
|
+
require("esbuild").build({
|
|
484
490
|
alias: {
|
|
485
|
-
|
|
486
|
-
?
|
|
487
|
-
|
|
488
|
-
}
|
|
491
|
+
devlogger:
|
|
492
|
+
process.env.NODE_ENV === "production" ? "devlogger/noop" : "devlogger",
|
|
493
|
+
},
|
|
489
494
|
});
|
|
490
495
|
```
|
|
491
496
|
|
|
@@ -495,13 +500,25 @@ The `noop` export provides the same API but all functions are no-ops, resulting
|
|
|
495
500
|
|
|
496
501
|
```typescript
|
|
497
502
|
import type {
|
|
498
|
-
LogEvent,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
503
|
+
LogEvent,
|
|
504
|
+
LogLevel,
|
|
505
|
+
LoggerConfig,
|
|
506
|
+
Source,
|
|
507
|
+
FilterState,
|
|
508
|
+
ErrorCaptureConfig,
|
|
509
|
+
LogContext,
|
|
510
|
+
SpanEvent,
|
|
511
|
+
SpanStatus,
|
|
512
|
+
ExportOptions,
|
|
513
|
+
DiffEntry,
|
|
514
|
+
DiffResult,
|
|
515
|
+
DiffChangeType,
|
|
516
|
+
NetworkCaptureConfig,
|
|
517
|
+
TimelineConfig,
|
|
518
|
+
} from "devlogger";
|
|
519
|
+
|
|
520
|
+
type LogLevel = "debug" | "info" | "warn" | "error";
|
|
521
|
+
type SpanStatus = "running" | "success" | "error";
|
|
505
522
|
type LogContext = Record<string, string | number | boolean>;
|
|
506
523
|
|
|
507
524
|
interface Source {
|
|
@@ -519,8 +536,8 @@ interface LogEvent {
|
|
|
519
536
|
data: unknown[];
|
|
520
537
|
source: Source;
|
|
521
538
|
sessionId: string;
|
|
522
|
-
context?: LogContext;
|
|
523
|
-
spanId?: string;
|
|
539
|
+
context?: LogContext; // Attached context/tags
|
|
540
|
+
spanId?: string; // Parent span ID
|
|
524
541
|
}
|
|
525
542
|
|
|
526
543
|
interface SpanEvent {
|
|
@@ -530,7 +547,7 @@ interface SpanEvent {
|
|
|
530
547
|
endTime?: number;
|
|
531
548
|
duration?: number;
|
|
532
549
|
status: SpanStatus;
|
|
533
|
-
parentId?: string;
|
|
550
|
+
parentId?: string; // For nested spans
|
|
534
551
|
context?: LogContext;
|
|
535
552
|
source: Source;
|
|
536
553
|
sessionId: string;
|
|
@@ -543,11 +560,11 @@ interface LoggerConfig {
|
|
|
543
560
|
}
|
|
544
561
|
|
|
545
562
|
interface ExportOptions {
|
|
546
|
-
format?:
|
|
547
|
-
lastMs?: number;
|
|
548
|
-
levels?: LogLevel[];
|
|
549
|
-
search?: string;
|
|
550
|
-
pretty?: boolean;
|
|
563
|
+
format?: "json" | "text";
|
|
564
|
+
lastMs?: number; // Filter by time
|
|
565
|
+
levels?: LogLevel[]; // Filter by levels
|
|
566
|
+
search?: string; // Filter by text
|
|
567
|
+
pretty?: boolean; // Pretty print JSON
|
|
551
568
|
}
|
|
552
569
|
|
|
553
570
|
interface FilterState {
|
|
@@ -564,16 +581,16 @@ interface ErrorCaptureConfig {
|
|
|
564
581
|
}
|
|
565
582
|
|
|
566
583
|
interface PersistenceConfig {
|
|
567
|
-
storage?:
|
|
584
|
+
storage?: "session" | "local";
|
|
568
585
|
maxPersisted?: number;
|
|
569
586
|
debounceMs?: number;
|
|
570
587
|
}
|
|
571
588
|
|
|
572
589
|
// Diff types
|
|
573
|
-
type DiffChangeType =
|
|
590
|
+
type DiffChangeType = "added" | "removed" | "changed" | "unchanged";
|
|
574
591
|
|
|
575
592
|
interface DiffEntry {
|
|
576
|
-
path: string;
|
|
593
|
+
path: string; // e.g., "user.profile.name"
|
|
577
594
|
type: DiffChangeType;
|
|
578
595
|
oldValue?: unknown;
|
|
579
596
|
newValue?: unknown;
|
|
@@ -664,6 +681,7 @@ interface TimelineConfig {
|
|
|
664
681
|
- Safari 14+
|
|
665
682
|
|
|
666
683
|
Requires support for:
|
|
684
|
+
|
|
667
685
|
- Shadow DOM
|
|
668
686
|
- BroadcastChannel
|
|
669
687
|
- ES2020+
|