dsh-preview-plugin
Manifest validLive preview tab for DSH — embed any dev server in an iframe with transparent SPA proxying.
DSH Preview Plugin
Live preview tab for DeepSeek Harness — embed any dev server in an iframe with transparent SPA proxying.
Features
- Path-based SPA proxy:
/preview/:port/*rewrites all routes so the embedded app works as if served from the DSH host - Editable URL bar: type a port, a path, a proxy path or a dev-server URL to move the preview wherever you need; the bar follows links clicked inside the page
- Multi-app monorepo support: Configure backend ports to proxy API calls through the same iframe (e.g. Astro frontend + Payload CMS backend)
- Smart routing: Static assets (JS, CSS, images) are proxied directly; SPA navigation serves root HTML so the client-side router handles routing
- Fragment-link safe:
#sectionlinks jump inside the page instead of reloading the site root, and anchors rendered after load are still reached - Root-relative resources work:
/assets/photo.png,srcsetandposterare rewritten too, so images and lazy-loaded stylesheets load instead of 404ing against the DSH host - Absolute URL rewriting: Intercepts
fetch()andXMLHttpRequest.open()to rewrite both relative and absolute URLs (e.g.http://localhost:3001/api/...→/preview/3001/api/...) - Real API traffic: Method, body, cookies and
Set-Cookieare forwarded, so the embedded app can log in, submit forms and usePUT/PATCH/DELETE— not just serve pages - Navigation stays inside the preview: forms,
history.pushState,window.openand links that open a new tab keep the proxy prefix; an app that assignslocation.href(which no script can intercept) is brought back instead of leaving you on the DSH app - Port config survives restarts: what you set is written to
$DSH_HOME/preview-plugin.jsonand restored when DSH starts again - Agent tool:
set_preview_porttool lets the agent configure ports programmatically when starting dev servers - Global or per-session ports: Set ports globally or per conversation session via the API
- Auto-sync: The preview tab polls the host for agent-set port changes
- Sandboxed iframe:
allow-scripts allow-same-origin allow-forms allow-popupsfor secure embedding
Installation
1. Add as a dependency
In your DSH web profile's package.json:
{
"dependencies": {
"dsh-preview-plugin": "file:~/workspace/dsh-preview-plugin"
}
}
2. Add to bundles
In your profile's package.json, add dsh-preview-plugin to the dsh.profile.bundles array:
{
"dsh": {
"profile": {
"bundles": [
"...",
"dsh-preview-plugin"
]
}
}
}
3. Restart DSH
systemctl restart dsh
Usage
Basic (single server)
- Start your dev server on the VPS (e.g.
npm run dev -- --host 0.0.0.0 --port 3000) - Open the Preview tab in DSH Web GUI
- Type the port (e.g.
3000) in the URL field and click Go
The field then switches to the page being shown (e.g. /preview/3000/laptops?page=2#specs),
so you can edit it at any time and press Enter to load something else.
Navigating
The URL field accepts all of these:
| You type | It loads |
|---|---|
4321 | port 4321, root page |
4321/laptops | port 4321, /laptops |
/laptops?page=2 | that path on the port already in use |
laptops | same as /laptops |
?page=2 / #specs | the current page with that query/fragment |
/preview/4321/laptops | a proxy path, as-is |
http://localhost:4321/laptops | the dev-server URL |
Non-local URLs are rejected (the proxy only reaches 127.0.0.1). ↻ reloads the
page currently shown, ↗ opens it in a browser tab of its own.
Multi-app monorepo
-
Start both servers:
# Backend (e.g. Payload CMS) cd apps/backend && pnpm dev # port 3001 # Frontend (e.g. Astro) cd apps/web && pnpm dev # port 4321 -
Open the Preview tab
-
Type
4321in URL and3001in Backend (comma-separated for multiple) -
Click Go — API calls to
http://localhost:3001/api/...are automatically proxied through/preview/3001/api/...
Agent tool
The agent can configure ports programmatically using the set_preview_port tool:
set_preview_port(port: 4321, backendPorts: [3001])
This updates the interceptor script and client UI automatically.
API
| Method | Endpoint | Description |
|---|---|---|
GET | /api/preview-port | Get the current global port and backend ports |
GET | /api/preview-port?sessionId=<id> | Get ports for a session (falls back to global) |
POST | /api/preview-port | Set global ports ({ "port": 4321, "backendPorts": [3001] }) |
POST | /api/preview-port?sessionId=<id> | Set ports for a session |
How It Works
- The host registers three routes on the DSH web server:
/api/preview-port— port management/preview/:port/*— transparent proxy
- Static assets (files with extensions like
.js,.css,.png) are proxied directly from the requested path - SPA navigation (all other paths) serves the root HTML from the dev server, so the client-side router handles routing
- For HTML responses, it injects:
<base href="https://github.com/RaulLazaro/dsh-preview-plugin/blob/HEAD/preview/:port/">for relative URL resolution- An interceptor script that:
- Handles fragment links (
#section) itself: with a<base>tag they would otherwise resolve to/preview/:port/#section, a different path, and reload the site root instead of jumping inside the page. Targets that appear later (SPA sections) are retried for a few seconds. - Rewrites
fetch()andXMLHttpRequest.open()URLs (relative, absolute frontend, and absolute backend) - Rewrites root-relative resource URLs (
src,srcset,poster,data-src, and resource<link href>) on nodes added by JS — a<base>tag only affects truly relative URLs, so"/assets/x.png"would otherwise be fetched from the DSH host
- Handles fragment links (
- Resource URLs in the initial HTML are rewritten server-side, before the page is sent
- Requests keep their method, headers and body, and
Set-Cookiecomes back, so sessions and writes work inside the preview - For non-HTML responses, it streams the response directly
Persistence
The preview config (frontend port + backend ports) is stored in
${DSH_HOME:-~/.dsh}/preview-plugin.json, so it survives a DSH restart and is shared by
every session. Deleting the file simply resets it; a corrupt file is ignored.
Known limit
location.href = "/…", location.assign() and location.replace() cannot be
intercepted: browsers install them as non-configurable own properties of the location
object ([LegacyUnforgeable]). The preview tab notices the escape and reloads that path
under the proxy, but an app that wants a clean URL should build it from its own base
(e.g. Astro's import.meta.env.BASE_URL) or use a relative path.
Testing
pnpm install
pnpm test
node --test runs three suites:
| Suite | What it covers |
|---|---|
test/client-helpers.test.mjs | URL/port parsing (resolveTarget) and proxy-path helpers |
test/preview-view.test.mjs | The Preview tab component with real React and happy-dom |
test/preview-proxy.test.mjs | The proxy end-to-end against a fixture site with Playwright (skipped when Chromium is unavailable) |
Security Notes
- The proxy only targets
127.0.0.1— no external SSRF possible - The iframe is sandboxed with
allow-scripts allow-same-origin allow-forms allow-popups - Port validation enforces 1–5 digit numbers only
- Backend ports are validated and stored per frontend port
- CORS headers are set for local development convenience
License
MIT
Comments
Loading…
Similar plugins
by Vithrive
DSH web plugin: a LiveBench tab in the Trajectory view. Run LiveBench evaluations against every model configured in the DeepSeek Harness and read scores in place.
★ 0
MIT
JavaScript
Sep 23, 2026
dsh plugin --profile web add dsh-livebench-panelby andrepontesmelo
DSH plugin: spawns one live session per ready Wayfinder task and draws the dependency map in the chat GUI
★ 0
MIT
JavaScript
Sep 29, 2026
dsh plugin --profile web add dsh-wayfinder-uiSidebar system monitor for the DSH Web UI: live host overview (CPU/memory/network/disk), process list, and Docker container status, all read-only.
★ 1
MIT
JavaScript
dsh plugin --profile web add dsh-side-monitorby nguyenduclong-ict
Real-time live streaming terminal output viewer for DeepSeek Harness (DSH Desktop)
★ 1
JavaScript
Sep 13, 2026
dsh plugin --profile web add dsh-plugin-live-terminalAdds a Reading tab to the DSH web client that shows process steps and live reasoning in source order, collapses successful turns to the final answer, and keeps the original tabs, composer, and tooling
★ 2
NOASSERTION
TypeScript
dsh plugin --profile web add dsh-clearviewby long6177
a dsh‑plugin for enhancing users' Markdown input experience | 一个 用于增强用户 markdown 输入体验的 dsh-plugin
★ 0
MIT
TypeScript
Oct 7, 2026
dsh plugin --profile web add dsh-markdown-input