Skip to content

fix(web): harden desktop webapp chat and mobile controls - #60829

Closed
seagpt wants to merge 22 commits into
NousResearch:mainfrom
seagpt:fix/desktop-webapp-mode
Closed

fix(web): harden desktop webapp chat and mobile controls#60829
seagpt wants to merge 22 commits into
NousResearch:mainfrom
seagpt:fix/desktop-webapp-mode

Conversation

@seagpt

@seagpt seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

Hardens the Hermes dashboard/Desktop-as-webapp path for browser and VPN/PWA-style use while keeping this PR focused on the existing desktop/web chat surfaces:

  • Desktop preview pane now falls back from Electron <webview> to a browser-safe <iframe> when the dashboard runs outside Electron.
  • Dashboard chat now keeps a stable, profile/session-scoped PTY attach token across hard refreshes.
  • Dashboard chat now keeps a stable, profile/session-scoped sidecar event channel across hard refreshes.
  • /api/pub/api/events now has a bounded per-channel replay buffer so tool/progress/thought frames emitted during a refresh gap are replayed to the reloaded sidebar.
  • Event replay delivery is serialized through per-subscriber queues to avoid concurrent WebSocket sends during replay + live broadcast.
  • Replay retention is bounded by frame count and payload bytes to avoid unbounded memory retention.
  • Mobile chat controls for Model & tools are larger and easier to tap at phone widths.

PWA installability metadata was split into focused follow-up PR #60873 so this PR stays reviewable.

Related Issue

No single issue. Follow-up from dashboard/desktop-as-webapp QA around browser-mode desktop use, refresh-safe in-flight chat state, and mobile phone usability over VPN.

Related / overlap checked:

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Tests
  • Documentation

Verification

Targeted automated verification on latest head e1333a862ec8b79558f508143bda587d27a358ec:

  • python -m py_compile hermes_cli/web_server.py
  • uv run --with pytest --with pytest-asyncio pytest -q tests/hermes_cli/test_web_server_pty_reconnect.py tests/test_pty_keepalive_ws.py — 7 passed
  • npm --workspace web run test -- src/lib/chat-identity.test.ts — 4 passed
  • npm --workspace web exec eslint -- src/pages/ChatPage.tsx src/lib/chat-identity.ts src/lib/chat-identity.test.ts --max-warnings=0
  • npm --workspace web run typecheck
  • npm --workspace web run build
  • npm --workspace apps/desktop run test:ui -- src/app/chat/right-rail/preview-pane.test.tsx — 2 passed
  • npm --workspace apps/desktop run typecheck
  • npm --workspace apps/desktop run lint — 0 errors, 1 existing unrelated warning in apps/desktop/src/app/settings/model-settings.tsx
  • npm --workspace apps/desktop run build
  • git diff --check origin/main...HEAD
  • python scripts/check-windows-footguns.py $(git diff --name-only origin/main...HEAD) — no issues
  • diff-only secret scan — 0 hits

Manual/browser verification:

  • Started local dashboard from this branch with isolated HERMES_HOME=[local Hermes home redacted], --host 127.0.0.1 --port 9132 --skip-build --no-open.
  • GET http://127.0.0.1:9132/api/status returned 200.
  • Loaded http://127.0.0.1:9132/chat in browser tooling.
  • Hard reload preserved hermes.pty.token.chat.default:new and hermes.pty.channel.chat.default:new in localStorage.
  • Root page horizontal overflow after reload was 0.
  • Responsive smoke matrix over /chat, /sessions, /models, /env, /system at iPhone SE, iPhone 12, and iPad Mini widths had root horizontal overflow 0. The one console event per route was an expected unauthenticated auth probe returning 401, not a layout failure.
  • Mobile drawer screenshots confirmed the navigation drawer and Model & tools panel are visible/usable at 320px.

