Skip to content
Closed
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
16 changes: 16 additions & 0 deletions src/main/runtime/orca-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13487,6 +13487,22 @@ describe('OrcaRuntimeService', () => {
expect(read.tail).toEqual(['after reload'])
})

it('notifies remote clients to refetch projects after the renderer graph reloads', () => {
const runtime = new OrcaRuntimeService(store)
const events: unknown[] = []
runtime.onClientEvent((event) => events.push(event))

syncSinglePty(runtime)
expect(events).toEqual([])

runtime.markRendererReloading(1)
syncSinglePty(runtime)
expect(events).toEqual([{ type: 'reposChanged' }])

syncSinglePty(runtime)
expect(events).toEqual([{ type: 'reposChanged' }])
})

it('keeps preallocated terminal handles valid when a reload graph omits the live leaf', async () => {
const runtime = new OrcaRuntimeService(store)
const handle = runtime.preAllocateHandleForPty('pty-1')
Expand Down
6 changes: 6 additions & 0 deletions src/main/runtime/orca-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3081,6 +3081,7 @@ export class OrcaRuntimeService {
throw new Error('Runtime graph publisher does not match the authoritative window')
}

const restoresReloadedRendererGraph = this.graphStatus === 'reloading'
this.tabs = new Map(graph.tabs.map((tab) => [tab.tabId, tab]))
this.syncMobileSessionTabs(graph.mobileSessionTabs)
const nextLeaves = new Map<string, RuntimeLeafRecord>()
Expand Down Expand Up @@ -3198,6 +3199,11 @@ export class OrcaRuntimeService {
this.notifyMobileSessionTabSnapshots()
this.graphStatus = 'ready'
this.refreshWritableFlags()
if (restoresReloadedRendererGraph) {
// Why: remote clients can discard project snapshots while this runtime is
// unavailable; the first rebuilt graph must tell them to fetch again.
this.emitClientEvent({ type: 'reposChanged' })
}
for (const leaf of this.leaves.values()) {
this.adoptPreAllocatedHandle(leaf)
}
Expand Down
27 changes: 27 additions & 0 deletions src/renderer/src/components/terminal-pane/pty-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
resetAgentStartupDelayedDeliveryForTests
} from '@/lib/agent-startup-delayed-delivery'
import type { PaneForegroundAgentEntry } from '@/store/slices/pane-foreground-agent'
import type { createPaneForegroundAgentTracker } from './pane-foreground-agent-tracker'

// Repro command:
// pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/pty-connection.test.ts -t "OpenTUI-style small ANSI redraw"
Expand Down Expand Up @@ -257,6 +258,12 @@ let mockStoreState: StoreState
let transportFactoryQueue: MockTransport[] = []
let createdTransportOptions: Record<string, unknown>[] = []
let storeSubscribers: ((state: StoreState) => void)[] = []
type PaneForegroundAgentTrackerFactory = typeof createPaneForegroundAgentTracker
type PaneForegroundAgentTracker = ReturnType<PaneForegroundAgentTrackerFactory>
type PaneForegroundAgentTrackerModule = {
createPaneForegroundAgentTracker: PaneForegroundAgentTrackerFactory
}
const paneForegroundAgentTrackers = new Set<PaneForegroundAgentTracker>()

vi.mock('@/runtime/sync-runtime-graph', () => ({
scheduleRuntimeGraphSync
Expand All @@ -278,6 +285,20 @@ vi.mock('./terminal-webgl-atlas-recovery', () => ({
scheduleTerminalWebglAtlasRecovery
}))

vi.mock('./pane-foreground-agent-tracker', async (importOriginal) => {
const actual = await importOriginal<PaneForegroundAgentTrackerModule>()
return {
...actual,
createPaneForegroundAgentTracker: (
...args: Parameters<typeof actual.createPaneForegroundAgentTracker>
) => {
const tracker = actual.createPaneForegroundAgentTracker(...args)
paneForegroundAgentTrackers.add(tracker)
return tracker
}
}
})

function notifyStoreSubscribers(): void {
for (const listener of storeSubscribers.slice()) {
listener(mockStoreState)
Expand Down Expand Up @@ -894,6 +915,12 @@ describe('connectPanePty', () => {
})

afterEach(() => {
// Why: most cases do not retain their pane binding; cancel its delayed reads
// before they can publish into the next case's freshly reset store mock.
for (const tracker of paneForegroundAgentTrackers) {
tracker.dispose()
}
paneForegroundAgentTrackers.clear()
vi.useRealTimers()
if (originalRequestAnimationFrame) {
globalThis.requestAnimationFrame = originalRequestAnimationFrame
Expand Down
Loading