Skip to content

fix: prevent stale markdown preview cache in app://md-preview protoco…#2387

Open
dushyant-hada-90 wants to merge 12 commits into
AgentWrapper:mainfrom
dushyant-hada-90:fix/markdown-preview-cache
Open

fix: prevent stale markdown preview cache in app://md-preview protoco…#2387
dushyant-hada-90 wants to merge 12 commits into
AgentWrapper:mainfrom
dushyant-hada-90:fix/markdown-preview-cache

Conversation

@dushyant-hada-90

@dushyant-hada-90 dushyant-hada-90 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Overview

This PR adds live markdown preview in the Electron browser panel alongside ao preview CLI support, file-watching with auto-refresh, and CSS-only theme control. The implementation spans backend (daemon + CLI), frontend (Electron main/renderer/preload), and agent skill assets.

Features

Markdown rendering in browser panel

  • .md files are rendered to styled HTML via marked + DOMPurify inside the Electron BrowserView using a custom app://md-preview protocol handler
  • Chromium caching is disabled on the protocol handler (Cache-Control: no-cache, no-store, must-revalidate) so reload() always fetches fresh content
  • File watching via chokidar with debounce provides live refresh on save — the watcher re-renders on change, and the renderer calls reload() on the BrowserView

ao preview CLI command

  • ao preview [url] — opens a URL in the desktop browser panel for the current session; with no argument, opens the workspace's index.html
  • ao preview clear — empties the panel
  • Relative paths are resolved to absolute before sending to the daemon
  • "File not found" for non-existent paths
  • Missing $AO_SESSION_ID returns a usage error

Daemon preview endpoints

  • POST /api/v1/sessions/{id}/preview — set preview URL; accepts file paths (resolved to file://), HTTP URLs, or daemon-proxy URLs
  • DELETE /api/v1/sessions/{id}/preview — clear preview; autodetects index.html in the workspace or falls back to blank
  • Preview state flows through CDC (SQLite triggers → SSE → session_updated event) to the renderer

File deletion handling

  • When a watched .md file is deleted, the daemon DELETE endpoint is called, reverting to index.html (if it exists) or clearing to blank — no stale content, no error page
  • Daemon port is wired into MarkdownHost via reportBoundPortsetDaemonPort() for the HTTP DELETE call

CSS-only dark/light theme toggle

  • Hidden checkbox + <label for> hack with body:has(#theme-toggle:checked) selectors
  • Full-viewport background via body:has() instead of .content (fixes white border from max-width: 920px)
  • Smooth transition: color 0.25s / background 0.25s
  • Zero JavaScript — fully CSP-compatible (script-src 'none')

Agent auto-preview instructions

  • SKILL.md updated to direct agents to run ao preview <path> immediately after creating a .md file
  • If producing multiple .md files, only the last one is auto-previewed (panel shows one at a time)

Key changes by layer

Layer Files Scope
Backend daemon internal/httpd/controllers/sessions.go Preview endpoints + index.html autodetection on clear
Backend CLI internal/cli/preview.go ao preview command with relative path resolution
Backend tests internal/cli/preview_test.go Tests for preview, clear, path resolution, missing session, help text
Frontend main main.ts, markdown-host.ts, markdown-renderer.ts Protocol handler, file watcher, markdown→HTML rendering, daemon port wiring
Frontend preload preload.ts IPC bridge for renderMarkdown / md:fileChanged
Frontend renderer components/BrowserPanel.tsx, hooks/useBrowserView.ts Markdown URL detection, render routing, file-change listener
Agent assets backend/internal/skillassets/markdown-preview/SKILL.md Auto-preview workflow instructions

Testing

  • CLI unit tests pass on Ubuntu, macOS, and Windows
  • Full go test ./internal/... and go vet ./internal/...

…l handler

Add Cache-Control: no-cache, no-store, must-revalidate headers to the
app://md-preview protocol handler response so Chromium does not cache
the rendered HTML. When the file-backed markdown changes, the chokidar
watcher updates the cached HTML in MarkdownHost and emits md:fileChanged;
the renderer calls reload(), which re-requests the URL. Without cache
prevention headers, Chromium served the stale cached copy instead of
re-invoking the protocol handler for the updated HTML.
…mon proxy URL file watching

The markdown auto-refresh failed for daemon-proxied URLs because:
1. Chromium cached the app://md-preview response (Cache-Control fix already deployed)
2. Daemon proxy URLs (http://host/api/v1/sessions/<id>/preview/files/<entry>)
   bypassed chokidar watchers since resolveLocalPath() only handled file://

Fix:
- Backend: sessionView() populates WorkspacePath from SessionMetadata
- API: SessionView DTO + regenerated OpenAPI spec + TypeScript types
- Frontend: workspacePath flows SessionView → BrowserPanel → useBrowserView
  → preload → markdownHost.render() → resolveLocalPath()
- markdown-host.ts: parseDaemonProxyEntry() extracts entry from proxy URL and
  resolves it against workspacePath with directory traversal guard
- Test: removed negative assertion for workspacePath leak (now curated field)
…roaden BrowserPanel path normalization

- Replace httpFetch() with native fetch() — simpler, standard, no extra deps
- Add tryLocalFile() fallback for UNC/drive-letter paths that fail fileURLToPath
- Guard file:// URLs from reaching fetch() to avoid ERR_INVALID_PROTOCOL
- Broaden BrowserPanel path normalization to handle UNC (\\host) and
  Windows drive-letter (C:\) paths
- Remove md_implementation.md from git tracking (internal reference doc,
  not intended for remote)
@dushyant-hada-90 dushyant-hada-90 marked this pull request as ready for review July 4, 2026 18:05
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