diff --git a/src/main/persistence.test.ts b/src/main/persistence.test.ts
index 9ac653533..d63f9cf31 100644
--- a/src/main/persistence.test.ts
+++ b/src/main/persistence.test.ts
@@ -5277,6 +5277,27 @@ describe('Store', () => {
expect(store.getUI().worktreeCardProperties).not.toContain('automation')
})
+ it('preserves the current defaulted Compact preset without expanding display toggles', async () => {
+ writeDataFile({
+ schemaVersion: 1,
+ repos: [],
+ worktreeMeta: {},
+ settings: { compactWorktreeCards: true, experimentalNewWorktreeCardStyle: true },
+ ui: {
+ worktreeCardProperties: ['status', 'unread'],
+ _worktreeCardModeDefaulted: true
+ },
+ githubCache: { pr: {}, issue: {} },
+ workspaceSession: {}
+ })
+ const store = await createStore()
+
+ expect(store.getSettings().compactWorktreeCards).toBe(true)
+ expect(store.getUI().worktreeCardProperties).toEqual(['status', 'unread'])
+ expect(store.getUI().worktreeCardProperties).not.toContain('ports')
+ expect(store.getUI().worktreeCardProperties).not.toContain('inline-agents')
+ })
+
it.each([
['raw', ['status', 'automation']],
['normalized', ['status', 'unread', 'automation']]
diff --git a/src/main/persistence.ts b/src/main/persistence.ts
index 0492a7b55..c99eb4343 100644
--- a/src/main/persistence.ts
+++ b/src/main/persistence.ts
@@ -90,7 +90,7 @@ import {
getDefaultRepoHookSettings,
getDefaultWorkspaceSession,
getWorktreeCardModeProperties,
- isLegacyDefaultedCompactWorktreeCardProperties,
+ isDefaultedCompactWorktreeCardProperties,
normalizeAgentActivityDisplayMode,
normalizeWorktreeCardProperties,
ONBOARDING_FLOW_VERSION,
@@ -2806,7 +2806,7 @@ export class Store {
const needsLegacyDefaultedCompactMigration =
loadedCompactWorktreeCards &&
parsed.ui?._worktreeCardModeDefaulted === true &&
- isLegacyDefaultedCompactWorktreeCardProperties(rawCardProps)
+ isDefaultedCompactWorktreeCardProperties(rawCardProps)
const migratedCardProps = (() => {
if (!Array.isArray(rawCardProps)) {
return undefined
diff --git a/src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx b/src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx
index 3d7e25d73..91be2c249 100644
--- a/src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCard.compact-hover.test.tsx
@@ -365,7 +365,8 @@ describe('WorktreeCard compact hover details', () => {
/>
)
- expect(markup).toContain('data-worktree-card-meta-row=""')
+ expect(markup).toContain('Workspace metadata')
+ expect(markup).not.toContain('data-worktree-card-meta-row=""')
expectParentBodyIsHoverTrigger(markup)
expect(markup.match(/data-hover-open-delay="100"/g)).toHaveLength(1)
expect(markup).toContain('Reviewer handoff note')
diff --git a/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx b/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx
index cb6128d1d..cd01ae925 100644
--- a/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCard.pinned-repo-icon.test.tsx
@@ -142,4 +142,22 @@ describe('WorktreeCard pinned repo icon', () => {
expect(markup).not.toContain('🦊')
expect(markup).not.toContain('Project orca')
})
+
+ it('uses the pinned-style repo icon in new card style instead of a metadata-row badge', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: true }
+ worktreeCardProperties = ['status']
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderToStaticMarkup(
+
+ )
+
+ expect(markup).toContain('🦊')
+ expect(markup).toContain('Project orca')
+ expect(markup).not.toContain('data-worktree-card-meta-row=""')
+ })
})
diff --git a/src/renderer/src/components/sidebar/WorktreeCard.tsx b/src/renderer/src/components/sidebar/WorktreeCard.tsx
index e8bc28686..99e2f10c8 100644
--- a/src/renderer/src/components/sidebar/WorktreeCard.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCard.tsx
@@ -220,8 +220,8 @@ const WorktreeCard = React.memo(function WorktreeCard({
const fetchIssue = useAppStore((s) => s.fetchIssue)
const fetchLinearIssue = useAppStore((s) => s.fetchLinearIssue)
const cardProps = useAppStore((s) => s.worktreeCardProperties)
- const compactCards = settings?.compactWorktreeCards === true
const newCardStyle = settings?.experimentalNewWorktreeCardStyle === true
+ const compactCards = !newCardStyle && settings?.compactWorktreeCards === true
const activeSurfaceIsSecondary = isActiveSurface && activeSurfaceVariant === 'secondary'
const handleEditIssue = useCallback(
(e: React.MouseEvent) => {
@@ -1031,9 +1031,13 @@ const WorktreeCard = React.memo(function WorktreeCard({
// Why: pinned trees mix repos in one section; a leading repo icon keeps the
// list scannable, so it shows regardless of groupBy's hideRepoBadge.
const showPinnedRepoIcon = inPinnedSection && !!repo
+ // Why: the new card style retired the Compact/Detailed layout switch; repo
+ // identity uses the same compact chip as pinned cards instead of a lower pill.
+ const showRepoIdentityInTitle = newCardStyle || compactCards
const showInlineRepoBadge =
- compactCards && !!repo && !hideRepoBadge && !isFolder && !showPinnedRepoIcon
- const showRepoBadgeInMetaRow = !compactCards && !!repo && !hideRepoBadge && !showPinnedRepoIcon
+ showRepoIdentityInTitle && !!repo && !hideRepoBadge && !isFolder && !showPinnedRepoIcon
+ const showRepoBadgeInMetaRow =
+ !showRepoIdentityInTitle && !!repo && !hideRepoBadge && !showPinnedRepoIcon
const showHostContextBadge = !compactCards && !!hostContextLabel
const showDetachedHeadInMetaRow = !compactCards && !isFolder && detachedHeadDisplay !== null
const showBranch =
@@ -1207,7 +1211,6 @@ const WorktreeCard = React.memo(function WorktreeCard({
className={cn(
'flex shrink-0 justify-center',
newCardStyle ? 'mr-1 w-5 items-center' : 'items-start pt-[2px]',
- compactCards && newCardStyle && 'items-center pt-0',
affiliateListMode && 'px-1'
)}
data-worktree-card-status-slot=""
diff --git a/src/renderer/src/web/web-preload-api.test.ts b/src/renderer/src/web/web-preload-api.test.ts
index ec236c402..6cdb12921 100644
--- a/src/renderer/src/web/web-preload-api.test.ts
+++ b/src/renderer/src/web/web-preload-api.test.ts
@@ -762,6 +762,90 @@ describe('web UI preload API', () => {
expect(ui.rightSidebarOpen).toBe(true)
})
+ it('seeds missing local card display properties from runtime-backed compact settings when ui.get is unavailable', async () => {
+ const runtimeCalls: { method: string; params: unknown }[] = []
+ vi.doMock('./web-runtime-client', () => ({
+ WebRuntimeClient: class {
+ call(method: string, params?: unknown): Promise> {
+ runtimeCalls.push({ method, params })
+ if (method === 'settings.get') {
+ return Promise.resolve({
+ id: `call-${runtimeCalls.length}`,
+ ok: true,
+ result: { settings: { compactWorktreeCards: true } },
+ _meta: { runtimeId: 'runtime-1' }
+ })
+ }
+ return Promise.resolve({
+ id: `call-${runtimeCalls.length}`,
+ ok: false,
+ error: { code: 'method_not_found', message: 'Unknown method' },
+ _meta: { runtimeId: 'runtime-1' }
+ })
+ }
+
+ close(): void {}
+ }
+ }))
+
+ const globals = installBrowserGlobals('Linux')
+ writeStoredRuntimeEnvironment(globals.storage)
+ const { installWebPreloadApi } = await import('./web-preload-api')
+ installWebPreloadApi()
+
+ await globals.window.api.settings.get()
+ const ui = await globals.window.api.ui.get()
+
+ expect(ui.worktreeCardProperties).toEqual(['status', 'unread'])
+ expect(ui.worktreeCardProperties).not.toContain('ports')
+ expect(ui.worktreeCardProperties).not.toContain('inline-agents')
+ expect(runtimeCalls.map((call) => call.method)).toEqual(['settings.get', 'ui.get'])
+ })
+
+ it('preserves explicit local card display properties when compact fallback settings are present', async () => {
+ const runtimeCalls: { method: string; params: unknown }[] = []
+ vi.doMock('./web-runtime-client', () => ({
+ WebRuntimeClient: class {
+ call(method: string, params?: unknown): Promise> {
+ runtimeCalls.push({ method, params })
+ if (method === 'settings.get') {
+ return Promise.resolve({
+ id: `call-${runtimeCalls.length}`,
+ ok: true,
+ result: { settings: { compactWorktreeCards: true } },
+ _meta: { runtimeId: 'runtime-1' }
+ })
+ }
+ return Promise.resolve({
+ id: `call-${runtimeCalls.length}`,
+ ok: false,
+ error: { code: 'method_not_found', message: 'Unknown method' },
+ _meta: { runtimeId: 'runtime-1' }
+ })
+ }
+
+ close(): void {}
+ }
+ }))
+
+ const globals = installBrowserGlobals('Linux')
+ writeStoredRuntimeEnvironment(globals.storage)
+ globals.storage.setItem(
+ 'orca.web.ui.v1',
+ JSON.stringify({ worktreeCardProperties: ['status', 'pr'] })
+ )
+ const { installWebPreloadApi } = await import('./web-preload-api')
+ installWebPreloadApi()
+
+ await globals.window.api.settings.get()
+ const ui = await globals.window.api.ui.get()
+
+ expect(ui.worktreeCardProperties).toEqual(['status', 'unread', 'pr'])
+ expect(ui.worktreeCardProperties).not.toContain('ports')
+ expect(ui.worktreeCardProperties).not.toContain('inline-agents')
+ expect(runtimeCalls.map((call) => call.method)).toEqual(['settings.get', 'ui.get'])
+ })
+
it('keeps newer feature interaction counts when runtime responses resolve out of order', async () => {
const pending: ((response: RuntimeRpcResponse) => void)[] = []
vi.doMock('./web-runtime-client', () => ({
diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts
index 6d4ec2f45..83e3d9993 100644
--- a/src/renderer/src/web/web-preload-api.ts
+++ b/src/renderer/src/web/web-preload-api.ts
@@ -31,6 +31,7 @@ import {
getDefaultSettings,
getDefaultUIState,
getDefaultWorkspaceSession,
+ getWorktreeCardModeProperties,
normalizeAgentActivityDisplayMode,
normalizeWorktreeCardProperties,
ONBOARDING_FLOW_VERSION
@@ -2758,11 +2759,19 @@ function closeWebOnboarding(base: OnboardingState): OnboardingState {
function readLocalWebUIState(): PersistedUIState {
const defaults = getDefaultUIState()
const stored = readJson>(UI_STORAGE_KEY, {})
- if (typeof stored.rightSidebarOpen === 'boolean') {
- return mergeWebUIState(defaults, stored)
- }
const storedSettings = getStoredSettings()
- return mergeWebUIState(defaults, {
+ const base = {
+ ...defaults,
+ // Why: when runtime ui.get is unavailable, web fallback must mirror the
+ // main-process missing-property seed from the legacy card layout mode.
+ worktreeCardProperties: getWorktreeCardModeProperties(
+ storedSettings.compactWorktreeCards ? 'Compact' : 'Default'
+ )
+ }
+ if (typeof stored.rightSidebarOpen === 'boolean') {
+ return mergeWebUIState(base, stored)
+ }
+ return mergeWebUIState(base, {
...stored,
// Why: web fallback lacks main-process normalization, so migrate the
// retired setting only when the local UI preference is still absent.
diff --git a/src/shared/constants.ts b/src/shared/constants.ts
index 00da56400..b502d4cc7 100644
--- a/src/shared/constants.ts
+++ b/src/shared/constants.ts
@@ -36,7 +36,7 @@ export {
TASK_WORKTREE_CARD_PROPERTIES,
getWorktreeCardModeProperties,
getWorktreeCardModeUpdates,
- isLegacyDefaultedCompactWorktreeCardProperties,
+ isDefaultedCompactWorktreeCardProperties,
normalizeWorktreeCardProperties
} from './worktree-card-properties'
diff --git a/src/shared/worktree-card-properties.test.ts b/src/shared/worktree-card-properties.test.ts
index 00a738380..6a24606c1 100644
--- a/src/shared/worktree-card-properties.test.ts
+++ b/src/shared/worktree-card-properties.test.ts
@@ -5,7 +5,7 @@ import {
TASK_WORKTREE_CARD_PROPERTIES,
getWorktreeCardModeProperties,
getWorktreeCardModeUpdates,
- isLegacyDefaultedCompactWorktreeCardProperties,
+ isDefaultedCompactWorktreeCardProperties,
normalizeWorktreeCardProperties
} from './worktree-card-properties'
@@ -62,15 +62,12 @@ describe('worktree card properties', () => {
})
})
- it('recognizes only the exact old defaulted Compact preset', () => {
- expect(isLegacyDefaultedCompactWorktreeCardProperties(['status', 'automation'])).toBe(true)
- expect(isLegacyDefaultedCompactWorktreeCardProperties(['status', 'unread', 'automation'])).toBe(
- true
- )
- expect(isLegacyDefaultedCompactWorktreeCardProperties(['automation', 'status'])).toBe(false)
- expect(isLegacyDefaultedCompactWorktreeCardProperties(['status'])).toBe(false)
- expect(isLegacyDefaultedCompactWorktreeCardProperties(['status', 'automation', 'pr'])).toBe(
- false
- )
+ it('recognizes only exact defaulted Compact presets', () => {
+ expect(isDefaultedCompactWorktreeCardProperties(['status'])).toBe(true)
+ expect(isDefaultedCompactWorktreeCardProperties(['status', 'unread'])).toBe(true)
+ expect(isDefaultedCompactWorktreeCardProperties(['status', 'automation'])).toBe(true)
+ expect(isDefaultedCompactWorktreeCardProperties(['status', 'unread', 'automation'])).toBe(true)
+ expect(isDefaultedCompactWorktreeCardProperties(['automation', 'status'])).toBe(false)
+ expect(isDefaultedCompactWorktreeCardProperties(['status', 'automation', 'pr'])).toBe(false)
})
})
diff --git a/src/shared/worktree-card-properties.ts b/src/shared/worktree-card-properties.ts
index ee1cee312..76b1c32eb 100644
--- a/src/shared/worktree-card-properties.ts
+++ b/src/shared/worktree-card-properties.ts
@@ -25,6 +25,7 @@ export const DEFAULT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = [
// Why: compact cards default to the quiet preset; metadata icons remain opt-in
// through Show properties instead of appearing automatically.
export const COMPACT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = ['status']
+const NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES: WorktreeCardProperty[] = ['status', 'unread']
const LEGACY_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION: WorktreeCardProperty[] = [
'status',
@@ -83,10 +84,12 @@ export function getWorktreeCardModeUpdates(mode: WorktreeCardMode): {
}
}
-export function isLegacyDefaultedCompactWorktreeCardProperties(
+export function isDefaultedCompactWorktreeCardProperties(
properties: readonly unknown[] | null | undefined
): boolean {
return (
+ matchesWorktreeCardProperties(properties, COMPACT_WORKTREE_CARD_PROPERTIES) ||
+ matchesWorktreeCardProperties(properties, NORMALIZED_COMPACT_WORKTREE_CARD_PROPERTIES) ||
matchesWorktreeCardProperties(
properties,
LEGACY_COMPACT_WORKTREE_CARD_PROPERTIES_WITH_AUTOMATION