Checklist

  • I searched for duplicate/overlapping PRs/issues and noted related work above.
  • I added regression tests for changed behavior.
  • I exercised the real dashboard/server path, not only isolated unit tests.
  • I verified mobile/phone-width behavior with a viewport matrix.
  • I considered path/profile/session isolation and avoided cross-profile localStorage collisions.
  • I avoided adding user-facing env vars.
  • I did not include secrets, credentials, or unrelated local files.
  • Docs/config/tool-schema changes are not required for this focused behavior fix; installability metadata is handled separately in feat(web): add installable dashboard metadata #60873.

Latest local model / gateway / VPN-mode verification (2026-07-08)

Additional user-facing smoke was run on the same WSL PC the user uses, without restarting the installed Hermes gateway service:

  • Installed local CLI resolved successfully; version reported Hermes Agent v0.16.0 (2026.6.5).
  • Real model smoke through installed CLI: timeout 180 hermes chat -q 'Reply with exactly OK.' returned exit code 0 and the assistant response OK.
  • Installed gateway service: hermes-gateway.service active/running under systemd user service, local PID 424; linger enabled.
  • Runtime gateway status from gateway.status.read_runtime_status() reported gateway_state: running, exit_reason: null, and Telegram state: connected.
  • PR dashboard smoke was started temporarily on 127.0.0.1:9134 with real HERMES_HOME=[local Hermes home redacted]; /api/status returned gateway_running: true, gateway_state: running, gateway_pid: 424, gateway_mode: single, Telegram connected, auth_required: false for loopback local mode.
  • Browser QA opened /chat; sidebar showed Gateway Status: Running, model/tool rail showed gpt-5.5 and live, and the browser chat smoke session Exact WEBOK Reply was present.
  • The temporary dashboard QA process was killed afterward; final port check showed no listeners on 9130/9132/9134. The real gateway service remained PID 424.

This push also clarifies the three deployment shapes in docs: local dashboard/local Desktop, VPN/LAN-hosted dashboard, and Remote Desktop backend. It updates stale --insecure wording to match current fail-closed behavior: non-loopback binds are treated as VPN/LAN/hosted mode and always require dashboard auth. The sensitive messaging gateway (hermes gateway) is distinct from Desktop's remote dashboard backend.

Follow-up verification: local/VPN/remote backend wording

  • Confirmed installed local Hermes CLI/model path still works separately from the PR branch:
    • local Hermes CLI path resolved (absolute path redacted)
    • hermes chat -q "Reply with exactly OK." returned OK with exit code 0.
  • Confirmed local gateway is a real user service, not a remote dashboard proxy:
    • hermes-gateway.service active/running; Telegram platform connected.
  • Clarified Desktop terminology so the UI/docs distinguish:
    • local backend bundled with Desktop,
    • VPN/LAN-hosted dashboard/backend,
    • remote dashboard/backend on another machine,
    • messaging gateway (hermes gateway) as a separate service.
  • Resolved the website typecheck blocker while validating docs:
    • npm run typecheck in website/ passes after npm ci.
    • npm run build in website/ succeeds; existing broken-link/anchor warnings remain unrelated.
  • Additional Desktop validation after terminology cleanup:
    • npm --workspace apps/desktop run test:ui -- src/components/boot-failure-reauth.test.ts src/components/gateway-connecting-overlay.test.tsx src/lib/desktop-fs.test.ts passed (24 tests).
    • npm --workspace apps/desktop run typecheck passed.
    • npm --workspace apps/desktop run lint passed with one pre-existing warning in model-settings.tsx.
    • npm --workspace apps/desktop run build succeeded.
  • Independent read-only review found no blockers, no generated artifacts, and no likely secrets.

Current rebase status

Draft: current main includes the Electron CommonJS-to-TypeScript migration. A merge rehearsal leaves four semantic conflicts, including apps/desktop/electron/main.ts and web/src/pages/ChatPage.tsx; this PR needs a dedicated behavior-preserving rebase before review. No conflict resolution has been force-pushed.

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have labels Jul 8, 2026
@seagpt seagpt changed the title fix(desktop): support preview pane in browser mode fix(web): harden desktop webapp chat and mobile controls Jul 8, 2026
@seagpt
seagpt force-pushed the fix/desktop-webapp-mode branch from 4e32b65 to 65acf34 Compare July 8, 2026 12:50
@seagpt

seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Added a follow-up fix for Ctrl+V screenshot/image paste in the Desktop webapp variant.

What changed:

  • Pasted image blobs are no longer forced through window.hermesDesktop.saveImageBuffer(...) at paste time.
  • The composer now keeps pasted screenshots as in-memory data:image/... preview attachments.
  • Submit-time sync uploads those inline bytes through the existing image.attach_bytes gateway RPC before prompt.submit, so this works when the webapp shell cannot write a local Electron-staged image file.
  • Regression coverage verifies:
    • paste-created screenshot attachments do not call saveImageBuffer;
    • pathless data:image attachments are uploaded via image.attach_bytes;
    • submit uploads the screenshot bytes before prompt.submit.

Validation run locally:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/hooks/use-composer-actions.test.ts src/app/session/hooks/use-prompt-actions/index.test.tsx src/app/session/hooks/use-prompt-actions/utils.test.ts --maxWorkers=1 --no-fileParallelism — 70 passed
  • npm --workspace apps/desktop run typecheck — passed
  • ESLint on changed Desktop files — passed
  • npm --workspace apps/desktop run build — passed, including assert-dist-built
  • git diff --check — passed
  • diff privacy scan — no hits

@seagpt

seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Added a follow-up fix for the attachment picker UX.

What changed:

  • The + → Files action now opens the normal unrestricted OS file picker:
    • openFile
    • multiSelections
    • no extension filters
  • Removed the separate image-only menu item so users do not get trapped in an image-filtered dialog.
  • Selected paths are routed by type after selection:
    • image paths become image/vision attachments;
    • every other path becomes a normal @file: attachment.
  • Added browser/mobile fallback behavior for environments without the Electron Desktop bridge:
    • uses native <input type="file" multiple>;
    • accepts any file type;
    • image files become in-memory image attachments;
    • non-image files are kept as memory-only data URLs and uploaded through file.attach on submit.
  • file.attach submit sync now supports pathless data_url file attachments and clears the data URL after staging.

Validation run locally:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/hooks/use-composer-actions.test.ts src/app/session/hooks/use-prompt-actions/index.test.tsx src/app/session/hooks/use-prompt-actions/utils.test.ts --maxWorkers=1 --no-fileParallelism — 74 passed
  • npm --workspace apps/desktop run typecheck — passed
  • ESLint on changed Desktop files — passed
  • npm --workspace apps/desktop run build — passed, including assert-dist-built
  • git diff --check — passed
  • diff privacy scan — no hits

@seagpt

seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Added a follow-up responsive-preview UX fix.

What changed:

  • The preview pane is still freely resizable by dragging the pane divider.
  • The preview rail max width is no longer capped at a narrow fixed 38rem; it can now expand up to 90vw, so desktop/ultrawide review widths are reachable on wide displays.
  • Embedded web previews now show a compact URL strip above the preview content.
  • The right side of that strip has quick responsive-width presets:
    • Fold 6:5
    • iPhone 9:16
    • Desktop 16:9
    • Ultrawide 21:9
  • Presets do not lock the aspect ratio; they only set the preview pane width override based on the live preview viewport height. Users can keep dragging/resizing immediately afterward.
  • Added a small sizing helper module so default preview width stays conservative while manual/preset sizing can widen intentionally.
  • Hardened panes.test.ts localStorage setup and updated the stale width-persistence expectation to match current behavior.

Validation run locally:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/right-rail/preview-pane.test.tsx src/components/pane-shell/pane-shell.test.tsx src/store/panes.test.ts --maxWorkers=1 --no-fileParallelism — 35 passed
  • npm --workspace apps/desktop run typecheck — passed
  • ESLint on changed Desktop files — passed
  • npm --workspace apps/desktop run build — passed, including assert-dist-built
  • git diff --check — passed
  • diff privacy scan — no hits

