Skip to content

Terminal lifecycle refactor#1089

Merged
Kitenite merged 7 commits intomainfrom
terminal-refactor-1
Jan 31, 2026
Merged

Terminal lifecycle refactor#1089
Kitenite merged 7 commits intomainfrom
terminal-refactor-1

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Jan 31, 2026

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • Refactor
    • Terminal internal lifecycle and event handling restructured into modular, reusable components for improved reliability and maintainability.
  • New Features
    • Added terminal manipulation operations: programmatic write, resize, detach, and clear-scrollback.
    • Improved keyboard interactions for terminal search and quick-scroll behavior.
  • API
    • Terminal public component API unchanged; new operation hooks/endpoints added for integrations.

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 31, 2026

📝 Walkthrough

Walkthrough

Refactors Terminal internals into three hooks—useTerminalLifecycle, useTerminalHotkeys, and useTerminalRefs—moving xterm instance management, event wiring, restoration, and hotkey logic out of the Terminal component while preserving its public API.

Changes

Cohort / File(s) Summary
Terminal component
apps/desktop/src/renderer/screens/main/.../Terminal/Terminal.tsx
Removed inline lifecycle/hotkey/ref logic; now delegates xterm creation, event wiring, restoration, and restart handling to hooks. Public component API unchanged.
New lifecycle hook
apps/desktop/src/renderer/screens/main/.../Terminal/hooks/useTerminalLifecycle.ts
New hook that creates/wires XTerm, loads addons, coordinates create/attach, restores state, handles I/O, focus/resize/paste, exposes xterm ref and restartTerminal.
Hotkeys hook
apps/desktop/src/renderer/screens/main/.../Terminal/hooks/useTerminalHotkeys.ts
New hook managing terminal-focused hotkeys (find, scroll-to-bottom), search UI open state, and focus-driven behavior.
Refs hook
apps/desktop/src/renderer/screens/main/.../Terminal/hooks/useTerminalRefs.ts
Centralizes MutableRefObjects for pane/tab state, initial commands/CWD, file link handling, debounced title setter, and registers clear/scroll callbacks.
Hook exports
apps/desktop/src/renderer/screens/main/.../Terminal/hooks/index.ts
Added re-exports for new hooks and their option/return types.
Types additions
apps/desktop/src/renderer/screens/main/.../Terminal/types.ts
Added onSettled to CreateOrAttachCallbacks and new mutation input/types: TerminalWrite*, TerminalResize*, TerminalDetach*, TerminalClearScrollback*.
Minor cleanup
apps/desktop/src/renderer/screens/main/.../Terminal/hooks/useTerminalRestore.ts
Whitespace cleanup only; behavior unchanged.

Sequence Diagram(s)

sequenceDiagram
  participant Terminal as Terminal Component
  participant Lifecycle as useTerminalLifecycle
  participant XTerm as XTerm Instance
  participant Backend as createOrAttach / Session
  participant Overlay as SessionKilledOverlay

  Terminal->>Lifecycle: mount + options (paneId, tabId, refs)
  Lifecycle->>XTerm: create instance + load addons
  Lifecycle->>Backend: createOrAttach(session params)
  Backend-->>Lifecycle: session attached / wasRecovered
  Lifecycle->>XTerm: pipe data, set title, register input handlers
  XTerm->>Lifecycle: user input / resize / focus events
  Lifecycle->>Overlay: on session killed -> expose restartTerminal
  Overlay->>Lifecycle: user triggers restartTerminal
  Lifecycle->>Backend: createOrAttach (restart flow)
  Backend-->>Lifecycle: new session
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code where logic used to sprawl,

