feat: Tab completion now respects shell current working directory

- Tab completion file/folder suggestions now show items relative to
  the shell's current directory (shellCwd) instead of the project root
- Added shellRelativePath computation to navigate the file tree to
  the correct subdirectory before showing suggestions
- Also includes: PTY terminal environment fix for proper PATH handling,
  and CSS fix to hide empty paragraphs in markdown prose

Claude ID: 0302fea8-0c56-49fc-8134-c463a0ea1096
Maestro ID: b9bc0d08-5be2-4fdf-93cd-5618a8d53b35
This commit is contained in:
Pedram Amini
2025-12-02 10:09:55 -06:00
parent dc0292e4c2
commit c6b3ebb954
3 changed files with 73 additions and 5 deletions

View File

@@ -156,12 +156,33 @@ export class ProcessManager extends EventEmitter {
ptyArgs = finalArgs;
}
// Build environment for PTY process
// For terminal sessions, we want the shell to build its own PATH from startup files
// (.zprofile, .zshrc, etc.) rather than inheriting Electron's limited PATH.
// Electron launched from Finder gets a minimal PATH from launchd, not the user's shell PATH.
let ptyEnv: NodeJS.ProcessEnv;
if (isTerminal) {
// For terminals: pass minimal env, let shell startup files set PATH
// This ensures the terminal has the same environment as a regular terminal app
ptyEnv = {
HOME: process.env.HOME,
USER: process.env.USER,
SHELL: process.env.SHELL,
TERM: 'xterm-256color',
LANG: process.env.LANG || 'en_US.UTF-8',
// Don't pass PATH - let the shell build it from scratch via .zprofile/.zshrc
};
} else {
// For AI agents in PTY mode: pass full env (they need NODE_PATH, etc.)
ptyEnv = process.env;
}
const ptyProcess = pty.spawn(ptyCommand, ptyArgs, {
name: 'xterm-256color',
cols: 100,
rows: 30,
cwd: cwd,
env: process.env as any,
env: ptyEnv as any,
});
const managedProcess: ManagedProcess = {

View File

@@ -543,6 +543,7 @@ const LogItemComponent = memo(({
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
.prose p:empty { display: none; }
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }
@@ -694,6 +695,7 @@ const LogItemComponent = memo(({
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
.prose p:empty { display: none; }
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }
@@ -827,6 +829,7 @@ const LogItemComponent = memo(({
.prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; }
.prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; }
.prose p:empty { display: none; }
.prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; }
.prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; }
.prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; }

View File

@@ -17,7 +17,7 @@ export interface UseTabCompletionReturn {
/**
* Hook for providing tab completion suggestions from:
* 1. Shell command history
* 2. Current directory file tree
* 2. Current directory file tree (relative to shell CWD)
* 3. Git branches and tags (for git commands in git repos)
*
* Performance optimizations:
@@ -26,10 +26,32 @@ export interface UseTabCompletionReturn {
* - getSuggestions is wrapped in useCallback to maintain referential equality
*/
export function useTabCompletion(session: Session | null): UseTabCompletionReturn {
// Compute relative path from project root (cwd) to shell working directory (shellCwd)
const shellRelativePath = useMemo(() => {
if (!session?.cwd || !session?.shellCwd) return '';
// Normalize paths
const projectRoot = session.cwd.replace(/\/$/, '');
const shellDir = session.shellCwd.replace(/\/$/, '');
// If shell is at project root, no relative path needed
if (shellDir === projectRoot) return '';
// If shell is within project, compute relative path
if (shellDir.startsWith(projectRoot + '/')) {
return shellDir.slice(projectRoot.length + 1);
}
// Shell is outside project root - can't use file tree
return null;
}, [session?.cwd, session?.shellCwd]);
// Build a flat list of file/folder names from the file tree
// Only re-computed when fileTree actually changes
// Filtered to show only files relative to the shell's current working directory
const fileNames = useMemo(() => {
if (!session?.fileTree) return [];
// If shell is outside project, return empty
if (shellRelativePath === null) return [];
const names: { name: string; type: 'file' | 'folder'; path: string }[] = [];
@@ -47,9 +69,31 @@ export function useTabCompletion(session: Session | null): UseTabCompletionRetur
}
};
traverse(session.fileTree);
// If we have a relative path, find that subtree first
if (shellRelativePath) {
const pathParts = shellRelativePath.split('/');
let currentNodes: FileNode[] = session.fileTree;
// Navigate to the shell's current directory in the tree
for (const part of pathParts) {
const found = currentNodes.find(n => n.name === part && n.type === 'folder');
if (found && found.children) {
currentNodes = found.children;
} else {
// Directory not found in tree - return empty
return [];
}
}
// Traverse from the shell's current directory
traverse(currentNodes);
} else {
// Shell is at project root - traverse entire tree
traverse(session.fileTree);
}
return names;
}, [session?.fileTree]);
}, [session?.fileTree, shellRelativePath]);
// Memoize shell history reference to avoid unnecessary getSuggestions re-creation
const shellHistory = useMemo(() => {