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

> Discover how the markdown editor handles partial files and auto-save. Learn about debounced timers and partial read offsets for efficient large file editing.

- Repository: [Eduard Ruzga/DesktopCommanderMCP](https://github.com/wonderwhy-er/DesktopCommanderMCP)
- Tags: internals
- Published: 2026-08-01

---

**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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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.

```typescript
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](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/controller.ts#L112)).

## 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.

```typescript
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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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:

```typescript
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](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/src/ui/file-preview/src/markdown/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/document-workspace.ts) and [`controller.ts`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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`](https://github.com/wonderwhy-er/DesktopCommanderMCP/blob/main/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.