Add right-click sanitized SVG export for dashboard tiles

Introduce a dashboard workflow to export screenshot SVGs without external font URL references.

Server changes:
- Added screenshot query flags: sanitize_font_urls=1 and download=1.
- Added SVG sanitization that strips @font-face src:url(...) for the vendored font path.
- Added safe filename normalization for download responses.
- Added Content-Disposition attachment support for downloadable screenshot exports.
- Preserved existing cache behavior while computing ETags from the actual response variant.

Dashboard changes:
- Added tile contextmenu handler (right-click) to trigger sanitized SVG download per tile.
- Download URL includes cache-busting timestamp to avoid stale browser downloads.

Tests:
- Added coverage for sanitized download response (attachment header + font URL removal).
- Added coverage asserting dashboard HTML includes right-click sanitized download wiring.
- Validated with make format && make check.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
GitHub Copilot
2026-02-15 15:11:12 +00:00
parent 5ade64b367
commit 5017e3a53f
2 changed files with 117 additions and 25 deletions
+33
View File
@@ -229,6 +229,39 @@ func TestScreenshotCreatesSessionFromRequestedRoute(t *testing.T) {
}
}
func TestScreenshotSanitizedDownloadRemovesFontFaceURL(t *testing.T) {
_, httpServer, _ := newServerForTests(t, false)
resp, err := http.Get(httpServer.URL + "/screenshot.svg?route_key=shell&sanitize_font_urls=1&download=1")
if err != nil {
t.Fatalf("screenshot request error = %v", err)
}
body, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected 200, got %d body=%q", resp.StatusCode, string(body))
}
if disposition := resp.Header.Get("Content-Disposition"); !strings.Contains(disposition, "attachment;") || !strings.Contains(disposition, "shell-screenshot.svg") {
t.Fatalf("unexpected content disposition: %q", disposition)
}
if strings.Contains(string(body), `src:url("/static/fonts/FiraCodeNerdFont-Regular.ttf")`) {
t.Fatalf("expected sanitized svg without font-face url")
}
}
func TestDashboardIncludesContextMenuSanitizedDownload(t *testing.T) {
_, httpServer, _ := newServerForTests(t, true)
resp, err := http.Get(httpServer.URL + "/")
if err != nil {
t.Fatalf("dashboard request error = %v", err)
}
body, _ := io.ReadAll(resp.Body)
_ = resp.Body.Close()
text := string(body)
if !strings.Contains(text, "contextmenu") || !strings.Contains(text, "sanitize_font_urls=1&download=1") {
t.Fatalf("expected contextmenu sanitized download wiring in dashboard page")
}
}
func TestRootTerminalPageAndSparklineValidation(t *testing.T) {
_, httpServer, _ := newServerForTests(t, false)
resp, err := http.Get(httpServer.URL + "/?route_key=shell")