From c1747dc1ce01444ae6d1b3bf1fc6a186dd3540ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E7=BF=94=E5=AE=87?= Date: Wed, 20 May 2026 09:29:28 +0800 Subject: [PATCH] fix(sit): reset accumulator after threshold fire MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prior path used a lastFired-timestamp dedupe but never reset the sitAccumActiveSec counter, so after the first 45min alert the panel kept showing "已坐 90 分钟" / "135 分钟" — counter monotonic across cycles. The UX intent is each ack starts a fresh cycle. Now: on fire, alert + reset counter to 0. Next triggerSec of sitting kicks a fresh alert. Reset is the simpler dedupe — there's no physical way to re-fire without accumulating triggerSec from 0. Co-Authored-By: Claude Opus 4.7 (1M context) --- Sources/engine/WorkerStore.swift | 38 +++++++++++++++++++------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/Sources/engine/WorkerStore.swift b/Sources/engine/WorkerStore.swift index 178a2a1..398e408 100644 --- a/Sources/engine/WorkerStore.swift +++ b/Sources/engine/WorkerStore.swift @@ -245,23 +245,31 @@ final class WorkerStore: ObservableObject { sitElapsedSec = sitAccumActiveSec - // Threshold notification, deduped by triggerSec. + // Threshold notification — fire once, then reset the + // accumulator so the next triggerSec of sitting kicks a + // FRESH cycle. The prior dedupe-by-lastFired path kept the + // counter monotonically growing, so the panel showed "已坐 + // 90 分钟" / "已坐 135 分钟" instead of restarting each + // cycle. Reset is the simpler dedupe: physically can't + // re-fire until the user accumulates another triggerSec. let triggerSec = sitTriggerMin * 60 if triggerSec > 0 && sitElapsedSec >= triggerSec { - let lastFired = defaults.double(forKey: K.sitLastNotified) - let nowTs = Date().timeIntervalSince1970 - if nowTs - lastFired >= Double(triggerSec) { - defaults.set(nowTs, forKey: K.sitLastNotified) - WorkerNotificationCenter.shared.notify( - title: "该起来动一下了", - body: "你已连续坐了 \(sitTriggerMin) 分钟,起身喝口水吧。" - ) - // 1Hz × 15 ticks of system Morse tone — the UN - // notification ding is too easy to miss in a meeting, - // so the sit alert gets a louder, longer signal. - SoundPlayer.shared.playMorseSitAlert(count: 15) - WorkerDebugLog.write("sit threshold notification + morse alert fired (\(sitElapsedSec)s)") - } + WorkerNotificationCenter.shared.notify( + title: "该起来动一下了", + body: "你已连续坐了 \(sitTriggerMin) 分钟,起身喝口水吧。" + ) + // 1Hz × 15 ticks of system Morse tone — the UN + // notification ding is too easy to miss in a meeting, + // so the sit alert gets a louder, longer signal. + SoundPlayer.shared.playMorseSitAlert(count: 15) + WorkerDebugLog.write("sit threshold fired @ \(sitElapsedSec)s — resetting accumulator") + + // Reset for next cycle. Update lastFired for telemetry + // even though it's no longer the dedupe gate. + sitAccumActiveSec = 0 + sitElapsedSec = 0 + defaults.set(0, forKey: K.sitAccumActive) + defaults.set(Date().timeIntervalSince1970, forKey: K.sitLastNotified) } // Persist accumulator once every N ticks (cheap & resilient).