Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a21aeaf
Share dashboard panel frame
matthewlouisbrockman May 27, 2026
c954aee
Move xterm styles into terminal component
matthewlouisbrockman May 27, 2026
171563e
Render terminal in dashboard panel frame
matthewlouisbrockman May 27, 2026
dc9d1e9
Use type-only terminal SDK imports
matthewlouisbrockman May 27, 2026
7ec924d
Keep sandbox terminals out of stored sessions
matthewlouisbrockman May 27, 2026
ae2b295
Make terminal restart action caller-controlled
matthewlouisbrockman May 27, 2026
e104b6c
Retry timed-out sandbox terminal attaches
matthewlouisbrockman May 27, 2026
0efbfb4
Debounce terminal autostart
matthewlouisbrockman May 27, 2026
0672776
Reset terminal input queue on disconnect
matthewlouisbrockman May 27, 2026
b6af83c
Show missing terminal sandbox access copy
matthewlouisbrockman May 27, 2026
c4257da
Add terminal tab to sandbox details
matthewlouisbrockman May 27, 2026
5fcd614
Add terminal tab route boundaries
matthewlouisbrockman May 27, 2026
9195326
Extract terminal instance hook
matthewlouisbrockman May 27, 2026
e61ed34
Remove sandbox inspect frame shim
matthewlouisbrockman May 27, 2026
2c9d146
Use dashboard panel frame directly
matthewlouisbrockman May 27, 2026
56ae8bf
Keep shared frame named for sandbox inspect
matthewlouisbrockman May 27, 2026
2b38566
Extract terminal attach retry helper
matthewlouisbrockman May 27, 2026
19faacd
Allow zero-delay terminal attach retries
matthewlouisbrockman May 27, 2026
2165839
Use bounded terminal attach backoff
matthewlouisbrockman May 28, 2026
9d6b490
Pass terminal launch options as a target
matthewlouisbrockman May 28, 2026
3c962d0
Resume paused sandbox inspect views for five minutes
matthewlouisbrockman May 28, 2026
2777f17
Use sandbox context in terminal tab
matthewlouisbrockman May 28, 2026
d6f7003
Reset sandbox terminal resume after attach failure
matthewlouisbrockman May 28, 2026
5ce2360
Kill terminal PTYs on teardown
matthewlouisbrockman May 28, 2026
320df06
Batch terminal input writes
matthewlouisbrockman May 28, 2026
3f4a7f7
Use terminal copy for terminal empty state
matthewlouisbrockman May 28, 2026
e1a2b96
Use sandbox context for terminal lifecycle
matthewlouisbrockman May 28, 2026
8d9f1dd
Batch terminal input and split panel header
matthewlouisbrockman May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client'

import { DashboardRouteError } from '@/features/dashboard/shared/route-error'

export default function SandboxTerminalPageError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return <DashboardRouteError error={error} reset={reset} />
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@/features/dashboard/loading-layout'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SandboxTerminalView from '@/features/dashboard/sandbox/terminal/view'

export default async function SandboxTerminalPage({
params,
}: {
params: Promise<{ teamSlug: string; sandboxId: string }>
}) {
const { sandboxId } = await params

return <SandboxTerminalView sandboxId={sandboxId} />
}
Comment thread
cursor[bot] marked this conversation as resolved.
9 changes: 0 additions & 9 deletions src/app/dashboard/terminal/layout.tsx

This file was deleted.

10 changes: 9 additions & 1 deletion src/app/dashboard/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ export default async function TerminalPage({
: teamsResult.data.find((candidate) => candidate.id === resolvedTeam?.id)

if (!team) {
return <TerminalUnavailable />
return (
<TerminalUnavailable
message={
terminalSandboxId
? 'Sandbox not found or you do not have access to it.'
: undefined
}
/>
)
}

const templateAvailable = terminalSandboxId
Expand Down
2 changes: 2 additions & 0 deletions src/configs/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export const PROTECTED_URLS = {
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/events`,
SANDBOX_LOGS: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/logs`,
SANDBOX_TERMINAL: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/terminal`,
SANDBOX_FILESYSTEM: (teamSlug: string, sandboxId: string) =>
`/dashboard/${teamSlug}/sandboxes/${sandboxId}/filesystem`,

Expand Down
2 changes: 1 addition & 1 deletion src/features/dashboard/sandbox/inspect/filesystem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import LoadingLayout from '@/features/dashboard/loading-layout'
import SandboxInspectFilesystemHeader from '@/features/dashboard/sandbox/inspect/filesystem-header'
import { SandboxInspectFrame } from '@/features/dashboard/shared'
import { ScrollArea } from '@/ui/primitives/scroll-area'
import { useSandboxContext } from '../context'
import SandboxInspectFrame from './frame'
import { useDirectoryState } from './hooks/use-directory'
import { useRootChildren } from './hooks/use-node'
import SandboxInspectNode from './node'
Expand Down
2 changes: 1 addition & 1 deletion src/features/dashboard/sandbox/inspect/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AnimatePresence } from 'motion/react'
import { useEffect, useState } from 'react'
import ShikiHighlighter, { type Language } from 'react-shiki'
import { useShikiTheme } from '@/configs/shiki'
import { SandboxInspectFrame } from '@/features/dashboard/shared'
import { useIsMobile } from '@/lib/hooks/use-mobile'
import { cn } from '@/lib/utils'
import { Button } from '@/ui/primitives/button'
Expand All @@ -16,7 +17,6 @@ import {
type UnreadableFileTypeContentState,
type UnreadableTooLargeContentState,
} from './filesystem/store'
import SandboxInspectFrame from './frame'
import { useContent } from './hooks/use-content'
import { useFile } from './hooks/use-file'
import { useErrorPaths, useSelectedPath } from './hooks/use-node'
Expand Down
7 changes: 7 additions & 0 deletions src/features/dashboard/sandbox/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
HistoryIcon,
ListIcon,
StorageIcon,
TerminalCustomIcon,
TrendIcon,
} from '@/ui/primitives/icons'
import { useSandboxContext } from './context'
Expand Down Expand Up @@ -68,6 +69,12 @@ export default function SandboxLayout({
href: PROTECTED_URLS.SANDBOX_LOGS(teamSlug, sandboxId),
icon: <ListIcon className="size-4" />,
},
{
id: 'terminal',
label: 'Terminal',
href: PROTECTED_URLS.SANDBOX_TERMINAL(teamSlug, sandboxId),
icon: <TerminalCustomIcon className="size-4" />,
},
{
id: 'filesystem',
label: 'Filesystem',
Expand Down
25 changes: 25 additions & 0 deletions src/features/dashboard/sandbox/terminal/view.tsx
Comment thread
ben-fornefeld marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client'

import { useDashboard } from '@/features/dashboard/context'
import DashboardTerminal from '@/features/dashboard/terminal/dashboard-terminal'

interface SandboxTerminalViewProps {
sandboxId: string
}

export default function SandboxTerminalView({
sandboxId,
}: SandboxTerminalViewProps) {
const { team } = useDashboard()

return (
<div className="flex min-h-0 flex-1 overflow-hidden p-3 md:p-6">
<DashboardTerminal
autoStart
initialSandboxId={sandboxId}
sandboxScoped
teamId={team.id}
/>
Comment thread
cursor[bot] marked this conversation as resolved.
</div>
)
}
1 change: 1 addition & 0 deletions src/features/dashboard/shared/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { IdBadge } from './id-badge'
export { SandboxInspectFrame } from './panel-frame'
export { UserAvatar } from './user-avatar'
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type SandboxInspectFrameProps = React.ComponentProps<typeof motion.div> & {
}
}

export default function SandboxInspectFrame({
export function SandboxInspectFrame({
className,
classNames,
children,
Expand Down
43 changes: 43 additions & 0 deletions src/features/dashboard/terminal/attach-terminal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
interface AttachTerminalWithRetryOptions<TResult> {
canRetry: boolean
isCurrent: () => boolean
isRetryableError: (error: unknown) => boolean
onRetry: (delayMs: number) => void
open: () => Promise<TResult | null>
retryDelaysMs: readonly number[]
waitForRetry: (delayMs: number) => Promise<void>
}

export async function attachTerminalWithRetry<TResult>({
canRetry,
isCurrent,
isRetryableError,
onRetry,
open,
retryDelaysMs,
waitForRetry,
}: AttachTerminalWithRetryOptions<TResult>) {
let attachAttempt = 0

while (true) {
try {
return await open()
} catch (error) {
const retryDelay = retryDelaysMs[attachAttempt]
if (
!canRetry ||
!retryDelay ||
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
!isCurrent() ||
!isRetryableError(error)
) {
throw error
}

attachAttempt += 1
onRetry(retryDelay)
await waitForRetry(retryDelay)

if (!isCurrent()) return null
}
}
}
3 changes: 3 additions & 0 deletions src/features/dashboard/terminal/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ export const DEFAULT_PANEL_HEIGHT = 260
export const MAX_TERMINAL_TRANSCRIPT_CHARS = 200_000
export const TERMINAL_SESSION_STORAGE_PREFIX = 'dashboard-terminal-session'
export const DEFAULT_CWD = '/home/user'
export const TERMINAL_AUTOSTART_DEBOUNCE_MS = 300
export const TERMINAL_ATTACH_ATTEMPT_TIMEOUT_MS = 15_000
export const TERMINAL_ATTACH_RETRY_DELAYS_MS = [1500, 3000, 5000] as const
Loading
Loading