mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
* fix(mobile): persist per-device tab selection so worktree return restores the last open tab A phone's tab selection lived only in the host's in-memory ClientSessionTabSelectionStore. Any host restart wiped it, and the per-device projection then fell back to deterministic topology, so returning to a worktree on mobile always landed on the first tab instead of the tab last opened on the phone. Persist the per-device selections in the Store (keyed deviceId -> worktreeId), hydrate them when the runtime constructs, and guard projection so an early empty snapshot after restart cannot wipe a hydrated selection before tabs arrive. Selections are pruned with the worktree/repo and on device revoke, and malformed persisted payloads degrade to empty instead of throwing. * fix(mobile): harden persisted tab selection cleanup * fix(mobile): preserve tab selection across worktree rename
62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
import type {
|
|
PersistedMobileClientTabSelection,
|
|
PersistedMobileClientTabSelections
|
|
} from '../../shared/types'
|
|
|
|
function normalizeClientSessionTabSelection(
|
|
raw: unknown
|
|
): PersistedMobileClientTabSelection | null {
|
|
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
return null
|
|
}
|
|
const candidate = raw as Partial<PersistedMobileClientTabSelection>
|
|
const activeTabId = typeof candidate.activeTabId === 'string' ? candidate.activeTabId : null
|
|
const activeGroupId = typeof candidate.activeGroupId === 'string' ? candidate.activeGroupId : null
|
|
const activeTabIdByGroupId: Record<string, string> = {}
|
|
if (
|
|
typeof candidate.activeTabIdByGroupId === 'object' &&
|
|
candidate.activeTabIdByGroupId &&
|
|
!Array.isArray(candidate.activeTabIdByGroupId)
|
|
) {
|
|
for (const [groupId, tabId] of Object.entries(candidate.activeTabIdByGroupId)) {
|
|
if (typeof tabId === 'string') {
|
|
activeTabIdByGroupId[groupId] = tabId
|
|
}
|
|
}
|
|
}
|
|
if (!activeTabId && !activeGroupId && Object.keys(activeTabIdByGroupId).length === 0) {
|
|
return null
|
|
}
|
|
return { activeTabId, activeGroupId, activeTabIdByGroupId }
|
|
}
|
|
|
|
// Why: this state comes off disk (and, for remote runtimes, another machine); a bad payload must degrade to "no selection", not throw.
|
|
export function normalizePersistedMobileClientTabSelections(
|
|
raw: unknown
|
|
): PersistedMobileClientTabSelections {
|
|
const normalized: PersistedMobileClientTabSelections = {}
|
|
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
return normalized
|
|
}
|
|
for (const [clientNavigationId, selectionsByWorktree] of Object.entries(raw)) {
|
|
if (
|
|
typeof selectionsByWorktree !== 'object' ||
|
|
selectionsByWorktree === null ||
|
|
Array.isArray(selectionsByWorktree)
|
|
) {
|
|
continue
|
|
}
|
|
const entries: Record<string, PersistedMobileClientTabSelection> = {}
|
|
for (const [worktreeId, selection] of Object.entries(selectionsByWorktree)) {
|
|
const normalizedSelection = normalizeClientSessionTabSelection(selection)
|
|
if (normalizedSelection) {
|
|
entries[worktreeId] = normalizedSelection
|
|
}
|
|
}
|
|
if (Object.keys(entries).length > 0) {
|
|
normalized[clientNavigationId] = entries
|
|
}
|
|
}
|
|
return normalized
|
|
}
|