mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 10:27:33 +00:00
refactor(emulator): group pane modules by feature owner (#42340)
This commit is contained in:
@@ -245,7 +245,7 @@
|
||||
"reason": "Actual Claude CLI tool-result line in the terminal still; preserve the illustrative output and line count."
|
||||
},
|
||||
{
|
||||
"filePath": "src/renderer/src/components/emulator-pane/use-emulator-video-stream.ts",
|
||||
"filePath": "src/renderer/src/components/emulator-pane/streaming/use-emulator-video-stream.ts",
|
||||
"kind": "object-property:error",
|
||||
"text": "This build does not support WebCodecs H.264 decoding.",
|
||||
"dynamic": false,
|
||||
|
||||
@@ -144,11 +144,11 @@
|
||||
3 src/renderer/src/components/editor/loading/useEditorPanelFileContentLoader.ts
|
||||
4 src/renderer/src/components/editor/loading/log-tail/useLocalLogTail.ts
|
||||
1 src/renderer/src/components/editor/markdown-preview/viewport/usePreserveSectionDuringExternalEdit.ts
|
||||
1 src/renderer/src/components/emulator-pane/emulator-screen-stream-content.tsx
|
||||
3 src/renderer/src/components/emulator-pane/use-emulator-pane-session.ts
|
||||
1 src/renderer/src/components/emulator-pane/use-emulator-pane-shutdown.ts
|
||||
1 src/renderer/src/components/emulator-pane/use-emulator-video-stream.ts
|
||||
1 src/renderer/src/components/emulator-pane/use-mobile-emulator-agent-setup-state.ts
|
||||
1 src/renderer/src/components/emulator-pane/streaming/emulator-screen-stream-content.tsx
|
||||
3 src/renderer/src/components/emulator-pane/session/use-emulator-pane-session.ts
|
||||
1 src/renderer/src/components/emulator-pane/session/use-emulator-pane-shutdown.ts
|
||||
1 src/renderer/src/components/emulator-pane/streaming/use-emulator-video-stream.ts
|
||||
1 src/renderer/src/components/emulator-pane/agent-setup/use-mobile-emulator-agent-setup-state.ts
|
||||
1 src/renderer/src/components/feature-tips/FeatureTipsModal.tsx
|
||||
1 src/renderer/src/components/feature-wall/setup/agent-capabilities/AgentCapabilitiesSetupAction.tsx
|
||||
1 src/renderer/src/components/feature-wall/browser-storyboard/BrowserPricingStoryboard.tsx
|
||||
|
||||
@@ -142,7 +142,6 @@ const NODE_RUNTIME_INCLUDE = [
|
||||
'src/main/runtime/mobile-cloud-relay/control-channel/relay-control-client.test.ts',
|
||||
'src/main/ipc/filesystem/watchers/filesystem-watcher-ignore-real.test.ts',
|
||||
'src/main/ipc/runtime-environments/catalog/runtime-environment-preference.test.ts',
|
||||
'src/renderer/src/components/emulator-pane/emulator-device-frame-visibility.test.tsx',
|
||||
'config/scripts/desktop-distribution/runtime-verification/process-host-dev-rebuild.test.ts',
|
||||
'config/scripts/test-runtime/node-worker/vitest-node-runtime-pool.test.ts',
|
||||
// Reopen WAL-backed row caches read-only, which Bun on macOS cannot.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { emulatorOpeningMessage } from './emulator-opening-message'
|
||||
import { emulatorOpeningMessage } from './session/emulator-opening-message'
|
||||
import type { Tab } from '../../../../shared/tab-types'
|
||||
import { EmulatorPaneToolbar } from './emulator-pane-toolbar'
|
||||
import { EmulatorDeviceFrame } from './emulator-device-frame'
|
||||
import { MobileEmulatorAgentSetupGuideLayer } from './MobileEmulatorAgentSetupGuideLayer'
|
||||
import { useEmulatorPaneSession } from './use-emulator-pane-session'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
|
||||
import { useEmulatorInputCheck } from './use-emulator-input-check'
|
||||
import { EmulatorPaneToolbar } from './devices/emulator-pane-toolbar'
|
||||
import { EmulatorDeviceFrame } from './frame/emulator-device-frame'
|
||||
import { MobileEmulatorAgentSetupGuideLayer } from './agent-setup/MobileEmulatorAgentSetupGuideLayer'
|
||||
import { useEmulatorPaneSession } from './session/use-emulator-pane-session'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './streaming/emulator-scrcpy-stream-url'
|
||||
import { useEmulatorInputCheck } from './input/use-emulator-input-check'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { useUiLanguageRevision } from '@/i18n/use-ui-language-revision'
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { Button } from '../ui/button'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import { Button } from '../../ui/button'
|
||||
import { MobileEmulatorAgentSetupGuideSteps } from './MobileEmulatorAgentSetupGuideSteps'
|
||||
import type { useMobileEmulatorAgentSetupState } from './use-mobile-emulator-agent-setup-state'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useState, type ReactNode } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import { MobileEmulatorAgentSetupGuide } from './MobileEmulatorAgentSetupGuide'
|
||||
import { shouldShowMobileEmulatorAgentSetupGuide } from './mobile-emulator-agent-setup-visibility'
|
||||
import { useMobileEmulatorAgentSetupState } from './use-mobile-emulator-agent-setup-state'
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import { ORCA_CLI_SKILL_NAME } from '@/lib/skills/installation/agent-feature-skill-names'
|
||||
import { AgentSkillSetupPanel } from '../skills/setup/AgentSkillSetupPanel'
|
||||
import { AgentSkillSetupPanel } from '../../skills/setup/AgentSkillSetupPanel'
|
||||
import type { useMobileEmulatorAgentSetupState } from './use-mobile-emulator-agent-setup-state'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { simulatorRuntimeLabel } from '@/lib/simulator/launch/simulator-runtime-label'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
|
||||
export function groupEmulatorDevices(devices: SimulatorDeviceRow[], selected: string | null) {
|
||||
const used: SimulatorDeviceRow[] = []
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { getDefaultSettings } from '../../../../shared/constants'
|
||||
import { defineFixture } from '../../component-fixtures/component-fixture'
|
||||
import { getDefaultSettings } from '../../../../../shared/constants'
|
||||
import { defineFixture } from '../../../component-fixtures/component-fixture'
|
||||
import { EmulatorDevicePicker } from './emulator-device-picker'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
const devices: SimulatorDeviceRow[] = [
|
||||
{
|
||||
name: 'Pixel9_API36',
|
||||
+1
-1
@@ -11,7 +11,7 @@ import {
|
||||
CommandList
|
||||
} from '@/components/ui/command'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
import { emulatorDeviceDescription, groupEmulatorDevices } from './emulator-device-picker-groups'
|
||||
|
||||
type Props = {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
|
||||
export type RawEmulatorDevice = {
|
||||
id: string
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
|
||||
export function markSimulatorDeviceState(
|
||||
devices: SimulatorDeviceRow[],
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { EmulatorDevicePicker } from './emulator-device-picker'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { cn } from '@/lib/class-names'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type EmulatorPaneToolbarProps = {
|
||||
+2
-2
@@ -9,8 +9,8 @@ import {
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import { toSimulatorDeviceRows, type RawEmulatorDevice } from './emulator-device-row-mapping'
|
||||
import { markSimulatorDeviceBooted } from './emulator-device-state'
|
||||
import { emulatorListDevicesErrorText } from './emulator-pane-error-copy'
|
||||
import type { SimulatorDeviceRow } from './emulator-pane-types'
|
||||
import { emulatorListDevicesErrorText } from '../session/emulator-pane-error-copy'
|
||||
import type { SimulatorDeviceRow } from '../session/emulator-pane-types'
|
||||
|
||||
export function useEmulatorDeviceInventory(
|
||||
mountedRef: RefObject<boolean>,
|
||||
@@ -1,138 +0,0 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { EmulatorDeviceFrame } from './emulator-device-frame'
|
||||
import { resetStaleDocumentVisibilityForTesting } from '../terminal-pane/viewport/visibility/stale-document-visibility'
|
||||
import { EMULATOR_STREAM_PARK_DELAY_MS } from './use-emulator-stream-window-visibility'
|
||||
|
||||
// Assert source stream IPC: inactive panes must avoid frame delivery/blob churn/decoding while keeping the session attached.
|
||||
|
||||
type FrameListener = (data: { streamId: string; bytes: ArrayBuffer }) => void
|
||||
type ErrorListener = (data: { streamId: string; message: string }) => void
|
||||
|
||||
let container: HTMLDivElement
|
||||
let root: Root
|
||||
let startFrameStream: ReturnType<typeof vi.fn>
|
||||
let stopFrameStream: ReturnType<typeof vi.fn>
|
||||
let streamCounter: number
|
||||
|
||||
beforeEach(() => {
|
||||
;(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true
|
||||
streamCounter = 0
|
||||
startFrameStream = vi.fn(async () => ({ streamId: `stream-${++streamCounter}` }))
|
||||
stopFrameStream = vi.fn(async () => {})
|
||||
Object.defineProperty(URL, 'createObjectURL', {
|
||||
configurable: true,
|
||||
value: vi.fn(() => 'blob:emulator-frame')
|
||||
})
|
||||
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: vi.fn() })
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: {
|
||||
emulator: {
|
||||
startFrameStream,
|
||||
stopFrameStream,
|
||||
onFrameStreamFrame: (_listener: FrameListener) => () => {},
|
||||
onFrameStreamError: (_listener: ErrorListener) => () => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
root = createRoot(container)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
root.unmount()
|
||||
})
|
||||
container.remove()
|
||||
delete (URL as Partial<typeof URL>).createObjectURL
|
||||
delete (URL as Partial<typeof URL>).revokeObjectURL
|
||||
delete (window as { api?: unknown }).api
|
||||
setDocumentVisibility('visible')
|
||||
resetStaleDocumentVisibilityForTesting()
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
function setDocumentVisibility(state: 'visible' | 'hidden'): void {
|
||||
Object.defineProperty(document, 'visibilityState', {
|
||||
configurable: true,
|
||||
get: () => state
|
||||
})
|
||||
document.dispatchEvent(new Event('visibilitychange'))
|
||||
}
|
||||
|
||||
async function renderFrame(isActive: boolean): Promise<void> {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<EmulatorDeviceFrame
|
||||
previewUrl="http://127.0.0.1:3100/stream.mjpeg"
|
||||
wsUrl="ws://127.0.0.1:3100/ws"
|
||||
loading={false}
|
||||
isLive={true}
|
||||
visualOrientation="portrait"
|
||||
isActive={isActive}
|
||||
onTap={vi.fn()}
|
||||
onGesture={vi.fn()}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('EmulatorDeviceFrame visibility gating', () => {
|
||||
it('tears the stream down when the pane becomes inactive and resumes when active again', async () => {
|
||||
await renderFrame(true)
|
||||
expect(startFrameStream).toHaveBeenCalledTimes(1)
|
||||
|
||||
await renderFrame(false)
|
||||
expect(stopFrameStream).toHaveBeenCalledWith({ streamId: 'stream-1' })
|
||||
|
||||
await renderFrame(true)
|
||||
expect(startFrameStream).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('EmulatorDeviceFrame window-visibility gating', () => {
|
||||
it('parks the stream after the window stays hidden past the park delay, and resumes when visible', async () => {
|
||||
vi.useFakeTimers()
|
||||
await renderFrame(true)
|
||||
expect(startFrameStream).toHaveBeenCalledTimes(1)
|
||||
|
||||
// A short park grace prevents stream teardown on quick window switches.
|
||||
await act(async () => {
|
||||
setDocumentVisibility('hidden')
|
||||
})
|
||||
expect(stopFrameStream).not.toHaveBeenCalled()
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(EMULATOR_STREAM_PARK_DELAY_MS)
|
||||
})
|
||||
expect(stopFrameStream).toHaveBeenCalledWith({ streamId: 'stream-1' })
|
||||
|
||||
await act(async () => {
|
||||
setDocumentVisibility('visible')
|
||||
})
|
||||
expect(startFrameStream).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('resumes a parked stream when user input proves hidden visibility is stale', async () => {
|
||||
vi.useFakeTimers()
|
||||
await renderFrame(true)
|
||||
|
||||
await act(async () => setDocumentVisibility('hidden'))
|
||||
await act(async () => vi.advanceTimersByTime(EMULATOR_STREAM_PARK_DELAY_MS))
|
||||
expect(stopFrameStream).toHaveBeenCalledWith({ streamId: 'stream-1' })
|
||||
|
||||
await act(async () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }))
|
||||
})
|
||||
expect(document.visibilityState).toBe('hidden')
|
||||
expect(startFrameStream).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
+1
-17
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
fitDeviceFrameToPane,
|
||||
resolveVisualScreenAspectRatio,
|
||||
resolveVisualStreamGeometry
|
||||
} from './emulator-device-frame-layout'
|
||||
import { fitDeviceFrameToPane, resolveVisualStreamGeometry } from './emulator-device-frame-layout'
|
||||
|
||||
describe('resolveVisualStreamGeometry', () => {
|
||||
it.each([
|
||||
@@ -64,21 +60,9 @@ describe('fitDeviceFrameToPane', () => {
|
||||
const pane = { width: 260, height: 320 }
|
||||
const layout = fitDeviceFrameToPane(pane, 9 / 19, 'phone')
|
||||
|
||||
expect(layout).not.toBeNull()
|
||||
expect(layout?.width).toBeLessThanOrEqual(pane.width)
|
||||
expect(layout?.height).toBeLessThanOrEqual(pane.height)
|
||||
expect(layout?.shellWidth).toBeGreaterThan(1)
|
||||
expect(layout?.shellHeight).toBeGreaterThan(1)
|
||||
})
|
||||
|
||||
it('fits the entire phone frame as landscape after a device rotation', () => {
|
||||
const pane = { width: 1000, height: 600 }
|
||||
const aspectRatio = resolveVisualScreenAspectRatio({ width: 844, height: 390 }, 'landscape')
|
||||
const layout = fitDeviceFrameToPane(pane, aspectRatio, 'phone')
|
||||
|
||||
expect(layout).not.toBeNull()
|
||||
expect(layout?.width).toBeLessThanOrEqual(pane.width)
|
||||
expect(layout?.height).toBeLessThanOrEqual(pane.height)
|
||||
expect(layout ? layout.shellWidth / layout.shellHeight : 0).toBeGreaterThan(1)
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { z } from 'zod'
|
||||
import type { RotateOrientation } from '../../../../shared/rpc-contract/emulator/emulator-params'
|
||||
import type { RotateOrientation } from '../../../../../shared/rpc-contract/emulator/emulator-params'
|
||||
|
||||
export type DeviceFrameKind = 'phone' | 'tablet'
|
||||
|
||||
+1
-31
@@ -5,7 +5,7 @@ import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { EmulatorDeviceFrame } from './emulator-device-frame'
|
||||
import type { EmulatorGesturePoint } from './emulator-screen-gesture'
|
||||
import type { EmulatorGesturePoint } from '../input/emulator-screen-gesture'
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
@@ -231,39 +231,9 @@ describe('EmulatorDeviceFrame wheel scrolling', () => {
|
||||
|
||||
const touches = decodedSentMessages<EmulatorGesturePoint>(0x03)
|
||||
expect(touches.map(({ type }) => type)).toEqual(['begin', 'move', 'end', 'begin'])
|
||||
expect(touches[2]).toEqual({ ...touches[1], type: 'end' })
|
||||
expect(touches[3]).toEqual({ type: 'begin', x: 0.5, y: 0.7 })
|
||||
})
|
||||
|
||||
it.each(['disable', 'replace', 'reconnect'])(
|
||||
'cancels wheel playback on control-session %s',
|
||||
(mode) => {
|
||||
const onGesture = vi.fn()
|
||||
renderFrame({ onGesture })
|
||||
wheel(getScreen(), mode === 'reconnect' ? 10_000 : 100)
|
||||
act(() => vi.advanceTimersByTime(16))
|
||||
const before = decodedSentMessages<EmulatorGesturePoint>(0x03)
|
||||
expect(before.at(-1)?.type).toBe('move')
|
||||
if (mode === 'reconnect') {
|
||||
act(() => FakeWebSocket.instances[0]?.close())
|
||||
act(() => vi.advanceTimersByTime(750))
|
||||
} else {
|
||||
const wsUrl = mode === 'replace' ? 'ws://127.0.0.1:3101/ws' : undefined
|
||||
renderFrame({ onGesture, isLive: mode !== 'disable', wsUrl })
|
||||
if (mode === 'disable') {
|
||||
renderFrame({ onGesture })
|
||||
}
|
||||
}
|
||||
act(() => FakeWebSocket.instances.at(-1)?.open())
|
||||
finishWheel()
|
||||
act(() => root.render(<div />))
|
||||
expect(decodedSentMessages<EmulatorGesturePoint>(0x03).slice(before.length)).toEqual(
|
||||
mode === 'reconnect' ? [] : [{ ...before.at(-1), type: 'end' }]
|
||||
)
|
||||
expect(onGesture).not.toHaveBeenCalled()
|
||||
}
|
||||
)
|
||||
|
||||
it('cancels queued movement when the frame is removed', () => {
|
||||
wheel(getScreen(), 100)
|
||||
act(() => root.render(<div />))
|
||||
+7
-7
@@ -23,15 +23,15 @@ import {
|
||||
type EmulatorScreenPoint,
|
||||
type EmulatorGesturePoint,
|
||||
type PointerSample
|
||||
} from './emulator-screen-gesture'
|
||||
} from '../input/emulator-screen-gesture'
|
||||
import { PhoneHardwareButtons } from './emulator-phone-hardware-buttons'
|
||||
import { EmulatorScreenSurface } from './emulator-screen-surface'
|
||||
import { useEmulatorControlStream } from './use-emulator-control-stream'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
|
||||
import { EmulatorScreenSurface } from '../streaming/emulator-screen-surface'
|
||||
import { useEmulatorControlStream } from '../streaming/use-emulator-control-stream'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from '../streaming/emulator-scrcpy-stream-url'
|
||||
import { useEmulatorPaneSize } from './use-emulator-pane-size'
|
||||
import { useEmulatorScreenKeyboard } from './use-emulator-screen-keyboard'
|
||||
import { useEmulatorStreamWindowVisible } from './use-emulator-stream-window-visibility'
|
||||
import { useEmulatorWheelGesture } from './use-emulator-wheel-gesture'
|
||||
import { useEmulatorScreenKeyboard } from '../input/use-emulator-screen-keyboard'
|
||||
import { useWindowStreamVisible as useEmulatorStreamWindowVisible } from '../../../hooks/use-window-stream-visibility'
|
||||
import { useEmulatorWheelGesture } from '../input/use-emulator-wheel-gesture'
|
||||
|
||||
type EmulatorDeviceFrameProps = {
|
||||
deviceId?: string
|
||||
-1
@@ -56,6 +56,5 @@ describe('emulator keyboard paste', () => {
|
||||
reason: 'cancelled',
|
||||
status: 'cancelled'
|
||||
})
|
||||
expect(sendKeyboardFrames).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
buildServeSimKeyboardFramesForKey,
|
||||
type ServeSimKeyboardFrame
|
||||
} from '../../../../shared/emulator-keyboard-frame'
|
||||
import { measureUtf8ByteLength } from '../../../../shared/utf8-byte-limits'
|
||||
} from '../../../../../shared/emulator-keyboard-frame'
|
||||
import { measureUtf8ByteLength } from '../../../../../shared/utf8-byte-limits'
|
||||
|
||||
export const EMULATOR_KEYBOARD_PASTE_MAX_BYTES = 4 * 1024
|
||||
export const EMULATOR_KEYBOARD_PASTE_MAX_FRAMES_PER_CHUNK = 48
|
||||
+1
-23
@@ -1,9 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
mapClientPointToSimulatorScreen,
|
||||
resolveEmulatorPointerAction
|
||||
} from './emulator-screen-gesture'
|
||||
import { resolveVisualStreamGeometry } from './emulator-device-frame-layout'
|
||||
import { resolveEmulatorPointerAction } from './emulator-screen-gesture'
|
||||
|
||||
describe('emulator screen gestures', () => {
|
||||
it('inverse rotates fallback taps, drag phases and the home edge into raw panel space', () => {
|
||||
@@ -34,22 +30,4 @@ describe('emulator screen gestures', () => {
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('maps the full landscape screen while the stream canvas shape settles', () => {
|
||||
const landscapeRect = {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 400,
|
||||
height: 200
|
||||
}
|
||||
const stalePortraitStream = { width: 390, height: 844 }
|
||||
|
||||
expect(
|
||||
mapClientPointToSimulatorScreen(
|
||||
{ clientX: 380, clientY: 100 },
|
||||
landscapeRect,
|
||||
resolveVisualStreamGeometry(stalePortraitStream, 'landscape').size
|
||||
)
|
||||
).toEqual({ x: 0.95, y: 0.5 })
|
||||
})
|
||||
})
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { VisualStreamGeometry } from './emulator-device-frame-layout'
|
||||
import type { VisualStreamGeometry } from '../frame/emulator-device-frame-layout'
|
||||
|
||||
type StreamRotation = VisualStreamGeometry['streamRotation']
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import { emulatorErrorText } from '@/lib/simulator/launch/emulator-error-copy'
|
||||
import { hasRuntimeRpcErrorCode } from '../../../../shared/runtime-rpc-error-code'
|
||||
import { EMULATOR_FAILURE_COPY } from '../../../../shared/emulator-failure-copy'
|
||||
import { hasRuntimeRpcErrorCode } from '../../../../../shared/runtime-rpc-error-code'
|
||||
import { EMULATOR_FAILURE_COPY } from '../../../../../shared/emulator-failure-copy'
|
||||
|
||||
const INTERACTION_RECHECK_MS = 5_000
|
||||
|
||||
+2
-2
@@ -11,8 +11,8 @@ import { translate } from '@/i18n/i18n'
|
||||
import {
|
||||
buildServeSimKeyboardFramesForKey,
|
||||
type ServeSimKeyboardFrame
|
||||
} from '../../../../shared/emulator-keyboard-frame'
|
||||
import type { ScrcpyKeyboard } from './use-emulator-control-stream'
|
||||
} from '../../../../../shared/emulator-keyboard-frame'
|
||||
import type { ScrcpyKeyboard } from '../streaming/use-emulator-control-stream'
|
||||
import {
|
||||
pasteTextIntoAndroidEmulator,
|
||||
pasteTextIntoEmulatorKeyboard,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { EmulatorSelectionRequired } from '../../../../shared/emulator-opening-preferences'
|
||||
import type { EmulatorSelectionRequired } from '../../../../../shared/emulator-opening-preferences'
|
||||
import type { EmulatorPaneSession, SimulatorDeviceRow } from './emulator-pane-types'
|
||||
|
||||
type EmulatorAttachResult = {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import type { EmulatorSelectionRequired } from '../../../../shared/emulator-opening-preferences'
|
||||
import type { EmulatorSelectionRequired } from '../../../../../shared/emulator-opening-preferences'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import type { Translate } from '@/i18n/use-translate'
|
||||
export function emulatorOpeningMessage(
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import type { EmulatorDeviceVisualOrientation } from './emulator-device-frame-layout'
|
||||
import type { EmulatorGesturePoint } from './emulator-screen-gesture'
|
||||
import type { EmulatorDeviceVisualOrientation } from '../frame/emulator-device-frame-layout'
|
||||
import type { EmulatorGesturePoint } from '../input/emulator-screen-gesture'
|
||||
|
||||
export function useEmulatorPaneControls(worktreeId: string, onRotateSettled?: () => void) {
|
||||
const nextRotateOrientationRef = useRef<'landscape_left' | 'portrait'>('landscape_left')
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import type { EmulatorPaneSession } from './emulator-pane-types'
|
||||
import {
|
||||
EMULATOR_MANUAL_LAUNCH_FAILED_EVENT,
|
||||
+1
-40
@@ -5,7 +5,7 @@ import { setLocalRuntimeCapabilitiesForTests } from '@/runtime/hosts/capabilitie
|
||||
import { act } from 'react'
|
||||
import { createRoot, type Root } from 'react-dom/client'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import {
|
||||
consumePrelaunchedSimulatorSession,
|
||||
rememberPrelaunchedSimulatorSession
|
||||
@@ -72,15 +72,6 @@ function runtimeSuccess<T>(result: T) {
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeFailure(code: string, message: string) {
|
||||
return {
|
||||
id: 'test',
|
||||
ok: false,
|
||||
error: { code, message },
|
||||
_meta: { runtimeId: 'test-runtime' }
|
||||
}
|
||||
}
|
||||
|
||||
function Probe(): React.JSX.Element {
|
||||
const state = useEmulatorPaneSession({
|
||||
worktreeId: WORKTREE_ID,
|
||||
@@ -213,7 +204,6 @@ describe('useEmulatorPaneSession', () => {
|
||||
await flushEffects()
|
||||
expect(latest?.isLive).toBe(true)
|
||||
expect(latest?.previewUrl).toBe('http://127.0.0.1:3200/stream.mjpeg')
|
||||
expect(latest?.displayName).toBe('iPhone B')
|
||||
})
|
||||
|
||||
it('ignores a rotate response from the previous session after switching devices', async () => {
|
||||
@@ -257,33 +247,4 @@ describe('useEmulatorPaneSession', () => {
|
||||
|
||||
expect(latest?.visualOrientation).toBe('portrait')
|
||||
})
|
||||
|
||||
it('keeps simulator discovery setup errors during auto attach', async () => {
|
||||
const message =
|
||||
'Xcode Simulator tools are unavailable. Install full Xcode, open it once, then select it with `sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer`.'
|
||||
consumePrelaunchedSimulatorSession(WORKTREE_ID)
|
||||
const runtimeCall = vi.fn(async ({ method }: RuntimeCallRequest) => {
|
||||
if (method === 'emulator.listDevices') {
|
||||
return runtimeFailure('emulator_simctl_unavailable', message)
|
||||
}
|
||||
if (method === 'emulator.attach') {
|
||||
throw new Error('attach should not run without a discovered target')
|
||||
}
|
||||
throw new Error(`Unexpected RPC method: ${method}`)
|
||||
})
|
||||
Object.defineProperty(window, 'api', {
|
||||
configurable: true,
|
||||
value: { runtime: { call: runtimeCall } }
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
root.render(<AutoAttachProbe />)
|
||||
})
|
||||
|
||||
await vi.waitFor(() => expect(latest?.error).toBe(message))
|
||||
expect(latest?.loading).toBe(false)
|
||||
expect(runtimeCall).not.toHaveBeenCalledWith(
|
||||
expect.objectContaining({ method: 'emulator.attach' })
|
||||
)
|
||||
})
|
||||
})
|
||||
+4
-4
@@ -1,19 +1,19 @@
|
||||
import { useEmulatorDeviceInventory } from './use-emulator-device-inventory'
|
||||
import { useEmulatorDeviceInventory } from '../devices/use-emulator-device-inventory'
|
||||
import {
|
||||
getEmulatorOpeningMode,
|
||||
type EmulatorSelectionRequired
|
||||
} from '../../../../shared/emulator-opening-preferences'
|
||||
} from '../../../../../shared/emulator-opening-preferences'
|
||||
import { supportsEmulatorOpeningPreferences } from '@/lib/simulator/launch/emulator-opening-preferences-capability'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import {
|
||||
deviceLabel,
|
||||
simulatorPreviewStreamUrl,
|
||||
type EmulatorPaneSession,
|
||||
type UseEmulatorPaneSessionArgs
|
||||
} from './emulator-pane-types'
|
||||
import { markSimulatorDeviceShutdown } from './emulator-device-state'
|
||||
import { markSimulatorDeviceShutdown } from '../devices/emulator-device-state'
|
||||
import { useEmulatorPaneControls } from './use-emulator-pane-controls'
|
||||
import { useEmulatorPaneSessionEvents } from './use-emulator-pane-session-events'
|
||||
import {
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useCallback, type Dispatch, type RefObject, type SetStateAction } from 'react'
|
||||
import { callRuntimeRpc } from '@/runtime/rpc/calls/runtime-rpc-call'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import { EMULATOR_LOCAL_SHUTDOWN_EVENT } from './use-emulator-pane-session-events'
|
||||
import { emulatorShutdownErrorText } from './emulator-pane-error-copy'
|
||||
|
||||
-17
@@ -24,7 +24,6 @@ class FakeVideoDecoder {
|
||||
decodeQueueSize = 0
|
||||
configs: VideoDecoderConfig[] = []
|
||||
chunks: { type: string }[] = []
|
||||
resets = 0
|
||||
constructor(
|
||||
private readonly init: {
|
||||
output: (frame: { displayWidth: number; displayHeight: number; close: () => void }) => void
|
||||
@@ -42,7 +41,6 @@ class FakeVideoDecoder {
|
||||
}
|
||||
reset(): void {
|
||||
this.state = 'unconfigured'
|
||||
this.resets += 1
|
||||
}
|
||||
close(): void {
|
||||
this.state = 'closed'
|
||||
@@ -235,7 +233,6 @@ describe('EmulatorScreenStreamContent', () => {
|
||||
await renderStream('b')
|
||||
|
||||
expect(container.querySelector('img')).toBeNull()
|
||||
expect(container.textContent).toContain('Connecting emulator…')
|
||||
expect(stopFrameStream).toHaveBeenCalledWith({ streamId: 'stream-1' })
|
||||
|
||||
await act(async () => {
|
||||
@@ -248,11 +245,6 @@ describe('EmulatorScreenStreamContent', () => {
|
||||
it('streams an iOS simulator as H.264 from its avcC description', async () => {
|
||||
await renderIosStream()
|
||||
|
||||
expect(startVideoStream).toHaveBeenCalledWith({
|
||||
deviceId: IOS_UDID,
|
||||
streamId: videoStreamId(0)
|
||||
})
|
||||
expect(startFrameStream).not.toHaveBeenCalled()
|
||||
await sendVideo(videoStreamId(0), 'config')
|
||||
await sendVideo(videoStreamId(0), 'delta')
|
||||
await sendVideo(videoStreamId(0), 'key')
|
||||
@@ -260,7 +252,6 @@ describe('EmulatorScreenStreamContent', () => {
|
||||
const [decoder] = FakeVideoDecoder.instances
|
||||
expect(decoder.configs[0]).toMatchObject({ codec: 'avc1.640033' })
|
||||
expect(decoder.chunks.map((chunk) => chunk.type)).toEqual(['key'])
|
||||
expect(container.querySelector('canvas')).not.toBeNull()
|
||||
})
|
||||
|
||||
it('falls back to the iOS JPEG stream without reporting a disconnect when H.264 fails', async () => {
|
||||
@@ -272,12 +263,10 @@ describe('EmulatorScreenStreamContent', () => {
|
||||
expect(startFrameStream).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ streamUrl: IOS_PREVIEW_URL })
|
||||
)
|
||||
expect(container.textContent).toContain('Connecting emulator…')
|
||||
await act(async () => {
|
||||
frameListeners[0]?.({ streamId: 'stream-1', bytes: new Uint8Array([1, 2, 3]).buffer })
|
||||
})
|
||||
expect(container.querySelector('img')?.getAttribute('src')).toBe('blob:emulator-frame-1')
|
||||
expect(startVideoStream).toHaveBeenCalledOnce()
|
||||
expect(onStreamError).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -290,15 +279,9 @@ describe('EmulatorScreenStreamContent', () => {
|
||||
|
||||
await sendVideo(videoStreamId(0), 'delta')
|
||||
|
||||
expect(stopVideoStream).toHaveBeenCalledWith({ streamId: videoStreamId(0) })
|
||||
expect(startVideoStream).toHaveBeenCalledTimes(2)
|
||||
expect(decoder.resets).toBe(1)
|
||||
expect(decoder.chunks).toHaveLength(1)
|
||||
|
||||
decoder.decodeQueueSize = 0
|
||||
await sendVideo(videoStreamId(0), 'key')
|
||||
await sendVideo(videoStreamId(1), 'delta')
|
||||
expect(decoder.chunks).toHaveLength(1)
|
||||
await sendVideo(videoStreamId(1), 'config')
|
||||
await sendVideo(videoStreamId(1), 'key')
|
||||
expect(decoder.chunks.map((chunk) => chunk.type)).toEqual(['key', 'key'])
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { useEmulatorFrameStream } from './use-emulator-frame-stream'
|
||||
import { useEmulatorVideoStream } from './use-emulator-video-stream'
|
||||
import { scrcpyDeviceIdFromStreamUrl } from './emulator-scrcpy-stream-url'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import type { VisualStreamGeometry } from './emulator-device-frame-layout'
|
||||
import type { VisualStreamGeometry } from '../frame/emulator-device-frame-layout'
|
||||
|
||||
type StreamSize = {
|
||||
height: number
|
||||
+1
-1
@@ -12,7 +12,7 @@ import type {
|
||||
DeviceFrameLayout,
|
||||
StreamSize,
|
||||
VisualStreamGeometry
|
||||
} from './emulator-device-frame-layout'
|
||||
} from '../frame/emulator-device-frame-layout'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
type EmulatorScreenSurfaceProps = {
|
||||
+4
-4
@@ -2,13 +2,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
encodeServeSimKeyboardFrame,
|
||||
type ServeSimKeyboardFrame
|
||||
} from '../../../../shared/emulator-keyboard-frame'
|
||||
} from '../../../../../shared/emulator-keyboard-frame'
|
||||
import {
|
||||
encodeServeSimTouchFrame,
|
||||
type ServeSimTouchFrame
|
||||
} from '../../../../shared/emulator-touch-frame'
|
||||
import { RotateOrientation } from '../../../../shared/rpc-contract/emulator/emulator-params'
|
||||
import type { EmulatorStreamOrientation } from './emulator-device-frame-layout'
|
||||
} from '../../../../../shared/emulator-touch-frame'
|
||||
import { RotateOrientation } from '../../../../../shared/rpc-contract/emulator/emulator-params'
|
||||
import type { EmulatorStreamOrientation } from '../frame/emulator-device-frame-layout'
|
||||
|
||||
const RECONNECT_DELAY_MS = 750
|
||||
const KEYBOARD_FRAME_DELAY_MS = 4
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { createNonSecureContextUuid as createBrowserUuid } from '../../../../shared/non-secure-context-uuid'
|
||||
import { createNonSecureContextUuid as createBrowserUuid } from '../../../../../shared/non-secure-context-uuid'
|
||||
|
||||
type VideoFrameMessage = {
|
||||
streamId: string
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { useAppStore } from '@/store'
|
||||
import { useAppStore } from '@/store/index'
|
||||
import { showMobileEmulatorHiddenToast } from './mobile-emulator-hidden-toast'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export {
|
||||
WINDOW_STREAM_PARK_DELAY_MS as EMULATOR_STREAM_PARK_DELAY_MS,
|
||||
useWindowStreamVisible as useEmulatorStreamWindowVisible
|
||||
} from '@/hooks/use-window-stream-visibility'
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { Import } from 'lucide-react'
|
||||
import { ORCA_CLI_SKILL_NAME } from '@/lib/skills/installation/agent-feature-skill-names'
|
||||
import { useMobileEmulatorAgentSetupState } from '../../../emulator-pane/use-mobile-emulator-agent-setup-state'
|
||||
import { useMobileEmulatorAgentSetupState } from '../../../emulator-pane/agent-setup/use-mobile-emulator-agent-setup-state'
|
||||
import { AgentSkillSetupPanel } from '../../../skills/setup/AgentSkillSetupPanel'
|
||||
import { MobileEmulatorExamples } from './MobileEmulatorExamples'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import { FilePlus, FileText, Globe, Smartphone, TerminalSquare } from 'lucide-react'
|
||||
import { useTranslate } from '@/i18n/use-translate'
|
||||
import { DropdownMenuItem, DropdownMenuShortcut } from '@/components/ui/dropdown-menu'
|
||||
import { MobileEmulatorTabIntroCallout } from '../emulator-pane/MobileEmulatorTabIntroCallout'
|
||||
import { MobileEmulatorTabIntroCallout } from '../emulator-pane/tab-intro/MobileEmulatorTabIntroCallout'
|
||||
import { ShellIcon } from './shell-icons'
|
||||
import { useAppStore } from '@/store'
|
||||
import { usePluginNewFiles } from '@/store/plugins/file-creation/plugin-new-files'
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
useWindowsTerminalCapabilities
|
||||
} from '@/runtime/hosts/capabilities/windows-terminal-capabilities'
|
||||
import type { WindowsTerminalCapabilities } from '@/runtime/hosts/capabilities/windows-terminal-capabilities'
|
||||
import { shouldShowMobileEmulatorTabIntro } from '../emulator-pane/mobile-emulator-tab-intro-visibility'
|
||||
import { shouldShowMobileEmulatorTabIntro } from '../emulator-pane/tab-intro/mobile-emulator-tab-intro-visibility'
|
||||
import { selectTabBarAgentProjections } from './tab-agent-types-by-tab-id'
|
||||
import type { TabBarAgentProjections } from './tab-agent-types-by-tab-id'
|
||||
import { shouldShowWindowsShellMenu } from './windows-shell-menu-visibility'
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from '../tabs/simulator-pane-shutdown-scheduler'
|
||||
import { assertClientCreationActionAvailable } from '../../workspace/creation/surface-policy/client-creation-action-policy'
|
||||
import { LOCAL_EXECUTION_HOST_ID } from '../../../../../shared/execution-host'
|
||||
import type { EmulatorStreamInfo } from '@/components/emulator-pane/emulator-pane-types'
|
||||
import type { EmulatorStreamInfo } from '@/components/emulator-pane/session/emulator-pane-types'
|
||||
|
||||
type OpenMobileEmulatorTabOptions = {
|
||||
targetGroupId?: string
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { EmulatorStreamInfo } from '@/components/emulator-pane/emulator-pane-types'
|
||||
import type { EmulatorStreamInfo } from '@/components/emulator-pane/session/emulator-pane-types'
|
||||
|
||||
export const EMULATOR_MANUAL_LAUNCH_STARTED_EVENT = 'orca:emulator-launch-started'
|
||||
export const EMULATOR_MANUAL_LAUNCH_FAILED_EVENT = 'orca:emulator-launch-failed'
|
||||
|
||||
Reference in New Issue
Block a user