@seagpt
seagpt force-pushed the fix/desktop-webapp-mode branch from 5551185 to 114ac55 Compare July 8, 2026 22:47
@seagpt

seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up after Steven’s UX clarification: previews must stay docked inside the right-rail/grid, not pop out of the app layout.

What changed:

  • Live web previews are now real right-rail tabs (preview:<url>), alongside file preview tabs, instead of one singleton preview slot.
  • Multiple live preview/browser tabs can stay open for the same chat.
  • Closing one preview tab only closes that tab; the rail stays open while other preview/file tabs remain.
  • Returning to a chat restores that chat’s live preview tabs.
  • The embedded preview now has an editable address bar; typing example.org/docs navigates in-place to https://example.org/docs inside the docked preview surface.
  • Electron webview storage is now scoped per chat session (persist:hermes-preview-<session>), so preview browser session state is chat-scoped instead of one global preview partition.
  • Existing responsive preview buttons remain inside the docked preview header: 6:5, 9:16, 16:9, 21:9.
  • Preview/file status buttons now detect and hide the matching preview tab instead of blindly closing whichever preview happens to be active.

Validation run in the clean PR worktree:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/right-rail/preview.test.tsx src/store/preview.test.ts src/app/session/hooks/use-preview-routing.test.tsx src/app/chat/right-rail/preview-pane.test.tsx src/components/pane-shell/pane-shell.test.tsx --maxWorkers=1 --no-fileParallelism
    • 36 passed
  • npm --workspace apps/desktop run typecheck
    • passed
  • ESLint on changed Desktop files
    • passed
  • npm --workspace apps/desktop run build
    • passed; assert-dist-built passed
  • git diff --check
    • passed
  • diff privacy scan
    • diff_privacy_hits=[]

Visual QA:

  • Rendered the real ChatPreviewRail in a temporary Vite harness with a mock chat/editor column and a right docked rail.
  • Verified visually that preview/file tabs remain docked in the same grid column, not floating/popped out.
  • Verified switching among Local app, Docs, and file tabs.
  • Verified address-bar navigation normalizes example.org/manual-test to https://example.org/manual-test and stays inside the docked preview iframe.

Installed-source backport:

  • Applied the updated PR patch to local Hermes installed checkout after hermes update.
  • Restored missing npm deps needed by browser tooling/Desktop validation.
  • Installed-source focused tests passed: 18 tests.
  • Installed-source Desktop typecheck passed.

@seagpt

seagpt commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Related split-out work:

  • Hermes Webapp product surface/foundation is now Add Hermes Webapp command and architecture docs #61171 (hermes webapp, Dashboard/Desktop/Webapp naming, browser-workspace architecture plan).
  • This PR remains focused on Desktop/right-rail behavior: docked preview/browser tabs, per-chat Electron preview partitions, responsive sizing, and address-bar navigation.

Steven's fork main now carries both focused branches as an integration branch, but upstream review can keep these PRs separate.

@seagpt
seagpt force-pushed the fix/desktop-webapp-mode branch from 114ac55 to 3d66eb0 Compare July 8, 2026 23:23
@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up from live Desktop QA: fixed the preview rail so responsive presets size an inner clipped preview frame instead of widening the outer PaneShell track; added visible in-pane annotate/debug overlays so those controls no longer appear to do nothing; reduced the preview rail max so stale persisted desktop/ultrawide widths cannot keep consuming the chat area. Validated focused preview tests, typecheck, lint, and Desktop build.

@seagpt
seagpt requested a review from a team July 9, 2026 00:55
@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up from Desktop/Webapp browser-surface QA: added explicit in-pane browser chrome to the preview rail (Preview URL bar, reload, open externally, find-in-preview button and Ctrl/Cmd+F, Ctrl/Cmd+L, Ctrl/Cmd+R/F5 shortcuts). Also wired CodeMirror search into file/text edit mode so normal editor find works with Ctrl/Cmd+F. Validation: focused preview/editor tests, typecheck, eslint, production Desktop build, and privacy scan passed.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: implemented the right-rail file manager surface requested in Desktop/Webapp QA.

What changed:

  • Added visible file-browser toolbar controls: refresh, recursive expand all, collapse all, new file, new folder.
  • Added built-in file search/filter in the right rail.
  • Added hardened local filesystem create-file/create-folder IPC bridge with parent/overwrite protections.
  • Added shared create dialogs and folder context-menu New File/New Folder actions alongside existing reveal/copy/rename/delete.
  • Added a visible editor Find affordance backed by CodeMirror native search, in addition to Ctrl/Cmd+F.

Validation:

  • Focused UI tests: 31 passed.
  • Electron fs-create node tests: 3 passed.
  • Typecheck: passed.
  • Focused eslint: passed.
  • git diff --check: passed.
  • Production Desktop build: passed.
  • Privacy scan on PR commit/merge: no hits.
  • Visual dogfood: temporary Vite harness rendered the right rail, verified search, recursive expand all, create dialog, and editor find; temporary harness files were removed before commit.

Known test noise: CodeMirror still logs the jsdom getClientRects geometry warning in search tests, but tests and production build pass.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: fixed preview/code-editor rail containment for new-session and full-screen/non-chat views.

What changed:

  • Added a shared contextual preview rail policy: the preview/code-editor rail is visible only for an actual chat session route with a preview/editor target.
  • New chat/root route no longer restores stale preview tabs just because the previous runtime session id is still cached.
  • Settings/full-screen overlays, Skills, Messaging, and Artifacts no longer expose or reserve layout for stale preview/code-editor panes.
  • Preview shortcut activation and Ctrl/Cmd+W close-preview behavior now follow the same contextual gate so hidden rails do not steal focus/shortcuts.

Validation:

  • RED tests added for the visibility policy and stale new-chat preview restoration.
  • Focused UI tests: 59 passed.
  • Typecheck: passed.
  • Focused eslint: passed.
  • git diff --check: passed.
  • Production Desktop build: passed.
  • Privacy scans: no hits on staged commit or fork-main merge.
  • Visual dogfood: temporary Vite harness verified chat-session shows the rail, new-chat hides it despite stored pane-open state, settings overlay owns the surface with no preview/editor controls visible, and Skills hides it. Temporary harness files were removed before commit.

Known test noise: CodeMirror jsdom getClientRects warnings still appear in editor-search tests; tests/build pass.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: fixed preview/code-editor auto-collapse when nothing is open.

Research/backtrace:

  • Checked the old working pattern in fbca8de9c Fix preview rail collapse after closing tabs.
  • That fix made the preview rail data-driven via syncPreviewPaneOpen(): open when preview/file tabs exist, closed when the last tab closes.
  • The recent containment patch hid the rail contextually, but it did not always reconcile a stale persisted preview.open=true bit. That can leave layout/titlebar state treating the rail as open even when content is gone.

What changed:

  • Added nextContextualPreviewPaneOpen() to compute the structural pane-open state from the same contextual policy.
  • DesktopController now reconciles setPaneOpen(PREVIEW_PANE_ID, ...) in useLayoutEffect, so no-preview/no-file states collapse to a 0px track before paint, and real chat preview/file targets open the pane again.
  • Default preview pane registration is now closed (open: false); preview/file open paths already set it true.

Validation:

  • RED tests added for stale paneOpen=true collapsing when no preview/editor target exists, and paneOpen=false opening when a contextual target appears.
  • Focused UI tests: 62 passed.
  • Typecheck: passed.
  • Focused eslint: passed.
  • git diff --check: passed.
  • Production Desktop build: passed.
  • Installed-source focused tests/typecheck/lint/build: passed.
  • Privacy scans: no hits.
  • Visual dogfood: temporary Vite harness started with deliberately stale preview.open=true and no target; empty-chat/new-chat/settings produced gridTemplateColumns = minmax(0px, 1fr) 0px, data-pane-open=false, no preview/editor rail text. Real chat target produced minmax(0px, 1fr) 360px, data-pane-open=true, and the rail appeared. Temporary harness files were removed before commit.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: audited and repaired the browser Webapp UX instead of only the Desktop rail.

What I verified visually:

  • Ran the actual web/ app against a local loopback dashboard + Vite server.
  • Captured /chat, /files, and /sessions at desktop/tablet/phone widths.
  • Corrected the screenshot method: plain headless Chrome at --window-size=390 can still report window.innerWidth=500 and crop the result, so final evidence uses CDP Emulation.setDeviceMetricsOverride and records window.innerWidth, documentElement.clientWidth, and body.scrollWidth.

Issues found and fixed:

  • Chat mobile Model/tools action now fits the header as a compact Panel button.
  • Chat copy-last-response control is icon-only on mobile and no longer clips offscreen.
  • Closed mobile Model/tools sheet no longer leaves offscreen focusable controls in the DOM.
  • Files bad-root state now keeps a usable path bar, extracts the failed path, and exposes Retry/Open parent.
  • Files page now exposes browser/file-manager basics together: path, search, refresh, upload, and New folder.
  • Files mobile now uses stacked cards instead of a hard 42rem table that clips columns/actions.

Docs/tests:

  • Added docs/design/webapp-visual-audit.md with the product goal, visual audit findings, fixed issues, and remaining scoped product gaps.
  • Added web/src/lib/files-ui.ts + tests for path join, raw managed-files error path extraction, parent recovery, and filtering.

Validation:

  • npm --workspace web run test — 8 files / 41 tests passed.
  • npm --workspace web run typecheck — passed.
  • Targeted eslint on touched Webapp files — passed.
  • git diff --check — passed.
  • Privacy scans over staged/commit/installed diffs — no hits.
  • npm --workspace web run build — passed. Existing Vite large-chunk warning only.
  • Installed checkout backport tests/typecheck/targeted eslint/build also passed.

Installed backport:

  • Copied the same Webapp source into [local Hermes installed checkout redacted] and rebuilt its web bundle.
  • Did not restart Hermes or Desktop.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up from screenshot: fixed the “one container too many” preview trap without removing the responsive/free-size canvas feature.

What changed:

  • Normal/Fit preview mode now renders directly in the tab content area (data-preview-frame is absent, viewport overflow hidden).
  • Responsive preset buttons still create the inner frame/canvas intentionally and still do not mutate the outer pane width.
  • Switching preview targets now resets responsive sizing, especially URL -> local file/image where the Fit controls are hidden. This prevents the stale small centered rectangle shown in the screenshot.

Validation:

  • Focused Desktop preview tests: 13 passed.
  • Desktop typecheck: passed.
  • Targeted eslint on touched preview files: passed.
  • git diff --check: passed.
  • Privacy scan: no hits.
  • Desktop production build: passed.
  • Visual harness dogfood: default URL fit had hasFrame=false; 16:9 preset had hasFrame=true; switching to local image cleared back to hasFrame=false.

Design note: the larger Windows-like linked preview workspace / snapping / layering / minimization idea is good, but I kept this commit to the screenshot bug: default browser-style fit + preserve explicit responsive canvas mode.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up preview-flow pass:

  • Added explicit preview fullscreen controls in the preview chrome and titlebar tool group.
  • Added browser Back/Forward controls wired to native Electron webview history when available, with iframe fallback history for address-bar navigation.
  • Added visible Back to preview exits inside annotation and debug overlays.
  • Escape now unwinds preview flow state in order: find bar, annotations, debug, fullscreen.
  • Debug Back to preview now closes the debug console panel too, so the user actually returns to the preview.
  • Re-validated the screenshot-adjacent preview flow in a temporary visual harness, then deleted the harness.

Validation:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/right-rail/preview-pane.test.tsx src/app/chat/right-rail/preview.test.tsx src/app/chat/right-rail/preview-file.test.tsx
  • npm --workspace apps/desktop run typecheck
  • npx eslint src/app/chat/right-rail/preview-pane.tsx src/app/chat/right-rail/preview-pane.test.tsx
  • git diff --check
  • diff-only privacy scan
  • npm --workspace apps/desktop run build

Installed checkout was backported and rebuilt without restarting Hermes.

@seagpt

seagpt commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up preview scaling pass:

  • Added shared preview scale controls to the preview chrome: decrease, reset to 100%, increase.
  • Default scale is 100%.
  • Any non-100% scale shows a persistent amber warning icon/percentage: “Preview scale adjusted to N%; sizes are not accurate.”
  • The same scale surface wraps browser previews, local image/media previews, text/markdown/source/diff previews, and editor/local-file surfaces.
  • Local file previews now get shared preview chrome with a read-only Preview target field so the scale controls are always available outside browser previews too.
  • Debug/console overlay chrome remains outside the scaled content surface so tools stay readable.

Visual dogfood:

  • Temporary harness tested Browser, Image/media, and Text modes.
  • Verified 100% default has no warning.
  • Verified 110% shows a visible warning chip/icon and scales the surface.
  • Verified reset returns to 100% and removes the warning.
  • Temporary harness removed.

Validation:

  • npm --workspace apps/desktop run test:ui -- src/app/chat/right-rail/preview-pane.test.tsx src/app/chat/right-rail/preview.test.tsx
  • npm --workspace apps/desktop run typecheck
  • npx eslint src/app/chat/right-rail/preview-pane.tsx src/app/chat/right-rail/preview-pane.test.tsx
  • git diff --check
  • diff-only privacy scan
  • npm --workspace apps/desktop run build

Installed checkout was backported and rebuilt without restarting Hermes.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the real dashboard refresh gap: current main still uses one localStorage attach token (web/src/pages/ChatPage.tsx:61-81) and directly sends sidecar frames without replay (hermes_cli/web_server.py:14693-14705).

Problems

  • web/src/lib/chat-identity.ts:84 maps every no-resume route to new. Because getPtyAttachToken() persists that scope in localStorage (:90-95), two new chats in one profile share an attach token/channel and can reattach or supersede each other's PTY. The tests cover stored sessions, not two no-resume tabs.
  • apps/desktop/src/app/chat/right-rail/preview-pane.tsx:1099 grants clipboard, microphone, and camera access to the iframe fallback without a sandbox. This is weaker than the Electron path's sandbox=yes configuration at :1088.
  • Current main has TypeScriptified the Electron surface (apps/desktop/electron/main.ts:65, hardening.ts:149); the PR's CJS fs-create.cjs imports ./hardening.cjs, so that portion needs a port during salvage.

Suggested changes

  • Use a tab-scoped, refresh-stable identity for no-resume chats and test two same-profile new tabs.
  • Sandbox/minimize iframe permissions and cover the fallback policy.
  • Port retained Electron work to the current TypeScript architecture.

Automated hermes-sweeper review.

resumeSessionId: string | null | undefined,
profile: string | null | undefined
): string {
const session = (resumeSessionId ?? '').trim() || 'new'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every no-resume chat becomes the same default:new localStorage identity, so two /chat tabs in one profile reuse their attach token/channel and can reattach or supersede each other's PTY. Use a tab-scoped refresh-stable identity for this case and add a two-new-tab regression test.

const iframe = document.createElement('iframe') as HTMLIFrameElement & PreviewWebview

iframe.className = 'h-full w-full border-0 bg-background'
iframe.setAttribute('allow', 'clipboard-read; clipboard-write; fullscreen; microphone; camera')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This browser fallback grants clipboard, microphone, and camera access without an iframe sandbox, weakening the sandboxed Electron-webview boundary above. Apply a minimal sandbox/Permissions Policy and only grant a capability when a user-mediated preview action requires it.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 10, 2026
@seagpt
seagpt marked this pull request as draft July 11, 2026 22:53
@seagpt seagpt closed this Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants