peachsim 0.1.1635 → 0.1.1641
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 +10 -1
- package/cli/app-config.ts +14 -5
- package/cli/app-stubs.ts +96 -22
- package/cli/bridge-flow-runner.ts +2 -2
- package/cli/cloud-client.ts +60 -40
- package/cli/cloud-session.ts +16 -0
- package/cli/commands/assert.ts +17 -4
- package/cli/commands/box.ts +33 -19
- package/cli/commands/doctor.ts +37 -20
- package/cli/commands/flow.ts +1 -1
- package/cli/commands/inspect/actions.ts +3 -2
- package/cli/commands/inspect/core.ts +0 -8
- package/cli/commands/inspect/shared.ts +9 -4
- package/cli/commands/inspect.ts +65 -61
- package/cli/commands/platform.ts +2 -1
- package/cli/commands/preview.ts +1 -1
- package/cli/commands/record.ts +154 -68
- package/cli/commands/swift.ts +18 -4
- package/cli/commands/three-mode.ts +4 -38
- package/cli/commands/upload.ts +55 -49
- package/cli/drivers/playwright-sim-host.ts +65 -42
- package/cli/flow-live-status.ts +0 -2
- package/cli/main.ts +2 -2
- package/cli/outbound-endpoints.ts +7 -5
- package/cli/pause-aware-timeout.ts +4 -0
- package/cli/ws-bridge.ts +19 -16
- package/detox/attempt-events.cjs +99 -6
- package/detox/attempt-events.d.cts +7 -0
- package/detox/expectations.ts +7 -3
- package/detox/index.ts +259 -139
- package/detox/jest-environment.cjs +5 -1
- package/detox/motion-change.cjs +170 -21
- package/detox/motion-change.d.cts +32 -0
- package/detox/navigation-lifecycle.ts +35 -3
- 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 +17 -7
- 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 +5014 -2268
- 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 +1036 -166
- 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 +4613 -1963
- package/dist-lib/expo-swift-metadata.cjs +9 -7
- package/dist-lib/expo-swift-metadata.mjs +9 -7
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +58072 -16400
- 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 +2790 -143
- 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 +1 -1
- package/dist-lib/menu.mjs +1 -1
- 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 +2579 -95
- package/dist-lib/native-app-identity-project.cjs +243 -53
- package/dist-lib/native-app-identity-project.mjs +243 -53
- package/dist-lib/native-app-identity.cjs +29 -30
- package/dist-lib/native-app-identity.mjs +29 -30
- 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 +2 -2
- package/dist-lib/react-native-host-modules.mjs +2 -2
- package/dist-lib/scripts/dev-server-scanner.cjs +8 -6
- package/dist-lib/sdk.cjs +4 -3
- package/dist-lib/sdk.mjs +4 -3
- package/dist-lib/skills.cjs +66475 -23097
- package/dist-lib/swift-compile.cjs +452 -260
- package/dist-lib/swift-compile.mjs +451 -252
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +14 -7
- package/dist-lib/swift-submit.mjs +14 -7
- package/dist-lib/swift-toolchain.cjs +316 -41
- package/dist-lib/swift.cjs +1 -1
- package/dist-lib/vite-swift.cjs +8188 -5636
- package/dist-lib/vite.cjs +2857 -12
- package/dist-lib/worklet-executable.cjs +14 -2
- package/dist-lib/worklet-executable.mjs +14 -2
- package/package.json +1 -1
- package/src/backend-origin.ts +21 -6
- package/src/expo-swift-host.ts +5 -2
- package/src/expoKotlinContract.ts +4 -7
- package/src/expoSwiftContract.ts +4 -6
- package/src/host/staged-host-files.ts +9 -0
- package/src/native-app-identity-project.ts +1 -4
- package/src/native-app-identity.ts +1 -4
- package/src/nativeAppIconContract.ts +1 -4
- package/src/output-lock-stdio.ts +36 -0
- package/src/react-native-bridge-modules.ts +106 -78
- package/src/react-native-host-modules.ts +4 -1
- package/src/settings-bundle.ts +1 -4
- package/src/swift-compile.ts +224 -139
- package/src/swift-distribution.lock.json +23 -12
- package/src/swift-distribution.ts +103 -4
- package/src/swift-host.ts +95 -30
- package/src/swift-icu.ts +16 -10
- package/src/swift-objc-compile.ts +547 -0
- package/src/swift-objc-metadata.ts +204 -0
- package/src/swift-objc-sdk.ts +883 -19
- package/src/swift-objc-standin.ts +126 -14
- package/src/swift-package-manifest.ts +42 -1
- package/src/swift-packages.ts +35 -21
- package/src/swift-root-lock.ts +153 -0
- package/src/swift-standin-emit.ts +40 -7
- package/src/swift-standins.ts +404 -138
- package/src/swift-submit.ts +18 -7
- package/src/swift-toolchain.ts +378 -46
- package/src/swiftContract.ts +12 -0
- package/src/worker-system-chunks.ts +11 -12
- package/src/worklet-executable.ts +19 -1
- package/src/xcode-project.ts +280 -33
|
@@ -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
|
@@ -106,30 +106,35 @@ function measureProofMotionChange(input) {
|
|
|
106
106
|
// exactly still, so only oracle changes of at least this much need a sootsim
|
|
107
107
|
// match.
|
|
108
108
|
const PAIRED_MOTION_MINIMUM_CHANGE_PERCENT = 0.3
|
|
109
|
-
//
|
|
110
|
-
// the two targets land a frame or two
|
|
109
|
+
// the last oracle change, when it is below this and follows a still
|
|
110
|
+
// transition, is a settle blip, which the two targets land a frame or two
|
|
111
|
+
// apart, so it needs no match. only that one blip is exempt.
|
|
111
112
|
const SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT = 0.6
|
|
112
113
|
|
|
113
114
|
// a present or dismiss records one second (60 transitions); a gesture ladder
|
|
114
115
|
// records four. a gesture jumps at its touch cadence and each capture phases
|
|
115
116
|
// that cadence differently, so a gesture is graded by total travel instead of
|
|
116
|
-
// by frame
|
|
117
|
-
//
|
|
117
|
+
// by frame. travel is judged over the whole gesture and again over each quarter
|
|
118
|
+
// so one jump or a lagging release cannot pass on its total.
|
|
118
119
|
const GESTURE_MOTION_MIN_TRANSITIONS = 120
|
|
119
120
|
const GESTURE_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.8, max: 1.25 })
|
|
121
|
+
const GESTURE_QUARTER_TRAVEL_RATIO_RANGE = Object.freeze({ min: 0.5, max: 2 })
|
|
120
122
|
|
|
121
123
|
// a sampled motion proves movement as a pair: the oracle moves in at least one
|
|
122
124
|
// transition. a one second motion also needs sootsim to move within one frame
|
|
123
125
|
// of every transition where the oracle changed by the paired minimum; a hold
|
|
124
126
|
// where the oracle holds needs no sootsim movement, while a static sootsim
|
|
125
127
|
// against a moving oracle fails. a gesture needs sootsim's total travel within
|
|
126
|
-
// a range of the oracle's.
|
|
127
|
-
// measurement must exist.
|
|
128
|
+
// a range of the oracle's. both lanes hold the same unique stage-label
|
|
129
|
+
// transitions in the same order; every measurement must exist.
|
|
128
130
|
function evaluateMotionTransitions(transitions, oracleTarget) {
|
|
129
131
|
const key = (transition) => `${transition.fromLabel}\u0000${transition.toLabel}`
|
|
130
132
|
const oracle = transitions.filter((transition) => transition.target === oracleTarget)
|
|
131
133
|
const sootsim = transitions.filter((transition) => transition.target === 'sootsim')
|
|
132
|
-
const
|
|
134
|
+
const paired =
|
|
135
|
+
oracle.length === sootsim.length &&
|
|
136
|
+
new Set(oracle.map(key)).size === oracle.length &&
|
|
137
|
+
oracle.every((transition, index) => key(transition) === key(sootsim[index]))
|
|
133
138
|
const measured = transitions.every(
|
|
134
139
|
(transition) =>
|
|
135
140
|
transition.dimensionsMatch &&
|
|
@@ -137,38 +142,64 @@ function evaluateMotionTransitions(transitions, oracleTarget) {
|
|
|
137
142
|
transition.changePixels !== null &&
|
|
138
143
|
transition.totalPixels !== null,
|
|
139
144
|
)
|
|
140
|
-
const moving = oracle.filter((transition) => transition.passedMinimum)
|
|
141
145
|
const travel = (list) =>
|
|
142
146
|
list
|
|
143
147
|
.filter(
|
|
144
148
|
(transition) => transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT,
|
|
145
149
|
)
|
|
146
150
|
.reduce((total, transition) => total + transition.changePercent, 0)
|
|
147
|
-
const
|
|
148
|
-
[index - 1, index, index + 1].some(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
151
|
+
const movesNear = (list, index) =>
|
|
152
|
+
[index - 1, index, index + 1].some((neighbour) => list[neighbour]?.passedMinimum === true)
|
|
153
|
+
const pairedChange = (transition) =>
|
|
154
|
+
transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT
|
|
155
|
+
const settleBlip = (list) => {
|
|
156
|
+
const index = list.findLastIndex(pairedChange)
|
|
157
|
+
return index >= 0 &&
|
|
158
|
+
list[index].changePercent < SETTLE_BLIP_MAXIMUM_CHANGE_PERCENT &&
|
|
159
|
+
!(list[index - 1]?.passedMinimum ?? true)
|
|
160
|
+
? index
|
|
161
|
+
: -1
|
|
162
|
+
}
|
|
163
|
+
const oracleBlip = settleBlip(oracle)
|
|
164
|
+
const sootsimBlip = settleBlip(sootsim)
|
|
165
|
+
// the oracle moves only where it changed by the paired minimum; drift and
|
|
166
|
+
// its settle blip alone are not a motion.
|
|
167
|
+
const moving = oracle.filter(
|
|
168
|
+
(transition, index) => pairedChange(transition) && index !== oracleBlip,
|
|
169
|
+
)
|
|
154
170
|
const within = (actual, expected, range) =>
|
|
155
171
|
actual >= expected * range.min && actual <= expected * range.max
|
|
172
|
+
const quarter = (list, index) =>
|
|
173
|
+
list.slice(
|
|
174
|
+
Math.floor((list.length * index) / 4),
|
|
175
|
+
Math.floor((list.length * (index + 1)) / 4),
|
|
176
|
+
)
|
|
156
177
|
const follows =
|
|
157
178
|
oracle.length >= GESTURE_MOTION_MIN_TRANSITIONS
|
|
158
|
-
? within(travel(sootsim), travel(oracle), GESTURE_TRAVEL_RATIO_RANGE)
|
|
179
|
+
? within(travel(sootsim), travel(oracle), GESTURE_TRAVEL_RATIO_RANGE) &&
|
|
180
|
+
[0, 1, 2, 3].every((index) =>
|
|
181
|
+
within(
|
|
182
|
+
travel(quarter(sootsim, index)),
|
|
183
|
+
travel(quarter(oracle, index)),
|
|
184
|
+
GESTURE_QUARTER_TRAVEL_RATIO_RANGE,
|
|
185
|
+
),
|
|
186
|
+
)
|
|
159
187
|
: oracle.every(
|
|
160
188
|
(transition, index) =>
|
|
161
|
-
!transition
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
189
|
+
!pairedChange(transition) ||
|
|
190
|
+
index === oracleBlip ||
|
|
191
|
+
movesNear(sootsim, index),
|
|
192
|
+
) &&
|
|
193
|
+
sootsim.every(
|
|
194
|
+
(transition, index) =>
|
|
195
|
+
!pairedChange(transition) || index === sootsimBlip || movesNear(oracle, index),
|
|
165
196
|
)
|
|
166
197
|
return {
|
|
167
198
|
moving: moving.length,
|
|
168
199
|
passed:
|
|
169
200
|
measured &&
|
|
170
201
|
oracle.length > 0 &&
|
|
171
|
-
|
|
202
|
+
paired &&
|
|
172
203
|
moving.length > 0 &&
|
|
173
204
|
follows,
|
|
174
205
|
}
|
|
@@ -429,8 +460,126 @@ function evaluateDiscreteFrameTrace(input) {
|
|
|
429
460
|
return { mode: 'playing', anchorMs: anchor.atMs, holds }
|
|
430
461
|
}
|
|
431
462
|
|
|
463
|
+
// a motion publishes when its transitions pair and its pixels agree at the
|
|
464
|
+
// ends and at a few places in between. the first and last stage must pass their
|
|
465
|
+
// region budget; a ladder of at least ten stages also needs two intermediate
|
|
466
|
+
// stages that pass while the oracle is still moving into them. dense samples
|
|
467
|
+
// may move less than the pairing bar; filtered pixel change away from both
|
|
468
|
+
// endpoint images still proves intermediate motion. a settled tail frame
|
|
469
|
+
// proves nothing about the motion and one frame of timing shift
|
|
470
|
+
// fails a stage outright. the remaining intermediate stages are reported as
|
|
471
|
+
// room to improve, never as a failure. a sparse ladder samples too coarsely to
|
|
472
|
+
// ask for intermediates. a repeating motion also needs every cycle's period
|
|
473
|
+
// within one frame of the oracle's, since re-anchoring each cycle would
|
|
474
|
+
// otherwise hide a lane that runs fast or slow.
|
|
475
|
+
const PUBLICATION_DENSE_LADDER_MIN_STAGES = 10
|
|
476
|
+
const PUBLICATION_MIN_INTERMEDIATE_PASSES = 2
|
|
477
|
+
|
|
478
|
+
// one onset per cycle of a repeating animation on the lane's own clock: the
|
|
479
|
+
// transition drive, then each `cycle` anchor in declaration order. ios reports
|
|
480
|
+
// recording seconds (the transition's triggerInVideoSec, each anchor's
|
|
481
|
+
// detection.atSec), android its producer clock (triggerProducerSeconds, each
|
|
482
|
+
// anchor's producerSeconds), and peach virtual clock milliseconds (driveStart,
|
|
483
|
+
// each anchor's own receipt).
|
|
484
|
+
function motionCycleOnsets(event, eventPath) {
|
|
485
|
+
if (!Array.isArray(event.anchors)) return []
|
|
486
|
+
const cycles = event.anchors.filter(
|
|
487
|
+
(anchor) => typeof anchor === 'object' && anchor !== null && anchor.cycle === true,
|
|
488
|
+
)
|
|
489
|
+
if (cycles.length === 0) return []
|
|
490
|
+
const finite = (value, label) => {
|
|
491
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
492
|
+
throw new Error(`motion event cycle ${label} has no onset time: ${eventPath}`)
|
|
493
|
+
return value
|
|
494
|
+
}
|
|
495
|
+
const android = event.captureMode === 'android-screenrecord-sequence'
|
|
496
|
+
const peach = typeof event.driveStart === 'object' && event.driveStart !== null
|
|
497
|
+
const basisMs = peach
|
|
498
|
+
? finite(event.driveStart.virtualTimestampMs, 'transition')
|
|
499
|
+
: android
|
|
500
|
+
? finite(event.triggerProducerSeconds, 'transition') * 1000
|
|
501
|
+
: finite(event.triggerInVideoSec, 'transition') * 1000
|
|
502
|
+
return [
|
|
503
|
+
{ id: 'transition', atMs: basisMs },
|
|
504
|
+
...cycles.map((anchor) => {
|
|
505
|
+
if (typeof anchor.id !== 'string')
|
|
506
|
+
throw new Error(`motion event cycle anchor has no id: ${eventPath}`)
|
|
507
|
+
const atMs = peach
|
|
508
|
+
? finite(anchor.virtualTimestampMs, anchor.id)
|
|
509
|
+
: android
|
|
510
|
+
? finite(anchor.producerSeconds, anchor.id) * 1000
|
|
511
|
+
: finite(anchor.detection?.atSec, anchor.id) * 1000
|
|
512
|
+
return { id: anchor.id, atMs }
|
|
513
|
+
}),
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// one display frame: the period a native repeat cycle may differ from
|
|
518
|
+
// peach's before re-anchoring each cycle would hide real drift
|
|
519
|
+
const MOTION_CYCLE_PERIOD_TOLERANCE_MS = 1000 / 60
|
|
520
|
+
|
|
521
|
+
function motionCyclePeriods(reference, sootsim) {
|
|
522
|
+
const ids = (onsets) => onsets.map((onset) => onset.id).join(',')
|
|
523
|
+
if (ids(reference) !== ids(sootsim))
|
|
524
|
+
throw new Error(
|
|
525
|
+
`motion cycle anchors differ between lanes: reference [${ids(reference)}], sootsim [${ids(sootsim)}]`,
|
|
526
|
+
)
|
|
527
|
+
return reference.slice(1).map((onset, index) => {
|
|
528
|
+
const referenceMs = onset.atMs - reference[index].atMs
|
|
529
|
+
const sootsimMs = sootsim[index + 1].atMs - sootsim[index].atMs
|
|
530
|
+
return {
|
|
531
|
+
id: onset.id,
|
|
532
|
+
passed: Math.abs(referenceMs - sootsimMs) <= MOTION_CYCLE_PERIOD_TOLERANCE_MS,
|
|
533
|
+
referenceMs,
|
|
534
|
+
sootsimMs,
|
|
535
|
+
}
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function evaluateMotionPublication(stages, transitions, oracleTarget, cycleOnsets) {
|
|
540
|
+
const cyclePeriods = motionCyclePeriods(cycleOnsets.reference, cycleOnsets.sootsim)
|
|
541
|
+
const intermediates = stages.slice(1, -1)
|
|
542
|
+
const intermediatePassed = intermediates.filter((stage) => stage.passedThreshold).length
|
|
543
|
+
const stagesByLabel = new Map(stages.map((stage) => [stage.label, stage]))
|
|
544
|
+
const oracleMovingInto = new Set(
|
|
545
|
+
transitions
|
|
546
|
+
.filter(
|
|
547
|
+
(transition) =>
|
|
548
|
+
transition.target === oracleTarget &&
|
|
549
|
+
(transition.changePercent >= PAIRED_MOTION_MINIMUM_CHANGE_PERCENT ||
|
|
550
|
+
(transition.changePixels > 0 &&
|
|
551
|
+
stagesByLabel.get(transition.toLabel)?.oracleFromFirstPercent >=
|
|
552
|
+
PAIRED_MOTION_MINIMUM_CHANGE_PERCENT &&
|
|
553
|
+
stagesByLabel.get(transition.toLabel)?.oracleFromLastPercent >=
|
|
554
|
+
PAIRED_MOTION_MINIMUM_CHANGE_PERCENT)),
|
|
555
|
+
)
|
|
556
|
+
.map((transition) => transition.toLabel),
|
|
557
|
+
)
|
|
558
|
+
const movingPassed = intermediates.filter(
|
|
559
|
+
(stage) => stage.passedThreshold && oracleMovingInto.has(stage.label),
|
|
560
|
+
).length
|
|
561
|
+
const needsIntermediates = stages.length >= PUBLICATION_DENSE_LADDER_MIN_STAGES
|
|
562
|
+
const first = stages[0]
|
|
563
|
+
const last = stages[stages.length - 1]
|
|
564
|
+
return {
|
|
565
|
+
passed:
|
|
566
|
+
stages.length > 0 &&
|
|
567
|
+
first.passedThreshold &&
|
|
568
|
+
last.passedThreshold &&
|
|
569
|
+
(!needsIntermediates || movingPassed >= PUBLICATION_MIN_INTERMEDIATE_PASSES) &&
|
|
570
|
+
cyclePeriods.every((period) => period.passed) &&
|
|
571
|
+
evaluateMotionTransitions(transitions, oracleTarget).passed,
|
|
572
|
+
cyclePeriods,
|
|
573
|
+
intermediateCount: intermediates.length,
|
|
574
|
+
intermediatePassed,
|
|
575
|
+
movingPassed,
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
432
579
|
module.exports = {
|
|
433
580
|
MOTION_CHANGE_DEFINITION,
|
|
581
|
+
evaluateMotionPublication,
|
|
582
|
+
motionCycleOnsets,
|
|
434
583
|
measureMotionChange,
|
|
435
584
|
measureProofMotionChange,
|
|
436
585
|
evaluateMotionTransitions,
|
|
@@ -55,6 +55,38 @@ export function evaluateMotionTransitions(
|
|
|
55
55
|
passed: boolean
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export type MotionCycleOnset = { id: string; atMs: number }
|
|
59
|
+
|
|
60
|
+
export type MotionCyclePeriod = {
|
|
61
|
+
id: string
|
|
62
|
+
passed: boolean
|
|
63
|
+
referenceMs: number
|
|
64
|
+
sootsimMs: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function motionCycleOnsets(
|
|
68
|
+
event: Record<string, unknown>,
|
|
69
|
+
eventPath: string,
|
|
70
|
+
): MotionCycleOnset[]
|
|
71
|
+
|
|
72
|
+
export function evaluateMotionPublication(
|
|
73
|
+
stages: readonly {
|
|
74
|
+
label: string
|
|
75
|
+
passedThreshold: boolean
|
|
76
|
+
oracleFromFirstPercent: number | null
|
|
77
|
+
oracleFromLastPercent: number | null
|
|
78
|
+
}[],
|
|
79
|
+
transitions: MotionTransitionSample[],
|
|
80
|
+
oracleTarget: string,
|
|
81
|
+
cycleOnsets: { reference: MotionCycleOnset[]; sootsim: MotionCycleOnset[] },
|
|
82
|
+
): {
|
|
83
|
+
passed: boolean
|
|
84
|
+
cyclePeriods: MotionCyclePeriod[]
|
|
85
|
+
intermediateCount: number
|
|
86
|
+
intermediatePassed: number
|
|
87
|
+
movingPassed: number
|
|
88
|
+
}
|
|
89
|
+
|
|
58
90
|
export type DiscreteFrameAsset = {
|
|
59
91
|
fixturePath: string
|
|
60
92
|
fixtureSha256: string
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readPeachConfigFromSearchParams } from '../src/config'
|
|
1
2
|
import { writeAttemptEvent } from './attempt-events.cjs'
|
|
2
3
|
import type { Page, Request, Response } from 'playwright-core'
|
|
3
4
|
|
|
@@ -77,6 +78,18 @@ type PageDiagnostic =
|
|
|
77
78
|
url: string
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// developer config belongs in the browser, never in persisted diagnostics.
|
|
82
|
+
const developerToken = readPeachConfigFromSearchParams(
|
|
83
|
+
new URL(process.env.PEACH_URL || 'http://localhost').searchParams,
|
|
84
|
+
)?.mapKit?.token
|
|
85
|
+
|
|
86
|
+
function redactDiagnosticText(text: string): string {
|
|
87
|
+
const withoutConfig = text.replace(/([?&]peachConfig=)[^&\s"'<>]+/g, '$1[redacted]')
|
|
88
|
+
return developerToken
|
|
89
|
+
? withoutConfig.replaceAll(developerToken, '[redacted]')
|
|
90
|
+
: withoutConfig
|
|
91
|
+
}
|
|
92
|
+
|
|
80
93
|
const DIAGNOSTIC_LIMIT = 40
|
|
81
94
|
const pageDiagnostics = new WeakMap<Page, PageDiagnostic[]>()
|
|
82
95
|
const activeNavigations = new WeakMap<Page, ActiveNavigation>()
|
|
@@ -95,6 +108,21 @@ function persistDiagnostic(diagnostic: PageDiagnostic) {
|
|
|
95
108
|
// prints this buffer, so a guest error writes the console tail that led to it
|
|
96
109
|
// into events.jsonl. passing runs emit no guest error and write nothing.
|
|
97
110
|
function recordPageDiagnostic(page: Page, diagnostic: PageDiagnostic, persist = false) {
|
|
111
|
+
if ('url' in diagnostic) diagnostic.url = redactDiagnosticText(diagnostic.url)
|
|
112
|
+
if ('navigationUrl' in diagnostic)
|
|
113
|
+
diagnostic.navigationUrl = redactDiagnosticText(diagnostic.navigationUrl)
|
|
114
|
+
if ('message' in diagnostic)
|
|
115
|
+
diagnostic.message = redactDiagnosticText(diagnostic.message)
|
|
116
|
+
if ('errorText' in diagnostic && diagnostic.errorText)
|
|
117
|
+
diagnostic.errorText = redactDiagnosticText(diagnostic.errorText)
|
|
118
|
+
if ('pageState' in diagnostic && diagnostic.pageState) {
|
|
119
|
+
if ('url' in diagnostic.pageState)
|
|
120
|
+
diagnostic.pageState.url = redactDiagnosticText(diagnostic.pageState.url)
|
|
121
|
+
if ('evaluateError' in diagnostic.pageState)
|
|
122
|
+
diagnostic.pageState.evaluateError = redactDiagnosticText(
|
|
123
|
+
diagnostic.pageState.evaluateError,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
98
126
|
const entries = pageDiagnostics.get(page)
|
|
99
127
|
if (!entries) return
|
|
100
128
|
entries.push(diagnostic)
|
|
@@ -229,7 +257,7 @@ async function readSootsimPageState(
|
|
|
229
257
|
page: Page,
|
|
230
258
|
): Promise<SootsimPageState | { evaluateError: string }> {
|
|
231
259
|
try {
|
|
232
|
-
|
|
260
|
+
const state = await page.evaluate(() => {
|
|
233
261
|
const bootMarks = Reflect.get(window, '__sootsimBootMarks')
|
|
234
262
|
return {
|
|
235
263
|
bodyText: document.body?.innerText?.slice(0, 500) ?? '',
|
|
@@ -248,6 +276,8 @@ async function readSootsimPageState(
|
|
|
248
276
|
url: window.location.href,
|
|
249
277
|
}
|
|
250
278
|
})
|
|
279
|
+
state.url = redactDiagnosticText(state.url)
|
|
280
|
+
return state
|
|
251
281
|
} catch (error) {
|
|
252
282
|
return {
|
|
253
283
|
evaluateError: error instanceof Error ? error.message : String(error),
|
|
@@ -277,7 +307,9 @@ export async function describeSootsimBridgeFailure(
|
|
|
277
307
|
`cause: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
278
308
|
`page: ${JSON.stringify(state)}`,
|
|
279
309
|
`recent page diagnostics:\n${JSON.stringify(getSootsimNavigationDiagnostics(page))}`,
|
|
280
|
-
]
|
|
310
|
+
]
|
|
311
|
+
.map(redactDiagnosticText)
|
|
312
|
+
.join('\n'),
|
|
281
313
|
)
|
|
282
314
|
}
|
|
283
315
|
|
|
@@ -289,7 +321,7 @@ export async function navigateSootsimPage(
|
|
|
289
321
|
attachPageDiagnostics(page)
|
|
290
322
|
const navigation = {
|
|
291
323
|
...identity,
|
|
292
|
-
navigationUrl: url,
|
|
324
|
+
navigationUrl: redactDiagnosticText(url),
|
|
293
325
|
navigationId: ++nextNavigationIdentity,
|
|
294
326
|
}
|
|
295
327
|
activeNavigations.set(page, navigation)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1641 | (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.1641 | (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.1641 | (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.1641 | (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.1641 | (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;
|
|
@@ -24,12 +24,14 @@ var backend_origin_exports = {};
|
|
|
24
24
|
__export(backend_origin_exports, {
|
|
25
25
|
DEFAULT_CONTRAST_BACKEND_ORIGIN: () => DEFAULT_CONTRAST_BACKEND_ORIGIN,
|
|
26
26
|
LOOPBACK_PROBE_HOSTNAMES: () => LOOPBACK_PROBE_HOSTNAMES,
|
|
27
|
+
PEACHSIM_NATIVE_HOST: () => PEACHSIM_NATIVE_HOST,
|
|
27
28
|
buildBrowserAssetProxyUrl: () => buildBrowserAssetProxyUrl,
|
|
28
29
|
buildLoopbackProbeBaseUrl: () => buildLoopbackProbeBaseUrl,
|
|
29
30
|
isBrowserAssetProxyUrl: () => isBrowserAssetProxyUrl,
|
|
30
31
|
isContrastBackendHost: () => isContrastBackendHost,
|
|
31
32
|
isContrastBackendOrigin: () => isContrastBackendOrigin,
|
|
32
33
|
isLoopbackHost: () => isLoopbackHost,
|
|
34
|
+
isLoopbackOrigin: () => isLoopbackOrigin,
|
|
33
35
|
isTrustedBridgeRequest: () => isTrustedBridgeRequest,
|
|
34
36
|
loopbackOriginOf: () => loopbackOriginOf,
|
|
35
37
|
normalizeHostname: () => normalizeHostname,
|
|
@@ -51,6 +53,13 @@ function isLoopbackHost(hostname) {
|
|
|
51
53
|
const normalized = normalizeHostname(hostname);
|
|
52
54
|
return normalized === "localhost" || normalized.endsWith(".localhost") || normalized === "0.0.0.0" || normalized === "::1" || /^127(?:\.\d{1,3}){3}$/.test(normalized);
|
|
53
55
|
}
|
|
56
|
+
function isLoopbackOrigin(origin) {
|
|
57
|
+
try {
|
|
58
|
+
return isLoopbackHost(new URL(origin).hostname);
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
54
63
|
function isTrustedBridgeRequest(headers) {
|
|
55
64
|
const origin = headers.origin;
|
|
56
65
|
if (origin === void 0) return true;
|
|
@@ -88,9 +97,10 @@ function isBrowserAssetProxyUrl(value) {
|
|
|
88
97
|
return false;
|
|
89
98
|
}
|
|
90
99
|
}
|
|
100
|
+
var PEACHSIM_NATIVE_HOST = "native.peachsim.com";
|
|
91
101
|
function isContrastBackendHost(hostname) {
|
|
92
102
|
const normalized = normalizeHostname(hostname);
|
|
93
|
-
return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev");
|
|
103
|
+
return isLoopbackHost(normalized) || normalized === "contrast.dev" || normalized.endsWith(".contrast.dev") || normalized === PEACHSIM_NATIVE_HOST;
|
|
94
104
|
}
|
|
95
105
|
function isContrastBackendOrigin(origin) {
|
|
96
106
|
try {
|
|
@@ -99,14 +109,12 @@ function isContrastBackendOrigin(origin) {
|
|
|
99
109
|
return false;
|
|
100
110
|
}
|
|
101
111
|
}
|
|
102
|
-
function resolveContrastBackendOrigin(currentOrigin) {
|
|
103
|
-
const origin = currentOrigin?.trim();
|
|
112
|
+
function resolveContrastBackendOrigin(currentOrigin, search) {
|
|
113
|
+
const origin = new URLSearchParams(search ?? "").get("backend")?.trim() || currentOrigin?.trim();
|
|
104
114
|
if (!origin) return DEFAULT_CONTRAST_BACKEND_ORIGIN;
|
|
105
115
|
try {
|
|
106
116
|
const parsed = new URL(origin);
|
|
107
|
-
if (
|
|
108
|
-
return parsed.origin;
|
|
109
|
-
}
|
|
117
|
+
if (isContrastBackendHost(parsed.hostname)) return parsed.origin;
|
|
110
118
|
} catch {
|
|
111
119
|
}
|
|
112
120
|
return DEFAULT_CONTRAST_BACKEND_ORIGIN;
|
|
@@ -115,12 +123,14 @@ function resolveContrastBackendOrigin(currentOrigin) {
|
|
|
115
123
|
0 && (module.exports = {
|
|
116
124
|
DEFAULT_CONTRAST_BACKEND_ORIGIN,
|
|
117
125
|
LOOPBACK_PROBE_HOSTNAMES,
|
|
126
|
+
PEACHSIM_NATIVE_HOST,
|
|
118
127
|
buildBrowserAssetProxyUrl,
|
|
119
128
|
buildLoopbackProbeBaseUrl,
|
|
120
129
|
isBrowserAssetProxyUrl,
|
|
121
130
|
isContrastBackendHost,
|
|
122
131
|
isContrastBackendOrigin,
|
|
123
132
|
isLoopbackHost,
|
|
133
|
+
isLoopbackOrigin,
|
|
124
134
|
isTrustedBridgeRequest,
|
|
125
135
|
loopbackOriginOf,
|
|
126
136
|
normalizeHostname,
|
package/dist-lib/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! peach v0.1.
|
|
1
|
+
/*! peach v0.1.1641 | (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.1641 | (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.1641 | (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.1641 | (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;
|