Skip to content

refactor(desktop): Split Terminal component into smaller modules#838

Merged
Kitenite merged 4 commits into
mainfrom
refactor-terminal
Jan 19, 2026
Merged

refactor(desktop): Split Terminal component into smaller modules#838
Kitenite merged 4 commits into
mainfrom
refactor-terminal

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 19, 2026

Summary

  • Refactor the large Terminal.tsx component (~1500 lines) into smaller, focused modules
  • Extract overlay components (SessionKilledOverlay, ConnectionErrorOverlay, RestoredModeOverlay)
  • Extract custom hooks for specific concerns (CWD, modes, file links, restore, stream, cold restore)
  • Move types, constants, and module-level state to dedicated files
  • Terminal.tsx reduced from ~1500 lines to ~685 lines (~55% reduction)

Changes

New Components:

  • SessionKilledOverlay - UI overlay for killed session state
  • ConnectionErrorOverlay - UI overlay for connection error state
  • RestoredModeOverlay - UI overlay for cold restore state

New Hooks:

  • useTerminalCwd - CWD state management and OSC-7 parsing
  • useTerminalModes - Alternate screen and bracketed paste tracking
  • useFileLinkClick - File link click handling
  • useTerminalRestore - Terminal state restoration from snapshots
  • useTerminalStream - Stream event handling (data, exit, error)
  • useTerminalColdRestore - Cold restore (reboot recovery) handling

New/Updated Files:

  • state.ts - Module-level maps for pendingDetaches and coldRestoreState
  • config.ts - Added DEBUG_TERMINAL and FIRST_RENDER_RESTORE_FALLBACK_MS constants
  • types.ts - Added CreateOrAttachResult and ColdRestoreState types

Test plan

  • Verify terminal opens and works normally
  • Test terminal session kill and restart
  • Test connection error and retry flow
  • Test cold restore (app restart) recovery
  • Verify file links work (both external editor and file viewer modes)
  • Test CWD tracking via OSC-7 sequences
  • Verify alternate screen mode (vim, less) works correctly
  • Test paste handling in both normal and bracketed paste modes

Summary by CodeRabbit

  • New Features

    • Terminal: dedicated overlays for connection errors, restored sessions, and killed sessions with retry/restart actions.
    • Terminal: clickable file links to open files in-app or external editor.
    • Settings: system theme option and copy-to-clipboard for app version.
    • Workspace: clickable repository/workspace paths with quick-open and copy actions.
  • Refactor

    • Major terminal internals re-architected for modularity and more robust restore/stream handling.
  • UI Improvements

    • Diff viewer: new diff layout toggle and editor remount stability.
    • Sidebar: additional header controls.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 19, 2026

Warning

Rate limit exceeded

@Kitenite has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 19 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 06b7a56 and efaf93a.

📒 Files selected for processing (18)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/ConnectionErrorOverlay/ConnectionErrorOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/ConnectionErrorOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/RestoredModeOverlay/RestoredModeOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/RestoredModeOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/SessionKilledOverlay/SessionKilledOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/SessionKilledOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useFileLinkClick.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalColdRestore.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalCwd.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalModes.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalRestore.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/state.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts
📝 Walkthrough

Walkthrough

Refactors the Terminal internals: extracts three overlay components, adds config constants and types/state, and moves terminal lifecycle, connection, restore, stream, CWD, and mode logic into multiple new hooks and state utilities while preserving the Terminal component's external signature.

Changes

Cohort / File(s) Summary
Terminal core
apps/desktop/.../Terminal/Terminal.tsx
Rewrote internal implementation to use new hooks (useTerminalStream, useTerminalRestore, useTerminalColdRestore, useTerminalCwd, useTerminalModes, useFileLinkClick); replaced inline overlays with components; imported config constants; removed module-level ad-hoc state in favor of hooks/refs.
Overlay components
apps/desktop/.../Terminal/components/ConnectionErrorOverlay/*, .../RestoredModeOverlay/*, .../SessionKilledOverlay/*, .../components/index.ts
Added three presentational overlays (ConnectionErrorOverlay, RestoredModeOverlay, SessionKilledOverlay) and an index re-export.
Terminal hooks & types
apps/desktop/.../Terminal/hooks/*, apps/desktop/.../Terminal/types.ts, apps/desktop/.../Terminal/state.ts, apps/desktop/.../Terminal/config.ts
Added many hooks (stream, restore, cold-restore, cwd, modes, file-link); exported hook index; introduced CreateOrAttachResult and ColdRestoreState types; added pendingDetaches and coldRestoreState maps; added FIRST_RENDER_RESTORE_FALLBACK_MS and DEBUG_TERMINAL constants.
File link behavior
apps/desktop/.../Terminal/hooks/useFileLinkClick.ts, hooks/index.ts
New hook to handle file link clicks with workspace-relative resolution, built-in file viewer vs external editor branching, TRPC mutation usage, and re-exports.
Settings/UI additions
apps/desktop/.../settings/* (AccountSettings, AppearanceSettings, SystemThemeCard, ClickablePath)
Added copy-version button in AccountSettings; added SystemThemeCard and SYSTEM_THEME_ID; added ClickablePath component and usages.
File viewer / diff tooling
apps/desktop/.../FileViewerPane/*, FileViewerContent, FileViewerToolbar, useFileContent
Propagated diffViewMode state; added diff view toggle in toolbar; removed isSaving/showEditableBadge props; adjusted diff refs and hook logic.
Minor UI/styling tweaks
PortsList/*, ChangesHeader/*, DiffViewer/*
Small styling/icon changes, added sidebar controls, and added key/Monaco option to DiffEditor.

Sequence Diagram(s)

sequenceDiagram
    participant Terminal as Terminal Component
    participant StreamHook as useTerminalStream
    participant RestoreHook as useTerminalRestore
    participant ColdHook as useTerminalColdRestore
    participant XTerm as XTerm Instance
    participant Main as Main Process / IPC

    Terminal->>StreamHook: init(stream refs, handlers)
    Terminal->>RestoreHook: init(pending state refs)
    Terminal->>ColdHook: init(recovery refs)

    Main-->>StreamHook: stream event (data / exit / error)
    StreamHook->>XTerm: write data (or queue if not ready)
    StreamHook->>Terminal: update modes / cwd refs

    alt connection error
        StreamHook->>Terminal: set connection error
        Terminal->>ColdHook: handleRetryConnection()
    else session killed
        StreamHook->>Terminal: set exit/killed
        Terminal->>ColdHook: handleRetryConnection()
    end

    ColdHook->>Main: createOrAttach (restore)
    Main-->>ColdHook: CreateOrAttachResult (possibly with snapshot)
    ColdHook->>RestoreHook: maybeApplyInitialState(snapshot)
    RestoreHook->>XTerm: apply snapshot content & modes
    RestoreHook->>XTerm: fitAddon.fit()
    Terminal->>Terminal: show RestoredModeOverlay
    Terminal->>ColdHook: handleStartShell()
    ColdHook->>Main: acknowledge & create new session
Loading
sequenceDiagram
    participant Terminal as Terminal Component
    participant CwdHook as useTerminalCwd
    participant ModesHook as useTerminalModes
    participant StreamHook as useTerminalStream
    participant Tabs as Tabs Store

    Terminal->>CwdHook: init(initialCwd, workspaceCwd)
    StreamHook-->>CwdHook: on data: updateCwdFromData(data)
    CwdHook->>Tabs: updatePaneCwd (debounced)
    StreamHook-->>ModesHook: on data: updateModesFromData(data)
    ModesHook->>ModesHook: track alt-screen / bracketed-paste refs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

Poem

🐰 I hopped through lines both old and new,

Extracted hooks and overlays too,
Streams now queue and restores behave,
Terminal’s tidy, stable, brave —
A carrot-shaped nod to code we drew! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(desktop): Split Terminal component into smaller modules' directly matches the main change: a large Terminal.tsx component is refactored into smaller, focused modules (overlays and hooks).
Description check ✅ Passed The PR description is well-structured with a clear Summary, detailed Changes section listing new components and hooks, new/updated files, and a comprehensive Test plan. It meets the template requirements with a proper Description section and includes related context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useFileLinkClick.ts`:
- Around line 71-88: The normalization logic in useFileLinkClick treats only
POSIX absolutes and can mis-handle Windows drive (e.g., C:\...) and UNC
(\\server\share) or file:// URIs, causing false relative paths; update the
branch around workspaceCwd/filePath handling to first normalize separators and
detect Windows/UNC/file-URI absolute forms (drive-letter with colon + backslash,
leading double backslashes, and file://) before comparing to workspaceCwd, and
if such paths are outside the workspace call toast.warning (same message) and
return; ensure addFileViewerPane is only called with truly workspace-relative
filePath.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalModes.ts`:
- Around line 43-50: The code in useTerminalModes.ts currently only checks for
1049 and 47 alternate-screen sequences; update the enterAltIndex and
exitAltIndex calculations to also consider 1047 by including
combined.lastIndexOf("\x1b[?1047h") in the enterAltIndex max and
combined.lastIndexOf("\x1b[?1047l") in the exitAltIndex max so the hook
(useTerminalModes / isAlternateScreenRef logic) correctly handles terminals/apps
that toggle alternate screen with 1047 sequences.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalStream.ts`:
- Around line 22-157: Two exported callbacks in useTerminalStream
(handleTerminalExit and handleStreamError) take multiple positional params;
update their signatures to accept single param objects (e.g.,
handleTerminalExit({ exitCode, xterm }) and handleStreamError({ event, xterm }))
and update all callers to pass a single object. Change the corresponding handler
type in UseTerminalStreamReturn, update useTerminalRestore's callbacks and
flushPendingEvents to enqueue/dispatch the new object-shaped events, and update
Terminal.tsx call sites to call these handlers with the params object; ensure
pendingEventsRef handling still pushes the same event objects for later
processing.
🧹 Nitpick comments (8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/ConnectionErrorOverlay/ConnectionErrorOverlay.tsx (1)

13-28: Add dialog semantics for screen readers.
The overlay behaves like a modal but lacks role, aria-modal, and labeling. This makes it harder for assistive tech users to understand context.

♿ Proposed a11y tweak
-		<div className="absolute inset-0 z-10 flex items-center justify-center bg-black/50">
+		<div
+			className="absolute inset-0 z-10 flex items-center justify-center bg-black/50"
+			role="alertdialog"
+			aria-modal="true"
+			aria-labelledby="terminal-connection-error-title"
+			aria-describedby="terminal-connection-error-desc"
+		>
 			<Card className="gap-3 py-4 px-2 max-w-xs">
 				<div className="flex flex-col items-center text-center gap-1.5 px-4">
 					<HiExclamationTriangle className="size-5 text-destructive" />
 					<div className="space-y-0.5">
-						<p className="text-sm font-medium">Connection error</p>
-						<p className="text-xs text-muted-foreground">
+						<p id="terminal-connection-error-title" className="text-sm font-medium">
+							Connection error
+						</p>
+						<p id="terminal-connection-error-desc" className="text-xs text-muted-foreground">
 							Lost connection to terminal daemon
 						</p>
 					</div>
 				</div>
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/SessionKilledOverlay/SessionKilledOverlay.tsx (1)

11-25: Add dialog semantics for screen readers.
This is a blocking overlay; add dialog semantics and label it so assistive tech can announce the context.

♿ Proposed a11y tweak
-		<div className="absolute inset-0 z-10 flex items-center justify-center bg-black/50">
+		<div
+			className="absolute inset-0 z-10 flex items-center justify-center bg-black/50"
+			role="alertdialog"
+			aria-modal="true"
+			aria-labelledby="terminal-session-killed-title"
+			aria-describedby="terminal-session-killed-desc"
+		>
 			<Card className="gap-3 py-4 px-2">
 				<div className="flex flex-col items-center text-center gap-1.5 px-4">
 					<LuPower className="size-5 text-muted-foreground" />
 					<div className="space-y-0.5">
-						<p className="text-sm font-medium">Session killed</p>
-						<p className="text-xs text-muted-foreground">
+						<p id="terminal-session-killed-title" className="text-sm font-medium">
+							Session killed
+						</p>
+						<p id="terminal-session-killed-desc" className="text-xs text-muted-foreground">
 							You terminated this shell session
 						</p>
 					</div>
 				</div>
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalModes.ts (1)

62-63: Extract the tail length magic number.
Pull 32 into a named constant to make intent clear and to tune in one place.

♻️ Proposed refactor
+const MODE_SCAN_TAIL_CHARS = 32;
+
 export function useTerminalModes(): UseTerminalModesReturn {
@@
-		modeScanBufferRef.current = combined.slice(-32);
+		modeScanBufferRef.current = combined.slice(-MODE_SCAN_TAIL_CHARS);
 	}, []);

As per coding guidelines, extract magic numbers to named constants.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/components/RestoredModeOverlay/RestoredModeOverlay.tsx (1)

12-30: Consider adding accessibility attributes for modal-like overlay.

The overlay functions as a modal dialog blocking interaction with the terminal behind it. For improved screen reader support, consider adding role="dialog" and aria-modal="true" to the container, and aria-labelledby pointing to the title.

♿ Suggested accessibility improvement
 	return (
-		<div className="absolute inset-0 z-10 flex items-center justify-center bg-black/50">
-			<Card className="gap-3 py-4 px-2">
+		<div
+			className="absolute inset-0 z-10 flex items-center justify-center bg-black/50"
+			role="dialog"
+			aria-modal="true"
+			aria-labelledby="restored-mode-title"
+		>
+			<Card className="gap-3 py-4 px-2">
 				<div className="flex flex-col items-center text-center gap-1.5 px-4">
 					<LuTerminal className="size-5 text-primary" />
 					<div className="space-y-0.5">
-						<p className="text-sm font-medium">Session restored</p>
+						<p id="restored-mode-title" className="text-sm font-medium">Session restored</p>
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalCwd.ts (2)

47-51: Consider extracting the debounce delay to a named constant.

The 150ms debounce delay is a magic number. Per coding guidelines, consider extracting it to config.ts alongside RESIZE_DEBOUNCE_MS (which is also 150ms) for consistency and discoverability.

🔧 Suggested extraction

In config.ts:

+export const CWD_UPDATE_DEBOUNCE_MS = 150;

In this file:

+import { CWD_UPDATE_DEBOUNCE_MS } from "../config";
+
 const debouncedUpdatePaneCwdRef = useRef(
 	debounce((id: string, cwd: string | null, confirmed: boolean) => {
 		updatePaneCwd(id, cwd, confirmed);
-	}, 150),
+	}, CWD_UPDATE_DEBOUNCE_MS),
 );

54-60: Unnecessary nullish coalescing on boolean state.

cwdConfirmed is initialized via useState(false) and only ever set to true or false, so it can never be nullish. The ?? false is redundant.

🔧 Suggested fix
 	useEffect(() => {
 		debouncedUpdatePaneCwdRef.current(
 			paneId,
 			terminalCwd,
-			cwdConfirmed ?? false,
+			cwdConfirmed,
 		);
 	}, [terminalCwd, cwdConfirmed, paneId]);
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalColdRestore.ts (1)

22-24: Replace any on createOrAttachRef with a typed signature.

Using any here removes type safety for the most critical session-creation path. Consider exporting a CreateOrAttachFn (or similar) type from Terminal/types.ts or useTerminalConnection and use it for createOrAttachRef.
As per coding guidelines, avoid any when a precise type is feasible.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useFileLinkClick.ts (1)

12-35: Use a params object for handleFileLinkClick to align with the codebase guideline.

Functions with 2+ parameters should accept a single params object. Update the handler signature from (path: string, line?: number, column?: number) to accept a single options object, and update all call sites accordingly.

♻️ Proposed shape for the handler
 export interface UseFileLinkClickReturn {
-	handleFileLinkClick: (path: string, line?: number, column?: number) => void;
+	handleFileLinkClick: (params: {
+		path: string;
+		line?: number;
+		column?: number;
+	}) => void;
 }
...
-	const handleFileLinkClick = useCallback(
-		(path: string, line?: number, column?: number) => {
+	const handleFileLinkClick = useCallback(
+		({ path, line, column }: { path: string; line?: number; column?: number }) => {

Update call sites in Terminal.tsx (line 321-322) and helpers.ts (line 258) to pass an object instead of positional arguments.

Comment on lines +71 to +88
// Normalize absolute paths to worktree-relative paths for file viewer
// File viewer expects relative paths, but terminal links can be absolute
let filePath = path;
// Use path boundary check to avoid incorrect prefix stripping
// e.g., /repo vs /repo-other should not match
if (path === workspaceCwd) {
filePath = ".";
} else if (path.startsWith(`${workspaceCwd}/`)) {
filePath = path.slice(workspaceCwd.length + 1);
} else if (path.startsWith("/")) {
// Absolute path outside workspace - show warning and don't attempt to open
toast.warning("File is outside the workspace", {
description:
"Switch to 'External editor' in Settings to open this file",
});
return;
}
addFileViewerPane(workspaceId, { filePath, line, column });
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle Windows/UNC absolute paths when normalizing file links.

The file-viewer normalization only checks POSIX-style absolute paths (/) and ${workspaceCwd}/. On Windows, absolute paths like C:\repo\file or \\server\share will fall through and be treated as relative, but the file viewer expects workspace‑relative paths, so these links will likely fail. Consider normalizing separators and detecting Windows/UNC/file-URI absolutes before deciding to open in the viewer.

🐛 Suggested hardening for cross-platform paths
-				let filePath = path;
+				const normalizedPath = path.replace(/\\/g, "/");
+				const normalizedCwd = workspaceCwd.replace(/\\/g, "/");
+				let filePath = normalizedPath;
 				// Use path boundary check to avoid incorrect prefix stripping
 				// e.g., /repo vs /repo-other should not match
-				if (path === workspaceCwd) {
+				if (normalizedPath === normalizedCwd) {
 					filePath = ".";
-				} else if (path.startsWith(`${workspaceCwd}/`)) {
-					filePath = path.slice(workspaceCwd.length + 1);
-				} else if (path.startsWith("/")) {
+				} else if (normalizedPath.startsWith(`${normalizedCwd}/`)) {
+					filePath = normalizedPath.slice(normalizedCwd.length + 1);
+				} else if (
+					normalizedPath.startsWith("/") ||
+					/^[A-Za-z]:\//.test(normalizedPath) ||
+					normalizedPath.startsWith("//")
+				) {
 					// Absolute path outside workspace - show warning and don't attempt to open
 					toast.warning("File is outside the workspace", {
 						description:
 							"Switch to 'External editor' in Settings to open this file",
 					});
 					return;
 				}
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useFileLinkClick.ts`
around lines 71 - 88, The normalization logic in useFileLinkClick treats only
POSIX absolutes and can mis-handle Windows drive (e.g., C:\...) and UNC
(\\server\share) or file:// URIs, causing false relative paths; update the
branch around workspaceCwd/filePath handling to first normalize separators and
detect Windows/UNC/file-URI absolute forms (drive-letter with colon + backslash,
leading double backslashes, and file://) before comparing to workspaceCwd, and
if such paths are outside the workspace call toast.warning (same message) and
return; ensure addFileViewerPane is only called with truly workspace-relative
filePath.

Comment on lines +43 to +50
const enterAltIndex = Math.max(
combined.lastIndexOf("\x1b[?1049h"),
combined.lastIndexOf("\x1b[?47h"),
);
const exitAltIndex = Math.max(
combined.lastIndexOf("\x1b[?1049l"),
combined.lastIndexOf("\x1b[?47l"),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle \x1b[?1047h/l alternate-screen sequences.
Some terminals/apps toggle alternate screen via 1047h/1047l. Missing these can leave isAlternateScreenRef stale.

🔧 Suggested fix
 		const enterAltIndex = Math.max(
 			combined.lastIndexOf("\x1b[?1049h"),
+			combined.lastIndexOf("\x1b[?1047h"),
 			combined.lastIndexOf("\x1b[?47h"),
 		);
 		const exitAltIndex = Math.max(
 			combined.lastIndexOf("\x1b[?1049l"),
+			combined.lastIndexOf("\x1b[?1047l"),
 			combined.lastIndexOf("\x1b[?47l"),
 		);
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalModes.ts`
around lines 43 - 50, The code in useTerminalModes.ts currently only checks for
1049 and 47 alternate-screen sequences; update the enterAltIndex and
exitAltIndex calculations to also consider 1047 by including
combined.lastIndexOf("\x1b[?1047h") in the enterAltIndex max and
combined.lastIndexOf("\x1b[?1047l") in the exitAltIndex max so the hook
(useTerminalModes / isAlternateScreenRef logic) correctly handles terminals/apps
that toggle alternate screen with 1047 sequences.

Break down the large Terminal.tsx (~1500 lines) into focused modules:

**New Components:**
- SessionKilledOverlay - UI for killed session state
- ConnectionErrorOverlay - UI for connection error state
- RestoredModeOverlay - UI for cold restore state

**New Hooks:**
- useTerminalCwd - CWD state management and OSC-7 parsing
- useTerminalModes - Alternate screen and bracketed paste tracking
- useFileLinkClick - File link click handling
- useTerminalRestore - Terminal state restoration from snapshots
- useTerminalStream - Stream event handling (data, exit, error)
- useTerminalColdRestore - Cold restore (reboot recovery) handling

**New Files:**
- state.ts - Module-level maps for pendingDetaches and coldRestoreState
- config.ts - Added DEBUG_TERMINAL and FIRST_RENDER_RESTORE_FALLBACK_MS
- types.ts - Added CreateOrAttachResult and ColdRestoreState types

Terminal.tsx reduced from ~1500 lines to ~685 lines (~55% reduction)
while maintaining the same functionality.
…l restore

- Handle exit/disconnect/error events immediately in handleStreamData
  instead of queueing them, as these are critical state changes
- Add mode tracking (updateModesFromData) to flushPendingEvents for queued data
- Replace dynamic require("@superset/ui/sonner") with static ES import
- Pass stream event handlers to useTerminalRestore via refs pattern
…ream ready

Reverts to original queueing behavior where ALL events (data, exit,
disconnect, error) are queued when isStreamReadyRef is false, then
flushed in order. This preserves the pre-refactor event sequencing.
@Kitenite Kitenite merged commit bb36720 into main Jan 19, 2026
5 checks passed
@Kitenite Kitenite deleted the refactor-terminal branch January 19, 2026 23:46
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant