How OfficeCLI Watch Enables Live Browser Previews with Auto‑Refresh

The officecli watch command starts a Server‑Sent‑Events (SSE) relay server that renders Office documents as HTML and pushes real‑time updates to connected browsers whenever the document changes.

OfficeCLI's watch command transforms static Office files into live, auto‑refreshing web previews. This article explains the complete implementation—from the initial HTML snapshot acquisition through real‑time DOM synchronization—based on the actual source code in the iOfficeAI/OfficeCLI repository.

Architecture Overview

The watch system consists of three coordinated components:

  • WatchServer – a lightweight TCP listener that serves HTML and manages SSE connections
  • WatchNotifier – a named‑pipe client used by other CLI commands to signal changes
  • Embedded JavaScript resources – client‑side code that receives SSE events and updates the DOM

This design keeps the watch process completely separate from document manipulation, avoiding file‑lock conflicts while enabling instant synchronization.

Starting the Watch Server

Command‑Line Invocation


# Start watching a Word document (default port 26315)

officecli watch mydoc.docx

# Watch a PowerPoint presentation on a custom port

officecli watch slides.pptx --port 8080

# The server prints a URL like: Watch: http://localhost:26315

When executed, the command in [CommandBuilder.Watch.cs](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/CommandBuilder.Watch.cs#L34-L70) performs two critical tasks:

  1. Obtains the initial HTML snapshot via ResidentClient.TrySend (fast path through the resident daemon) or falls back to RenderViaRegistry using the appropriate document handler (PowerPointHandler, ExcelHandler, or WordHandler)
  2. Launches WatchServer with the rendered HTML and configured port

The server immediately begins listening on http://localhost:<port> and injects two embedded resources—watch‑sse‑core.js and watch‑overlay.js—into every served page.

Server‑Sent Events Implementation

Core SSE Infrastructure

WatchServer in [WatchServer.cs](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/Watch/WatchServer.cs#L81-L91) implements pure Server‑Sent Events rather than WebSockets. This choice provides:

  • Unidirectional server‑to‑client streaming (no need for client acknowledgments)
  • Automatic reconnection handling in browsers
  • Simpler state management with HTTP/1.1 compatibility

The server maintains an in‑memory _currentHtml cache and a version counter. Each connected browser receives:

<!-- Injected by WatchServer -->
<script>
// watch-sse-core.js establishes EventSource connection
const es = new EventSource('/events');
es.onmessage = (e) => applyPatch(JSON.parse(e.data));
</script>

Real‑Time Update Pipeline

Changes flow through a named‑pipe IPC channel identified by officecli-watch-<hash>. Any command that modifies a watched document—set, add, remove, mark—activates the update chain:

  1. Command invokes WatchNotifier.Send in [WatchNotifier.cs](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/Watch/WatchNotifier.cs#L15-L25):
// Simplified from WatchNotifier.cs
public static void Send(string pipeName, WatchMessage message)
{
    using var pipe = new NamedPipeClientStream(".", pipeName, 
        PipeDirection.Out);
    pipe.Connect(timeoutMs: 500);
    
    var json = JsonSerializer.Serialize(message);
    pipe.Write(Encoding.UTF8.GetBytes(json));
}
  1. WatchServer receives and processes in [WatchServer.cs](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/Watch/WatchServer.cs#L95-L143):
// From RunPipeListenerAsync → HandleSinglePipeClientAsync
async Task HandleWatchMessage(WatchMessage msg)
{
    lock (_htmlLock)
    {
        if (msg.FullHtml != null)
            _currentHtml = msg.FullHtml;           // Complete replacement
        else if (msg.Patch != null)
            _currentHtml = PatchSlideInHtml(        // Incremental update
                _currentHtml, msg.SlideNumber, msg.Patch);
        
        _version++;
    }
    
    await BroadcastSseEvent(new 
    {
        action = msg.Action,           // "full", "replace", "add", "remove"
        slide = msg.SlideNumber,       // For PowerPoint context
        html = msg.HtmlFragment,       // When partial content provided
        scroll = msg.ScrollSelector,   // Optional auto-scroll target
        version = _version
    });
}
  1. Browser applies DOM changes through watch‑sse‑core.js:
    • Full refresh – replaces entire <body> content
    • Slide‑level patch – updates specific slide element (PowerPoint)
    • Block‑level diff – computes and applies minimal changes (Word via ComputeWordPatches)
    • Scroll coordination – navigates to element matching the provided selector

Message Types and Incremental Updates

The WatchMessage protocol supports multiple update strategies to minimize browser rendering overhead:

Message Type Use Case Data Contents
FullHtml Major structural changes, initial sync Complete HTML document string
replace Single slide replacement (PowerPoint) Slide number + new HTML fragment
add New slide appended (PowerPoint) Slide number + insert position
remove Slide deletion (PowerPoint) Slide number only
scroll Navigation without content change CSS selector for target element

Incremental patching is implemented in PatchSlideInHtml, AppendSlideToHtml, and RemoveSlideFromHtml methods, which manipulate the cached HTML string using DOM‑aware parsing before re‑serving to browsers.

Interactive Features: Marks and Annotations

The watch system supports live annotation through the mark sub‑command:


# Highlight a specific paragraph in the live preview

officecli watch mark mydoc.docx "/body/p[3]" --color "#00ff00" --note "Review required"

# Remove a mark

officecli watch unmark mydoc.docx "/body/p[3]"

# List all active marks

officecli watch marks mydoc.docx

Mark metadata is stored in‑memory by WatchServer and synchronized via the same SSE channel. The watch‑overlay.js resource renders visual indicators and selection rectangles directly on the preview.

Resource Management and Lifecycle

Idle Shutdown

To prevent orphaned processes, WatchServer implements automatic termination in [WatchServer.cs](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Core/Watch/WatchServer.cs#L55-L73):

// Default: 5 minutes (configurable via OFFICECLI_WATCH_IDLE_SECONDS)
async Task RunIdleWatchdogAsync(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        await Task.Delay(TimeSpan.FromSeconds(checkInterval), ct);
        
        var idleTime = DateTime.UtcNow - _lastClientActivity;
        if (idleTime > _idleTimeout && _sseClients.IsEmpty)
        {
            _logger.LogInformation("Idle timeout elapsed — shutting down");
            _shutdownCts.Cancel();
            return;
        }
    }
}

Graceful Termination

Signal handlers capture SIGTERM, SIGHUP, SIGQUIT, and Ctrl‑C, triggering StopAsync which:

  1. Cancels all listener tasks
  2. Closes TCP sockets gracefully
  3. Deletes the on‑disk marker file
  4. Releases named pipe resources

Complete Workflow Example


# Terminal 1: Start the watch server

$ officecli watch report.docx
Watch: http://localhost:26315

# Terminal 2: Make live edits — each command triggers instant refresh

$ officecli set report.docx --text "Q4 Financial Results"
$ officecli set report.docx --heading "Executive Summary" --level 1
$ officecli watch mark report.docx "/body/h1[1]" --color "#ff6600"

# The browser at localhost:26315 updates automatically after each command

Summary

  • Pure SSE architecture eliminates WebSocket complexity and browser compatibility issues
  • Named‑pipe IPC enables any CLI command to push updates without direct file access
  • Incremental patching minimizes DOM manipulation for better performance
  • In‑memory operation prevents file‑lock contention with Office applications
  • Automatic lifecycle management ensures servers terminate cleanly when unused

Frequently Asked Questions

How does OfficeCLI watch avoid file‑lock conflicts with Microsoft Office?

The watch server never opens the Office document directly. It receives HTML snapshots either from the resident daemon process or through RenderViaRegistry at startup, then operates solely on cached HTML. When modifications occur, other CLI commands handle document access and push updates via named pipe, leaving the watch process isolated from file I/O.

Can multiple browsers connect to the same watch server simultaneously?

Yes. WatchServer maintains a concurrent collection of SSE clients (_sseClients) and broadcasts events to all connected browsers. Each client receives identical update sequences, though scroll commands may target different viewport positions depending on individual browser state.

What happens if the watch server crashes or is forcibly terminated?

The named pipe and TCP socket are released by the operating system. Subsequent CLI commands attempting to notify the watch will fail silently after a brief timeout. A new officecli watch invocation creates fresh server resources with a new port and pipe name.

Is the live preview suitable for production document sharing?

No. The watch server binds to localhost only and lacks authentication, HTTPS, or access controls. It is designed exclusively for local development workflows. For production sharing, export static HTML via officecli render or deploy through proper web infrastructure.

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 →