Skip to content

Save chat HTML visuals as ordinary workspace files - #27633

Closed
brennanb2025 wants to merge 12 commits into
mainfrom
brennanb2025/chat-html-workspace-files
Closed

brennanb2025 wants to merge 12 commits into
mainfrom
brennanb2025/chat-html-workspace-files

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 45 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​809 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1898 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1089
Prod 68 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​603 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1590 $\color{#cf222e}{\Huge{\mathbf{−}}}$​987

ELI5

When an agent draws a chart or report in a native chat, Orca shows it inline. Today that HTML page is saved in a hidden per-chat folder inside Orca's own data, so it isn't a normal file: you can't edit it, share it, or open it on SSH and paired hosts the way you open every other file. This PR saves those pages as ordinary .html files in the chat's working folder and shows them the same way a file link in the chat works: read from the workspace, opened like any file. Edit the page and the inline view catches up.

What Changed

The problem (STA-10097):

  • The agent wrote each page to a hidden folder in Orca's data directory, one folder per chat, outside the workspace.
  • Every regular file feature (the editor, the HTML preview, Share, the file tree) finds files by workspace plus a path inside it. A file outside the workspace can't be named that way on SSH or paired hosts, so none of those features could reach these pages.
  • To still show them, Orca had a separate read path just for visuals (an agentSession.readVisual call that looked the folder up from the chat session) and a separate read-only "chat visual" tab (Open a chat visual in its own tab instead of the sidebar #26728). Neither could edit or share, and both were special cases next to the normal file features.

Before:

  • "Open in new tab" opened the separate read-only chat-visual tab, on desktop; the phone opened a full-screen sheet.
  • No editing, no Share, and the page didn't appear in the file tree.
  • An inline visual never changed after it first loaded.

After:

  • Where pages go: the agent saves a self-contained .html file in its working folder, for example reports/latency-by-region.html. It shows up in the file tree like anything else the agent writes.
  • Inline preview: works as before on desktop and on the phone, for workspace chats.
  • Open in new tab: opens the file the way a file link in chat already does. Locally that's the browser tab, which has Share. On SSH and paired hosts it's the HTML preview, which gets Share in Share any HTML file from the editor and the remote preview #27608. On the phone it opens the phone's file viewer for that file.
  • Edits show up: when the agent finishes a turn, when the host comes back, or when you save the file in the editor, every reply that shows that page reads it again. The frame is rebuilt only if the page actually changed.
  • Gone: the separate chat-visual tab, the phone's full-screen sheet, and the visual-only read call.

Mechanism:

  • Reply line: ::orca-visual{path="..." title="..."}. The path is relative to the chat's folder and must be a plain relative .html/.htm path: no .., no absolute paths, no drive letters. The old file="<bare name>" form is no longer recognised and shows as text.
  • Desktop read: the visual resolves its path with the chat's file-link context, the same context chat file links use (the workspace, or a floating chat's pinned folder). From that one resolution it gets the absolute path, the host that owns it and the routing to reach it, then reads with the ordinary file-preview read (readRuntimeFilePreview). A paired host answers through its normal files.readPreview. If the host can't be pinned down, the visual shows "unavailable" and nothing is read. A small cache paints a page instantly when a reply remounts; every load still reads the file.
  • Local reads outside a project: a floating chat lives in the home folder by default, outside any project, so main needs to allow that read without a click. This adds a read-only access kind, chat-html, next to the existing chat-image one and using the same rule: any local file whose real target (after symlinks) is .html/.htm, never a device or a network share. So page.html -> ~/.ssh/id_rsa is refused. Files inside a project still get the ordinary project check first.
  • Refresh: the chat's file-link checker already rechecks every linked path when a turn ends or the host comes back. It now also exposes a recheck counter, and visuals re-read when it changes. Visuals also listen to the app's existing file-change events (the watcher that already serves open editor tabs and Markdown previews), filtered to their own host and path; no new watches are added.
  • Unavailable vs. keep showing: a missing, binary or over-512 KiB file shows "unavailable". A host that can't be reached keeps the page already shown.
  • Phone read: files.read with the chat's workspace and the relative path, the same call the phone's file viewer makes. Truncated (over 512 KiB) or binary reads show "unavailable".
  • Agent setup: the bundled skill tells agents to write one self-contained file (styles, scripts, fonts and data inline, nothing loaded from the network) in their working folder, a new file per visual, and to keep the theme-colour fallbacks. Orca no longer creates a per-chat folder, sets ORCA_CHAT_VISUALS_DIR, adds an extra writable root for Claude or Codex, or sweeps old folders on startup.
  • Deleted: agentSession.readVisual (method, params, host reader and its folder-containment code, phone allowlist entry), the read client's revision protocol, the per-chat folder and its startup sweep and workspace check, the chat-visual tab kind, and the phone's full-screen sheet.

Why

The decision (STA-10097) was to make chat HTML a regular file, unified with everything else Orca does with files. Reading, opening and sharing it through the same path as a chat file link means it inherits that path's host routing and permissions instead of keeping a second copy of them.

Alternatives considered:

  • Keep the visual-only read call, pointed at the workspace. Rejected: it duplicates host routing and folder containment that the ordinary file read already does, and Share and Open would still have used a different path than the inline view.
  • Allow reads under each floating chat's pinned folder (home by default). Rejected: every automatic read (chat images, Markdown images) would then be allowed any file type under home. chat-html limits the new allowance to HTML files, the same way chat-image limits images.
  • Keep the hidden folder and give the chat-visual tab edit and Share. Rejected: it rebuilds the editor and sharing for one kind of file, and on remote hosts the folder still sits outside anything the file features can name.

Difference from the common pattern:

  • Intended: pages live in the workspace, not an app-owned folder. In Orca, opening, editing, previewing and sharing a file all go through the workspace, including on SSH and paired hosts, and the agent can already write there without extra permissions.
  • Temporary: phone visuals in floating chats show "unavailable" (see Notes). Follow-up: a ticket for the phone to read files in a floating chat's own folder.

Linked Issue

STA-10097 (Linear). STA-10098 (Share right from the inline preview) follows in a separate PR on top of this one and #27608.

Visual Proof

Live check on the M4Air at head 745b0011590 (all three PRs together): a hidden, isolated dev build with real Claude, plus a second isolated Orca paired to it as a remote host.

Claude wrote fruit-chart.html into the repo root (it appears in the file tree, uncommitted), and the reply shows it inline:

Inline chart written as a workspace file

"Open in new tab" opens it as an ordinary local browser tab on the file:// path:

Opened as a normal browser tab

With the file open in the editor beside the chat, a save showed up in the inline view 362 ms and 464 ms later. No new file watch was added; the inline view listens to the workspace's existing file-change events:

Editor save reflected inline

Asking Claude to recolor it rewrote the same file (no second .html). After the turn, the earlier reply shows the new version too:

Both replies show the rewritten file

A floating chat (default folder: home) wrote pets-bar-chart.html there, and it renders on desktop:

Floating chat visual

After that file was deleted, the floating chat shows "Visualization unavailable" and the workspace chat's visuals keep rendering:

Deleted file shows unavailable

The same flow on a workspace of a paired remote host: Claude wrote the file on that host, and it renders inline:

Visual on a paired host

Not checked live: the phone (the emulator skill isn't installed on the QA machine).

Testing

  • Unit tests:

    • reply-line grammar, including the old file= line staying text;
    • chat-html access: reads a local HTML file outside every project and in a home-folder chat; refuses relative paths, devices, network shares, missing pages, other types, page.html linked to a text file or a device, and any write;
    • the desktop reader: owner resolution (local and paired), one read for concurrent asks, missing / too large / unreachable outcomes;
    • the inline visual: reads through the chat's folder with chat-html, placeholder and no read before a floating chat's folder is pinned, a floating chat page in the home folder, unavailable with no read when the host is ambiguous, a recheck re-reads and keeps the frame when the page didn't change, an earlier reply shows the rewritten page, unreachable keeps the frame while missing drops it, file-change events filtered by path and host (overflow included), binary and oversize pages;
    • the file-link checker tells recheck subscribers;
    • phone: reads with files.read, sends nothing for a floating chat, truncated / binary / refused / older host read as unavailable, transport failures keep the cached page, one request for concurrent reads;
    • skill text.
  • Typecheck: node, web and mobile typechecks pass, and so does the mobile test-typecheck ratchet. orca-ci-checks passes except two known local-only reds (a bun script lookup and mobile import-cycle warnings that only appear with mobile dependencies installed).

  • I manually tested these changes locally (desktop rows live on head 745b0011590, incl. a paired host; phone not run)

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Mixed versions (intended; native chat has no real users yet):
    • Only path= lines render. An older desktop or phone shows a new path= line as text, and this build shows an old file= line as text. Old per-chat folders stay on disk; nothing migrates them.
    • No new RPC method, field or capability. agentSession.readVisual is removed; an older phone that still calls it gets the "not allowed" answer it already treats as "unavailable".
  • Known limits:
    • Phone, floating chats: visuals show "unavailable" and the phone sends no request. The phone's files.read can only name the current floating folder, which may not be the folder the chat was pinned to, so reading it could show the wrong file. Desktop is unaffected.
    • Same workspace id on two hosts (STA-4343): if a chat started on host A while host B is the selected host for that id, its visual reads (and Share later publishes) host B's file at that path, or shows "unavailable". Chat file links, the file tree and the editor behave the same way today. Before this PR the visual read from the chat's own host, so this is a narrow regression for that case. The fix belongs with the host-qualified workspace work in STA-4343 (check the chat's recorded host, and keep the host when Open activates the workspace).
    • Direct SSH and WSL: reads go through the ordinary file path for those hosts; neither is claimed as validated here.
    • Pages that load from the network: the inline frame allows a few CDNs; opened and shared pages don't. The skill asks for self-contained pages so they look the same everywhere.
    • Live refresh of unwatched folders: a save shows up immediately only where an existing watch covers the folder (for example a workspace with an open editor tab). Floating chats and unwatched folders refresh at turn end, host return or reopen.
  • Launch snapshot: the agent launch-parity snapshot changes on purpose, and only by the removed ORCA_CHAT_VISUALS_DIR value, Claude's --add-dir for the old folder, and that key in the inherited-env removal list.
  • Folder workspaces: the folder comes from the chat's file-link context, so plain folders work the same as git worktrees.
  • Cross-platform: paths use / in the reply line and are checked for Windows-reserved names on every platform; chat-html keeps chat-image's Windows device and share refusals.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

brennanb2025 and others added 2 commits October 11, 2026 16:06
Agents now write inline visuals as normal .html files in the chat's
working folder and name them by relative path in the reply line, so the
page can be opened, edited and shared like any other file. The host reads
the page from the chat's own folder with real-path containment.

Removes the per-chat visuals folder, its env var, extra writable roots,
startup sweep, and the separate read-only chat-visual tab.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
brennanb2025 and others added 5 commits October 11, 2026 16:50
The approval card now reaches the file-open routing through the inline
visual's Open button, which reads i18n.language at import.

Co-Authored-By: Claude <noreply@anthropic.com>
… context

A visual is now a chat file link that renders. Desktop resolves it with the
chat's file-link context and reads it with readRuntimeFilePreview under a new
read-only `chat-html` automatic-load access (the chat-image rule with an HTML
real-target test). It re-reads at every file-link recheck (turn end, host
return) and when another view's watch reports the file, and swaps the frame
only when the page changed. The phone reads through files.read and opens the
page in its file viewer; floating chats show unavailable without a request.

Removes agentSession.readVisual, its params, host reader and phone allowlist
entry, the read client's revision protocol, the legacy `file=` line and the
phone's fullscreen modal. The skill asks for a self-contained page.

Co-Authored-By: Claude <noreply@anthropic.com>
…l-workspace-files

# Conflicts:
#	src/main/acp/acp-structured-launch-resolution-omp.test.ts
#	src/main/acp/acp-structured-launch-resolution.ts
#	src/main/acp/acp-structured-launch-visuals.test.ts
#	src/main/claude/claude-structured-launch-resolution.ts
#	src/main/codex/codex-structured-child-environment.ts
#	src/main/codex/codex-structured-visuals.test.ts
#	src/main/native-chat/native-chat-inline-visuals-setting.test.ts
#	src/main/native-chat/native-chat-visuals-delivery.test.ts
#	src/main/runtime/structured-agent-runtime-registrations.ts
Co-Authored-By: Claude <noreply@anthropic.com>
…l-workspace-files

Co-Authored-By: Claude <noreply@anthropic.com>
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status (quick review loop, 2026-10-12)

The problem this PR fixes. When an agent draws a chart or report in a native chat, Orca saved that HTML page in a hidden per-chat folder inside Orca's own data. It wasn't a normal file: you couldn't edit it, share it, see it in the file tree, or open it on SSH and paired hosts. Orca kept a separate read call (agentSession.readVisual) and a separate read-only "chat visual" tab just for these pages, and an inline visual never changed after it first loaded.

What changes for you. The agent now writes an ordinary .html file in the chat's working folder, and the chat shows it the way a file link in the chat already works.

  • The file appears in the file tree and can be edited.
  • It opens like any file (browser tab locally, HTML preview on remote hosts, the file viewer on the phone).
  • Editing it, or the agent rewriting it, updates the inline view, including earlier replies that show the same file.
  • On the phone, visuals in floating chats show "unavailable" and send no request.
  • Old chats' visual lines (file=…) now show as plain text.

This round. Fresh reviewers went through the whole diff, and one architecture pass covered all three stacked PRs; the architecture pass kept this PR's design as is. Then a readiness check ran on the final diff, followed by the one main sync.

  • Fixed in 90f98d35c47: the PR had added an unrelated @pnpm/exe lockfile entry; the lockfile now matches main.
  • Main sync 6923d7a28c9:
    • Main deleted runtime-file-client.ts, so the visual reader now imports the "file missing" check from runtime-file-metadata-client.
    • The one other conflict was a file this PR deletes.
    • The launch-parity snapshot and the params catalog are unchanged by the sync.
  • Deferred, readiness P2: a local visual reads the whole HTML file before applying the 512 KiB limit. The local read allows up to 50 MB for .html, so a visual pointed at a large existing report (for example a coverage index.html) is read in full at every turn end and file change, only to show "unavailable". Files over 50 MB show the "can't reach" state instead of "unavailable". Remote reads are already capped. Follow-up: check the size first (stat or bounded read) and treat too-large as unavailable.
  • Deferred, small:
    • Existing hidden visuals folders from 1.4.224 onward stay on disk now that their cleanup sweep is gone (no native-chat data migration, by decision).
    • A refresh that arrives while a read is still in flight reuses that read, so the visual can show the page from before a rewrite until the next recheck.
    • When the chat's folder or host changes under a mounted visual and the new host doesn't answer, the previous file's page stays shown.
    • "Visualization unavailable" can flash briefly if a chat renders before its repo connection has resolved (not shown to happen on a real restore).

Verified.

  • Local, after the main sync:
    • Typechecks: web, node, CLI and mobile, plus the mobile test ratchet.
    • Desktop tests: 1,304 of 1,305 files pass. The one failure is a temp-folder cleanup race in an untouched file; it passes 10/10 alone.
    • Mobile tests: all 366 files pass.
    • The repo's CI checks pass apart from the known local-only typecheck:ci failure and main's break below.
  • Earlier live QA on these heads passed desktop rows 1–8, including a paired host: agent writes a page, Open, editor save updates the inline view, agent rewrite, floating chat, missing file. Screenshots are in the body.

Not verified.

  • The phone: neither the workspace visual with Open to the file viewer nor the floating "unavailable" state has been checked live.
  • CI on 6923d7a28c9 is red only because of a break on main. config/scripts/check-main-sync-spawn-ratchet.mjs (from ci: stop new main-process code from freezing Orca with synchronous launches #27756) imports four functions that chore(ratchet): one ratchet baseline library #27788 moved to ratchet-baseline.mjs, so the static-analysis step fails on every PR and the test jobs after it are skipped, so CI ran no unit tests on this head. Those files match main exactly here. The pre-sync head, abe8ac86cf4, was fully green. CI needs one re-run after main is fixed.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 12, 2026 21:17
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: af0cc502-1225-450e-9df0-362207b61c73
📥 Commits

Reviewing files that changed from the base of the PR and between 8c5946f and 7f33664.

⛔ Files ignored due to path filters (2)
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (3)
  • mobile/src/session/MobileNativeChatMessage.copy.test.tsx
  • mobile/src/session/MobileNativeChatMessageActionsSheet.test.tsx
  • mobile/src/session/mobile-native-chat-message-plain-text.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

📝 Walkthrough

Walkthrough

Chat visuals now use workspace-relative HTML paths and file-preview operations. Desktop rendering resolves visuals through chat file-link context, while mobile opens them in the file viewer. Local-file access adds read-only HTML access for chat previews. The change removes the session visual-read RPC, per-chat visual-folder setup and cleanup, and dedicated chat-visual editor tabs.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 7f336

A context change can leave the previous visual on screen when the new file is unreachable, and large local visuals that cannot be previewed may still incur repeated reads. These are bounded follow-ups; ordinary visual previews remain available.

Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 52 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly summarizes the main change: storing chat HTML visuals as ordinary workspace files.
Description check Passed The description is complete and directly aligned with the template. It explains the user impact, implementation, motivation, issue references, visual proof, testing, known limitations, and checklist s…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1ca36097-c631-4cd8-b9cf-e1b0c5dd7b04
📥 Commits

Reviewing files that changed from the base of the PR and between 8e8c087 and 7590f90.

⛔ Files ignored due to path filters (2)
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
  • src/shared/rpc-contract/rpc-params-catalog.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (109)
  • mobile/src/components/MobileMarkdown.tsx
  • mobile/src/components/WorktreeAgentRow.visual-line.test.tsx
  • mobile/src/components/mobile-markdown-visual-lines.test.ts
  • mobile/src/session/MobileNativeChatVisual.tsx
  • mobile/src/session/mobile-native-chat-message-plain-text.test.ts
  • mobile/src/session/mobile-native-chat-message-visuals.test.ts
  • mobile/src/session/mobile-native-chat-visual-read.test.ts
  • mobile/src/session/mobile-native-chat-visual-read.ts
  • mobile/src/session/mobile-structured-agent-session-contract.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • mobile/src/session/use-mobile-native-chat-visual.test.tsx
  • mobile/src/session/use-mobile-native-chat-visual.ts
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • resources/native-chat-visuals/skills/orca-chat-visuals/SKILL.md
  • src/main/acp/acp-structured-launch-resolution-omp.test.ts
  • src/main/acp/acp-structured-launch-resolution.ts
  • src/main/acp/acp-structured-launch-visuals.test.ts
  • src/main/acp/acp-structured-launch-visuals.ts
  • src/main/ai-vault/session-scanner-values.test.ts
  • src/main/claude/claude-child-process-environment.ts
  • src/main/claude/claude-structured-launch-resolution.ts
  • src/main/claude/claude-structured-launch-visuals.test.ts
  • src/main/claude/claude-structured-real-cli-visuals.test.ts
  • src/main/codex/codex-structured-launch-resolution.test.ts
  • src/main/codex/codex-structured-launch-resolution.ts
  • src/main/codex/codex-structured-real-cli-visuals.test.ts
  • src/main/codex/codex-structured-session-acquire.ts
  • src/main/codex/codex-structured-thread-open.ts
  • src/main/codex/codex-structured-visuals.test.ts
  • src/main/codex/codex-structured-visuals.ts
  • src/main/ipc/local-file-access-resolution-network-path.test.ts
  • src/main/ipc/local-file-access-resolution.test.ts
  • src/main/ipc/local-file-access-resolution.ts
  • src/main/native-chat/native-chat-inline-visuals-setting.test.ts
  • src/main/native-chat/native-chat-visual-file-read.test.ts
  • src/main/native-chat/native-chat-visual-file-read.ts
  • src/main/native-chat/native-chat-visuals-delivery.test.ts
  • src/main/native-chat/native-chat-visuals-delivery.ts
  • src/main/native-chat/native-chat-visuals-folder.ts
  • src/main/native-chat/native-chat-visuals-sweep.test.ts
  • src/main/native-chat/native-chat-visuals-sweep.ts
  • src/main/runtime/agent-session-record-store.ts
  • src/main/runtime/agent-session-store-state.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • src/main/runtime/native-chat-visuals-workspace-verdict.test.ts
  • src/main/runtime/native-chat-visuals-workspace-verdict.ts
  • src/main/runtime/orca-runtime-get-worktree-ps.ts
  • src/main/runtime/rpc/methods/index.ts
  • src/main/runtime/rpc/methods/structured-agent-session-visual.test.ts
  • src/main/runtime/rpc/methods/structured-agent-session-visual.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-agent-session-methods.ts
  • src/main/runtime/structured-agent-launch-composition.ts
  • src/main/runtime/structured-agent-launch-parity.test.ts
  • src/main/runtime/structured-agent-session-runtime.ts
  • src/renderer/src/components/editor/EditorContent.tsx
  • src/renderer/src/components/editor/closed-editor-tab-disposal.ts
  • src/renderer/src/components/editor/editor-labels.ts
  • src/renderer/src/components/native-chat/NativeChatInlineVisual.tsx
  • src/renderer/src/components/native-chat/NativeChatMarkdown.visual.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx
  • src/renderer/src/components/native-chat/NativeChatPacedMarkdown.file-links.test.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageEditor.test.tsx
  • src/renderer/src/components/native-chat/NativeChatView.tsx
  • src/renderer/src/components/native-chat/NativeChatVisualFrame.test.tsx
  • src/renderer/src/components/native-chat/NativeChatVisualFrame.tsx
  • src/renderer/src/components/native-chat/NativeChatVisualTab.test.tsx
  • src/renderer/src/components/native-chat/NativeChatVisualTab.tsx
  • src/renderer/src/components/native-chat/native-chat-file-link-existence.test.ts
  • src/renderer/src/components/native-chat/native-chat-file-link-existence.ts
  • src/renderer/src/components/native-chat/native-chat-visual-file.test.ts
  • src/renderer/src/components/native-chat/native-chat-visual-file.ts
  • src/renderer/src/components/native-chat/native-chat-visual-markdown-extension.tsx
  • src/renderer/src/components/native-chat/native-chat-visual-markdown-syntax.test.tsx
  • src/renderer/src/components/native-chat/native-chat-visual-markdown-syntax.ts
  • src/renderer/src/components/native-chat/native-chat-visual-open.ts
  • src/renderer/src/components/native-chat/native-chat-visual-owner.tsx
  • src/renderer/src/components/native-chat/native-chat-visual-read-client.test.ts
  • src/renderer/src/components/native-chat/native-chat-visual-read-client.ts
  • src/renderer/src/components/native-chat/native-chat-visual-tab.ts
  • src/renderer/src/components/native-chat/use-native-chat-file-link-existence.tsx
  • src/renderer/src/components/native-chat/use-native-chat-visual-document.ts
  • src/renderer/src/components/tab-bar/EditorFileTab.tsx
  • src/renderer/src/components/tab-bar/EditorFileTabContextMenu.test.tsx
  • src/renderer/src/components/tab-bar/RecentTabSwitcher.tsx
  • src/renderer/src/components/tab-bar/tab-bar-item-model.git-status.test.ts
  • src/renderer/src/hooks/useEditorExternalWatch-targets.test.ts
  • src/renderer/src/lib/local-file-access.ts
  • src/renderer/src/lib/workspace-session-unified-tabs.ts
  • src/renderer/src/runtime/sync-runtime-graph/mobile-session-surfaces.ts
  • src/renderer/src/store/slices/editor-chat-visual-tabs.test.ts
  • src/renderer/src/store/slices/editor/actions/chat-visual-tab-actions.ts
  • src/renderer/src/store/slices/editor/actions/close-file-action.ts
  • src/renderer/src/store/slices/editor/actions/open-file-action.ts
  • src/renderer/src/store/slices/editor/actions/recently-closed-editor-tabs.ts
  • src/renderer/src/store/slices/editor/create-editor-slice.ts
  • src/renderer/src/store/slices/editor/tabs/editor-tab-content-type.ts
  • src/renderer/src/store/slices/editor/types/editor-files-slice.ts
  • src/renderer/src/store/slices/editor/types/open-file.ts
  • src/shared/editor-tab-view-types.ts
  • src/shared/local-file-access.ts
  • src/shared/native-chat-visual-directive.test.ts
  • src/shared/native-chat-visual-directive.ts
  • src/shared/rpc-contract/agent-session-visual-params.ts
  • src/shared/structured-agent-session-latest-request.test.ts
  • src/shared/tab-types.ts
  • src/shared/workspace-layout/old-client/old-client-session-tabs-projection.ts
  • src/shared/workspace-layout/tab-order.ts
  • tests/e2e/cross-version-wire/structured-agent-session-surface-calls.ts
  • tests/e2e/cross-version-wire/structured-agent-session-surface-manifest.ts
💤 Files with no reviewable changes (35)
  • src/renderer/src/components/native-chat/native-chat-visual-read-client.test.ts
  • src/renderer/src/components/native-chat/NativeChatVisualTab.test.tsx
  • src/main/native-chat/native-chat-visuals-sweep.test.ts
  • src/renderer/src/components/editor/closed-editor-tab-disposal.ts
  • src/renderer/src/store/slices/editor/create-editor-slice.ts
  • src/renderer/src/components/native-chat/native-chat-visual-tab.ts
  • tests/e2e/cross-version-wire/structured-agent-session-surface-manifest.ts
  • src/shared/rpc-contract/agent-session-visual-params.ts
  • src/main/runtime/native-chat-visuals-workspace-verdict.test.ts
  • src/renderer/src/components/native-chat/NativeChatVisualTab.tsx
  • src/main/runtime/rpc/methods/structured-agent-session-visual.test.ts
  • src/main/native-chat/native-chat-visual-file-read.test.ts
  • src/renderer/src/components/editor/EditorContent.tsx
  • src/main/runtime/structured-agent-launch-composition.ts
  • src/renderer/src/store/slices/editor/actions/chat-visual-tab-actions.ts
  • src/renderer/src/components/native-chat/NativeChatView.tsx
  • src/main/runtime/rpc/methods/index.ts
  • tests/e2e/cross-version-wire/structured-agent-session-surface-calls.ts
  • src/renderer/src/components/editor/editor-labels.ts
  • mobile/src/session/mobile-structured-agent-session-contract.ts
  • src/main/runtime/mobile-rpc-allowlist.test.ts
  • src/renderer/src/store/slices/editor-chat-visual-tabs.test.ts
  • src/main/native-chat/native-chat-visuals-folder.ts
  • src/main/runtime/agent-session-store-state.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-agent-session-methods.ts
  • src/shared/workspace-layout/old-client/old-client-session-tabs-projection.ts
  • src/renderer/src/components/native-chat/native-chat-visual-read-client.ts
  • src/shared/editor-tab-view-types.ts
  • src/main/claude/claude-child-process-environment.ts
  • src/main/native-chat/native-chat-visual-file-read.ts
  • src/main/native-chat/native-chat-visuals-sweep.ts
  • src/main/runtime/native-chat-visuals-workspace-verdict.ts
  • src/main/runtime/rpc/methods/structured-agent-session-visual.ts
  • mobile/src/session/use-mobile-structured-agent-session.ts
  • src/shared/tab-types.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

Comment on lines +105 to +136
async function readOnce(
key: string,
file: NativeChatVisualFile
): Promise<NativeChatVisualReadOutcome> {
try {
const result = await readRuntimeFilePreview(
file.fileContext,
file.absolutePath,
chatHtmlAccess()
)
if (
result.isBinary ||
new TextEncoder().encode(result.content).byteLength > NATIVE_CHAT_VISUAL_MAX_BYTES
) {
cache.delete(key)
return { kind: 'refused' }
}
const document = { html: result.content }
remember(key, document)
return { kind: 'ready', document }
} catch (error) {
// Why only these: a paired host answers `file_too_large` past 512 KiB; anything else may be transport.
if (
isMissingRuntimePathError(error) ||
(error instanceof Error && error.message === 'file_too_large')
) {
cache.delete(key)
return { kind: 'refused' }
}
return { kind: 'unreachable' }
}
}

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.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '100,155p' src/renderer/src/components/native-chat/native-chat-visual-file.ts
rg -n 'readRuntimeFilePreview|file_too_large|maxBytes|maxSize' src/renderer/src src/main/ipc | head -100

Repository: stablyai/orca

Length of output: 12133


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- runtime preview implementation ---'
nl -ba src/renderer/src/runtime/runtime-file-read-client.ts | sed -n '1,190p'
printf '%s\n' '--- preview tests ---'
nl -ba src/renderer/src/runtime/runtime-file-client.test.ts | sed -n '430,555p'
printf '%s\n' '--- visual reader declarations/callers ---'
rg -n -F --glob '*.{ts,tsx}' -- 'readNativeChatVisualFile' src/renderer/src
printf '%s\n' '--- visual file constants and reader context ---'
nl -ba src/renderer/src/components/native-chat/native-chat-visual-file.ts | sed -n '1,180p'

Repository: stablyai/orca

Length of output: 20384


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- local fs.readFile bindings and limits ---'
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'fs.readFile' src/main src/shared src/preload src/renderer | head -120
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'readFile:' src/main src/shared src/preload src/renderer | head -120
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'FILE_PREVIEW' src/main src/shared src/preload src/renderer
printf '%s\n' '--- visual document hook ---'
nl -ba src/renderer/src/components/native-chat/use-native-chat-visual-document.ts | sed -n '1,180p'
printf '%s\n' '--- visual reader tests ---'
nl -ba src/renderer/src/components/native-chat/native-chat-visual-file.test.ts | sed -n '1,155p'

Repository: stablyai/orca

Length of output: 16316


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- preload filesystem API ---'
nl -ba src/preload/api/filesystem-api.ts | sed -n '1,130p'
nl -ba src/preload/api/fs-bridge.ts | sed -n '1,120p'
printf '%s\n' '--- filesystem IPC bindings ---'
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'readFile' src/main/ipc src/main | head -180
printf '%s\n' '--- size/stat APIs ---'
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'stat' src/preload src/main/ipc src/main src/shared | head -160
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'readFileChunk' src/preload src/main/ipc src/main src/shared | head -160

Repository: stablyai/orca

Length of output: 41463


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- local read handler ---'
nl -ba src/main/ipc/filesystem/filesystem-read-handlers.ts | sed -n '1,215p'
printf '%s\n' '--- regular-file reader ---'
nl -ba src/main/ipc/filesystem/local-regular-file-read.ts | sed -n '1,135p'
printf '%s\n' '--- stat API declaration/bridge ---'
rg -n -F --glob '*.{ts,tsx}' -- 'stat:' src/preload/api/filesystem-api.ts src/preload/api/fs-bridge.ts
rg -n -F --glob '*.{ts,tsx}' -- 'ipcRenderer.invoke('\"'\"'fs:stat'\"'\"'' src/preload/api/fs-bridge.ts
nl -ba src/preload/api/filesystem-api.ts | sed -n '125,230p'

Repository: stablyai/orca

Length of output: 16538


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- delegated local content read ---'
nl -ba src/main/ipc/filesystem/filesystem-file-content-inspection.ts | sed -n '1,240p'
printf '%s\n' '--- local content read references and limits ---'
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'readLocalFileContent' src
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' --glob '!**/*.test.*' -- '50 * 1024 * 1024' src
rg -n -F --glob '*.{ts,tsx}' --glob '!**/*.test.*' -- 'FILE_READ' src/main src/shared

Repository: stablyai/orca

Length of output: 8054


🏁 Script executed:

set -o pipefail
rg -n -F --glob '*.{ts,tsx}' -- 'useNativeChatVisualDocument(' src/renderer/src

Repository: stablyai/orca

Length of output: 389


Check the local file size before reading its content.

For local HTML between 512 KiB and 50 MiB, readRuntimeFilePreview reads and transfers the complete file. readOnce then rejects it. The visual hook repeats this read on refreshes and file-change events, causing repeated I/O, IPC transfer, and memory allocation for files that will always be refused.

🐛 Suggested fix
diff --git a/src/renderer/src/runtime/runtime-file-read-client.ts b/src/renderer/src/runtime/runtime-file-read-client.ts
@@
 export async function readRuntimeFilePreview(
   context: RuntimeFileOperationArgs,
   filePath: string,
-  access?: LocalFileAccess
+  access?: LocalFileAccess,
+  maxBytes?: number
 ): Promise<RuntimeFilePreviewResult> {
@@
   if (!remoteArgs) {
     if (hasRemoteRuntimeOwner(context)) {
       throw new Error('Remote file is outside the owning runtime worktree')
     }
+    if (maxBytes !== undefined) {
+      const stats = await window.api.fs.stat({
+        filePath,
+        connectionId: context.connectionId,
+        ...localAccess(context.connectionId, access)
+      })
+      if (stats.size > maxBytes) {
+        throw new Error('file_too_large')
+      }
+    }
     return window.api.fs.readFile({
       filePath,
       connectionId: context.connectionId,
diff --git a/src/renderer/src/components/native-chat/native-chat-visual-file.ts b/src/renderer/src/components/native-chat/native-chat-visual-file.ts
@@
     const result = await readRuntimeFilePreview(
       file.fileContext,
       file.absolutePath,
-      chatHtmlAccess()
+      chatHtmlAccess(),
+      NATIVE_CHAT_VISUAL_MAX_BYTES
     )

Comment on lines +138 to +152
/** Reads the file now; visuals of the same file asking at once share one read. */
export function readNativeChatVisualFile(
file: NativeChatVisualFile
): Promise<NativeChatVisualReadOutcome> {
const key = cacheKey(file)
const pending = inFlight.get(key)
if (pending) {
return pending
}
const read = readOnce(key, file).finally(() => {
inFlight.delete(key)
})
inFlight.set(key, read)
return read
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

sed -n '65,115p' src/renderer/src/components/native-chat/use-native-chat-visual-document.ts
sed -n '130,155p' src/renderer/src/components/native-chat/native-chat-visual-file.ts

Repository: stablyai/orca

Length of output: 2285


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- hook outline and references ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'useNativeChatVisualDocument' src/renderer/src
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'useVisualFileChangeCount' src/renderer/src
printf '%s\n' '--- hook source ---'
sed -n '1,180p' src/renderer/src/components/native-chat/use-native-chat-visual-document.ts
printf '%s\n' '--- visual file source ---'
sed -n '1,180p' src/renderer/src/components/native-chat/native-chat-visual-file.ts
printf '%s\n' '--- relevant tests ---'
rg -n -F --glob '*test*' --glob '*spec*' -- 'refreshKey' src/renderer/src/components/native-chat
rg -n -F --glob '*test*' --glob '*spec*' -- 'readNativeChatVisualFile' src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 10999


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- inline visual caller ---'
sed -n '1,150p' src/renderer/src/components/native-chat/NativeChatInlineVisual.tsx
printf '%s\n' '--- change event declarations and dispatchers ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'ORCA_WORKTREE_FILE_CHANGE_EVENT' src
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'dispatchEvent' src/renderer/src src/hooks shared
printf '%s\n' '--- refresh and file-link resolution call sites ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'refreshKey' src/renderer/src/components/native-chat src
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'resolveNativeChatVisualFile' src
printf '%s\n' '--- preview implementation and tests ---'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'function readRuntimeFilePreview' src
sed -n '1,180p' src/renderer/src/runtime/runtime-file-read-client.ts
sed -n '1,180p' src/renderer/src/components/native-chat/native-chat-visual-file.test.ts

Repository: stablyai/orca

Length of output: 42150


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- file-link existence recheck hook ---'
sed -n '1,220p' src/renderer/src/components/native-chat/use-native-chat-file-link-existence.ts
printf '%s\n' '--- worktree change event source ---'
sed -n '1,130p' src/renderer/src/hooks/worktree-file-change-event.ts
sed -n '1,125p' src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts
printf '%s\n' '--- visual refresh tests ---'
sed -n '1,190p' src/renderer/src/components/native-chat/NativeChatMarkdown.visual.test.tsx
printf '%s\n' '--- change-specific diff ---'
git diff --no-ext-diff --unified=30 8e8c08758118f821a76453388c4d004de0144c2b 7590f90e286557a37d594351b55210ec40e5ec76 -- src/renderer/src/components/native-chat/use-native-chat-visual-document.ts src/renderer/src/components/native-chat/native-chat-visual-file.ts src/renderer/src/components/native-chat/use-native-chat-file-link-existence.ts src/renderer/src/hooks/editor-external-watch-event-reconciliation.ts

Repository: stablyai/orca

Length of output: 25446


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- locate existence implementation ---'
rg --files src/renderer/src/components/native-chat | rg 'native-chat-file-link-existence'
rg -n -F --glob '*.ts' --glob '*.tsx' -- 'useNativeChatFileLinkRecheckGeneration' src/renderer/src/components/native-chat
printf '%s\n' '--- visual refresh test remainder ---'
sed -n '190,430p' src/renderer/src/components/native-chat/NativeChatMarkdown.visual.test.tsx
printf '%s\n' '--- existence implementation ---'
for f in src/renderer/src/components/native-chat/native-chat-file-link-existence.*; do
  [ -f "$f" ] && { echo "### $f"; sed -n '1,260p' "$f"; }
done

Repository: stablyai/orca

Length of output: 29140


Start a new read for refreshes that supersede an in-flight read.

When a matching file-change event or file-link recheck occurs during a pending read, the effect runs again but readNativeChatVisualFile returns the existing promise. If that promise contains the pre-change content, the refresh displays that content. No further read starts until another trigger changes the effect dependencies.

Keep concurrent initial loads deduplicated, but add a refresh-aware read path that bypasses or queues behind the pending promise. Ensure an older promise cannot remove a newer entry from inFlight.

Suggested fix
- import { useEffect, useState } from 'react'
+ import { useEffect, useRef, useState } from 'react'
...
+  const previousLoad = useRef<{
+    file: NativeChatVisualFile
+    enabled: boolean
+    refreshKey: unknown
+    changes: number
+  } | null>(null)
...
+    const forceRefresh =
+      previousLoad.current !== null &&
+      (previousLoad.current.file !== file ||
+        previousLoad.current.enabled !== enabled ||
+        previousLoad.current.refreshKey !== refreshKey ||
+        previousLoad.current.changes !== changes)
+    previousLoad.current = { file, enabled, refreshKey, changes }
-    void readNativeChatVisualFile(file).then((outcome) => {
+    void readNativeChatVisualFile(file, { force: forceRefresh }).then((outcome) => {
 export function readNativeChatVisualFile(
-  file: NativeChatVisualFile
+  file: NativeChatVisualFile,
+  options: { force?: boolean } = {}
 ): Promise<NativeChatVisualReadOutcome> {
   const key = cacheKey(file)
   const pending = inFlight.get(key)
-  if (pending) {
+  if (pending && !options.force) {
     return pending
   }
   const read = readOnce(key, file).finally(() => {
-    inFlight.delete(key)
+    if (inFlight.get(key) === read) {
+      inFlight.delete(key)
+    }
   })

Comment on lines 68 to +111
/**
* The visual's current document. Starts from the cached revision when there is one, then asks the
* host (revalidating it) once `enabled`, and again whenever `refreshKey` changes. A later failure
* keeps a document already shown.
* The visual's current page. Starts from the cached copy when there is one, reads the file once
* `enabled`, and again whenever `refreshKey` changes or the file changes under a watch. An
* unchanged page keeps its frame; an unreachable host keeps the page already shown.
*/
export function useNativeChatVisualDocument(
identity: NativeChatVisualIdentity,
file: NativeChatVisualFile | null,
enabled: boolean,
refreshKey?: unknown
refreshKey: unknown
): NativeChatVisualDocumentState {
const { sessionId, file } = identity
const environmentId =
identity.target.kind === 'environment' ? identity.target.environmentId : null
const [state, setState] = useState<NativeChatVisualDocumentState>(() => {
const cached = peekCachedNativeChatVisual(identity)
const cached = file ? peekNativeChatVisualFile(file) : null
return cached ? { status: 'ready', document: cached } : { status: 'loading' }
})
const changes = useVisualFileChangeCount(file, enabled)

useEffect(() => {
if (!enabled) {
return
}
const identityNow: NativeChatVisualIdentity = {
target: environmentId === null ? { kind: 'local' } : { kind: 'environment', environmentId },
sessionId,
file
if (!file) {
setState({ status: 'unavailable' })
return
}
let current = true
void readNativeChatVisualFile(file).then((outcome) => {
if (!current) {
return
}
setState((shown) => {
if (outcome.kind === 'ready') {
return shown.status === 'ready' && shown.document.html === outcome.document.html
? shown
: { status: 'ready', document: outcome.document }
}
return outcome.kind === 'unreachable' && shown.status === 'ready'
? shown
: { status: 'unavailable' }
})
})
return () => {
current = false
}
const abort = new AbortController()
void loadWithRetries(identityNow, abort.signal, setState)
return () => abort.abort()
}, [enabled, environmentId, file, sessionId, refreshKey])
}, [enabled, file, refreshKey, changes])

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

rg -n 'NativeChatInlineVisual|NativeChatVisualOwnerProvider|NativeChatFileLinkContext|useNativeChatFileLinkContext' src/renderer/src/components/native-chat
sed -n '1,110p' src/renderer/src/components/native-chat/NativeChatInlineVisual.tsx
sed -n '1,125p' src/renderer/src/components/native-chat/use-native-chat-visual-document.ts

Repository: stablyai/orca

Length of output: 20820


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- visual markdown extension ---'
sed -n '1,150p' src/renderer/src/components/native-chat/native-chat-visual-markdown-extension.tsx
printf '%s\n' '--- visual owner and view ---'
sed -n '1,180p' src/renderer/src/components/native-chat/native-chat-visual-owner.tsx
sed -n '1,120p' src/renderer/src/components/native-chat/NativeChatView.tsx
printf '%s\n' '--- file-link context hook and resolver ---'
sed -n '1,230p' src/renderer/src/components/native-chat/use-native-chat-file-link-context.ts
sed -n '1,230p' src/renderer/src/components/native-chat/native-chat-file-link.ts
printf '%s\n' '--- structured session visual-related render path ---'
rg -n -F --glob '*.tsx' -- 'NativeChatView' src/renderer/src/components src/renderer/src | head -80
rg -n -F --glob '*.tsx' -- 'NativeChatVisualOwnerProvider' src/renderer/src | head -80

Repository: stablyai/orca

Length of output: 20261


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- owner worktree hook and directory resolution ---'
sed -n '1,180p' src/renderer/src/components/native-chat/use-native-chat-tab-owner.ts
sed -n '1,260p' src/renderer/src/components/native-chat/native-chat-tab-directory.ts
printf '%s\n' '--- visual extension callers and nearby keys ---'
rg -n -F --glob '*.tsx' -- 'useNativeChatVisualMarkdownExtension' src/renderer/src
rg -n -F --glob '*.tsx' -- 'key={props.sessionId}' src/renderer/src/components/native-chat
rg -n -F --glob '*.tsx' -- 'key=' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
printf '%s\n' '--- connection lookup declaration/usages ---'
rg -n -F --glob '*.ts' -- 'function getConnectionIdFromState' src/renderer/src
rg -n -F --glob '*.ts' -- 'getConnectionIdFromState' src/renderer/src/lib src/renderer/src/store src/renderer/src/components/native-chat

Repository: stablyai/orca

Length of output: 13824


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- markdown consumer ---'
sed -n '1,180p' src/renderer/src/components/native-chat/NativeChatMarkdown.tsx
printf '%s\n' '--- structured session render blocks ---'
sed -n '250,390p' src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
printf '%s\n' '--- connection resolver ---'
sed -n '1,180p' src/renderer/src/lib/connection-owner-resolution.ts
printf '%s\n' '--- visual file identity and reader ---'
sed -n '1,300p' src/renderer/src/components/native-chat/native-chat-visual-file.ts

Repository: stablyai/orca

Length of output: 19816


🏁 Script executed:

set -o pipefail
printf '%s\n' '--- owner selector update tests ---'
sed -n '185,250p' src/renderer/src/components/native-chat/native-chat-owner-selectors.test.tsx
printf '%s\n' '--- message list and row declarations ---'
rg -n -F --glob '*.tsx' -- 'function NativeChatMessageList' src/renderer/src/components/native-chat
rg -n -F --glob '*.tsx' -- 'NativeChatMarkdown' src/renderer/src/components/native-chat | head -60
printf '%s\n' '--- numbered decisive source ---'
nl -ba src/renderer/src/components/native-chat/NativeChatInlineVisual.tsx | sed -n '38,72p'
nl -ba src/renderer/src/components/native-chat/use-native-chat-visual-document.ts | sed -n '68,118p'
nl -ba src/renderer/src/components/native-chat/native-chat-visual-file.ts | sed -n '40,100p'
nl -ba src/renderer/src/components/native-chat/NativeChatView.tsx | sed -n '13,30p'
nl -ba src/renderer/src/components/native-chat/native-chat-visual-owner.tsx | sed -n '25,52p'

Repository: stablyai/orca

Length of output: 16346


Reset the visual document when the resolved file changes.

NativeChatInlineVisual can stay mounted while its folder or connection changes. An unreachable read then preserves the previous ready document, so the frame can display HTML from the prior folder or host. Reset the document to loading for a new file identity. Retain the old document only when rereading the same file.

Suggested fix
-import { useEffect, useState } from 'react'
+import { useEffect, useRef, useState } from 'react'
...
   const [state, setState] = useState<NativeChatVisualDocumentState>(() => {
     const cached = file ? peekNativeChatVisualFile(file) : null
     return cached ? { status: 'ready', document: cached } : { status: 'loading' }
   })
+  const previousFile = useRef(file)
+  const fileChanged = previousFile.current !== file
+  if (fileChanged) {
+    previousFile.current = file
+  }
   const changes = useVisualFileChangeCount(file, enabled)
...
-        return outcome.kind === 'unreachable' && shown.status === 'ready'
+        return !fileChanged && outcome.kind === 'unreachable' && shown.status === 'ready'
           ? shown
           : { status: 'unavailable' }
...
-  return state
+  return fileChanged ? { status: 'loading' } : state

…l-workspace-files

# Conflicts:
#	mobile/src/session/mobile-structured-agent-session-contract.ts
#	mobile/src/session/use-mobile-structured-agent-session.ts
…l-workspace-files

# Conflicts:
#	src/main/codex/codex-structured-real-cli-visuals.test.ts
#	src/main/codex/codex-structured-visuals.test.ts
#	src/main/native-chat/native-chat-inline-visuals-setting.test.ts
…l-workspace-files

# Conflicts:
#	mobile/src/session/mobile-native-chat-message-plain-text.test.ts
Main's new copy tests used the file= visual lines, which this branch no longer treats as visuals.
@brennanb2025
brennanb2025 marked this pull request as draft October 13, 2026 02:37
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Superseded by the rebuilt six-PR chat scratch stack (#28385 merged, #28389, #28397, #28437, #28394, #28479). This piece is replaced by #28389, #28397 and #28437 (visuals stay in the chat's scratch folder and open like any file). Closing.

@brennanb2025
brennanb2025 deleted the brennanb2025/chat-html-workspace-files branch October 14, 2026 02:41
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