Skip to content

feat(desktop): add userTitle field for user-renamed tabs#289

Merged
Kitenite merged 14 commits intomainfrom
courageous-opossum-0bc099
Dec 8, 2025
Merged

feat(desktop): add userTitle field for user-renamed tabs#289
Kitenite merged 14 commits intomainfrom
courageous-opossum-0bc099

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Dec 8, 2025

Summary

  • Adds userTitle optional field to Tab interface to store user-set titles via rename
  • Auto-generated name (e.g., "Terminal 1") is preserved for consistent tab numbering
  • User title takes precedence when non-empty; clearing it reverts to auto-generated name

Test plan

  • Create new tabs and verify they get auto-generated names ("Terminal 1", "Terminal 2", etc.)
  • Rename a tab and verify the custom name is displayed
  • Clear a renamed tab's title (empty string) and verify it reverts to auto-generated name
  • Delete a tab and create a new one to verify numbering still works correctly
  • Verify persisted tabs retain both name and userTitle after app restart

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Users can set and clear custom tab names independently from auto-generated titles.
    • Tab titles can be auto-updated from terminal input (debounced on Enter) when no custom name is set.
  • Improvements
    • Auto-generated titles are sanitized, truncated for readability, and reliably capture pasted input.
  • Tests
    • Added tests covering title sanitization and command buffering.

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

@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 8, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
website Ready Ready Preview Comment Dec 8, 2025 11:47pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 8, 2025

Walkthrough

Adds user-settab titles via a new optional userTitle field, separates user renames (writes userTitle) from auto-generated titles (name via new setTabAutoTitle), updates display precedence, and implements terminal command buffering, paste capture and sanitization to derive debounced auto-titles.

Changes

Cohort / File(s) Summary
Types & Store API
apps/desktop/src/renderer/stores/tabs/types.ts, apps/desktop/src/renderer/stores/tabs/store.ts
Adds optional userTitle?: string on Tab; renameTab now updates userTitle instead of name; adds new setTabAutoTitle(tabId, title) to update name.
Display logic
apps/desktop/src/renderer/stores/tabs/utils.ts
getTabDisplayName() now prefers trimmed non-empty userTitle over tab.name (fallback "Terminal").
Tab rename UI
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
startRename() initializes input from `tab.userTitle
Terminal buffering & integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, .../helpers.ts
Adds command buffer refs, onKey/onPaste handling, debounced setTabAutoTitle on Enter, uses refs for stable pane/tab titles, and wires setupPasteHandler with an onPaste callback; ensures proper listener cleanup.
Sanitizer & tests
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts, .../commandBuffer.test.ts
New `sanitizeForTitle(text): string
Dependency
apps/desktop/package.json
Adds dependency "strip-ansi": "^7.1.2" used by the new sanitizer.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Terminal as Terminal Component
    participant PasteH as Paste Handler
    participant Store as TabsStore
    participant Display as Tab Display (getTabDisplayName)

    Note over User,Terminal: User types or pastes into terminal
    User->>Terminal: type chars / paste text
    Terminal->>PasteH: setupPasteHandler(onPaste)
    PasteH-->>Terminal: onPaste(pastedText)
    Terminal->>Terminal: append to commandBufferRef
    Terminal->>Terminal: onKey Enter -> sanitizeForTitle(buffer)
    alt sanitized non-null
      Terminal->>Store: setTabAutoTitle(tabId, sanitized)
      Store->>Store: update tab.name = sanitized
    else sanitized null
      Note right of Terminal: no auto-title update
    end

    Note over Display,Store: Rendering precedence when showing tab title
    Display->>Store: read tab
    Store-->>Display: return { name, userTitle }
    Display->>Display: show userTitle if non-empty else name
    Display-->>User: render tab title
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify sanitizeForTitle() handles ANSI, multibyte/emoji, and truncation correctly.
  • Inspect Terminal onKey/onPaste listener lifecycles and debounce disposal to avoid leaks.
  • Confirm all call sites correctly distinguish renameTab (userTitle) vs setTabAutoTitle (name).

Possibly related PRs

Poem

🐰 I nibble keys and paste with glee,
I trim the noise to a title that's free.
User names persist, auto-names hum,
Enter to set, clear to become glum.
Hop—tabs renamed, a carrot for me! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding a userTitle field for user-renamed tabs, which is the core feature across the entire changeset.
Description check ✅ Passed The description covers the main feature (userTitle field), the test plan checklist, and implementation details; however, it is missing the standard template sections like Related Issues, Type of Change selection, and formal Testing section.
✨ 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 courageous-opossum-0bc099

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Dec 8, 2025

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

Separates auto-generated tab names from user-set titles. The auto-generated
name (e.g., "Terminal 1") is preserved for consistent tab numbering while
userTitle stores custom names set via rename. User title takes precedence
when non-empty; clearing it reverts to the auto-generated name.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite force-pushed the courageous-opossum-0bc099 branch from f75ba37 to 28915fe Compare December 8, 2025 21:05
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: 0

🧹 Nitpick comments (1)
apps/desktop/src/renderer/stores/tabs/store.ts (1)

161-167: Implementation is correct and aligns with PR objectives.

The mutation correctly updates userTitle instead of name, preserving the auto-generated name for consistent numbering while storing the user's custom title.

For improved clarity, consider renaming the parameter to reflect that it sets userTitle:

-renameTab: (tabId, newName) => {
+renameTab: (tabId, newTitle) => {
  set((state) => ({
    tabs: state.tabs.map((t) =>
-     t.id === tabId ? { ...t, userTitle: newName } : t,
+     t.id === tabId ? { ...t, userTitle: newTitle } : t,
    ),
  }));
},

Also update the type signature in types.ts:

-renameTab: (tabId: string, newName: string) => void;
+renameTab: (tabId: string, newTitle: string) => void;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0109b87 and f75ba37.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Avoid any type in TypeScript unless absolutely necessary

Files:

  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/src/{renderer,shared,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process code or shared code in the desktop app

Files:

  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
  • apps/desktop/src/renderer/stores/tabs/store.ts
apps/desktop/src/{main,renderer,shared}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC system for communication between Electron main and renderer processes

Files:

  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
**/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,pages}/**/*.tsx: Use one folder per component with structure ComponentName/ComponentName.tsx plus index.ts for barrel export
Create one component per file - do not use multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
🧬 Code graph analysis (1)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)
apps/desktop/src/renderer/stores/tabs/types.ts (1)
  • Tab (25-32)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (4)
apps/desktop/src/renderer/stores/tabs/utils.ts (1)

15-21: LGTM! Precedence logic is well-implemented.

The userTitle precedence logic correctly handles all edge cases: optional chaining safely handles undefined, trim() ensures whitespace-only titles fall through to the auto-generated name, and the falsy check on the trimmed string properly catches empty strings.

apps/desktop/src/renderer/stores/tabs/types.ts (1)

27-28: LGTM! Clear type definition with helpful documentation.

The optional userTitle field is properly typed, and the inline comments clearly explain the distinction between the auto-generated name and the user-set userTitle with its precedence behavior.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (2)

79-87: LGTM! Rename initialization correctly prioritizes userTitle.

The initialization logic properly uses userTitle when available, falling back to name and then displayName. The nullish coalescing chain ensures the input always has a value to display.


89-97: LGTM! Rename submission logic correctly handles clearing userTitle.

The comparison against currentUserTitle (trimmed or empty string) properly supports all rename scenarios:

  • Setting a custom title updates userTitle
  • Clearing the input (empty string) clears userTitle, revealing the auto-generated name
  • No-op when the value hasn't changed

Tracks terminal input and updates the tab's auto-generated title when
the user submits a command (presses Enter). Handles backspace, Ctrl+C,
and Ctrl+U for accurate command tracking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 0

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)

79-86: Consider treating empty/whitespace userTitle as “no custom title” when initializing rename

tab.userTitle ?? tab.name ?? displayName will use userTitle even when it’s an empty string or all-whitespace, so the rename input can appear blank while the tab label shows the auto-generated name. To better match the “empty string means cleared” semantics and user expectations, you could normalize here and fall back to the auto name when userTitle is empty/whitespace:

- const startRename = () => {
-   setRenameValue(tab.userTitle ?? tab.name ?? displayName);
+ const startRename = () => {
+   const initialTitle = tab.userTitle?.trim() || tab.name || displayName;
+   setRenameValue(initialTitle);
    setIsRenaming(true);
    setTimeout(() => {
      inputRef.current?.focus();
      inputRef.current?.select();
    }, 0);
  };

This keeps the behavior for existing non-empty titles while avoiding the “blank input but non-blank label” edge case.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f75ba37 and 28915fe.

📒 Files selected for processing (4)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (2 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/desktop/src/renderer/stores/tabs/utils.ts
  • apps/desktop/src/renderer/stores/tabs/store.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
🧰 Additional context used
📓 Path-based instructions (5)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
**/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,pages}/**/*.tsx: Use one folder per component with structure ComponentName/ComponentName.tsx plus index.ts for barrel export
Create one component per file - do not use multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Avoid any type in TypeScript unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
apps/desktop/src/{renderer,shared,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process code or shared code in the desktop app

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TabItem/index.tsx (1)

88-95: Rename submission logic correctly targets userTitle and supports clearing

The submitRename flow looks consistent with the new userTitle semantics:

  • It trims the input and compares against the trimmed existing tab.userTitle, avoiding no-op writes and whitespace-only changes.
  • It calls renameTab(tab.id, trimmedValue) even when trimmedValue is "", enabling the “clear to revert to auto-generated name” behavior described in the PR.
  • It does not touch the underlying name field, so auto-generated numbering can remain stable.

This aligns well with the intent of keeping name for numbering and using userTitle purely as an override.

Moves command input processing to commandBuffer.ts with comprehensive
tests for enter, backspace, and cancel key handling.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use strip-ansi library for ANSI escape sequence removal
- Add lodash debounce (100ms) to prevent rapid title updates
- Limit title length to 32 characters
- Add tests for sanitization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove strip-ansi dependency and use simple regex to keep only
alphanumeric, space, underscore, dash, dot, and slash characters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 0

🧹 Nitpick comments (4)
apps/desktop/src/renderer/stores/tabs/types.ts (1)

25-32: Clarify contract between name, userTitle, and setTabAutoTitle

The new userTitle?: string field and setTabAutoTitle API fit the described behavior (auto-generated vs user-provided titles) and look type-safe. To prevent misuse, I’d suggest tightening the comments around Tab/TabsStore to spell out that:

  • name is the persisted auto-generated title (updated via setTabAutoTitle only).
  • userTitle is an optional user-renamed display title, written via renameTab.
  • UI display helpers should always prefer a non-empty userTitle over name.

This will make the intended separation obvious to future callers.

Also applies to: 48-54

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

1-137: Good behavioral coverage; consider aligning with “one assertion per test” guideline

These tests exercise the command buffer and sanitization paths well (enter/cancel/backspace flows, printable filtering, ANSI stripping, truncation, null handling) and are readable/independent.

However, many it blocks contain multiple expect calls, which diverges from the repo guideline that tests should have one assert per test for apps/desktop/**/*.test.{ts,tsx,js,jsx}. If you want strict alignment with that style guide, consider:

  • Grouping expectations into a single structural assertion per test (e.g., expect(result).toMatchObject({ buffer: "...", submittedCommand: ... })), or
  • Splitting some cases (e.g., buffer vs submittedCommand) into separate it blocks.

Functionally this is fine as-is, so this can be a follow-up cleanup rather than a blocker.

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

31-31: Auto-title integration looks good; refine debounce lifecycle to avoid stray timers

The new flow—tracking a per-terminal commandBufferRef, running input through processCommandInput, and debounced-calling setTabAutoTitle on submittedCommand tied to parentTabIdRef—is a nice separation of concerns and keeps PTY writes (writeRef.current({ tabId: paneId, data })) unchanged.

One improvement: the debounced function is recreated on every render:

  • useRef(debounce(setTabAutoTitle, 100)) initializes once, but
  • debouncedSetTabAutoTitleRef.current = debounce(setTabAutoTitle, 100); assigns a new debounced instance every render, and
  • There’s no cancel() on unmount.

That can (a) weaken debouncing guarantees across re-renders and (b) leave pending timers that can still fire after unmount.

You can stabilize and clean this up by initializing once in an effect and cancelling on teardown, e.g.:

-	const parentTabId = pane?.tabId;
+	const parentTabId = pane?.tabId;-	const parentTabIdRef = useRef(parentTabId);
-	parentTabIdRef.current = parentTabId;
-
-	const debouncedSetTabAutoTitleRef = useRef(debounce(setTabAutoTitle, 100));
-	debouncedSetTabAutoTitleRef.current = debounce(setTabAutoTitle, 100);
+	const parentTabIdRef = useRef(parentTabId);
+	parentTabIdRef.current = parentTabId;
+
+	const debouncedSetTabAutoTitleRef =
+		useRef<ReturnType<typeof debounce> | null>(null);
+
+	useEffect(() => {
+		const debounced = debounce(setTabAutoTitle, 100);
+		debouncedSetTabAutoTitleRef.current = debounced;
+		return () => debounced.cancel();
+	}, [setTabAutoTitle]);
@@
-			if (result.submittedCommand && parentTabIdRef.current) {
-				debouncedSetTabAutoTitleRef.current(
-					parentTabIdRef.current,
-					result.submittedCommand,
-				);
-			}
+			if (
+				result.submittedCommand &&
+				parentTabIdRef.current &&
+				debouncedSetTabAutoTitleRef.current
+			) {
+				debouncedSetTabAutoTitleRef.current(
+					parentTabIdRef.current,
+					result.submittedCommand,
+				);
+			}

This keeps the debounce identity stable across renders and guarantees any queued callbacks are cancelled when the terminal unmounts.

If you want to double-check lodash/debounce typings/behavior with this pattern in your current version, please verify against the library docs or type definitions.

Also applies to: 38-39, 42-42, 73-78, 225-243

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

1-56: Command buffer & title sanitization are solid; optional edge-case refinements

The sanitizeForTitle and processCommandInput implementations are clear, side-effect free, and align with the tests:

  • ANSI is stripped via strip-ansi, non-printables removed, whitespace trimmed, and titles truncated to 32 chars with null for empty/whitespace-only results.
  • The buffer state machine (ENTER → submit & clear, BACKSPACE → drop last char, CANCEL → clear, otherwise append printable chars including tab) is easy to reason about.

Two optional considerations:

  • Control-char precedence: You currently prioritize ENTER, then BACKSPACE, then CANCEL based on input.includes(...). If xterm ever emits multiple control characters in a single chunk, BACKSPACE would win over CANCEL. If you’d prefer “clear line” semantics to always win, you could check hasCancel before hasBackspace.
  • Non-ASCII commands: The [^\x20-\x7e] filter strips all non-ASCII characters from titles. If you later want to support non-Latin commands in tab titles, you might loosen this to only strip control characters (Unicode category \p{C}) while keeping printable non-ASCII.

These aren’t blockers; the current behavior is consistent with the tests and the PR’s goals.

Since this relies on strip-ansi’s behavior and your expected xterm onData semantics, please confirm against your terminal/strip-ansi versions if you change the control-char or character-class handling.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28915fe and c725109.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/tabs/types.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/stores/tabs/store.ts
🧰 Additional context used
📓 Path-based instructions (7)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Avoid any type in TypeScript unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
apps/desktop/src/{renderer,shared,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process code or shared code in the desktop app

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
apps/desktop/src/{main,renderer,shared}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC system for communication between Electron main and renderer processes

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
  • apps/desktop/src/renderer/stores/tabs/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
**/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,pages}/**/*.tsx: Use one folder per component with structure ComponentName/ComponentName.tsx plus index.ts for barrel export
Create one component per file - do not use multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.test.{ts,tsx,js,jsx}: Tests should have one assert per test
Tests should be readable
Tests should be fast
Tests should be independent
Tests should be repeatable

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
🧠 Learnings (3)
📚 Learning: 2025-12-06T18:44:13.290Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T18:44:13.290Z
Learning: Applies to apps/desktop/src/{main,renderer,shared}/**/*.ts : Use type-safe IPC system for communication between Electron main and renderer processes

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be readable

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
📚 Learning: 2025-11-24T21:33:13.267Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.267Z
Learning: Applies to apps/desktop/**/*.test.{ts,tsx,js,jsx} : Tests should be repeatable

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
🧬 Code graph analysis (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (2)
  • processCommandInput (22-56)
  • sanitizeForTitle (13-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (1)
apps/desktop/package.json (1)

84-84: strip-ansi dependency addition matches new terminal title sanitization

Adding strip-ansi as a runtime dependency aligns with its use in commandBuffer.ts for cleaning terminal input before deriving tab titles; keeping it in dependencies (not devDependencies) is appropriate.

Please confirm your bundler/tsconfig is configured for default ESM-style imports for strip-ansi (as used in commandBuffer.ts), or adjust the import to match your module settings if needed.

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

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

31-42: Consider cancelling debounced tab-title updates on unmount

Using refs for parentTabId and setTabAutoTitle plus a debounced updater is a solid pattern to avoid effect churn and stale closures. One minor improvement: the debounced function can keep a pending timeout after this component unmounts. It’s harmless since it only hits the store, but cancelling it in the cleanup would avoid stray work and make behavior more predictable (especially in tests):

return () => {
+ debouncedSetTabAutoTitle.cancel?.();
  isUnmounted = true;
  inputDisposable.dispose();
  cleanupKeyboard();
  cleanupFocus?.();
  cleanupResize();
  cleanupPaste();
  cleanupQuerySuppression();
  detachRef.current({ tabId: paneId });
  setSubscriptionEnabled(false);
  xterm.dispose();
  xtermRef.current = null;
  searchAddonRef.current = null;
};

Also applies to: 73-84, 320-327

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c725109 and 33a5398.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (5 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
**/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,pages}/**/*.tsx: Use one folder per component with structure ComponentName/ComponentName.tsx plus index.ts for barrel export
Create one component per file - do not use multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Avoid any type in TypeScript unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
apps/desktop/src/{renderer,shared,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process code or shared code in the desktop app

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
apps/desktop/src/{main,renderer,shared}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC system for communication between Electron main and renderer processes

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
🧠 Learnings (1)
📚 Learning: 2025-12-06T18:44:13.290Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T18:44:13.290Z
Learning: Applies to apps/desktop/src/{main,renderer,shared}/**/*.ts : Use type-safe IPC system for communication between Electron main and renderer processes

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)

5-12: Command-buffer and debounce imports are cleanly scoped

Bringing in lodash/debounce and local processCommandInput keeps the terminal component lean, and there are no renderer/main boundary issues here. Looks good.


231-249: handleTerminalInput restart + auto-title plumbing looks correct

Resetting commandBufferRef on restart and early-returning avoids leaking old input into a new PTY session, and routing all non-exit input through processCommandInput before calling writeRef cleanly isolates auto-title logic from the actual terminal I/O. No issues spotted here.

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

11-19: sanitizeForTitle is a solid, bounded sanitizer

Capping input length before sanitizing, then trimming and enforcing a final max length with a conservative allowlist is a good balance between robustness and readable titles. Returning null for empty output keeps callers’ logic simple.

Comment on lines +21 to +55
export function processCommandInput(
currentBuffer: string,
input: string,
): CommandBufferResult {
const hasEnter = ENTER.some((char) => input.includes(char));
const hasBackspace = BACKSPACE.some((char) => input.includes(char));
const hasCancel = CANCEL.some((char) => input.includes(char));

if (hasEnter) {
return {
buffer: "",
submittedCommand: sanitizeForTitle(currentBuffer),
};
}

if (hasBackspace) {
return {
buffer: currentBuffer.slice(0, -1),
submittedCommand: null,
};
}

if (hasCancel) {
return {
buffer: "",
submittedCommand: null,
};
}

const printableChars = input.replace(/[^\x20-\x7e\t]/g, "");
return {
buffer: currentBuffer + printableChars,
submittedCommand: null,
};
}
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

processCommandInput may mis-track buffer for multi-character input chunks

The current logic treats input as if it contains at most one special key: on hasEnter/hasBackspace/hasCancel, it ignores any printable characters in the same chunk and only looks at currentBuffer. For example:

  • If input === "ls\r" and currentBuffer === "", you’ll submit sanitizeForTitle("") (i.e. null) rather than "ls".
  • If input === "d\b" and currentBuffer === "abc", the new buffer becomes "ab" instead of the expected "abc" (from "abcd".slice(0, -1)).

This is fine as long as upstream always calls it once per keystroke, but it will drift from what the user sees if input is ever batched (e.g., some paste scenarios or repeated keys).

You could make this more robust by processing input character-by-character, applying the same rules in sequence:

export function processCommandInput(
  currentBuffer: string,
  input: string,
): CommandBufferResult {
  let buffer = currentBuffer;
  let submittedCommand: string | null = null;

  for (const ch of input) {
    if (ENTER.includes(ch)) {
      submittedCommand = sanitizeForTitle(buffer);
      buffer = "";
    } else if (BACKSPACE.includes(ch)) {
      buffer = buffer.slice(0, -1);
    } else if (CANCEL.includes(ch)) {
      buffer = "";
    } else if (/^[\x20-\x7e\t]$/.test(ch)) {
      buffer += ch;
    }
  }

  return { buffer, submittedCommand };
}

That keeps behavior the same for single-key inputs while handling batched ones correctly.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
around lines 21-55, the function treats the entire input chunk as one event
(enter/backspace/cancel) and ignores printable characters in the same chunk,
causing incorrect buffer/submission for multi-character inputs; fix by iterating
over each character in input in order, updating a local buffer and
submittedCommand for each char (enter -> submit sanitized current buffer and
clear buffer, backspace -> slice last char from buffer, cancel -> clear buffer,
printable -> append to buffer), then return the final buffer and
submittedCommand.

Track keystrokes via xterm.onKey instead of parsing PTY data from
onData. This avoids TUI escape sequence pollution in tab titles since
onKey captures actual keyboard events rather than shell output.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pass onPaste callback to setupPasteHandler so pasted text is appended
to the command buffer, not just typed keystrokes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Keep most characters in tab titles, only strip ANSI escape sequences.
This preserves uppercase, special chars like @, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Read current line from xterm buffer when Enter is pressed instead of
tracking keystrokes. This captures autocompleted text from shells like
zsh with powerlevel10k or zsh-autosuggestions.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Use refs for paneName, terminalTheme, and debouncedSetTabAutoTitle
  to avoid triggering effect re-runs
- Theme changes handled by separate sync effect, not terminal recreation
- Revert to keystroke tracking (onKey + paste) for command buffer
- Add sanitizeForTitle to strip ANSI sequences from tab titles

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 33a5398 and ed8cbe3.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • apps/desktop/package.json (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (9 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts
🧰 Additional context used
📓 Path-based instructions (6)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
**/{components,pages}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,pages}/**/*.tsx: Use one folder per component with structure ComponentName/ComponentName.tsx plus index.ts for barrel export
Create one component per file - do not use multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them
Avoid any type in TypeScript unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
apps/desktop/src/{renderer,shared,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process code or shared code in the desktop app

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
apps/desktop/src/{main,renderer,shared}/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC system for communication between Electron main and renderer processes

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
🧬 Code graph analysis (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (2)
  • createTerminalInstance (52-128)
  • setupPasteHandler (156-187)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (1)
  • sanitizeForTitle (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (1)

137-173: LGTM! Clean extension for paste tracking.

The addition of the PasteHandlerOptions interface and optional onPaste callback is well-designed:

  • Backward compatible with default parameter
  • Properly invokes the callback before calling xterm.paste()
  • Enables command buffer tracking without changing existing behavior
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)

46-47: Good use of refs to optimize re-renders and prevent stale closures.

The introduction of initialThemeRef (lines 46-47) prevents unnecessary terminal recreation when the theme changes, while separate refs for parentTabId, paneName, and setTabAutoTitle (lines 76-83) ensure callbacks always reference the latest values without triggering effect re-runs. This is an effective optimization pattern.

Also applies to: 76-83, 166-170


326-330: Paste handler integration is consistent with buffer approach.

The paste callback correctly appends pasted text to the command buffer. Note that this inherits the same cursor-position limitations as the keyboard buffering (text pasted in the middle of a command won't be tracked correctly), but this is consistent with the overall v1 approach.

apps/desktop/package.json (1)

84-84: The strip-ansi@^7.1.2 version is valid and is currently the latest version available on npm. No security vulnerabilities are associated with this version.

Comment on lines +249 to 271
const handleKeyPress = (event: {
key: string;
domEvent: KeyboardEvent;
}) => {
const { domEvent } = event;
if (domEvent.key === "Enter") {
const title = sanitizeForTitle(commandBufferRef.current);
if (title && parentTabIdRef.current) {
debouncedSetTabAutoTitleRef.current(parentTabIdRef.current, title);
}
commandBufferRef.current = "";
} else if (domEvent.key === "Backspace") {
commandBufferRef.current = commandBufferRef.current.slice(0, -1);
} else if (domEvent.key === "c" && domEvent.ctrlKey) {
commandBufferRef.current = "";
} else if (
domEvent.key.length === 1 &&
!domEvent.ctrlKey &&
!domEvent.metaKey
) {
commandBufferRef.current += domEvent.key;
}
};
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Dec 8, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Command buffering approach has significant limitations.

The current implementation buffers keystrokes character-by-character, which works for simple sequential typing but fails in several common scenarios:

  • Cursor movement: Typing "test", pressing ←← (left arrow twice), then "x" results in buffer "testx" but actual command is "tex st"
  • Shell history: Using ↑/↓ to navigate command history won't update the buffer
  • Multi-line commands: Continuation lines aren't properly tracked
  • TUI applications: Vim, nano, etc. will completely break this approach
  • Unicode handling: domEvent.key.length === 1 may not correctly handle multi-byte characters

Given the PR objectives mention this is for deriving auto-titles (not comprehensive command tracking), this simplified approach may be acceptable for v1, but users will encounter incorrect titles in the above scenarios.

Would you like me to:

  1. Add comments documenting these known limitations?
  2. Suggest a more robust approach using shell integration (OSC 133 sequences)?
  3. Help implement cursor position tracking for at least basic left/right arrow support?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

what's 2?

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.

@Kitenite Shell integration using OSC 133 sequences is a protocol that modern shells can emit to communicate with terminal emulators. Here's how it works:

What are OSC 133 sequences?

OSC (Operating System Command) 133 is a set of escape sequences that shells can output to mark semantic regions in the terminal. The key sequences are:

  • OSC 133 ; A ST - Marks the start of a command prompt
  • OSC 133 ; B ST - Marks the end of prompt, start of command input
  • OSC 133 ; C ST - Marks the end of command input, start of execution
  • OSC 133 ; D ST - Marks the end of command output

How this helps:

Instead of trying to track keystrokes, the terminal would listen for these escape sequences. When the shell emits OSC 133 ; B and OSC 133 ; C, everything between them is the actual command being executed - regardless of cursor movements, history navigation, or multi-line edits.

Example:

[Prompt shown]
OSC 133 ; A ST  (prompt starts)
OSC 133 ; B ST  (command input starts)
git status      (user types this - shell captures the final command)
OSC 133 ; C ST  (command about to execute)
[output shown]
OSC 133 ; D ST  (command finished)

Real-world implementations:

  • VS Code terminal uses this for "smart command detection" and command history features
  • iTerm2 pioneered this with their Shell Integration
  • Warp terminal relies heavily on this

Trade-offs:

  • ✅ Accurate: Gets the actual executed command, not keystroke approximations
  • ✅ Handles all edge cases: arrows, history, multi-line, etc.
  • ❌ Requires shell configuration: Users need to modify .bashrc, .zshrc, etc.
  • ❌ Not universal: Won't work until user sets it up

For your use case, shell integration would be significantly more robust but requires user setup. The current keystroke approach works immediately but with known limitations.

Would you like me to sketch out what shell integration setup would look like, or are you leaning toward documenting the limitations of the current v1 approach?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

keystroke is fine, perhaps improve it with some simple cursor tracking

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.

@Kitenite I'll add simple cursor tracking to handle arrow keys and character insertion at the cursor position. Here's an improved implementation:

// Add a ref to track cursor position within the command buffer
const commandBufferRef = useRef("");
const cursorPositionRef = useRef(0);

// ... existing code ...

const handleKeyPress = (event: {
	key: string;
	domEvent: KeyboardEvent;
}) => {
	const { domEvent } = event;
	if (domEvent.key === "Enter") {
		const title = sanitizeForTitle(commandBufferRef.current);
		if (title && parentTabIdRef.current) {
			debouncedSetTabAutoTitleRef.current(parentTabIdRef.current, title);
		}
		commandBufferRef.current = "";
		cursorPositionRef.current = 0;
	} else if (domEvent.key === "Backspace") {
		// Delete character before cursor
		if (cursorPositionRef.current > 0) {
			const buffer = commandBufferRef.current;
			commandBufferRef.current =
				buffer.slice(0, cursorPositionRef.current - 1) +
				buffer.slice(cursorPositionRef.current);
			cursorPositionRef.current--;
		}
	} else if (domEvent.key === "ArrowLeft") {
		// Move cursor left
		cursorPositionRef.current = Math.max(0, cursorPositionRef.current - 1);
	} else if (domEvent.key === "ArrowRight") {
		// Move cursor right
		cursorPositionRef.current = Math.min(
			commandBufferRef.current.length,
			cursorPositionRef.current + 1,
		);
	} else if (domEvent.key === "Home") {
		// Move cursor to start
		cursorPositionRef.current = 0;
	} else if (domEvent.key === "End") {
		// Move cursor to end
		cursorPositionRef.current = commandBufferRef.current.length;
	} else if (domEvent.key === "ArrowUp" || domEvent.key === "ArrowDown") {
		// Shell history navigation - reset buffer
		commandBufferRef.current = "";
		cursorPositionRef.current = 0;
	} else if (domEvent.key === "c" && domEvent.ctrlKey) {
		commandBufferRef.current = "";
		cursorPositionRef.current = 0;
	} else if (
		domEvent.key.length === 1 &&
		!domEvent.ctrlKey &&
		!domEvent.metaKey
	) {
		// Insert character at cursor position
		const buffer = commandBufferRef.current;
		commandBufferRef.current =
			buffer.slice(0, cursorPositionRef.current) +
			domEvent.key +
			buffer.slice(cursorPositionRef.current);
		cursorPositionRef.current++;
	}
};

What this adds:

  1. Cursor tracking: cursorPositionRef tracks position within the buffer
  2. Arrow keys: Left/right move cursor, up/down reset buffer (shell history)
  3. Home/End: Jump to start/end of buffer
  4. Positional editing: Backspace and insertion now work at cursor position

Example scenario:

  • Type "test" → buffer: "test", cursor: 4
  • Press ← ← → buffer: "test", cursor: 2
  • Type "x" → buffer: "text st", cursor: 3 ✅

This handles basic cursor movement while keeping the implementation simple. Still won't track shell history contents or handle TUI apps, but covers the common editing cases.

🐰✏️


🧠 Learnings used
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T23:32:32.232Z
Learning: Applies to apps/desktop/src/main/lib/terminal-*.ts : Use node-pty for terminal session management in the desktop app

Remove comments that describe what the code does rather than why.
Also cancel debounced tab title updates on unmount to prevent
memory leaks from pending timers.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite merged commit a92b1a4 into main Dec 8, 2025
7 of 8 checks passed
@Kitenite Kitenite deleted the courageous-opossum-0bc099 branch December 8, 2025 23:49
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: 0

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (2)

239-261: Cursor tracking from previous discussion not yet implemented.

The command buffering works for simple sequential typing but doesn't include the cursor position tracking that was discussed and agreed upon in the previous review. The current implementation has these limitations:

  • Backspace only removes from the end (line 251)
  • Arrow keys, Home/End are not handled
  • Editing in the middle of a command (after cursor movement) produces incorrect buffer

These were acknowledged as acceptable for v1, but you mentioned wanting "simple cursor tracking" in the prior review thread.

The previous review provided a complete implementation with cursor tracking. Would you like me to generate that enhanced version again, or are you planning to address this in a follow-up?


254-259: Minor: Unicode handling edge case.

The check domEvent.key.length === 1 at line 255 may not correctly handle all Unicode characters (e.g., some emojis or composed characters where key.length > 1). In practice this is unlikely to cause issues since sanitizeForTitle handles the final result, but it's worth noting.

If you want to be more precise, consider:

 		} else if (
-			domEvent.key.length === 1 &&
+			domEvent.key.length === 1 && !domEvent.altKey &&
 			!domEvent.ctrlKey &&
 			!domEvent.metaKey
 		) {

Adding !domEvent.altKey prevents capturing keys like Alt+F, Alt+B (used for word navigation in shells).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed8cbe3 and 2ade020.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (8 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Maintain type safety and avoid using any unless absolutely necessary in TypeScript code

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
**/components/**/[A-Z]*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Use one folder per component with structure ComponentName/ComponentName.tsx + index.ts barrel export

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process or shared code - only in src/main/

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-08T23:32:32.232Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-08T23:32:32.232Z
Learning: Applies to apps/desktop/src/main/lib/terminal-*.ts : Use node-pty for terminal session management in the desktop app

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
🧬 Code graph analysis (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (4)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/types.ts (1)
  • TerminalStreamEvent (6-8)
apps/desktop/src/renderer/stores/theme/store.ts (1)
  • useTerminalTheme (184-185)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts (2)
  • createTerminalInstance (52-128)
  • setupPasteHandler (156-187)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/commandBuffer.ts (1)
  • sanitizeForTitle (5-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx (3)

82-86: Debounce cleanup properly implemented.

The debounced function is now correctly cancelled on unmount at line 330, resolving the previously flagged critical issue about potential memory leaks and React warnings from post-unmount updates.

Also applies to: 330-330


315-319: Paste capture correctly integrated.

The paste handler now accumulates pasted text into the command buffer, ensuring pasted commands contribute to auto-title generation.


158-162: Theme handling optimized to prevent unnecessary recreation.

Using initialThemeRef.current for terminal initialization and excluding terminalTheme from effect dependencies (line 338) is the correct pattern. Theme updates are applied to the live terminal instance via the separate effect at lines 340-345, avoiding expensive terminal disposal and recreation.

Also applies to: 338-338

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