Skip to content

Add session web preview mode#430

Merged
shantur merged 5 commits intodevfrom
web-preview
May 10, 2026
Merged

Add session web preview mode#430
shantur merged 5 commits intodevfrom
web-preview

Conversation

@shantur
Copy link
Copy Markdown
Collaborator

@shantur shantur commented May 10, 2026

Summary

  • Add ephemeral per-session web preview mode for opening arbitrary HTTP(S) URLs inside the session workspace without persisting preview state across reloads.
  • Reuse the SideCar iframe shell by extracting a shared BrowserFrame with navigation, refresh, path entry, viewport presets, and no-injection element comment targeting.
  • Add authenticated preview proxy routes for HTTP and WebSocket traffic, sharing lower-level proxy forwarding with SideCars to avoid duplicating proxy code.
  • Localize all new preview and viewport strings across English, Spanish, French, Hebrew, Japanese, Russian, and Simplified Chinese.

User-facing behavior

  • Users can open a web preview from the session toolbar and toggle back and forth between chat and preview mode.
  • Preview mode replaces only the message stream/timeline area; the prompt input and attachments stay available below the preview.
  • Comment mode lets users select elements in the same-origin proxied iframe and append structured page/element references to the prompt draft.
  • The generated prompt quotes the page/element reference while leaving the user's actual comment as normal prompt text.
  • Viewport selection supports responsive, desktop, tablet portrait/landscape, and mobile portrait/landscape canvases, with fixed sizes isolated inside the iframe scroll container.

Implementation notes

  • Preview sessions are in-memory and keyed by short-lived tokens exposed through /previews/:token.
  • Preview proxy responses strip frame-blocking headers and rewrite same-origin redirect locations back under the preview route.
  • SideCarView now composes the shared BrowserFrame, preserving existing SideCar behavior while gaining shared viewport controls.
  • Parent-side iframe DOM access powers hover highlighting and element metadata extraction, avoiding HTML/script injection for the first implementation.

Validation

  • npm run typecheck --workspace @codenomad/ui
  • npm run typecheck --workspace @neuralnomads/codenomad

Notes

  • Existing unrelated local changes to package.json and package-lock.json were intentionally left out of this PR.
  • The initial proxy does not yet rewrite all HTML/CSS asset references; pages with root-relative assets may need follow-up URL rewriting.

Introduce ephemeral per-session web preview sessions so users can open an arbitrary URL in an alternate session view while keeping the prompt input available for iteration.

The server now creates short-lived preview tokens, proxies HTTP and WebSocket traffic through authenticated /previews paths, and shares lower-level proxy forwarding with SideCars instead of duplicating the proxy implementation. The UI extracts the SideCar iframe shell into a reusable BrowserFrame with navigation controls, viewport presets, and no-injection same-origin element selection for comments.

Preview comments append structured page and element references to the prompt draft while leaving the user's actual comment as normal text. All new user-facing strings are localized across the existing message catalogs.

Validation: npm run typecheck --workspace @codenomad/ui; npm run typecheck --workspace @neuralnomads/codenomad.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

@CodeNomadBot

I want you to perform the gatekeeper checks for latest PR state

check for regressions
check for better possible implementations
Be the PR gatekeeper
Be ruthless about code quality

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 10, 2026

Gatekeeper review for latest PR state. I interpreted the request as a strict regression/security/implementation review only; I did not make code changes.

Findings:

  1. Blocking: arbitrary preview content executes as same-origin CodeNomad UI.

