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:
- Obtains the initial HTML snapshot via
ResidentClient.TrySend(fast path through the resident daemon) or falls back toRenderViaRegistryusing the appropriate document handler (PowerPointHandler,ExcelHandler, orWordHandler) - Launches
WatchServerwith 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:
- Command invokes
WatchNotifier.Sendin [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));
}
- 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
});
}
- 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
- Full refresh – replaces entire
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:
- Cancels all listener tasks
- Closes TCP sockets gracefully
- Deletes the on‑disk marker file
- 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →