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.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user