Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughIntroduces 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
Sequence DiagramsequenceDiagram
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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
🚀 Preview Deployment🔗 Preview Links
Preview updates automatically with new commits |
There was a problem hiding this comment.
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 withprojectIdguard (optional)
handleSavebails out whenprojectIdis falsy, butisValidonly checksnameandcommands. TodayprojectIdis always set when opening the modal, but for extra defense (and clearer intent) you could fold!!projectIdintoisValidso 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 SaveYou already disable the Save button while
savePresetMutation.isPending, which covers normal use. To be maximally defensive against very fast double‑clicks right beforeisPendingflips, you could add a cheap guard insidehandleSave: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
📒 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.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsxapps/desktop/src/shared/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsxapps/desktop/src/renderer/stores/preset-modal.tsapps/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 intsconfig.jsonwhen 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.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsxapps/desktop/src/shared/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsxapps/desktop/src/renderer/stores/preset-modal.tsapps/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
Avoidanytype in TypeScript unless absolutely necessary
Files:
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsxapps/desktop/src/shared/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsxapps/desktop/src/renderer/stores/preset-modal.tsapps/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.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/WindowView/WindowPane.tsxapps/desktop/src/shared/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabContentContextMenu.tsxapps/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.tsapps/desktop/src/shared/types.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/index.tsapps/desktop/src/renderer/stores/preset-modal.ts
**/{components,pages}/**/*.tsx
📄 CodeRabbit inference engine (AGENTS.md)
**/{components,pages}/**/*.tsx: Use one folder per component with structureComponentName/ComponentName.tsxplusindex.tsfor 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.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/PresetModal/PresetModal.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/PresetContextMenu.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/TerminalPresets/TerminalPresets.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/TabsView/index.tsxapps/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.tsapps/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.tsapps/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
terminalSessionquery will return undefined if the session doesn't exist yet, which is handled gracefully inhandleSaveAsPreset.
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
TerminalPresetinterface is well-designed with clear documentation. The flexiblecommandstype (string or array) accommodates both single and multi-command presets.
140-140: LGTM!The optional
terminalPresetsfield integrates cleanly into the existingSetupConfiginterface.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
asChildprovides 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
saveTerminalPresetendpoint simply pushes new presets without validation (line 144 in config.ts), allowing duplicates. Additionally,deleteTerminalPresetfilters by name and removes all matching presets, so duplicate names would cause unintended deletions. Usingpreset.nameas the React key will cause rendering issues if duplicate names exist.Consider either:
- Enforcing unique preset names with validation in
saveTerminalPreset- Adding a unique
idfield toTerminalPresetand 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 solidUsing the preset-modal store for
isOpen/projectId/prefillCwdplus theuseEffectreset on open keeps the form lifecycle predictable. PrefillingcwdfromprefillCwd || ""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 goodLabels 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.getTerminalPresetsbefore 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:getTerminalPresetsbehavior on missing/invalid config is reasonableThe 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 guidelinesThe PresetModal store shape and actions are straightforward, and the convenience hooks (
usePresetModalOpen,usePresetModalProjectId, etc.) keep components from over‑subscribing to state. NormalizingprefillCwdtonulland fully resetting state incloseModalavoids stale data leaks between modal sessions and aligns with the preference for zustand‑based state.
| // 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 }; | ||
| }), |
There was a problem hiding this comment.
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_TEMPLATEto include"terminalPresets": []for a clearer default shape. - Deciding whether preset names should be unique. Right now
saveTerminalPresetallows duplicates, anddeleteTerminalPresetremoves all presets with a matchingname.
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.
| // 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}`; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 -30Repository: 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 -50Repository: 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 -40Repository: 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 -60Repository: 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.tsRepository: 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.
Summary
.superset/config.jsonfor quick-launch preconfigured terminalsConfig Schema
{ "setup": [], "teardown": [], "terminalPresets": [ { "name": "Dev Server", "cwd": "apps/website", "commands": "bun dev" }, { "name": "Test Watch", "commands": ["bun install", "bun test:watch"] } ] }Test plan
apps/websiteand./apps/website).superset/config.json🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.