How the Markdown Editor in File Preview Handles Partial Files and Auto-Save

The markdown editor uses a 1-second debounced auto-save timer and partial read offsets to edit large files without loading them entirely into memory, applying chunked edits via the edit_block tool with conflict detection.

The wonderwhy-er/DesktopCommanderMCP repository implements a sophisticated file preview system that treats markdown documents as editable workspaces. Unlike traditional editors that require full file ingestion, this architecture supports partial file reads for performance and implements intelligent auto-save with granular error recovery.

Partial File Handling in the Markdown Editor

When working with large log files or documents, loading the entire content into the browser is inefficient. The editor solves this by parsing range metadata and fetching only necessary slices.

Detecting Partial Ranges with parseReadRange

The system identifies partial read requests through the parseReadRange utility defined in src/ui/file-preview/src/document-workspace.ts. This function extracts readOffset, fromLine, and toLine from range annotations (e.g., #read_range:10-20), determining whether the current payload represents a subset of the full file.

When the controller initializes, it checks if the original payload contained a partial range. If so, it calculates the exact slice needed using toLine - fromLine + 1 for the length parameter.

Reading File Slices on Demand

The readPayload function in src/ui/file-preview/src/markdown/controller.ts (around lines 181-188) invokes the read_file tool with explicit offset and length parameters. This allows the editor to request specific byte ranges from the backend rather than the entire file content.

async function readPayload(
    filePath: string,
    length?: number,
    offset?: number,
): Promise<RenderPayload | null> {
    // Fetch only the required slice when partial range is detected
    const { rawResult, payload } = await callReadFile(filePath, length, offset);
    return payload;
}

Merging Partial Payloads Back into the Workspace

When exiting fullscreen mode or refreshing content, the controller re-assembles the document by stitching the freshly-read slice into the current draft. The syncStateFromContent method preserves any unsaved edits while updating the underlying file content, ensuring the workspace remains consistent even when only a portion of the file resides in memory (see controller.ts#L112-L119).

Auto-Save Architecture and Debouncing

The auto-save system balances responsiveness with network efficiency, preventing excessive write operations while ensuring minimal data loss.

Scheduling Saves with AUTOSAVE_DEBOUNCE_MS

The controller maintains an autosaveTimer that implements a debounce pattern with a fixed delay of 1000 milliseconds (AUTOSAVE_DEBOUNCE_MS). Every keystroke triggers scheduleAutosave(), which cancels any pending timer and restarts the countdown.

function scheduleAutosave(): void {
    if (autosaveTimer !== null) {
        clearTimeout(autosaveTimer);
    }
    autosaveTimer = setTimeout(() => {
        autosaveTimer = null;
        void saveDocument();
    }, AUTOSAVE_DEBOUNCE_MS);
}

This logic appears in src/ui/file-preview/src/markdown/controller.ts (lines 16-28), where the timer is also cleared on editor disposal via cancelAutosave() to prevent memory leaks.

Chunked Edit Blocks and Partial Save Recovery

When the debounce expires, saveDocument() computes differences between the draft and source using computeDiffHunks and computeAnchoredDiffHunks. To handle large edits efficiently, the system splits changes into blocks of maximum 40 lines (MAX_EDIT_BLOCK_LINES), sending each to the backend via the edit_block tool.

Partial success is tracked through three counters:

  • appliedCount: Blocks successfully written
  • skippedCount: Blocks that failed to apply
  • totalCount: Total blocks attempted

When some blocks succeed while others fail, the UI displays "Saved (partial)" via flashSaveStatus and updates the error state:

if (isPartialSuccess) {
    state.error = `${appliedCount} of ${totalCount} edit${totalCount === 1 ? '' : 's'} saved. ` +
                  `${skippedCount} ${skippedCount === 1 ? 'edit' : 'edits'} did not apply …`;
    dependencies.rerender();
    flashSaveStatus('Saved (partial)', 'saved', 3000);
    dependencies.trackUiEvent?.('markdown_save_partial', { appliedCount, skippedCount });
}

This handling appears around lines 84-115 and 152-168 in controller.ts.

Conflict Detection and User Feedback

When saveDocument encounters errors indicating the file changed on disk (total failure), it triggers showConflictDialog(). This presents users with a choice between keeping their current draft or loading the disk version. Telemetry events track these outcomes via trackUiEvent, logging markdown_saved, markdown_save_partial, markdown_save_conflict_shown, and markdown_save_failed for analytics.

Summary

  • Partial file support: The editor uses parseReadRange to detect slice requests and readPayload with offset/length parameters to fetch only necessary content from large files, as implemented in document-workspace.ts and controller.ts.
  • Debounced auto-save: A 1000ms timer (AUTOSAVE_DEBOUNCE_MS) batches edits, cancelling and restarting on each keystroke to minimize network traffic while preventing data loss.
  • Chunked persistence: Changes are split into 40-line blocks using computeDiffHunks, with per-block error handling that supports partial save states and avoids all-or-nothing failures.
  • Conflict resolution: Total failures trigger a conflict dialog when disk changes are detected, while partial successes display explicit UI status indicators and detailed error messages.

Frequently Asked Questions

How does the editor handle files too large to load entirely?

The system parses range metadata through parseReadRange in document-workspace.ts to identify partial requests. When initializing or refreshing, readPayload requests specific byte offsets and lengths via the read_file tool, loading only the visible slice while keeping the rest of the document on disk. This approach is backed by the streaming text utilities in src/utils/files/text.ts.

What happens if auto-save fails due to concurrent edits?

If the backend rejects edits because the file changed on disk, saveDocument detects the total failure and invokes showConflictDialog(). Users can choose to overwrite with their draft or load the current disk version, with the decision tracked via trackUiEvent('markdown_save_conflict_shown') to maintain audit trails of synchronization conflicts.

Can users disable auto-save or adjust the debounce interval?

The current implementation hardcodes AUTOSAVE_DEBOUNCE_MS to 1000 milliseconds in controller.ts. There is no user-facing toggle to disable auto-save or modify the debounce duration in the reviewed source code; saves occur automatically after every editing pause of one second.

How are partial saves indicated in the UI?

When appliedCount is less than totalCount, the controller calls flashSaveStatus('Saved (partial)', 'saved', 3000) and sets state.error to indicate how many edits applied versus skipped. This status appears in the editor interface alongside a visual badge, distinguishing partial success from full saves or total failures, allowing users to retry failed hunks or accept the partial state.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →