Skip to content

loosen up the requirements to show the File Browser in term context menu#3232

Merged
sawka merged 1 commit intomainfrom
sawka/term-filebrowser
Apr 17, 2026
Merged

loosen up the requirements to show the File Browser in term context menu#3232
sawka merged 1 commit intomainfrom
sawka/term-filebrowser

Conversation

@sawka
Copy link
Copy Markdown
Member

@sawka sawka commented Apr 17, 2026

No description provided.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

Walkthrough

This pull request refactors the File Browser menu item visibility logic in the terminal view. The canShowFileBrowser condition in term-model.ts changes from depending on shell integration readiness status to depending on the last executed command. A new helper function isLikelyOnSameHost is introduced in termutil.ts that returns false if the last command starts with "ssh " (after trimming whitespace), and true otherwise, indicating whether the terminal is likely connected to the same host.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to evaluate whether the description relates to the changeset. Add a description explaining the rationale for loosening the File Browser visibility requirements and the impact of this change.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: relaxing the File Browser visibility requirement in the terminal context menu from shell integration readiness to host-likelihood checks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sawka/term-filebrowser

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 and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying waveterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: bc56458
Status: ✅  Deploy successful!
Preview URL: https://b71f4249.waveterm.pages.dev
Branch Preview URL: https://sawka-term-filebrowser.waveterm.pages.dev

View logs

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/app/view/term/term-model.ts (1)

965-984: ⚠️ Potential issue | 🟡 Minor

File Browser heuristic is insufficient for SSH detection; only checks ssh prefix.

The File Browser currently uses isLikelyOnSameHost() which only checks !cmd.startsWith("ssh "). This heuristic fails in common scenarios:

  • Misses mosh and autossh commands
  • Most critically: fails when user runs any command after SSH'ing (e.g., user SSH's, runs ls, then opens File Browser—cmd:cwd becomes remote but isLikelyOnSameHost("ls") returns true)

When the heuristic incorrectly allows File Browser creation with a remote cmd:cwd on the original (local) connection, the preview block attempts to browse the remote path on the wrong host.

Consider checking additional context like shell:lastcmdexitcode behavior or expanding the heuristic to detect SSH wrappers. Alternatively, include the targeted host in the label (e.g., File Browser ({connection or "local"})) to surface the actual target.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/term-model.ts` around lines 965 - 984, The File
Browser opens on the wrong host because the current heuristic
(isLikelyOnSameHost) only checks cmd.startsWith("ssh ") and misses wrappers
(mosh, autossh) and cases where the last interactive command was run after an
SSH session; update the logic in the File Browser click handler that reads
blockData?.meta (referenced via this.blockAtom and createBlock/BlockDef) to: 1)
prefer an explicit meta.connection if present and pass it into
BlockDef.meta.connection so the preview uses the correct host; 2) improve
isLikelyOnSameHost to also detect common SSH wrappers (e.g.,
/^\\s*(ssh|mosh|autossh)\\b/i), detect remote cwd patterns (user@host: or /ssh:
or other remote path markers), and consider shell:lastcmdexitcode or other meta
flags when available; and 3) update the menu label to include the target (e.g.,
`File Browser (${connection ?? "local"})`) so users see which host will be
browsed.
🧹 Nitpick comments (1)
frontend/app/view/term/termutil.ts (1)

400-406: Type signature should allow null.

lastCommand is read from lastCommandAtom, which is explicitly set to null in several places (see osc-handlers.ts and termwrap.ts), and the function body already handles null. Typing the parameter as string misrepresents the contract and will trip up strict null-checking callers.

Proposed fix
-export function isLikelyOnSameHost(lastCommand: string): boolean {
+export function isLikelyOnSameHost(lastCommand: string | null | undefined): boolean {
     if (lastCommand == null) {
         return true;
     }
     const cmd = lastCommand.trimStart();
     return !cmd.startsWith("ssh ");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/termutil.ts` around lines 400 - 406, The parameter