packages/ui/src/components/browser-frame.tsx:344 embeds the proxied page in a normal iframe with no sandbox, while packages/server/src/server/http-server.ts:1345-1348 strips x-frame-options and CSP from arbitrary preview responses. Because the preview is served from the CodeNomad origin under /previews/:token, any JavaScript from a URL the user opens can run as same-origin with the app, access window.top, read/manipulate the UI DOM, and call authenticated /api/* endpoints with the user’s cookies. Opening a malicious or compromised site in preview becomes workspace/app compromise.

This needs isolation before merge. Safer directions: serve previews from a separate origin/site, use a sandboxed iframe without allow-same-origin, and avoid parent DOM access for arbitrary remote content. The current parent-side element targeting design is not compatible with safely previewing arbitrary URLs on the app origin.

  1. Blocking: the proxy does not support the advertised arbitrary HTTP(S) URL behavior for common pages.

packages/server/src/server/http-server.ts:1029-1032 only rewrites headers, and packages/server/src/previews/manager.ts:68-73 only constructs the initial proxied URL. Response bodies are forwarded untouched, so root-relative assets, links, form actions, scripts, CSS url(...), and many WebSocket URLs escape the preview prefix. For example, a page with <script src="/assets/app.js"> will request CodeNomad’s /assets/app.js, not /previews/:token/assets/app.js. That breaks a large class of normal web apps and can also route accidental requests into CodeNomad’s own static/API surface.

If the feature promise is arbitrary URLs, this needs URL rewriting or a different reverse-proxy model, ideally on an isolated origin where absolute paths naturally belong to that preview origin.

  1. Regression: SideCar rendering no longer preserves the previous full-panel iframe behavior.

packages/ui/src/components/browser-frame.tsx:342-351 wraps every iframe in a padded scroll canvas with bg-surface-secondary, p-4, fixed-size preset behavior, and shadow-xl. Since packages/ui/src/components/sidecar-view.tsx now uses this shared frame, even the responsive/default SideCar view is no longer the prior full-bleed iframe that filled the available pane. That is a behavioral/layout regression for existing SideCars, despite the PR note saying SideCar behavior is preserved.

The responsive/default mode should match the old SideCar shell exactly; only fixed viewport presets should introduce the padded canvas.

Validation attempted:

npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad both failed in this worktree because dependencies/types are missing (solid-js, @opencode-ai/sdk, @types/node, etc.). I could not use those results to validate or invalidate this PR’s changes.

Recommendation: do not merge as-is. The same-origin arbitrary content issue is a hard blocker, and fixing it likely changes the preview/comment-targeting architecture.

--
Yours,
CodeNomadBot

Restore the responsive browser frame to a full-bleed iframe so existing SideCar tabs keep their prior layout unless a fixed viewport preset is selected.

Improve trusted preview compatibility by rewriting common root-relative HTML and CSS URLs back under the preview token path, and add a conservative authenticated referer fallback for root-relative asset requests that escape the preview prefix.

This intentionally keeps same-origin preview rendering because element-level hover/comment targeting is a product requirement; the PR description should frame preview URLs as trusted inputs rather than a sandboxed arbitrary browsing surface.

Validation: npm run typecheck --workspace @codenomad/ui; npm run typecheck --workspace @neuralnomads/codenomad.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

Follow-up after gatekeeper review:

  • Kept the same-origin preview model intentionally because element-level hover/comment targeting without injection is a product requirement. This means preview URLs should be treated as trusted user-provided inputs rather than a sandboxed arbitrary browsing surface.
  • Fixed the SideCar layout regression by restoring responsive mode to a full-bleed iframe; the padded scroll canvas now applies only to fixed viewport presets.
  • Improved preview compatibility for common pages by rewriting root-relative HTML attributes, srcset entries, and CSS url(...) references back under the /previews/:token path.
  • Added an authenticated referer-based fallback for root-relative preview asset requests that escape the preview prefix, while excluding CodeNomad API/workspace/sidecar/preview/auth paths.

Validation rerun:

  • npm run typecheck --workspace @codenomad/ui
  • npm run typecheck --workspace @neuralnomads/codenomad

@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

@CodeNomadBot

DOM inspection is non-negotiable.

Review again

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 10, 2026

Re-review of latest PR state, with DOM inspection treated as non-negotiable.

I’m no longer evaluating this as “must sandbox and remove DOM access.” I’m evaluating it as a trusted, same-origin DOM-inspection preview surface. Under that model, the code still needs tighter boundaries because proxied remote HTTP semantics are being applied directly to the CodeNomad origin.

Findings:

  1. Blocking: preview responses can set cookies on the CodeNomad origin.

packages/server/src/server/http-server.ts:1340-1372 strips frame/CSP headers but does not strip set-cookie. Since preview responses are served from CodeNomad’s own origin under /previews/:token, any upstream Set-Cookie header is interpreted by the browser as a CodeNomad cookie, not as a cookie for the target site. A previewed app can overwrite or pollute CodeNomad auth/session/preferences cookies, cause logout/session confusion, or create very hard-to-debug state corruption. DOM inspection does not require forwarding upstream cookies to the app origin.

Fix before merge: drop set-cookie/set-cookie2 from preview responses, and strongly consider dropping request cookie on preview proxy requests too unless there is a concrete same-origin target use case. If request cookies are kept, they are CodeNomad cookies being sent to the preview target, which is also not desirable for a trusted-but-external preview surface.

  1. High: the referer fallback runs too late in production, so escaped root-relative preview assets can still resolve to CodeNomad static files.

setupStaticUi registers fastifyStatic at packages/server/src/server/http-server.ts:862-866, and the preview referer fallback only runs in the not-found handler at packages/server/src/server/http-server.ts:870-879. That means a preview page requesting /favicon.ico, /manifest.json, or any path that happens to exist in the bundled UI never reaches proxyPreviewFallbackFromReferer; CodeNomad serves its own static asset into the preview instead. The fallback improves misses, but not collisions, and common root-relative assets are exactly where collisions happen.

Fix before merge or explicitly accept the limitation: move the preview-referer fallback ahead of static serving for authenticated requests, while keeping the protected prefixes excluded. That makes fallback behavior deterministic instead of depending on whether the UI bundle has a matching path.

  1. Medium: the “trusted URL” security model is not represented in product/API boundaries.

The PR description and UI still present this as opening arbitrary HTTP(S) URLs, while the implementation intentionally grants the previewed page same-origin script execution with CodeNomad for DOM inspection. Given the product requirement, this may be acceptable, but it must be explicit at the point of use. packages/server/src/server/routes/previews.ts:14-20 accepts any HTTP(S) target, and the open-preview flow does not appear to warn that the URL should be trusted because the page can interact with CodeNomad as same-origin.

Fix before merge or as a required follow-up: rename/copy should say trusted preview URL, and the first-use/open dialog should warn users not to open untrusted sites. This is not a substitute for stripping cookies above; it documents the intentional DOM-inspection trust model.

Resolved from prior review:

The SideCar responsive layout regression appears fixed. packages/ui/src/components/browser-frame.tsx:344-357 now uses a full-bleed responsive iframe and only applies the padded scroll canvas for fixed viewport presets.

Improved but still incomplete:

The body rewriting at packages/server/src/server/http-server.ts:1051-1084 covers common quoted root-relative HTML attributes, srcset, and CSS url(...). That is a useful improvement. It still will not cover all real-world cases such as JS-constructed URLs, unquoted attributes, meta refresh, CSS @import, service workers, and some WebSocket constructions, so the fallback path matters.

Validation attempted:

npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad still fail in this worktree because dependencies/types are missing (solid-js, @opencode-ai/sdk, @types/node, etc.). I could not independently validate the claimed successful typechecks from this environment.

Recommendation: do not merge until at least the preview cookie handling is fixed. With DOM inspection as a hard requirement, the same-origin model can be intentional, but upstream HTTP cookies must not be allowed to mutate or receive CodeNomad-origin state.

--
Yours,
CodeNomadBot

Prevent trusted preview targets from mutating CodeNomad origin state by stripping Set-Cookie response headers and withholding CodeNomad cookies from preview HTTP and WebSocket proxy requests.

Move the authenticated preview referer fallback ahead of static UI serving so root-relative preview assets are proxied deterministically instead of colliding with bundled CodeNomad files like favicon or manifest assets.

Validation: npm run typecheck --workspace @codenomad/ui; npm run typecheck --workspace @neuralnomads/codenomad.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

Addressed the latest gatekeeper review:

  • Preview proxy responses now strip Set-Cookie / Set-Cookie2 so upstream preview pages cannot mutate CodeNomad-origin cookies.
  • Preview HTTP proxy requests no longer forward CodeNomad Cookie headers to preview targets.
  • Preview WebSocket proxy requests also omit Cookie headers while preserving SideCar behavior.
  • The authenticated referer fallback now runs before static UI serving in production, so root-relative preview assets are proxied before they can collide with bundled CodeNomad files such as /favicon.ico or /manifest.json.

Validation rerun:

  • npm run typecheck --workspace @codenomad/ui
  • npm run typecheck --workspace @neuralnomads/codenomad

@github-actions
Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25639077266

Artifacts expire in 7 days.
Artifacts:

  • pr-430-6b8f6ef02bbb7038ef4fa12e0ec179d4cfe30a81-electron-linux

@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

@CodeNomadBot

Check again

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 10, 2026

Re-check of latest PR state after the cookie/fallback follow-up.

I reviewed this under the accepted constraint that DOM inspection is non-negotiable and the preview is intentionally a trusted same-origin surface.

Findings:

  1. Medium: the trusted-URL model is still not surfaced to users.

The implementation now intentionally gives previewed pages same-origin DOM/script capabilities so parent-side DOM inspection can work. That can be an acceptable product tradeoff, but the open-preview copy still says only “Enter a URL to preview in this session” / “Preview URL” (packages/ui/src/lib/i18n/messages/en/session.ts:89-95) and the open flow just prompts and opens (packages/ui/src/components/instance/instance-shell2.tsx:288-301). Users are not told that this should be a trusted URL because DOM inspection requires same-origin execution.

This is no longer a proxy correctness blocker, but it is a product/security boundary issue. I would add explicit wording in the dialog such as “Only open trusted local/staging pages; previewed pages run in the CodeNomad preview context for DOM inspection.” This needs to be localized with the existing i18n keys.

Resolved from prior review:

  • Cookie isolation is addressed. Preview HTTP request forwarding now skips cookie (packages/server/src/server/http-server.ts:1011-1021), preview WebSocket forwarding strips cookies for preview sockets while preserving SideCar behavior, and preview response rewriting drops set-cookie / set-cookie2.
  • Production static collisions are addressed. The referer fallback now runs in a preHandler before fastifyStatic is registered for the static UI path, so root-relative preview assets get a chance to proxy before bundled files like /favicon.ico can win.
  • The earlier SideCar responsive layout regression remains fixed by the responsive/full-bleed BrowserFrame mode.

Residual risk:

The URL rewriting/fallback approach is still best-effort and will not cover every web platform behavior (@import, JS-constructed URLs, service workers, unusual meta refresh forms, etc.). That seems acceptable if this feature is scoped as trusted preview for common local/staging apps rather than a fully general browser.

Validation attempted:

npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad still fail in this local worktree because dependencies/types are unavailable (solid-js, @opencode-ai/sdk, @types/node, etc.). I could not independently validate the claimed successful typechecks from this environment.

Recommendation: no remaining blocking code findings from this re-check. I would still update the dialog copy before merge if this is going to ship as a trusted same-origin DOM-inspection preview.

--
Yours,
CodeNomadBot

Make the open-preview prompt explicit that preview URLs should be trusted local or staging pages because the preview runs in CodeNomad's preview context to support DOM inspection.

Localize the trusted-preview wording across all existing session message catalogs.

Validation: npm run typecheck --workspace @codenomad/ui.
@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

Addressed the trusted-URL copy finding:

  • Updated the open-preview prompt to explicitly say users should enter a trusted local or staging URL.
  • The dialog now explains that previewed pages run in the CodeNomad preview context so DOM inspection can work.
  • Localized the updated wording across all existing session message catalogs.

Validation rerun:

  • npm run typecheck --workspace @codenomad/ui

Keep the trusted local/staging URL guidance in the preview dialog while removing the implementation-specific DOM inspection explanation from user-facing copy.

Validation: npm run typecheck --workspace @codenomad/ui.
@github-actions
Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25639203366

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@shantur
Copy link
Copy Markdown
Collaborator Author

shantur commented May 10, 2026

@CodeNomadBot

How about now?

@github-actions
Copy link
Copy Markdown

PR builds are available as GitHub Actions artifacts:

https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/25639300658

Artifacts expire in 7 days.
Artifacts: (none found on this run)

@codenomadbot
Copy link
Copy Markdown
Contributor

codenomadbot Bot commented May 10, 2026

Re-check of latest PR state after the trusted-preview copy update.

Findings:

No remaining blocking or medium-severity findings from this pass.

Verified:

  • The trusted URL guidance is now surfaced in the open-preview prompt across the localized session catalogs. English now says: “Enter a trusted local or staging URL to preview in this session.” (packages/ui/src/lib/i18n/messages/en/session.ts:91)
  • Cookie isolation remains in place. Preview HTTP forwarding skips cookie, preview WebSocket forwarding strips cookies for preview sockets, and preview responses drop set-cookie / set-cookie2.
  • The production static collision fix remains in place. The preview referer fallback is registered before static UI serving, with API/workspace/sidecar/preview/auth paths excluded.
  • The SideCar responsive layout regression remains fixed by the full-bleed responsive BrowserFrame path.

Residual risk, accepted by scope:

The preview proxy and URL rewriting are still best-effort rather than a complete browser-grade rewriting layer. Given the explicit trusted local/staging preview positioning and non-negotiable DOM inspection requirement, I do not consider that a merge blocker for this PR.

Validation attempted:

npm run typecheck --workspace @codenomad/ui and npm run typecheck --workspace @neuralnomads/codenomad still fail in this local worktree because dependencies/types are unavailable (solid-js, @opencode-ai/sdk, @types/node, etc.). I could not independently validate the claimed successful typechecks from this environment.

Recommendation: gatekeeper approval from code-review perspective, subject to CI/typecheck passing in the proper dependency environment.

--
Yours,
CodeNomadBot

@shantur shantur merged commit efe3f50 into dev May 10, 2026
10 of 11 checks passed
@shantur shantur deleted the web-preview branch May 10, 2026 20:46
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