mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 08:02:12 +00:00
fix(native-chat): ask which workspace the composer serves now
The IME-flush ownership check compared the workspace id captured when the drop happened against the same captured value, so for a structured pane the comparison could only ever hold. The live protection came from the host and owner checks beside it; this one asked nothing. Read the id through a ref so the check means what it reads as. A pane whose structured target moves between the drop and the composition settling now refuses the queued path instead of attaching it.
This commit is contained in:
+20
-1
@@ -111,6 +111,8 @@ type ProbeProps = {
|
||||
disabled?: boolean
|
||||
initialDraft?: string
|
||||
structured?: boolean
|
||||
/** Overrides only the structured target, leaving the pane's scope key alone. */
|
||||
structuredWorkspaceId?: string
|
||||
workspaceId?: string
|
||||
}
|
||||
|
||||
@@ -121,6 +123,7 @@ function ComposerProbe({
|
||||
disabled = false,
|
||||
initialDraft = '',
|
||||
structured = true,
|
||||
structuredWorkspaceId,
|
||||
workspaceId = 'worktree-1'
|
||||
}: ProbeProps): React.JSX.Element {
|
||||
const [draft, setDraft] = useState(initialDraft)
|
||||
@@ -143,7 +146,7 @@ function ComposerProbe({
|
||||
})
|
||||
const workspaceFileDropHandlers = useNativeChatWorkspaceFileDrop({
|
||||
terminalTabId: 'terminal-tab-1',
|
||||
structuredWorktreeId: structured ? workspaceId : undefined,
|
||||
structuredWorktreeId: structured ? (structuredWorkspaceId ?? workspaceId) : undefined,
|
||||
disabled,
|
||||
attachResolvedPaths: attachments.attachResolvedPaths,
|
||||
setNotice
|
||||
@@ -244,6 +247,7 @@ describe('native chat workspace file drops', () => {
|
||||
testState.ownerSshGeneration = 4
|
||||
testState.ownerWorktreePath = '/remote/repo'
|
||||
testState.targetIsRemoteRuntime = false
|
||||
testState.store.tabsByWorktree = { 'worktree-1': [{ id: 'terminal-tab-1' }] }
|
||||
latestInput = null
|
||||
bubbledDrop.mockReset()
|
||||
})
|
||||
@@ -425,6 +429,21 @@ describe('native chat workspace file drops', () => {
|
||||
expect(screen.getByText('Files can only be attached to their source workspace.')).toBeTruthy()
|
||||
})
|
||||
|
||||
// The queued check must ask which workspace this composer serves NOW. Comparing
|
||||
// a captured id against itself would pass no matter where the pane ended up.
|
||||
it('rejects an IME-queued path when the pane changes workspace before settling', () => {
|
||||
const view = render(<ComposerProbe initialDraft="preedit" />)
|
||||
const input = editor()
|
||||
fireEvent.compositionStart(input)
|
||||
dispatchDragEvent('drop', input, internalTransfer(['/repo/a.ts']))
|
||||
|
||||
view.rerender(<ComposerProbe initialDraft="preedit" structuredWorkspaceId="worktree-2" />)
|
||||
fireEvent.compositionEnd(input, { data: '' })
|
||||
|
||||
expect(screen.getByTestId('draft').textContent).toBe('preedit')
|
||||
expect(screen.getByText('Files can only be attached to their source workspace.')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('rejects only the exact unresolved-owner sentinel', () => {
|
||||
testState.executionHostId = 'runtime:unresolved-owner'
|
||||
const view = render(<ComposerProbe />)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, type DragEventHandler } from 'react'
|
||||
import { useCallback, useLayoutEffect, useRef, type DragEventHandler } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { getExecutionHostIdForWorktree } from '@/lib/worktree-runtime-owner'
|
||||
import {
|
||||
@@ -67,6 +67,14 @@ export function useNativeChatWorkspaceFileDrop({
|
||||
structuredWorktreeId,
|
||||
terminalTabId
|
||||
}: Args): WorkspaceFileDropHandlers {
|
||||
// The IME-flush check runs against a closure captured at drop time. Reading
|
||||
// the prop through a ref keeps "is this still my workspace?" a real question
|
||||
// rather than a comparison of one captured value against itself.
|
||||
const structuredWorktreeIdRef = useRef(structuredWorktreeId)
|
||||
useLayoutEffect(() => {
|
||||
structuredWorktreeIdRef.current = structuredWorktreeId
|
||||
}, [structuredWorktreeId])
|
||||
|
||||
const onDragOverCapture = useCallback<DragEventHandler<HTMLDivElement>>(
|
||||
(event) => {
|
||||
if (!hasWorkspaceFileDragType(event.dataTransfer)) {
|
||||
@@ -130,7 +138,7 @@ export function useNativeChatWorkspaceFileDrop({
|
||||
const targetOwnerIsCurrent = (): boolean => {
|
||||
const currentState = useAppStore.getState()
|
||||
const currentWorkspaceId =
|
||||
structuredWorktreeId ??
|
||||
structuredWorktreeIdRef.current ??
|
||||
findTerminalTabWorktreeId(currentState.tabsByWorktree, terminalTabId)
|
||||
if (currentWorkspaceId !== source.workspaceId) {
|
||||
return false
|
||||
@@ -139,7 +147,7 @@ export function useNativeChatWorkspaceFileDrop({
|
||||
const currentOwner = resolveNativeChatAttachmentOwnerForWorktree(
|
||||
currentState,
|
||||
currentWorkspaceId,
|
||||
structuredWorktreeId ? undefined : terminalTabId
|
||||
structuredWorktreeIdRef.current ? undefined : terminalTabId
|
||||
)
|
||||
return (
|
||||
isResolvedWorkspaceFileDragExecutionHost(currentHostId) &&
|
||||
|
||||
Reference in New Issue
Block a user