diff --git a/README.md b/README.md index 458b0e6..7bc1597 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ https://github.com/user-attachments/assets/62c52183-83a3-4fb5-97b1-ed001de4f53a - Typeahead find for quickly finding and launching sessions with minimal friction - Web terminal with reconnect support - Ghostty WebAssembly terminal engine for fast rendering from [`ghostty-web`](https://github.com/rcarmo/ghostty-web) -- Session dashboard with live SVG screenshots from [`go-te`](https://github.com/rcarmo/go-te) +- Session dashboard with live SVG (or optional PNG) screenshots from [`go-te`](https://github.com/rcarmo/go-te) - Docker watch mode (`webterm-command` / `webterm-theme` labels) - Docker compose manifest ingestion - CPU sparkline tiles for compose services diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 3b209b2..0ce508b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -16,7 +16,8 @@ webterm/server.go (LocalServer) ├── docker_exec_session.go (Docker exec-backed sessions) ├── docker_watcher.go (container add/remove discovery) ├── docker_stats.go (CPU sampling + sparkline data) - └── svg_exporter.go (terminal snapshot -> SVG) + ├── svg_exporter.go (terminal snapshot -> SVG) + └── png_exporter.go (terminal snapshot -> PNG via coverage blending) ``` ## Packages @@ -24,6 +25,7 @@ webterm/server.go (LocalServer) - `cmd/webterm`: CLI entrypoint - `webterm`: server/runtime/domain logic - `internal/terminalstate`: Go terminal emulator wrapper (`go-te`) used for screenshots +- `webterm/coverage_table.go`: coverage map for approximate PNG rendering ## Runtime data flow @@ -33,7 +35,7 @@ webterm/server.go (LocalServer) - live WS stream (`stdout`) - replay buffer (reconnect support) - terminal-state tracker (`go-te`) for screenshots -4. Dashboard pulls `/screenshot.svg` and listens on `/events` for activity. +4. Dashboard pulls `/screenshot.svg` (default) or `/screenshot.png` when `WEBTERM_SCREENSHOT_MODE=png`, and listens on `/events` for activity. ## Static assets diff --git a/webterm/server.go b/webterm/server.go index d85ac7b..78c8881 100644 --- a/webterm/server.go +++ b/webterm/server.go @@ -1087,12 +1087,11 @@ func (s *LocalServer) handleRoot(w http.ResponseWriter, r *http.Request) { dockerWatchJS = "true" } screenshotEndpoint := "/screenshot.svg" + screenshotDownloadEndpoint := "/screenshot.svg" screenshotDownloadQuery := "sanitize_font_urls=1&download=1" screenshotDownloadExt := "svg" if s.screenshotMode == "png" { screenshotEndpoint = "/screenshot.png" - screenshotDownloadQuery = "download=1" - screenshotDownloadExt = "png" } html := fmt.Sprintf(` @@ -1151,6 +1150,7 @@ func (s *LocalServer) handleRoot(w http.ResponseWriter, r *http.Request) { const composeMode = %s; const dockerWatchMode = %s; const screenshotEndpoint = %q; + const screenshotDownloadEndpoint = %q; const screenshotDownloadQuery = %q; const screenshotDownloadExt = %q; let cardsBySlug = {}; @@ -1172,7 +1172,7 @@ func (s *LocalServer) handleRoot(w http.ResponseWriter, r *http.Request) { function downloadSanitizedScreenshot(slug) { if (!slug) return; const link = document.createElement('a'); - link.href = screenshotEndpoint + '?route_key=' + encodeURIComponent(slug) + '&' + screenshotDownloadQuery + '&_t=' + Date.now(); + link.href = screenshotDownloadEndpoint + '?route_key=' + encodeURIComponent(slug) + '&' + screenshotDownloadQuery + '&_t=' + Date.now(); link.download = slug + '-screenshot.' + screenshotDownloadExt; document.body.appendChild(link); link.click(); @@ -1605,7 +1605,7 @@ func (s *LocalServer) handleRoot(w http.ResponseWriter, r *http.Request) { } -`, string(tilesJSON), composeModeJS, dockerWatchJS, screenshotEndpoint, screenshotDownloadQuery, screenshotDownloadExt) +`, string(tilesJSON), composeModeJS, dockerWatchJS, screenshotEndpoint, screenshotDownloadEndpoint, screenshotDownloadQuery, screenshotDownloadExt) w.Header().Set("Content-Type", "text/html; charset=utf-8") _, _ = io.WriteString(w, html) return diff --git a/webterm/server_test.go b/webterm/server_test.go index afd20ba..ac6202e 100644 --- a/webterm/server_test.go +++ b/webterm/server_test.go @@ -552,6 +552,9 @@ func TestDashboardUsesPNGWhenEnabled(t *testing.T) { if !strings.Contains(text, "screenshot.png") { t.Fatalf("expected dashboard to request png screenshots when enabled") } + if !strings.Contains(text, "sanitize_font_urls=1&download=1") || !strings.Contains(text, "screenshot.svg") { + t.Fatalf("expected contextmenu downloads to use svg screenshots") + } } func TestRootTerminalPageAndSparklineValidation(t *testing.T) {