mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
feat(web): add chromeless TerminalView and full-screen terminal route
New `/runs/:id/terminal` route renders a bare TerminalView at viewport size, opened outside the AppShell so there is no nav, sidebar, or run detail tabs around it. TerminalView gains a `chromeless` prop that hides the toolbar and decorative wrapper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5ac9a188c6
commit
cb316936e3
3 changed files with 54 additions and 26 deletions
|
|
@ -188,9 +188,11 @@ function StatusPill({
|
|||
export default function TerminalView({
|
||||
runId,
|
||||
leading,
|
||||
chromeless = false,
|
||||
}: {
|
||||
runId: string;
|
||||
leading?: React.ReactNode;
|
||||
chromeless?: boolean;
|
||||
}) {
|
||||
const { push } = useToast();
|
||||
const stateQuery = useRunState(runId);
|
||||
|
|
@ -349,35 +351,38 @@ export default function TerminalView({
|
|||
<section
|
||||
className="flex h-full min-h-0 flex-col"
|
||||
aria-labelledby={headingId}
|
||||
style={chromeless ? { backgroundColor: TERMINAL_BACKGROUND } : undefined}
|
||||
>
|
||||
<h2 id={headingId} className="sr-only">Terminal</h2>
|
||||
<div className="mb-2 flex shrink-0 flex-wrap items-center gap-3">
|
||||
{leading}
|
||||
<StatusPill status={status} detail={sandboxDetail} />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Tooltip label="Reconnect">
|
||||
<button
|
||||
type="button"
|
||||
className={ICON_BUTTON_CLASS}
|
||||
onClick={reconnect}
|
||||
aria-label="Reconnect terminal"
|
||||
>
|
||||
<ArrowPathIcon className="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{accessCommandLabel && (
|
||||
<button
|
||||
type="button"
|
||||
className={SECONDARY_BUTTON_CLASS}
|
||||
onClick={() => void copyAccessCommand()}
|
||||
aria-label={`Copy ${accessCommandLabel} command`}
|
||||
>
|
||||
<ClipboardDocumentIcon className="size-4" aria-hidden="true" />
|
||||
{accessCommandLabel}
|
||||
</button>
|
||||
)}
|
||||
{!chromeless && (
|
||||
<div className="mb-2 flex shrink-0 flex-wrap items-center gap-3">
|
||||
{leading}
|
||||
<StatusPill status={status} detail={sandboxDetail} />
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Tooltip label="Reconnect">
|
||||
<button
|
||||
type="button"
|
||||
className={ICON_BUTTON_CLASS}
|
||||
onClick={reconnect}
|
||||
aria-label="Reconnect terminal"
|
||||
>
|
||||
<ArrowPathIcon className="size-4" aria-hidden="true" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
{accessCommandLabel && (
|
||||
<button
|
||||
type="button"
|
||||
className={SECONDARY_BUTTON_CLASS}
|
||||
onClick={() => void copyAccessCommand()}
|
||||
aria-label={`Copy ${accessCommandLabel} command`}
|
||||
>
|
||||
<ClipboardDocumentIcon className="size-4" aria-hidden="true" />
|
||||
{accessCommandLabel}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{error ? (
|
||||
<div className="flex min-h-0 flex-1 items-center justify-center" role="alert">
|
||||
<ErrorState
|
||||
|
|
@ -386,6 +391,8 @@ export default function TerminalView({
|
|||
onRetry={error.recoverable ? reconnect : undefined}
|
||||
/>
|
||||
</div>
|
||||
) : chromeless ? (
|
||||
<div ref={terminalEl} className="h-full min-h-0 p-3" />
|
||||
) : (
|
||||
<div
|
||||
className="min-h-0 flex-1 overflow-hidden rounded border border-line pb-3"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import * as RunEvents from "./routes/run-events";
|
|||
import * as RunArtifacts from "./routes/run-artifacts";
|
||||
import * as RunFiles from "./routes/run-files";
|
||||
import * as RunSandbox from "./routes/run-sandbox";
|
||||
import * as RunTerminal from "./routes/run-terminal";
|
||||
import * as RunBilling from "./routes/run-billing";
|
||||
import * as Insights from "./routes/insights";
|
||||
import * as InsightsEditor from "./routes/insights-editor";
|
||||
|
|
@ -75,6 +76,7 @@ export const routes: RouteObject[] = [
|
|||
indexRoute(RedirectHome),
|
||||
route("setup", Setup),
|
||||
route("login", AuthLogin),
|
||||
route("runs/:id/terminal", RunTerminal),
|
||||
{
|
||||
Component: withRouteModule({
|
||||
default: AppShellModule,
|
||||
|
|
|
|||
19
apps/fabro-web/app/routes/run-terminal.tsx
Normal file
19
apps/fabro-web/app/routes/run-terminal.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
import TerminalView from "../components/terminal-view";
|
||||
|
||||
export default function RunTerminal({ params }: { params: { id: string } }) {
|
||||
useEffect(() => {
|
||||
const previous = document.title;
|
||||
document.title = `Terminal · ${params.id} · Fabro`;
|
||||
return () => {
|
||||
document.title = previous;
|
||||
};
|
||||
}, [params.id]);
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen overflow-hidden">
|
||||
<TerminalView runId={params.id} chromeless />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue