peachsim 0.1.1640 → 0.1.1644
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/cli/app-bundle-resources.ts +57 -0
- package/cli/app-stubs.ts +38 -13
- package/cli/cloud-session.ts +16 -0
- package/cli/commands/assert.ts +17 -4
- package/cli/commands/box.ts +33 -19
- package/cli/commands/control.ts +12 -0
- package/cli/commands/doctor.ts +24 -15
- package/cli/commands/inspect/actions.ts +3 -2
- package/cli/commands/inspect/core.ts +0 -8
- package/cli/commands/inspect.ts +0 -14
- package/cli/commands/platform.ts +2 -1
- package/cli/commands/record.ts +64 -28
- package/cli/commands/swift.ts +10 -2
- package/cli/commands/three-mode.ts +4 -38
- package/cli/commands/upload.ts +8 -8
- package/cli/drivers/playwright.ts +39 -0
- package/cli/flow-live-status.ts +0 -2
- package/cli/main.ts +1 -1
- package/cli/outbound-endpoints.ts +4 -2
- package/cli/ws-bridge.ts +0 -6
- package/detox/attempt-events.cjs +41 -5
- package/detox/index.ts +155 -109
- package/detox/jest-environment.cjs +5 -1
- package/detox/motion-change.cjs +7 -5
- package/detox/motion-change.d.cts +1 -0
- package/detox/motion-leg-starts.cjs +20 -0
- package/detox/motion-leg-starts.d.cts +8 -0
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +12 -3
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/bundle-channel.cjs +2671 -1833
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/config.mjs +1 -1
- package/dist-lib/detox/index.cjs +578 -130
- package/dist-lib/detox/jest-environment.cjs +5 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/expo-swift-bundle.cjs +2374 -1554
- package/dist-lib/expo-swift-metadata.cjs +1 -1
- package/dist-lib/expo-swift-metadata.mjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +2968 -2141
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +132 -57
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/kotlin-host.cjs +1 -1
- package/dist-lib/menu.cjs +14 -6
- package/dist-lib/menu.mjs +14 -6
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +120 -53
- package/dist-lib/native-app-identity-project.cjs +96 -44
- package/dist-lib/native-app-identity-project.mjs +95 -44
- package/dist-lib/native-app-identity.cjs +1 -1
- package/dist-lib/native-app-identity.mjs +1 -1
- package/dist-lib/native-firebase-metadata.cjs +1 -1
- package/dist-lib/native-firebase-metadata.mjs +1 -1
- package/dist-lib/plist.cjs +1 -1
- package/dist-lib/plist.mjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +5 -3
- package/dist-lib/sdk.mjs +5 -3
- package/dist-lib/skills.cjs +3932 -2868
- package/dist-lib/swift-compile.cjs +587 -374
- package/dist-lib/swift-compile.mjs +595 -369
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +8 -3
- package/dist-lib/swift-submit.mjs +8 -3
- package/dist-lib/swift-toolchain.cjs +1 -1
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +2263 -1467
- package/dist-lib/vite.cjs +1 -1
- package/dist-lib/worklet-executable.cjs +1 -1
- package/dist-lib/worklet-executable.mjs +1 -1
- package/package.json +1 -1
- package/src/appBundleResourcesContract.ts +51 -0
- package/src/backend-origin.ts +11 -1
- package/src/host/bridge-host.ts +17 -11
- package/src/host/staged-host-files.ts +28 -4
- package/src/launchMetadataContract.ts +1 -0
- package/src/menu.ts +12 -4
- package/src/native-app-identity-project.ts +47 -38
- package/src/react-native-bridge-modules.ts +89 -76
- package/src/runtime-delivery.ts +6 -7
- package/src/swift-compile.ts +150 -112
- package/src/swift-compiler-digest.ts +5 -3
- package/src/swift-distribution.lock.json +9 -8
- package/src/swift-host.ts +6 -4
- package/src/swift-objc-compile.ts +416 -194
- package/src/swift-objc-metadata.ts +204 -0
- package/src/swift-objc-sdk.ts +195 -2
- package/src/swift-package-manifest.ts +6 -3
- package/src/swift-root-lock.ts +302 -0
- package/src/swift-submit.ts +15 -2
- package/src/swiftContract.ts +12 -0
- package/src/xcode-project.ts +77 -7
package/detox/index.ts
CHANGED
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
type ChromiumLaunchAdmission,
|
|
15
15
|
} from '../../../scripts/lib/reap-browser'
|
|
16
16
|
import { automateWebAuthenticationSessions } from '../../peach-engine/src/web-authentication-automation-host'
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
LockstepInputReceipt,
|
|
19
|
+
LockstepScheduledInput,
|
|
20
|
+
SootSimLockstepFrameReceipt,
|
|
21
|
+
} from '@peach/globals'
|
|
18
22
|
export { reserveChromiumAdmission } from '../../../scripts/lib/reap-browser'
|
|
19
23
|
import {
|
|
20
24
|
readBrowserCompositeCaptureRequest,
|
|
@@ -72,6 +76,8 @@ import type { DiscreteProofContext } from './attempt-events.cjs'
|
|
|
72
76
|
import type { SootSimElement } from './element-types'
|
|
73
77
|
import type { Browser, BrowserContext, Page } from 'playwright-core'
|
|
74
78
|
|
|
79
|
+
const WORKLET_FRAME_TRACE_LIMIT = 16384
|
|
80
|
+
|
|
75
81
|
type DiscreteStageFrame = {
|
|
76
82
|
label: string
|
|
77
83
|
offsetMs: number
|
|
@@ -492,8 +498,12 @@ async function waitForSootsimWorkletsIdle(page: Page, timeout = 3000): Promise<v
|
|
|
492
498
|
)
|
|
493
499
|
if (!result?.settled) {
|
|
494
500
|
const pending = result?.pending ?? 'no peer stats reply'
|
|
495
|
-
throw
|
|
496
|
-
|
|
501
|
+
throw await describeSootsimBridgeFailure(
|
|
502
|
+
page,
|
|
503
|
+
new Error(
|
|
504
|
+
`sootsim worklet runtime did not settle after ${result?.elapsed ?? timeout}ms (pending=${pending}; observation=${JSON.stringify(result?.observation)})`,
|
|
505
|
+
),
|
|
506
|
+
'sootsim worklet runtime did not settle',
|
|
497
507
|
)
|
|
498
508
|
}
|
|
499
509
|
}
|
|
@@ -2119,7 +2129,7 @@ export const device = {
|
|
|
2119
2129
|
},
|
|
2120
2130
|
|
|
2121
2131
|
async uninstallApp() {
|
|
2122
|
-
|
|
2132
|
+
await closeBrowser(false)
|
|
2123
2133
|
},
|
|
2124
2134
|
|
|
2125
2135
|
async openURL(url: { url: string; sourceApp?: string }) {
|
|
@@ -2639,15 +2649,18 @@ export const device = {
|
|
|
2639
2649
|
options: {
|
|
2640
2650
|
detectionTimeoutMs?: number
|
|
2641
2651
|
workletFrameTimestampsSeconds?: number[]
|
|
2652
|
+
workletFrameTracePath?: string
|
|
2642
2653
|
frame?: { height: number; width: number; x: number; y: number } | null
|
|
2643
2654
|
// a pointer the capture itself presses at the trigger point and releases
|
|
2644
2655
|
// once durationMs of lockstep time has elapsed, so held states (a lifted
|
|
2645
2656
|
// control thumb, a pressed row) are painted mid-hold. moves drag the
|
|
2646
2657
|
// held pointer to each point once afterMs of lockstep time has elapsed
|
|
2647
|
-
// since the press
|
|
2658
|
+
// since the press. an explicit release position preserves witnessed up coordinates.
|
|
2648
2659
|
hold?: {
|
|
2649
2660
|
durationMs: number
|
|
2650
|
-
|
|
2661
|
+
eventDurationMs?: number
|
|
2662
|
+
release?: { x: number; y: number }
|
|
2663
|
+
moves?: Array<{ afterMs: number; eventAfterMs?: number; x: number; y: number }>
|
|
2651
2664
|
x: number
|
|
2652
2665
|
y: number
|
|
2653
2666
|
} | null
|
|
@@ -2703,6 +2716,14 @@ export const device = {
|
|
|
2703
2716
|
definition: MOTION_CHANGE_DEFINITION,
|
|
2704
2717
|
installGlobal: true,
|
|
2705
2718
|
})
|
|
2719
|
+
if (options.workletFrameTracePath) {
|
|
2720
|
+
await page.evaluate(async (limit) => {
|
|
2721
|
+
const enable = window.SootSim?.bridges?.test?.setShellReanimatedTraceEnabled
|
|
2722
|
+
if (typeof enable !== 'function')
|
|
2723
|
+
throw new Error('worklet trace bridge is absent')
|
|
2724
|
+
await enable(true, true, limit)
|
|
2725
|
+
}, WORKLET_FRAME_TRACE_LIMIT)
|
|
2726
|
+
}
|
|
2706
2727
|
const capturePromise: Promise<{
|
|
2707
2728
|
inputReceipts: LockstepInputReceipt[]
|
|
2708
2729
|
anchors: Array<{
|
|
@@ -2877,61 +2898,9 @@ export const device = {
|
|
|
2877
2898
|
const controller = await lockstep.begin(60, {
|
|
2878
2899
|
workletFrameTimestampsSeconds: input.workletFrameTimestampsSeconds,
|
|
2879
2900
|
})
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
// deadlines are frame indexes: comparing summed virtual timestamps
|
|
2884
|
-
// against a duration lets rounding push an event past its frame.
|
|
2885
|
-
const moves = input.hold?.moves ?? []
|
|
2886
|
-
const holdState = {
|
|
2887
|
-
nextMove: 0,
|
|
2888
|
-
pressFrameIndex: null as number | null,
|
|
2889
|
-
releaseReceipt: null as SootSimLockstepFrameReceipt | null,
|
|
2890
|
-
released: false,
|
|
2891
|
-
x: input.hold?.x ?? 0,
|
|
2892
|
-
y: input.hold?.y ?? 0,
|
|
2893
|
-
}
|
|
2894
|
-
const holdEventFrameIndex = (afterMs: number) =>
|
|
2895
|
-
(holdState.pressFrameIndex ?? 0) + controller.framesForOffset(afterMs)
|
|
2896
|
-
const nextHoldEventFrameIndex = () => {
|
|
2897
|
-
if (!input.hold || holdState.released || holdState.pressFrameIndex === null) {
|
|
2898
|
-
return null
|
|
2899
|
-
}
|
|
2900
|
-
const move = moves[holdState.nextMove]
|
|
2901
|
-
return holdEventFrameIndex(move ? move.afterMs : input.hold.durationMs)
|
|
2902
|
-
}
|
|
2903
|
-
const advanceHold = async (receipt: SootSimLockstepFrameReceipt) => {
|
|
2904
|
-
if (!input.hold || !interact || holdState.released) return
|
|
2905
|
-
if (holdState.pressFrameIndex === null) {
|
|
2906
|
-
holdState.pressFrameIndex = receipt.frameIndex - 1
|
|
2907
|
-
}
|
|
2908
|
-
for (
|
|
2909
|
-
let move = moves[holdState.nextMove];
|
|
2910
|
-
move && receipt.frameIndex >= holdEventFrameIndex(move.afterMs);
|
|
2911
|
-
move = moves[holdState.nextMove]
|
|
2912
|
-
) {
|
|
2913
|
-
holdState.nextMove++
|
|
2914
|
-
holdState.x = move.x
|
|
2915
|
-
holdState.y = move.y
|
|
2916
|
-
// event time remains on the replay clock when delivery ceilings
|
|
2917
|
-
// to a display tick or several native samples share one frame.
|
|
2918
|
-
await interact.touchMove(
|
|
2919
|
-
move.x,
|
|
2920
|
-
move.y,
|
|
2921
|
-
999,
|
|
2922
|
-
controller.initialFrame.virtualTimestampMs + move.afterMs,
|
|
2923
|
-
)
|
|
2924
|
-
}
|
|
2925
|
-
if (receipt.frameIndex < holdEventFrameIndex(input.hold.durationMs)) return
|
|
2926
|
-
holdState.released = true
|
|
2927
|
-
holdState.releaseReceipt = receipt
|
|
2928
|
-
await interact.touchUp(
|
|
2929
|
-
holdState.x,
|
|
2930
|
-
holdState.y,
|
|
2931
|
-
999,
|
|
2932
|
-
controller.initialFrame.virtualTimestampMs + input.hold.durationMs,
|
|
2933
|
-
)
|
|
2934
|
-
}
|
|
2901
|
+
let heldInput: Promise<void> | null = null
|
|
2902
|
+
const releaseReceipt = () =>
|
|
2903
|
+
controller.inputReceipts.find((receipt) => receipt.phase === 'up')
|
|
2935
2904
|
// an anchor is its visible-change frame; its bitmap is kept so a 0ms
|
|
2936
2905
|
// stage can still capture the anchor frame itself.
|
|
2937
2906
|
const keptFrames = new Map<
|
|
@@ -2960,17 +2929,51 @@ export const device = {
|
|
|
2960
2929
|
throw new Error('paired worklets callback preceded the Peach trigger')
|
|
2961
2930
|
state.ready = true
|
|
2962
2931
|
if (input.hold && interact) {
|
|
2963
|
-
const
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2932
|
+
const hold = input.hold
|
|
2933
|
+
const moves = hold.moves ?? []
|
|
2934
|
+
const release = hold.release ?? moves.at(-1) ?? hold
|
|
2935
|
+
const events: LockstepScheduledInput[] = [
|
|
2936
|
+
{
|
|
2937
|
+
phase: 'down',
|
|
2938
|
+
x: hold.x,
|
|
2939
|
+
y: hold.y,
|
|
2940
|
+
pointerId: 999,
|
|
2941
|
+
afterMs: 0,
|
|
2942
|
+
async deliver(timestamp) {
|
|
2943
|
+
const hit = await interact.touchDown(hold.x, hold.y, 999, timestamp)
|
|
2944
|
+
if (!hit)
|
|
2945
|
+
throw new Error(`peach motion hold missed at ${hold.x},${hold.y}`)
|
|
2946
|
+
},
|
|
2947
|
+
},
|
|
2948
|
+
...moves.map(
|
|
2949
|
+
(move): LockstepScheduledInput => ({
|
|
2950
|
+
phase: 'move',
|
|
2951
|
+
x: move.x,
|
|
2952
|
+
y: move.y,
|
|
2953
|
+
pointerId: 999,
|
|
2954
|
+
afterMs: move.afterMs,
|
|
2955
|
+
eventAfterMs: move.eventAfterMs,
|
|
2956
|
+
async deliver(timestamp: number) {
|
|
2957
|
+
await interact.touchMove(move.x, move.y, 999, timestamp)
|
|
2958
|
+
},
|
|
2959
|
+
}),
|
|
2960
|
+
),
|
|
2961
|
+
{
|
|
2962
|
+
phase: 'up',
|
|
2963
|
+
x: release.x,
|
|
2964
|
+
y: release.y,
|
|
2965
|
+
pointerId: 999,
|
|
2966
|
+
afterMs: hold.durationMs,
|
|
2967
|
+
eventAfterMs: hold.eventDurationMs,
|
|
2968
|
+
async deliver(timestamp) {
|
|
2969
|
+
await interact.touchUp(release.x, release.y, 999, timestamp)
|
|
2970
|
+
},
|
|
2971
|
+
},
|
|
2972
|
+
]
|
|
2973
|
+
heldInput = controller.scheduleInput(events)
|
|
2974
|
+
// rendering propagates delivery errors; also observe cancellation rejection.
|
|
2975
|
+
heldInput.catch(() => {})
|
|
2976
|
+
await controller.flushFrame()
|
|
2974
2977
|
} else {
|
|
2975
2978
|
while (!state.triggerStarted) {
|
|
2976
2979
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
@@ -3008,12 +3011,12 @@ export const device = {
|
|
|
3008
3011
|
for (let frame = 0; !visibleStart && frame < detectionFrameLimit; frame++) {
|
|
3009
3012
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
3010
3013
|
const receipt = await controller.stepFrame()
|
|
3011
|
-
await advanceHold(receipt)
|
|
3012
3014
|
const candidateBitmap = await captureBitmap()
|
|
3013
3015
|
// a touch anchor's stages may precede the first visible change
|
|
3014
3016
|
if (
|
|
3015
3017
|
input.anchorAtTouch === 'down' ||
|
|
3016
|
-
(input.anchorAtTouch === 'up' &&
|
|
3018
|
+
(input.anchorAtTouch === 'up' &&
|
|
3019
|
+
releaseReceipt()?.frameIndex === receipt.frameIndex)
|
|
3017
3020
|
) {
|
|
3018
3021
|
keepFrame(receipt, await createImageBitmap(candidateBitmap))
|
|
3019
3022
|
}
|
|
@@ -3053,17 +3056,17 @@ export const device = {
|
|
|
3053
3056
|
let currentReceipt = visibleStart
|
|
3054
3057
|
if (input.anchorAtTouch === 'down') driveStart = controller.initialFrame
|
|
3055
3058
|
if (input.anchorAtTouch === 'up') {
|
|
3056
|
-
while (!
|
|
3059
|
+
while (!releaseReceipt()) {
|
|
3057
3060
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
3058
3061
|
const receipt = await controller.stepFrame()
|
|
3059
|
-
await advanceHold(receipt)
|
|
3060
3062
|
currentReceipt = receipt
|
|
3061
|
-
if (
|
|
3063
|
+
if (releaseReceipt()) keepFrame(receipt, await captureBitmap())
|
|
3062
3064
|
}
|
|
3063
|
-
|
|
3065
|
+
const released = releaseReceipt()
|
|
3066
|
+
if (!released) {
|
|
3064
3067
|
throw new Error('peach release anchoring found no release frame')
|
|
3065
3068
|
}
|
|
3066
|
-
driveStart =
|
|
3069
|
+
driveStart = { ...released, tenantFrame: null }
|
|
3067
3070
|
}
|
|
3068
3071
|
const resolvedAnchors = new Map<string, SootSimLockstepFrameReceipt>()
|
|
3069
3072
|
const anchorRecords: Array<{
|
|
@@ -3107,7 +3110,6 @@ export const device = {
|
|
|
3107
3110
|
while (searchReceipt.virtualTimestampMs < searchStartVts) {
|
|
3108
3111
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
3109
3112
|
searchReceipt = await controller.stepFrame()
|
|
3110
|
-
await advanceHold(searchReceipt)
|
|
3111
3113
|
}
|
|
3112
3114
|
currentReceipt = searchReceipt
|
|
3113
3115
|
let anchorBitmap = await captureBitmap()
|
|
@@ -3123,7 +3125,6 @@ export const device = {
|
|
|
3123
3125
|
for (let frame = 0; frame < detectionFrameLimit; frame++) {
|
|
3124
3126
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
3125
3127
|
const receipt = await controller.stepFrame()
|
|
3126
|
-
await advanceHold(receipt)
|
|
3127
3128
|
const candidateBitmap = await captureBitmap()
|
|
3128
3129
|
const bounds = boundsFor(candidateBitmap)
|
|
3129
3130
|
const candidate = regionPixels(candidateBitmap)
|
|
@@ -3176,9 +3177,6 @@ export const device = {
|
|
|
3176
3177
|
for (const stage of input.stages) {
|
|
3177
3178
|
if (state.cancelled) throw new Error('peach motion capture was cancelled')
|
|
3178
3179
|
const { anchorId, receipt: anchorReceipt } = await resolveAnchor(stage)
|
|
3179
|
-
// step through every hold move and the release that fall before
|
|
3180
|
-
// this stage one frame at a time, so the stage paints the pointer
|
|
3181
|
-
// where it belongs rather than a hold the jump skipped past.
|
|
3182
3180
|
const stageFrameIndex =
|
|
3183
3181
|
anchorReceipt.frameIndex + controller.framesForOffset(stage.offsetMs)
|
|
3184
3182
|
const kept = keptFrames.get(stageFrameIndex)
|
|
@@ -3195,15 +3193,7 @@ export const device = {
|
|
|
3195
3193
|
})
|
|
3196
3194
|
continue
|
|
3197
3195
|
}
|
|
3198
|
-
for (
|
|
3199
|
-
let eventFrameIndex = nextHoldEventFrameIndex();
|
|
3200
|
-
eventFrameIndex !== null && stageFrameIndex > eventFrameIndex;
|
|
3201
|
-
eventFrameIndex = nextHoldEventFrameIndex()
|
|
3202
|
-
) {
|
|
3203
|
-
await advanceHold(await controller.stepFrame())
|
|
3204
|
-
}
|
|
3205
3196
|
const receipt = await controller.stepToOffset(anchorReceipt, stage.offsetMs)
|
|
3206
|
-
await advanceHold(receipt)
|
|
3207
3197
|
currentReceipt = receipt
|
|
3208
3198
|
snapshots.push({
|
|
3209
3199
|
actualMs: receipt.virtualTimestampMs - anchorReceipt.virtualTimestampMs,
|
|
@@ -3222,11 +3212,11 @@ export const device = {
|
|
|
3222
3212
|
// own frames; the settle jump below would carry the pointer past them
|
|
3223
3213
|
// and leave it down. the settle then runs from that release.
|
|
3224
3214
|
let releasedOffsetMs = 0
|
|
3225
|
-
while (
|
|
3215
|
+
while (input.hold && !releaseReceipt()) {
|
|
3226
3216
|
const receipt = await controller.stepFrame()
|
|
3227
|
-
await advanceHold(receipt)
|
|
3228
3217
|
releasedOffsetMs = receipt.virtualTimestampMs - driveStart.virtualTimestampMs
|
|
3229
3218
|
}
|
|
3219
|
+
await heldInput
|
|
3230
3220
|
if (input.settleMs > 0) {
|
|
3231
3221
|
const maximumPrimaryMs = snapshots.reduce(
|
|
3232
3222
|
(maximum, snapshot) =>
|
|
@@ -3297,23 +3287,25 @@ export const device = {
|
|
|
3297
3287
|
},
|
|
3298
3288
|
)
|
|
3299
3289
|
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
(captureToken) => {
|
|
3303
|
-
const registry = window.__sootsimVisibleChangeCaptures
|
|
3304
|
-
return registry?.[captureToken]?.ready === true
|
|
3305
|
-
},
|
|
3306
|
-
token,
|
|
3307
|
-
{ timeout: 5000 },
|
|
3308
|
-
),
|
|
3309
|
-
capturePromise.then(() => {
|
|
3310
|
-
throw new Error('peach motion capture completed before its trigger')
|
|
3311
|
-
}),
|
|
3312
|
-
])
|
|
3313
|
-
|
|
3314
|
-
const triggerStartMs = Date.now()
|
|
3290
|
+
let captureFailure: { error: unknown } | undefined
|
|
3291
|
+
let triggerStartMs = 0
|
|
3315
3292
|
let triggerCompletedMs = 0
|
|
3316
3293
|
try {
|
|
3294
|
+
await Promise.race([
|
|
3295
|
+
page.waitForFunction(
|
|
3296
|
+
(captureToken) => {
|
|
3297
|
+
const registry = window.__sootsimVisibleChangeCaptures
|
|
3298
|
+
return registry?.[captureToken]?.ready === true
|
|
3299
|
+
},
|
|
3300
|
+
token,
|
|
3301
|
+
{ timeout: 5000 },
|
|
3302
|
+
),
|
|
3303
|
+
capturePromise.then(() => {
|
|
3304
|
+
throw new Error('peach motion capture completed before its trigger')
|
|
3305
|
+
}),
|
|
3306
|
+
])
|
|
3307
|
+
|
|
3308
|
+
triggerStartMs = Date.now()
|
|
3317
3309
|
const triggerPromise = (async () => {
|
|
3318
3310
|
if (!options.hold) {
|
|
3319
3311
|
if (typeof options.trigger !== 'function') {
|
|
@@ -3349,6 +3341,7 @@ export const device = {
|
|
|
3349
3341
|
},
|
|
3350
3342
|
}
|
|
3351
3343
|
} catch (error) {
|
|
3344
|
+
captureFailure = { error }
|
|
3352
3345
|
await page
|
|
3353
3346
|
.evaluate((captureToken) => {
|
|
3354
3347
|
const registry = window.__sootsimVisibleChangeCaptures
|
|
@@ -3358,6 +3351,59 @@ export const device = {
|
|
|
3358
3351
|
.catch(() => {})
|
|
3359
3352
|
await capturePromise.catch(() => {})
|
|
3360
3353
|
throw error
|
|
3354
|
+
} finally {
|
|
3355
|
+
if (options.workletFrameTracePath) {
|
|
3356
|
+
try {
|
|
3357
|
+
const events = await page.evaluate(async () => {
|
|
3358
|
+
const test = window.SootSim?.bridges?.test
|
|
3359
|
+
const read = test?.getShellReanimatedTrace
|
|
3360
|
+
const enable = test?.setShellReanimatedTraceEnabled
|
|
3361
|
+
if (typeof read !== 'function' || typeof enable !== 'function')
|
|
3362
|
+
throw new Error('worklet trace bridge is absent')
|
|
3363
|
+
try {
|
|
3364
|
+
return await read()
|
|
3365
|
+
} finally {
|
|
3366
|
+
await enable(false, false)
|
|
3367
|
+
}
|
|
3368
|
+
})
|
|
3369
|
+
fs.writeFileSync(
|
|
3370
|
+
options.workletFrameTracePath,
|
|
3371
|
+
JSON.stringify(
|
|
3372
|
+
{
|
|
3373
|
+
source: 'peach-native-frame-queue',
|
|
3374
|
+
limit: WORKLET_FRAME_TRACE_LIMIT,
|
|
3375
|
+
events,
|
|
3376
|
+
},
|
|
3377
|
+
null,
|
|
3378
|
+
2,
|
|
3379
|
+
),
|
|
3380
|
+
)
|
|
3381
|
+
} catch (traceError) {
|
|
3382
|
+
if (!captureFailure) throw traceError
|
|
3383
|
+
const primary = captureFailure.error
|
|
3384
|
+
if (
|
|
3385
|
+
primary &&
|
|
3386
|
+
(typeof primary === 'object' || typeof primary === 'function') &&
|
|
3387
|
+
Object.isExtensible(primary)
|
|
3388
|
+
) {
|
|
3389
|
+
try {
|
|
3390
|
+
Object.defineProperty(primary, 'workletFrameTraceError', {
|
|
3391
|
+
value: traceError,
|
|
3392
|
+
configurable: true,
|
|
3393
|
+
})
|
|
3394
|
+
} catch (attachmentError) {
|
|
3395
|
+
console.error(
|
|
3396
|
+
'[peach motion capture] could not attach trace failure:',
|
|
3397
|
+
attachmentError,
|
|
3398
|
+
)
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
console.error(
|
|
3402
|
+
'[peach motion capture] secondary worklet trace failure:',
|
|
3403
|
+
traceError,
|
|
3404
|
+
)
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3361
3407
|
}
|
|
3362
3408
|
},
|
|
3363
3409
|
|
|
@@ -50,11 +50,15 @@ module.exports = class PeachEnvironment extends TestEnvironment {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async handleTestEvent(event) {
|
|
53
|
+
async handleTestEvent(event, state) {
|
|
54
54
|
if (event.name === 'hook_failure') {
|
|
55
55
|
this.controller.abort(new Error('Peach test hook failed'))
|
|
56
56
|
try {
|
|
57
57
|
await this.global.__peachDetoxCleanup?.()
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// circus already recorded the hook failure. a cleanup failure belongs
|
|
60
|
+
// beside it, rather than escaping this handler and replacing the result.
|
|
61
|
+
state.unhandledErrors.push(error)
|
|
58
62
|
} finally {
|
|
59
63
|
this.releaseAdmission?.()
|
|
60
64
|
this.releaseAdmission = null
|
package/detox/motion-change.cjs
CHANGED
|
@@ -112,8 +112,10 @@ const PAIRED_MOTION_MINIMUM_CHANGE_PERCENT = 0.3
|
|
|
112
112
|
const SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT = 0.6
|
|
113
113
|
|
|
114
114
|
// a present or dismiss records one second (60 transitions); a gesture ladder
|
|
115
|
-
// records four.
|
|
116
|
-
//
|
|
115
|
+
// records four. repeated animation ladders can also exceed that length, but
|
|
116
|
+
// their declared cycles retain frame pairing. a gesture jumps at its touch
|
|
117
|
+
// cadence and each capture phases that cadence differently, so it is graded by
|
|
118
|
+
// total travel instead of
|
|
117
119
|
// by frame. travel is judged over the whole gesture and again over each quarter
|
|
118
120
|
// so one jump or a lagging release cannot pass on its total.
|
|
119
121
|
const GESTURE_MOTION_MIN_TRANSITIONS = 120
|
|
@@ -127,7 +129,7 @@ const GESTURE_QUARTER_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.5, max: 2 })
|
|
|
127
129
|
// against a moving oracle fails. a gesture needs sootsim's total travel within
|
|
128
130
|
// a range of the oracle's. both lanes hold the same unique stage-label
|
|
129
131
|
// transitions in the same order; every measurement must exist.
|
|
130
|
-
function evaluateMotionTransitions(transitions, oracleTarget) {
|
|
132
|
+
function evaluateMotionTransitions(transitions, oracleTarget, repeating) {
|
|
131
133
|
const key = (transition) => `${transition.fromLabel}\u0000${transition.toLabel}`
|
|
132
134
|
const oracle = transitions.filter((transition) => transition.target === oracleTarget)
|
|
133
135
|
const sootsim = transitions.filter((transition) => transition.target === 'sootsim')
|
|
@@ -175,7 +177,7 @@ function evaluateMotionTransitions(transitions, oracleTarget) {
|
|
|
175
177
|
Math.floor((list.length * (index + 1)) / 4),
|
|
176
178
|
)
|
|
177
179
|
const follows =
|
|
178
|
-
oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS
|
|
180
|
+
!repeating && oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS
|
|
179
181
|
? within(travel(sootsim), travel(oracle), GESTURE_TRAVEL_RATIO_RANGE) &&
|
|
180
182
|
[0, 1, 2, 3].every((index) =>
|
|
181
183
|
within(
|
|
@@ -568,7 +570,7 @@ function evaluateMotionPublication(stages, transitions, oracleTarget, cycleOnset
|
|
|
568
570
|
last.passedThreshold &&
|
|
569
571
|
(!needsIntermediates || movingPassed >= PUBLICATION_MIN_INTERMEDIATE_PASSES) &&
|
|
570
572
|
cyclePeriods.every((period) => period.passed) &&
|
|
571
|
-
evaluateMotionTransitions(transitions, oracleTarget).passed,
|
|
573
|
+
evaluateMotionTransitions(transitions, oracleTarget, cyclePeriods.length > 0).passed,
|
|
572
574
|
cyclePeriods,
|
|
573
575
|
intermediateCount: intermediates.length,
|
|
574
576
|
intermediatePassed,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// animation start witnesses are diagnostics; they never replace pixel or cycle grading.
|
|
2
|
+
function animationLegStarts(value) {
|
|
3
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
4
|
+
throw new Error('animation leg starts must be a non-empty array')
|
|
5
|
+
return value.map((row) => {
|
|
6
|
+
if (
|
|
7
|
+
!row ||
|
|
8
|
+
typeof row.track !== 'string' ||
|
|
9
|
+
!row.track ||
|
|
10
|
+
typeof row.leg !== 'string' ||
|
|
11
|
+
!row.leg ||
|
|
12
|
+
!Number.isFinite(row.atMs) ||
|
|
13
|
+
!Number.isFinite(row.startValue)
|
|
14
|
+
)
|
|
15
|
+
throw new Error('invalid animation leg start witness')
|
|
16
|
+
return { track: row.track, leg: row.leg, atMs: row.atMs, startValue: row.startValue }
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = { animationLegStarts }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -31,6 +31,7 @@ __export(backend_origin_exports, {
|
|
|
31
31
|
isContrastBackendHost: () => isContrastBackendHost,
|
|
32
32
|
isContrastBackendOrigin: () => isContrastBackendOrigin,
|
|
33
33
|
isLoopbackHost: () => isLoopbackHost,
|
|
34
|
+
isLoopbackOrigin: () => isLoopbackOrigin,
|
|
34
35
|
isTrustedBridgeRequest: () => isTrustedBridgeRequest,
|
|
35
36
|
loopbackOriginOf: () => loopbackOriginOf,
|
|
36
37
|
normalizeHostname: () => normalizeHostname,
|
|
@@ -38,7 +39,7 @@ __export(backend_origin_exports, {
|
|
|
38
39
|
resolveContrastBackendOrigin: () => resolveContrastBackendOrigin
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(backend_origin_exports);
|
|
41
|
-
var DEFAULT_CONTRAST_BACKEND_ORIGIN = "https://
|
|
42
|
+
var DEFAULT_CONTRAST_BACKEND_ORIGIN = "https://peachsim.com";
|
|
42
43
|
var LOOPBACK_PROBE_HOSTNAMES = ["127.0.0.1", "::1"];
|
|
43
44
|
function normalizeHostname(hostname) {
|
|
44
45
|
return hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
@@ -52,6 +53,13 @@ function isLoopbackHost(hostname) {
|
|
|
52
53
|
const normalized = normalizeHostname(hostname);
|
|
53
54
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
|
|
54
55
|
}
|
|
56
|
+
function isLoopbackOrigin(origin) {
|
|
57
|
+
try {
|
|
58
|
+
return isLoopbackHost(new URL(origin).hostname);
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
55
63
|
function isTrustedBridgeRequest(headers) {
|
|
56
64
|
const origin = headers.origin;
|
|
57
65
|
if (origin === void 0) return true;
|
|
@@ -92,7 +100,7 @@ function isBrowserAssetProxyUrl(value) {
|
|
|
92
100
|
var PEACHSIM_NATIVE_HOST = "native.peachsim.com";
|
|
93
101
|
function isContrastBackendHost(hostname) {
|
|
94
102
|
const normalized = normalizeHostname(hostname);
|
|
95
|
-
return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev") || normalized === PEACHSIM_NATIVE_HOST;
|
|
103
|
+
return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev") || normalized === "peachsim.com" || normalized === "www.peachsim.com" || normalized === PEACHSIM_NATIVE_HOST;
|
|
96
104
|
}
|
|
97
105
|
function isContrastBackendOrigin(origin) {
|
|
98
106
|
try {
|
|
@@ -122,6 +130,7 @@ function resolveContrastBackendOrigin(currentOrigin, search) {
|
|
|
122
130
|
isContrastBackendHost,
|
|
123
131
|
isContrastBackendOrigin,
|
|
124
132
|
isLoopbackHost,
|
|
133
|
+
isLoopbackOrigin,
|
|
125
134
|
isTrustedBridgeRequest,
|
|
126
135
|
loopbackOriginOf,
|
|
127
136
|
normalizeHostname,
|
package/dist-lib/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
package/dist-lib/beta.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1644 | (c) 2026 Tamagui LLC | Proprietary; see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|