Skip to content

feat: add watch command with live browser preview#17

Merged
JonRC merged 3 commits intomainfrom
feat/watch-command
Feb 9, 2026
Merged

feat: add watch command with live browser preview#17
JonRC merged 3 commits intomainfrom
feat/watch-command

Conversation

@JonRC
Copy link
Copy Markdown
Owner

@JonRC JonRC commented Feb 6, 2026

Summary

  • Add excalirender watch subcommand that starts a local HTTP server with live browser preview
  • Watches .excalidraw files for changes and auto-refreshes the browser via Server-Sent Events (SSE)
  • 1 file = export mode, 2 files = diff mode (auto-detected)
  • Supports all rendering options: --dark, --scale, --transparent, --frame, --background
  • No new dependencies — uses Bun.serve(), fs.watch(), native ReadableStream

Files

  • src/watch.ts — HTTP server, SSE, file watcher, rendering (new)
  • src/cli.tsWatchCLIArgs, buildWatchArgs(), watch subcommand
  • src/index.ts — watch routing with validation
  • README.md — watch command section with options table and examples
  • docs/WATCH.md — architecture and implementation details

Test plan

  • CI checks pass (lint, typecheck, unit-tests, visual-tests, docker-build, native-build)
  • excalirender watch diagram.excalidraw opens browser with rendered PNG
  • excalirender watch old.excalidraw new.excalidraw shows diff preview
  • File save triggers auto-refresh in browser
  • --dark, --scale 2, --transparent, --port, --no-open options work
  • Parse errors don't crash — keeps last good render
  • Ctrl+C cleanly stops the server

JonRC and others added 2 commits February 9, 2026 20:35
Add `excalirender watch` subcommand that starts a local HTTP server
with live browser preview. Watches .excalidraw files for changes and
auto-refreshes via SSE. Supports export mode (1 file) and diff mode
(2 files) with all rendering options (--dark, --scale, --transparent).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Validation tests: 0 files, 3+ files, stdin errors
- Server tests: HTML page, PNG image, SSE endpoint, custom port, diff mode
- Options tests: dark mode rendering, scale factor dimensions
- Add test:watch script to package.json and CI workflow
@JonRC JonRC force-pushed the feat/watch-command branch from 2dd9f92 to 78993f9 Compare February 9, 2026 23:38
Copy link
Copy Markdown
Owner Author

@JonRC JonRC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: feat/watch command

Overall the architecture is solid — clean HTTP server, SSE live reload, file watcher with debounce. A few items to address:

Must fix

  1. Port validation — NaN possible (src/cli.ts buildWatchArgs)
    Number.parseInt(opts.port as string, 10) can return NaN for invalid input (e.g. --port abc). Same pattern as the --gap NaN bug in combine. Add a fallback:

    port: Number.isNaN(Number.parseInt(opts.port as string, 10))
      ? 3333
      : Number.parseInt(opts.port as string, 10),
  2. XSS in HTML page title (src/watch.ts buildHtmlPage)
    Filename is interpolated directly into <title> and <span> without escaping. A file named "><script>alert(1)</script> would execute JS. Add HTML escaping:

    function escapeHtml(s: string): string {
      return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
    }

Should fix

  1. File watchers not cleaned up (src/watch.ts)
    watch(filePath, onFileChange) return values are discarded. Store the FSWatcher refs and close them in the SIGINT handler for clean shutdown.

  2. xdg-open is Linux-only (src/watch.ts browser open)
    Should detect platform: open on macOS, start on Windows. Or at minimum, document the limitation. Since the project targets Linux, this is low priority but worth a comment in code.

Nits

  1. Inconsistent error logging: render errors during watch use console.log() but validation errors use console.error(). Should use console.error() consistently for error messages.

  2. Add comment for DEBOUNCE_MS = 200: explain why 200ms (handles editor save sequences: temp file → rename).

What looks good

  • Dynamic import of watch.ts in index.ts — correct pattern for optional heavy module
  • SSE implementation is clean and lightweight
  • Debounce logic prevents render storms
  • Error resilience — parse errors don't crash, keeps last good render
  • Test coverage: 10 unit tests covering validation, server responses, and options

…rm open

- Add NaN guard on --port to fallback to 3333 for invalid input
- Escape HTML in watch preview page title to prevent XSS
- Store FSWatcher refs and close them on SIGINT for clean shutdown
- Detect platform for browser open (macOS: open, Windows: cmd /c start)
- Use console.error consistently for error messages
- Add comment explaining DEBOUNCE_MS value
@JonRC JonRC merged commit 281d4b8 into main Feb 9, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant