Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/renderer/src/app-shell/use-persisted-ui-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export function usePersistedUIWriter(): void {
sortBy: s.sortBy,
projectOrderBy: s.projectOrderBy,
showSleepingWorkspaces: s.showSleepingWorkspaces,
workspaceHostScope: s.workspaceHostScope,
visibleWorkspaceHostIds: s.visibleWorkspaceHostIds,
hideDefaultBranchWorkspace: s.hideDefaultBranchWorkspace,
hideAutomationGeneratedWorkspaces: s.hideAutomationGeneratedWorkspaces,
hideCliCreatedWorkspaces: s.hideCliCreatedWorkspaces,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,40 @@ describe('workspace view preferences: cross-client persistence (STA-5781)', () =
expect(after.hideCliCreatedWorkspaces).toBe(before.hideCliCreatedWorkspaces)
})

it('saves a revealed remote host after an unrelated older broadcast', async () => {
act(() => {
authority.set({ workspaceHostScope: 'local', visibleWorkspaceHostIds: ['local'] })
})
deliverBroadcasts()
act(() => {
authority.set({ sidebarWidth: 320 })
store.getState().setVisibleWorkspaceHostIds(['local', 'runtime:m4air'])
})
pendingBroadcasts.reverse()
deliverBroadcasts()
expect(store.getState().visibleWorkspaceHostIds).toEqual(['local', 'runtime:m4air'])
await flushDesktopDebounce()
deliverBroadcasts()
expect(authority.get().visibleWorkspaceHostIds).toEqual(['local', 'runtime:m4air'])
expect(authority.get().sidebarWidth).toBe(320)
})

it('saves All hosts after flipping back during a host-selection write', async () => {
holdAcks = true
act(() => store.getState().setWorkspaceHostScope('local'))
await flushDesktopDebounce()
act(() => store.getState().setWorkspaceHostScope('all'))
deliverBroadcasts()
expect(store.getState().visibleWorkspaceHostIds).toBeNull()
expect(store.getState().workspaceHostScope).toBe('all')
await resolveAcks()
holdAcks = false
await flushDesktopDebounce()
deliverBroadcasts()
expect(authority.get().visibleWorkspaceHostIds).toBeNull()
expect(authority.get().workspaceHostScope).toBe('all')
})

it('persists a left sidebar close across an unrelated sync and restores it on startup', async () => {
act(() => {
store.getState().toggleSidebar()
Expand Down
13 changes: 13 additions & 0 deletions src/renderer/src/store/slices/persisted-ui-write-baseline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ function makeBaseline(overrides: Partial<PersistedUIWriteBaseline> = {}): Persis
sortBy: 'recent',
projectOrderBy: 'manual',
showSleepingWorkspaces: true,
workspaceHostScope: 'all',
visibleWorkspaceHostIds: null,
hideDefaultBranchWorkspace: false,
hideAutomationGeneratedWorkspaces: false,
hideCliCreatedWorkspaces: false,
Expand Down Expand Up @@ -58,18 +60,29 @@ describe('PERSISTED_UI_WRITE_BASELINE_FIELDS', () => {
describe('diffPersistedUIWriteFields', () => {
it('is empty when values are equal even across fresh array/record identities', () => {
const a = makeBaseline({
visibleWorkspaceHostIds: ['local', 'runtime:m4air'],
filterRepoIds: ['r1', 'r2'],
showDotfilesByWorktree: { w1: true },
acknowledgedAgentsByPaneKey: { p1: 5 }
})
const b = makeBaseline({
visibleWorkspaceHostIds: ['local', 'runtime:m4air'],
filterRepoIds: ['r1', 'r2'],
showDotfilesByWorktree: { w1: true },
acknowledgedAgentsByPaneKey: { p1: 5 }
})
expect(diffPersistedUIWriteFields(a, b)).toEqual({})
})

it('distinguishes All hosts from a selected host list', () => {
expect(
diffPersistedUIWriteFields(
makeBaseline({ visibleWorkspaceHostIds: null }),
makeBaseline({ visibleWorkspaceHostIds: ['local'] })
)
).toEqual({ visibleWorkspaceHostIds: null })
})

it('reports only the diverged fields, valued from the current mirror', () => {
const baseline = makeBaseline()
const current = makeBaseline({ showSleepingWorkspaces: false, filterRepoIds: ['r1'] })
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/src/store/slices/persisted-ui-write-baseline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export type PersistedUIWriteBaseline = {
sortBy: PersistedUIState['sortBy']
projectOrderBy: PersistedUIState['projectOrderBy']
showSleepingWorkspaces: boolean
workspaceHostScope: PersistedUIState['workspaceHostScope']
visibleWorkspaceHostIds: PersistedUIState['visibleWorkspaceHostIds']
hideDefaultBranchWorkspace: boolean
hideAutomationGeneratedWorkspaces: boolean
hideCliCreatedWorkspaces: boolean
Expand Down Expand Up @@ -52,6 +54,8 @@ const PERSISTED_UI_WRITE_BASELINE_FIELD_SET = {
sortBy: true,
projectOrderBy: true,
showSleepingWorkspaces: true,
workspaceHostScope: true,
visibleWorkspaceHostIds: true,
hideDefaultBranchWorkspace: true,
hideAutomationGeneratedWorkspaces: true,
hideCliCreatedWorkspaces: true,
Expand Down Expand Up @@ -104,6 +108,9 @@ function writeFieldEqual(field: keyof PersistedUIWriteBaseline, a: unknown, b: u
if (field === 'filterRepoIds') {
return stringArrayEqual(a as readonly string[], b as readonly string[])
}
if (field === 'visibleWorkspaceHostIds') {
return a === b || (Array.isArray(a) && Array.isArray(b) && stringArrayEqual(a, b))
}
if (
field === 'explorerDisplayRootByWorktree' ||
field === 'showDotfilesByWorktree' ||
Expand Down
58 changes: 58 additions & 0 deletions src/renderer/src/store/slices/ui-host-filter-sync.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createUIStore, makePersistedUI } from './ui-slice-test-harness'

beforeEach(() => {
vi.stubGlobal('window', { api: { ui: { set: vi.fn().mockResolvedValue(undefined) } } })
})
afterEach(() => vi.unstubAllGlobals())

describe('host visibility across saved UI broadcasts', () => {
it('keeps a newly revealed remote host when an older UI snapshot arrives', () => {
const store = createUIStore()
const previous = makePersistedUI({
workspaceHostScope: 'local',
visibleWorkspaceHostIds: ['local']
})
store.getState().hydratePersistedUI(previous)

store.getState().setVisibleWorkspaceHostIds(['local', 'runtime:m4air'])
store.getState().hydratePersistedUI(previous, 'sync')

expect(store.getState().visibleWorkspaceHostIds).toEqual(['local', 'runtime:m4air'])
})

it('keeps All hosts while the prior selection is being saved', () => {
const store = createUIStore()
const previous = makePersistedUI({ workspaceHostScope: 'all', visibleWorkspaceHostIds: null })
store.getState().hydratePersistedUI(previous)
const fields = ['workspaceHostScope', 'visibleWorkspaceHostIds'] as const
store.getState().setWorkspaceHostScope('local')
store.getState().notePersistedUIWriteStarted(fields)
store.getState().setWorkspaceHostScope('all')

store
.getState()
.hydratePersistedUI(
makePersistedUI({ workspaceHostScope: 'local', visibleWorkspaceHostIds: ['local'] }),
'sync'
)

expect(store.getState().workspaceHostScope).toBe('all')
expect(store.getState().visibleWorkspaceHostIds).toBeNull()
})

it('accepts another client’s host selection when there is no local edit', () => {
const store = createUIStore()
store.getState().hydratePersistedUI(makePersistedUI())
store.getState().hydratePersistedUI(
makePersistedUI({
workspaceHostScope: 'runtime:m4air',
visibleWorkspaceHostIds: ['runtime:m4air']
}),
'sync'
)

expect(store.getState().workspaceHostScope).toBe('runtime:m4air')
expect(store.getState().visibleWorkspaceHostIds).toEqual(['runtime:m4air'])
})
})
14 changes: 4 additions & 10 deletions src/renderer/src/store/slices/ui-hydration-view-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ describe('createUISlice hydratePersistedUI', () => {
expect(setUI).not.toHaveBeenCalled()
})

it('persists workspace host scope changes', () => {
it('updates workspace host scope for the guarded UI writer', () => {
const setUI = vi.fn(() => Promise.resolve())
vi.stubGlobal('window', { api: { ui: { set: setUI } } })
const store = createUIStore()
Expand All @@ -489,13 +489,10 @@ describe('createUISlice hydratePersistedUI', () => {

expect(store.getState().workspaceHostScope).toBe('runtime:env-1')
expect(store.getState().visibleWorkspaceHostIds).toEqual(['runtime:env-1'])
expect(setUI).toHaveBeenCalledWith({
workspaceHostScope: 'runtime:env-1',
visibleWorkspaceHostIds: ['runtime:env-1']
})
expect(setUI).not.toHaveBeenCalled()
})

it('persists visible workspace host changes independently of focused host', () => {
it('updates host visibility independently of focused host for the guarded UI writer', () => {
const setUI = vi.fn(() => Promise.resolve())
vi.stubGlobal('window', { api: { ui: { set: setUI } } })
const store = createUIStore()
Expand All @@ -505,10 +502,7 @@ describe('createUISlice hydratePersistedUI', () => {

expect(store.getState().workspaceHostScope).toBe('runtime:env-1')
expect(store.getState().visibleWorkspaceHostIds).toEqual(['local', 'runtime:env-1'])
expect(setUI).toHaveBeenLastCalledWith({
workspaceHostScope: 'runtime:env-1',
visibleWorkspaceHostIds: ['local', 'runtime:env-1']
})
expect(setUI).not.toHaveBeenCalled()
})

it('persists workspace host order changes', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ export function createUiPreferenceActions(set: UISliceSet, get: UISliceGet): Par
const normalized = normalizeExecutionHostScope(scope)
const visibleWorkspaceHostIds = normalized === 'all' ? null : [normalized]
set({ workspaceHostScope: normalized, visibleWorkspaceHostIds })
window.api.ui
.set({ workspaceHostScope: normalized, visibleWorkspaceHostIds })
.catch(console.error)
},
visibleWorkspaceHostIds: null,
setVisibleWorkspaceHostIds: (ids) => {
Expand All @@ -82,9 +79,6 @@ export function createUiPreferenceActions(set: UISliceSet, get: UISliceGet): Par
workspaceHostScope = normalized[0]
}
set({ visibleWorkspaceHostIds: normalized, workspaceHostScope })
window.api.ui
.set({ visibleWorkspaceHostIds: normalized, workspaceHostScope })
.catch(console.error)
},
workspaceHostOrder: [],
setWorkspaceHostOrder: (ids) => {
Expand Down
Loading