feat: add watch command with live browser preview#17
Conversation
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
2dd9f92 to
78993f9
Compare
JonRC
left a comment
There was a problem hiding this comment.
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
-
Port validation — NaN possible (
src/cli.tsbuildWatchArgs)
Number.parseInt(opts.port as string, 10)can returnNaNfor invalid input (e.g.--port abc). Same pattern as the--gapNaN bug in combine. Add a fallback:port: Number.isNaN(Number.parseInt(opts.port as string, 10)) ? 3333 : Number.parseInt(opts.port as string, 10),
-
XSS in HTML page title (
src/watch.tsbuildHtmlPage)
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); }
Should fix
-
File watchers not cleaned up (
src/watch.ts)
watch(filePath, onFileChange)return values are discarded. Store theFSWatcherrefs and close them in the SIGINT handler for clean shutdown. -
xdg-openis Linux-only (src/watch.tsbrowser open)
Should detect platform:openon macOS,starton Windows. Or at minimum, document the limitation. Since the project targets Linux, this is low priority but worth a comment in code.
Nits
-
Inconsistent error logging: render errors during watch use
console.log()but validation errors useconsole.error(). Should useconsole.error()consistently for error messages. -
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
Summary
excalirender watchsubcommand that starts a local HTTP server with live browser preview.excalidrawfiles for changes and auto-refreshes the browser via Server-Sent Events (SSE)--dark,--scale,--transparent,--frame,--backgroundBun.serve(),fs.watch(), nativeReadableStreamFiles
src/watch.ts— HTTP server, SSE, file watcher, rendering (new)src/cli.ts—WatchCLIArgs,buildWatchArgs(), watch subcommandsrc/index.ts— watch routing with validationREADME.md— watch command section with options table and examplesdocs/WATCH.md— architecture and implementation detailsTest plan
excalirender watch diagram.excalidrawopens browser with rendered PNGexcalirender watch old.excalidraw new.excalidrawshows diff preview--dark,--scale 2,--transparent,--port,--no-openoptions work