Skip to content

Commit 83b6cef

Browse files
davindicodeclaude
andcommitted
Context-aware terminal actions, cd picker, file loading UX, typecheck fix
- Terminal InputBox: context-aware tabs swap based on mode (editor/tmux/terminal) - nano/vim: show editor commands when inside, file opener when outside - tmux: show commands when inside, session picker when outside - Editor mode hides irrelevant tabs (cmds, tmux, other editor) - Nav keys reduced in editor mode (no y/n/q that interfere) - Sticky modifier tab: Ctrl+, Ctrl+Shift+, Alt+, Alt+Shift+ toggle - cd directory picker in cmds: browsable folder buttons, .. and ~ shortcuts - Tab buttons teal-tinted to distinguish from popup action buttons - Track inTmux/inEditor per terminal session in store - File explorer: spinner during directory and file loading - File open errors: meaningful message with size info and Back button - File path: mr-2 margin, shrink-0 on all button containers, title attr - Terminal scrollbar: always visible with styled thumb - Fix typecheck: cast req to any for originalUrl in proxy pathRewrite Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a075f9c commit 83b6cef

8 files changed

Lines changed: 558 additions & 195 deletions

File tree

app/app.css

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,24 @@ body {
2828
}
2929

3030
.xterm-viewport {
31-
overflow-y: auto !important;
31+
overflow-y: scroll !important;
32+
}
33+
34+
.xterm-viewport::-webkit-scrollbar {
35+
width: 8px;
36+
}
37+
38+
.xterm-viewport::-webkit-scrollbar-track {
39+
background: transparent;
40+
}
41+
42+
.xterm-viewport::-webkit-scrollbar-thumb {
43+
background: rgba(255, 255, 255, 0.15);
44+
border-radius: 4px;
45+
}
46+
47+
.xterm-viewport::-webkit-scrollbar-thumb:hover {
48+
background: rgba(255, 255, 255, 0.3);
3249
}
3350

3451
/* Safe area for iOS bottom bar */

app/components/files/CodeEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export default function CodeEditor({ path, content, onSave, onClose }: CodeEdito
223223
<div className="flex flex-col h-full">
224224
{/* Toolbar */}
225225
<div className="flex items-center gap-2 px-3 py-1.5 bg-[#16162a] border-b border-gray-700 shrink-0">
226-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none" title={path}>{path}</span>
226+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none mr-2" title={path}>{path}</span>
227227
<div className="flex items-center gap-1.5 shrink-0">
228228
{canPreview && (
229229
<div className="flex items-center bg-[#0d0d1a] rounded overflow-hidden border border-gray-700">

app/components/files/FileViewer.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function MediaViewer({ path, type, onClose }: { path: string; type: "video" | "a
2727
return (
2828
<div className="flex flex-col h-full">
2929
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
30-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none">{path}</span>
30+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none mr-2" title={path}>{path}</span>
3131
<div className="flex items-center gap-2 shrink-0">
3232
<a
3333
href={`/api/files/download?path=${encodeURIComponent(path)}`}
@@ -75,8 +75,8 @@ function PdfViewer({ path, onClose }: { path: string; onClose: () => void }) {
7575
return (
7676
<div className="flex flex-col h-full">
7777
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
78-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none">{path}</span>
79-
<div className="flex items-center gap-2">
78+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none mr-2" title={path}>{path}</span>
79+
<div className="flex items-center gap-2 shrink-0">
8080
{numPages > 0 && (
8181
<span className="text-[10px] text-gray-500">{numPages} pg</span>
8282
)}
@@ -158,8 +158,8 @@ export default function FileViewer({ path, content, onSave, onClose }: FileViewe
158158
return (
159159
<div className="flex flex-col h-full">
160160
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
161-
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none">{path}</span>
162-
<div className="flex items-center gap-2">
161+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none mr-2" title={path}>{path}</span>
162+
<div className="flex items-center gap-2 shrink-0">
163163
<a
164164
href={`/api/files/download?path=${encodeURIComponent(path)}`}
165165
className="p-1 bg-gray-700 hover:bg-gray-600 text-gray-300 hover:text-white rounded transition-colors"

app/components/files/FilesPage.tsx

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,23 +106,36 @@ function FileSessionView({ session }: { session: FileSession }) {
106106
}, []);
107107

108108
const { cwd, entries, showHidden, selectedFile, fileContent, loading } = session;
109+
const [fileLoading, setFileLoading] = useState(false);
110+
const [fileError, setFileError] = useState<string | null>(null);
109111

110112
const fullPath = (name: string) => (cwd === "/" ? `/${name}` : `${cwd}/${name}`);
111113

112114
const handleOpen = async (entry: FileEntry) => {
113115
if (entry.isDirectory) {
114116
await loadDirectory(fullPath(entry.name));
115117
patch({ selectedFile: null, fileContent: null });
118+
setFileError(null);
116119
} else {
117120
const filePath = fullPath(entry.name);
118-
patch({ selectedFile: filePath });
121+
patch({ selectedFile: filePath, fileContent: null });
122+
setFileLoading(true);
123+
setFileError(null);
119124
try {
120125
const res = await fetch(`/api/files/read?path=${encodeURIComponent(filePath)}`);
121126
const data = await res.json();
122-
patch({ fileContent: data.content !== undefined ? data.content : null });
123-
} catch {
127+
if (data.error) {
128+
const sizeInfo = data.size ? ` (${formatSize(data.size)})` : "";
129+
setFileError(`${data.error}${sizeInfo}`);
130+
patch({ fileContent: null });
131+
} else {
132+
patch({ fileContent: data.content !== undefined ? data.content : null });
133+
}
134+
} catch (err: any) {
135+
setFileError(err.message || "Failed to load file");
124136
patch({ fileContent: null });
125137
}
138+
setFileLoading(false);
126139
}
127140
};
128141

@@ -150,7 +163,7 @@ function FileSessionView({ session }: { session: FileSession }) {
150163
}
151164
};
152165

153-
const handleCloseFile = () => patch({ selectedFile: null, fileContent: null });
166+
const handleCloseFile = () => { patch({ selectedFile: null, fileContent: null }); setFileError(null); };
154167

155168
const toggleHidden = (val: boolean) => {
156169
patch({ showHidden: val });
@@ -209,6 +222,35 @@ function FileSessionView({ session }: { session: FileSession }) {
209222
const handleInfo = (entry: FileEntry) => setDialog({ type: "info", entry });
210223

211224
if (selectedFile) {
225+
if (fileLoading) {
226+
return (
227+
<div className="flex flex-col items-center justify-center h-full bg-[#0d0d1a] text-gray-500 gap-3">
228+
<svg className="w-8 h-8 animate-spin text-blue-500" fill="none" viewBox="0 0 24 24">
229+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
230+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
231+
</svg>
232+
<span className="text-sm">Opening file...</span>
233+
<span className="text-xs text-gray-600 overflow-hidden text-ellipsis max-w-xs">{selectedFile}</span>
234+
</div>
235+
);
236+
}
237+
if (fileError) {
238+
return (
239+
<div className="flex flex-col items-center justify-center h-full bg-[#0d0d1a] text-gray-400 gap-3">
240+
<svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" strokeWidth={1.5}>
241+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" />
242+
</svg>
243+
<span className="text-sm text-red-400">{fileError}</span>
244+
<span className="text-xs text-gray-600 overflow-hidden text-ellipsis max-w-xs">{selectedFile}</span>
245+
<button
246+
onClick={handleCloseFile}
247+
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded text-sm transition-colors"
248+
>
249+
Back
250+
</button>
251+
</div>
252+
);
253+
}
212254
return (
213255
<FileViewer
214256
path={selectedFile}
@@ -251,7 +293,13 @@ function FileSessionView({ session }: { session: FileSession }) {
251293
</div>
252294

253295
{loading ? (
254-
<div className="flex items-center justify-center flex-1 text-gray-500 text-sm">Loading...</div>
296+
<div className="flex flex-col items-center justify-center flex-1 text-gray-500 gap-2">
297+
<svg className="w-6 h-6 animate-spin text-blue-500" fill="none" viewBox="0 0 24 24">
298+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
299+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
300+
</svg>
301+
<span className="text-sm">Loading...</span>
302+
</div>
255303
) : (
256304
<FileList entries={entries} onOpen={handleOpen} onDelete={handleDelete} onInfo={handleInfo} onRename={handleRename} />
257305
)}

app/components/files/ImageViewer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export default function ImageViewer({ path, onClose }: ImageViewerProps) {
3434
return (
3535
<div className="flex flex-col h-full">
3636
<div className="flex items-center justify-between px-3 py-2 bg-[#16162a] border-b border-gray-700 shrink-0">
37-
<span className="text-sm text-gray-300 overflow-x-auto scrollbar-none whitespace-nowrap min-w-0 flex-1 select-all">{path}</span>
37+
<span className="text-sm text-gray-300 whitespace-nowrap min-w-0 flex-1 overflow-hidden text-ellipsis select-none mr-2" title={path}>{path}</span>
3838
<div className="flex items-center gap-2 shrink-0">
3939
{/* Zoom controls */}
4040
<div className="flex items-center gap-1 border border-gray-700 rounded overflow-hidden">

0 commit comments

Comments
 (0)