type for isLikelyOnSameHost should allow null since callers set lastCommandAtom
to null; change the function signature from (lastCommand: string) to
(lastCommand: string | null) so the type matches the runtime check and satisfies
strict null-checking, leaving the existing null handling in the body intact
(update any TypeScript usages/call sites if they rely on it being just string).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/view/term/term-model.ts`:
- Around line 961-963: Guard against this.termRef?.current?.lastCommandAtom
being undefined before calling globalStore.get: retrieve the atom into a local
(e.g., lastCommandAtom = this.termRef?.current?.lastCommandAtom), only call
globalStore.get(lastCommandAtom) if that atom is truthy, otherwise set
lastCommand to undefined; then compute cwd = blockData?.meta?.["cmd:cwd"] and
keep canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand) so
callers of lastCommandAtom (and globalStore.get) no longer throw when the
TermWrap or termRef is uninitialized.

In `@frontend/app/view/term/termutil.ts`:
- Around line 400-406: The heuristic in isLikelyOnSameHost only checks for a
literal leading "ssh " and has blind spots; add a concise JSDoc above export
function isLikelyOnSameHost(lastCommand: string): boolean explaining this is a
best-effort, immediate-prior-command heuristic (not a guarantee) and list
examples of common false negatives (mosh, autossh, gcloud compute ssh,
docker/kubectl exec, prefixed env/absolute-path ssh, varied spacing, and the
case where lastCommand becomes a remote command) so future readers won't assume
stronger semantics; optionally, if you want to broaden detection, replace the
simple startsWith check with a trimmed regex match like
/(^|\s)(ssh|mosh|autossh|et)\s/ as noted in the review (but keep the JSDoc
regardless).

---

Outside diff comments:
In `@frontend/app/view/term/term-model.ts`:
- Around line 965-984: The File Browser opens on the wrong host because the
current heuristic (isLikelyOnSameHost) only checks cmd.startsWith("ssh ") and
misses wrappers (mosh, autossh) and cases where the last interactive command was
run after an SSH session; update the logic in the File Browser click handler
that reads blockData?.meta (referenced via this.blockAtom and
createBlock/BlockDef) to: 1) prefer an explicit meta.connection if present and
pass it into BlockDef.meta.connection so the preview uses the correct host; 2)
improve isLikelyOnSameHost to also detect common SSH wrappers (e.g.,
/^\\s*(ssh|mosh|autossh)\\b/i), detect remote cwd patterns (user@host: or /ssh:
or other remote path markers), and consider shell:lastcmdexitcode or other meta
flags when available; and 3) update the menu label to include the target (e.g.,
`File Browser (${connection ?? "local"})`) so users see which host will be
browsed.

---

Nitpick comments:
In `@frontend/app/view/term/termutil.ts`:
- Around line 400-406: The parameter type for isLikelyOnSameHost should allow
null since callers set lastCommandAtom to null; change the function signature
from (lastCommand: string) to (lastCommand: string | null) so the type matches
the runtime check and satisfies strict null-checking, leaving the existing null
handling in the body intact (update any TypeScript usages/call sites if they
rely on it being just string).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2d6af3b8-0d30-4ac7-81c5-f5c12021c59c

📥 Commits

Reviewing files that changed from the base of the PR and between 4969ee1 and bc56458.

📒 Files selected for processing (2)
  • frontend/app/view/term/term-model.ts
  • frontend/app/view/term/termutil.ts

Comment on lines +961 to +963
const lastCommand = globalStore.get(this.termRef?.current?.lastCommandAtom);
const cwd = blockData?.meta?.["cmd:cwd"];
const canShowFileBrowser = shellIntegrationStatus === "ready" && cwd != null;
const canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand);
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm globalStore.get behavior with undefined and check how other call sites guard lastCommandAtom access.
rg -nP -C2 '\blastCommandAtom\b'
rg -nP -C2 'globalStore\.get\(\s*this\.termRef'

Repository: wavetermdev/waveterm

Length of output: 5605


🏁 Script executed:

#!/bin/bash
# Get context around line 961 in term-model.ts
sed -n '945,975p' frontend/app/view/term/term-model.ts | cat -n

# Search for where this function is called from to understand preconditions
rg -B5 -A15 'canShowFileBrowser' frontend/app/view/term/term-model.ts | head -40

# Check how globalStore.get is typed/documented
rg -B3 -A3 'globalStore.*=.*new' frontend/app/store/*.ts

Repository: wavetermdev/waveterm

Length of output: 2535


🏁 Script executed:

#!/bin/bash
# Find where globalStore is defined and check its type definition
rg -B3 -A10 'const globalStore' frontend/app/store/*.ts

# Check jotai atom and store usage patterns
rg -l 'jotai' frontend/app/store/*.ts | head -3

# Search for error handling around globalStore.get calls
rg -B3 -A3 'globalStore\.get\(' frontend/app/view/term/term-model.ts | head -50

# Check if termRef is initialized in constructor/lifecycle
rg -B5 -A10 'termRef.*=' frontend/app/view/term/term-model.ts | head -60

Repository: wavetermdev/waveterm

Length of output: 4568


🏁 Script executed:

#!/bin/bash
# Check if there are examples in code where undefined atoms are caught/handled
rg -B2 -A2 'try.*globalStore\.get|catch.*globalStore' frontend/app/

# Look for jotai types or TypeScript warnings about undefined atoms
rg 'Atom<' frontend/app/view/term/term-model.ts | head -10

# Check if this context menu function is called early (before mount)
rg -B10 'getMenuItemsForBlockContextMenu' frontend/app/view/term/term-model.ts | head -40

# Check for jotai usage documentation in package.json or comments
rg -i 'jotai' frontend/package.json

Repository: wavetermdev/waveterm

Length of output: 947


🏁 Script executed:

#!/bin/bash
# Find where getMenuItemsForBlockContextMenu is called from
rg 'getMenuItemsForBlockContextMenu' frontend/

# Look at the broader context - is this part of a lifecycle hook or event handler?
rg -B20 'const lastCommand = globalStore\.get.*lastCommandAtom' frontend/app/view/term/term-model.ts | head -50

# Check if there are any type errors or comments about this
rg -B5 'lastCommandAtom.*undefined|undefined.*lastCommandAtom' frontend/

Repository: wavetermdev/waveterm

Length of output: 926


🏁 Script executed:

#!/bin/bash
# Find the function definition containing the problematic code
rg -B5 'getMenuItemsForBlockContextMenu|const lastCommand = globalStore\.get.*termRef.*lastCommandAtom' frontend/app/view/term/term-model.ts | grep -E '(getMenuItemsForBlockContextMenu|private|public|async)' | head -5

# Search for function calls to this menu function across the entire repo
rg 'getMenuItemsForBlockContextMenu\(' frontend/ | head -20

# Get full function signature and context around line 961
sed -n '920,970p' frontend/app/view/term/term-model.ts

Repository: wavetermdev/waveterm

Length of output: 2348


🏁 Script executed:

#!/bin/bash
# Get the full function name and signature
sed -n '890,920p' frontend/app/view/term/term-model.ts

# Find where this context menu function is invoked
rg 'getContextMenuItems|getMenuItems' frontend/app/view/term/term-model.ts

# Look for right-click handler or context menu trigger
rg -B3 -A3 'onContextMenu|contextmenu' frontend/app/view/term/

Repository: wavetermdev/waveterm

Length of output: 2234


🏁 Script executed:

#!/bin/bash
# Find where handleContextMenu is defined in term.tsx
rg -B5 -A10 'handleContextMenu' frontend/app/view/term/term.tsx

# Check when termRef.current is assigned (when TermWrap mounts)
rg -B3 -A3 'ref={termRef}|termRef\.current\s*=' frontend/app/view/term/termwrap.ts

# Find the React component useEffect/mount cycle for TermWrap
rg -B5 -A15 'componentDidMount|useEffect.*termRef|React\.forwardRef' frontend/app/view/term/termwrap.ts | head -60

Repository: wavetermdev/waveterm

Length of output: 1482


🏁 Script executed:

#!/bin/bash
# Find where termRef is assigned in term.tsx (the component that passes termRef down)
rg -B10 -A10 'termRef.*current\s*=' frontend/app/view/term/term.tsx

# Check TermWrap class to understand lifecycle
rg -B5 -A15 'class TermWrap|forwardRef.*TermWrap' frontend/app/view/term/termwrap.ts | head -80

# Look at the componentDidMount or constructor for TermWrap
sed -n '120,160p' frontend/app/view/term/termwrap.ts

Repository: wavetermdev/waveterm

Length of output: 3322


Guard against a null lastCommandAtom.

this.termRef?.current?.lastCommandAtom can be undefined if termRef.current isn't set yet or the TermWrap hasn't initialized its atoms. globalStore.get(undefined) will throw, breaking the context menu. Other code sites (e.g., tabrpcclient.ts line 165) explicitly guard against this pattern.

Proposed fix
-        const lastCommand = globalStore.get(this.termRef?.current?.lastCommandAtom);
+        const lastCommandAtom = this.termRef?.current?.lastCommandAtom;
+        const lastCommand = lastCommandAtom ? globalStore.get(lastCommandAtom) : null;
         const cwd = blockData?.meta?.["cmd:cwd"];
         const canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lastCommand = globalStore.get(this.termRef?.current?.lastCommandAtom);
const cwd = blockData?.meta?.["cmd:cwd"];
const canShowFileBrowser = shellIntegrationStatus === "ready" && cwd != null;
const canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand);
const lastCommandAtom = this.termRef?.current?.lastCommandAtom;
const lastCommand = lastCommandAtom ? globalStore.get(lastCommandAtom) : null;
const cwd = blockData?.meta?.["cmd:cwd"];
const canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/term-model.ts` around lines 961 - 963, Guard against
this.termRef?.current?.lastCommandAtom being undefined before calling
globalStore.get: retrieve the atom into a local (e.g., lastCommandAtom =
this.termRef?.current?.lastCommandAtom), only call
globalStore.get(lastCommandAtom) if that atom is truthy, otherwise set
lastCommand to undefined; then compute cwd = blockData?.meta?.["cmd:cwd"] and
keep canShowFileBrowser = cwd != null && isLikelyOnSameHost(lastCommand) so
callers of lastCommandAtom (and globalStore.get) no longer throw when the
TermWrap or termRef is uninitialized.

