Prevent sidebar crashes from missing worktree titles (#6127)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
0c8c248199
commit
fac7e0440c
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 : '')
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue