@kedataindo/docflow-core 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37,6 +37,7 @@ __export(index_exports, {
37
37
  LOCAL_STORAGE_KEY: () => LOCAL_STORAGE_KEY,
38
38
  PAGE_SIZES: () => PAGE_SIZES,
39
39
  PaginationPlus: () => PaginationPlus,
40
+ PerformanceMonitor: () => PerformanceMonitor,
40
41
  SearchAndReplaceExtension: () => SearchAndReplaceExtension,
41
42
  SubdocumentProvider: () => SubdocumentProvider,
42
43
  buildAIPrompt: () => buildAIPrompt,
@@ -46,6 +47,7 @@ __export(index_exports, {
46
47
  createActionMap: () => createActionMap,
47
48
  createCollaboration: () => createCollaboration,
48
49
  createEditor: () => createEditor,
50
+ createPerformanceMonitor: () => createPerformanceMonitor,
49
51
  definePlugin: () => definePlugin,
50
52
  findMatches: () => findMatches,
51
53
  getSearchState: () => getSearchState,
@@ -1262,6 +1264,188 @@ var PAGE_SIZES = {
1262
1264
  TABLOID: TABLOID_PAGE_SIZE
1263
1265
  };
1264
1266
 
1267
+ // src/PerformanceMonitor.ts
1268
+ var TARGET_FRAME_MS = 1e3 / 60;
1269
+ var formatMb = (bytes) => `${Math.round(bytes / 1024 / 1024)}MB`;
1270
+ var PerformanceMonitor = class {
1271
+ container;
1272
+ interval;
1273
+ metrics;
1274
+ root = null;
1275
+ cpuBar = null;
1276
+ cpuValue = null;
1277
+ ramBar = null;
1278
+ ramValue = null;
1279
+ rafId = 0;
1280
+ lastFrame = 0;
1281
+ busyMs = 0;
1282
+ windowStart = 0;
1283
+ timer = null;
1284
+ longTaskObserver = null;
1285
+ destroyed = false;
1286
+ constructor(options = {}) {
1287
+ this.container = options.container ?? (typeof document !== "undefined" ? document.body : void 0);
1288
+ this.interval = options.interval ?? 1e3;
1289
+ this.metrics = new Set(options.metrics ?? ["cpu", "ram"]);
1290
+ }
1291
+ /** Append the widget to the container and start sampling. */
1292
+ start() {
1293
+ if (this.destroyed || this.root) return;
1294
+ if (!this.container || typeof window === "undefined" || typeof document === "undefined") return;
1295
+ this.buildWidget();
1296
+ this.windowStart = performance.now();
1297
+ this.lastFrame = this.windowStart;
1298
+ this.busyMs = 0;
1299
+ this.rafId = requestAnimationFrame(this.onFrame);
1300
+ this.longTaskObserver = this.createLongTaskObserver();
1301
+ this.timer = setInterval(this.onTick, this.interval);
1302
+ }
1303
+ /**
1304
+ * Render a fresh snapshot. Used by the internal ticker; also public so
1305
+ * tests and hosts can render known values (values are optional — a metric
1306
+ * without a value keeps its previous text).
1307
+ */
1308
+ update(cpuPercent, ramBytes, ramLimitBytes) {
1309
+ if (!this.root) return;
1310
+ if (typeof cpuPercent === "number") {
1311
+ const clamped = Math.max(0, Math.min(100, cpuPercent));
1312
+ this.renderBar(this.cpuBar, clamped);
1313
+ if (this.cpuValue) this.cpuValue.textContent = `${Math.round(clamped)}%`;
1314
+ }
1315
+ if (typeof ramBytes === "number") {
1316
+ const pct = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? Math.max(0, Math.min(100, ramBytes / ramLimitBytes * 100)) : 0;
1317
+ this.renderBar(this.ramBar, pct);
1318
+ if (this.ramValue) {
1319
+ this.ramValue.textContent = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? `${formatMb(ramBytes)} / ${formatMb(ramLimitBytes)}` : formatMb(ramBytes);
1320
+ }
1321
+ }
1322
+ }
1323
+ /** Stop sampling, disconnect observers, and remove the widget. */
1324
+ destroy() {
1325
+ this.destroyed = true;
1326
+ if (this.rafId) cancelAnimationFrame(this.rafId);
1327
+ this.rafId = 0;
1328
+ if (this.timer) clearInterval(this.timer);
1329
+ this.timer = null;
1330
+ this.longTaskObserver?.disconnect();
1331
+ this.longTaskObserver = null;
1332
+ if (this.root && this.root.parentElement) {
1333
+ this.root.parentElement.removeChild(this.root);
1334
+ }
1335
+ this.root = null;
1336
+ }
1337
+ buildWidget() {
1338
+ const root = document.createElement("div");
1339
+ root.setAttribute("data-docflow-perf-monitor", "");
1340
+ root.style.cssText = [
1341
+ "position:fixed",
1342
+ "right:12px",
1343
+ "bottom:12px",
1344
+ "z-index:2147483000",
1345
+ "background:rgba(15,23,42,0.55)",
1346
+ "backdrop-filter:blur(6px)",
1347
+ "-webkit-backdrop-filter:blur(6px)",
1348
+ "color:rgba(226,232,240,0.9)",
1349
+ "font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",
1350
+ "padding:8px 10px",
1351
+ "border-radius:8px",
1352
+ "border:1px solid rgba(255,255,255,0.14)",
1353
+ "pointer-events:none",
1354
+ "user-select:none",
1355
+ "opacity:0.85",
1356
+ "box-shadow:0 4px 16px rgba(0,0,0,0.25)"
1357
+ ].join(";");
1358
+ if (this.metrics.has("cpu")) {
1359
+ root.appendChild(this.buildRow("CPU", (el) => this.cpuBar = el, (el) => this.cpuValue = el));
1360
+ }
1361
+ if (this.metrics.has("ram")) {
1362
+ root.appendChild(this.buildRow("RAM", (el) => this.ramBar = el, (el) => this.ramValue = el));
1363
+ }
1364
+ this.root = root;
1365
+ this.container.appendChild(root);
1366
+ }
1367
+ buildRow(label, setBar, setValue) {
1368
+ const row = document.createElement("div");
1369
+ row.style.cssText = "display:flex;align-items:center;gap:6px;white-space:nowrap";
1370
+ const labelEl = document.createElement("span");
1371
+ labelEl.textContent = label;
1372
+ labelEl.style.cssText = "opacity:0.75;min-width:26px";
1373
+ const track = document.createElement("div");
1374
+ track.style.cssText = "width:46px;height:5px;border-radius:3px;background:rgba(255,255,255,0.18);overflow:hidden";
1375
+ const bar = document.createElement("div");
1376
+ bar.style.cssText = "width:0%;height:100%;border-radius:3px;background:#22d3ee;transition:width .3s ease";
1377
+ track.appendChild(bar);
1378
+ setBar(bar);
1379
+ const value = document.createElement("span");
1380
+ value.textContent = "\u2014";
1381
+ value.style.cssText = "min-width:64px;text-align:right";
1382
+ setValue(value);
1383
+ row.append(labelEl, track, value);
1384
+ return row;
1385
+ }
1386
+ renderBar(bar, pct) {
1387
+ if (!bar) return;
1388
+ bar.style.width = `${pct}%`;
1389
+ bar.style.background = pct >= 80 ? "#f87171" : pct >= 50 ? "#fbbf24" : "#22d3ee";
1390
+ }
1391
+ onFrame = (now) => {
1392
+ if (this.destroyed) return;
1393
+ const delta = now - this.lastFrame;
1394
+ this.lastFrame = now;
1395
+ if (delta > TARGET_FRAME_MS) {
1396
+ this.busyMs += delta - TARGET_FRAME_MS;
1397
+ }
1398
+ this.rafId = requestAnimationFrame(this.onFrame);
1399
+ };
1400
+ onTick = () => {
1401
+ if (this.destroyed) return;
1402
+ const now = performance.now();
1403
+ const windowMs = now - this.windowStart || this.interval;
1404
+ this.windowStart = now;
1405
+ let cpu;
1406
+ if (this.metrics.has("cpu")) {
1407
+ cpu = Math.max(0, Math.min(100, this.busyMs / windowMs * 100));
1408
+ this.busyMs = 0;
1409
+ }
1410
+ let ram;
1411
+ let ramLimit;
1412
+ if (this.metrics.has("ram")) {
1413
+ const memory = this.getMemoryInfo();
1414
+ if (memory) {
1415
+ ram = memory.usedJSHeapSize;
1416
+ ramLimit = memory.jsHeapSizeLimit;
1417
+ }
1418
+ }
1419
+ this.update(cpu, ram, ramLimit);
1420
+ };
1421
+ getMemoryInfo() {
1422
+ const perf = performance;
1423
+ const memory = perf.memory;
1424
+ if (!memory || typeof memory.usedJSHeapSize !== "number") return null;
1425
+ return memory;
1426
+ }
1427
+ createLongTaskObserver() {
1428
+ if (typeof PerformanceObserver === "undefined") return null;
1429
+ try {
1430
+ const observer = new PerformanceObserver((list) => {
1431
+ if (this.destroyed) return;
1432
+ for (const entry of list.getEntries()) {
1433
+ this.busyMs += entry.duration;
1434
+ }
1435
+ });
1436
+ observer.observe({ entryTypes: ["longtask"] });
1437
+ return observer;
1438
+ } catch {
1439
+ return null;
1440
+ }
1441
+ }
1442
+ };
1443
+ function createPerformanceMonitor(options) {
1444
+ const monitor = new PerformanceMonitor(options);
1445
+ monitor.start();
1446
+ return monitor;
1447
+ }
1448
+
1265
1449
  // src/Editor.ts
1266
1450
  function sanitizePastedHTML(html) {
1267
1451
  let cleaned = html.replace(/<meta[^>]*>/gi, "");
@@ -1339,6 +1523,7 @@ function createEditor(options = {}) {
1339
1523
  };
1340
1524
  const plugins = migratedOptions.plugins ?? [];
1341
1525
  const collaborationSetup = migratedOptions.collaboration ? createCollaboration(migratedOptions.collaboration) : void 0;
1526
+ const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
1342
1527
  let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
1343
1528
  let pluginActions = createActionMap(tiptapEditor, plugins);
1344
1529
  for (const plugin of plugins) {
@@ -1379,6 +1564,7 @@ function createEditor(options = {}) {
1379
1564
  }
1380
1565
  tiptapEditor.destroy();
1381
1566
  collaborationSetup?.destroy();
1567
+ performanceMonitor?.destroy();
1382
1568
  },
1383
1569
  use: (plugin) => {
1384
1570
  plugins.push(plugin);
@@ -1387,7 +1573,8 @@ function createEditor(options = {}) {
1387
1573
  },
1388
1574
  get pluginActions() {
1389
1575
  return pluginActions;
1390
- }
1576
+ },
1577
+ performanceMonitor
1391
1578
  };
1392
1579
  return editor;
1393
1580
  }
@@ -2235,6 +2422,7 @@ function openaiCompatibleProvider(config) {
2235
2422
  LOCAL_STORAGE_KEY,
2236
2423
  PAGE_SIZES,
2237
2424
  PaginationPlus,
2425
+ PerformanceMonitor,
2238
2426
  SearchAndReplaceExtension,
2239
2427
  SubdocumentProvider,
2240
2428
  buildAIPrompt,
@@ -2244,6 +2432,7 @@ function openaiCompatibleProvider(config) {
2244
2432
  createActionMap,
2245
2433
  createCollaboration,
2246
2434
  createEditor,
2435
+ createPerformanceMonitor,
2247
2436
  definePlugin,
2248
2437
  findMatches,
2249
2438
  getSearchState,
package/dist/index.d.cts CHANGED
@@ -307,6 +307,70 @@ type AIDraftFn = (req: {
307
307
  k?: number;
308
308
  }, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
309
309
 
310
+ /**
311
+ * Lightweight client-side performance monitor (debug overlay).
312
+ *
313
+ * Shows current CPU utilization (event-loop busy time measured via
314
+ * `requestAnimationFrame` + the Long Tasks API) and JS heap usage in a
315
+ * semi-transparent widget pinned to the bottom-right corner of the viewport.
316
+ *
317
+ * The widget is opt-in and controlled by the host through the `debug: true`
318
+ * EditorOptions flag, so it never appears in production builds unless the
319
+ * consumer enables it. Because it is implemented with plain DOM + inline
320
+ * styles (no CSS imports, no framework), it works in every host surface the
321
+ * library ships: vanilla `createEditor()`, the Vue component, and the
322
+ * `<docs-editor>` Web Component (Shadow DOM).
323
+ *
324
+ * This is a pure debug *view* — it never reads or mutates the ProseMirror
325
+ * document, so the "single source of truth" and LIBRARY_CONTRACT rules hold.
326
+ */
327
+ type MonitorMetric = 'cpu' | 'ram';
328
+ interface PerformanceMonitorOptions {
329
+ /** Element the widget is appended to. Defaults to `document.body`. */
330
+ container?: HTMLElement;
331
+ /** Refresh interval in ms. Defaults to 1000. */
332
+ interval?: number;
333
+ /** Which metrics to render. Defaults to `['cpu', 'ram']`. */
334
+ metrics?: MonitorMetric[];
335
+ }
336
+ declare class PerformanceMonitor {
337
+ private readonly container;
338
+ private readonly interval;
339
+ private readonly metrics;
340
+ private root;
341
+ private cpuBar;
342
+ private cpuValue;
343
+ private ramBar;
344
+ private ramValue;
345
+ private rafId;
346
+ private lastFrame;
347
+ private busyMs;
348
+ private windowStart;
349
+ private timer;
350
+ private longTaskObserver;
351
+ private destroyed;
352
+ constructor(options?: PerformanceMonitorOptions);
353
+ /** Append the widget to the container and start sampling. */
354
+ start(): void;
355
+ /**
356
+ * Render a fresh snapshot. Used by the internal ticker; also public so
357
+ * tests and hosts can render known values (values are optional — a metric
358
+ * without a value keeps its previous text).
359
+ */
360
+ update(cpuPercent?: number, ramBytes?: number, ramLimitBytes?: number): void;
361
+ /** Stop sampling, disconnect observers, and remove the widget. */
362
+ destroy(): void;
363
+ private buildWidget;
364
+ private buildRow;
365
+ private renderBar;
366
+ private onFrame;
367
+ private onTick;
368
+ private getMemoryInfo;
369
+ private createLongTaskObserver;
370
+ }
371
+ /** Convenience factory: creates + starts a monitor (returns a stopped one when no DOM). */
372
+ declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
373
+
310
374
  /**
311
375
  * Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
312
376
  * during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
@@ -337,6 +401,12 @@ interface EditorOptions {
337
401
  aiStream?: AIStreamFn;
338
402
  /** Host-injected cited-draft transport (Phase 7E — editor → server → RAG LLM). */
339
403
  aiDraft?: AIDraftFn;
404
+ /**
405
+ * Enable the debug overlay (CPU + RAM monitor) pinned to the bottom-right
406
+ * corner of the viewport. Pure debug view — never touches document state.
407
+ * Defaults to `false`, so production consumers are unaffected.
408
+ */
409
+ debug?: boolean;
340
410
  }
341
411
  interface DocsEditor {
342
412
  editor: Editor;
@@ -346,6 +416,8 @@ interface DocsEditor {
346
416
  destroy: () => void;
347
417
  use: (plugin: DocsEditorPlugin) => void;
348
418
  pluginActions: Record<string, (...args: unknown[]) => boolean>;
419
+ /** Active performance monitor when `debug: true` was set (undefined otherwise). */
420
+ performanceMonitor?: PerformanceMonitor;
349
421
  }
350
422
  declare function createEditor(options?: EditorOptions): DocsEditor;
351
423
 
@@ -608,4 +680,4 @@ interface OpenAICompatibleConfig {
608
680
  }
609
681
  declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
610
682
 
611
- export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type OpenAICompatibleConfig, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
683
+ export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type MonitorMetric, type OpenAICompatibleConfig, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
package/dist/index.d.ts CHANGED
@@ -307,6 +307,70 @@ type AIDraftFn = (req: {
307
307
  k?: number;
308
308
  }, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
309
309
 
310
+ /**
311
+ * Lightweight client-side performance monitor (debug overlay).
312
+ *
313
+ * Shows current CPU utilization (event-loop busy time measured via
314
+ * `requestAnimationFrame` + the Long Tasks API) and JS heap usage in a
315
+ * semi-transparent widget pinned to the bottom-right corner of the viewport.
316
+ *
317
+ * The widget is opt-in and controlled by the host through the `debug: true`
318
+ * EditorOptions flag, so it never appears in production builds unless the
319
+ * consumer enables it. Because it is implemented with plain DOM + inline
320
+ * styles (no CSS imports, no framework), it works in every host surface the
321
+ * library ships: vanilla `createEditor()`, the Vue component, and the
322
+ * `<docs-editor>` Web Component (Shadow DOM).
323
+ *
324
+ * This is a pure debug *view* — it never reads or mutates the ProseMirror
325
+ * document, so the "single source of truth" and LIBRARY_CONTRACT rules hold.
326
+ */
327
+ type MonitorMetric = 'cpu' | 'ram';
328
+ interface PerformanceMonitorOptions {
329
+ /** Element the widget is appended to. Defaults to `document.body`. */
330
+ container?: HTMLElement;
331
+ /** Refresh interval in ms. Defaults to 1000. */
332
+ interval?: number;
333
+ /** Which metrics to render. Defaults to `['cpu', 'ram']`. */
334
+ metrics?: MonitorMetric[];
335
+ }
336
+ declare class PerformanceMonitor {
337
+ private readonly container;
338
+ private readonly interval;
339
+ private readonly metrics;
340
+ private root;
341
+ private cpuBar;
342
+ private cpuValue;
343
+ private ramBar;
344
+ private ramValue;
345
+ private rafId;
346
+ private lastFrame;
347
+ private busyMs;
348
+ private windowStart;
349
+ private timer;
350
+ private longTaskObserver;
351
+ private destroyed;
352
+ constructor(options?: PerformanceMonitorOptions);
353
+ /** Append the widget to the container and start sampling. */
354
+ start(): void;
355
+ /**
356
+ * Render a fresh snapshot. Used by the internal ticker; also public so
357
+ * tests and hosts can render known values (values are optional — a metric
358
+ * without a value keeps its previous text).
359
+ */
360
+ update(cpuPercent?: number, ramBytes?: number, ramLimitBytes?: number): void;
361
+ /** Stop sampling, disconnect observers, and remove the widget. */
362
+ destroy(): void;
363
+ private buildWidget;
364
+ private buildRow;
365
+ private renderBar;
366
+ private onFrame;
367
+ private onTick;
368
+ private getMemoryInfo;
369
+ private createLongTaskObserver;
370
+ }
371
+ /** Convenience factory: creates + starts a monitor (returns a stopped one when no DOM). */
372
+ declare function createPerformanceMonitor(options?: PerformanceMonitorOptions): PerformanceMonitor;
373
+
310
374
  /**
311
375
  * Sanitize pasted HTML content (e.g. from Google Docs) to prevent crashes
312
376
  * during ProseMirror parsing. Strips non-content tags like <meta>, <style>,
@@ -337,6 +401,12 @@ interface EditorOptions {
337
401
  aiStream?: AIStreamFn;
338
402
  /** Host-injected cited-draft transport (Phase 7E — editor → server → RAG LLM). */
339
403
  aiDraft?: AIDraftFn;
404
+ /**
405
+ * Enable the debug overlay (CPU + RAM monitor) pinned to the bottom-right
406
+ * corner of the viewport. Pure debug view — never touches document state.
407
+ * Defaults to `false`, so production consumers are unaffected.
408
+ */
409
+ debug?: boolean;
340
410
  }
341
411
  interface DocsEditor {
342
412
  editor: Editor;
@@ -346,6 +416,8 @@ interface DocsEditor {
346
416
  destroy: () => void;
347
417
  use: (plugin: DocsEditorPlugin) => void;
348
418
  pluginActions: Record<string, (...args: unknown[]) => boolean>;
419
+ /** Active performance monitor when `debug: true` was set (undefined otherwise). */
420
+ performanceMonitor?: PerformanceMonitor;
349
421
  }
350
422
  declare function createEditor(options?: EditorOptions): DocsEditor;
351
423
 
@@ -608,4 +680,4 @@ interface OpenAICompatibleConfig {
608
680
  }
609
681
  declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
610
682
 
611
- export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type OpenAICompatibleConfig, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
683
+ export { type AIAction, type AIActionRequest, type AICompleteRequest, type AIConfig, type AIDraftCitation, type AIDraftEvent, type AIDraftFn, type AIProvider, type AIProviderFactory, type AIStreamFn, type Auth, type AwarenessState, BlockAttributesExtension, CONTEXT_CHAR_CAP, type CitationPort, type CollaborationOptions, type CollaborationSetup, type CslDate, type CslItemData, type CslName, type DocsEditor, type DocsEditorPlugin, EditorContextExtension, type EditorContextOptions, type EditorOptions, FontSizeExtension, type HttpKeyStorageUrls, type ImageUploadHandler, type ImageUploadResult, type KeyStorage, LOCAL_STORAGE_KEY, type MonitorMetric, type OpenAICompatibleConfig, PerformanceMonitor, type PerformanceMonitorOptions, SearchAndReplaceExtension, type SearchMatch, type SearchState, type SlashCommand, type StreamEvent, type SubdocState, SubdocumentProvider, type SubdocumentProviderOptions, type ToolbarItem, buildAIPrompt, clearSearch, collaborationExtensions, collectExtensions, createActionMap, createCollaboration, createEditor, createPerformanceMonitor, definePlugin, findMatches, getSearchState, httpKeyStorage, localStorageKeyStorage, memoryKeyStorage, openaiCompatibleProvider, replaceAll, replaceCurrent, resolveAction, sanitizePastedHTML, searchAndReplaceKey, searchNext, searchPrev, setSearchQuery, toAIStreamFn, trimContextAfter, trimContextBefore };
package/dist/index.js CHANGED
@@ -1193,6 +1193,188 @@ var PAGE_SIZES = {
1193
1193
  TABLOID: TABLOID_PAGE_SIZE
1194
1194
  };
1195
1195
 
1196
+ // src/PerformanceMonitor.ts
1197
+ var TARGET_FRAME_MS = 1e3 / 60;
1198
+ var formatMb = (bytes) => `${Math.round(bytes / 1024 / 1024)}MB`;
1199
+ var PerformanceMonitor = class {
1200
+ container;
1201
+ interval;
1202
+ metrics;
1203
+ root = null;
1204
+ cpuBar = null;
1205
+ cpuValue = null;
1206
+ ramBar = null;
1207
+ ramValue = null;
1208
+ rafId = 0;
1209
+ lastFrame = 0;
1210
+ busyMs = 0;
1211
+ windowStart = 0;
1212
+ timer = null;
1213
+ longTaskObserver = null;
1214
+ destroyed = false;
1215
+ constructor(options = {}) {
1216
+ this.container = options.container ?? (typeof document !== "undefined" ? document.body : void 0);
1217
+ this.interval = options.interval ?? 1e3;
1218
+ this.metrics = new Set(options.metrics ?? ["cpu", "ram"]);
1219
+ }
1220
+ /** Append the widget to the container and start sampling. */
1221
+ start() {
1222
+ if (this.destroyed || this.root) return;
1223
+ if (!this.container || typeof window === "undefined" || typeof document === "undefined") return;
1224
+ this.buildWidget();
1225
+ this.windowStart = performance.now();
1226
+ this.lastFrame = this.windowStart;
1227
+ this.busyMs = 0;
1228
+ this.rafId = requestAnimationFrame(this.onFrame);
1229
+ this.longTaskObserver = this.createLongTaskObserver();
1230
+ this.timer = setInterval(this.onTick, this.interval);
1231
+ }
1232
+ /**
1233
+ * Render a fresh snapshot. Used by the internal ticker; also public so
1234
+ * tests and hosts can render known values (values are optional — a metric
1235
+ * without a value keeps its previous text).
1236
+ */
1237
+ update(cpuPercent, ramBytes, ramLimitBytes) {
1238
+ if (!this.root) return;
1239
+ if (typeof cpuPercent === "number") {
1240
+ const clamped = Math.max(0, Math.min(100, cpuPercent));
1241
+ this.renderBar(this.cpuBar, clamped);
1242
+ if (this.cpuValue) this.cpuValue.textContent = `${Math.round(clamped)}%`;
1243
+ }
1244
+ if (typeof ramBytes === "number") {
1245
+ const pct = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? Math.max(0, Math.min(100, ramBytes / ramLimitBytes * 100)) : 0;
1246
+ this.renderBar(this.ramBar, pct);
1247
+ if (this.ramValue) {
1248
+ this.ramValue.textContent = typeof ramLimitBytes === "number" && ramLimitBytes > 0 ? `${formatMb(ramBytes)} / ${formatMb(ramLimitBytes)}` : formatMb(ramBytes);
1249
+ }
1250
+ }
1251
+ }
1252
+ /** Stop sampling, disconnect observers, and remove the widget. */
1253
+ destroy() {
1254
+ this.destroyed = true;
1255
+ if (this.rafId) cancelAnimationFrame(this.rafId);
1256
+ this.rafId = 0;
1257
+ if (this.timer) clearInterval(this.timer);
1258
+ this.timer = null;
1259
+ this.longTaskObserver?.disconnect();
1260
+ this.longTaskObserver = null;
1261
+ if (this.root && this.root.parentElement) {
1262
+ this.root.parentElement.removeChild(this.root);
1263
+ }
1264
+ this.root = null;
1265
+ }
1266
+ buildWidget() {
1267
+ const root = document.createElement("div");
1268
+ root.setAttribute("data-docflow-perf-monitor", "");
1269
+ root.style.cssText = [
1270
+ "position:fixed",
1271
+ "right:12px",
1272
+ "bottom:12px",
1273
+ "z-index:2147483000",
1274
+ "background:rgba(15,23,42,0.55)",
1275
+ "backdrop-filter:blur(6px)",
1276
+ "-webkit-backdrop-filter:blur(6px)",
1277
+ "color:rgba(226,232,240,0.9)",
1278
+ "font:11px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace",
1279
+ "padding:8px 10px",
1280
+ "border-radius:8px",
1281
+ "border:1px solid rgba(255,255,255,0.14)",
1282
+ "pointer-events:none",
1283
+ "user-select:none",
1284
+ "opacity:0.85",
1285
+ "box-shadow:0 4px 16px rgba(0,0,0,0.25)"
1286
+ ].join(";");
1287
+ if (this.metrics.has("cpu")) {
1288
+ root.appendChild(this.buildRow("CPU", (el) => this.cpuBar = el, (el) => this.cpuValue = el));
1289
+ }
1290
+ if (this.metrics.has("ram")) {
1291
+ root.appendChild(this.buildRow("RAM", (el) => this.ramBar = el, (el) => this.ramValue = el));
1292
+ }
1293
+ this.root = root;
1294
+ this.container.appendChild(root);
1295
+ }
1296
+ buildRow(label, setBar, setValue) {
1297
+ const row = document.createElement("div");
1298
+ row.style.cssText = "display:flex;align-items:center;gap:6px;white-space:nowrap";
1299
+ const labelEl = document.createElement("span");
1300
+ labelEl.textContent = label;
1301
+ labelEl.style.cssText = "opacity:0.75;min-width:26px";
1302
+ const track = document.createElement("div");
1303
+ track.style.cssText = "width:46px;height:5px;border-radius:3px;background:rgba(255,255,255,0.18);overflow:hidden";
1304
+ const bar = document.createElement("div");
1305
+ bar.style.cssText = "width:0%;height:100%;border-radius:3px;background:#22d3ee;transition:width .3s ease";
1306
+ track.appendChild(bar);
1307
+ setBar(bar);
1308
+ const value = document.createElement("span");
1309
+ value.textContent = "\u2014";
1310
+ value.style.cssText = "min-width:64px;text-align:right";
1311
+ setValue(value);
1312
+ row.append(labelEl, track, value);
1313
+ return row;
1314
+ }
1315
+ renderBar(bar, pct) {
1316
+ if (!bar) return;
1317
+ bar.style.width = `${pct}%`;
1318
+ bar.style.background = pct >= 80 ? "#f87171" : pct >= 50 ? "#fbbf24" : "#22d3ee";
1319
+ }
1320
+ onFrame = (now) => {
1321
+ if (this.destroyed) return;
1322
+ const delta = now - this.lastFrame;
1323
+ this.lastFrame = now;
1324
+ if (delta > TARGET_FRAME_MS) {
1325
+ this.busyMs += delta - TARGET_FRAME_MS;
1326
+ }
1327
+ this.rafId = requestAnimationFrame(this.onFrame);
1328
+ };
1329
+ onTick = () => {
1330
+ if (this.destroyed) return;
1331
+ const now = performance.now();
1332
+ const windowMs = now - this.windowStart || this.interval;
1333
+ this.windowStart = now;
1334
+ let cpu;
1335
+ if (this.metrics.has("cpu")) {
1336
+ cpu = Math.max(0, Math.min(100, this.busyMs / windowMs * 100));
1337
+ this.busyMs = 0;
1338
+ }
1339
+ let ram;
1340
+ let ramLimit;
1341
+ if (this.metrics.has("ram")) {
1342
+ const memory = this.getMemoryInfo();
1343
+ if (memory) {
1344
+ ram = memory.usedJSHeapSize;
1345
+ ramLimit = memory.jsHeapSizeLimit;
1346
+ }
1347
+ }
1348
+ this.update(cpu, ram, ramLimit);
1349
+ };
1350
+ getMemoryInfo() {
1351
+ const perf = performance;
1352
+ const memory = perf.memory;
1353
+ if (!memory || typeof memory.usedJSHeapSize !== "number") return null;
1354
+ return memory;
1355
+ }
1356
+ createLongTaskObserver() {
1357
+ if (typeof PerformanceObserver === "undefined") return null;
1358
+ try {
1359
+ const observer = new PerformanceObserver((list) => {
1360
+ if (this.destroyed) return;
1361
+ for (const entry of list.getEntries()) {
1362
+ this.busyMs += entry.duration;
1363
+ }
1364
+ });
1365
+ observer.observe({ entryTypes: ["longtask"] });
1366
+ return observer;
1367
+ } catch {
1368
+ return null;
1369
+ }
1370
+ }
1371
+ };
1372
+ function createPerformanceMonitor(options) {
1373
+ const monitor = new PerformanceMonitor(options);
1374
+ monitor.start();
1375
+ return monitor;
1376
+ }
1377
+
1196
1378
  // src/Editor.ts
1197
1379
  function sanitizePastedHTML(html) {
1198
1380
  let cleaned = html.replace(/<meta[^>]*>/gi, "");
@@ -1270,6 +1452,7 @@ function createEditor(options = {}) {
1270
1452
  };
1271
1453
  const plugins = migratedOptions.plugins ?? [];
1272
1454
  const collaborationSetup = migratedOptions.collaboration ? createCollaboration(migratedOptions.collaboration) : void 0;
1455
+ const performanceMonitor = migratedOptions.debug ? createPerformanceMonitor() : void 0;
1273
1456
  let tiptapEditor = createTiptapEditor(migratedOptions, plugins, collaborationSetup);
1274
1457
  let pluginActions = createActionMap(tiptapEditor, plugins);
1275
1458
  for (const plugin of plugins) {
@@ -1310,6 +1493,7 @@ function createEditor(options = {}) {
1310
1493
  }
1311
1494
  tiptapEditor.destroy();
1312
1495
  collaborationSetup?.destroy();
1496
+ performanceMonitor?.destroy();
1313
1497
  },
1314
1498
  use: (plugin) => {
1315
1499
  plugins.push(plugin);
@@ -1318,7 +1502,8 @@ function createEditor(options = {}) {
1318
1502
  },
1319
1503
  get pluginActions() {
1320
1504
  return pluginActions;
1321
- }
1505
+ },
1506
+ performanceMonitor
1322
1507
  };
1323
1508
  return editor;
1324
1509
  }
@@ -2165,6 +2350,7 @@ export {
2165
2350
  LOCAL_STORAGE_KEY,
2166
2351
  PAGE_SIZES,
2167
2352
  PaginationPlus,
2353
+ PerformanceMonitor,
2168
2354
  SearchAndReplaceExtension,
2169
2355
  SubdocumentProvider,
2170
2356
  buildAIPrompt,
@@ -2174,6 +2360,7 @@ export {
2174
2360
  createActionMap,
2175
2361
  createCollaboration,
2176
2362
  createEditor,
2363
+ createPerformanceMonitor,
2177
2364
  definePlugin,
2178
2365
  findMatches,
2179
2366
  getSearchState,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kedataindo/docflow-core",
3
3
  "license": "UNLICENSED",
4
- "version": "0.0.11",
4
+ "version": "0.0.13",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",