Hooks gathered corners, I tidied up the hall,
Lifecycle hums, hotkeys sing, refs neatly in a nest,
The terminal breathes lighter — the rabbit deems it best,
A twitch, a nibble, and a hop — refactor blessed! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only the empty template without any substantive content in any section: no description of changes, related issues, type-of-change selection, testing steps, or additional notes. Fill in the Description section with details about the refactoring, select 'Refactor' in Type of Change, describe testing performed, and explain the commit message 'Fix race' in context.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Terminal lifecycle refactor' accurately summarizes the main change: a substantial refactoring of terminal lifecycle management into dedicated hooks.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch terminal-refactor-1

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: 1

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts`:
- Around line 512-521: Replace the misleading biome-ignore comment near the
effect that omits tabId from the dependency array with a concise clarifying
comment stating that tabId is a plain prop (not a ref) and is architecturally
stable for the life of this component because any change to tabId causes the
component to remount, so omitting tabId from the useEffect dependency array in
useTerminalLifecycle is intentional and safe; update the comment near the effect
(where tabId is used but not listed) to explicitly document this reasoning.
🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts (2)

492-496: Extract magic number to named constant.

The 50 ms timeout for deferred detach is a magic number. Consider extracting it to a named constant at module level for clarity.

Proposed fix

Add at the top of the file near other config imports:

const DETACH_DELAY_MS = 50;

Then update the usage:

-		const detachTimeout = setTimeout(() => {
+		const detachTimeout = setTimeout(() => {
 			detachRef.current({ paneId });
 			pendingDetaches.delete(paneId);
 			coldRestoreState.delete(paneId);
-		}, 50);
+		}, DETACH_DELAY_MS);

199-204: Consider adding error handling for dynamic import.

The dynamic import of SearchAddon doesn't handle potential import failures. While search functionality is non-critical, an unhandled rejection could clutter error logs in edge cases.

Proposed fix
 		import("@xterm/addon-search").then(({ SearchAddon }) => {
 			if (isUnmounted) return;
 			const searchAddon = new SearchAddon();
 			xterm.loadAddon(searchAddon);
 			searchAddonRef.current = searchAddon;
-		});
+		}).catch((err) => {
+			console.warn("[Terminal] Failed to load search addon:", err);
+		});

@Kitenite Kitenite changed the title Terminal lifecycle Terminal lifecycle refactor Jan 31, 2026
@Kitenite Kitenite merged commit c246ff0 into main Jan 31, 2026
4 checks passed
@Kitenite Kitenite deleted the terminal-refactor-1 branch January 31, 2026 22:06
@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! 🎉

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: 1

🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts`:
- Around line 241-246: The dynamic import of the SearchAddon in
useTerminalLifecycle.ts currently has no error handling; update the
import("@xterm/addon-search").then(...) chain to add a .catch(...) that logs
failures using the prefixed pattern (e.g. console.error("[terminal/search-addon]
failed to load SearchAddon:", err)), and ensure any error does not crash the app
(do not swallow—log with context). Keep the existing isUnmounted check and the
assignment to searchAddonRef.current and xterm.loadAddon(SearchAddon) inside the
success handler; only handle and log errors in the .catch block referencing
SearchAddon, searchAddonRef, xterm.loadAddon and isUnmounted.
🧹 Nitpick comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts (3)

34-83: Use params objects for helper functions with 2+ args.
markAttachInFlight, clearAttachInFlight, and waitForAttachClear should take a single params object; consider aligning RegisterCallback/UnregisterCallback similarly (will require matching changes in useTerminalRefs).

♻️ Suggested refactor (update call sites accordingly)
@@
-function markAttachInFlight(paneId: string, attachId: number): void {
+function markAttachInFlight({
+	paneId,
+	attachId,
+}: { paneId: string; attachId: number }): void {
@@
-function clearAttachInFlight(paneId: string, attachId?: number): void {
+function clearAttachInFlight({
+	paneId,
+	attachId,
+}: { paneId: string; attachId?: number }): void {
@@
-function waitForAttachClear(paneId: string, waiter: () => void): () => void {
+function waitForAttachClear({
+	paneId,
+	waiter,
+}: { paneId: string; waiter: () => void }): () => void {
@@
-	cancelAttachWait = waitForAttachClear(paneId, () => {
+	cancelAttachWait = waitForAttachClear({ paneId, waiter: () => {
@@
-	});
+	}});
As per coding guidelines: Functions with 2+ parameters should accept a single params object with named properties instead of positional arguments.

192-195: Standardize log prefixes to [domain/operation].
Please align the log prefix here (and other console.* in this hook) with the required format.

♻️ Example adjustment
-			console.log(`[Terminal] Mount: ${paneId}`);
+			console.log(`[terminal/lifecycle] Mount: ${paneId}`);
As per coding guidelines: Use prefixed console logging with pattern `[domain/operation] message` for all logging.

570-575: Extract the detach delay magic number.
Make the delay explicit via a named constant at module scope.

♻️ Suggested refactor
@@
-const attachWaitersByPane = new Map<string, Set<() => void>>();
+const attachWaitersByPane = new Map<string, Set<() => void>>();
+const DETACH_DELAY_MS = 50;
@@
-		const detachTimeout = setTimeout(() => {
+		const detachTimeout = setTimeout(() => {
 			detachRef.current({ paneId });
 			pendingDetaches.delete(paneId);
 			coldRestoreState.delete(paneId);
-		}, 50);
+		}, DETACH_DELAY_MS);
As per coding guidelines: Avoid magic numbers by extracting them to named constants at module top.

Comment on lines +241 to +246
import("@xterm/addon-search").then(({ SearchAddon }) => {
if (isUnmounted) return;
const searchAddon = new SearchAddon();
xterm.loadAddon(searchAddon);
searchAddonRef.current = searchAddon;
});
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

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts | sed -n '235,250p'

Repository: superset-sh/superset

Length of output: 659


🏁 Script executed:

# Check for surrounding context and see if there's any error handling wrapping this code
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts | sed -n '220,260p'

Repository: superset-sh/superset

Length of output: 1512


🏁 Script executed:

# Get the function signature and broader context
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts | sed -n '180,250p'

Repository: superset-sh/superset

Length of output: 2717


🏁 Script executed:

# Search for other import() or error handling patterns in the file to understand conventions
rg -n "import\(" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts -A 5

Repository: superset-sh/superset

Length of output: 297


🏁 Script executed:

# Check for logging patterns and console.error usage in the renderer directory to verify convention compliance
rg -n "console\.(error|log)" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts

Repository: superset-sh/superset

Length of output: 375


🏁 Script executed:

# Check other terminal-related files for logging patterns
rg -n "console\.(error|log)" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ -t ts -t tsx | head -20

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

# Check other terminal-related files for logging patterns
rg -n "console\.(error|log)" apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/ | head -20

Repository: superset-sh/superset

Length of output: 3381


Add error handling for the search addon dynamic import.

The dynamic import can fail; add a .catch() block to log errors explicitly.

Suggested fix
 		import("@xterm/addon-search").then(({ SearchAddon }) => {
 			if (isUnmounted) return;
 			const searchAddon = new SearchAddon();
 			xterm.loadAddon(searchAddon);
 			searchAddonRef.current = searchAddon;
-		});
+		}).catch((error) => {
+			console.error(
+				"[terminal/lifecycle] Failed to load search addon",
+				error,
+			);
+		});

Per coding guidelines: Never swallow errors silently; at minimum log them with context, and use prefixed console logging with pattern [domain/operation] message for all logging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import("@xterm/addon-search").then(({ SearchAddon }) => {
if (isUnmounted) return;
const searchAddon = new SearchAddon();
xterm.loadAddon(searchAddon);
searchAddonRef.current = searchAddon;
});
import("@xterm/addon-search").then(({ SearchAddon }) => {
if (isUnmounted) return;
const searchAddon = new SearchAddon();
xterm.loadAddon(searchAddon);
searchAddonRef.current = searchAddon;
}).catch((error) => {
console.error(
"[terminal/lifecycle] Failed to load search addon",
error,
);
});
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts`
around lines 241 - 246, The dynamic import of the SearchAddon in
useTerminalLifecycle.ts currently has no error handling; update the
import("@xterm/addon-search").then(...) chain to add a .catch(...) that logs
failures using the prefixed pattern (e.g. console.error("[terminal/search-addon]
failed to load SearchAddon:", err)), and ensure any error does not crash the app
(do not swallow—log with context). Keep the existing isUnmounted check and the
assignment to searchAddonRef.current and xterm.loadAddon(SearchAddon) inside the
success handler; only handle and log errors in the .catch block referencing
SearchAddon, searchAddonRef, xterm.loadAddon and isUnmounted.

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