fix: stale-frame race, stuck verdict buttons, and 3 more frontend defects
seance.tsx / JudgmentPanel.tsx: neither `ritual_complete` nor `judgment_result` carries an entity id, and both were applied unconditionally — so a response still in flight when the seeker summoned a fresh entity landed on whatever entity happened to be current when it arrived, leaking the *previous* entity's hidden traits into the new one's revealed-traits UI. Both frames are now gated on our still waiting for one (ritual.status === 'in_progress' / judgmentPending), which the 'entity' case clears the moment a new presence arrives, so a late answer for the old entity is dropped instead of misattributed. JudgmentPanel also had `pending` in component-local state that only cleared when judgmentResult became a *new* truthy object. If the entity changed while judgmentResult was already null, the reset was a no-op (null === null) and pending stayed stuck, permanently disabling all four verdict buttons. It now reads the shared judgmentPending flag, which the reducer resets. coldSpot.ts: severity was ungated by `warm` while isColdSpot/ isPressureAnomaly were correctly gated. ColdSpotPanel feeds severity straight into the composite disturbance gauge with no boolean gate of its own, so a freshly-paired device could show "disturbance rising" off its 2nd reading — exactly what the minSamples warm-up exists to prevent. PlanchetteBoard.tsx: the first GOODBYE deadline was a bare randomBetween(120,300) compared against `t`, which is seconds since performance.timeOrigin (page load), not since mount. Every later reschedule correctly offsets from `t`. On a tab open >5min before the board mounted (or any remount via navigation), t was already past the deadline and the planchette snapped to GOODBYE on the first frame. sdr.ts: close() and setFrequency() inside the sweep loop were not wrapped in withTimeout despite the file's own header claiming every stalling USB call is. A dongle going unresponsive mid-sweep or during teardown hung forever — the same silent-hang symptom withTimeout was added to eliminate. InventoryPanel.tsx: essence was decremented client-side using a possibly stale fallback price and never reconciled. The server already returns the real post-purchase balance in PurchaseOut; use it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -194,7 +194,12 @@ export function applyTemperatureReading(
|
||||
)
|
||||
const drop = deviation !== null ? Math.max(0, -deviation) : 0
|
||||
const isColdSpot = warm && drop >= COLD_SPOT_DROP_C
|
||||
const severity = clamp01(drop / (COLD_SPOT_DROP_C * 3))
|
||||
// Gated by `warm` for the same reason isColdSpot is: pre-warm-up
|
||||
// deviations are against a baseline that hasn't had a real chance to
|
||||
// average out sensor noise, and severity feeds the composite disturbance
|
||||
// gauge directly (ColdSpotPanel.tsx), which has no boolean gate of its
|
||||
// own to catch an ungated value here.
|
||||
const severity = warm ? clamp01(drop / (COLD_SPOT_DROP_C * 3)) : 0
|
||||
return { state: next, deviation, isColdSpot, severity }
|
||||
}
|
||||
|
||||
@@ -252,7 +257,9 @@ export function applyPressureReading(
|
||||
)
|
||||
const swing = deviation !== null ? Math.abs(deviation) : 0
|
||||
const isPressureAnomaly = warm && swing >= PRESSURE_SWING_HPA
|
||||
const severity = clamp01(swing / (PRESSURE_SWING_HPA * 3))
|
||||
// See applyTemperatureReading's comment: gated by `warm` so the composite
|
||||
// disturbance gauge can't be driven by a pre-warm-up baseline swing.
|
||||
const severity = warm ? clamp01(swing / (PRESSURE_SWING_HPA * 3)) : 0
|
||||
const direction: 'rise' | 'drop' = deviation !== null && deviation < 0 ? 'drop' : 'rise'
|
||||
return { state: next, deviation, isPressureAnomaly, severity, direction }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user