Comment on lines +400 to +406
export function isLikelyOnSameHost(lastCommand: string): boolean {
if (lastCommand == null) {
return true;
}
const cmd = lastCommand.trimStart();
return !cmd.startsWith("ssh ");
}
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.

⚠️ Potential issue | 🟡 Minor

Heuristic has known blind spots — worth documenting.

The check only matches a leading literal "ssh ". Common remote-session invocations will slip through and the File Browser will be offered against a remote host it can't actually reach:

  • mosh user@host, autossh ..., et user@host, kubectl exec, docker exec -it ... bash, gcloud compute ssh, ssh host (multiple spaces is fine, but SSH host, /usr/bin/ssh host, or env FOO=1 ssh host are not), etc.
  • Conversely, once the user runs any command inside the ssh session (and shell integration is active on the remote), lastCommand becomes that remote command and isLikelyOnSameHost returns true again — so the File Browser re-appears and opens the local/original connection's filesystem against a cwd that belongs to the remote host.

Given the function is named isLikely…, the fuzziness is acknowledged, but please at least add a brief JSDoc noting it's a best-effort heuristic gated on the immediately prior command, so future readers don't expect stronger guarantees. If tightening is desired, matching /(^|\s)(ssh|mosh|autossh|et)\s/ on a trimmed command would catch more cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/view/term/termutil.ts` around lines 400 - 406, The heuristic in
isLikelyOnSameHost only checks for a literal leading "ssh " and has blind spots;
add a concise JSDoc above export function isLikelyOnSameHost(lastCommand:
string): boolean explaining this is a best-effort, immediate-prior-command
heuristic (not a guarantee) and list examples of common false negatives (mosh,
autossh, gcloud compute ssh, docker/kubectl exec, prefixed env/absolute-path
ssh, varied spacing, and the case where lastCommand becomes a remote command) so
future readers won't assume stronger semantics; optionally, if you want to
broaden detection, replace the simple startsWith check with a trimmed regex
match like /(^|\s)(ssh|mosh|autossh|et)\s/ as noted in the review (but keep the
JSDoc regardless).

@sawka sawka merged commit 3e4fe8c into main Apr 17, 2026
9 checks passed
@sawka sawka deleted the sawka/term-filebrowser branch April 17, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant