Auto-prompt workspace rename for unnamed workspaces#1151
Conversation
📝 WalkthroughWalkthroughThis PR introduces an Changes
Sequence DiagramsequenceDiagram
participant Terminal as Terminal Component
participant Query as Workspace Query
participant Lifecycle as Terminal Lifecycle Hook
participant Mutation as Update Mutation
participant Cache as Query Cache
Terminal->>Query: useQuery(workspace.isUnnamed)
Query-->>Terminal: isUnnamed status
Lifecycle->>Lifecycle: onTitleChange(newTitle)
Lifecycle->>Terminal: renameUnnamedWorkspaceRef.current(title)
alt isUnnamed && title is valid
Terminal->>Terminal: cleanTitle(stripEmoji)
Terminal->>Mutation: updateWorkspace({<br/>name: cleanTitle,<br/>preserveUnnamedStatus: true})
Mutation->>Cache: invalidateQueries
Cache-->>Terminal: cache updated
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx`:
- Around line 186-192: The code currently calls autoRenamedWorkspaces.add(id)
before the delayed rename, which marks the workspace prompted even if the
timeout is cancelled; move the autoRenamedWorkspaces.add(id) call inside the
timeout callback immediately after rename.startRename() so the id is recorded
only when the rename actually runs (update the block around the setTimeout
callback that calls rename.startRename(), and keep the existing
clearTimeout(timer) cleanup as-is).
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx (1)
189-191: Extract the rename delay into a named constant.The
100ms delay is a magic number; please lift it to a module-level constant for clarity and future tuning.♻️ Proposed refactor
+const AUTO_RENAME_DELAY_MS = 100;- const timer = setTimeout(() => { + const timer = setTimeout(() => { rename.startRename(); - }, 100); + }, AUTO_RENAME_DELAY_MS);As per coding guidelines: Avoid magic numbers by extracting them to named constants at module top.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx`:
- Around line 171-194: The effect in WorkspaceListItem.tsx uses a magic number
100 for the auto-rename timeout and depends on rename.startRename which is
recreated each render; extract the delay into constants.ts as AUTO_RENAME_DELAY
and import it into WorkspaceListItem (replace the inline 100 with
AUTO_RENAME_DELAY), and modify the useWorkspaceRename hook to wrap startRename
in useCallback (so startRename is stable and won't cause the effect to re-run);
reference the symbols AUTO_RENAME_DELAY, WorkspaceListItem (the effect using
setTimeout), and useWorkspaceRename.startRename when making these changes.
| // Auto-trigger rename for unnamed workspaces (name is null/empty) when active | ||
| // Only for worktree workspaces (not branch workspaces) | ||
| const isUnnamed = !name; | ||
| useEffect(() => { | ||
| if ( | ||
| isUnnamed && | ||
| isActive && | ||
| !isBranchWorkspace && | ||
| !isCollapsed && | ||
| !rename.isRenaming | ||
| ) { | ||
| const timer = setTimeout(() => { | ||
| rename.startRename(); | ||
| }, 100); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [ | ||
| isUnnamed, | ||
| isActive, | ||
| isBranchWorkspace, | ||
| isCollapsed, | ||
| rename.isRenaming, | ||
| rename.startRename, | ||
| ]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and check the constants file for this component
find . -path "*WorkspaceSidebar*" -name "constants.ts" -type fRepository: superset-sh/superset
Length of output: 245
🏁 Script executed:
# Find the useWorkspaceRename hook
find . -name "*useWorkspaceRename*" -type fRepository: superset-sh/superset
Length of output: 151
🏁 Script executed:
# Check if AUTO_RENAME_DELAY is already defined anywhere in the workspace sidebar
rg "AUTO_RENAME_DELAY" --glob "**/WorkspaceSidebar/**"Repository: superset-sh/superset
Length of output: 46
🏁 Script executed:
# Check the WorkspaceListItem constants file
cat -n ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/constants.tsRepository: superset-sh/superset
Length of output: 615
🏁 Script executed:
# Check the parent WorkspaceSidebar constants file
cat -n ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/constants.tsRepository: superset-sh/superset
Length of output: 400
🏁 Script executed:
# Check the useWorkspaceRename hook implementation
cat -n ./apps/desktop/src/renderer/screens/main/hooks/useWorkspaceRename/useWorkspaceRename.tsRepository: superset-sh/superset
Length of output: 2042
🏁 Script executed:
# Verify the WorkspaceListItem component imports and uses these
head -100 ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx | grep -E "import|useWorkspaceRename"Repository: superset-sh/superset
Length of output: 1362
🏁 Script executed:
# Check if useCallback is imported in useWorkspaceRename or if it should be
grep -n "useCallback\|import.*React" ./apps/desktop/src/renderer/screens/main/hooks/useWorkspaceRename/useWorkspaceRename.tsRepository: superset-sh/superset
Length of output: 46
🏁 Script executed:
# Verify the full imports at the top of WorkspaceListItem to see what's imported from constants
head -80 ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx | grep -A 10 "from \"./constants\""Repository: superset-sh/superset
Length of output: 351
🏁 Script executed:
# Double-check the useWorkspaceRename implementation and verify startRename is indeed not memoized
grep -E "const startRename|useCallback" ./apps/desktop/src/renderer/screens/main/hooks/useWorkspaceRename/useWorkspaceRename.tsRepository: superset-sh/superset
Length of output: 92
🏁 Script executed:
# Get the full import statement from constants to see current imports
sed -n '24,35p' ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxRepository: superset-sh/superset
Length of output: 318
🏁 Script executed:
# Search for the import statement that includes constants from "./constants"
rg "from \"./constants\"" -A 8 ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxRepository: superset-sh/superset
Length of output: 313
🏁 Script executed:
# Get the complete import block from constants
rg -B 8 "from \"./constants\"" ./apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx | head -20Repository: superset-sh/superset
Length of output: 275
Extract the timeout delay to a named constant.
The 100 ms delay is a magic number. For consistency with other constants in this component (e.g., HOVER_CARD_OPEN_DELAY), extract it to ./constants.ts.
Add to ./constants.ts:
export const AUTO_RENAME_DELAY = 100;Then update the import in WorkspaceListItem.tsx:
import {
GITHUB_STATUS_STALE_TIME,
HOVER_CARD_CLOSE_DELAY,
HOVER_CARD_OPEN_DELAY,
MAX_KEYBOARD_SHORTCUT_INDEX,
AUTO_RENAME_DELAY,
} from "./constants";And replace the magic number:
const timer = setTimeout(() => {
rename.startRename();
}, AUTO_RENAME_DELAY);Wrap startRename in useCallback in the hook.
The startRename function in useWorkspaceRename is recreated on every render. Since it's included in the effect dependency array in WorkspaceListItem, the effect will re-run unnecessarily. Add useCallback:
import { useCallback, useEffect, useRef, useState } from "react";
export function useWorkspaceRename(workspaceId: string, workspaceName: string) {
// ...existing state...
const startRename = useCallback(() => {
setIsRenaming(true);
}, []);
// ...rest of the function...
}🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx`
around lines 171 - 194, The effect in WorkspaceListItem.tsx uses a magic number
100 for the auto-rename timeout and depends on rename.startRename which is
recreated each render; extract the delay into constants.ts as AUTO_RENAME_DELAY
and import it into WorkspaceListItem (replace the inline 100 with
AUTO_RENAME_DELAY), and modify the useWorkspaceRename hook to wrap startRename
in useCallback (so startRename is stable and won't cause the effect to re-run);
reference the symbols AUTO_RENAME_DELAY, WorkspaceListItem (the effect using
setTimeout), and useWorkspaceRename.startRename when making these changes.
07a4eef to
dc89bb5
Compare
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/desktop/src/lib/trpc/routers/workspaces/procedures/create.ts (1)
381-388:⚠️ Potential issue | 🟡 MinorNormalize provided name before deriving
isUnnamed.
Ifinput.nameis whitespace,namebecomes blank whileisUnnamedbecomes false, preventing the rename prompt. Trim once and use the normalized value for bothnameandisUnnamed.✅ Suggested fix (apply in both create & openWorktree paths)
- const workspace = localDb + const trimmedName = input.name?.trim(); + const workspace = localDb .insert(workspaces) .values({ projectId: input.projectId, worktreeId: worktree.id, type: "worktree", branch, - name: input.name ?? branch, - isUnnamed: !input.name, + name: trimmedName || branch, + isUnnamed: !trimmedName, tabOrder: maxTabOrder + 1, })- const workspace = localDb + const trimmedName = input.name?.trim(); + const workspace = localDb .insert(workspaces) .values({ projectId: worktree.projectId, worktreeId: worktree.id, type: "worktree", branch: worktree.branch, - name: input.name ?? worktree.branch, - isUnnamed: !input.name, + name: trimmedName || worktree.branch, + isUnnamed: !trimmedName, tabOrder: maxTabOrder + 1, })Also applies to: 590-596
🤖 Fix all issues with AI agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx`:
- Around line 109-129: The effect in WorkspaceListItem is re-triggering
startRename() after a cancel because there is no session-level guard; add a
session-scoped Set<string> (e.g., promptedUnnamedWorkspaces) in a parent/store
and pass it (or access it) to this component, then modify the useEffect in
WorkspaceListItem to check that the workspace id is not in
promptedUnnamedWorkspaces before scheduling rename.startRename(), and add the
workspace id to promptedUnnamedWorkspaces when the rename prompt is shown or
when rename.cancelRename() is invoked so repeated prompts are suppressed; update
any caller of rename.cancelRename() (or the rename handler) to record the id in
the session Set.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`:
- Around line 38-64: The rename mutation swallows errors and the query uses a
magic number; modify electronTrpc.workspaces.update.useMutation
(updateWorkspace) to add an onError handler that logs the error with a prefixed
message like "[workspaces/update] Failed to rename workspace: <error>" and any
relevant context (workspaceId, attempted name) and keep the existing onSuccess
logic; replace the hardcoded 30_000 in electronTrpc.workspaces.get.useQuery with
a named constant (e.g., WORKSPACE_QUERY_STALE_TIME) declared at the top of the
module and reference that constant when creating the query; ensure the logging
follows the prefixed console pattern and update any references to isUnnamedRef
and renameUnnamedWorkspaceRef remain unchanged.
| useEffect(() => { | ||
| if ( | ||
| isUnnamed && | ||
| isActive && | ||
| !isBranchWorkspace && | ||
| !isCollapsed && | ||
| !rename.isRenaming | ||
| ) { | ||
| const timer = setTimeout(() => { | ||
| rename.startRename(); | ||
| }, 100); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [ | ||
| isUnnamed, | ||
| isActive, | ||
| isBranchWorkspace, | ||
| isCollapsed, | ||
| rename.isRenaming, | ||
| rename.startRename, | ||
| ]); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if session-level tracking exists
echo "=== Searching for rename tracking mechanisms ==="
rg -n "autoRenamed|promptedWorkspace|renamedWorkspace|trackedWorkspace" --type=ts --type=tsx -g '!node_modules'
echo ""
echo "=== Searching for Set-based tracking in WorkspaceSidebar ==="
rg -n "new Set|Set<" apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/ --type=ts --type=tsx
echo ""
echo "=== Checking useWorkspaceRename hook implementation ==="
fd -t f "useWorkspaceRename" apps/desktop --exec cat {}Repository: superset-sh/superset
Length of output: 1835
🏁 Script executed:
#!/bin/bash
# Get the full WorkspaceListItem.tsx file
wc -l apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx
echo ""
echo "=== Full file content ==="
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsxRepository: superset-sh/superset
Length of output: 25157
Implement session-level tracking to prevent repeated rename prompts.
The session-level tracking mechanism mentioned in the PR description is not implemented. When a user dismisses the rename dialog via cancelRename() (ESC key or blur), isRenaming becomes false, which re-triggers the effect condition. Since the effect depends on rename.isRenaming, it will schedule another startRename() after 100ms, creating an unwanted loop of rename prompts until the user actually submits a rename.
Add a session-level Set<string> (e.g., in a parent component or store) to track which unnamed workspaces have already been prompted, and check this set before calling startRename() in the effect.
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/WorkspaceListItem.tsx`
around lines 109 - 129, The effect in WorkspaceListItem is re-triggering
startRename() after a cancel because there is no session-level guard; add a
session-scoped Set<string> (e.g., promptedUnnamedWorkspaces) in a parent/store
and pass it (or access it) to this component, then modify the useEffect in
WorkspaceListItem to check that the workspace id is not in
promptedUnnamedWorkspaces before scheduling rename.startRename(), and add the
workspace id to promptedUnnamedWorkspaces when the rename prompt is shown or
when rename.cancelRename() is invoked so repeated prompts are suppressed; update
any caller of rename.cancelRename() (or the rename handler) to record the id in
the session Set.
| // Query workspace to check if it's unnamed | ||
| const { data: workspaceData } = electronTrpc.workspaces.get.useQuery( | ||
| { id: workspaceId }, | ||
| { staleTime: 30_000 }, | ||
| ); | ||
| const isUnnamedRef = useRef(false); | ||
| isUnnamedRef.current = workspaceData?.isUnnamed ?? false; | ||
|
|
||
| // Mutation to rename workspace | ||
| const utils = electronTrpc.useUtils(); | ||
| const updateWorkspace = electronTrpc.workspaces.update.useMutation({ | ||
| onSuccess: () => { | ||
| utils.workspaces.getAllGrouped.invalidate(); | ||
| utils.workspaces.get.invalidate({ id: workspaceId }); | ||
| }, | ||
| }); | ||
|
|
||
| // Callback ref to rename unnamed workspace when terminal title changes | ||
| const renameUnnamedWorkspaceRef = useRef<(title: string) => void>(() => {}); | ||
| renameUnnamedWorkspaceRef.current = (title: string) => { | ||
| if (isUnnamedRef.current && title.trim()) { | ||
| updateWorkspace.mutate({ | ||
| id: workspaceId, | ||
| patch: { name: title.trim() }, | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Handle rename mutation errors and avoid the staleTime magic number.
The rename mutation has no error handling, so failures are silent. Also, 30_000 should be a named constant.
🔧 Suggested fix
+const WORKSPACE_QUERY_STALE_TIME_MS = 30_000;
+
const { data: workspaceData } = electronTrpc.workspaces.get.useQuery(
{ id: workspaceId },
- { staleTime: 30_000 },
+ { staleTime: WORKSPACE_QUERY_STALE_TIME_MS },
);
@@
const updateWorkspace = electronTrpc.workspaces.update.useMutation({
onSuccess: () => {
utils.workspaces.getAllGrouped.invalidate();
utils.workspaces.get.invalidate({ id: workspaceId });
},
+ onError: (error) => {
+ console.error(
+ "[workspace/rename] Failed to rename workspace",
+ error,
+ );
+ },
});As per coding guidelines: Never swallow errors silently; at minimum log them with context. Use prefixed console logging with pattern [domain/operation] message for all logging. Avoid magic numbers by extracting them to named constants at module top.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Query workspace to check if it's unnamed | |
| const { data: workspaceData } = electronTrpc.workspaces.get.useQuery( | |
| { id: workspaceId }, | |
| { staleTime: 30_000 }, | |
| ); | |
| const isUnnamedRef = useRef(false); | |
| isUnnamedRef.current = workspaceData?.isUnnamed ?? false; | |
| // Mutation to rename workspace | |
| const utils = electronTrpc.useUtils(); | |
| const updateWorkspace = electronTrpc.workspaces.update.useMutation({ | |
| onSuccess: () => { | |
| utils.workspaces.getAllGrouped.invalidate(); | |
| utils.workspaces.get.invalidate({ id: workspaceId }); | |
| }, | |
| }); | |
| // Callback ref to rename unnamed workspace when terminal title changes | |
| const renameUnnamedWorkspaceRef = useRef<(title: string) => void>(() => {}); | |
| renameUnnamedWorkspaceRef.current = (title: string) => { | |
| if (isUnnamedRef.current && title.trim()) { | |
| updateWorkspace.mutate({ | |
| id: workspaceId, | |
| patch: { name: title.trim() }, | |
| }); | |
| } | |
| }; | |
| const WORKSPACE_QUERY_STALE_TIME_MS = 30_000; | |
| // Query workspace to check if it's unnamed | |
| const { data: workspaceData } = electronTrpc.workspaces.get.useQuery( | |
| { id: workspaceId }, | |
| { staleTime: WORKSPACE_QUERY_STALE_TIME_MS }, | |
| ); | |
| const isUnnamedRef = useRef(false); | |
| isUnnamedRef.current = workspaceData?.isUnnamed ?? false; | |
| // Mutation to rename workspace | |
| const utils = electronTrpc.useUtils(); | |
| const updateWorkspace = electronTrpc.workspaces.update.useMutation({ | |
| onSuccess: () => { | |
| utils.workspaces.getAllGrouped.invalidate(); | |
| utils.workspaces.get.invalidate({ id: workspaceId }); | |
| }, | |
| onError: (error) => { | |
| console.error( | |
| "[workspace/rename] Failed to rename workspace", | |
| error, | |
| ); | |
| }, | |
| }); | |
| // Callback ref to rename unnamed workspace when terminal title changes | |
| const renameUnnamedWorkspaceRef = useRef<(title: string) => void>(() => {}); | |
| renameUnnamedWorkspaceRef.current = (title: string) => { | |
| if (isUnnamedRef.current && title.trim()) { | |
| updateWorkspace.mutate({ | |
| id: workspaceId, | |
| patch: { name: title.trim() }, | |
| }); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx`
around lines 38 - 64, The rename mutation swallows errors and the query uses a
magic number; modify electronTrpc.workspaces.update.useMutation
(updateWorkspace) to add an onError handler that logs the error with a prefixed
message like "[workspaces/update] Failed to rename workspace: <error>" and any
relevant context (workspaceId, attempted name) and keep the existing onSuccess
logic; replace the hardcoded 30_000 in electronTrpc.workspaces.get.useQuery with
a named constant (e.g., WORKSPACE_QUERY_STALE_TIME) declared at the top of the
module and reference that constant when creating the query; ensure the logging
follows the prefixed console pattern and update any references to isUnnamedRef
and renameUnnamedWorkspaceRef remain unchanged.
- Add preserveUnnamedStatus flag to workspace update mutation - Auto-rename from terminal title preserves isUnnamed: true - Only manual rename sets isUnnamed: false - Revert sidebar UI changes (no auto-prompt for rename) - Add migration for is_unnamed column
Filter out leading emoji/symbol characters (e.g., "✳ Casual Greeting" becomes "Casual Greeting") when auto-renaming workspaces from terminal title.
The isUnnamed field was not included in the touchWorkspace additionalFields type, preventing it from being updated.
Migration 0016 was already taken by telemetry_enabled in main.
Description
When a worktree workspace is created without a user-provided name, the app now automatically triggers the rename UI when the workspace becomes active for the first time. This improves the UX by prompting users to give their workspaces meaningful names right after creation.
Type of Change
Implementation Details
Uses a session-level
Set<string>to track which workspaces have already been prompted, preventing re-triggers if the user dismisses the rename dialog. The check detects unnamed workspaces by checking!nameand only applies to worktree workspaces (not branch workspaces).Summary by CodeRabbit
New Features
Database