mirror of
https://github.com/stablyai/orca.git
synced 2026-09-24 00:02:24 +00:00
* fix(mobile): keep the create form on screen through drawer swaps and survive reconnects The create-worktree flow could reach a state where the shared modal host was mounted with no sheet in it: a full-screen transparent window that swallows every tap with no way out. Frame analysis of the reported recording and a live simulator repro both land on the same state - the form sheet laid out at the right frame with progress=1, backdrop painting, sheet not painted. - Keep the form sheet mounted through every drawer transition, so the host Modal is never on screen without a sheet, and drop the render-read pin ref. - Re-assert a pinned sheet's enter transform when it takes the window back from a fill picker; nothing re-applied it before. - Key the form session on hostId, not on the RpcClient object: useHostClient swaps that object on every reconnect, which silently remounted the form and threw away the picked source. - Run the pasted-item lookup concurrently with the provider fan-out instead of after it (measured 2631ms -> 1480ms for a typed PR number). * fix(mobile): remount the sheet view on window hand-back so a rebuilt native view repaints On-device confirmation showed the committed hand-back re-assert never reaches the native view: progress already sits at 1 and translateY at 0, so withTiming produces no style delta, and the dead screen stayed reachable (1/25 on the committed build; 1/9 with a sub-pixel value nudge, which lands on the stale native binding when the view was rebuilt with a new tag). Remounting the sheet's Animated.View on an epoch keyed to the hand-back mounts a fresh native view with the style computed from the current shared values - progress is already 1, so it paints in place with no visible animation. 0 dead in 50 attempts on the remount build under the same churn condition that reproduced the dead screen on base. LANE-REPORT.md carries the full confirmation evidence and limits. * chore: drop the stray lane report from the repo root It is a working artifact, not source, and the root directory guard blocks any new top-level entry. * test(mobile): assert the sheet subtree rebuild directly, not through a test-only prop The hand-back test proved the remount by reading an epoch-keyed nativeID that existed only for it — production markup shaped by a test, and an assertion a future refactor could satisfy without rebuilding anything. Count mounts of the sheet's content instead, which is the property the fix actually depends on, and drop the nativeID. Also stop typing test renderers as 'ReactTestRenderer | null'. The static analysis job installs no mobile/node_modules, so that type is unresolvable there and the union trips no-redundant-type-constituents on every added line. The hand-back re-assert is not dead code as the old comment implied: the drawer swap hands back at 166ms, before the 180ms enter animation ends. * fix(mobile): keep the create form when a render is thrown away The session key was built from counters mutated during render. A blurred screen suspends this subtree (react-native-screens freezes via react-freeze), so React runs the component and then discards that render — but the counter bumps survive it. The next committed render then produced a new key and remounted the form, throwing away the picked source for a host switch or a close that never committed. Hold the open epoch in state, which React discards with the render that set it, and put the host in the key directly instead of counting host changes.
60 lines
1.9 KiB
TypeScript
60 lines
1.9 KiB
TypeScript
import { createElement } from 'react'
|
|
import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { RpcClient } from '../transport/rpc-client'
|
|
import { useSmartWorkspaceSource } from './use-smart-workspace-source'
|
|
|
|
function Probe(props: { client: RpcClient; query: string }) {
|
|
useSmartWorkspaceSource({
|
|
client: props.client,
|
|
enabled: true,
|
|
mode: 'smart',
|
|
query: props.query,
|
|
repoId: 'repo-1',
|
|
githubAvailable: true,
|
|
gitlabAvailable: false,
|
|
linearAvailable: false,
|
|
mrStateFilter: 'opened',
|
|
repos: [{ id: 'repo-1', displayName: 'orca', slug: { owner: 'stablyai', repo: 'orca' } }]
|
|
})
|
|
return null
|
|
}
|
|
|
|
// The picker makes two independent host round trips for a pasted PR number: the
|
|
// provider fan-out and the exact-item lookup. Awaiting the fan-out first stacked
|
|
// them, so the rows appeared a whole extra round trip late.
|
|
describe('smart source paste lookup concurrency', () => {
|
|
const mounted: ReactTestRenderer[] = []
|
|
|
|
beforeEach(() => vi.useFakeTimers())
|
|
afterEach(() => {
|
|
act(() => {
|
|
for (const renderer of mounted) {
|
|
renderer.unmount()
|
|
}
|
|
})
|
|
mounted.length = 0
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it('issues the pasted-number lookup while the fan-out is still in flight', async () => {
|
|
const sent: string[] = []
|
|
const sendRequest = vi.fn((method: string) => {
|
|
sent.push(method)
|
|
// Nothing ever settles: only requests issued concurrently can be observed.
|
|
return new Promise(() => {})
|
|
})
|
|
const client = { sendRequest } as unknown as RpcClient
|
|
|
|
await act(async () => {
|
|
mounted.push(create(createElement(Probe, { client, query: '16831' })))
|
|
})
|
|
await act(async () => {
|
|
await vi.advanceTimersByTimeAsync(300)
|
|
})
|
|
|
|
expect(sent).toContain('github.listWorkItems')
|
|
expect(sent).toContain('github.workItem')
|
|
})
|
|
})
|