Workspaces and Terminals

This commit is contained in:
2026-02-19 18:01:08 +00:00
parent 9870fa7ae8
commit 4733a972dd
14 changed files with 402 additions and 57 deletions
@@ -3,23 +3,26 @@ import { ChevronRight, Home } from 'lucide-react';
type BreadcrumbProps = {
path: string;
onNavigate: (path: string) => void;
basePath?: string;
};
export const Breadcrumb = ({ path, onNavigate }: BreadcrumbProps) => {
const segments = path.split('/').filter(Boolean);
export const Breadcrumb = ({ path, onNavigate, basePath = '/' }: BreadcrumbProps) => {
const relativePath = basePath !== '/' && path.startsWith(basePath) ? path.slice(basePath.length) : path;
const segments = relativePath.split('/').filter(Boolean);
return (
<nav className="flex items-center gap-1 text-sm flex-wrap">
<button
onClick={() => onNavigate('/')}
onClick={() => onNavigate(basePath)}
className="flex items-center gap-1 text-duck-forest hover:text-duck-teal transition-colors cursor-pointer font-medium"
>
<Home className="h-4 w-4" />
<span>home</span>
<span>{basePath === '/' ? 'home' : basePath.split('/').pop()}</span>
</button>
{segments.map((segment, i) => {
const segmentPath = '/' + segments.slice(0, i + 1).join('/');
const relative = '/' + segments.slice(0, i + 1).join('/');
const segmentPath = basePath === '/' ? relative : basePath + relative;
const isLast = i === segments.length - 1;
return (
+16 -7
View File
@@ -18,6 +18,7 @@ export type TerminalViewProps = {
wsPath?: string;
sessionId?: string;
sandboxed?: boolean;
cwd?: string;
fontSize?: number;
fontFamily?: string;
theme?: TerminalTheme;
@@ -34,13 +35,14 @@ const DEFAULT_THEME: Required<TerminalTheme> = {
selectionBackground: '#3a3a5e',
};
const buildWsUrl = (wsPath: string, sessionId?: string, sandboxed?: boolean) => {
const buildWsUrl = (wsPath: string, sessionId?: string, sandboxed?: boolean, cwd?: string) => {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const token = localStorage.getItem('BEARER_TOKEN') ?? '';
const separator = wsPath.includes('?') ? '&' : '?';
let url = `${protocol}//${window.location.host}${wsPath}${separator}token=${encodeURIComponent(token)}`;
if (sessionId) url += `&sessionId=${encodeURIComponent(sessionId)}`;
if (sandboxed === false) url += '&sandboxed=false';
if (cwd) url += `&cwd=${encodeURIComponent(cwd)}`;
return url;
};
@@ -50,6 +52,7 @@ export const TerminalView = ({
wsPath = '/api/terminal/ws',
sessionId,
sandboxed = true,
cwd,
fontSize = 14,
fontFamily = 'Menlo, Monaco, "Courier New", monospace',
theme,
@@ -99,6 +102,13 @@ export const TerminalView = ({
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(container);
const viewport = container.querySelector('.xterm-viewport') as HTMLElement | null;
if (viewport) {
viewport.style.scrollbarWidth = 'none';
viewport.style.overflow = 'hidden';
}
fitAddon.fit();
if (autoFocus) term.focus();
@@ -106,7 +116,7 @@ export const TerminalView = ({
fitAddonRef.current = fitAddon;
onReadyRef.current?.(term);
const ws = new WebSocket(buildWsUrl(wsPath, sessionId, sandboxed));
const ws = new WebSocket(buildWsUrl(wsPath, sessionId, sandboxed, cwd));
wsRef.current = ws;
const handleOpen = () => {
@@ -180,6 +190,7 @@ export const TerminalView = ({
wsPath,
sessionId,
sandboxed,
cwd,
fontSize,
fontFamily,
background,
@@ -190,10 +201,8 @@ export const TerminalView = ({
]);
return (
<div
ref={containerRef}
className={className}
style={{ backgroundColor: background, ...style }}
/>
<div className={className} style={{ backgroundColor: background, overflow: 'hidden', ...style }}>
<div ref={containerRef} style={{ width: '100%', height: '100%' }} />
</div>
);
};