diff --git a/config/react-compiler-bailout-baseline.txt b/config/react-compiler-bailout-baseline.txt
index b2c752cee6c..e2f3ac9e4a3 100644
--- a/config/react-compiler-bailout-baseline.txt
+++ b/config/react-compiler-bailout-baseline.txt
@@ -401,7 +401,6 @@
1 src/renderer/src/components/settings/browser/extensions/BrowserExtensionsSection.tsx
1 src/renderer/src/components/settings/browser/extensions/BrowserExtensionsWebStoreInstall.tsx
1 src/renderer/src/components/settings/browser/profiles/BrowserNewProfileDialog.tsx
-1 src/renderer/src/components/settings/chat/ChatNamingSetting.tsx
1 src/renderer/src/components/settings/cli/install/CliSection.tsx
3 src/renderer/src/components/settings/cli/install/WslCliRegistration.tsx
2 src/renderer/src/components/settings/cli/install/use-cli-registration-actions.ts
@@ -509,8 +508,6 @@
1 src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneCardList.tsx
1 src/renderer/src/components/sidebar/workspace-board/viewport/WorkspaceKanbanLaneGrid.tsx
3 src/renderer/src/components/sidebar/workspace-board/viewport/use-workspace-kanban-column-resize.ts
-1 src/renderer/src/components/sidebar/workspace-card/activity/WorktreeCardAgents.tsx
-1 src/renderer/src/components/sidebar/workspace-card/activity/useWorktreeAgentRows.ts
3 src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-compact-agent-row.tsx
2 src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-compact-agents.tsx
1 src/renderer/src/components/sidebar/workspace-card/identity/AutoRenameFailedDialog.tsx
diff --git a/config/scripts/locale-catalog/locale-ko-key-overrides.json b/config/scripts/locale-catalog/locale-ko-key-overrides.json
index c5808babd64..4b700a64caf 100644
--- a/config/scripts/locale-catalog/locale-ko-key-overrides.json
+++ b/config/scripts/locale-catalog/locale-ko-key-overrides.json
@@ -1742,12 +1742,6 @@
"auto.components.settings.ExperimentalPane.agentHibernation.toggleLabel": {
"ko": "에이전트 최대 절전 전환"
},
- "auto.components.settings.ExperimentalPane.newWorktreeCardStyle.copy": {
- "ko": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다."
- },
- "auto.components.settings.ExperimentalPane.newWorktreeCardStyle.description": {
- "ko": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다."
- },
"auto.components.settings.EphemeralVmsPane.recipes": {
"ko": "레시피"
},
@@ -2585,9 +2579,6 @@
"auto.components.settings.experimental.search.agentHibernation.title": {
"ko": "에이전트 최대 절전"
},
- "auto.components.settings.experimental.search.newWorktreeCardStyle.description": {
- "ko": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다."
- },
"auto.components.settings.general.search.12ecc640a8": {
"ko": "mru"
},
diff --git a/src/main/runtime/client/settings/runtime-client-settings-shape.ts b/src/main/runtime/client/settings/runtime-client-settings-shape.ts
index 88df33e74c3..cb86e2fdfd2 100644
--- a/src/main/runtime/client/settings/runtime-client-settings-shape.ts
+++ b/src/main/runtime/client/settings/runtime-client-settings-shape.ts
@@ -25,7 +25,6 @@ export type RuntimeClientSettings = Pick<
| 'defaultLinearTeamSelection'
| 'githubProjects'
| 'experimentalNewWorktreeCardStyle'
- | 'worktreeCardStyle'
| 'worktreeCardDetailsRow'
| 'compactWorktreeCards'
| 'minimaxGroupId'
@@ -77,7 +76,6 @@ export type RuntimeClientSettingsUpdate = Pick<
| 'defaultLinearTeamSelection'
| 'githubProjects'
| 'experimentalNewWorktreeCardStyle'
- | 'worktreeCardStyle'
| 'worktreeCardDetailsRow'
| 'compactWorktreeCards'
| 'minimaxGroupId'
diff --git a/src/main/runtime/client/settings/runtime-client-settings.ts b/src/main/runtime/client/settings/runtime-client-settings.ts
index 5dda749b732..bb5f03f96c1 100644
--- a/src/main/runtime/client/settings/runtime-client-settings.ts
+++ b/src/main/runtime/client/settings/runtime-client-settings.ts
@@ -13,7 +13,6 @@ import { haveSameDisabledTuiAgents } from '../../../../shared/agent-launch/catal
import { normalizeSourceControlAiSettings } from '../../../../shared/source-control/ai/settings/source-control-ai-settings'
import { normalizeSourceControlGroupOrder } from '../../../../shared/source-control/listing/source-control-group-order'
import { SOURCE_CONTROL_LAUNCH_ACTION_IDS } from '../../../../shared/source-control/ai/recipes/source-control-ai-actions'
-import { getWorktreeCardStyle } from '../../../../shared/worktree/card-style'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { applyNativeChatSessionOptionSettingsMutation } from '../../../../shared/native-chat/input/native-chat-session-option-defaults'
import type { NativeChatSessionOptionSettingsMutation } from '../../../../shared/native-chat/input/native-chat-session-options'
@@ -92,8 +91,6 @@ export class RuntimeClientSettingsController {
: null,
githubProjects: settings.githubProjects,
experimentalNewWorktreeCardStyle: settings.experimentalNewWorktreeCardStyle === true,
- // Always present, so a client can tell this host accepts the three-way choice.
- worktreeCardStyle: getWorktreeCardStyle(settings),
worktreeCardDetailsRow: settings.worktreeCardDetailsRow !== false,
compactWorktreeCards: settings.compactWorktreeCards === true,
minimaxGroupId: settings.minimaxGroupId ?? '',
diff --git a/src/main/runtime/client/settings/runtime-workspace-view-change-events.ts b/src/main/runtime/client/settings/runtime-workspace-view-change-events.ts
index 3e1003d1f66..b1fd9c112f6 100644
--- a/src/main/runtime/client/settings/runtime-workspace-view-change-events.ts
+++ b/src/main/runtime/client/settings/runtime-workspace-view-change-events.ts
@@ -9,7 +9,6 @@ import type { RuntimeStore } from '../../construction/runtime-store-contract'
const WORKSPACE_VIEW_SETTINGS: readonly (keyof GlobalSettings)[] = [
'compactWorktreeCards',
'experimentalNewWorktreeCardStyle',
- 'worktreeCardStyle',
'worktreeCardDetailsRow',
'showPinnedWorktreesInGroups'
]
diff --git a/src/main/runtime/construction/runtime-store-contract.ts b/src/main/runtime/construction/runtime-store-contract.ts
index ea77ef7a205..26469e02989 100644
--- a/src/main/runtime/construction/runtime-store-contract.ts
+++ b/src/main/runtime/construction/runtime-store-contract.ts
@@ -168,7 +168,6 @@ export type RuntimeStore = {
defaultLinearTeamSelection?: GlobalSettings['defaultLinearTeamSelection']
githubProjects?: GlobalSettings['githubProjects']
experimentalNewWorktreeCardStyle?: GlobalSettings['experimentalNewWorktreeCardStyle']
- worktreeCardStyle?: GlobalSettings['worktreeCardStyle']
worktreeCardDetailsRow?: GlobalSettings['worktreeCardDetailsRow']
compactWorktreeCards?: GlobalSettings['compactWorktreeCards']
minimaxGroupId?: GlobalSettings['minimaxGroupId']
diff --git a/src/renderer/src/components/activity/activity-thread-hover-card.tsx b/src/renderer/src/components/activity/activity-thread-hover-card.tsx
index a09e34fa5d2..3794aa85c88 100644
--- a/src/renderer/src/components/activity/activity-thread-hover-card.tsx
+++ b/src/renderer/src/components/activity/activity-thread-hover-card.tsx
@@ -174,7 +174,6 @@ function ActivityThreadHoverCardContent({
linkedAzureDevOpsPR: review.linkedAzureDevOpsPR,
linkedGiteaPR: review.linkedGiteaPR,
newCardStyle: foundation.newCardStyle,
- worktreeCardStyle: foundation.worktreeCardStyle,
compactCards: foundation.compactCards,
cardDetails: foundation.cardDetails,
agentActivityDisplayMode: foundation.agentActivityDisplayMode,
diff --git a/src/renderer/src/components/settings/advanced/ExperimentalPane.tsx b/src/renderer/src/components/settings/advanced/ExperimentalPane.tsx
index fad5c4d38b8..e04aafc4feb 100644
--- a/src/renderer/src/components/settings/advanced/ExperimentalPane.tsx
+++ b/src/renderer/src/components/settings/advanced/ExperimentalPane.tsx
@@ -1,8 +1,3 @@
-import {
- getWorktreeCardStyle,
- worktreeCardStyleSettingsUpdate,
- type WorktreeCardStyle
-} from '../../../../../shared/worktree/card-style'
import type { GlobalSettings } from '../../../../../shared/global-settings-types'
import { Label } from '../../ui/label'
import { Switch } from '../../ui/switch'
@@ -54,7 +49,6 @@ export function ExperimentalPane({
getExperimentalSearchEntry().newWorktreeCardStyle
])
const agentHibernationEnabled = settings.experimentalAgentHibernation === true
- const worktreeCardStyle = getWorktreeCardStyle(settings)
// Use the planner’s clamped millisecond value/default while editing minutes.
const agentHibernationIdleMinutes = Math.round(
getEffectiveAgentHibernationIdleMs(settings.agentHibernationIdleMs) / MS_PER_MINUTE
@@ -239,10 +233,14 @@ export function ExperimentalPane({
)}
-
+
size="sm"
- value={worktreeCardStyle}
- onChange={(style) => updateSettings(worktreeCardStyleSettingsUpdate(style))}
+ value={
+ settings.experimentalNewWorktreeCardStyle === true ? 'experimental' : 'classic'
+ }
+ onChange={(style) =>
+ updateSettings({ experimentalNewWorktreeCardStyle: style === 'experimental' })
+ }
ariaLabel={t(
'auto.components.settings.ExperimentalPane.worktreeCardStyle.ariaLabel',
'Card style'
@@ -256,7 +254,7 @@ export function ExperimentalPane({
)
},
{
- value: 'experimental-1',
+ value: 'experimental',
label: t(
'auto.components.settings.ExperimentalPane.worktreeCardStyle.experimental',
'Experimental'
diff --git a/src/renderer/src/components/settings/source-control/use-global-text-action-recipe-draft.ts b/src/renderer/src/components/settings/source-control/use-global-text-action-recipe-draft.ts
index d576fcfdc6a..cea43fe8c4a 100644
--- a/src/renderer/src/components/settings/source-control/use-global-text-action-recipe-draft.ts
+++ b/src/renderer/src/components/settings/source-control/use-global-text-action-recipe-draft.ts
@@ -110,6 +110,7 @@ export function useGlobalTextActionRecipeDraft({
: {}),
...(customCommandDirty ? { customAgentCommand: customCommand } : {})
}))
+ setIsSaving(false)
if (!isMountedRef.current) {
return
}
@@ -121,9 +122,8 @@ export function useGlobalTextActionRecipeDraft({
setCustomCommandDraft(hasNewCommandEdits ? latestValue.customCommand : null)
onDirtyChangeRef.current?.(hasNewEdits || hasNewCommandEdits)
} catch (error) {
- onSaveError(error)
- } finally {
setIsSaving(false)
+ onSaveError(error)
}
}
diff --git a/src/renderer/src/components/sidebar/WorktreeCardExperimental.fixture.tsx b/src/renderer/src/components/sidebar/WorktreeCardExperimental.fixture.tsx
index 57844969e6d..c488a120100 100644
--- a/src/renderer/src/components/sidebar/WorktreeCardExperimental.fixture.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCardExperimental.fixture.tsx
@@ -238,7 +238,6 @@ function Card({
lineageCollapsed={collapsed}
lineageDescendantIdentities={descendantIdentities}
lineageChildWorktreeIds={descendantIds}
- lineageChildIdentities={descendantIdentities}
lineageChildren={children}
lineageChildrenStyle={rows.getLineageChildrenInlineStyle(
rows.LINEAGE_CHILDREN_INLINE_OFFSET
diff --git a/src/renderer/src/components/sidebar/WorktreeCardSlot.tsx b/src/renderer/src/components/sidebar/WorktreeCardSlot.tsx
index d81fbe6bd42..2466e28dcac 100644
--- a/src/renderer/src/components/sidebar/WorktreeCardSlot.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCardSlot.tsx
@@ -1,8 +1,7 @@
import React from 'react'
-import { Activity, ChevronRight, CircleX } from 'lucide-react'
+import { CircleX } from 'lucide-react'
import { AgentQuestionIcon } from '@/components/AgentQuestionIcon'
-import { AgentWorkingSpinner } from '@/components/AgentWorkingSpinner'
-import { translate } from '@/i18n/i18n'
+import { useTranslate, type Translate } from '@/i18n/use-translate'
import { formatShortTimeAgo } from '@/lib/time-format/short-time-ago'
import { cn } from '@/lib/class-names'
import type { SlotException, SlotSignal } from './worktree-card-slot-signal'
@@ -13,108 +12,59 @@ function formatSlotTime(at: number | null, now: number): string {
return at === null ? '' : formatShortTimeAgo(at, now)
}
-export function slotLabel(signal: SlotSignal): string | undefined {
+function slotLabel(signal: SlotSignal, t: Translate): string | undefined {
switch (signal.kind) {
case 'needs':
- return translate('sidebar.cardSlot.needsYouLabel', 'Waiting on you')
+ return t('sidebar.cardSlot.needsYouLabel', 'Waiting on you')
case 'failed':
- return translate('sidebar.cardSlot.failedLabel', 'Last turn failed')
+ return t('sidebar.cardSlot.failedLabel', 'Last turn failed')
case 'unread':
return signal.manual
- ? translate('sidebar.cardSlot.markedUnread', 'Marked unread by you')
- : translate('sidebar.cardSlot.unread', 'Unread result')
+ ? t('sidebar.cardSlot.markedUnread', 'Marked unread by you')
+ : t('sidebar.cardSlot.unread', 'Unread result')
case 'working':
return signal.monitoring
- ? translate('sidebar.cardSlot.monitoring', 'Monitoring')
- : translate('sidebar.cardSlot.working', 'Working')
+ ? t('sidebar.cardSlot.monitoring', 'Monitoring')
+ : t('sidebar.cardSlot.working', 'Working')
case 'offline':
- return translate('sidebar.cardSlot.offlineLabel', 'Host offline')
+ return t('sidebar.cardSlot.offlineLabel', 'Host offline')
case 'time':
case 'quiet':
return undefined
}
}
-/** The one status slot at a row's right edge: the first exception, otherwise the time. */
+/** The title's time, or its host's offline state. */
export function WorktreeCardSlot({
signal,
now,
- stale = false,
timeTone
}: {
- signal: SlotSignal
+ signal: Extract
now: number
- /** The host is offline: show what was last seen, never a live state. */
- stale?: boolean
- /** A plain time beside an unread or working glyph reads like that state's own time. */
timeTone?: 'unread' | 'working'
-}): React.JSX.Element | null {
- if (signal.kind === 'quiet') {
- return null
- }
- const base =
- 'inline-flex min-w-[22px] shrink-0 items-center justify-end gap-1 whitespace-nowrap text-3xs leading-none tabular-nums'
- const label = slotLabel(signal)
- const slotProps = { 'data-worktree-card-slot': signal.kind, title: label }
-
- if (stale || signal.kind === 'offline') {
- return (
-
- {signal.kind === 'offline'
- ? translate('sidebar.cardSlot.offline', 'offline')
- : 'at' in signal
- ? formatSlotTime(signal.at, now)
- : null}
-
- )
- }
-
- switch (signal.kind) {
- case 'needs':
- return
- case 'failed':
- return (
-
- {/* A crossed circle, not a red dot, so failed never reads like the blue unread dot. */}
-
- {translate('sidebar.cardSlot.failed', 'Failed')}
-
- )
- case 'unread':
- return (
-
-
- {formatSlotTime(signal.at, now)}
-
- )
- case 'working':
- return (
-
- {signal.monitoring ? (
-
- ) : (
-
- )}
- {formatSlotTime(signal.at, now)}
-
- )
- case 'time':
- return (
-
- {formatSlotTime(signal.at, now)}
-
- )
- }
+}): React.JSX.Element {
+ const t = useTranslate()
+ return (
+
+ {signal.kind === 'offline'
+ ? t('sidebar.cardSlot.offline', 'offline')
+ : formatSlotTime(signal.at, now)}
+
+ )
}
function titleTimeTone(exceptions: readonly SlotException[]): 'unread' | 'working' | undefined {
@@ -127,11 +77,12 @@ function titleTimeTone(exceptions: readonly SlotException[]): 'unread' | 'workin
type PillSignal = Extract
/** The loud statuses get a labelled pill, so a card that needs you is hard to miss. */
-export function WorktreeCardStatusPill({ signal }: { signal: PillSignal }): React.JSX.Element {
+function WorktreeCardStatusPill({ signal }: { signal: PillSignal }): React.JSX.Element {
+ const t = useTranslate()
return (
)}
{signal.kind === 'failed'
- ? translate('sidebar.cardSlot.failed', 'Failed')
+ ? t('sidebar.cardSlot.failed', 'Failed')
: signal.count > 1
- ? translate('sidebar.cardSlot.needYouCount', '{{count}} need you', {
+ ? t('sidebar.cardSlot.needYouCount', '{{count}} need you', {
count: signal.count
})
- : translate('sidebar.cardSlot.needsYou', 'Needs you')}
+ : t('sidebar.cardSlot.needsYou', 'Needs you')}
)
}
@@ -165,6 +116,7 @@ export function WorktreeCardTitlePills({
exceptions: readonly SlotException[]
resolveTargets: (kind: SlotException['kind']) => SlotTarget[]
}): React.JSX.Element | null {
+ const t = useTranslate()
const pills = exceptions.filter(
(exception): exception is PillSignal =>
exception.kind === 'needs' || exception.kind === 'failed'
@@ -177,7 +129,7 @@ export function WorktreeCardTitlePills({
{pills.map((pill) => (
resolveTargets(pill.kind)}
className="-my-0.5 inline-flex min-w-0 items-center"
>
@@ -188,76 +140,20 @@ export function WorktreeCardTitlePills({
)
}
-export type TitleTimeToggle = {
- open: boolean
- label: string
- disabled: boolean
- onPointerDown: React.PointerEventHandler
- onToggle: (event: React.MouseEvent) => void
-}
-
-/**
- * The time. On a card that opens it is also the open/close button: hover or keyboard focus
- * shows the chevron in its place, and a tap on the time works where there is no hover.
- */
export function WorktreeCardTitleTime({
exceptions,
at,
- now,
- toggle,
- disclosureOnly = false
+ now
}: {
exceptions: readonly SlotException[]
at: number | null
now: number
- toggle: TitleTimeToggle | null
- disclosureOnly?: boolean
}): React.JSX.Element {
- const time = (
+ return (
)
- if (toggle === null) {
- return time
- }
- return (
-
- )
}
diff --git a/src/renderer/src/components/sidebar/workspace-card/activity/use-worktree-agent-list-model.ts b/src/renderer/src/components/sidebar/workspace-card/activity/use-worktree-agent-list-model.ts
index 142395e560e..5cef249f4ea 100644
--- a/src/renderer/src/components/sidebar/workspace-card/activity/use-worktree-agent-list-model.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/activity/use-worktree-agent-list-model.ts
@@ -6,7 +6,7 @@ import { useAppStore } from '@/store'
import { deriveRunningAgentSendTargets } from '@/lib/agent-session/messaging/running-agent-targets'
import type { DashboardAgentRow as DashboardAgentRowData } from '@/components/dashboard/useDashboardData'
import { buildAgentRowLineageTree } from '@/components/dashboard/agent-row-lineage-model'
-import { translate } from '@/i18n/i18n'
+import { useTranslate } from '@/i18n/use-translate'
import { agentSendDisabledReasonLabel } from '@/lib/agent-session/messaging/agent-send-disabled-reason-label'
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
import {
@@ -40,6 +40,7 @@ export function useWorktreeAgentListModel(
agents: DashboardAgentRowData[],
executionHostId?: ExecutionHostId
) {
+ const t = useTranslate()
const languageRevision = useUiLanguageRevision()
const { agentActivityDisplayMode, showAgentModels } = useWorktreeAgentDisplay()
const dropAgentStatus = useAppStore((s) => s.dropAgentStatus)
@@ -73,7 +74,7 @@ export function useWorktreeAgentListModel(
const sendTargetsByPaneKey = useMemo(
() =>
recomputeOn([agentStatusEpoch, languageRevision], () => {
- if (!isAgentSendTargetModeActive) {
+ if (!agentSendPopoverTargetMode) {
return new Map<
string,
{ status: 'eligible' | 'disabled' | 'sending'; disabledReason?: string }
@@ -87,7 +88,7 @@ export function useWorktreeAgentListModel(
agentSendPopoverTargetMode.sendingPaneKey === target.paneKey
? {
status: 'sending' as const,
- disabledReason: translate(
+ disabledReason: t(
'auto.components.sidebar.WorktreeCardAgents.sendingTarget',
'Sending...'
)
@@ -105,9 +106,8 @@ export function useWorktreeAgentListModel(
// Freshness timers advance this epoch without replacing the status map.
agentStatusEpoch,
languageRevision,
- agentSendPopoverTargetMode?.sendingPaneKey,
- agentSendPopoverTargetMode?.status,
- isAgentSendTargetModeActive,
+ t,
+ agentSendPopoverTargetMode,
sendTargetInputs,
worktreeId
]
@@ -162,7 +162,7 @@ export function useWorktreeAgentListModel(
isAgentSendTargetModeActive
? (sendTargetsByPaneKey.get(paneKey) ?? {
status: 'disabled' as const,
- disabledReason: translate(
+ disabledReason: t(
'auto.components.sidebar.WorktreeCardAgents.agentUnavailable',
'Agent is not available'
)
diff --git a/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-agents-expansion-state.ts b/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-agents-expansion-state.ts
index 53dcc355269..92cd17eec26 100644
--- a/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-agents-expansion-state.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-agents-expansion-state.ts
@@ -1,4 +1,4 @@
-import { useCallback, useEffect, useState } from 'react'
+import { useCallback, useSyncExternalStore } from 'react'
export type WorktreeAgentExpansionState = {
// Child lists start collapsed so spawning subagents cannot grow the card and shift the sidebar.
@@ -26,6 +26,7 @@ function trimPersistedExpansions(): void {
break
}
expansionByWorktreeId.delete(oldest)
+ notifyExpansion(oldest)
}
}
@@ -98,34 +99,21 @@ export type WorktreeAgentExpansionControls = {
/** Restore each worktree’s disclosures across virtualizer and sibling-lineage remounts. */
export function useWorktreeAgentExpansionState(worktreeId: string): WorktreeAgentExpansionControls {
- const [rendered, setRendered] = useState<{
- worktreeId: string
- state: WorktreeAgentExpansionState
- }>(() => ({ worktreeId, state: readExpansionState(worktreeId) }))
-
- // Memoized bodies can change worktree without remounting; avoid displaying the previous card’s disclosure.
- const current =
- rendered.worktreeId === worktreeId ? rendered.state : readExpansionState(worktreeId)
+ const subscribe = useCallback(
+ (listener: () => void) => subscribeExpansion(worktreeId, listener),
+ [worktreeId]
+ )
+ const getSnapshot = useCallback(() => readExpansionState(worktreeId), [worktreeId])
+ const current = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
const commit = useCallback(
(next: WorktreeAgentExpansionState) => {
persistExpansionState(worktreeId, next)
- setRendered({ worktreeId, state: next })
- // The agent list and the title's roll-up both read this state; keep every mounted reader in step.
notifyExpansion(worktreeId)
},
[worktreeId]
)
- // A mounted card picks up an outside unfold; an unmounted one reads it from the cache on mount.
- useEffect(
- () =>
- subscribeExpansion(worktreeId, () =>
- setRendered({ worktreeId, state: readExpansionState(worktreeId) })
- ),
- [worktreeId]
- )
-
const toggleLineageParent = useCallback(
(paneKey: string) => {
const base = readExpansionState(worktreeId)
@@ -164,10 +152,6 @@ export function clearWorktreeAgentExpansionStateForTests(): void {
expansionByWorktreeId.clear()
}
-export function getWorktreeAgentExpansionCountForTests(): number {
- return expansionByWorktreeId.size
-}
-
export function seedWorktreeAgentExpansionStateForTests(
worktreeId: string,
state: WorktreeAgentExpansionState
diff --git a/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-idle-child-agents.tsx b/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-idle-child-agents.tsx
index 9d625e16793..549b47770eb 100644
--- a/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-idle-child-agents.tsx
+++ b/src/renderer/src/components/sidebar/workspace-card/activity/worktree-card-idle-child-agents.tsx
@@ -70,8 +70,8 @@ type IdleChildAgentGroupRowProps = {
count: number
expanded: boolean
onToggle: () => void
- // Full-mode child rows use a wider inset; match it so the dots share one column. Slot = the 2.0 card's lines.
- layout: 'compact' | 'full' | 'slot'
+ // Full-mode child rows use a wider inset; match it so the dots share one column.
+ layout: 'compact' | 'full'
// Sits among treeitem siblings, so it joins the tree at their level.
ariaLevel: number
}
@@ -107,11 +107,8 @@ export function IdleChildAgentGroupRow({
draggable={false}
className={cn(
'worktree-agent-row-hover',
- layout === 'slot'
- ? 'flex h-[22px] w-full min-w-0 items-center gap-1.5 rounded-sm pr-1.5 pl-1 text-left text-2xs leading-none'
- : 'flex h-6 min-w-0 items-center gap-1 rounded-sm text-left text-[11px] leading-none',
- layout === 'full' && '-ml-1 w-[calc(100%+0.25rem)] pl-1 pr-2',
- layout === 'compact' && 'w-full px-1',
+ 'flex h-6 min-w-0 items-center gap-1 rounded-sm text-left text-[11px] leading-none',
+ layout === 'full' ? '-ml-1 w-[calc(100%+0.25rem)] pl-1 pr-2' : 'w-full px-1',
// Group header is meta, not an agent: use the trail tone so it doesn't read as a row name.
'text-muted-foreground/65 tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-worktree-sidebar-ring'
)}
@@ -154,7 +151,7 @@ type ChildAgentsWithIdleGroupProps = {
flat: boolean
idleExpanded: boolean
onToggleIdle: () => void
- layout: 'compact' | 'full' | 'slot'
+ layout: 'compact' | 'full'
ariaLevel: number
renderChild: (childAgent: DashboardAgentRowData, idleGroupExpanded: boolean) => React.ReactNode
}
@@ -184,7 +181,7 @@ export function ChildAgentsWithIdleGroup({
layout={layout}
ariaLevel={ariaLevel}
/>
- {layout !== 'full' ? (
+ {layout === 'compact' ? (
{idleChildren.map((childAgent) => renderChild(childAgent, idleExpanded))}
diff --git a/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-controller.ts b/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-controller.ts
index c0335c270d5..6b69c13d76c 100644
--- a/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-controller.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-controller.ts
@@ -147,7 +147,6 @@ export function useWorktreeCardController(props: ResolvedWorktreeCardProps) {
linkedAzureDevOpsPR: review.linkedAzureDevOpsPR,
linkedGiteaPR: review.linkedGiteaPR,
newCardStyle: foundation.newCardStyle,
- worktreeCardStyle: foundation.worktreeCardStyle,
compactCards: foundation.compactCards,
cardDetails: foundation.cardDetails,
agentActivityDisplayMode: foundation.agentActivityDisplayMode,
diff --git a/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-foundation.ts b/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-foundation.ts
index 5e40b0bef61..82602820425 100644
--- a/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-foundation.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/controller/use-worktree-card-foundation.ts
@@ -2,10 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { DEFAULT_AGENT_ACTIVITY_DISPLAY_MODE } from '../../../../../../shared/constants'
import { getWorktreeCardDetails } from '../../../../../../shared/worktree/row-display/card-properties'
-import {
- getWorktreeCardStyle,
- isExperimentalWorktreeCardStyle
-} from '../../../../../../shared/worktree/card-style'
import {
isRuntimeOwnedSshTargetId,
parseExecutionHostId,
@@ -47,8 +43,7 @@ export function useWorktreeCardFoundation({
const agentActivityDisplayMode =
useAppStore((s) => s.agentActivityDisplayMode) ?? DEFAULT_AGENT_ACTIVITY_DISPLAY_MODE
const projectGroups = useAppStore((s) => s.projectGroups)
- const worktreeCardStyle = getWorktreeCardStyle(settings)
- const newCardStyle = isExperimentalWorktreeCardStyle(worktreeCardStyle)
+ const newCardStyle = settings?.experimentalNewWorktreeCardStyle === true
const compactWorktreeCards = settings?.compactWorktreeCards === true
const cardDetails = useMemo(
() => getWorktreeCardDetails(cardProps, { compactWorktreeCards, newCardStyle }),
@@ -230,7 +225,6 @@ export function useWorktreeCardFoundation({
cardProps,
agentActivityDisplayMode,
projectGroups,
- worktreeCardStyle,
newCardStyle,
compactCards,
cardDetails,
diff --git a/src/renderer/src/components/sidebar/workspace-card/controller/worktree-card-model.ts b/src/renderer/src/components/sidebar/workspace-card/controller/worktree-card-model.ts
index 4f57acffc96..eb1670a4953 100644
--- a/src/renderer/src/components/sidebar/workspace-card/controller/worktree-card-model.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/controller/worktree-card-model.ts
@@ -4,7 +4,6 @@ import type React from 'react'
import type { Repo } from '../../../../../../shared/repo-types'
import type { WorkspaceStatus, Worktree } from '../../../../../../shared/worktree/types'
-import type { HostScopedWorktreeKey } from '../../../../../../shared/worktree/host-qualified-identity'
import type { WorktreeCardPrDisplay } from '../review/worktree-card-pr-display'
import { translate } from '@/i18n/i18n'
import type { Translate } from '@/i18n/use-translate'
@@ -38,7 +37,6 @@ export type WorktreeCardProps = {
flushSurface?: boolean
lineageChildCount?: number
lineageChildWorktreeIds?: readonly string[]
- lineageChildIdentities?: readonly HostScopedWorktreeKey[]
lineageCollapsed?: boolean
/** Workspaces nested under this one; the 2.0 card rolls up their latest time and, when collapsed, their exceptions. */
lineageDescendantIdentities?: WorktreeLineageSource
diff --git a/src/renderer/src/components/sidebar/workspace-card/details/worktree-card-secondary-details-inputs.ts b/src/renderer/src/components/sidebar/workspace-card/details/worktree-card-secondary-details-inputs.ts
index 36833e04ef0..04fe26b97be 100644
--- a/src/renderer/src/components/sidebar/workspace-card/details/worktree-card-secondary-details-inputs.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/details/worktree-card-secondary-details-inputs.ts
@@ -14,7 +14,6 @@ export type WorktreeCardSecondaryDetailsInputs = Pick<
Pick<
Foundation,
| 'newCardStyle'
- | 'worktreeCardStyle'
| 'compactCards'
| 'cardDetails'
| 'agentActivityDisplayMode'
diff --git a/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-derivation.ts b/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-derivation.ts
index 93ea9a81f69..940503e9304 100644
--- a/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-derivation.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/review/worktree-card-pr-derivation.ts
@@ -137,21 +137,3 @@ export function deriveWorktreeCardPrDisplay(
)
return { prDisplay, cachedBranchFallbackGitHubPRNumber }
}
-
-/** A worktree's review read straight from the store, for cards that aren't mounted (hidden children). */
-export function selectWorktreeCardPrDisplay(
- state: AppState,
- worktree: Worktree,
- repo: Repo | undefined
-): WorktreeCardPrDisplay | null {
- const { hostedReviewCacheKey, prCacheKey } = getWorktreeReviewCacheKeys(
- worktree,
- repo,
- state.settings
- )
- return deriveWorktreeCardPrDisplay(
- worktree,
- hostedReviewCacheKey ? state.hostedReviewCache[hostedReviewCacheKey] : undefined,
- prCacheKey ? state.prCache?.[prCacheKey] : undefined
- ).prDisplay
-}
diff --git a/src/renderer/src/components/sidebar/workspace-card/review/worktree-review-presentation.tsx b/src/renderer/src/components/sidebar/workspace-card/review/worktree-review-presentation.tsx
index c8bd6b09533..e544449f944 100644
--- a/src/renderer/src/components/sidebar/workspace-card/review/worktree-review-presentation.tsx
+++ b/src/renderer/src/components/sidebar/workspace-card/review/worktree-review-presentation.tsx
@@ -55,7 +55,7 @@ function getCheckTone(
)
}
-export function getReviewStateTone(state: WorktreeCardPrDisplay['state']): string {
+function getStateTone(state: WorktreeCardPrDisplay['state']): string {
if (state === 'merged') {
return 'text-purple-600/70 dark:text-purple-400/70'
}
@@ -71,12 +71,6 @@ export function getReviewStateTone(state: WorktreeCardPrDisplay['state']): strin
return 'text-muted-foreground opacity-70'
}
-export function getReviewTone(
- review: Pick
-): string {
- return getCheckTone(review) ?? getReviewStateTone(review.state)
-}
-
export function ReviewIcon({
review,
className,
@@ -93,6 +87,9 @@ export function ReviewIcon({
variant === 'provider' && review.provider === 'gitlab' ? GitMerge : PullRequestIcon
const Icon = getReviewStateIcon(review.state) ?? providerIcon
return createElement(Icon, {
- className: cn(className, showChecks ? getReviewTone(review) : getReviewStateTone(review.state))
+ className: cn(
+ className,
+ (showChecks ? getCheckTone(review) : null) ?? getStateTone(review.state)
+ )
})
}
diff --git a/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.tsx b/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.tsx
index a4c23d0ecb2..6c3a2e5ca31 100644
--- a/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.tsx
+++ b/src/renderer/src/components/sidebar/workspace-card/surface/WorktreeCard.tsx
@@ -35,7 +35,6 @@ const WorktreeCard = React.memo(function WorktreeCard({
flushSurface = false,
lineageChildCount = 0,
lineageChildWorktreeIds,
- lineageChildIdentities,
lineageCollapsed = false,
lineageDescendantIdentities,
lineageChildren,
@@ -74,7 +73,6 @@ const WorktreeCard = React.memo(function WorktreeCard({
flushSurface,
lineageChildCount,
lineageChildWorktreeIds,
- lineageChildIdentities,
lineageCollapsed,
lineageDescendantIdentities,
lineageChildren,
diff --git a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-frame-geometry.ts b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-frame-geometry.ts
index 52e4c39b88a..dfa643dc207 100644
--- a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-frame-geometry.ts
+++ b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-frame-geometry.ts
@@ -8,16 +8,14 @@ import {
export function getWorktreeCardFrameGeometry({
flushSurface,
contentIndent,
- statusLane,
- minContentInset
+ statusLane
}: {
flushSurface: boolean
contentIndent: number
statusLane: boolean
- minContentInset?: number
}): { cardStyle: React.CSSProperties | undefined; parentContentMarginLeft: number } {
const cardPaddingLeft = flushSurface
- ? getFlushWorktreeCardPaddingLeft(contentIndent, statusLane, minContentInset)
+ ? getFlushWorktreeCardPaddingLeft(contentIndent, statusLane)
: contentIndent > 0
? `calc(0.125rem + ${contentIndent}px)`
: null
diff --git a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-option-items.tsx b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-option-items.tsx
index a30db6d229f..a3de6e26bb9 100644
--- a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-option-items.tsx
+++ b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-option-items.tsx
@@ -19,7 +19,6 @@ import {
getWorktreeCardDisplayOptions
} from '../../worktree-list/controls/sidebar-workspace-option-items'
import { useTranslate } from '@/i18n/use-translate'
-import { getWorktreeCardStyle } from '../../../../../../shared/worktree/card-style'
type CardMenuItemProps = {
preserveWorkspaceBoardOpen: boolean
@@ -69,7 +68,7 @@ export function WorktreeCardDisplayMenuItem({
const hasProjectGroups = useAppStore((s) => s.projectGroups.length > 0)
const toggleProperty = useToggleCardProperty()
const options = useMemo(() => getWorktreeCardDisplayOptions(hasProjectGroups), [hasProjectGroups])
- const experimentalCard = useAppStore((s) => getWorktreeCardStyle(s.settings) !== 'classic')
+ const experimentalCard = useAppStore((s) => s.settings?.experimentalNewWorktreeCardStyle === true)
const detailsRow = useAppStore((s) => s.settings?.worktreeCardDetailsRow !== false)
const updateSettings = useAppStore((s) => s.updateSettings)
return (
diff --git a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-title-status.tsx b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-title-status.tsx
index f750fbcfabb..09f1e3ac769 100644
--- a/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-title-status.tsx
+++ b/src/renderer/src/components/sidebar/workspace-card/surface/worktree-card-title-status.tsx
@@ -9,7 +9,6 @@ import { selectAgentRowsUnread } from '../activity/worktree-card-agent-ack-input
import { selectSendTargetControlInputs } from '../activity/worktree-card-send-target-inputs'
import { useWorktreeAgentExpansionState } from '../activity/worktree-card-agents-expansion-state'
import type { WorktreeCardController } from '../controller/use-worktree-card-controller'
-import { getWorktreeCardHost } from '../../worktree-card-host-name'
import {
collectLineageSlotTargets,
useCollapsedLineageSlots,
@@ -41,7 +40,6 @@ type TitleStatus = {
/** What the title speaks for: hidden agents, collapsed child workspaces, the workspace's own unread. */
exceptions: readonly SlotException[]
at: number | null
- now: number
resolveTargets: (kind: SlotException['kind']) => SlotTarget[]
}
@@ -67,9 +65,7 @@ export function WorktreeCardTitleStatusProvider({
const { worktree, showInlineAgentList, lineageCollapsed, lineageDescendantIdentities } = card
const agents = card.inlineAgentRows
const signals = useSlotAgentSignals(agents)
- const now = useNow(30_000)
- const host = getWorktreeCardHost(card)
- const offline = card.isRuntimeDisconnected || (host?.kind === 'ssh' && host.down)
+ const offline = card.isRuntimeDisconnected || card.isSshDisconnected
const markedByUser = useAppStore((s) => Boolean(s.workspacesMarkedUnreadByUser[worktree.id]))
const pickingSendTarget = useAppStore(
(s) => selectSendTargetControlInputs(s, worktree.id).targetMode !== null
@@ -78,10 +74,10 @@ export function WorktreeCardTitleStatusProvider({
// Compact mode folds two or more agents behind a summary row until it is expanded.
const foldedBehindSummary =
card.agentActivityDisplayMode === 'compact' &&
- signals.rootLines.length > 1 &&
+ signals.rootCount > 1 &&
!compactRootListExpanded &&
!pickingSendTarget
- const linesShown = showInlineAgentList && signals.rootLines.length > 0 && !foldedBehindSummary
+ const linesShown = showInlineAgentList && signals.rootCount > 0 && !foldedBehindSummary
const descendants = lineageDescendantIdentities ?? NO_DESCENDANTS
const hiddenDescendants = lineageCollapsed ? descendants : NO_DESCENDANTS
const collapsedLineage = useCollapsedLineageSlots(hiddenDescendants)
@@ -126,8 +122,8 @@ export function WorktreeCardTitleStatusProvider({
const exceptions = slot.kind === 'status' ? slot.exceptions : NO_EXCEPTIONS
const at = slot.kind === 'status' ? slot.at : null
const value = useMemo(
- () => ({ offline, exceptions, at, now, resolveTargets }),
- [at, exceptions, now, offline, resolveTargets]
+ () => ({ offline, exceptions, at, resolveTargets }),
+ [at, exceptions, offline, resolveTargets]
)
return {children}
}
@@ -141,11 +137,12 @@ export function useWorktreeCardRolledUpUnread(): boolean {
/** The title row's right edge: a pill per loud status the card speaks for, then the time. */
export function WorktreeCardTitleStatus(): React.JSX.Element | null {
const status = useContext(TitleStatusContext)
+ const now = useNow(30_000)
if (!status) {
return null
}
if (status.offline) {
- return
+ return
}
return (
<>
@@ -153,12 +150,7 @@ export function WorktreeCardTitleStatus(): React.JSX.Element | null {
exceptions={status.exceptions}
resolveTargets={status.resolveTargets}
/>
-
+
>
)
}
diff --git a/src/renderer/src/components/sidebar/worktree-card-host-name.tsx b/src/renderer/src/components/sidebar/worktree-card-host-name.tsx
deleted file mode 100644
index 300ac959479..00000000000
--- a/src/renderer/src/components/sidebar/worktree-card-host-name.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import React from 'react'
-import { Server, ServerOff } from 'lucide-react'
-import { translate } from '@/i18n/i18n'
-import { cn } from '@/lib/class-names'
-import type { WorktreeCardController } from './workspace-card/controller/use-worktree-card-controller'
-
-export type WorktreeCardHost = {
- label: string
- kind: 'ssh' | 'runtime'
- /** The link to the host is down; agent state on the card is the last seen. */
- down: boolean
-}
-
-/** The computer a remote workspace runs on; local workspaces have none. */
-export function getWorktreeCardHost(card: WorktreeCardController): WorktreeCardHost | null {
- const {
- repo,
- parsedRepoHost,
- sshTargetLabel,
- sshStatus,
- isRuntimeDisconnected,
- runtimeHostLabel
- } = card
- if (repo?.connectionId) {
- return {
- label: sshTargetLabel || repo.displayName,
- kind: 'ssh',
- down: sshStatus != null && sshStatus !== 'connected'
- }
- }
- if (parsedRepoHost?.kind === 'runtime') {
- return {
- label: runtimeHostLabel ?? translate('sidebar.cardSlot.orcaServer', 'Orca server'),
- kind: 'runtime',
- down: isRuntimeDisconnected
- }
- }
- return null
-}
-
-function hostTooltip(host: WorktreeCardHost): string {
- if (host.kind === 'runtime') {
- return host.down
- ? translate(
- 'auto.components.sidebar.WorktreeCard.runtimeHostDisconnectedNamed',
- '{{hostName}} disconnected',
- { hostName: host.label }
- )
- : translate(
- 'auto.components.sidebar.WorktreeCard.runtimeHostProjectNamed',
- 'Project on {{hostName}}',
- {
- hostName: host.label
- }
- )
- }
- return translate(
- 'auto.components.sidebar.WorktreeCardSshHostControl.connectedName',
- 'Project on SSH host {{value0}}',
- { value0: host.label }
- )
-}
-
-/** A server glyph after the title marks a remote card; the machine name is in its tooltip so titles keep their width. */
-export function WorktreeCardHostName({ host }: { host: WorktreeCardHost }): React.JSX.Element {
- const Icon = host.down ? ServerOff : Server
- const label = hostTooltip(host)
- return (
-
-
-
- )
-}
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-agent-signals.ts b/src/renderer/src/components/sidebar/worktree-card-slot-agent-signals.ts
index bce9a4acacd..86cbc3c69e6 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-agent-signals.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-agent-signals.ts
@@ -1,50 +1,31 @@
-import type { AgentDotState } from '@/components/AgentStateDot'
import type { AgentRowState } from '../../../../shared/agent-status/presentation/agent-row-decay-state'
import type { AgentChildRowModel } from '../../../../shared/agent-status/presentation/agent-child-row-model'
import type { DashboardAgentRow } from '@/components/dashboard/useDashboardData'
import { buildAgentRowLineageTree } from '@/components/dashboard/agent-row-lineage-model'
import { agentRowDisplayDotState } from '@/lib/agent-status/presentation/agent-row-dot-state'
import { resolveRowProgramStatus } from '@/lib/program-status/agent-row-presentation'
-import {
- classifySlotLine,
- compareSlotLines,
- slotLineAt,
- type SlotLineSignal
-} from './worktree-card-slot-signal'
+import { classifySlotLine, slotLineAt, type SlotLineSignal } from './worktree-card-slot-signal'
export type SlotAgentLine = {
agent: DashboardAgentRow
- dotState: AgentDotState
signal: SlotLineSignal
}
export type SlotAgentSignals = {
- /** Lead agents, most urgent first by their own and their subagents' state. */
- rootLines: SlotAgentLine[]
- childrenByParentPaneKey: ReadonlyMap
- lineByPaneKey: ReadonlyMap
- /** The most urgent of an agent and its subagents; a folded lead reports this. */
- rolledByPaneKey: ReadonlyMap
- /** Every agent's own signal, subagents included. */
+ rootCount: number
+ lines: SlotAgentLine[]
allSignals: SlotLineSignal[]
}
-const EXCEPTION_KINDS: ReadonlySet = new Set([
- 'needs',
- 'failed',
- 'unread',
- 'working'
-])
-
/** One agent's dot and slot kind, program-status blocks included; visible lines and collapsed rollups both read this. */
-export function projectSlotAgentLine(
+function projectSlotAgentLine(
agent: {
state: AgentRowState
entry: DashboardAgentRow['entry']
childRow?: Pick
},
unreadCompletion: boolean
-): { dotState: AgentDotState; kind: SlotLineSignal['kind'] } {
+) {
const { presentation, dotStateOverride } = resolveRowProgramStatus(agent)
const dotState = dotStateOverride ?? agentRowDisplayDotState(agent)
return {
@@ -62,64 +43,16 @@ export function buildSlotAgentSignals(
agents: readonly DashboardAgentRow[],
unread: readonly boolean[]
): SlotAgentSignals {
- const lineByPaneKey = new Map()
- agents.forEach((agent, index) => {
+ const lines = agents.map((agent, index): SlotAgentLine => {
const { dotState, kind } = projectSlotAgentLine(agent, unread[index] ?? false)
- lineByPaneKey.set(agent.paneKey, {
+ return {
agent,
- dotState,
signal: { kind, at: slotLineAt(agent, kind), monitoring: dotState === 'monitoring' }
- })
+ }
})
-
- const { rootRows, childrenByParentPaneKey } = buildAgentRowLineageTree([...agents])
- const rolledByPaneKey = new Map()
- const visiting = new Set()
- const rolled = (paneKey: string): SlotLineSignal | null => {
- const cached = rolledByPaneKey.get(paneKey)
- if (cached) {
- return cached
- }
- const own = lineByPaneKey.get(paneKey)?.signal ?? null
- // Lineage metadata may cycle; a pane never counts its own subtree twice.
- if (!own || visiting.has(paneKey)) {
- return own
- }
- visiting.add(paneKey)
- let best = own
- for (const child of childrenByParentPaneKey.get(paneKey) ?? []) {
- const childSignal = rolled(child.paneKey)
- if (
- childSignal &&
- EXCEPTION_KINDS.has(childSignal.kind) &&
- // Only a more urgent state takes over; the lead keeps its own time for the same state.
- compareSlotLines({ kind: childSignal.kind, at: null }, { kind: best.kind, at: null }) < 0
- ) {
- best = childSignal
- }
- }
- visiting.delete(paneKey)
- rolledByPaneKey.set(paneKey, best)
- return best
- }
- for (const agent of agents) {
- rolled(agent.paneKey)
- }
-
- const rootLines = rootRows
- .map((agent) => lineByPaneKey.get(agent.paneKey))
- .filter((line): line is SlotAgentLine => line !== undefined)
- .sort((a, b) =>
- compareSlotLines(
- rolledByPaneKey.get(a.agent.paneKey) ?? a.signal,
- rolledByPaneKey.get(b.agent.paneKey) ?? b.signal
- )
- )
return {
- rootLines,
- childrenByParentPaneKey,
- lineByPaneKey,
- rolledByPaneKey,
- allSignals: [...lineByPaneKey.values()].map((line) => line.signal)
+ rootCount: buildAgentRowLineageTree([...agents]).rootRows.length,
+ lines,
+ allSignals: lines.map((line) => line.signal)
}
}
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-fixture-seeds.ts b/src/renderer/src/components/sidebar/worktree-card-slot-fixture-seeds.ts
index 9a2ad644282..879c1b44817 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-fixture-seeds.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-fixture-seeds.ts
@@ -8,7 +8,6 @@ import type {
import type { Tab } from '../../../../shared/tab-types'
import type { TerminalTab } from '../../../../shared/workspace-layout/terminal-tab-types'
import type { Worktree } from '../../../../shared/worktree/types'
-import { worktreeCardStyleSettingsUpdate } from '../../../../shared/worktree/card-style'
import * as agentExpansion from './workspace-card/activity/worktree-card-agents-expansion-state'
import { getWorktreeReviewCacheKeys } from './workspace-card/review/worktree-card-pr-derivation'
@@ -191,7 +190,7 @@ export function seedWorkspaces(seeds: readonly WorkspaceSeed[], now: number): vo
settings: state.settings
? {
...state.settings,
- ...worktreeCardStyleSettingsUpdate('experimental-1')
+ experimentalNewWorktreeCardStyle: true
}
: state.settings,
worktreeCardProperties: ['status', 'inline-agents'],
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-jump.tsx b/src/renderer/src/components/sidebar/worktree-card-slot-jump.tsx
index bad57d1a302..96b67f42c8c 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-jump.tsx
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-jump.tsx
@@ -79,10 +79,10 @@ function SlotTargetMenuItem({
target: SlotTarget
now: number
}): React.JSX.Element {
- // The menu is transient, so a snapshot of the name is enough.
- const workspaceName =
- useAppStore.getState().getKnownWorktreeById(target.worktreeId, target.executionHostId)
- ?.displayName ?? ''
+ const workspaceName = useAppStore(
+ (state) =>
+ state.getKnownWorktreeById(target.worktreeId, target.executionHostId)?.displayName ?? ''
+ )
return (
openSlotTarget(target)}>
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-rollup.test.ts b/src/renderer/src/components/sidebar/worktree-card-slot-rollup.test.ts
index 61530ca32f8..88c5cd8bbf0 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-rollup.test.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-rollup.test.ts
@@ -16,10 +16,15 @@ import type { AgentStatusEntry } from '../../../../shared/agent-status/contracts
import { composeWorktreeHostIdentity } from '../../../../shared/worktree/host-qualified-identity'
import {
collectLineageSlotTargets,
- selectCollapsedLineageSlot,
+ selectCollapsedLineageSlots,
selectLineageLatestAt
} from './worktree-card-slot-rollup'
import { slotTargetsOfKind, workspaceUnreadTarget } from './worktree-card-slot-targets'
+import { fromLineageSlotRecord } from './worktree-card-slot-summary'
+
+function collapsedSlot(...args: Parameters
) {
+ return fromLineageSlotRecord(selectCollapsedLineageSlots(...args))[0] ?? null
+}
const original = useAppStore.getState()
const LEAF = '11111111-1111-4111-8111-111111111111'
@@ -93,7 +98,7 @@ it('includes migration blocks, terminal-derived agents and blocked subagents in
})
const state = useAppStore.getState()
const targets = collectLineageSlotTargets(state, [identity])
- expect(selectCollapsedLineageSlot(state, [identity])).toEqual({ kind: 'needs', count: 2 })
+ expect(collapsedSlot(state, [identity])).toEqual({ kind: 'needs', count: 2 })
expect(
targets
.filter((target) => target.kind === 'needs')
@@ -131,10 +136,10 @@ it('expires hidden working evidence on the same freshness tick as agent rows', (
tabsByWorktree: { child: [makeTab({ id: 'parent', worktreeId: 'child', title: '' })] },
agentStatusByPaneKey: { [pane('parent')]: entry('parent') }
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [identity])?.kind).toBe('working')
+ expect(collapsedSlot(useAppStore.getState(), [identity])?.kind).toBe('working')
vi.setSystemTime(20_000_000)
useAppStore.setState({ agentStatusEpoch: 2 })
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [identity])).toBeNull()
+ expect(collapsedSlot(useAppStore.getState(), [identity])).toBeNull()
})
it('updates hidden subtree attention after status changes, acknowledgement, and freshness expiry', () => {
@@ -175,14 +180,14 @@ it('updates hidden subtree attention after status changes, acknowledgement, and
agentStatusByPaneKey: { [pane('agent')]: entry('agent', { worktreeId: 'grandchild' }) }
})
expect(selectLineageLatestAt(useAppStore.getState(), source)).toBe(1_950_000)
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)?.kind).toBe('working')
+ expect(collapsedSlot(useAppStore.getState(), source)?.kind).toBe('working')
useAppStore.setState({
agentStatusByPaneKey: {
[pane('agent')]: entry('agent', { worktreeId: 'grandchild', state: 'blocked' })
},
agentStatusEpoch: 2
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)).toEqual({
+ expect(collapsedSlot(useAppStore.getState(), source)).toEqual({
kind: 'needs',
count: 1
})
@@ -192,21 +197,21 @@ it('updates hidden subtree attention after status changes, acknowledgement, and
},
agentStatusEpoch: 3
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)).toEqual({
+ expect(collapsedSlot(useAppStore.getState(), source)).toEqual({
kind: 'unread',
at: 1_900_000,
manual: false
})
useAppStore.setState({ acknowledgedAgentsByPaneKey: { [pane('agent')]: 1_900_000 } })
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)).toBeNull()
+ expect(collapsedSlot(useAppStore.getState(), source)).toBeNull()
useAppStore.setState({
agentStatusByPaneKey: { [pane('agent')]: entry('agent', { worktreeId: 'grandchild' }) },
agentStatusEpoch: 4
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)?.kind).toBe('working')
+ expect(collapsedSlot(useAppStore.getState(), source)?.kind).toBe('working')
vi.setSystemTime(20_000_000)
useAppStore.setState({ agentStatusEpoch: 5 })
- expect(selectCollapsedLineageSlot(useAppStore.getState(), source)).toBeNull()
+ expect(collapsedSlot(useAppStore.getState(), source)).toBeNull()
})
it('refreshes unstamped structured agent summaries when workspace ownership changes', () => {
@@ -227,14 +232,14 @@ it('refreshes unstamped structured agent summaries when workspace ownership chan
runtimeEnvironments: [publicRuntimeEnvironment({ id: 'box' })],
agentStatusByPaneKey: { [paneKey]: entry('legacy-native', { paneKey, state: 'blocked' }) }
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [identity])).toEqual({
+ expect(collapsedSlot(useAppStore.getState(), [identity])).toEqual({
kind: 'needs',
count: 1
})
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [remoteIdentity])).toBeNull()
+ expect(collapsedSlot(useAppStore.getState(), [remoteIdentity])).toBeNull()
useAppStore.setState({ repos: [{ ...TEST_REPO, executionHostId: 'runtime:box' }] })
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [identity])).toBeNull()
- expect(selectCollapsedLineageSlot(useAppStore.getState(), [remoteIdentity])).toEqual({
+ expect(collapsedSlot(useAppStore.getState(), [identity])).toBeNull()
+ expect(collapsedSlot(useAppStore.getState(), [remoteIdentity])).toEqual({
kind: 'needs',
count: 1
})
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-rollup.ts b/src/renderer/src/components/sidebar/worktree-card-slot-rollup.ts
index 6b331876dd5..179bd2aa900 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-rollup.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-rollup.ts
@@ -4,7 +4,7 @@ import { selectWorktreesByRepo } from '@/store/workspaces/catalog/workspace-reco
import {
toLineageSlotRecord,
fromLineageSlotRecord,
- mergeRecord
+ type LineageSlotRecord
} from './worktree-card-slot-summary'
import { useCallback, useMemo } from 'react'
import { useShallow } from 'zustand/react/shallow'
@@ -29,21 +29,14 @@ import {
} from './workspace-card/activity/worktree-agent-rows-projection'
import { selectAgentRowsUnread } from './workspace-card/activity/worktree-card-agent-ack-inputs'
import { buildSlotAgentSignals } from './worktree-card-slot-agent-signals'
+import { slotExceptionsFor, type SlotException } from './worktree-card-slot-signal'
import {
- slotExceptionFor,
- slotExceptionsFor,
- type SlotException,
- type SlotSignal
-} from './worktree-card-slot-signal'
-import {
- expandWorkspaceUnreadTargets,
slotTargetsFromAgentLines,
- slotTargetsOfKind,
workspaceUnreadTarget,
type SlotTarget
} from './worktree-card-slot-targets'
-export function worktreeForIdentity(
+function worktreeForIdentity(
state: AppState,
identity: HostScopedWorktreeKey
): Worktree | undefined {
@@ -155,31 +148,30 @@ export function collectLineageSlotTargets(
return source.flatMap((identity) => ownSlot(state, identity).targets)
}
-export function selectCollapsedLineageSlot(
+export function selectCollapsedLineageSlots(
state: AppState,
source: WorktreeLineageSource
-): SlotSignal | null {
+): LineageSlotRecord {
return isLineageRange(source)
- ? (fromLineageSlotRecord(descendantSlot(state, source.tree, source.index).record)[0] ?? null)
- : slotExceptionFor(collectLineageSlotTargets(state, source))
+ ? descendantSlot(state, source.tree, source.index).record
+ : toLineageSlotRecord(slotExceptionsFor(collectLineageSlotTargets(state, source)))
+}
+
+function createCollapsedLineageSlotsSelector(source: WorktreeLineageSource) {
+ let inputs: readonly unknown[] = []
+ let record = toLineageSlotRecord([])
+ return (state: AppState) => {
+ const next = worktreeAgentProjectionInputs(state)
+ if (!next.every((value, index) => value === inputs[index])) {
+ inputs = next
+ record = selectCollapsedLineageSlots(state, source)
+ }
+ return record
+ }
}
export function useCollapsedLineageSlots(source: WorktreeLineageSource): SlotException[] {
- const select = useMemo(() => {
- let inputs: readonly unknown[] = []
- let record = toLineageSlotRecord([])
- return (state: AppState) => {
- const next = worktreeAgentProjectionInputs(state)
- if (next.every((value, index) => value === inputs[index])) {
- return record
- }
- inputs = next
- record = isLineageRange(source)
- ? descendantSlot(state, source.tree, source.index).record
- : toLineageSlotRecord(slotExceptionsFor(collectLineageSlotTargets(state, source)))
- return record
- }
- }, [source])
+ const select = useMemo(() => createCollapsedLineageSlotsSelector(source), [source])
const record = useAppStore(useShallow(select))
return useMemo(() => fromLineageSlotRecord(record), [record])
}
@@ -203,93 +195,3 @@ export function useLineageLatestAt(source: WorktreeLineageSource): number | null
useCallback((state: AppState) => selectLineageLatestAt(state, source), [source])
)
}
-
-function childRange(source: WorktreeLineageSource | undefined, identity: HostScopedWorktreeKey) {
- if (!source || !isLineageRange(source)) {
- return null
- }
- const index = source.tree.indexByIdentity.get(identity)
- return index === undefined
- ? null
- : { tree: source.tree, index, length: source.tree.ends[index]! - index - 1 }
-}
-
-function childSlot(
- state: AppState,
- identity: HostScopedWorktreeKey,
- source?: WorktreeLineageSource
-) {
- const range = childRange(source, identity)
- if (!range) {
- return slotExceptionFor(ownSlot(state, identity).targets)
- }
- const slots = slotsForTree(state, range.tree)
- const record = { ...slots.own[range.index]!.record }
- mergeRecord(record, descendantSlot(state, range.tree, range.index).record)
- return fromLineageSlotRecord(record)[0] ?? null
-}
-
-export type LineageChildCounts = { needs: number; failed: number; unread: number; working: number }
-export function selectLineageChildCounts(
- state: AppState,
- children: readonly HostScopedWorktreeKey[],
- source?: WorktreeLineageSource
-): LineageChildCounts {
- const counts: LineageChildCounts = { needs: 0, failed: 0, unread: 0, working: 0 }
- for (const child of children) {
- const signal = childSlot(state, child, source)
- if (
- signal?.kind === 'needs' ||
- signal?.kind === 'failed' ||
- signal?.kind === 'unread' ||
- signal?.kind === 'working'
- ) {
- counts[signal.kind]++
- }
- }
- return counts
-}
-
-export function useLineageChildCounts(
- children: readonly HostScopedWorktreeKey[],
- source?: WorktreeLineageSource
-): LineageChildCounts {
- const select = useMemo(() => {
- let inputs: readonly unknown[] = []
- let counts: LineageChildCounts = { needs: 0, failed: 0, unread: 0, working: 0 }
- return (state: AppState) => {
- const next = worktreeAgentProjectionInputs(state)
- if (next.every((value, index) => value === inputs[index])) {
- return counts
- }
- inputs = next
- counts = selectLineageChildCounts(state, children, source)
- return counts
- }
- }, [children, source])
- return useAppStore(useShallow(select))
-}
-
-export function collectLineageChildTargetsOfKind(
- state: AppState,
- children: readonly HostScopedWorktreeKey[],
- kind: keyof LineageChildCounts,
- source?: WorktreeLineageSource
-): SlotTarget[] {
- return slotTargetsOfKind(
- expandWorkspaceUnreadTargets(
- state,
- children.flatMap((child) => {
- if (childSlot(state, child, source)?.kind !== kind) {
- return []
- }
- const range = childRange(source, child)
- return [
- ...ownSlot(state, child).targets,
- ...(range ? collectLineageSlotTargets(state, range) : [])
- ]
- })
- ),
- kind
- )
-}
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-signal.ts b/src/renderer/src/components/sidebar/worktree-card-slot-signal.ts
index 4b610e2ebd2..4519a460c51 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-signal.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-signal.ts
@@ -2,7 +2,7 @@ import type { AgentDotState } from '@/components/AgentStateDot'
import type { DashboardAgentRow } from '@/components/dashboard/useDashboardData'
import { lastEnteredDoneAt } from '@/components/dashboard/agent-finished-timestamp'
-/** What one agent line means for the Experimental 2.0 card, in slot priority order. */
+/** What one agent line means for the experimental card, in slot priority order. */
export type SlotLineKind = 'needs' | 'failed' | 'unread' | 'working' | 'seen' | 'idle'
/** `monitoring`: working, but watching rather than taking turns; drawn with its own glyph. */
@@ -18,15 +18,6 @@ export type SlotSignal =
| { kind: 'time'; at: number | null }
| { kind: 'quiet' }
-const LINE_KIND_RANK: Record = {
- needs: 0,
- failed: 1,
- unread: 2,
- working: 3,
- seen: 4,
- idle: 5
-}
-
export function classifySlotLine(args: {
dotState: AgentDotState
needsAttention: boolean
@@ -65,10 +56,6 @@ export function slotLineAt(
return lastEnteredDoneAt(agent) ?? stateStartedAt
}
-export function compareSlotLines(a: SlotLineSignal, b: SlotLineSignal): number {
- return LINE_KIND_RANK[a.kind] - LINE_KIND_RANK[b.kind] || (b.at ?? 0) - (a.at ?? 0)
-}
-
function latest(lines: readonly SlotLineSignal[]): number | null {
let at: number | null = null
for (const line of lines) {
@@ -116,15 +103,6 @@ export function slotExceptionsFor(lines: readonly SlotLineSignal[]): SlotExcepti
return exceptions
}
-/** The most urgent exception a set of lines reports, or null when every line is quiet. */
-export function slotExceptionFor(lines: readonly SlotLineSignal[]): SlotSignal | null {
- return slotExceptionsFor(lines)[0] ?? null
-}
-
-export function slotForLine(line: SlotLineSignal): SlotSignal {
- return slotExceptionFor([line]) ?? { kind: 'time', at: line.at }
-}
-
function mergeSameKind(a: SlotException, b: SlotException): SlotException {
if (a.kind === 'needs' && b.kind === 'needs') {
return { kind: 'needs', count: a.count + b.count }
diff --git a/src/renderer/src/components/sidebar/worktree-card-slot-targets.ts b/src/renderer/src/components/sidebar/worktree-card-slot-targets.ts
index fd384c231be..0b6272d1a5e 100644
--- a/src/renderer/src/components/sidebar/worktree-card-slot-targets.ts
+++ b/src/renderer/src/components/sidebar/worktree-card-slot-targets.ts
@@ -19,7 +19,7 @@ import { activateWorktreeAgentPane } from './workspace-card/activity/use-worktre
import { activateRetainedAgentRow } from './workspace-card/activity/activate-retained-agent-row'
import { expandWorktreeAgentList } from './workspace-card/activity/worktree-card-agents-expansion-state'
import type { SlotAgentSignals } from './worktree-card-slot-agent-signals'
-import type { SlotLineKind, SlotLineSignal, SlotSignal } from './worktree-card-slot-signal'
+import type { SlotLineKind, SlotLineSignal } from './worktree-card-slot-signal'
/** The agent (or unread workspace) behind a rolled-up slot signal, so clicking the slot can open it. */
export type SlotTarget = SlotLineSignal & {
@@ -44,7 +44,7 @@ export function slotTargetsFromAgentLines(
executionHostId?: ExecutionHostId
): SlotTarget[] {
const targets: SlotTarget[] = []
- for (const { agent, signal } of signals.lineByPaneKey.values()) {
+ for (const { agent, signal } of signals.lines) {
if (!TARGET_KINDS.has(signal.kind)) {
continue
}
@@ -184,18 +184,3 @@ function activateUnreadTab(
})
}
}
-
-/** The line kind a slot points at, or null when the slot is a time, quiet, or offline. */
-export function slotTargetKind(signal: SlotSignal): SlotLineKind | null {
- switch (signal.kind) {
- case 'needs':
- case 'failed':
- case 'unread':
- case 'working':
- return signal.kind
- case 'offline':
- case 'quiet':
- case 'time':
- return null
- }
-}
diff --git a/src/renderer/src/components/sidebar/worktree-list/controls/workspace-options-menu-items.tsx b/src/renderer/src/components/sidebar/worktree-list/controls/workspace-options-menu-items.tsx
index 1f27c2019f9..4d5880cfe24 100644
--- a/src/renderer/src/components/sidebar/worktree-list/controls/workspace-options-menu-items.tsx
+++ b/src/renderer/src/components/sidebar/worktree-list/controls/workspace-options-menu-items.tsx
@@ -1,7 +1,3 @@
-import {
- getWorktreeCardStyle,
- isExperimentalWorktreeCardStyle
-} from '../../../../../../shared/worktree/card-style'
import { Fragment, useMemo, type ComponentType, type JSX } from 'react'
import { useAppStore } from '@/store'
import {
@@ -272,9 +268,7 @@ export function WorkspaceOptionsMenuItems({
const groupBy = useAppStore((s) => s.groupBy)
const showSleepingWorkspaces = useAppStore((s) => s.showSleepingWorkspaces)
const hideWorkspacesFromOtherDevices = useAppStore((s) => s.hideWorkspacesFromOtherDevices)
- const newCardStyle = useAppStore((s) =>
- isExperimentalWorktreeCardStyle(getWorktreeCardStyle(s.settings))
- )
+ const newCardStyle = useAppStore((s) => s.settings?.experimentalNewWorktreeCardStyle === true)
const runtimeEnvironmentCount = useAppStore((s) => s.runtimeEnvironments.length)
const runtimeEnvironmentCatalogHydrated = useAppStore((s) => s.runtimeEnvironmentCatalogHydrated)
const { hostOptions } = useSidebarHostScopeOptions()
diff --git a/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx b/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx
index 95e079ec2f1..f298d51b75b 100644
--- a/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx
+++ b/src/renderer/src/components/sidebar/worktree-list/listing/WorktreeList.tsx
@@ -1,4 +1,3 @@
-import { collectRenderedSidebarRowKeys } from './rendered-row-keys'
import React, { useCallback, useMemo } from 'react'
import { createPortal } from 'react-dom'
import { useAppStore } from '@/store/index'
@@ -162,11 +161,6 @@ const WorktreeList = React.memo(function WorktreeList({
prCache,
agentSendTargetWorktreeId
})
- const disclosedSectionRows = rowModel.sectionRows
- const disclosedRowKeys = useMemo(
- () => collectRenderedSidebarRowKeys(disclosedSectionRows),
- [disclosedSectionRows]
- )
const visibleProjectRepoIdsBySection = useMemo(
() => new Map(rowModel.occurrencePlan.sections.map(({ key, group }) => [key, group.repoIds])),
[rowModel.occurrencePlan]
@@ -200,7 +194,7 @@ const WorktreeList = React.memo(function WorktreeList({
)
}, [allProjectsCollapsed, projectCollapseKeys, setCollapsedGroups])
const selection = useSidebarWorktreeSelection({
- sectionRows: disclosedSectionRows,
+ sectionRows: rowModel.sectionRows,
pinnedDisplayPolicy
})
const statusMutations = useWorktreeStatusMutations({
@@ -253,7 +247,7 @@ const WorktreeList = React.memo(function WorktreeList({
useSidebarRevealRequests({
groupBy,
- renderedSidebarRowKeys: disclosedRowKeys,
+ renderedSidebarRowKeys: rowModel.renderedSidebarRowKeys,
visibleWorktrees,
visibleFolderWorkspaces: visibleScope.visibleFolderWorkspacesForRows,
currentSidebarWorktreeId,
@@ -326,7 +320,7 @@ const WorktreeList = React.memo(function WorktreeList({
0
- const newCardStyle = isExperimentalWorktreeCardStyle(getWorktreeCardStyle(state.settings))
+ const newCardStyle = state.settings?.experimentalNewWorktreeCardStyle === true
const folderCardsNeedReview =
hasFolderWorkspaces &&
(newCardStyle ? cardProperties.includes('status') : cardProperties.includes('pr'))
diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/folder-row.tsx b/src/renderer/src/components/sidebar/worktree-list/rows/folder-row.tsx
index 3d7f9e56ca5..b7e4aa88c3d 100644
--- a/src/renderer/src/components/sidebar/worktree-list/rows/folder-row.tsx
+++ b/src/renderer/src/components/sidebar/worktree-list/rows/folder-row.tsx
@@ -171,7 +171,6 @@ export function renderFolderWorkspaceOption(args: {
hostContextLabel={row.hostContextLabel}
lineageChildCount={row.lineageChildCount}
lineageChildWorktreeIds={row.lineageChildWorktreeIds}
- lineageChildIdentities={row.lineageChildIdentities}
lineageDescendantIdentities={row.lineageDescendantIdentities}
lineageCollapsed={row.lineageCollapsed}
lineageChildren={lineage?.children}
diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/indentation.ts b/src/renderer/src/components/sidebar/worktree-list/rows/indentation.ts
index 1b91a318d65..d948330a1ff 100644
--- a/src/renderer/src/components/sidebar/worktree-list/rows/indentation.ts
+++ b/src/renderer/src/components/sidebar/worktree-list/rows/indentation.ts
@@ -7,8 +7,6 @@ export const FLUSH_CARD_CONTENT_PULLBACK = 4
export const NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK = 8
// Why: keeps a zero-indent flush card off the sidebar edge.
export const FLUSH_CARD_MIN_CONTENT_INSET = 2
-// Why: 2.0 nested cards have no indent, so their content needs its own breathing room off the card edge.
-export const SLOT_CARD_MIN_CONTENT_INSET = 6
export const WORKTREE_CARD_SURFACE_MARGIN = 4
// Why: level-1 lineage keeps the grouped-card anchor; deeper levels advance evenly, not cumulatively.
export const LINEAGE_IMMEDIATE_PARENT_STEP =
@@ -16,8 +14,6 @@ export const LINEAGE_IMMEDIATE_PARENT_STEP =
export const LINEAGE_NESTED_ROW_SURFACE_INSET = 0
export const LINEAGE_CHILDREN_INLINE_OFFSET =
LINEAGE_IMMEDIATE_PARENT_STEP - FLUSH_CARD_MIN_CONTENT_INSET
-// 2.0 children start on the parent's title column after its leading icon and gap.
-export const SLOT_LINEAGE_TITLE_COLUMN_SHIFT = 7
// Why: header icon+gap (22) vs. card margin+border+legacy lane+gap (23) minus pullback lands the title on the header label.
const GROUPED_CARD_TITLE_HEADER_LABEL_OFFSET = 3
export const PROJECT_GROUP_HEADER_BASE_PADDING = 10
@@ -136,15 +132,14 @@ export function getWorktreeCardSurfaceInset(args: {
export function getFlushWorktreeCardPaddingLeft(
contentIndent: number,
- applyNewCardStyleStatusLaneOffset = false,
- minContentInset = FLUSH_CARD_MIN_CONTENT_INSET
+ applyNewCardStyleStatusLaneOffset = false
): string {
const pullback =
FLUSH_CARD_CONTENT_PULLBACK +
(applyNewCardStyleStatusLaneOffset ? NEW_CARD_STYLE_STATUS_LANE_EXTRA_PULLBACK : 0)
return contentIndent > 0
- ? `max(${minContentInset}px, calc(${contentIndent}px - ${pullback}px))`
- : `${minContentInset}px`
+ ? `max(${FLUSH_CARD_MIN_CONTENT_INSET}px, calc(${contentIndent}px - ${pullback}px))`
+ : `${FLUSH_CARD_MIN_CONTENT_INSET}px`
}
export function getNewCardStyleParentContentMarginLeft(contentIndent: number): number {
diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx b/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx
index b6e7958dc1c..fc38b4b1b7c 100644
--- a/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx
+++ b/src/renderer/src/components/sidebar/worktree-list/rows/item-row.tsx
@@ -139,7 +139,6 @@ export function renderWorktreeItemRow(
renameRowKey={itemRow.rowKey}
lineageChildCount={itemRow.lineageChildCount}
lineageChildWorktreeIds={itemRow.lineageChildWorktreeIds}
- lineageChildIdentities={itemRow.lineageChildIdentities}
lineageCollapsed={itemRow.lineageCollapsed}
lineageDescendantIdentities={itemRow.lineageDescendantIdentities}
onLineageToggle={
diff --git a/src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx b/src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx
index 0a5d251ddd7..d9bfbdd7123 100644
--- a/src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx
+++ b/src/renderer/src/components/sidebar/worktree-list/viewport/VirtualizedWorktreeViewport.tsx
@@ -1,7 +1,3 @@
-import {
- getWorktreeCardStyle,
- isExperimentalWorktreeCardStyle
-} from '../../../../../../shared/worktree/card-style'
import React, { useCallback, useMemo, useRef, useState } from 'react'
import { useAppStore } from '@/store'
import { createProgrammaticScrollMarks } from '@/hooks/programmatic-scroll-marks'
@@ -81,7 +77,7 @@ export const VirtualizedWorktreeViewport = React.memo(function VirtualizedWorktr
repoMap,
projectGrouping: props.projectGrouping
})
- const newCardStyle = isExperimentalWorktreeCardStyle(getWorktreeCardStyle(settings))
+ const newCardStyle = settings?.experimentalNewWorktreeCardStyle === true
const {
handleOpenRepoSettings,
handleOpenWorktreeVisibility,
diff --git a/src/renderer/src/components/sidebar/worktree-list/viewport/use-visible-review-refresh.ts b/src/renderer/src/components/sidebar/worktree-list/viewport/use-visible-review-refresh.ts
index 9fd7868c3f5..e78ccc189ef 100644
--- a/src/renderer/src/components/sidebar/worktree-list/viewport/use-visible-review-refresh.ts
+++ b/src/renderer/src/components/sidebar/worktree-list/viewport/use-visible-review-refresh.ts
@@ -5,10 +5,7 @@ import { useAppStore } from '@/store'
import type { Worktree } from '../../../../../../shared/worktree/types'
import type { WorktreeGroupBy } from '../../../../../../shared/sidebar-list/row-types'
import type { RenderRow } from '../listing/render-row'
-import { lineageIdentities } from '../../../../../../shared/sidebar-list/worktree-lineage-range'
-import { getWorktreeIdFromHostIdentity } from '../../../../../../shared/worktree/host-qualified-identity'
-import { folderWorkspaceToWorktree } from '../../../../../../shared/folder-workspaces/folder-workspace-worktree'
-import type { WorktreeItemRow, FolderWorkspaceItemRow } from '../listing/renderable-rows'
+import type { WorktreeItemRow } from '../listing/renderable-rows'
import { getMountedWorktreeOptionsById } from '../rows/option-dom'
export function installWorktreeVisibleRefreshVisibilityListener(onChange: () => void): () => void {
@@ -46,34 +43,18 @@ export function visibleReviewCardIds(args: {
viewportHeight: number
}): string[] {
const bottom = args.viewportTop + args.viewportHeight
- const ids = new Set()
- for (const item of args.virtualItems) {
- if (item.start >= bottom || item.end <= args.viewportTop) {
- continue
- }
- const row = args.renderRows[item.index]
- const cards: (WorktreeItemRow | FolderWorkspaceItemRow)[] =
- row?.type === 'item' || row?.type === 'folder-workspace' ? [row] : []
- for (const card of cards) {
- const worktree =
- card.type === 'item' ? card.worktree : folderWorkspaceToWorktree(card.folderWorkspace)
- if (
- card.type === 'item' &&
- (card.repo?.kind ?? 'git') === 'git' &&
- !worktree.isBare &&
- !worktree.isArchived &&
- worktree.branch
- ) {
- ids.add(worktree.id)
- }
- if (card.lineageDescendantsHidden && card.lineageDescendantIdentities) {
- for (const identity of lineageIdentities(card.lineageDescendantIdentities)) {
- ids.add(getWorktreeIdFromHostIdentity(identity))
- }
- }
- }
- }
- return [...ids]
+ return args.virtualItems
+ .filter((item) => item.start < bottom && item.end > args.viewportTop)
+ .map((item) => args.renderRows[item.index])
+ .flatMap((row): WorktreeItemRow[] => (row?.type === 'item' ? [row] : []))
+ .filter(
+ (row) =>
+ (row.repo?.kind ?? 'git') === 'git' &&
+ !row.worktree.isBare &&
+ !row.worktree.isArchived &&
+ Boolean(row.worktree.branch)
+ )
+ .map((row) => row.worktree.id)
}
export function useVisiblePrRefreshReporting(args: {
diff --git a/src/renderer/src/components/tab-bar/shell-icons.tsx b/src/renderer/src/components/tab-bar/shell-icons.tsx
index 46a15e637d3..b3a376525ea 100644
--- a/src/renderer/src/components/tab-bar/shell-icons.tsx
+++ b/src/renderer/src/components/tab-bar/shell-icons.tsx
@@ -112,26 +112,6 @@ function GenericTerminalIcon({ size = 14 }: { size?: number }): React.JSX.Elemen
)
}
-export type ShellIconKind = 'powershell' | 'cmd' | 'wsl' | 'git-bash' | 'generic'
-
-export function shellIconKind(shell: string | null | undefined): ShellIconKind {
- const normalized = (shell ?? '').toLowerCase()
- const normalizedName = normalized.replaceAll('\\', '/').split('/').pop()
- if (normalized === 'powershell.exe' || normalized === 'pwsh.exe') {
- return 'powershell'
- }
- if (normalized === 'cmd.exe') {
- return 'cmd'
- }
- if (normalized === 'wsl.exe' || normalized.startsWith('wsl')) {
- return 'wsl'
- }
- if (normalized === WINDOWS_GIT_BASH_SHELL || normalizedName === 'bash.exe') {
- return 'git-bash'
- }
- return 'generic'
-}
-
export function ShellIcon({
shell,
size = 14
@@ -139,16 +119,19 @@ export function ShellIcon({
shell: string | null | undefined
size?: number
}): React.JSX.Element {
- switch (shellIconKind(shell)) {
- case 'powershell':
- return
- case 'cmd':
- return
- case 'wsl':
- return
- case 'git-bash':
- return
- case 'generic':
- return
+ const normalized = (shell ?? '').toLowerCase()
+ const normalizedName = normalized.replaceAll('\\', '/').split('/').pop()
+ if (normalized === 'powershell.exe' || normalized === 'pwsh.exe') {
+ return
}
+ if (normalized === 'cmd.exe') {
+ return
+ }
+ if (normalized === 'wsl.exe' || normalized.startsWith('wsl')) {
+ return
+ }
+ if (normalized === WINDOWS_GIT_BASH_SHELL || normalizedName === 'bash.exe') {
+ return
+ }
+ return
}
diff --git a/src/renderer/src/i18n/en-runtime-required.json b/src/renderer/src/i18n/en-runtime-required.json
index 5736dae1dc2..425a6f99631 100644
--- a/src/renderer/src/i18n/en-runtime-required.json
+++ b/src/renderer/src/i18n/en-runtime-required.json
@@ -659,14 +659,6 @@
"4d03dec2d0": "Read app interface trees and perform requested actions.",
"6b5a2cd3a5": "Accessibility"
},
- "ExperimentalPane": {
- "newWorktreeCardStyle": {
- "copy": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
- "description": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
- "title": "New card style",
- "toggleLabel": "Toggle new card style"
- }
- },
"McpConfigSection": {
"detectedServers_one": "{{detected}} detected · {{count}} server",
"detectedServers_other": "{{detected}} detected · {{count}} servers"
@@ -831,14 +823,6 @@
"permissionsRequired_one": "{{value0}} permission required before agents can operate app windows.",
"permissionsRequired_other": "{{value0}} permissions required before agents can operate app windows."
},
- "experimental": {
- "search": {
- "newWorktreeCardStyle": {
- "description": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
- "title": "New card style"
- }
- }
- },
"repository": {
"search": {
"keepForkUpToDate": "Keep Fork Up to Date"
diff --git a/src/renderer/src/i18n/locales/de.json b/src/renderer/src/i18n/locales/de.json
index 17bbd3da586..31955408bc1 100644
--- a/src/renderer/src/i18n/locales/de.json
+++ b/src/renderer/src/i18n/locales/de.json
@@ -8009,12 +8009,6 @@
"title": "Agenten-Ruhezustand",
"toggleLabel": "Agenten-Ruhezustand umschalten"
},
- "newWorktreeCardStyle": {
- "copy": "Vorschau auf das überarbeitete Layout der Worktree-Karten, die Anordnung der Metadaten, Menüoptionen zur Kartenanzeige und die Statusdarstellung.",
- "description": "Vorschau auf das überarbeitete Layout der Worktree-Karten, die Anordnung der Metadaten, Menüoptionen zur Kartenanzeige und die Statusdarstellung.",
- "title": "Neuer Kartenstil",
- "toggleLabel": "Neuen Kartenstil umschalten"
- },
"ca2219fe5e": "Zeigt ein kleines animiertes Haustier, das unten rechts angeheftet ist. Im Haustiermenü der Statusleiste eine Figur (Claudino, OpenCode, Gremlin) auswählen oder eine eigene PNG-, APNG-, GIF-, WebP-, JPG- oder SVG-Datei hochladen. Es lässt sich jederzeit über dasselbe Menü ausblenden, ohne diese Einstellung zu deaktivieren.",
"dd6f0a1d45": "Haustier",
"0e89a574ae": "Schwebendes animiertes Haustier unten rechts.",
@@ -10174,11 +10168,9 @@
"newWorktreeCardStyle": {
"card": "Karte",
"cards": "Karten",
- "description": "Vorschau auf das überarbeitete Layout der Worktree-Karten, die Anordnung der Metadaten, Menüoptionen zur Kartenanzeige und die Statusdarstellung.",
"menu": "Menü",
"metadata": "Metadaten",
"status": "Status",
- "title": "Neuer Kartenstil",
"worktree": "Worktree",
"worktrees": "Worktrees"
},
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index 25b13fd63b0..767209b1c53 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -266,7 +266,6 @@
"needsYou": "Needs you",
"failed": "Failed",
"detailsRow": "Details row",
- "orcaServer": "Orca server",
"workspaceUnread": "Unread in this workspace",
"unreadTab": "Unread tab"
}
@@ -8709,12 +8708,6 @@
"title": "Agent sleep",
"toggleLabel": "Toggle agent sleep"
},
- "newWorktreeCardStyle": {
- "copy": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
- "description": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
- "title": "New card style",
- "toggleLabel": "Toggle new card style"
- },
"ca2219fe5e": "Shows a small animated pet pinned to the bottom-right corner. Pick a character (Claudino, OpenCode, Gremlin) or upload your own PNG, APNG, GIF, WebP, JPG, or SVG from the status-bar pet menu. Hide it any time from the same menu without disabling this setting.",
"dd6f0a1d45": "Pet",
"0e89a574ae": "Floating animated pet in the bottom-right corner.",
@@ -10922,11 +10915,9 @@
"newWorktreeCardStyle": {
"card": "card",
"cards": "cards",
- "description": "Preview updated worktree-card layout, metadata placement, card-display menu options, and status presentation.",
"menu": "menu",
"metadata": "metadata",
"status": "status",
- "title": "New card style",
"worktree": "worktree",
"worktrees": "worktrees"
},
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index e4aaffd2b81..02b9d571f83 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -7622,12 +7622,6 @@
"title": "Suspensión de agentes",
"toggleLabel": "Alternar suspensión de agentes"
},
- "newWorktreeCardStyle": {
- "copy": "Previsualiza el diseño actualizado de tarjetas de worktree, la ubicación de metadatos, las opciones del menú de visualización de tarjetas y la presentación de estado.",
- "description": "Previsualiza el diseño actualizado de tarjetas de worktree, la ubicación de metadatos, las opciones del menú de visualización de tarjetas y la presentación de estado.",
- "title": "Nuevo estilo de tarjeta",
- "toggleLabel": "Alternar nuevo estilo de tarjeta"
- },
"nativeChat": {
"shellEnvNamesLabel": "Variables que se pasan desde tu shell",
"shellEnvNamePlaceholder": "Nombre de la variable",
@@ -9748,11 +9742,9 @@
"newWorktreeCardStyle": {
"card": "tarjeta",
"cards": "tarjetas",
- "description": "Previsualiza el diseño actualizado de tarjetas de worktree, la ubicación de metadatos, las opciones del menú de visualización de tarjeta y la presentación de estado.",
"menu": "menú",
"metadata": "metadatos",
"status": "estado",
- "title": "Nuevo estilo de tarjeta",
"worktree": "worktree",
"worktrees": "worktrees"
},
diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json
index b29f957389b..10d64a2ab00 100644
--- a/src/renderer/src/i18n/locales/fr.json
+++ b/src/renderer/src/i18n/locales/fr.json
@@ -7890,12 +7890,6 @@
"title": "Mise en veille des agents",
"toggleLabel": "Activer la mise en veille des agents"
},
- "newWorktreeCardStyle": {
- "copy": "Prévisualise la nouvelle mise en page des cartes de worktree, l'emplacement des métadonnées, les options du menu d'affichage des cartes et la présentation des statuts.",
- "description": "Prévisualise la nouvelle mise en page des cartes de worktree, l'emplacement des métadonnées, les options du menu d'affichage des cartes et la présentation des statuts.",
- "title": "Nouveau style de carte",
- "toggleLabel": "Activer le nouveau style de carte"
- },
"ca2219fe5e": "Affiche un petit compagnon animé épinglé dans le coin inférieur droit. Choisissez un personnage (Claudino, OpenCode, Gremlin) ou importez votre propre PNG, APNG, GIF, WebP, JPG ou SVG depuis le menu compagnon de la barre d'état. Masquez-le à tout moment depuis le même menu sans désactiver ce réglage.",
"dd6f0a1d45": "Compagnon",
"0e89a574ae": "Compagnon animé flottant dans le coin inférieur droit.",
@@ -10056,11 +10050,9 @@
"newWorktreeCardStyle": {
"card": "carte",
"cards": "cartes",
- "description": "Prévisualise la nouvelle mise en page des cartes de worktree, l'emplacement des métadonnées, les options du menu d'affichage des cartes et la présentation des statuts.",
"menu": "menu",
"metadata": "métadonnées",
"status": "statut",
- "title": "Nouveau style de carte",
"worktree": "worktree",
"worktrees": "worktrees"
},
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 898ac076569..eae552969fc 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -7335,12 +7335,6 @@
"title": "Agent のスリープ",
"toggleLabel": "Agent のスリープを切り替え"
},
- "newWorktreeCardStyle": {
- "copy": "更新されたワークツリーカードのレイアウト、メタデータ配置、カード表示メニュー項目、ステータス表示をプレビューします。",
- "description": "更新されたワークツリーカードのレイアウト、メタデータ配置、カード表示メニュー項目、ステータス表示をプレビューします。",
- "title": "新規カードスタイル",
- "toggleLabel": "新規カードスタイルを切り替え"
- },
"nativeChat": {
"shellEnvNamesLabel": "シェルから渡す変数",
"shellEnvNamePlaceholder": "変数名",
@@ -9496,11 +9490,9 @@
"newWorktreeCardStyle": {
"card": "カード",
"cards": "カード",
- "description": "更新されたワークツリーカードのレイアウト、メタデータ配置、カード表示メニュー項目、ステータス表示をプレビューします。",
"menu": "メニュー",
"metadata": "メタデータ",
"status": "状態",
- "title": "新規カードスタイル",
"worktree": "ワークツリー",
"worktrees": "ワークツリー"
},
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index 0fe240adf4a..433832e124f 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -7332,12 +7332,6 @@
"title": "에이전트 최대 절전",
"toggleLabel": "에이전트 최대 절전 전환"
},
- "newWorktreeCardStyle": {
- "copy": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다.",
- "description": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다.",
- "title": "새 카드 스타일",
- "toggleLabel": "새 카드 스타일 전환"
- },
"nativeChat": {
"shellEnvNamesLabel": "셸에서 전달할 변수",
"shellEnvNamePlaceholder": "변수 이름",
@@ -9456,11 +9450,9 @@
"newWorktreeCardStyle": {
"card": "카드",
"cards": "카드",
- "description": "업데이트된 워크트리 카드 레이아웃, 메타데이터 배치, 카드 표시 메뉴 옵션 및 상태 표시를 미리 봅니다.",
"menu": "메뉴",
"metadata": "메타데이터",
"status": "상태",
- "title": "새 카드 스타일",
"worktree": "워크트리",
"worktrees": "worktrees"
},
diff --git a/src/renderer/src/i18n/locales/pt.json b/src/renderer/src/i18n/locales/pt.json
index a6fecb606e6..ac2a580b56d 100644
--- a/src/renderer/src/i18n/locales/pt.json
+++ b/src/renderer/src/i18n/locales/pt.json
@@ -5468,11 +5468,9 @@
"newWorktreeCardStyle": {
"card": "cartão",
"cards": "cartões",
- "description": "Pré-visualize o layout atualizado do cartão de worktree, o posicionamento dos metadados, as opções do menu de exibição do cartão e a apresentação do status.",
"menu": "menu",
"metadata": "metadados",
"status": "status",
- "title": "Novo estilo de cartão",
"worktree": "worktree",
"worktrees": "worktrees"
},
@@ -5944,12 +5942,6 @@
"title": "Suspensão do agente",
"toggleLabel": "Alternar suspensão do agente"
},
- "newWorktreeCardStyle": {
- "copy": "Pré-visualize o layout atualizado do cartão de worktree, o posicionamento dos metadados, as opções do menu de exibição do cartão e a apresentação do status.",
- "description": "Pré-visualize o layout atualizado do cartão de worktree, o posicionamento dos metadados, as opções do menu de exibição do cartão e a apresentação do status.",
- "title": "Novo estilo de cartão",
- "toggleLabel": "Alternar novo estilo de cartão"
- },
"ca2219fe5e": "Mostra um pequeno bichinho animado fixado no canto inferior direito. Escolha um personagem (Claudino, OpenCode, Gremlin) ou envie seu próprio PNG, APNG, GIF, WebP, JPG ou SVG no menu do bichinho na barra de status. Oculte-o quando quiser no mesmo menu sem desativar esta configuração.",
"dd6f0a1d45": "Bichinho",
"0e89a574ae": "Bichinho animado flutuante no canto inferior direito.",
diff --git a/src/renderer/src/i18n/locales/ru.json b/src/renderer/src/i18n/locales/ru.json
index f538f76d6c3..948b130bd71 100644
--- a/src/renderer/src/i18n/locales/ru.json
+++ b/src/renderer/src/i18n/locales/ru.json
@@ -8340,12 +8340,6 @@
"title": "Сон агентов",
"toggleLabel": "Переключить сон агентов"
},
- "newWorktreeCardStyle": {
- "copy": "Предпросмотр нового вида карточек рабочих деревьев, размещения метаданных, меню отображения карточек и статусов.",
- "description": "Предпросмотр нового вида карточек рабочих деревьев, размещения метаданных, меню отображения карточек и статусов.",
- "title": "Новый вид карточек",
- "toggleLabel": "Переключить новый вид карточек"
- },
"ca2219fe5e": "Показывает небольшого анимированного питомца в правом нижнем углу. Выберите персонажа (Claudino, OpenCode, Gremlin) или загрузите свой PNG, APNG, GIF, WebP, JPG или SVG через меню питомца в строке состояния. Там же его можно скрыть, не выключая эту настройку.",
"dd6f0a1d45": "Питомец",
"0e89a574ae": "Анимированный питомец в правом нижнем углу.",
@@ -10517,11 +10511,9 @@
"newWorktreeCardStyle": {
"card": "карточка",
"cards": "карточки",
- "description": "Предпросмотр нового вида карточек рабочих деревьев, размещения метаданных, меню отображения карточек и статусов.",
"menu": "меню",
"metadata": "метаданные",
"status": "статус",
- "title": "Новый вид карточек",
"worktree": "рабочее дерево",
"worktrees": "рабочие деревья"
},
diff --git a/src/renderer/src/i18n/locales/tr.json b/src/renderer/src/i18n/locales/tr.json
index e29d95431e3..1dbba5df25f 100644
--- a/src/renderer/src/i18n/locales/tr.json
+++ b/src/renderer/src/i18n/locales/tr.json
@@ -7986,12 +7986,6 @@
"title": "Ajan uykusu",
"toggleLabel": "Ajan uykusunu aç/kapat"
},
- "newWorktreeCardStyle": {
- "copy": "Güncellenmiş worktree kartı düzenini, meta veri yerleşimini, kart görüntüleme menü seçeneklerini ve durum sunumunu önizleyin.",
- "description": "Güncellenmiş worktree kartı düzenini, meta veri yerleşimini, kart görüntüleme menü seçeneklerini ve durum sunumunu önizleyin.",
- "title": "Yeni kart stili",
- "toggleLabel": "Yeni kart stilini aç/kapat"
- },
"ca2219fe5e": "Sağ alt köşeye sabitlenmiş küçük, animasyonlu bir evcil hayvan gösterir. Durum çubuğu evcil hayvan menüsünden bir karakter (Claudino, OpenCode, Gremlin) seçin veya kendi PNG, APNG, GIF, WebP, JPG ya da SVG dosyanızı yükleyin. Bu ayarı devre dışı bırakmadan aynı menüden istediğiniz zaman gizleyebilirsiniz.",
"dd6f0a1d45": "Evcil Hayvan",
"0e89a574ae": "Sağ alt köşede yüzen animasyonlu evcil hayvan.",
@@ -10151,11 +10145,9 @@
"newWorktreeCardStyle": {
"card": "kart",
"cards": "kartlar",
- "description": "Güncellenmiş worktree kartı düzenini, meta veri yerleşimini, kart görüntüleme menüsü seçeneklerini ve durum sunumunu önizleyin.",
"menu": "menü",
"metadata": "meta veri",
"status": "durum",
- "title": "Yeni kart stili",
"worktree": "worktree",
"worktrees": "worktree'ler"
},
diff --git a/src/renderer/src/i18n/locales/zh-Hant.json b/src/renderer/src/i18n/locales/zh-Hant.json
index 494611889b4..0d3477ecf03 100644
--- a/src/renderer/src/i18n/locales/zh-Hant.json
+++ b/src/renderer/src/i18n/locales/zh-Hant.json
@@ -7830,12 +7830,6 @@
"title": "代理程式睡眠",
"toggleLabel": "切換代理程式睡眠"
},
- "newWorktreeCardStyle": {
- "copy": "預覽更新後的工作樹卡片版面配置、後設資料位置、卡片顯示選單選項和狀態呈現。",
- "description": "預覽更新後的工作樹卡片版面配置、後設資料位置、卡片顯示選單選項和狀態呈現。",
- "title": "新卡片樣式",
- "toggleLabel": "切換新卡片樣式"
- },
"ca2219fe5e": "顯示固定在右下角的小動畫寵物。從狀態列寵物選單中選擇一個角色(Claudino、OpenCode、Gremlin)或上傳您自己的 PNG、APNG、GIF、WebP、JPG 或 SVG。隨時從同一選單隱藏它,而不停用此設定。",
"dd6f0a1d45": "寵物",
"0e89a574ae": "右下角漂浮的動畫寵物。",
@@ -9995,11 +9989,9 @@
"newWorktreeCardStyle": {
"card": "卡片",
"cards": "卡片",
- "description": "預覽更新後的工作樹卡片版面配置、後設資料位置、卡片顯示選單選項和狀態呈現。",
"menu": "選單",
"metadata": "後設資料",
"status": "狀態",
- "title": "新卡片樣式",
"worktree": "工作樹",
"worktrees": "工作樹"
},
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index b3196b946b1..6eba1f3a1e0 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -7553,12 +7553,6 @@
"title": "代理睡眠",
"toggleLabel": "切换代理睡眠"
},
- "newWorktreeCardStyle": {
- "copy": "预览更新后的工作树卡片布局、元数据位置、卡片显示菜单选项和状态呈现",
- "description": "预览更新后的工作树卡片布局、元数据位置、卡片显示菜单选项和状态呈现",
- "title": "新卡片样式",
- "toggleLabel": "切换新卡片样式"
- },
"nativeChat": {
"shellEnvNamesLabel": "从 shell 传递的变量",
"shellEnvNamePlaceholder": "变量名",
@@ -9693,11 +9687,9 @@
"newWorktreeCardStyle": {
"card": "卡片",
"cards": "卡片",
- "description": "预览更新后的 Worktree 卡片布局、元数据位置、卡片显示菜单选项和状态呈现",
"menu": "菜单",
"metadata": "元数据",
"status": "状态",
- "title": "新卡片样式",
"worktree": "工作树",
"worktrees": "工作树"
},
diff --git a/src/renderer/src/store/github/refresh/stale-worktree-refresh-actions.ts b/src/renderer/src/store/github/refresh/stale-worktree-refresh-actions.ts
index 66182c4b081..648125d9047 100644
--- a/src/renderer/src/store/github/refresh/stale-worktree-refresh-actions.ts
+++ b/src/renderer/src/store/github/refresh/stale-worktree-refresh-actions.ts
@@ -1,7 +1,3 @@
-import {
- getWorktreeCardStyle,
- isExperimentalWorktreeCardStyle
-} from '../../../../../shared/worktree/card-style'
import type { StateCreator } from 'zustand'
import type { AppState } from '../../types'
import type { GitHubSlice } from '../github-slice-contract'
@@ -39,7 +35,7 @@ export const createStaleWorktreeRefreshActions = (
const cardProps = state.worktreeCardProperties ?? []
const shouldRefreshPR =
state.groupBy === 'pr-status' ||
- (isExperimentalWorktreeCardStyle(getWorktreeCardStyle(state.settings))
+ (state.settings?.experimentalNewWorktreeCardStyle === true
? cardProps.includes('status')
: cardProps.includes('pr') || cardProps.some((property: string) => property === 'ci')) ||
rightSidebarShowsPullRequestData(state)
diff --git a/src/renderer/src/web/preload-api/web-preferences-store.ts b/src/renderer/src/web/preload-api/web-preferences-store.ts
index c8b8e3da98e..85b6494310c 100644
--- a/src/renderer/src/web/preload-api/web-preferences-store.ts
+++ b/src/renderer/src/web/preload-api/web-preferences-store.ts
@@ -23,10 +23,7 @@ import { callRuntimeResult } from './web-runtime-calls'
import { followHostVoiceSettings } from './web-speech-api'
import { requireActiveEnvironmentOrNull, webRuntimeState } from './web-runtime-session'
import { zcodePlanSiteOwner, settingsForZcodePlanSiteOwner } from './web-zcode-plan-site'
-import {
- hostWorktreeCardStyleUpdate,
- readHostWorktreeCardStyle
-} from './web-worktree-card-style-sync'
+import * as cardPreferences from './web-worktree-card-style-sync'
import { SETTINGS_STORAGE_KEY, UI_STORAGE_KEY, readJson, writeJson } from './web-storage'
export type WebSettingsApi = NonNullable
@@ -136,15 +133,12 @@ export async function getRuntimeBackedStoredSettings(): Promise
}
Object.assign(
runtimeSettings,
- readHostWorktreeCardStyle(
+ cardPreferences.read(
result.settings,
requestedEnvironment.id,
currentEnvironment?.id === requestedEnvironment.id
)
)
- if (typeof result.settings.compactWorktreeCards === 'boolean') {
- runtimeSettings.compactWorktreeCards = result.settings.compactWorktreeCards
- }
if (typeof result.settings.minimaxGroupId === 'string') {
runtimeSettings.minimaxGroupId = result.settings.minimaxGroupId
}
@@ -211,10 +205,7 @@ export async function syncRuntimeBackedSettings(
if (visibilityDefaults) {
runtimeUpdates.worktreeVisibilityDefaults = visibilityDefaults
}
- Object.assign(runtimeUpdates, hostWorktreeCardStyleUpdate(updates, requestedEnvironment.id))
- if (typeof updates.compactWorktreeCards === 'boolean') {
- runtimeUpdates.compactWorktreeCards = updates.compactWorktreeCards
- }
+ Object.assign(runtimeUpdates, cardPreferences.updates(updates, requestedEnvironment.id))
if (typeof updates.minimaxGroupId === 'string') {
runtimeUpdates.minimaxGroupId = updates.minimaxGroupId
}
diff --git a/src/renderer/src/web/preload-api/web-runtime-session.ts b/src/renderer/src/web/preload-api/web-runtime-session.ts
index 3206f95e762..9750f10c5c9 100644
--- a/src/renderer/src/web/preload-api/web-runtime-session.ts
+++ b/src/renderer/src/web/preload-api/web-runtime-session.ts
@@ -26,8 +26,6 @@ export const webRuntimeState: {
zcodePlanSiteRuntimeValue: ZcodePlanSite | null
worktreeVisibilityDefaultsRuntimeEnvironmentId: string | null
worktreeVisibilityDefaultsRuntimeValue: WorktreeVisibilityDefaults | null
- /** The paired host that advertised `worktreeCardStyle`; older hosts reject the unknown key. */
- worktreeCardStyleRuntimeEnvironmentId: string | null
worktreeCardDetailsRowRuntimeEnvironmentId: string | null
activeClient: WebRuntimeClient | null
activeClientEnvironmentId: string | null
@@ -38,7 +36,6 @@ export const webRuntimeState: {
zcodePlanSiteRuntimeValue: null,
worktreeVisibilityDefaultsRuntimeEnvironmentId: null,
worktreeVisibilityDefaultsRuntimeValue: null,
- worktreeCardStyleRuntimeEnvironmentId: null,
worktreeCardDetailsRowRuntimeEnvironmentId: null,
activeClient: null,
activeClientEnvironmentId: null,
diff --git a/src/renderer/src/web/preload-api/web-worktree-card-style-sync.ts b/src/renderer/src/web/preload-api/web-worktree-card-style-sync.ts
index 0278669a012..19cfbe8407b 100644
--- a/src/renderer/src/web/preload-api/web-worktree-card-style-sync.ts
+++ b/src/renderer/src/web/preload-api/web-worktree-card-style-sync.ts
@@ -1,14 +1,13 @@
import type { GlobalSettings } from '../../../../shared/global-settings-types'
-import { isWorktreeCardStyle } from '../../../../shared/worktree/card-style'
import { webRuntimeState } from './web-runtime-session'
type CardStyleSettings = Pick<
Partial,
- 'experimentalNewWorktreeCardStyle' | 'worktreeCardStyle' | 'worktreeCardDetailsRow'
+ 'experimentalNewWorktreeCardStyle' | 'worktreeCardDetailsRow' | 'compactWorktreeCards'
>
-/** Mirror the host's card style, and remember whether this host accepts the three-way field. */
-export function readHostWorktreeCardStyle(
+/** Mirror card preferences and remember whether this host accepts the details-row field. */
+export function read(
host: CardStyleSettings,
environmentId: string,
environmentStillActive: boolean
@@ -16,39 +15,30 @@ export function readHostWorktreeCardStyle(
if (environmentStillActive) {
webRuntimeState.worktreeCardDetailsRowRuntimeEnvironmentId =
typeof host.worktreeCardDetailsRow === 'boolean' ? environmentId : null
- webRuntimeState.worktreeCardStyleRuntimeEnvironmentId = isWorktreeCardStyle(
- host.worktreeCardStyle
- )
- ? environmentId
- : null
}
return {
worktreeCardDetailsRow: host.worktreeCardDetailsRow !== false,
+ ...(typeof host.compactWorktreeCards === 'boolean'
+ ? { compactWorktreeCards: host.compactWorktreeCards }
+ : {}),
...(typeof host.experimentalNewWorktreeCardStyle === 'boolean'
? { experimentalNewWorktreeCardStyle: host.experimentalNewWorktreeCardStyle }
- : {}),
- ...(isWorktreeCardStyle(host.worktreeCardStyle)
- ? { worktreeCardStyle: host.worktreeCardStyle }
: {})
}
}
-/** Older hosts reject unknown setting keys, so send the style only to a host that advertised it. */
-export function hostWorktreeCardStyleUpdate(
- updates: CardStyleSettings,
- environmentId: string
-): CardStyleSettings {
+/** Older hosts reject unknown setting keys; only send the details preference when advertised. */
+export function updates(patch: CardStyleSettings, environmentId: string): CardStyleSettings {
return {
- ...(typeof updates.worktreeCardDetailsRow === 'boolean' &&
+ ...(typeof patch.worktreeCardDetailsRow === 'boolean' &&
webRuntimeState.worktreeCardDetailsRowRuntimeEnvironmentId === environmentId
- ? { worktreeCardDetailsRow: updates.worktreeCardDetailsRow }
+ ? { worktreeCardDetailsRow: patch.worktreeCardDetailsRow }
: {}),
- ...(typeof updates.experimentalNewWorktreeCardStyle === 'boolean'
- ? { experimentalNewWorktreeCardStyle: updates.experimentalNewWorktreeCardStyle }
+ ...(typeof patch.compactWorktreeCards === 'boolean'
+ ? { compactWorktreeCards: patch.compactWorktreeCards }
: {}),
- ...(isWorktreeCardStyle(updates.worktreeCardStyle) &&
- webRuntimeState.worktreeCardStyleRuntimeEnvironmentId === environmentId
- ? { worktreeCardStyle: updates.worktreeCardStyle }
+ ...(typeof patch.experimentalNewWorktreeCardStyle === 'boolean'
+ ? { experimentalNewWorktreeCardStyle: patch.experimentalNewWorktreeCardStyle }
: {})
}
}
diff --git a/src/renderer/src/web/web-preload-api-settings.test.ts b/src/renderer/src/web/web-preload-api-settings.test.ts
index 5784716184e..1e8cf443335 100644
--- a/src/renderer/src/web/web-preload-api-settings.test.ts
+++ b/src/renderer/src/web/web-preload-api-settings.test.ts
@@ -25,7 +25,6 @@ describe('web settings preload API', () => {
ok: true,
result: {
settings: {
- worktreeCardStyle: 'experimental-2',
worktreeCardDetailsRow: method === 'settings.update'
}
},
diff --git a/src/shared/rpc-contract/client-ui/client-settings-params.ts b/src/shared/rpc-contract/client-ui/client-settings-params.ts
index 83f8107db2a..067ee832fc6 100644
--- a/src/shared/rpc-contract/client-ui/client-settings-params.ts
+++ b/src/shared/rpc-contract/client-ui/client-settings-params.ts
@@ -113,7 +113,6 @@ export const SettingsUpdate = z
.enum(['issues', 'my-issues', 'prs', 'my-prs', 'review', 'all'])
.optional(),
experimentalNewWorktreeCardStyle: z.boolean().optional(),
- worktreeCardStyle: z.enum(['classic', 'experimental-1', 'experimental-2']).optional(),
worktreeCardDetailsRow: z.boolean().optional(),
/** No UI: the host-side switch for connectors, so a headless server can be turned on. */
experimentalConnectors: z.boolean().optional(),
diff --git a/src/shared/sidebar-list/feed/sidebar-feed-rows.test.ts b/src/shared/sidebar-list/feed/sidebar-feed-rows.test.ts
index 738704876bb..e1235c31fc7 100644
--- a/src/shared/sidebar-list/feed/sidebar-feed-rows.test.ts
+++ b/src/shared/sidebar-list/feed/sidebar-feed-rows.test.ts
@@ -166,7 +166,7 @@ describe('buildSidebarFeedRows nests each paired server’s own lineage', () =>
expect([...lineageIdentities(parent.lineageDescendantIdentities ?? [])]).toEqual([
'runtime:a|r::/r/c'
])
- expect(parent.lineageDescendantsHidden).toBe(collapsed)
+ expect(parent.lineageCollapsed).toBe(collapsed)
expect(outline(built.sectionRows).includes('runtime:a|r::/r/c@1')).toBe(!collapsed)
})
diff --git a/src/shared/sidebar-list/row-types.ts b/src/shared/sidebar-list/row-types.ts
index b54dc6f15c3..8450e29a4fd 100644
--- a/src/shared/sidebar-list/row-types.ts
+++ b/src/shared/sidebar-list/row-types.ts
@@ -5,7 +5,6 @@ import type { ProjectGroup } from '../project-group-types'
import type { Repo } from '../repo-types'
import type { DetectedWorktree, WorkspaceStatus, Worktree } from '../worktree/types'
import type { ExecutionHostId } from '../execution-host'
-import type { HostScopedWorktreeKey } from '../worktree/host-qualified-identity'
import type { PRGroupKey } from '../workspace-view/workspace-pr-groups'
export type WorktreeGroupBy = 'none' | 'workspace-status' | 'repo' | 'pr-status'
@@ -65,13 +64,10 @@ export type WorktreeRow = {
isLastLineageChild: boolean
lineageChildCount: number
lineageChildWorktreeIds?: readonly string[]
- /** Host-qualified, since workspace ids repeat across hosts. */
- lineageChildIdentities?: readonly HostScopedWorktreeKey[]
lineageGroupKey?: string
lineageCollapsed?: boolean
/** Every workspace nested under this one, collapsed or not; host-qualified. */
lineageDescendantIdentities?: WorktreeLineageSource
- lineageDescendantsHidden?: boolean
hostContextLabel?: string
showProjectName?: boolean
}
@@ -120,10 +116,7 @@ export type PendingCreationRow = {
}
export type FolderWorkspaceRow = {
- occurrence?: WorktreeOccurrence
- lineageChildIdentities?: readonly HostScopedWorktreeKey[]
lineageDescendantIdentities?: WorktreeLineageSource
- lineageDescendantsHidden?: boolean
type: 'folder-workspace'
key: string
folderWorkspace: FolderWorkspace
diff --git a/src/shared/sidebar-list/sections/row-builders.ts b/src/shared/sidebar-list/sections/row-builders.ts
index 6ac47d3214e..f1c918a771e 100644
--- a/src/shared/sidebar-list/sections/row-builders.ts
+++ b/src/shared/sidebar-list/sections/row-builders.ts
@@ -1,12 +1,9 @@
import type { Repo } from '../../repo-types'
import type { Worktree } from '../../worktree/types'
-import {
- getWorktreeHostIdentity,
- type HostScopedWorktreeKey
-} from '../../worktree/host-qualified-identity'
+import { getWorktreeHostIdentity } from '../../worktree/host-qualified-identity'
import { getFolderWorkspaceHostIdentity } from '../../folder-workspaces/folder-workspace-worktree'
import { getWorktreeLineageGroupKey } from './group-keys'
-import { buildWorktreeLineageRanges, type WorktreeLineageSource } from './worktree-lineage-range'
+import { buildWorktreeLineageRanges, type WorktreeLineageSource } from '../worktree-lineage-range'
import {
getExpandedWorktreeOccurrences,
type WorktreeOccurrence,
@@ -86,15 +83,11 @@ export function appendWorktreeRows(
const lineageRanges = buildWorktreeLineageRanges(plan)
const sectionOccurrences = plan.bySection.get(options.sectionKey) ?? []
const childIdsByParent = new Map()
- const childIdentitiesByParent = new Map()
sectionOccurrences.forEach((occurrence) => {
if (occurrence.parent) {
const children = childIdsByParent.get(occurrence.parent.key) ?? []
children.push(occurrence.row.id)
childIdsByParent.set(occurrence.parent.key, children)
- const identities = childIdentitiesByParent.get(occurrence.parent.key) ?? []
- identities.push(getWorktreeHostIdentity(occurrence.row))
- childIdentitiesByParent.set(occurrence.parent.key, identities)
}
})
const occurrences = getExpandedWorktreeOccurrences(sectionOccurrences, (worktree) =>
@@ -108,7 +101,6 @@ export function appendWorktreeRows(
buildFolderWorkspaceRow(folderPair, options.groupDepth, occurrence, {
collapsedGroups: options.collapsedGroups,
childWorktreeIds: childIdsByParent.get(occurrence.key),
- childIdentities: childIdentitiesByParent.get(occurrence.key),
descendantIdentities: lineageRanges.get(occurrence.key),
hostContextLabel: options.hostContextLabelByWorktreeIdentity?.get(
getWorktreeHostIdentity(worktree)
@@ -129,11 +121,7 @@ export function appendWorktreeRows(
isLastLineageChild: occurrence.isLastChild,
lineageChildCount: occurrence.childCount,
lineageChildWorktreeIds: childIdsByParent.get(occurrence.key),
- lineageChildIdentities: childIdentitiesByParent.get(occurrence.key),
lineageDescendantIdentities: lineageRanges.get(occurrence.key),
- lineageDescendantsHidden:
- occurrence.childCount > 0 &&
- options.collapsedGroups.has(getWorktreeLineageGroupKey(worktree)),
showProjectName: occurrence.showProjectName,
hostContextLabel:
options.hostContextLabelByWorktreeIdentity?.get(getWorktreeHostIdentity(worktree)) ??
@@ -156,7 +144,6 @@ export function buildFolderWorkspaceRow(
options?: {
collapsedGroups: ReadonlySet
childWorktreeIds?: readonly string[]
- childIdentities?: readonly HostScopedWorktreeKey[]
descendantIdentities?: WorktreeLineageSource
hostContextLabel?: string
}
@@ -165,11 +152,7 @@ export function buildFolderWorkspaceRow(
occurrence && occurrence.childCount > 0 ? getWorktreeLineageGroupKey(occurrence.row) : undefined
return {
type: 'folder-workspace',
- occurrence,
- lineageChildIdentities: options?.childIdentities,
lineageDescendantIdentities: options?.descendantIdentities,
- lineageDescendantsHidden:
- lineageGroupKey !== undefined && options?.collapsedGroups.has(lineageGroupKey) === true,
key: `folder-workspace:${getFolderWorkspaceHostIdentity(pair.folderWorkspace)}`,
folderWorkspace: pair.folderWorkspace,
projectGroup: pair.projectGroup,
diff --git a/src/shared/sidebar-list/worktree-lineage-range.ts b/src/shared/sidebar-list/worktree-lineage-range.ts
index eaff4778a5a..dfff1960b1b 100644
--- a/src/shared/sidebar-list/worktree-lineage-range.ts
+++ b/src/shared/sidebar-list/worktree-lineage-range.ts
@@ -8,11 +8,7 @@ import {
export type WorktreeLineageTree = {
identities: HostScopedWorktreeKey[]
worktrees: Worktree[]
- parents: number[]
ends: number[]
- indexByIdentity: Map
- indexByWorktreeId: Map
- rangesByOccurrence: Map
}
export type WorktreeLineageRange = {
@@ -55,25 +51,15 @@ export function buildWorktreeLineageRanges(
const tree: WorktreeLineageTree = {
identities: [],
worktrees: [],
- parents: [],
- ends: [],
- indexByIdentity: new Map(),
- indexByWorktreeId: new Map(),
- rangesByOccurrence: result
+ ends: []
}
const stack: number[] = []
occurrences.forEach((occurrence, index) => {
while (stack.length && occurrences[stack.at(-1)!]!.depth >= occurrence.depth) {
tree.ends[stack.pop()!] = index
}
- tree.parents.push(stack.at(-1) ?? -1)
tree.identities.push(getWorktreeHostIdentity(occurrence.row))
tree.worktrees.push(occurrence.row)
- tree.indexByIdentity.set(getWorktreeHostIdentity(occurrence.row), index)
- tree.indexByWorktreeId.set(
- occurrence.row.id,
- tree.indexByWorktreeId.has(occurrence.row.id) ? null : index
- )
stack.push(index)
})
for (const index of stack) {
diff --git a/src/shared/worktree/card-display-settings-types.ts b/src/shared/worktree/card-display-settings-types.ts
index f15526486e9..ce30186fe09 100644
--- a/src/shared/worktree/card-display-settings-types.ts
+++ b/src/shared/worktree/card-display-settings-types.ts
@@ -1,6 +1,4 @@
export type WorktreeCardDisplaySettings = {
- /** Which worktree card the sidebar draws ('experimental-2' is retired and reads as 'experimental-1'); `experimentalNewWorktreeCardStyle` stays in step for older clients. */
- worktreeCardStyle?: 'classic' | 'experimental-1' | 'experimental-2'
/** Experimental card: draw the details row (Card display items) under each name unless explicitly disabled. */
worktreeCardDetailsRow?: boolean
}
diff --git a/src/shared/worktree/card-style.test.ts b/src/shared/worktree/card-style.test.ts
deleted file mode 100644
index a3541ea5ec2..00000000000
--- a/src/shared/worktree/card-style.test.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { getWorktreeCardStyle, worktreeCardStyleSettingsUpdate } from './card-style'
-
-describe('getWorktreeCardStyle', () => {
- it('maps the legacy boolean when the style was never written', () => {
- expect(getWorktreeCardStyle({ experimentalNewWorktreeCardStyle: true })).toBe('experimental-1')
- expect(getWorktreeCardStyle({ experimentalNewWorktreeCardStyle: false })).toBe('classic')
- expect(getWorktreeCardStyle({})).toBe('classic')
- expect(getWorktreeCardStyle(null)).toBe('classic')
- })
-
- it('round-trips every style through the paired settings update', () => {
- for (const style of ['classic', 'experimental-1'] as const) {
- expect(getWorktreeCardStyle(worktreeCardStyleSettingsUpdate(style))).toBe(style)
- }
- })
-
- it('reads the retired second experimental card as the experimental card', () => {
- expect(
- getWorktreeCardStyle({
- worktreeCardStyle: 'experimental-2',
- experimentalNewWorktreeCardStyle: true
- })
- ).toBe('experimental-1')
- })
-
- it('lets an older client that only flips the boolean win', () => {
- expect(
- getWorktreeCardStyle({
- worktreeCardStyle: 'experimental-2',
- experimentalNewWorktreeCardStyle: false
- })
- ).toBe('classic')
- expect(
- getWorktreeCardStyle({
- worktreeCardStyle: 'classic',
- experimentalNewWorktreeCardStyle: true
- })
- ).toBe('experimental-1')
- })
-})
diff --git a/src/shared/worktree/card-style.ts b/src/shared/worktree/card-style.ts
deleted file mode 100644
index 2709dcabe4e..00000000000
--- a/src/shared/worktree/card-style.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { GlobalSettings } from '../global-settings-types'
-
-export const WORKTREE_CARD_STYLES = ['classic', 'experimental-1'] as const
-
-export type WorktreeCardStyle = (typeof WORKTREE_CARD_STYLES)[number]
-
-type WorktreeCardStyleSettings = Pick<
- GlobalSettings,
- 'worktreeCardStyle' | 'experimentalNewWorktreeCardStyle'
->
-
-export function isWorktreeCardStyle(value: unknown): value is WorktreeCardStyle {
- return WORKTREE_CARD_STYLES.some((style) => style === value)
-}
-
-/**
- * Older clients only write the boolean, so it wins whenever the two disagree. A stored
- * 'experimental-2' (the retired second card) reads as the one experimental card.
- */
-export function getWorktreeCardStyle(
- settings: Partial | null | undefined
-): WorktreeCardStyle {
- const legacyExperimental = settings?.experimentalNewWorktreeCardStyle === true
- const style = settings?.worktreeCardStyle
- if (isWorktreeCardStyle(style) && (style !== 'classic') === legacyExperimental) {
- return style
- }
- return legacyExperimental ? 'experimental-1' : 'classic'
-}
-
-export function isExperimentalWorktreeCardStyle(style: WorktreeCardStyle): boolean {
- return style !== 'classic'
-}
-
-export function worktreeCardStyleSettingsUpdate(
- style: WorktreeCardStyle
-): Required {
- return {
- worktreeCardStyle: style,
- experimentalNewWorktreeCardStyle: isExperimentalWorktreeCardStyle(style)
- }
-}