@unrulysystems/native-motion 0.1.0-alpha.0
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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/android/build.gradle +24 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/GestureExclusionModule.kt +55 -0
- package/android/src/main/java/com/unrulysystems/nativemotion/NativeMotionPackage.kt +27 -0
- package/package.json +57 -0
- package/react-native.config.js +14 -0
- package/src/native/driver/instantWindow.ts +31 -0
- package/src/native/driver/strictModeReplay.ts +20 -0
- package/src/native/driver/uiLayoutEngine.ts +845 -0
- package/src/native/driver/uiLayoutGraph.ts +338 -0
- package/src/native/driver/uiValueChannel.ts +4640 -0
- package/src/native/driver/workletDriver.ts +3860 -0
- package/src/native/motion/AnimatePresence.tsx +1605 -0
- package/src/native/motion/LayoutGroup.tsx +163 -0
- package/src/native/motion/MotionConfig.tsx +400 -0
- package/src/native/motion/MotionRoot.tsx +250 -0
- package/src/native/motion/MotionView.tsx +6761 -0
- package/src/native/motion/addScaleCorrector.ts +97 -0
- package/src/native/motion/colorEndpointUiFeed.ts +32 -0
- package/src/native/motion/colorProjection.ts +39 -0
- package/src/native/motion/composeTransform.ts +271 -0
- package/src/native/motion/constraintMeasure.ts +157 -0
- package/src/native/motion/deferredPendingSnapshots.ts +25 -0
- package/src/native/motion/discreteProjection.ts +36 -0
- package/src/native/motion/dragAncestorPanContext.ts +28 -0
- package/src/native/motion/dragControls.ts +152 -0
- package/src/native/motion/dragGestureWiring.ts +1168 -0
- package/src/native/motion/dragHandoffBinding.ts +179 -0
- package/src/native/motion/dragHubStream.ts +756 -0
- package/src/native/motion/dragPropagationLock.ts +57 -0
- package/src/native/motion/driverValueChannel.ts +3288 -0
- package/src/native/motion/externalDragDriver.ts +214 -0
- package/src/native/motion/frameData.ts +31 -0
- package/src/native/motion/gestureBinding.ts +107 -0
- package/src/native/motion/gestureStateGate.ts +563 -0
- package/src/native/motion/gestureStateResolver.ts +286 -0
- package/src/native/motion/identityValueChannelControllerAdapter.ts +2043 -0
- package/src/native/motion/identityValueChannelControllerReconciliation.ts +176 -0
- package/src/native/motion/identityValueChannelLaneMarker.ts +14 -0
- package/src/native/motion/imperativeAnimate.ts +816 -0
- package/src/native/motion/keyframeTiming.ts +7 -0
- package/src/native/motion/layoutIdBinding.ts +261 -0
- package/src/native/motion/layoutIdFlightConfig.ts +51 -0
- package/src/native/motion/layoutProjection.ts +89 -0
- package/src/native/motion/layoutScroll.ts +204 -0
- package/src/native/motion/layoutTransition.ts +1302 -0
- package/src/native/motion/lengthLayoutContext.tsx +100 -0
- package/src/native/motion/lengthLayoutHost.ts +44 -0
- package/src/native/motion/mappedKeys.ts +184 -0
- package/src/native/motion/motionViewController.ts +3180 -0
- package/src/native/motion/nativeHostMarker.ts +22 -0
- package/src/native/motion/panSessionWiring.ts +127 -0
- package/src/native/motion/pathTransition.ts +101 -0
- package/src/native/motion/popLayout.ts +176 -0
- package/src/native/motion/presenceBinding.ts +382 -0
- package/src/native/motion/scaleCorrectorRegistry.ts +123 -0
- package/src/native/motion/scrollValues.ts +260 -0
- package/src/native/motion/serializablePayload.ts +151 -0
- package/src/native/motion/severity.ts +16 -0
- package/src/native/motion/shippedSurface.ts +843 -0
- package/src/native/motion/staticLengthGate.ts +86 -0
- package/src/native/motion/styleBaseGate.ts +72 -0
- package/src/native/motion/styleValueBinding.ts +657 -0
- package/src/native/motion/systemGestureExclusion.ts +61 -0
- package/src/native/motion/tapGestureWiring.ts +215 -0
- package/src/native/motion/transformOrder.ts +38 -0
- package/src/native/motion/transformStringBinding.ts +200 -0
- package/src/native/motion/transformTemplateGate.ts +45 -0
- package/src/native/motion/transitionGate.ts +435 -0
- package/src/native/motion/useAnimate.ts +52 -0
- package/src/native/motion/useCycle.ts +44 -0
- package/src/native/motion/useInstantTransition.ts +68 -0
- package/src/native/motion/useReducedMotion.ts +58 -0
- package/src/native/motion/useScroll.ts +162 -0
- package/src/native/motion/useViewportScroll.ts +29 -0
- package/src/native/motion/valueChannel.ts +7121 -0
- package/src/native/motion/valueHooks.ts +901 -0
- package/src/native/motion/variantChildRegistry.ts +67 -0
- package/src/native/motion/variantContext.tsx +172 -0
- package/src/native/motion/variantProps.ts +598 -0
- package/src/native.ts +159 -0
- package/src/verification/harnessMetrics.ts +78 -0
- package/src/verification/probe/LayoutIdentityWorkletProbe.tsx +251 -0
- package/src/verification/probe/WorkletParityProbe.tsx +162 -0
- package/src/verification/probe/layoutIdentityProbeEngine.ts +429 -0
- package/src/verification/screens/ArcPathChecksScreen.tsx +273 -0
- package/src/verification/screens/BooleanAnimateChecksScreen.tsx +358 -0
- package/src/verification/screens/ChoreographyGalleryScreen.tsx +1010 -0
- package/src/verification/screens/ColorBindingChecksScreen.tsx +568 -0
- package/src/verification/screens/CompletionChecksScreen.tsx +294 -0
- package/src/verification/screens/ConformanceScreen.tsx +573 -0
- package/src/verification/screens/ContentionProbeScreen.tsx +224 -0
- package/src/verification/screens/DriverSmokeScreen.tsx +99 -0
- package/src/verification/screens/DurationOnlyTweenChecksScreen.tsx +294 -0
- package/src/verification/screens/DynamicDragConfigChecksScreen.tsx +237 -0
- package/src/verification/screens/FrameDataChecksScreen.tsx +252 -0
- package/src/verification/screens/GestureChecksScreen.tsx +685 -0
- package/src/verification/screens/InstantTransitionChecksScreen.tsx +839 -0
- package/src/verification/screens/LayoutAnimationStartChecksScreen.tsx +648 -0
- package/src/verification/screens/LayoutChecksScreen.tsx +822 -0
- package/src/verification/screens/LayoutCommitSpikeScreen.tsx +142 -0
- package/src/verification/screens/MotionViewChecksScreen.tsx +930 -0
- package/src/verification/screens/PresenceChecksScreen.tsx +614 -0
- package/src/verification/screens/ReducedMotionChecksScreen.tsx +614 -0
- package/src/verification/screens/RestThresholdChecksScreen.tsx +634 -0
- package/src/verification/screens/ScaleCorrectorChecksScreen.tsx +425 -0
- package/src/verification/screens/SharedLayoutContinuityChecksScreen.tsx +338 -0
- package/src/verification/screens/SharedLayoutCrossfadeChecksScreen.tsx +425 -0
- package/src/verification/screens/TransitionDefaultSelectionChecksScreen.tsx +514 -0
- package/src/verification/screens/ViewportScrollAliasChecksScreen.tsx +303 -0
- package/src/verification/screens/conformanceBanner.ts +21 -0
- package/src/verification/screens/proofConsoleTap.ts +18 -0
- package/src/verification.ts +47 -0
- package/src/web.ts +98 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
// U7c rest-threshold device checks (the U7c packet's floor 3): authored restDelta/restSpeed
|
|
2
|
+
// reaching the spring solver's settle condition, exercised as VISIBLY different settle points
|
|
3
|
+
// under authored thresholds. The card mirrors the W2 web oracle
|
|
4
|
+
// (packages/native-motion-web/e2e/u7c-rest-thresholds.e2e.ts) and the pinned catalog
|
|
5
|
+
// useSpring.tsx element form: ONE trigger flies four boxes 0 → 100 on the catalog's
|
|
6
|
+
// stiffness-300 / damping-28 spring — loose (restDelta 80, restSpeed 2000), default (no
|
|
7
|
+
// thresholds), zero (0/0 — the pin's ||= quirk reads explicit 0 as ABSENT, motion-dom
|
|
8
|
+
// spring.ts:275-280), tight (0.00001/0.00001 — the catalog values verbatim) — plus a fifth box
|
|
9
|
+
// on an ordinary 600ms tween carrying the tight thresholds (the pin IGNORES them outside the
|
|
10
|
+
// spring generator — only generators/spring.ts and inertia.ts read them). Four rows:
|
|
11
|
+
//
|
|
12
|
+
// 1. SETTLE ORDERING — the loose box's activity stops measurably EARLIEST, the tight box's
|
|
13
|
+
// measurably LATEST, and the zero box matches the default box within a couple frames (the
|
|
14
|
+
// ||= quirk: explicit 0 rides the pin's default selection). Measured via each box's LAST
|
|
15
|
+
// onUpdate timestamp — the committed-values marshal is the only readback, never a JS-side
|
|
16
|
+
// mirror, and never a fixed frame chain (the 7b lesson): every box's settle is awaited by
|
|
17
|
+
// a fail-closed quiescence poll (quiet for 250ms AND at the target, else a loud deadline),
|
|
18
|
+
// so a box that never animates or never settles fails LOUD with its latest value named.
|
|
19
|
+
// Counterfactual (the W2 web leg's, observed): thresholds dropped or refused anywhere in
|
|
20
|
+
// the forwarding chain collapse every box onto the pin's 2 / 0.5 defaults and all four
|
|
21
|
+
// settle on the same frame — both margins read ~0 and fail.
|
|
22
|
+
// 2. EXACT SNAP — the loose box's AND settle fires mid-flight (restDelta 80 admits
|
|
23
|
+
// |target − current| ≤ 80; restSpeed 2000 never binds — peak |v| ≈ 730px/s), so its last
|
|
24
|
+
// pre-settle onUpdate sample sits FAR from 100 while the committed post-settle value is
|
|
25
|
+
// EXACTLY 100 (the pin's `state.done ? target : current` snap, spring.ts:416-423 — never
|
|
26
|
+
// the in-flight current). An engine that settles on thresholds but commits `current`
|
|
27
|
+
// leaves the box at ~24 and fails the exact read; dropped thresholds settle the loose box
|
|
28
|
+
// at the default ~517ms, where the pre-settle sample reads ~99.5 and fails the
|
|
29
|
+
// far-off-target bound.
|
|
30
|
+
// 3. TWEEN IGNORES THEM (the pin's parity) — { type: 'tween', duration: 0.6, restDelta:
|
|
31
|
+
// 0.00001, restSpeed: 0.00001 } flies the ORDINARY 600ms tween: the thresholds don't
|
|
32
|
+
// refuse (no validation report, the flight runs), don't truncate (mid-flight samples
|
|
33
|
+
// strictly between 0 and 100 exist; a threshold-driven early snap settles ~50ms and fails
|
|
34
|
+
// the class band), and don't mis-settle (the flight completes in its 600ms class, not the
|
|
35
|
+
// tight spring's ~1233ms).
|
|
36
|
+
// 4. CLEAN RUN — zero refusal reports through the console tap (the U5 pattern: console.error
|
|
37
|
+
// is the severity law's production error channel) across the whole timeline.
|
|
38
|
+
//
|
|
39
|
+
// Readback honesty: every x sample rides the element's own onUpdate (the driver's per-frame
|
|
40
|
+
// committed-values marshal) with a JS-receipt timestamp; ORDERING across boxes is the claim
|
|
41
|
+
// (every box rides the same marshal path, so delivery lag is shared), and each landing latency
|
|
42
|
+
// is disclosed as evidence, never gated to a fixed offset. A screenshot is the assertion; the
|
|
43
|
+
// console tap is evidence capture, never suppression.
|
|
44
|
+
|
|
45
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
46
|
+
import { ScrollView, StyleSheet, Text, View } from 'react-native'
|
|
47
|
+
import { MotionView } from '../../native/motion/MotionView'
|
|
48
|
+
import { ambientNativeSeverity } from '../../native/motion/severity'
|
|
49
|
+
|
|
50
|
+
const ROW_ORDER = 'settle ordering: loose earliest, tight latest, zero ≈ default (the ||= quirk)'
|
|
51
|
+
const ROW_SNAP = 'exact snap: the loose settle fires mid-flight yet commits exactly 100'
|
|
52
|
+
const ROW_TWEEN = 'tween ignores them: the 600ms tween flies its full class with 1e-5 authored'
|
|
53
|
+
const ROW_CLEAN = 'clean run: zero refusal reports through the console tap'
|
|
54
|
+
const ROWS = [ROW_ORDER, ROW_SNAP, ROW_TWEEN, ROW_CLEAN] as const
|
|
55
|
+
|
|
56
|
+
// Every subject flies 0 → 100; the 100px delta keeps every spring in the pin's DEFAULT
|
|
57
|
+
// threshold lane (|target − origin| < 5 is the granular lane — the same code path, pinned
|
|
58
|
+
// natively at unit level by W1).
|
|
59
|
+
const TARGET = 100
|
|
60
|
+
// The catalog's spring constants (useSpring.tsx:10-15).
|
|
61
|
+
const SPRING = { type: 'spring', stiffness: 300, damping: 28 } as const
|
|
62
|
+
|
|
63
|
+
// Committed-x equality margin (device px) for "reached the target"; the EXACT-snap row owns
|
|
64
|
+
// exactness and reads === 100.
|
|
65
|
+
const EPS = 0.5
|
|
66
|
+
// Row 3's flight margin: a mid-flight sample must clear 2px off both endpoints to count.
|
|
67
|
+
const FLIGHT_MARGIN = 2
|
|
68
|
+
|
|
69
|
+
// Row 1 margins (ms), mirroring the W2 web leg: the stepped pinned generator at 60fps settles
|
|
70
|
+
// loose ~50ms, default/zero ~517ms (the identical frame), tight ~1233ms — observed gaps ≈ 467
|
|
71
|
+
// and ≈ 717ms, so both bounds carry >2x margin; the zero-vs-default band absorbs a couple
|
|
72
|
+
// frames of marshal spread (web observed the IDENTICAL frame).
|
|
73
|
+
const LOOSE_EARLY_MARGIN_MS = 200
|
|
74
|
+
const TIGHT_LATE_MARGIN_MS = 400
|
|
75
|
+
const ZERO_DEFAULT_BAND_MS = 50
|
|
76
|
+
// Row 2's far-off-target bound for the loose box's last pre-settle sample (observed ~24px into
|
|
77
|
+
// the flight on the web leg; >2x margin).
|
|
78
|
+
const LOOSE_PRE_SETTLE_BOUND = 50
|
|
79
|
+
// Row 3's tween-class band (ms post-trigger): 600ms nominal plus startup/delivery slack;
|
|
80
|
+
// below the floor a threshold-driven truncation settled early, above the ceiling the flight
|
|
81
|
+
// rode something other than the 600ms tween (the tight spring's class is ~1233ms + delivery).
|
|
82
|
+
const TWEEN_CLASS_FLOOR_MS = 350
|
|
83
|
+
const TWEEN_CLASS_CEILING_MS = 1500
|
|
84
|
+
|
|
85
|
+
// Fail-closed observation: a box is SETTLED when its onUpdate marshal has been quiet for
|
|
86
|
+
// QUIET_MS AND its latest committed value reads the target — a box that never animates, stalls
|
|
87
|
+
// mid-flight, or oscillates forever (the literal-0 counterfactual: |v| ≤ 0 AND
|
|
88
|
+
// |target − current| ≤ 0 is unsatisfiable) hits the deadline and fails LOUD.
|
|
89
|
+
const QUIET_MS = 250
|
|
90
|
+
const SPRING_SETTLE_DEADLINE_MS = 6000
|
|
91
|
+
const TWEEN_SETTLE_DEADLINE_MS = 4000
|
|
92
|
+
|
|
93
|
+
// Timeline (ms). The boot dwell (0–2400) holds every subject at its origin for the first
|
|
94
|
+
// capture; the trigger fires at 2400; rows 1–3 SELF-FINALIZE as their quiescence polls resolve
|
|
95
|
+
// (loose ~+50ms, default/zero ~+517ms, the tween ~+600ms, tight ~+1233ms, each plus the
|
|
96
|
+
// device-dependent marshal delivery — disclosed, never gated); the clean-run verdict lands at
|
|
97
|
+
// 8000 and the final dwell holds the tally from ~8200 on.
|
|
98
|
+
const T_START = 2400
|
|
99
|
+
const T_CLEAN = 8000
|
|
100
|
+
const T_DONE = 8200
|
|
101
|
+
|
|
102
|
+
interface RowResult {
|
|
103
|
+
readonly pass: boolean
|
|
104
|
+
readonly detail: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface XSample {
|
|
108
|
+
readonly at: number
|
|
109
|
+
readonly value: number
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface SubjectTrace {
|
|
113
|
+
latest: number
|
|
114
|
+
// Mount-relative stamp of the most recent onUpdate marshal (-1 = none yet).
|
|
115
|
+
lastSampleAt: number
|
|
116
|
+
readonly samples: XSample[]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// A quiescence poll's resolution: the mount-relative stamp of the box's LAST onUpdate sample
|
|
120
|
+
// (the settle point) and the poll's own resolution time — or null when the deadline passed
|
|
121
|
+
// first (never-animates / never-settles both land here and fail loud).
|
|
122
|
+
interface SettleLanding {
|
|
123
|
+
readonly settleAt: number
|
|
124
|
+
readonly resolvedAt: number
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
type SpringKey = 'loose' | 'default' | 'zero' | 'tight'
|
|
128
|
+
type SubjectKey = SpringKey | 'tween'
|
|
129
|
+
|
|
130
|
+
const freshTrace = (): SubjectTrace => ({ latest: 0, lastSampleAt: -1, samples: [] })
|
|
131
|
+
|
|
132
|
+
function freshTraces(): Record<SubjectKey, SubjectTrace> {
|
|
133
|
+
return {
|
|
134
|
+
loose: freshTrace(),
|
|
135
|
+
default: freshTrace(),
|
|
136
|
+
zero: freshTrace(),
|
|
137
|
+
tight: freshTrace(),
|
|
138
|
+
tween: freshTrace(),
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function readX(latest: Readonly<Record<string, string | number>>): number | null {
|
|
143
|
+
const raw = latest['x']
|
|
144
|
+
const value = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : NaN
|
|
145
|
+
return Number.isFinite(value) ? value : null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The rows 1–3 observation primitive (the 7b lesson: landing polls, never fixed frame
|
|
149
|
+
// chains): poll per rAF until the trace has been QUIET for QUIET_MS with the latest committed
|
|
150
|
+
// value at the target, or the fail-closed deadline passes. The settle point is the last
|
|
151
|
+
// sample's own receipt stamp — measured EVIDENCE, with the deadline the only time gate. Fires
|
|
152
|
+
// `onResolve` exactly once.
|
|
153
|
+
function pollSettle(
|
|
154
|
+
trace: SubjectTrace,
|
|
155
|
+
startStamp: number,
|
|
156
|
+
deadlineAt: number,
|
|
157
|
+
now: () => number,
|
|
158
|
+
trackRaf: (id: ReturnType<typeof requestAnimationFrame>) => void,
|
|
159
|
+
onResolve: (landing: SettleLanding | null) => void,
|
|
160
|
+
): void {
|
|
161
|
+
const step = (): void => {
|
|
162
|
+
const t = now()
|
|
163
|
+
const flown = trace.lastSampleAt >= startStamp
|
|
164
|
+
const quiet = flown && t - trace.lastSampleAt >= QUIET_MS
|
|
165
|
+
const atTarget = Math.abs(trace.latest - TARGET) <= EPS
|
|
166
|
+
if (quiet && atTarget) {
|
|
167
|
+
onResolve({ settleAt: trace.lastSampleAt, resolvedAt: t })
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if (t >= deadlineAt) {
|
|
171
|
+
onResolve(null)
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
trackRaf(requestAnimationFrame(step))
|
|
175
|
+
}
|
|
176
|
+
trackRaf(requestAnimationFrame(step))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function RestThresholdChecksScreen() {
|
|
180
|
+
const severity = ambientNativeSeverity()
|
|
181
|
+
|
|
182
|
+
const [on, setOn] = useState(false)
|
|
183
|
+
const [stage, setStage] = useState('boot: five boxes at their origins (initial={false})')
|
|
184
|
+
const [live, setLive] = useState<Partial<Record<SubjectKey, string>>>({})
|
|
185
|
+
const [rows, setRows] = useState<Partial<Record<(typeof ROWS)[number], RowResult>>>({})
|
|
186
|
+
|
|
187
|
+
const mountStampRef = useRef(Date.now())
|
|
188
|
+
const tracesRef = useRef<Record<SubjectKey, SubjectTrace> | null>(null)
|
|
189
|
+
if (tracesRef.current === null) tracesRef.current = freshTraces()
|
|
190
|
+
|
|
191
|
+
// Row evidence: the trigger stamp and each box's poll resolution (undefined = the poll is
|
|
192
|
+
// still running; null = the deadline passed first).
|
|
193
|
+
const startStampRef = useRef(0)
|
|
194
|
+
const landingsRef = useRef<Partial<Record<SubjectKey, SettleLanding | null>>>({})
|
|
195
|
+
|
|
196
|
+
const rafsRef = useRef<ReturnType<typeof requestAnimationFrame>[]>([])
|
|
197
|
+
const reportsRef = useRef<string[]>([])
|
|
198
|
+
|
|
199
|
+
const trackRaf = (id: ReturnType<typeof requestAnimationFrame>): void => {
|
|
200
|
+
rafsRef.current.push(id)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const record = useCallback((key: SubjectKey, value: number): void => {
|
|
204
|
+
const trace = tracesRef.current![key]
|
|
205
|
+
const at = Date.now() - mountStampRef.current
|
|
206
|
+
trace.latest = value
|
|
207
|
+
trace.lastSampleAt = at
|
|
208
|
+
trace.samples.push({ at, value })
|
|
209
|
+
setLive((previous) => ({ ...previous, [key]: value.toFixed(1) }))
|
|
210
|
+
}, [])
|
|
211
|
+
|
|
212
|
+
const finalize = (id: (typeof ROWS)[number], pass: boolean, detail: string): void => {
|
|
213
|
+
setRows((previous) => ({ ...previous, [id]: { pass, detail } }))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
const mountStamp = mountStampRef.current
|
|
218
|
+
const now = (): number => Date.now() - mountStamp
|
|
219
|
+
const traces = tracesRef.current!
|
|
220
|
+
|
|
221
|
+
// The production error channel tap (the U5 pattern): refusal reports ride consoleReporter →
|
|
222
|
+
// console.error. Installed for this screen's lifetime only; everything passes through.
|
|
223
|
+
const originalError = console.error
|
|
224
|
+
console.error = (...args: unknown[]) => {
|
|
225
|
+
for (const arg of args) {
|
|
226
|
+
const message = arg instanceof Error ? arg.message : typeof arg === 'string' ? arg : null
|
|
227
|
+
if (message !== null && /motion/i.test(message)) reportsRef.current.push(message)
|
|
228
|
+
}
|
|
229
|
+
originalError(...args)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const timers: ReturnType<typeof setTimeout>[] = []
|
|
233
|
+
const at = (ms: number, run: () => void): void => {
|
|
234
|
+
timers.push(setTimeout(run, ms))
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 1. SETTLE ORDERING — finalizes once ALL FOUR spring polls resolved. A null landing is
|
|
238
|
+
// the loud never-animates / never-settles class and names the box.
|
|
239
|
+
const maybeFinalizeOrdering = (): void => {
|
|
240
|
+
const landings = landingsRef.current
|
|
241
|
+
const keys: readonly SpringKey[] = ['loose', 'default', 'zero', 'tight']
|
|
242
|
+
if (!keys.every((key) => landings[key] !== undefined)) return
|
|
243
|
+
const start = startStampRef.current
|
|
244
|
+
const missing = keys.find((key) => landings[key] === null)
|
|
245
|
+
if (missing !== undefined) {
|
|
246
|
+
const trace = traces[missing]
|
|
247
|
+
finalize(
|
|
248
|
+
ROW_ORDER,
|
|
249
|
+
false,
|
|
250
|
+
`the ${missing} box never settled within the ${SPRING_SETTLE_DEADLINE_MS}ms deadline ` +
|
|
251
|
+
`(latest ${trace.latest.toFixed(1)}, ${trace.samples.length} onUpdate samples) — ` +
|
|
252
|
+
(trace.samples.length === 0
|
|
253
|
+
? 'it never animated'
|
|
254
|
+
: 'it never reached a quiet settle at the target — the never-settles class (on ' +
|
|
255
|
+
'the zero box, a literal-0 honoring makes the AND settle unsatisfiable: the ' +
|
|
256
|
+
'||= quirk is not in effect)'),
|
|
257
|
+
)
|
|
258
|
+
setStage('ordering row finalized (FAIL) — the tween and clean-run rows remain')
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
const ms = (key: SpringKey): number => landings[key]!.settleAt - start
|
|
262
|
+
const loose = ms('loose')
|
|
263
|
+
const dflt = ms('default')
|
|
264
|
+
const zero = ms('zero')
|
|
265
|
+
const tight = ms('tight')
|
|
266
|
+
const earlyGap = dflt - loose
|
|
267
|
+
const lateGap = tight - dflt
|
|
268
|
+
const zeroGap = Math.abs(zero - dflt)
|
|
269
|
+
const stamps = `loose +${loose.toFixed(0)}ms, default +${dflt.toFixed(0)}ms, zero +${zero.toFixed(0)}ms, tight +${tight.toFixed(0)}ms`
|
|
270
|
+
let bad: string | null = null
|
|
271
|
+
if (earlyGap <= LOOSE_EARLY_MARGIN_MS) {
|
|
272
|
+
bad =
|
|
273
|
+
`the loose thresholds did not settle early: default − loose = ${earlyGap.toFixed(0)}ms ` +
|
|
274
|
+
`(want > ${LOOSE_EARLY_MARGIN_MS}ms; ${stamps}) — the authored restDelta/restSpeed ` +
|
|
275
|
+
'did not reach the solver (dropped or refused thresholds collapse every box onto the ' +
|
|
276
|
+
"pin's 2 / 0.5 defaults and this gap reads ~0)"
|
|
277
|
+
} else if (lateGap <= TIGHT_LATE_MARGIN_MS) {
|
|
278
|
+
bad =
|
|
279
|
+
`the tight thresholds did not settle late: tight − default = ${lateGap.toFixed(0)}ms ` +
|
|
280
|
+
`(want > ${TIGHT_LATE_MARGIN_MS}ms; ${stamps}) — the authored restDelta/restSpeed did ` +
|
|
281
|
+
'not reach the solver'
|
|
282
|
+
} else if (zeroGap > ZERO_DEFAULT_BAND_MS) {
|
|
283
|
+
bad =
|
|
284
|
+
`explicit 0 did not read as absent: |zero − default| = ${zeroGap.toFixed(0)}ms ` +
|
|
285
|
+
`(want ≤ ${ZERO_DEFAULT_BAND_MS}ms, a couple frames; ${stamps}) — the pin's ||= quirk ` +
|
|
286
|
+
'(spring.ts:275-280) is not in effect'
|
|
287
|
+
}
|
|
288
|
+
finalize(
|
|
289
|
+
ROW_ORDER,
|
|
290
|
+
bad === null,
|
|
291
|
+
bad ??
|
|
292
|
+
`last-onUpdate settle stamps (post-trigger): ${stamps} — default − loose = ` +
|
|
293
|
+
`${earlyGap.toFixed(0)}ms (> ${LOOSE_EARLY_MARGIN_MS}: the loose thresholds reached ` +
|
|
294
|
+
`the solver), tight − default = ${lateGap.toFixed(0)}ms (> ${TIGHT_LATE_MARGIN_MS}: ` +
|
|
295
|
+
`the tight thresholds reached it), |zero − default| = ${zeroGap.toFixed(0)}ms (≤ ` +
|
|
296
|
+
`${ZERO_DEFAULT_BAND_MS}, a couple frames: explicit 0 rides the default selection — ` +
|
|
297
|
+
'the ||= quirk verbatim)',
|
|
298
|
+
)
|
|
299
|
+
setStage('ordering row finalized — the tween and clean-run rows remain')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 2. EXACT SNAP — finalizes when the loose box's poll resolves. The last pre-settle sample
|
|
303
|
+
// must sit far off the target (the AND settle fired mid-flight) and the committed
|
|
304
|
+
// post-settle value must be EXACTLY 100 (the snap-at-done, not the in-flight current).
|
|
305
|
+
const finalizeSnap = (landing: SettleLanding | null): void => {
|
|
306
|
+
const start = startStampRef.current
|
|
307
|
+
const post = traces.loose.samples.filter((s) => s.at >= start)
|
|
308
|
+
const snapIndex = post.findIndex((s) => s.value === TARGET)
|
|
309
|
+
let bad: string | null = null
|
|
310
|
+
if (landing === null) {
|
|
311
|
+
bad =
|
|
312
|
+
`the loose box never settled within the ${SPRING_SETTLE_DEADLINE_MS}ms deadline ` +
|
|
313
|
+
`(latest ${traces.loose.latest.toFixed(1)}) — the snap cannot be witnessed`
|
|
314
|
+
} else if (snapIndex === -1) {
|
|
315
|
+
bad =
|
|
316
|
+
`no post-trigger onUpdate sample reads exactly ${TARGET} (latest ` +
|
|
317
|
+
`${traces.loose.latest.toFixed(1)}) — the settle committed the current value, not the ` +
|
|
318
|
+
'target (the pin commits `state.done ? target : current`)'
|
|
319
|
+
} else if (snapIndex === 0) {
|
|
320
|
+
bad =
|
|
321
|
+
`the loose box's FIRST post-trigger sample already reads ${TARGET} — the mid-flight ` +
|
|
322
|
+
'settle was never witnessed (a teleport, not a spring flight)'
|
|
323
|
+
} else {
|
|
324
|
+
const preSettle = post[snapIndex - 1]!
|
|
325
|
+
if (preSettle.value >= LOOSE_PRE_SETTLE_BOUND) {
|
|
326
|
+
bad =
|
|
327
|
+
`the loose box's last pre-settle sample reads ${preSettle.value.toFixed(1)} at ` +
|
|
328
|
+
`+${(preSettle.at - start).toFixed(0)}ms (want < ${LOOSE_PRE_SETTLE_BOUND}) — the ` +
|
|
329
|
+
'loose AND settle did not fire mid-flight (dropped thresholds settle it at the ' +
|
|
330
|
+
'default ~517ms, where the pre-settle sample reads ~99.5)'
|
|
331
|
+
} else if (traces.loose.latest !== TARGET) {
|
|
332
|
+
bad =
|
|
333
|
+
`the loose box's committed post-settle value reads ${traces.loose.latest}, not ` +
|
|
334
|
+
`exactly ${TARGET} — no exact snap at rest`
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
finalize(
|
|
338
|
+
ROW_SNAP,
|
|
339
|
+
bad === null,
|
|
340
|
+
bad ??
|
|
341
|
+
`the loose AND settle fired mid-flight: the last pre-settle onUpdate reads ` +
|
|
342
|
+
`${post[snapIndex - 1]!.value.toFixed(1)} at +${(post[snapIndex - 1]!.at - start).toFixed(0)}ms ` +
|
|
343
|
+
`(< ${LOOSE_PRE_SETTLE_BOUND}; restDelta 80 admits |target − current| ≤ 80, ` +
|
|
344
|
+
'restSpeed 2000 never binds), and the committed post-settle value is EXACTLY ' +
|
|
345
|
+
`${TARGET} — the pin's snap-at-done (\`state.done ? target : current\`), a ~` +
|
|
346
|
+
`${(TARGET - post[snapIndex - 1]!.value).toFixed(0)}px commit jump that only the ` +
|
|
347
|
+
`snap produces; settle stamp +${(landing!.settleAt - start).toFixed(0)}ms`,
|
|
348
|
+
)
|
|
349
|
+
setStage('snap row finalized')
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// 3. TWEEN IGNORES THEM — finalizes when the tween box's poll resolves: the authored tight
|
|
353
|
+
// thresholds neither refused (the flight ran, no report — row 4 owns the tap), nor
|
|
354
|
+
// truncated (mid-flight samples strictly between, the settle lands in the 600ms class),
|
|
355
|
+
// nor mis-settled it.
|
|
356
|
+
const finalizeTween = (landing: SettleLanding | null): void => {
|
|
357
|
+
const start = startStampRef.current
|
|
358
|
+
const post = traces.tween.samples.filter((s) => s.at >= start)
|
|
359
|
+
const interior = post.filter(
|
|
360
|
+
(s) => s.value > 0 + FLIGHT_MARGIN && s.value < TARGET - FLIGHT_MARGIN,
|
|
361
|
+
)
|
|
362
|
+
let bad: string | null = null
|
|
363
|
+
if (landing === null) {
|
|
364
|
+
bad =
|
|
365
|
+
`the tween box never settled within the ${TWEEN_SETTLE_DEADLINE_MS}ms deadline ` +
|
|
366
|
+
`(latest ${traces.tween.latest.toFixed(1)}, ${post.length} samples) — the authored ` +
|
|
367
|
+
'thresholds refused or mis-settled the tween (the pin ignores them outside the ' +
|
|
368
|
+
'spring generator)'
|
|
369
|
+
} else if (interior.length === 0) {
|
|
370
|
+
bad =
|
|
371
|
+
`ZERO mid-flight samples strictly between 0 and ${TARGET} — the tween did not fly ` +
|
|
372
|
+
`its 600ms class (latest ${traces.tween.latest.toFixed(1)})`
|
|
373
|
+
} else {
|
|
374
|
+
const latency = landing.settleAt - start
|
|
375
|
+
if (latency < TWEEN_CLASS_FLOOR_MS || latency > TWEEN_CLASS_CEILING_MS) {
|
|
376
|
+
bad =
|
|
377
|
+
`the tween settled at +${latency.toFixed(0)}ms, outside its 600ms class band ` +
|
|
378
|
+
`(${TWEEN_CLASS_FLOOR_MS}–${TWEEN_CLASS_CEILING_MS}ms) — the authored thresholds ` +
|
|
379
|
+
'truncated or re-classed the flight'
|
|
380
|
+
} else if (Math.abs(traces.tween.latest - TARGET) > EPS) {
|
|
381
|
+
bad = `the tween committed ${traces.tween.latest.toFixed(1)}, want ${TARGET}`
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
finalize(
|
|
385
|
+
ROW_TWEEN,
|
|
386
|
+
bad === null,
|
|
387
|
+
bad ??
|
|
388
|
+
`the 600ms tween flew its full class with restDelta/restSpeed 1e-5 authored: ` +
|
|
389
|
+
`${interior.length} mid-flight samples strictly between 0 and ${TARGET} ` +
|
|
390
|
+
`(${interior[0]!.value.toFixed(1)} → ${interior[interior.length - 1]!.value.toFixed(1)}), ` +
|
|
391
|
+
`settled at +${(landing!.settleAt - start).toFixed(0)}ms post-trigger (the ` +
|
|
392
|
+
`${TWEEN_CLASS_FLOOR_MS}–${TWEEN_CLASS_CEILING_MS}ms band, 600ms nominal + ` +
|
|
393
|
+
`delivery), final ${traces.tween.latest.toFixed(1)} — the thresholds neither ` +
|
|
394
|
+
'refused, nor truncated, nor mis-settled the tween (the pin parity: only the ' +
|
|
395
|
+
'spring and inertia generators read them)',
|
|
396
|
+
)
|
|
397
|
+
setStage('tween row finalized')
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// THE TRIGGER — one state flip flies all five boxes 0 → 100; each box's settle is awaited
|
|
401
|
+
// by its own fail-closed quiescence poll (never a fixed frame chain).
|
|
402
|
+
at(T_START, () => {
|
|
403
|
+
startStampRef.current = now()
|
|
404
|
+
const start = startStampRef.current
|
|
405
|
+
setOn(true)
|
|
406
|
+
setStage(
|
|
407
|
+
`trigger fired: 0 → ${TARGET} on the catalog 300/28 spring (loose / default / zero / ` +
|
|
408
|
+
'tight) + the threshold-carrying 600ms tween',
|
|
409
|
+
)
|
|
410
|
+
const springKeys: readonly SpringKey[] = ['loose', 'default', 'zero', 'tight']
|
|
411
|
+
for (const key of springKeys) {
|
|
412
|
+
pollSettle(
|
|
413
|
+
traces[key],
|
|
414
|
+
start,
|
|
415
|
+
start + SPRING_SETTLE_DEADLINE_MS,
|
|
416
|
+
now,
|
|
417
|
+
trackRaf,
|
|
418
|
+
(landing) => {
|
|
419
|
+
landingsRef.current[key] = landing
|
|
420
|
+
if (key === 'loose') finalizeSnap(landing)
|
|
421
|
+
maybeFinalizeOrdering()
|
|
422
|
+
},
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
pollSettle(
|
|
426
|
+
traces.tween,
|
|
427
|
+
start,
|
|
428
|
+
start + TWEEN_SETTLE_DEADLINE_MS,
|
|
429
|
+
now,
|
|
430
|
+
trackRaf,
|
|
431
|
+
(landing) => {
|
|
432
|
+
landingsRef.current.tween = landing
|
|
433
|
+
finalizeTween(landing)
|
|
434
|
+
},
|
|
435
|
+
)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// 4. CLEAN RUN — the console tap saw zero refusal reports across the whole timeline.
|
|
439
|
+
at(T_CLEAN, () => {
|
|
440
|
+
const reports = reportsRef.current
|
|
441
|
+
finalize(
|
|
442
|
+
ROW_CLEAN,
|
|
443
|
+
reports.length === 0,
|
|
444
|
+
reports.length === 0
|
|
445
|
+
? `0 refusal reports through the console tap across ${now().toFixed(0)}ms of ` +
|
|
446
|
+
'the trigger, all four spring settles, and the threshold-carrying tween'
|
|
447
|
+
: `${reports.length} report(s): ${reports
|
|
448
|
+
.map((message) => `"${message.slice(0, 80)}…"`)
|
|
449
|
+
.join(' | ')}`,
|
|
450
|
+
)
|
|
451
|
+
})
|
|
452
|
+
at(T_DONE, () => setStage('done'))
|
|
453
|
+
|
|
454
|
+
return () => {
|
|
455
|
+
for (const timer of timers) clearTimeout(timer)
|
|
456
|
+
for (const raf of rafsRef.current) cancelAnimationFrame(raf)
|
|
457
|
+
console.error = originalError
|
|
458
|
+
}
|
|
459
|
+
// All captured values are refs or stable setters; the timeline runs once per mount.
|
|
460
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
461
|
+
}, [])
|
|
462
|
+
|
|
463
|
+
const done = ROWS.every((id) => rows[id] !== undefined)
|
|
464
|
+
const failing = ROWS.filter((id) => rows[id] !== undefined && !rows[id]!.pass)
|
|
465
|
+
const verdict = !done ? 'RUNNING' : failing.length === 0 ? 'PASS' : 'FAIL'
|
|
466
|
+
const passed = Object.keys(rows).length - failing.length
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<ScrollView style={styles.scroll} contentContainerStyle={styles.container}>
|
|
470
|
+
<Text style={styles.title}>U7c rest-threshold checks</Text>
|
|
471
|
+
<Text style={styles.subtitle}>
|
|
472
|
+
authored restDelta/restSpeed order the spring settle — the catalog useSpring.tsx element
|
|
473
|
+
form ({severity} severity)
|
|
474
|
+
</Text>
|
|
475
|
+
<View
|
|
476
|
+
style={[
|
|
477
|
+
styles.banner,
|
|
478
|
+
verdict === 'PASS' ? styles.pass : verdict === 'FAIL' ? styles.fail : styles.running,
|
|
479
|
+
]}
|
|
480
|
+
>
|
|
481
|
+
<Text style={styles.bannerText}>{verdict}</Text>
|
|
482
|
+
</View>
|
|
483
|
+
<Text style={styles.tally}>
|
|
484
|
+
{done
|
|
485
|
+
? `${passed} PASS / ${failing.length} FAIL of ${ROWS.length} rows`
|
|
486
|
+
: `${Object.keys(rows).length}/${ROWS.length} rows complete — RUNNING`}
|
|
487
|
+
</Text>
|
|
488
|
+
<Text style={styles.tallyDetail}>
|
|
489
|
+
{done
|
|
490
|
+
? failing.length === 0
|
|
491
|
+
? 'no failing rows'
|
|
492
|
+
: `FAILING: ${failing.join(' | ')}`
|
|
493
|
+
: `stage: ${stage}`}
|
|
494
|
+
</Text>
|
|
495
|
+
<Text style={styles.status}>
|
|
496
|
+
u7c stage: {stage} · loose={live.loose ?? '0.0'} default={live.default ?? '0.0'} zero=
|
|
497
|
+
{live.zero ?? '0.0'} tight={live.tight ?? '0.0'} tween={live.tween ?? '0.0'}
|
|
498
|
+
</Text>
|
|
499
|
+
|
|
500
|
+
{/* The subjects: five boxes on one trigger, 0 → 100. The four spring boxes share the
|
|
501
|
+
catalog's 300/28 spring and differ ONLY in the authored thresholds (loose 80/2000,
|
|
502
|
+
default none, zero 0/0, tight 1e-5/1e-5 — the catalog values verbatim); the fifth
|
|
503
|
+
flies an ordinary 600ms tween carrying the tight thresholds (the pin ignores them
|
|
504
|
+
outside the spring generator). onUpdate on each is the committed-values readback. */}
|
|
505
|
+
<View style={styles.stageRow}>
|
|
506
|
+
<MotionView
|
|
507
|
+
initial={false}
|
|
508
|
+
animate={{ x: on ? TARGET : 0 }}
|
|
509
|
+
transition={{ ...SPRING, restDelta: 80, restSpeed: 2000 }}
|
|
510
|
+
onUpdate={(latest) => {
|
|
511
|
+
const value = readX(latest)
|
|
512
|
+
if (value !== null) record('loose', value)
|
|
513
|
+
}}
|
|
514
|
+
style={styles.looseBox}
|
|
515
|
+
accessibilityLabel="u7c-loose-subject"
|
|
516
|
+
/>
|
|
517
|
+
<MotionView
|
|
518
|
+
initial={false}
|
|
519
|
+
animate={{ x: on ? TARGET : 0 }}
|
|
520
|
+
transition={SPRING}
|
|
521
|
+
onUpdate={(latest) => {
|
|
522
|
+
const value = readX(latest)
|
|
523
|
+
if (value !== null) record('default', value)
|
|
524
|
+
}}
|
|
525
|
+
style={styles.defaultBox}
|
|
526
|
+
accessibilityLabel="u7c-default-subject"
|
|
527
|
+
/>
|
|
528
|
+
<MotionView
|
|
529
|
+
initial={false}
|
|
530
|
+
animate={{ x: on ? TARGET : 0 }}
|
|
531
|
+
transition={{ ...SPRING, restDelta: 0, restSpeed: 0 }}
|
|
532
|
+
onUpdate={(latest) => {
|
|
533
|
+
const value = readX(latest)
|
|
534
|
+
if (value !== null) record('zero', value)
|
|
535
|
+
}}
|
|
536
|
+
style={styles.zeroBox}
|
|
537
|
+
accessibilityLabel="u7c-zero-subject"
|
|
538
|
+
/>
|
|
539
|
+
<MotionView
|
|
540
|
+
initial={false}
|
|
541
|
+
animate={{ x: on ? TARGET : 0 }}
|
|
542
|
+
transition={{ ...SPRING, restDelta: 0.00001, restSpeed: 0.00001 }}
|
|
543
|
+
onUpdate={(latest) => {
|
|
544
|
+
const value = readX(latest)
|
|
545
|
+
if (value !== null) record('tight', value)
|
|
546
|
+
}}
|
|
547
|
+
style={styles.tightBox}
|
|
548
|
+
accessibilityLabel="u7c-tight-subject"
|
|
549
|
+
/>
|
|
550
|
+
<MotionView
|
|
551
|
+
initial={false}
|
|
552
|
+
animate={{ x: on ? TARGET : 0 }}
|
|
553
|
+
transition={{ type: 'tween', duration: 0.6, restDelta: 0.00001, restSpeed: 0.00001 }}
|
|
554
|
+
onUpdate={(latest) => {
|
|
555
|
+
const value = readX(latest)
|
|
556
|
+
if (value !== null) record('tween', value)
|
|
557
|
+
}}
|
|
558
|
+
style={styles.tweenBox}
|
|
559
|
+
accessibilityLabel="u7c-tween-subject"
|
|
560
|
+
/>
|
|
561
|
+
</View>
|
|
562
|
+
|
|
563
|
+
{ROWS.map((id) => {
|
|
564
|
+
const row = rows[id]
|
|
565
|
+
return (
|
|
566
|
+
<View key={id} style={styles.row}>
|
|
567
|
+
<Text
|
|
568
|
+
style={[
|
|
569
|
+
styles.verdict,
|
|
570
|
+
row === undefined ? styles.pending : row.pass ? styles.ok : styles.bad,
|
|
571
|
+
]}
|
|
572
|
+
>
|
|
573
|
+
{row === undefined ? '…' : row.pass ? 'PASS' : 'FAIL'}
|
|
574
|
+
</Text>
|
|
575
|
+
<View style={styles.rowBody}>
|
|
576
|
+
<Text style={styles.rowId}>{id}</Text>
|
|
577
|
+
<Text style={styles.rowDetail} numberOfLines={4}>
|
|
578
|
+
{row === undefined ? 'pending' : row.detail}
|
|
579
|
+
</Text>
|
|
580
|
+
</View>
|
|
581
|
+
</View>
|
|
582
|
+
)
|
|
583
|
+
})}
|
|
584
|
+
</ScrollView>
|
|
585
|
+
)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const styles = StyleSheet.create({
|
|
589
|
+
scroll: { flex: 1, backgroundColor: '#0b0b12' },
|
|
590
|
+
container: { paddingVertical: 72, paddingHorizontal: 8 },
|
|
591
|
+
title: { color: '#fff', fontSize: 22, fontWeight: '700', textAlign: 'center' },
|
|
592
|
+
subtitle: { color: '#8a8aa0', fontSize: 12, marginTop: 6, textAlign: 'center' },
|
|
593
|
+
banner: {
|
|
594
|
+
alignSelf: 'center',
|
|
595
|
+
paddingHorizontal: 36,
|
|
596
|
+
paddingVertical: 10,
|
|
597
|
+
borderRadius: 10,
|
|
598
|
+
marginTop: 18,
|
|
599
|
+
marginBottom: 10,
|
|
600
|
+
},
|
|
601
|
+
pass: { backgroundColor: '#1d7a3e' },
|
|
602
|
+
fail: { backgroundColor: '#a32633' },
|
|
603
|
+
running: { backgroundColor: '#7a641d' },
|
|
604
|
+
bannerText: { color: '#fff', fontSize: 22, fontWeight: '800', letterSpacing: 2 },
|
|
605
|
+
tally: { color: '#e6e6f0', fontSize: 14, fontWeight: '700', textAlign: 'center' },
|
|
606
|
+
tallyDetail: { color: '#f87171', fontSize: 11, marginTop: 3, textAlign: 'center' },
|
|
607
|
+
status: {
|
|
608
|
+
color: '#c9c9d8',
|
|
609
|
+
fontSize: 11,
|
|
610
|
+
marginTop: 8,
|
|
611
|
+
textAlign: 'center',
|
|
612
|
+
fontFamily: 'Menlo',
|
|
613
|
+
},
|
|
614
|
+
stageRow: {
|
|
615
|
+
flexDirection: 'row',
|
|
616
|
+
marginTop: 18,
|
|
617
|
+
alignItems: 'center',
|
|
618
|
+
justifyContent: 'space-evenly',
|
|
619
|
+
},
|
|
620
|
+
// The web oracle's palette (e2e/fixtures/examples.tsx U7c card).
|
|
621
|
+
looseBox: { width: 56, height: 56, backgroundColor: '#0ea5e9', borderRadius: 10 },
|
|
622
|
+
defaultBox: { width: 56, height: 56, backgroundColor: '#64748b', borderRadius: 10 },
|
|
623
|
+
zeroBox: { width: 56, height: 56, backgroundColor: '#f59e0b', borderRadius: 10 },
|
|
624
|
+
tightBox: { width: 56, height: 56, backgroundColor: '#7c3aed', borderRadius: 10 },
|
|
625
|
+
tweenBox: { width: 56, height: 56, backgroundColor: '#22c55e', borderRadius: 10 },
|
|
626
|
+
row: { flexDirection: 'row', marginTop: 10 },
|
|
627
|
+
verdict: { width: 52, fontSize: 12, fontWeight: '800', paddingTop: 1 },
|
|
628
|
+
ok: { color: '#4ade80' },
|
|
629
|
+
bad: { color: '#f87171' },
|
|
630
|
+
pending: { color: '#8a8aa0' },
|
|
631
|
+
rowBody: { flex: 1 },
|
|
632
|
+
rowId: { color: '#e6e6f0', fontSize: 13, fontWeight: '600' },
|
|
633
|
+
rowDetail: { color: '#6f6f85', fontSize: 11, marginTop: 2 },
|
|
634
|
+
})
|