# How the HTML Rendering Engine Works for Document Visualization in OfficeCLI

> Discover how the OfficeCLI HTML rendering engine visualizes Office documents in the browser using a two-layer JavaScript architecture for SSE streaming DOM mutations and interactive overlays.

- Repository: [OfficeAI/OfficeCLI](https://github.com/iofficeai/OfficeCLI)
- Tags: internals
- Published: 2026-08-07

---

**The OfficeCLI HTML rendering engine visualizes Word, Excel, and PowerPoint documents in the browser through a two-layer JavaScript architecture: Layer 1 handles Server-Sent Events (SSE) streaming and DOM mutations via [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js), while Layer 2 manages selection overlays and interactive decorations via [`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js).**

OfficeCLI transforms Office documents into live, interactive web visualizations without requiring desktop applications. This article explains the complete rendering pipeline based on the `iOfficeAI/OfficeCLI` source code, walking through how document updates flow from server to screen and how user interactions sync back in real time.

## Two-Layer Rendering Architecture

The HTML rendering engine splits responsibilities between tightly coupled but functionally distinct layers:

| Layer | Responsibility | Core File |
|-------|----------------|-----------|
| **Layer 1 – Document Rendering & Navigation** | SSE stream management, full document swaps, diff/patch updates, scroll commands, and re-apply hook infrastructure | [[`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js)](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Resources/watch-sse-core.js) |
| **Layer 2 – Overlay & Decoration** | Selection tracking, rectangular overlay drawing, CSS class injection, mouse-driven interactions, and inline marks | [[`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js)](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Resources/watch-overlay.js) |

The coupling contract between layers operates through two global window properties: `window._watchEs` exposes the SSE connection for Layer 2 to listen on, and `window._watchReapplyHook` allows Layer 1 to trigger visual refreshes after any DOM mutation.

## Layer 1: Event-Driven Document Rendering

### SSE Connection Initialization

When the visualization page loads, [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) establishes a persistent Server-Sent Events connection to `/events` and stores it globally:

```javascript
// In watch-sse-core.js (auto-executed)
(function () {
  const es = new EventSource('/events');   // ← opens SSE stream
  window._watchEs = es;                    // ← exported for overlay layer
  // …register listeners for 'update', 'doc-switched', etc.
})();

```

This `EventSource` listens continuously for server-side update events that carry JSON payloads with an `action` field determining how to process the message.

### Message Dispatch and Routing

The main event listener in [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) parses incoming SSE data and routes to specialized handlers based on the `action` field:

- **`full`** – Complete document body replacement
- **`word-diff`** – Incremental diff for Word documents
- **`word-patch`** – Granular DOM patch operations
- **`scroll`** – Viewport positioning without DOM changes

```javascript
// Simplified dispatch pattern from watch-sse-core.js#L17-L33
function onMessage(event) {
  const msg = JSON.parse(event.data);
  switch (msg.action) {
    case 'full':           _replaceDocumentBody(msg); break;
    case 'word-diff':      wordDiffUpdate(msg);       break;
    case 'word-patch':     wordPatchUpdate(msg);      break;
    case 'scroll':         scrollToSlide(msg);        break;
  }
  _callReapplyHook();  // notify Layer 2 to repaint overlays
}

```

### Full Document Swaps

For non-Word documents (Excel, PowerPoint), the `_replaceDocumentBody` function in [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) handles complete refreshes. This function:

1. Fetches current HTML from the root endpoint
2. Parses with `DOMParser` to extract `<head>` styles
3. Preserves scroll position before mutation
4. Re-injects the SSE script into the new body
5. Invokes `_callReapplyHook()` for overlay synchronization

```javascript
function _replaceDocumentBody(msg) {
  fetch('/').then(r => r.text()).then(html => {
    const doc = new DOMParser().parseFromString(html, 'text/html');
    // Copy head styles, preserve scroll offset
    const scrollY = window.scrollY;
    document.head.innerHTML = doc.head.innerHTML;
    document.body.innerHTML = doc.body.innerHTML;
    window.scrollTo(0, scrollY);
    // Re-inject SSE script and notify overlay layer
    _callReapplyHook();
  });
}

```

### Word Document: Diff Algorithm

Word documents use `wordDiffUpdate` for incremental updates. This lightweight algorithm de-paginates content, merges old and new sections, and replaces only changed node ranges. If the version gap exceeds a threshold, it falls back to full replacement.

### Word Document: Granular Patching

The `wordPatchUpdate` function applies surgical DOM operations using marker elements (`.wb` / `.we` classes) that delimit content blocks. Supported operations include `add`, `replace`, `remove`, and `style`. After each patch, it re-paginates via `window._wordPaginate()` and triggers the re-apply hook.

```javascript
// From watch-sse-core.js#L221-L259
function wordPatchUpdate(msg) {
  msg.patches.forEach(patch => {
    const startMarker = findMarker(patch.start, 'wb');
    const endMarker   = findMarker(patch.end,   'we');
    const range = document.createRange();
    range.setStartAfter(startMarker);
    range.setEndBefore(endMarker);
    
    switch (patch.op) {
      case 'replace': range.deleteContents(); range.insertNode(parseHtml(patch.html)); break;
      case 'remove':  range.deleteContents();  break;
      case 'style':   applyStyles(range, patch.css); break;
    }
  });
  window._wordPaginate?.();
  _callReapplyHook();
}

```

### Scroll Commands

Pure navigation without DOM mutation: the `scroll` action accepts a selector or slide number and adjusts viewport position directly.

## Layer 2: Selection Overlay and Interactive Decoration

### Coupling Contract with Layer 1

[`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js) reads the shared SSE connection and registers its refresh callback:

```javascript
// From watch-overlay.js#L5-L9
(function () {
  const es = window._watchEs;  // ← consume Layer 1's EventSource
  window._watchReapplyHook = reapplyDecorations;
  
  es.addEventListener('selection-update', onSelectionUpdate);
  es.addEventListener('marks-update', onMarksUpdate);
})();

```

### Selection State Synchronization

The client maintains `_selection` as a local mirror of the server's `currentSelection`. Server updates arrive via `selection-update` events, replace the local array, and immediately trigger `applySelectionToDom()`.

```javascript
// From watch-overlay.js#L41-L50
function onSelectionUpdate(event) {
  const data = JSON.parse(event.data);
  _selection.length = 0;
  _selection.push(...data.paths);
  applySelectionToDom();
}

```

### Rectangular Overlay Positioning

For contiguous Excel-style ranges, `_detectRect` computes bounding sheet coordinates. The `_selOverlay` div positions absolutely within the nearest `<table>` to maintain alignment during scrolling:

```javascript
function _positionSelOverlay(cellEls) {
  const container = cellEls[0].closest('table') || document.body;
  const ov = _getSelOverlayEl();
  
  // Compute union rectangle of selected cells
  const rects = cellEls.map(el => el.getBoundingClientRect());
  const minL = Math.min(...rects.map(r => r.left));
  const minT = Math.min(...rects.map(r => r.top));
  const maxR = Math.max(...rects.map(r => r.right));
  const maxB = Math.max(...rects.map(r => r.bottom));
  
  const cRect = container.getBoundingClientRect();
  ov.style.left   = `${minL - cRect.left + container.scrollLeft}px`;
  ov.style.top    = `${minT - cRect.top  + container.scrollTop}px`;
  ov.style.width  = `${maxR - minL}px`;
  ov.style.height = `${maxB - minT}px`;
  ov.style.display = 'block';
}

```

### CSS Injection for Highlights

A self-executing block at module startup injects all highlight classes into the document head:

```javascript
// From watch-overlay.js#L27-L71
(function injectStyles() {
  const style = document.createElement('style');
  style.textContent = `
    .officecli-range-fill { background: rgba(66, 133, 244, 0.16); }
    .officecli-row-header { background: #e8f0fe; }
    .officecli-col-header { background: #e8f0fe; }
    .officecli-handle      { border: 2px solid #4285f4; }
    .officecli-mark        { background: yellow; }
    /* …additional classes */
  `;
  document.head.appendChild(style);
})();

```

### Mouse-Driven Interactions

[`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js) handles click selection (with Shift/Ctrl modifiers), rubber-band dragging, header dragging, and chart dragging. Each interaction:

1. Mutates `_selection` or `_marks` locally
2. Calls `applySelectionToDom()` for immediate visual feedback
3. Posts changes back to server endpoints

```javascript
// Simplified interaction handler from watch-overlay.js#L140-L148
function onMouseUp(event) {
  const newSelection = computeSelectionFromDrag();
  _selection.length = 0;
  _selection.push(...newSelection);
  applySelectionToDom();
  
  fetch('/api/selection', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ paths: _selection })
  });
}

```

### Inline Marks (Search Highlights)

The `applyMarks()` function processes search results and annotations. It distinguishes block-level marks from inline patterns (plain strings or regular expressions), builds text node maps for target elements, and wraps matching ranges in `<span class="officecli-mark…">` elements.

Crucially, Layer 2 never mutates the underlying document structure—only CSS classes and transient wrappers—ensuring that Layer 1's full or diff updates can safely replace content without corrupting visual state.

## Complete Data Flow

```

┌─────────┐     SSE (/events)      ┌─────────────────────┐
│ Server  │ ─────────────────────→ │ watch-sse-core.js   │
│         │                        │ (Layer 1)             │
│         │  update {action,…}     │  ├─► full swap      │
│         │───────────────────────→│  ├─► diff           │
│         │                        │  ├─► patch          │
│         │                        │  └─► scroll         │
│         │                        │                       │
│         │  after DOM mutation    │  _callReapplyHook()  │
│         │←───────────────────────│        ↓             │
│         │                        ├─────────────────────┤
│         │                        │ watch-overlay.js    │
│         │  selection-update      │ (Layer 2)           │
│         │───────────────────────→│  ├─► overlay draw   │
│         │  marks-update          │  ├─► CSS injection  │
│         │───────────────────────→│  └─► mouse handling │
│         │                        │                       │
│         │  POST /api/selection   │  user interaction ──┐│
│         │←───────────────────────│  POST /api/send     ││
└─────────┘                        └─────────────────────┘

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`src/officecli/Resources/watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Resources/watch-sse-core.js) | **Layer 1:** SSE connection, DOM swapping, Word diff/patch logic, scroll handling, re-apply hook infrastructure |
| [`src/officecli/Resources/watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/src/officecli/Resources/watch-overlay.js) | **Layer 2:** Selection state, overlay rendering, CSS injection, mouse interactions, marks |
| [`npm/officecli.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/npm/officecli.js) | CLI entry point that boots the web server for HTML serving and SSE streaming |
| [`sdk/node/index.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js) | Node SDK for server-side HTML payload generation |

## Summary

- **Two-layer architecture** separates document mutation (**[`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js)**) from visual decoration (**[`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js)**), connected via `window._watchEs` and `window._watchReapplyHook`
- **Server-Sent Events** drive all rendering through a single `/events` stream carrying update, selection, and mark messages
- **Three update strategies**: full body replacement for Excel/PPT, diff merging for Word, and granular patch operations for Word blocks
- **Overlay system** computes absolute-positioned rectangles for Excel ranges and injects self-contained CSS without touching document structure
- **Bidirectional synchronization**: server pushes state changes, client posts interaction results to `/api/selection` and `/api/send`

## Frequently Asked Questions

### What protocol does OfficeCLI use for real-time document updates?

OfficeCLI uses **Server-Sent Events (SSE)** via the `/events` endpoint. The `EventSource` API in [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) maintains a persistent connection that streams JSON messages with `action` fields (`full`, `word-diff`, `word-patch`, `scroll`) for document updates and `selection-update`/`marks-update` for overlay state. This unidirectional server-push protocol was chosen over WebSockets for simpler reconnection handling and HTTP compatibility.

### How does OfficeCLI preserve user selections during document refreshes?

The **re-apply hook pattern** preserves selections across DOM mutations. [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) exposes `window._watchReapplyHook`, which [`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js) sets to its `reapplyDecorations` function. After every full swap, diff, or patch operation, Layer 1 calls this hook, causing Layer 2 to re-query `_selection` state and redraw overlays at the new DOM positions. Since Layer 2 only adds CSS classes and transient wrappers—not structural changes—Layer 1 can safely replace content without losing selection state.

### Why does Word document rendering use both diff and patch strategies?

Word documents require **granular update strategies** because pagination makes full replacements visually disruptive. The `wordDiffUpdate` function handles section-level merging for moderate changes, while `wordPatchUpdate` applies surgical DOM operations (`add`, `replace`, `remove`, `style`) using `.wb`/`.we` marker elements. If version gaps are detected, the engine falls back from patch to diff, ensuring consistency while minimizing layout shifts.

### Can the rendering engine handle multiple users editing simultaneously?

The source code shows **single-user architecture** with server-authoritative selection state. While the SSE stream broadcasts updates to all connected clients, there's no operational transform or conflict resolution logic visible in [`watch-sse-core.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-sse-core.js) or [`watch-overlay.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/watch-overlay.js). The `/api/selection` endpoint posts single-user changes; concurrent modifications would likely exhibit last-write-wins behavior based on server-side implementation in [`npm/officecli.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/npm/officecli.js) or [`sdk/node/index.js`](https://github.com/iOfficeAI/OfficeCLI/blob/main/sdk/node/index.js).