mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
Route file explorer requests by worktree owner
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -289,3 +289,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-13: Routed Delete Workspace dirty-state preloads through the selected worktree runtime owner instead of ambient focused settings, so delete warnings inspect the host that owns the workspace. Verified with focused DeleteWorktreeDialog host-boundary tests, existing worktree-owner tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed editor file-drop stat/import operations through the selected worktree runtime owner instead of ambient focused settings, so local, SSH, and runtime workspace drops use the host that owns the workspace. Verified with focused file-drop owner tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed agent draft-paste readiness probes and paste writes through the tab's worktree runtime owner when available, covering legacy remote PTY ids that do not embed their runtime owner. Verified with focused agent-paste tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-13: Routed File Explorer directory reads, stat/import/create/move/duplicate operations, ignored-file checks, and background git/conflict polling through the selected worktree runtime owner instead of ambient focused settings. Verified with focused right-sidebar boundary/projection/polling/drag tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`.
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const root = process.cwd()
|
||||
|
||||
function source(path: string): string {
|
||||
return readFileSync(join(root, path), 'utf8')
|
||||
}
|
||||
|
||||
describe('right sidebar file/git runtime ownership boundaries', () => {
|
||||
it.each([
|
||||
'src/renderer/src/components/right-sidebar/useFileExplorerTree.ts',
|
||||
'src/renderer/src/components/right-sidebar/useFileExplorerImport.ts',
|
||||
'src/renderer/src/components/right-sidebar/useFileExplorerInlineInput.ts',
|
||||
'src/renderer/src/components/right-sidebar/useFileExplorerDragDrop.ts',
|
||||
'src/renderer/src/components/right-sidebar/useFileDuplicate.ts',
|
||||
'src/renderer/src/components/right-sidebar/useFileExplorerVisibleRowProjection.ts',
|
||||
'src/renderer/src/components/right-sidebar/useGitStatusPolling.ts'
|
||||
])('%s routes file/git requests by the selected worktree owner', (path) => {
|
||||
const text = source(path)
|
||||
|
||||
expect(text).toContain('getRightSidebarWorktreeRuntimeSettings')
|
||||
expect(text).not.toContain('settings: useAppStore.getState().settings')
|
||||
expect(text).not.toContain('const settings = useAppStore.getState().settings')
|
||||
})
|
||||
|
||||
it('derives owner settings through the shared worktree runtime owner helper', () => {
|
||||
const text = source('src/renderer/src/components/right-sidebar/file-explorer-runtime-owner.ts')
|
||||
|
||||
expect(text).toContain('getSettingsForWorktreeRuntimeOwner')
|
||||
expect(text).toContain('useAppStore.getState()')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
import { getSettingsForWorktreeRuntimeOwner } from '@/lib/worktree-runtime-owner'
|
||||
import { useAppStore } from '@/store'
|
||||
|
||||
export function getRightSidebarWorktreeRuntimeSettings(
|
||||
worktreeId: string | null | undefined
|
||||
): Pick<GlobalSettings, 'activeRuntimeEnvironmentId'> {
|
||||
const store = useAppStore.getState()
|
||||
// Why: right-sidebar file/git actions operate on the selected workspace.
|
||||
// Route by that workspace owner so global focused-host changes cannot retarget them.
|
||||
return getSettingsForWorktreeRuntimeOwner(store, worktreeId)
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { basename, dirname, joinPath } from '@/lib/path'
|
||||
import type { TreeNode } from './file-explorer-types'
|
||||
import { useAppStore } from '@/store'
|
||||
import { copyRuntimePath, runtimePathExists } from '@/runtime/runtime-file-client'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
/**
|
||||
* Electron's ipcRenderer.invoke wraps errors as:
|
||||
@@ -42,9 +42,8 @@ export function useFileDuplicate({
|
||||
const ext = dotIndex > 0 ? name.slice(dotIndex) : ''
|
||||
|
||||
const run = async (): Promise<void> => {
|
||||
const settings = useAppStore.getState().settings
|
||||
const context = {
|
||||
settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId: getConnectionId(activeWorktreeId) ?? undefined
|
||||
|
||||
@@ -12,6 +12,7 @@ import { remapOpenEditorTabsForPathChange } from '@/lib/remap-open-editor-tabs-f
|
||||
import { requestEditorSaveQuiesce } from '@/components/editor/editor-autosave'
|
||||
import { commitFileExplorerOp } from './fileExplorerUndoRedo'
|
||||
import { renameRuntimePath } from '@/runtime/runtime-file-client'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
function extractIpcErrorMessage(err: unknown, fallback: string): string {
|
||||
if (!(err instanceof Error)) {
|
||||
@@ -233,7 +234,7 @@ export function useFileExplorerDragDrop({
|
||||
try {
|
||||
const connectionId = getConnectionId(activeWorktreeId ?? null) ?? undefined
|
||||
const fileContext = {
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId
|
||||
|
||||
@@ -2,9 +2,9 @@ import { useEffect, useRef } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { extractIpcErrorMessage } from '@/lib/ipc-error'
|
||||
import { useAppStore } from '@/store'
|
||||
import { importExternalPathsToRuntime } from '@/runtime/runtime-file-client'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
type UseFileExplorerImportParams = {
|
||||
worktreePath: string | null
|
||||
@@ -63,10 +63,9 @@ export function useFileExplorerImport({
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
const settings = useAppStore.getState().settings
|
||||
const { results } = await importExternalPathsToRuntime(
|
||||
{
|
||||
settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(wtId),
|
||||
worktreeId: wtId,
|
||||
worktreePath: worktreePathRef.current,
|
||||
connectionId
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { TreeNode } from './file-explorer-types'
|
||||
import type { FileExplorerRowProjection } from './file-explorer-row-projection'
|
||||
import { commitFileExplorerOp } from './fileExplorerUndoRedo'
|
||||
import { createRuntimePath, deleteRuntimePath } from '@/runtime/runtime-file-client'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
type UseFileExplorerInlineInputParams = {
|
||||
activeWorktreeId: string | null
|
||||
@@ -110,7 +111,7 @@ export function useFileExplorerInlineInput({
|
||||
const run = async (): Promise<void> => {
|
||||
const connectionId = getConnectionId(activeWorktreeId ?? null) ?? undefined
|
||||
const fileContext = {
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId
|
||||
|
||||
@@ -6,8 +6,8 @@ import type { DirCache, TreeNode } from './file-explorer-types'
|
||||
import { splitPathSegments } from './path-tree'
|
||||
import { shouldIncludeFileExplorerEntry } from './file-explorer-entries'
|
||||
import { readRuntimeDirectory, statRuntimePath } from '@/runtime/runtime-file-client'
|
||||
import { useAppStore } from '@/store'
|
||||
import { createFileExplorerDirLoadTracker } from './file-explorer-dir-load-tracker'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
type UseFileExplorerTreeResult = {
|
||||
dirCache: Record<string, DirCache>
|
||||
@@ -63,7 +63,7 @@ export function useFileExplorerTree(
|
||||
const connectionId = getConnectionId(activeWorktreeId ?? null) ?? undefined
|
||||
const entries = await readRuntimeDirectory(
|
||||
{
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId
|
||||
@@ -133,7 +133,7 @@ export function useFileExplorerTree(
|
||||
const connectionId = getConnectionId(activeWorktreeId ?? null) ?? undefined
|
||||
return statRuntimePath(
|
||||
{
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { getConnectionId } from '@/lib/connection-context'
|
||||
import { getRuntimeGitIgnoredPaths } from '@/runtime/runtime-git-client'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
import { isDotfileRelativePath } from './file-explorer-entries'
|
||||
import type { DirCache, TreeNode } from './file-explorer-types'
|
||||
import {
|
||||
@@ -162,7 +163,7 @@ export function useFileExplorerVisibleRowProjection(
|
||||
const connectionId = getConnectionId(activeWorktreeId) ?? undefined
|
||||
void getRuntimeGitIgnoredPaths(
|
||||
{
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId
|
||||
|
||||
@@ -163,7 +163,15 @@ describe('useGitStatusPolling', () => {
|
||||
})
|
||||
|
||||
expect(state.setUpstreamStatus).not.toHaveBeenCalled()
|
||||
expect(state.fetchUpstreamStatus).toHaveBeenCalledWith(worktree.id, '/repo', undefined)
|
||||
expect(state.fetchUpstreamStatus).toHaveBeenCalledWith(
|
||||
worktree.id,
|
||||
'/repo',
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
runtimeTargetSettings: { activeRuntimeEnvironmentId: null }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it('passes the explicit push target to upstream refreshes', async () => {
|
||||
@@ -182,7 +190,10 @@ describe('useGitStatusPolling', () => {
|
||||
worktree.id,
|
||||
'/repo',
|
||||
undefined,
|
||||
pushTarget
|
||||
pushTarget,
|
||||
{
|
||||
runtimeTargetSettings: { activeRuntimeEnvironmentId: null }
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { refreshGitStatusForWorktree } from './git-status-refresh'
|
||||
import { createCoalescedPollRunner } from './coalesced-poll-runner'
|
||||
import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval'
|
||||
import { shouldPollActiveGitStatus } from '@/lib/passive-macos-app-data-access'
|
||||
import { getRightSidebarWorktreeRuntimeSettings } from './file-explorer-runtime-owner'
|
||||
|
||||
const POLL_INTERVAL_MS = 3000
|
||||
|
||||
@@ -93,7 +94,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
|
||||
try {
|
||||
const connectionId = getConnectionId(activeWorktreeId) ?? undefined
|
||||
await refreshGitStatusForWorktree({
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(activeWorktreeId),
|
||||
worktreeId: activeWorktreeId,
|
||||
worktreePath,
|
||||
connectionId,
|
||||
@@ -175,7 +176,7 @@ export function useGitStatusPolling(options: { enabled?: boolean } = {}): void {
|
||||
continue
|
||||
}
|
||||
const op = (await getRuntimeGitConflictOperation({
|
||||
settings: useAppStore.getState().settings,
|
||||
settings: getRightSidebarWorktreeRuntimeSettings(id),
|
||||
worktreeId: id,
|
||||
worktreePath: path,
|
||||
connectionId
|
||||
|
||||
Reference in New Issue
Block a user