This guide documents the internal profiling helpers currently available for export and save flows in the Electron desktop app.
These flags are intended for development and debugging. They are not part of the end-user UI.
Run the desktop app:
make run-appOpen the project in Electron and open DevTools in the renderer process.
Use this profiler when investigating delays during:
- Export to
.elpx - "Download project" in offline/Electron mode
- Delays before the native save dialog appears
window.eXeLearning.config.debugElpxExport = true;
window.eXeLearning.config.debugElpxExportIncludeCaller = true;Trigger the normal .elpx export from the UI.
After the export finishes or is cancelled:
window.__lastElpxExportSummary
window.__lastElpxExportTimelinetotalElapsedMs: end-to-end export timezipGenerateMs: ZIP creation timeelectronSaveMs: full Electron save phaseelectronPromptMs: native save dialog phaseelectronNormalizeMs: renderer payload ->Buffernormalization in Electron mainelectronWriteMs: final disk write timedeflatedFiles/storedFiles: how many files were compressed vs stored as-isdeflatedBytes/storedBytes: byte totals for each group
bridge:exporter:run:start/endexporter:preprocess-pages:start/endexporter:asset-export-map:start/endexporter:assets-to-zip:start/endexporter:zip-generate:start/endbridge:electron:dialog:start/endbridge:electron:buffer-normalize:start/endbridge:electron:write:start/end
Largest phases:
window.__lastElpxExportTimeline
.slice()
.sort((a, b) => b.elapsedMs - a.elapsedMs);Only ZIP and Electron save phases:
window.__lastElpxExportTimeline.filter(entry =>
entry.phase.includes('zip-generate') ||
entry.phase.includes('bridge:electron:')
);- If
zipGenerateMsis dominant, the bottleneck is archive generation/compression. - If
electronPromptMsis dominant, the native dialog is the slow phase. - If
electronNormalizeMsis dominant, the main-process payload conversion is expensive. - If
electronWriteMsis dominant, the bottleneck is disk I/O.
Use this profiler when investigating high RAM usage or long save times in the Yjs/Electron save flow.
window.eXeLearning.config.debugSaveMemory = true;Optional experiment flags:
window.eXeLearning.config.saveMemoryExperiment = 'auto';Supported values:
'auto': current default behavior'baseline': restore pre-optimization Electron small-asset batching'small-session-batches': lower session byte cap'legacy-batches': force legacy sequential batches'yjs-only': skip asset upload'assets-only': skip Yjs upload
Optional batch-size overrides:
window.eXeLearning.config.saveMemorySessionBatchBytes = 5 * 1024 * 1024;
window.eXeLearning.config.saveMemoryBatchBytes = 5 * 1024 * 1024;Trigger a normal save from the UI.
window.__lastSaveMemorySummary
window.__lastSaveMemoryTimelinerssheapUsedheapTotalexternalarrayBuffersrendererWorkingSetSizerendererPeakWorkingSetSizerendererPrivateBytes
save:startyjs:serialize:start/endyjs:upload:start/endassets:metadata:start/endbatch:blob-load:start/endbatch:formdata:start/endbatch:request:start/endbatch:mark-uploaded:start/endsave:endsave:delayed+3000ms
- Peak before upload starts usually points to Yjs serialization.
- Peak during blob-load points to asset loading pressure.
- Peak during
formdataorrequestusually points to multipart/request buffering. - High delayed samples suggest retained references after save completion.
Reset debug flags after a run:
delete window.eXeLearning.config.debugElpxExport;
delete window.eXeLearning.config.debugElpxExportIncludeCaller;
delete window.eXeLearning.config.debugSaveMemory;
delete window.eXeLearning.config.saveMemoryExperiment;
delete window.eXeLearning.config.saveMemorySessionBatchBytes;
delete window.eXeLearning.config.saveMemoryBatchBytes;Capture a copy before changing flags:
const run1 = structuredClone(window.__lastElpxExportSummary);
const run2 = structuredClone(window.__lastSaveMemorySummary);window.__lastElpxExportTimeline.slice(-20);window.__lastSaveMemoryTimeline
.slice()
.sort((a, b) => (b.rss || 0) - (a.rss || 0))
.slice(0, 10);