mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
* fix(repos): remove a paired computer's deleted projects from every connected device A project deleted on a paired Orca host stayed in every connected client's sidebar and could not be removed there. Two independent defects: 1. Host-local repo IPC mutations only sent `repos:changed` to the host's own renderer (src/main/ipc/repos.ts:2711). The runtime client-event stream was fed only by mutations arriving over runtime RPC, and clients refetch a remote catalog only on a `reposChanged` event -- there is no polling on desktop -- so the deleted rows persisted indefinitely. The shared `notifyReposChanged` helper now also calls the new `OrcaRuntimeService.notifyReposChangedForRemoteClients()` (src/main/runtime/orca-runtime.ts:5175), mirroring the existing `notifyWorktreesChangedForRemoteClients` precedent. This covers every repo, project-group and folder-workspace IPC mutation, so renames, colors, reorders and adds propagate too. 2. Deleting the ghost row on the client routed `repo.rm` to the owner, which answered `repo_not_found`. `removeProject` wrapped its whole body in one try/catch, so the rejection aborted the local purge before the `set()` (src/renderer/src/store/slices/repos.ts:3466) and the delete button appeared to do nothing. Only `repo_not_found` is now tolerated; any other failure still keeps the row, and an opt-in `errorFeedback: 'toast'` makes it visible at the three single-project user-initiated entry points. Bulk and background callers keep today's silence plus their own aggregate reporting. Closes #11994 Co-authored-by: Orca <help@stably.ai> * fix(repos): revert inert RepositoryPane removeProject arg The settings pane's only render site drops the argument; the toast is already delivered by removeSettingsProjectFromAllHosts. Co-authored-by: Orca <help@stably.ai> * fix(repos): scope duplicate-repo-id deletes to the owning execution host Cover the cross-host collisions #11994's broadcast now fans out to every paired device. Same-name projects on different hosts were already isolated (per-host UUIDs, host-scoped catalog merge and purge) and are pinned by regression tests. Two same-repo-id paths were not: `repo.rm` with a `path:`/`name:` selector and `deleteProjectHostSetup` both resolved one row and then deleted by bare id, taking the sibling host's registration with it. Co-authored-by: Orca <help@stably.ai> * test(mobile): align the poll-interval rationale with the new reposChanged emission Co-authored-by: Orca <help@stably.ai> * fix(repos): resolve deleteProjectHostSetup's repo row only on the setup's own host The sibling-host fallback could only ever pick a row on a host the caller did not name; with no exact match the setup is stale and the existing path already drops just the setup. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
91 lines
3.0 KiB
TypeScript
91 lines
3.0 KiB
TypeScript
import { AppState } from 'react-native'
|
|
import type { RuntimeClientEventStreamMessage } from '../../../src/shared/runtime-client-events'
|
|
import type { RpcClient } from '../transport/rpc-client'
|
|
|
|
const WORKTREE_REFRESH_MS = 3000
|
|
|
|
type WorktreeRefreshOptions = { allowDuringModal?: boolean }
|
|
type RepoRefreshOptions = { force?: boolean; queueIfInFlight?: boolean }
|
|
|
|
type HostWorktreeRefreshArgs = {
|
|
client: RpcClient
|
|
fetchWorktrees: (options?: WorktreeRefreshOptions) => Promise<void>
|
|
fetchRepoMetadata: (options?: RepoRefreshOptions) => Promise<void>
|
|
}
|
|
|
|
export function startHostWorktreeRefresh({
|
|
client,
|
|
fetchWorktrees,
|
|
fetchRepoMetadata
|
|
}: HostWorktreeRefreshArgs): () => void {
|
|
let stale = false
|
|
let eventStreamReady = false
|
|
|
|
const refreshOnForeground = (): void => {
|
|
if (AppState.currentState !== 'active') {
|
|
return
|
|
}
|
|
void fetchWorktrees({ allowDuringModal: true })
|
|
void fetchRepoMetadata({ queueIfInFlight: true })
|
|
}
|
|
|
|
const appStateSubscription = AppState.addEventListener('change', (state) => {
|
|
if (state === 'active') {
|
|
refreshOnForeground()
|
|
}
|
|
})
|
|
const interval = setInterval(() => {
|
|
if (AppState.currentState !== 'active') {
|
|
return
|
|
}
|
|
void fetchWorktrees()
|
|
// Why: desktop Settings repo edits (icon/color/name, repo removal) now emit `reposChanged`
|
|
// (#11994), but a host on an older build does not; keep this periodic repo.list as the
|
|
// convergence safety-net. fetchRepoMetadata self-throttles to REPO_METADATA_REFRESH_MS
|
|
// (60s), so this is ~1 request/min while foregrounded — the AppState gate is what removes
|
|
// the waste (both stop while backgrounded).
|
|
void fetchRepoMetadata()
|
|
}, WORKTREE_REFRESH_MS)
|
|
const unsubscribe = client.subscribe(
|
|
'runtime.clientEvents.subscribe',
|
|
null,
|
|
(payload: unknown) => {
|
|
if (stale || !payload || typeof payload !== 'object') {
|
|
return
|
|
}
|
|
const event = payload as RuntimeClientEventStreamMessage | { type: 'error' }
|
|
if (event.type === 'ready') {
|
|
const replayedAfterReconnect = eventStreamReady
|
|
eventStreamReady = true
|
|
if (replayedAfterReconnect) {
|
|
// Why: client events are not queued while disconnected, so re-read both snapshots after replay.
|
|
void fetchWorktrees()
|
|
void fetchRepoMetadata({ force: true, queueIfInFlight: true })
|
|
}
|
|
return
|
|
}
|
|
if (event.type === 'end' || event.type === 'error') {
|
|
eventStreamReady = false
|
|
return
|
|
}
|
|
if (event.type === 'reposChanged') {
|
|
// Why: folder workspace mutations publish reposChanged, and worktree.ps owns this catalog.
|
|
void fetchWorktrees()
|
|
void fetchRepoMetadata({ force: true, queueIfInFlight: true })
|
|
} else if (event.type === 'worktreesChanged') {
|
|
void fetchWorktrees()
|
|
}
|
|
}
|
|
)
|
|
|
|
void fetchWorktrees()
|
|
void fetchRepoMetadata({ force: true, queueIfInFlight: true })
|
|
|
|
return () => {
|
|
stale = true
|
|
clearInterval(interval)
|
|
appStateSubscription.remove()
|
|
unsubscribe()
|
|
}
|
|
}
|