Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const superdoc = new SuperDoc({
});
```

SuperDoc now uses the layout-engine powered `PresentationEditor` under the hood, so pages/zoom/error telemetry are always available—no pagination flag required.
SuperDoc now uses the layout-engine powered `PresentationEditor` under the hood, so pages/zoom/error events are always available—no pagination flag required.

For a list of all available properties and events, see the documentation or refer to [SuperDoc.js](packages/superdoc/src/core/SuperDoc.js)

Expand Down
2 changes: 1 addition & 1 deletion docs/breaking-changes-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Key impacts:
* A new **layout engine** is now the default rendering system
* **Pagination, lists, and paragraph formatting were reimplemented**
* Several **extensions, commands, and configuration options were removed**
* **Selection, comments, telemetry, and tracked-changes behavior changed**
* **Selection, comments, and tracked-changes behavior changed**

Most applications will require **explicit migration work**.

Expand Down
52 changes: 1 addition & 51 deletions packages/layout-engine/layout-bridge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ export { isListItem, getWordLayoutConfig, calculateTextStartIndent, extractParag
export type { TextIndentCalculationParams } from './list-indent-utils';
export { LayoutVersionManager } from './layout-version-manager';
export type { VersionedLayoutState, LayoutVersionMetrics } from './layout-version-manager';
export { LayoutVersionLogger, LayoutVersionMetricsCollector, globalLayoutVersionMetrics } from './instrumentation';
export type { LayoutVersionTelemetry } from './instrumentation';
export { LayoutVersionLogger } from './instrumentation';

// Font Metrics Cache
export { FontMetricsCache } from './font-metrics-cache';
Expand Down Expand Up @@ -237,42 +236,6 @@ const isAtomicFragment = (fragment: Fragment): fragment is AtomicFragment => {
return fragment.kind === 'drawing' || fragment.kind === 'image';
};

/**
* Click mapping telemetry for tracking DOM vs geometry mapping usage.
* Exposed for performance monitoring and optimization decisions.
*/
export interface ClickMappingTelemetry {
/** Total click mappings attempted */
total: number;
/** Successful DOM-based mappings */
domSuccess: number;
/** Successful geometry-based mappings */
geometrySuccess: number;
/** Failed mappings (returned null) */
failed: number;
}

/**
* Global click mapping telemetry instance.
* Reset this periodically to avoid unbounded growth.
*/
export const clickMappingTelemetry: ClickMappingTelemetry = {
total: 0,
domSuccess: 0,
geometrySuccess: 0,
failed: 0,
};

/**
* Resets click mapping telemetry counters.
*/
export function resetClickMappingTelemetry(): void {
clickMappingTelemetry.total = 0;
clickMappingTelemetry.domSuccess = 0;
clickMappingTelemetry.geometrySuccess = 0;
clickMappingTelemetry.failed = 0;
}

const logClickStage = (_level: 'log' | 'warn' | 'error', _stage: string, _payload: Record<string, unknown>) => {
// No-op in production. Enable for debugging click-to-position mapping.
};
Expand Down Expand Up @@ -827,7 +790,6 @@ export function clickToPosition(
clientY?: number,
geometryHelper?: import('./page-geometry-helper').PageGeometryHelper,
): PositionHit | null {
clickMappingTelemetry.total++;
const layoutEpoch = layout.layoutEpoch ?? 0;

logClickStage('log', 'entry', {
Expand All @@ -849,7 +811,6 @@ export function clickToPosition(
clientX,
clientY,
});
clickMappingTelemetry.domSuccess++;
// DOM mapping succeeded - we need to construct a PositionHit with metadata
// Find the block containing this position to get blockId
let blockId = '';
Expand Down Expand Up @@ -1047,7 +1008,6 @@ export function clickToPosition(
}

const column = determineColumn(layout, fragment.x);
clickMappingTelemetry.geometrySuccess++;
logPositionDebug({
origin: 'geometry',
pos,
Expand Down Expand Up @@ -1084,15 +1044,12 @@ export function clickToPosition(
const pmRange = getAtomicPmRange(fragment, block);
const pos = pmRange.pmStart ?? pmRange.pmEnd ?? null;
if (pos == null) {
clickMappingTelemetry.failed++;
logClickStage('warn', 'atomic-without-range', {
fragmentId: fragment.blockId,
});
return null;
}

clickMappingTelemetry.geometrySuccess++;

logClickStage('log', 'success', {
blockId: fragment.blockId,
pos,
Expand Down Expand Up @@ -1152,7 +1109,6 @@ export function clickToPosition(
const pos = mapPointToPm(cellBlock, line, localX, isRTL, availableWidth, alignmentOverride);

if (pos != null) {
clickMappingTelemetry.geometrySuccess++;
logClickStage('log', 'success', {
blockId: tableHit.fragment.blockId,
pos,
Expand All @@ -1176,7 +1132,6 @@ export function clickToPosition(
// Fallback: return first position in the cell if line/position mapping fails
const firstRun = cellBlock.runs?.[0];
if (firstRun && firstRun.pmStart != null) {
clickMappingTelemetry.geometrySuccess++;
logClickStage('log', 'success', {
blockId: tableHit.fragment.blockId,
pos: firstRun.pmStart,
Expand Down Expand Up @@ -1211,15 +1166,12 @@ export function clickToPosition(
const pmRange = getAtomicPmRange(fragment, block);
const pos = pmRange.pmStart ?? pmRange.pmEnd ?? null;
if (pos == null) {
clickMappingTelemetry.failed++;
logClickStage('warn', 'atomic-without-range', {
fragmentId: fragment.blockId,
});
return null;
}

clickMappingTelemetry.geometrySuccess++;

logClickStage('log', 'success', {
blockId: fragment.blockId,
pos,
Expand All @@ -1239,8 +1191,6 @@ export function clickToPosition(
};
}

clickMappingTelemetry.failed++;

logClickStage('warn', 'no-fragment', {
pageIndex: pageHit.pageIndex,
pageRelativePoint,
Expand Down
112 changes: 0 additions & 112 deletions packages/layout-engine/layout-bridge/src/instrumentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,25 +367,6 @@ export class MetricsCollector {
*/
export const globalMetrics = new MetricsCollector();

/**
* Layout version telemetry for performance monitoring.
* Tracks stale layout usage and fallback behavior.
*/
export interface LayoutVersionTelemetry {
/** Number of times selection overlay used stale layout */
staleLayoutReads: number;
/** Number of times geometry fallback was used */
geometryFallbacks: number;
/** Total staleness duration across all stale periods (ms) */
totalStalenessDuration: number;
/** Maximum staleness duration observed (ms) */
maxStalenessDuration: number;
/** Number of PM transactions processed */
totalPmTransactions: number;
/** Number of layout completions */
totalLayoutCompletions: number;
}

/**
* Logger for layout version events.
* Guards all logging behind SD_DEBUG_LAYOUT_VERSION flag for zero overhead in production.
Expand Down Expand Up @@ -456,96 +437,3 @@ export const LayoutVersionLogger = {
console.log(`[LayoutVersion] Layout complete → v${version} (${status})`);
},
};

/**
* Metrics collector for layout version telemetry.
* Tracks aggregate statistics for monitoring and optimization.
*/
export class LayoutVersionMetricsCollector {
private telemetry: LayoutVersionTelemetry = {
staleLayoutReads: 0,
geometryFallbacks: 0,
totalStalenessDuration: 0,
maxStalenessDuration: 0,
totalPmTransactions: 0,
totalLayoutCompletions: 0,
};

/**
* Record a stale layout read event.
*
* @param stalenessDuration - Duration layout has been stale (ms)
*/
recordStaleLayoutRead(stalenessDuration: number): void {
this.telemetry.staleLayoutReads++;
this.telemetry.totalStalenessDuration += stalenessDuration;
this.telemetry.maxStalenessDuration = Math.max(this.telemetry.maxStalenessDuration, stalenessDuration);
}

/**
* Record a geometry fallback event.
*/
recordGeometryFallback(): void {
this.telemetry.geometryFallbacks++;
}

/**
* Record a PM transaction.
*/
recordPmTransaction(): void {
this.telemetry.totalPmTransactions++;
}

/**
* Record a layout completion.
*/
recordLayoutCompletion(): void {
this.telemetry.totalLayoutCompletions++;
}

/**
* Get current telemetry snapshot.
*
* @returns Current telemetry data
*/
getTelemetry(): Readonly<LayoutVersionTelemetry> {
return { ...this.telemetry };
}

/**
* Reset all telemetry counters (for testing or new session).
*/
reset(): void {
this.telemetry = {
staleLayoutReads: 0,
geometryFallbacks: 0,
totalStalenessDuration: 0,
maxStalenessDuration: 0,
totalPmTransactions: 0,
totalLayoutCompletions: 0,
};
}

/**
* Log summary of telemetry data.
* Only logs if debugging is enabled.
*/
logSummary(): void {
if (!FeatureFlags.DEBUG_LAYOUT_VERSION) return;

const t = this.telemetry;
console.log('[LayoutVersion] Telemetry Summary:', {
staleLayoutReads: t.staleLayoutReads,
geometryFallbacks: t.geometryFallbacks,
avgStalenessDuration: t.staleLayoutReads > 0 ? (t.totalStalenessDuration / t.staleLayoutReads).toFixed(2) : 0,
maxStalenessDuration: t.maxStalenessDuration,
totalPmTransactions: t.totalPmTransactions,
totalLayoutCompletions: t.totalLayoutCompletions,
});
}
}

/**
* Global layout version metrics collector.
*/
export const globalLayoutVersionMetrics = new LayoutVersionMetricsCollector();
5 changes: 0 additions & 5 deletions packages/layout-engine/pm-adapter/scripts/extract-pm-json.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ async function extractPMJson() {

// Convert to PM JSON
const converter = {
telemetry: {
trackFileStructure: () => {},
trackUsage: () => {},
trackStatistic: () => {},
},
docHiglightColors: new Set(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,6 @@ async function docxToPMJson(docxPath: string): Promise<PMNode> {

// Convert to PM JSON
const converter = {
telemetry: {
trackFileStructure: () => {},
trackUsage: () => {},
trackStatistic: () => {},
},
docHiglightColors: new Set(),
};

Expand Down
39 changes: 2 additions & 37 deletions packages/super-editor/src/core/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import type { EditorState, Transaction, Plugin } from 'prosemirror-state';
import type { EditorView as PmEditorView } from 'prosemirror-view';
import type { Node as PmNode, Schema } from 'prosemirror-model';
import type { EditorOptions, User, FieldValue, DocxFileEntry } from './types/EditorConfig.js';
import type {
EditorHelpers,
ExtensionStorage,
ProseMirrorJSON,
PageStyles,
TelemetryData,
Toolbar,
} from './types/EditorTypes.js';
import type { EditorHelpers, ExtensionStorage, ProseMirrorJSON, PageStyles, Toolbar } from './types/EditorTypes.js';
import type { ChainableCommandObject, CanObject, EditorCommands } from './types/ChainedCommands.js';
import type { EditorEventMap, FontsResolvedPayload, Comment } from './types/EditorEvents.js';
import type { SchemaSummaryJSON } from './types/EditorSchema.js';
Expand Down Expand Up @@ -305,9 +298,6 @@ export class Editor extends EventEmitter<EditorEventMap> {
// async (file) => url;
handleImageUpload: null,

// telemetry
telemetry: null,

// Docx xml updated by User
customUpdatedFiles: {},

Expand Down Expand Up @@ -1506,7 +1496,6 @@ export class Editor extends EventEmitter<EditorEventMap> {
media: this.options.mediaFiles,
fonts: this.options.fonts,
debug: true,
telemetry: this.options.telemetry,
fileSource: this.options.fileSource,
documentId: this.options.documentId,
mockWindow: this.options.mockWindow ?? null,
Expand Down Expand Up @@ -1822,8 +1811,6 @@ export class Editor extends EventEmitter<EditorEventMap> {
});

this.createNodeViews();

(this.options.telemetry as TelemetryData | null)?.trackUsage?.('editor_initialized', {});
}

/**
Expand Down Expand Up @@ -2059,7 +2046,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
}

/**
* Get document identifier for telemetry (async - may generate hash)
* Get document identifier (async - may generate hash)
*/
async getDocumentIdentifier(): Promise<string | null> {
return (await this.converter?.getDocumentIdentifier()) || null;
Expand All @@ -2079,23 +2066,6 @@ export class Editor extends EventEmitter<EditorEventMap> {
return this.converter?.documentModified || false;
}

/**
* Get telemetry data (async because of lazy hash generation)
*/
async getTelemetryData(): Promise<{
documentId: string | null;
isModified: boolean;
isPermanentId: boolean;
version: string | null;
}> {
return {
documentId: await this.getDocumentIdentifier(),
isModified: this.isDocumentModified(),
isPermanentId: !!this.converter?.documentGuid,
version: this.converter?.getSuperdocVersion(),
};
}

/**
* @deprecated use getDocumentGuid instead
*/
Expand Down Expand Up @@ -2535,11 +2505,6 @@ export class Editor extends EventEmitter<EditorEventMap> {
isHeadless: this.options.isHeadless,
});

(this.options.telemetry as TelemetryData | null)?.trackUsage?.('document_export', {
documentType: 'docx',
timestamp: new Date().toISOString(),
});

return result;
} catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
Expand Down
Loading