Prevent sidebar crashes from missing worktree titles (#6127)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson 2026-06-23 11:07:09 -07:00 committed by GitHub
parent 0c8c248199
commit fac7e0440c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 109 additions and 10 deletions

View File

@ -45,7 +45,10 @@ import {
import { WorktreeCardPortsDetails, WorktreeCardPortsTrigger } from './WorktreeCardPorts'
import { writeWorkspaceDragData } from './workspace-status'
import { getWorktreeCardPrDisplay } from './worktree-card-pr-display'
import { getWorktreeCardTitleDisplay } from './worktree-card-title-display'
import {
coerceWorktreeCardVisibleTitle,
getWorktreeCardTitleDisplay
} from './worktree-card-title-display'
import { useWorktreeCardDetailsHoverControl } from './worktree-card-details-hover-state'
import { isEventTargetInsideCurrentTarget } from './worktree-card-dom-events'
import { getWorkspacePortsByWorktreeId } from '@/lib/workspace-port-groups'
@ -505,7 +508,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
issueTitle: issueDisplay?.title,
reviewTitle: prDisplay?.title
})
const visibleCardTitle = newCardStyle ? cardTitleDisplay : worktree.displayName
const legacyCardTitleDisplay = coerceWorktreeCardVisibleTitle(worktree.displayName)
const visibleCardTitle = newCardStyle ? cardTitleDisplay : legacyCardTitleDisplay
const isDeleting = deleteState?.isDeleting ?? false
const deleteModifierPressed = useWorkspaceDeleteModifierPressed()

View File

@ -1,5 +1,8 @@
import { describe, expect, it } from 'vitest'
import { getWorktreeCardTitleDisplay } from './worktree-card-title-display'
import {
coerceWorktreeCardVisibleTitle,
getWorktreeCardTitleDisplay
} from './worktree-card-title-display'
describe('worktree card title display', () => {
it('keeps custom workspace titles', () => {
@ -10,6 +13,14 @@ describe('worktree card title display', () => {
reviewTitle: 'Fix stale PR'
})
).toBe('Custom workspace')
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: ' Custom workspace ',
branchName: 'feature/custom',
reviewTitle: 'Fix stale PR'
})
).toBe(' Custom workspace ')
})
it('uses linked work titles instead of repeating the branch as the card title', () => {
@ -49,4 +60,63 @@ describe('worktree card title display', () => {
})
).toBe('test454545')
})
it('uses linked work titles when the stored title is nullish and the branch is usable', () => {
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: undefined,
branchName: 'feature/local-branch',
reviewTitle: 'Fix stale GH PR'
})
).toBe('Fix stale GH PR')
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: null,
branchName: 'feature/local-branch',
issueTitle: 'Fix stale issue'
})
).toBe('Fix stale issue')
})
it('treats blank stored titles as absent', () => {
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: ' ',
branchName: 'feature/local-branch'
})
).toBe('')
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: '',
branchName: 'feature/local-branch',
linearIssueTitle: 'Fix stale Linear issue'
})
).toBe('Fix stale Linear issue')
})
it('skips linked-title replacement when the branch name is nullish or blank', () => {
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: 'Custom workspace',
branchName: undefined,
reviewTitle: 'Fix stale GH PR'
})
).toBe('Custom workspace')
expect(
getWorktreeCardTitleDisplay({
storedDisplayName: null,
branchName: ' ',
reviewTitle: 'Fix stale GH PR'
})
).toBe('')
})
it('coerces legacy visible titles before downstream title operations', () => {
expect(coerceWorktreeCardVisibleTitle(undefined).trim()).toBe('')
expect(coerceWorktreeCardVisibleTitle(null).trim()).toBe('')
expect(coerceWorktreeCardVisibleTitle(' Custom workspace ')).toBe(' Custom workspace ')
})
})

View File

@ -1,11 +1,16 @@
type WorktreeCardTitleDisplayInput = {
storedDisplayName: string
branchName: string
storedDisplayName: string | null | undefined
branchName: string | null | undefined
linearIssueTitle?: string | null
issueTitle?: string | null
reviewTitle?: string | null
}
function normalizeComparableTitle(value: string | null | undefined): string | null {
const trimmed = value?.trim()
return trimmed ? trimmed : null
}
function normalizeTitle(value: string | null | undefined): string | null {
const trimmed = value?.trim()
if (!trimmed) {
@ -17,8 +22,17 @@ function normalizeTitle(value: string | null | undefined): string | null {
return trimmed
}
function isBranchTitle(displayName: string, branchName: string): boolean {
return displayName.trim() === branchName.trim()
function isBranchTitle(
normalizedDisplayName: string | null,
normalizedBranchName: string | null
): boolean {
return normalizedDisplayName !== null && normalizedDisplayName === normalizedBranchName
}
export function coerceWorktreeCardVisibleTitle(value: string | null | undefined): string {
// Why: the legacy card path can bypass title selection but still feeds trim()
// and inline rename props, so nullish persisted titles stop at this boundary.
return typeof value === 'string' ? value : ''
}
export function getWorktreeCardTitleDisplay({
@ -28,8 +42,19 @@ export function getWorktreeCardTitleDisplay({
issueTitle,
reviewTitle
}: WorktreeCardTitleDisplayInput): string {
if (!branchName || !isBranchTitle(storedDisplayName, branchName)) {
return storedDisplayName
const normalizedStoredDisplayName = normalizeComparableTitle(storedDisplayName)
const normalizedBranchName = normalizeComparableTitle(branchName)
const visibleStoredDisplayName = coerceWorktreeCardVisibleTitle(storedDisplayName)
if (!normalizedBranchName) {
return normalizedStoredDisplayName ? visibleStoredDisplayName : ''
}
if (
normalizedStoredDisplayName &&
!isBranchTitle(normalizedStoredDisplayName, normalizedBranchName)
) {
return visibleStoredDisplayName
}
// Why: branch names are available in hover/details; the closed card title
@ -38,6 +63,6 @@ export function getWorktreeCardTitleDisplay({
normalizeTitle(linearIssueTitle) ??
normalizeTitle(issueTitle) ??
normalizeTitle(reviewTitle) ??
storedDisplayName
(normalizedStoredDisplayName ? visibleStoredDisplayName : '')
)
}