Skip to content

feat(desktop): add terminal presets system#279

Closed
Kitenite wants to merge 2 commits intomainfrom
passing-meadowlark-c80a0e
Closed

feat(desktop): add terminal presets system#279
Kitenite wants to merge 2 commits intomainfrom
passing-meadowlark-c80a0e

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Dec 8, 2025

Summary

  • Add terminal presets to .superset/config.json for quick-launch preconfigured terminals
  • Preset buttons appear in sidebar below window list
  • Support creating presets via modal ("+ New Preset" button) or "Save as Preset" from terminal context menu
  • Presets can specify custom working directory and startup commands
  • Right-click to delete presets

Config Schema

{
  "setup": [],
  "teardown": [],
  "terminalPresets": [
    {
      "name": "Dev Server",
      "cwd": "apps/website",
      "commands": "bun dev"
    },
    {
      "name": "Test Watch",
      "commands": ["bun install", "bun test:watch"]
    }
  ]
}

Test plan

  • Create a preset via "+ New Preset" button in sidebar
  • Create a preset via "Save as Preset" from terminal context menu
  • Click preset button to launch terminal with custom cwd and commands
  • Verify relative paths work (both apps/website and ./apps/website)
  • Delete preset via right-click context menu
  • Verify presets persist in .superset/config.json

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Terminal presets: Save and reuse terminal configurations with custom commands and working directories
    • Preset management: Create new presets from active terminals and delete presets via a dedicated sidebar panel
    • Quick launch: Select any saved preset to instantly open a new terminal with its configured settings

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

Kitenite and others added 2 commits December 7, 2025 16:10
Add terminal presets to config.json that appear as buttons in the sidebar,
allowing users to quickly launch preconfigured terminals with custom cwd
and startup commands.

Features:
- Preset buttons in sidebar below window list
- "+ New Preset" button to create presets via modal
- "Save as Preset" in terminal context menu (prefills cwd)
- Right-click to delete presets
- Presets stored in .superset/config.json

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Support both `apps/website` and `./apps/website` as relative paths
- Any path not starting with `/` is treated as relative to worktree
- Match pattern used in workspace setup to avoid command race condition

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 8, 2025

Walkthrough

Introduces terminal preset functionality enabling users to save, retrieve, and manage terminal session configurations. Adds three TRPC procedures for preset management, a preset modal component with form handling, a presets display component with context menu, and a Zustand store for modal state.

Changes

Cohort / File(s) Change Summary
Type definitions and TRPC config
apps/desktop/src/shared/types.ts, apps/desktop/src/lib/trpc/routers/config/config.ts
Added TerminalPreset interface with name, cwd, and commands fields; extended SetupConfig with optional terminalPresets array. Implemented three TRPC procedures: getTerminalPresets, saveTerminalPreset, and deleteTerminalPreset for reading and persisting presets.
Preset modal
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/*
Created PresetModal component with dialog UI for creating presets; manages form state (name, cwd, commands), validates input, calls TRPC to save, and invalidates cache on success. Includes command parsing logic (single vs. multi-line).
Terminal presets display
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/*
Introduced TerminalPresets component fetching presets via TRPC, rendering clickable preset list with per-item delete context menu via PresetContextMenu. Selectable presets create new window panes with preset's cwd and commands. Supports opening preset modal via "New Preset" action.
Preset modal store
apps/desktop/src/renderer/stores/preset-modal.ts
Created Zustand store tracking modal open state, projectId, and prefillCwd with convenience hooks (useOpenPresetModal, useClosePresetModal, etc.).
Component integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx, ...ContentView/TabsContent/TabContentContextMenu.tsx, ...WindowView/WindowPane.tsx
Added optional onSaveAsPreset callback to TabContentContextMenu; wired handler in WindowPane to open preset modal with projectId and cwd context. Injected PresetModal and TerminalPresets into TabsView layout.

Sequence Diagram

sequenceDiagram
    participant User
    participant TabContextMenu as Tab Context Menu
    participant WindowPane
    participant PresetModal
    participant PresetStore as Preset Store
    participant TRPC
    participant ConfigFile as Config File

    User->>TabContextMenu: Right-click terminal tab
    TabContextMenu->>WindowPane: onSaveAsPreset()
    WindowPane->>WindowPane: Read projectId, cwd from context
    WindowPane->>PresetStore: openModal(projectId, cwd)
    PresetStore->>PresetModal: Update state
    PresetModal->>User: Display modal form

    User->>PresetModal: Enter name, commands, submit
    PresetModal->>PresetModal: Validate & parse commands
    PresetModal->>TRPC: config.saveTerminalPreset(projectId, preset)
    TRPC->>ConfigFile: Append preset to project config
    ConfigFile-->>TRPC: Success
    TRPC-->>PresetModal: { success: true }
    PresetModal->>PresetStore: Invalidate cache, closeModal()
    PresetStore->>PresetModal: Close modal
    PresetModal->>User: Modal closed
Loading
sequenceDiagram
    participant User
    participant TerminalPresets
    participant TRPC
    participant ConfigFile as Config File
    participant NewWindowPane as New Window Pane

    User->>TerminalPresets: Load presets view
    TerminalPresets->>TRPC: config.getTerminalPresets(projectId)
    TRPC->>ConfigFile: Read project config
    ConfigFile-->>TRPC: Return presets array
    TRPC-->>TerminalPresets: Return presets
    TerminalPresets->>User: Display preset list

    User->>TerminalPresets: Click on preset
    TerminalPresets->>TerminalPresets: Resolve cwd & normalize commands
    TerminalPresets->>NewWindowPane: Create pane with cwd & initialCommands
    NewWindowPane->>User: Open new terminal with preset config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • TRPC procedures (getTerminalPresets, saveTerminalPreset, deleteTerminalPreset): verify file I/O logic, error handling, and config persistence correctness
  • PresetModal form handling: validate input parsing (multi-line command splitting, trimming), field reset logic, and cache invalidation sequencing
  • Store integration: confirm PresetStore hook usage is consistent across components and state lifecycle is correct
  • Component integration: check conditional rendering and data flow between TabContentContextMenu, WindowPane, TerminalPresets, and modal

Possibly related PRs

Poem

🐰 A rabbit hops through presets saved with glee,
Commands stored neat, cwd and all so clean!
Click, save, and spawn new terminals—spree!
Modal pops up with forms so keen,
Context menus whisper "delete me"... ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 'feat(desktop): add terminal presets system' accurately and concisely describes the main feature addition—a new terminal presets system for the desktop application.
Description check ✅ Passed The PR description covers all required template sections with detailed information: a clear summary of changes, test plan checklist, config schema example, and implementation notes, though formal 'Related Issues' and 'Type of Change' sections are absent.
✨ 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 passing-meadowlark-c80a0e

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 Deployment

🔗 Preview Links

Service Status Link
Database (Neon) View Branch

Preview updates automatically with new commits

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

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx (2)

50-69: Align button enabled state with projectId guard (optional)

handleSave bails out when projectId is falsy, but isValid only checks name and commands. Today projectId is always set when opening the modal, but for extra defense (and clearer intent) you could fold !!projectId into isValid so the Save button is disabled whenever the mutation would early‑return.

- const isValid = name.trim().length > 0 && commands.trim().length > 0;
+ const isValid =
+ 	!!projectId &&
+ 	name.trim().length > 0 &&
+ 	commands.trim().length > 0;

This keeps the UI and handler logic perfectly in sync.


50-67: Optional: guard against very fast double‑clicks on Save

You already disable the Save button while savePresetMutation.isPending, which covers normal use. To be maximally defensive against very fast double‑clicks right before isPending flips, you could add a cheap guard inside handleSave:

const handleSave = () => {
-	if (!projectId || !name.trim() || !commands.trim()) return;
+	if (
+		savePresetMutation.isPending ||
+		!projectId ||
+		!name.trim() ||
+		!commands.trim()
+	)
+		return;

Not critical, but it makes the mutation path robust even under edge‑case clicking.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9810396 and b2ccf66.

📒 Files selected for processing (11)
  • apps/desktop/src/lib/trpc/routers/config/config.ts (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (3 hunks)
  • apps/desktop/src/renderer/stores/preset-modal.ts (1 hunks)
  • apps/desktop/src/shared/types.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/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
  • apps/desktop/src/renderer/stores/preset-modal.ts
  • apps/desktop/src/lib/trpc/routers/config/config.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/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
  • apps/desktop/src/renderer/stores/preset-modal.ts
  • apps/desktop/src/lib/trpc/routers/config/config.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/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
  • apps/desktop/src/renderer/stores/preset-modal.ts
  • apps/desktop/src/lib/trpc/routers/config/config.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/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx
  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
  • apps/desktop/src/renderer/stores/preset-modal.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/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts
  • apps/desktop/src/renderer/stores/preset-modal.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/WindowView/WindowPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx
🧠 Learnings (6)
📚 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/{renderer,shared,preload}/**/*.{ts,tsx} : Never import Node.js modules in renderer process code or shared code in the desktop app

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.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/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/shared/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.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/**/*.{ts,tsx,js,jsx} : For Electron interprocess communication, ALWAYS use tRPC as defined in `src/lib/trpc`

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx
📚 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/Sidebar/TabsView/index.tsx
📚 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/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Applied to files:

  • apps/desktop/src/renderer/stores/preset-modal.ts
📚 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 packages/db/src/schema/**/*.ts : Store database schema in `packages/db/src/schema` using Drizzle ORM

Applied to files:

  • apps/desktop/src/lib/trpc/routers/config/config.ts
🧬 Code graph analysis (6)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (1)
apps/desktop/src/renderer/stores/preset-modal.ts (1)
  • useOpenPresetModal (38-39)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx (6)
apps/desktop/src/renderer/stores/preset-modal.ts (4)
  • usePresetModalOpen (32-33)
  • usePresetModalProjectId (34-35)
  • usePresetModalPrefillCwd (36-37)
  • useClosePresetModal (40-41)
packages/ui/src/components/dialog.tsx (6)
  • Dialog (132-132)
  • DialogContent (134-134)
  • DialogHeader (137-137)
  • DialogTitle (140-140)
  • DialogDescription (135-135)
  • DialogFooter (136-136)
packages/ui/src/components/label.tsx (1)
  • Label (22-22)
packages/ui/src/components/input.tsx (1)
  • Input (29-29)
packages/ui/src/components/textarea.tsx (1)
  • Textarea (18-18)
packages/ui/src/components/button.tsx (1)
  • Button (61-61)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx (1)
packages/ui/src/components/context-menu.tsx (4)
  • ContextMenu (241-241)
  • ContextMenuTrigger (242-242)
  • ContextMenuContent (243-243)
  • ContextMenuItem (244-244)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsx (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx (1)
  • TerminalPresets (16-127)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx (1)
  • PresetModal (22-140)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsx (1)
packages/ui/src/components/context-menu.tsx (2)
  • ContextMenuSeparator (248-248)
  • ContextMenuItem (244-244)
apps/desktop/src/lib/trpc/routers/config/config.ts (1)
apps/desktop/src/shared/types.ts (1)
  • SetupConfig (137-141)
⏰ 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 (21)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/index.ts (1)

1-1: LGTM!

The barrel export follows the component organization guidelines correctly.

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

9-10: LGTM!

The imports are correctly structured for the new preset functionality.


135-142: LGTM!

The conditional rendering properly checks for both workspace ID and project ID, ensuring the presets section only appears when applicable. The positioning within the layout is appropriate.


160-161: LGTM!

The PresetModal is correctly placed outside the LayoutGroup (to avoid animation interference) and manages its own visibility state.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.ts (1)

1-1: LGTM!

The barrel export follows the component organization pattern correctly.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsx (3)

62-64: LGTM!

The TRPC queries are properly structured. The terminalSession query will return undefined if the session doesn't exist yet, which is handled gracefully in handleSaveAsPreset.


120-127: LGTM!

The handler correctly uses optional chaining to safely access the workspace's projectId and terminal's cwd, passing undefined cwd when the session isn't available yet (which the modal accepts as optional).


159-161: LGTM!

The conditional prop passing ensures the "Save as Preset" menu item only appears when a project is associated with the workspace.

apps/desktop/src/shared/types.ts (2)

129-134: LGTM!

The TerminalPreset interface is well-designed with clear documentation. The flexible commands type (string or array) accommodates both single and multi-command presets.


140-140: LGTM!

The optional terminalPresets field integrates cleanly into the existing SetupConfig interface.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsx (1)

14-28: LGTM!

The context menu component is cleanly implemented with appropriate destructive styling for the delete action. The composition pattern with asChild provides flexibility.

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

38-46: LGTM!

The conditional "Save as Preset" menu item is well-integrated with appropriate placement, separator, and icon. The optional prop pattern allows graceful degradation when not applicable.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx (5)

21-38: LGTM!

The queries and mutations are properly configured with appropriate cache invalidation on preset deletion.


58-76: LGTM!

The command normalization and fire-and-forget mutation pattern is well-explained in the comments. The approach correctly avoids a race condition by pre-creating the terminal session before the Terminal component mounts.


79-85: LGTM!

The delete and new preset handlers are straightforward and correctly parameterized.


112-124: LGTM!

The "New Preset" button is appropriately styled and positioned.


93-110: Use a unique identifier for React keys instead of preset names.

Preset names are not enforced to be unique at the backend level. The saveTerminalPreset endpoint simply pushes new presets without validation (line 144 in config.ts), allowing duplicates. Additionally, deleteTerminalPreset filters by name and removes all matching presets, so duplicate names would cause unintended deletions. Using preset.name as the React key will cause rendering issues if duplicate names exist.

Consider either:

  • Enforcing unique preset names with validation in saveTerminalPreset
  • Adding a unique id field to TerminalPreset and using it as the key
  • Preventing duplicate names in the UI before submission
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsx (2)

22-49: Modal state wiring and reset behavior look solid

Using the preset-modal store for isOpen/projectId/prefillCwd plus the useEffect reset on open keeps the form lifecycle predictable. Prefilling cwd from prefillCwd || "" and clearing fields when the modal opens matches the expected “start fresh on each open” behavior and aligns well with the zustand store design.


71-138: Form structure, accessibility, and command normalization look good

Labels are correctly tied to inputs, helper text clarifies cwd semantics and command execution, and the commands normalization (trim + newline split + string vs array coercion) matches the backend contract. The success handler invalidating config.getTerminalPresets before closing the modal ensures the sidebar list stays in sync. No issues here.

apps/desktop/src/lib/trpc/routers/config/config.ts (1)

104-121: getTerminalPresets behavior on missing/invalid config is reasonable

The read path is conservative: it returns an empty array when the project or config file is missing, or when JSON parsing fails. That gives a safe default and avoids throwing from the router in face of user‑edited or partially written config.json. This is a good fit for a “read and render UI” operation.

apps/desktop/src/renderer/stores/preset-modal.ts (1)

1-41: Zustand modal store is clean and matches guidelines

The PresetModal store shape and actions are straightforward, and the convenience hooks (usePresetModalOpen, usePresetModalProjectId, etc.) keep components from over‑subscribing to state. Normalizing prefillCwd to null and fully resetting state in closeModal avoids stale data leaks between modal sessions and aligns with the preference for zustand‑based state.

Comment on lines +123 to +174
// Save a new terminal preset
saveTerminalPreset: publicProcedure
.input(
z.object({
projectId: z.string(),
preset: z.object({
name: z.string(),
cwd: z.string().optional(),
commands: z.union([z.string(), z.array(z.string())]),
}),
}),
)
.mutation(({ input }) => {
const project = db.data.projects.find((p) => p.id === input.projectId);
if (!project) throw new Error("Project not found");

const configPath = ensureConfigExists(project.mainRepoPath);
const content = readFileSync(configPath, "utf-8");
const config = JSON.parse(content) as SetupConfig;

config.terminalPresets = config.terminalPresets || [];
config.terminalPresets.push(input.preset);

writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
return { success: true };
}),

// Delete a terminal preset by name
deleteTerminalPreset: publicProcedure
.input(
z.object({
projectId: z.string(),
presetName: z.string(),
}),
)
.mutation(({ input }) => {
const project = db.data.projects.find((p) => p.id === input.projectId);
if (!project) throw new Error("Project not found");

const configPath = getConfigPath(project.mainRepoPath);
if (!existsSync(configPath)) return { success: false };

const content = readFileSync(configPath, "utf-8");
const config = JSON.parse(content) as SetupConfig;

config.terminalPresets = (config.terminalPresets || []).filter(
(p) => p.name !== input.presetName,
);

writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
return { success: true };
}),
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

Harden JSON read/write error handling in preset mutations

Both saveTerminalPreset and deleteTerminalPreset assume that config.json is valid JSON and readable. If the file is manually edited and broken, or a previous write was truncated, JSON.parse will throw and surface as an unhandled error from the TRPC procedure.

Given that getTerminalPresets and getConfigContent already defend against bad JSON, it would be good to harden the mutation paths similarly so a malformed config doesn’t completely break preset management. For example:

saveTerminalPreset: publicProcedure
  ...
- .mutation(({ input }) => {
+ .mutation(({ input }) => {
    const project = db.data.projects.find((p) => p.id === input.projectId);
    if (!project) throw new Error("Project not found");

-   const configPath = ensureConfigExists(project.mainRepoPath);
-   const content = readFileSync(configPath, "utf-8");
-   const config = JSON.parse(content) as SetupConfig;
-
-   config.terminalPresets = config.terminalPresets || [];
-   config.terminalPresets.push(input.preset);
-
-   writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
-   return { success: true };
+   const configPath = ensureConfigExists(project.mainRepoPath);
+   try {
+     const content = readFileSync(configPath, "utf-8");
+     const config = (JSON.parse(content) as SetupConfig) ?? {};
+
+     config.terminalPresets = config.terminalPresets || [];
+     config.terminalPresets.push(input.preset);
+
+     writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
+     return { success: true };
+   } catch (error) {
+     // Optionally log/annotate for debugging
+     return { success: false };
+   }
  }),

and analogously for deleteTerminalPreset (wrap read/parse/write in a try/catch and return { success: false } on failure).

You might also consider, as a follow‑up:

  • Initializing CONFIG_TEMPLATE to include "terminalPresets": [] for a clearer default shape.
  • Deciding whether preset names should be unique. Right now saveTerminalPreset allows duplicates, and deleteTerminalPreset removes all presets with a matching name.

These are not blockers, but tightening the mutation error handling will make the feature more resilient to config corruption.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +43 to +56
// Resolve cwd - join with worktree path if relative (not starting with /)
let cwd: string | undefined;
if (preset.cwd) {
const isAbsolute = preset.cwd.startsWith("/");
if (isAbsolute) {
cwd = preset.cwd;
} else if (worktreePath) {
// Remove leading ./ if present, then join with worktree path
const relativeCwd = preset.cwd.startsWith("./")
? preset.cwd.slice(2)
: preset.cwd;
cwd = `${worktreePath}/${relativeCwd}`;
}
}
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# First, check the specific file and its imports
echo "=== Checking TerminalPresets.tsx for current imports ==="
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx | head -30

echo -e "\n=== Searching for path handling patterns in renderer code ==="
rg -n --type=ts --type=tsx 'startsWith\("/"' apps/desktop/src/renderer/ apps/desktop/src/shared/ | head -20

echo -e "\n=== Searching for path.isAbsolute usage in codebase ==="
rg -n --type=ts --type=tsx 'path\.isAbsolute|from.*node:path|from "path"' apps/desktop/ | head -20

echo -e "\n=== Checking what Node.js modules are imported in renderer files ==="
rg -n --type=ts --type=tsx 'from.*node:|from "path"|from "fs"|from "os"' apps/desktop/src/renderer/ apps/desktop/src/shared/ | head -20

Repository: superset-sh/superset

Length of output: 1594


🏁 Script executed:

#!/bin/bash

# Search without the type filter for tsx files
echo "=== Searching for startsWith(\"/\") pattern ==="
rg -n 'startsWith\("/"' apps/desktop/src/renderer/ apps/desktop/src/shared/ 2>/dev/null | head -20

echo -e "\n=== Searching for path.isAbsolute usage ==="
rg -n 'path\.isAbsolute' apps/desktop/ 2>/dev/null | head -20

echo -e "\n=== Checking for Windows path handling patterns ==="
rg -n 'startsWith.*[CcDdEe]:\\|\\\\|isAbsolute.*Windows' apps/desktop/ 2>/dev/null | head -20

echo -e "\n=== Checking platform detection or OS-specific code ==="
rg -n 'process\.platform|win32|darwin|linux' apps/desktop/src/renderer/ apps/desktop/src/shared/ 2>/dev/null | head -20

echo -e "\n=== Looking at TerminalPresets context - search for full file ==="
wc -l apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx

echo -e "\n=== Checking TerminalPreset type definition ==="
rg -n 'type TerminalPreset|interface TerminalPreset' apps/desktop/src/shared/ -A 10 | head -30

Repository: superset-sh/superset

Length of output: 4544


🏁 Script executed:

#!/bin/bash

echo "=== Reading full TerminalPresets.tsx to understand context ==="
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx | sed -n '40,65p'

echo -e "\n=== Checking where getTerminalPresets comes from (line 21) ==="
rg -n 'getTerminalPresets' apps/desktop/src/ -A 5 -B 2 | head -40

echo -e "\n=== Checking config router in main process ==="
rg -n 'getTerminalPresets|config.*procedure' apps/desktop/src/main/ -A 3 | head -50

Repository: superset-sh/superset

Length of output: 5441


🏁 Script executed:

#!/bin/bash

echo "=== Checking saveTerminalPreset procedure ==="
rg -n 'saveTerminalPreset|createTerminalPreset' apps/desktop/src/lib/trpc/routers/config/ -A 10 | head -50

echo -e "\n=== Checking workspace type definition ==="
rg -n 'worktreePath' apps/desktop/src/ -B 2 -A 2 | head -60

echo -e "\n=== Checking how workspace is stored/retrieved ==="
rg -n 'getActive.*workspace|worktreePath.*=' apps/desktop/src/main/ -B 2 -A 2 | head -40

Repository: superset-sh/superset

Length of output: 7061


🏁 Script executed:

#!/bin/bash

echo "=== Checking package.json for target platforms ==="
cat apps/desktop/package.json | grep -A 10 -B 2 '"build"' | head -30

echo -e "\n=== Checking if there's a path normalization utility ==="
rg -n 'normalize.*path|path.*normalize' apps/desktop/src/ --type-not=test | head -20

echo -e "\n=== Checking if main process normalizes worktreePath before sending ==="
rg -n 'worktreePath|getActive' apps/desktop/src/lib/trpc/routers/workspaces/ -A 3 -B 2 | head -60

Repository: superset-sh/superset

Length of output: 5452


🏁 Script executed:

#!/bin/bash

echo "=== Checking getWorktreePath implementation ==="
rg -n 'function getWorktreePath|const getWorktreePath|export.*getWorktreePath' apps/desktop/src/ -A 5 | head -30

echo -e "\n=== Looking at full worktreePath creation context ==="
cat -n apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts | sed -n '35,50p'

echo -e "\n=== Checking if Node.js path module is imported in workspaces.ts ==="
head -20 apps/desktop/src/lib/trpc/routers/workspaces/workspaces.ts

Repository: superset-sh/superset

Length of output: 1807


Handle Windows paths in preset cwd resolution.

The code checks preset.cwd.startsWith("/") to detect absolute paths, which only works for Unix-style paths. On Windows, worktreePath is generated using path.join() in the main process, producing backslash-separated paths (e.g., C:\Users\...). When this reaches the renderer, the check fails, and the path is incorrectly treated as relative. Additionally, line 54 uses a hardcoded / separator, creating mixed separators on Windows (e.g., C:\path/src).

Since renderer code cannot import Node.js modules, use a regex pattern to detect absolute paths: preset.cwd.startsWith("/") || /^[a-zA-Z]:/.test(preset.cwd), or consider normalizing paths to forward slashes on the main process before sending them to the renderer.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsx
around lines 43–56, the code only treats POSIX absolute paths (startsWith("/"))
and hardcodes "/" when joining, which breaks on Windows; update the logic to
detect Windows drive-letter absolute paths (e.g. preset.cwd.startsWith("/") ||
/^[a-zA-Z]:/.test(preset.cwd)) and treat those as absolute, and when building a
joined path use the separator that already appears in worktreePath (e.g. if
worktreePath contains "\" use "\" else use "/") or normalize separators before
joining so you don’t hardcode "/" in the renderer.

@Kitenite Kitenite closed this Dec 9, 2025
@Kitenite Kitenite deleted the passing-meadowlark-c80a0e branch December 11, 2025 03:06
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