Files
FreeTLab bccc5c48b8
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
docs(openspec): add SDD artifact trail for recent alarm and EQ changes
Persist the exploration, proposal, spec, design, tasks, and
verify/archive reports produced during the multi-device EQ,
alarm-countdown, and notification-visual-polish SDD cycles.
2026-07-04 12:42:11 +02:00

127 lines
6.3 KiB
Markdown

# Alarm Pre-Notice Countdown Specification
## Purpose
True per-minute live countdown for the 30-minute alarm pre-notice notification, mirroring the proven snooze-countdown repeating-alarm pattern. Replaces the current frozen, single-shot pre-notice ("30 min" forever) with a self-healing chain that updates every minute (29, 28, ... 1) until the real alarm fires.
## Requirements
### Requirement: First Pre-Notice Post
The system MUST post the initial pre-notice notification at `triggerAtMillis - 30min` (T-30min), unchanged from current behavior.
#### Scenario: First post at T-30min
- GIVEN an alarm scheduled to fire at time T
- WHEN the system clock reaches T-30min
- THEN an exact alarm fires `ACTION_PRE_NOTICE`
- AND a notification showing "30 min" remaining is posted using `notificationIdForAlarm(id)`
### Requirement: Per-Minute Tick Re-Arm
After posting a pre-notice notification, the system MUST re-arm itself to fire again at the next minute boundary, reusing `ACTION_PRE_NOTICE` for both the first post and every subsequent tick (no separate action constant).
#### Scenario: Tick re-arms next minute
- GIVEN `ACTION_PRE_NOTICE` has just fired and posted a notification with remaining minutes `R` where `R > 1`
- WHEN the post completes
- THEN `AlarmScheduler.armNextPreNoticeCountdownTick(id, R)` arms a new exact alarm at `triggerAtMillis - (R - 1) * 60_000L`
- AND the new alarm uses requestCode slot 9
#### Scenario: Tick updates notification content
- GIVEN the tick chain is active for alarm `id`
- WHEN a re-armed `ACTION_PRE_NOTICE` fires at a later minute boundary
- THEN the notification at `notificationIdForAlarm(id)` is updated (not duplicated) to show the new remaining-minutes value
### Requirement: Self-Healing Minute Computation
Each tick MUST compute remaining minutes from the current wall-clock time relative to `triggerAtMillis`, using `ceilMinutes()`, rather than decrementing a stored counter.
#### Scenario: Normal tick sequence
- GIVEN consecutive ticks fire close to their scheduled minute boundaries
- WHEN each tick computes remaining minutes via `ceilMinutes(triggerAtMillis - now)`
- THEN the displayed sequence is 29, 28, 27, ... 1 with no manual decrement state
#### Scenario: Missed tick self-heals by jumping, not crashing
- GIVEN the OS delays or coalesces a scheduled tick (e.g. Doze quota) so the receiver fires late
- WHEN the delayed tick recomputes remaining minutes from wall clock
- THEN the displayed countdown jumps forward to the correct current value (e.g. skips from 15 to 12) instead of crashing, looping, or showing a stale/negative value
### Requirement: Self-Stop at Final Minute
The tick chain MUST stop re-arming once computed remaining minutes is `<= 1`; the final minute is left to the real fire alarm, not a tick.
#### Scenario: Chain stops before final minute
- GIVEN a tick fires and computes remaining minutes `R <= 1`
- WHEN the tick finishes posting/updating the notification
- THEN no further `armNextPreNoticeCountdownTick` call is made
- AND the alarm's existing `setAlarmClock` fire alarm remains the sole next trigger
### Requirement: Consistent Rounding via ceilMinutes
The system MUST use `ceilMinutes()` for pre-notice remaining-minutes computation, replacing the prior floor-based `computeRemainingMinutes()`, for consistency with the snooze-countdown chain.
#### Scenario: Rounding matches snooze countdown
- GIVEN identical time-remaining deltas for a pre-notice tick and a snooze-countdown tick
- WHEN both compute their displayed minute value
- THEN both use `ceilMinutes()` and produce the same rounding result for equivalent inputs
### Requirement: Tick Chain Cancellation
The system MUST tear down the pending pre-notice tick alarm at all of the following sites: `cancelAlarm()`, the `scheduleSpec` no-next-trigger branch, the snooze-transition branch, `ACTION_SKIP_NEXT`, and `ACTION_POSTPONE_NEXT`. No site may leave an orphaned repeating alarm.
#### Scenario: Full alarm cancellation tears down tick chain
- GIVEN a pre-notice tick chain is active for alarm `id`
- WHEN `cancelAlarm(id)` is called
- THEN the pending pre-notice tick `PendingIntent` (slot 9) is cancelled
- AND no further `ACTION_PRE_NOTICE` ticks fire for `id`
#### Scenario: No-next-trigger reschedule cancels tick chain
- GIVEN a pre-notice tick chain is active for alarm `id`
- WHEN `scheduleSpec` recomputes and finds no next trigger time for `id`
- THEN the pending pre-notice tick is cancelled in the same branch that already cancels the single-shot pre-notice and snooze-countdown pendings
#### Scenario: Snooze transition cancels pre-notice tick chain
- GIVEN a pre-notice tick chain is active for alarm `id`
- WHEN the user snoozes the alarm, transitioning it into snooze-countdown mode
- THEN the pre-notice tick chain is cancelled
- AND no pre-notice notification or alarm remains pending while snooze-countdown is active
#### Scenario: Skip-next action cancels tick chain
- GIVEN a pre-notice tick chain is active for alarm `id`
- WHEN the user taps "Skip" on the pre-notice notification, triggering `ACTION_SKIP_NEXT`
- THEN the pending pre-notice tick alarm for `id` is cancelled
- AND no further pre-notice ticks fire for the skipped occurrence
#### Scenario: Postpone-next action cancels tick chain
- GIVEN a pre-notice tick chain is active for alarm `id`
- WHEN the user taps "Postpone" on the pre-notice notification, triggering `ACTION_POSTPONE_NEXT`
- THEN the pending pre-notice tick alarm for `id` is cancelled
- AND no further pre-notice ticks fire for the postponed occurrence
### Requirement: Notification ID Reuse and Mutual Exclusivity with Snooze
The pre-notice tick chain MUST reuse the same notification ID (`notificationIdForAlarm(id)`) as snooze-countdown, and the two chains MUST remain mutually exclusive in time for the same alarm `id`.
#### Scenario: Pre-notice and snooze-countdown never run concurrently
- GIVEN alarm `id` has an active pre-notice tick chain
- WHEN the alarm is not snoozed
- THEN no snooze-countdown chain is scheduled for `id` concurrently, preserving the existing `scheduleSpec` branch invariant on `snoozeUntilMillis`
#### Scenario: Notification updates in place, no duplicate
- GIVEN a pre-notice tick posts an update for alarm `id`
- WHEN the notification ID matches a previously posted pre-notice or snooze-countdown notification for the same `id`
- THEN the system tray shows a single updated notification, not a duplicate entry