-
Notifications
You must be signed in to change notification settings - Fork 1
Schedule Preparation SRD
This document defines the software requirements for preparing for a schedule from the moment a schedule becomes eligible for preparation until the preparation flow ends. It includes official start, early start, delayed entry, runtime ticking, step completion, finish, late/early result, persistence, and cleanup behavior.
This SRD covers:
- Schedule selection and preparation-window classification.
- User entry from Home, notification,
/scheduleStart, and/alarmScreen. - Early start before the calculated preparation start time.
- Official preparation start at the calculated preparation start time.
- Runtime preparation timer behavior.
- Step skip, step completion, all-steps-done continuation, and finish.
- Late/early result calculation.
- Persistence and recovery after app restart.
- Cache invalidation after schedule mutation, finish, end, or schedule switch.
This SRD does not cover schedule creation form field validation except where created schedule data affects preparation timing.
| Term | Meaning |
|---|---|
scheduleTime |
Appointment time. |
moveTime |
Travel time required before appointment. |
scheduleSpareTime |
User buffer time before appointment. Null is treated as zero. |
preparation.totalDuration |
Sum of all preparation step durations. |
totalDuration |
moveTime + preparation.totalDuration + scheduleSpareTime. |
preparationStartTime |
scheduleTime - totalDuration. |
| Leave threshold |
scheduleTime - moveTime - scheduleSpareTime. |
| Early start | User begins preparation before preparationStartTime. |
| Official start | System begins the start prompt at preparationStartTime. |
| Late entry | User/app enters after preparationStartTime but before scheduleTime. |
latenessTime |
Positive minutes late at finish, or 0 when not late. |
- User: starts early, starts at prompt, skips steps, finishes, continues after all steps, leaves the screen.
- Schedule runtime:
ScheduleBloc, timers, stream subscription, cache restore, notification dispatch. - Router: maps runtime state to
/scheduleStart,/alarmScreen,/earlyLate, or/home. - Persistence layer: stores early-start sessions and timed preparation snapshots.
- Backend/use cases: provide nearest upcoming schedule and persist schedule completion.
The active preparation schedule comes from GetNearestUpcomingScheduleUseCase. The runtime holds only one current schedule at a time. Persisted local state can restore progress only if it matches the current schedule fingerprint.
Relevant code:
lib/domain/entities/schedule_with_preparation_entity.dartlib/domain/entities/preparation_with_time_entity.dartlib/presentation/app/bloc/schedule/schedule_bloc.dartlib/presentation/alarm/screens/schedule_start_screen.dartlib/presentation/alarm/screens/alarm_screen.dartlib/presentation/home/utils/today_tile_navigation.dartlib/presentation/shared/router/go_router.dart
- The system shall calculate
preparationStartTimeas:
scheduleTime - moveTime - preparation.totalDuration - scheduleSpareTime
- The system shall calculate remaining leave time as:
scheduleTime - now - moveTime - scheduleSpareTime
- The system shall classify the active schedule at runtime:
| Runtime condition | Required state |
|---|---|
| No schedule | notExists |
scheduleTime < now |
notExists |
| Restorable early-start session exists | started(isEarlyStarted=true) |
now < preparationStartTime |
upcoming |
preparationStartTime < now < scheduleTime |
ongoing |
| User starts preparation | started |
- At the exact official start boundary, the system shall transition from
upcomingtostartedthroughScheduleStarted.
flowchart TD
A["Schedule stream emits nearest upcoming schedule"] --> B{"Schedule exists and scheduleTime >= now?"}
B -- No --> C["Clear persisted state if an id is known"]
C --> D["Emit notExists"]
B -- Yes --> E{"Schedule id changed?"}
E -- Yes --> F["Clear previous schedule session and snapshot"]
E -- No --> G["Use incoming id as current id"]
F --> G
G --> H{"Early-start session exists?"}
H -- Yes --> I["Load timed snapshot if valid"]
H -- No --> J{"now < preparationStartTime?"}
J -- Yes --> K["Clear timed snapshot for this id"]
K --> L["Emit upcoming"]
L --> M["Arm official start timer"]
J -- No --> I
I --> N{"Snapshot exists and fingerprint matches?"}
N -- Yes --> O["Restore snapshot and fast-forward by now - savedAt"]
N -- No --> P["Use canonical incoming preparation"]
O --> Q{"Early-start session exists?"}
P --> Q
Q -- Yes --> R["Emit started(isEarlyStarted=true)"]
Q -- No --> S{"now inside preparation window?"}
S -- Yes --> T["Emit ongoing"]
S -- No --> L
R --> U["Start preparation timer"]
T --> U
U --> V["Tick each second and save snapshots"]
V --> W{"All steps done?"}
W -- No --> X["Continue current/next step"]
X --> V
W -- Yes --> Y["Show completion dialog"]
Y --> Z{"User finishes?"}
Z -- No --> AA["Continue preparation screen with leave countdown"]
AA --> Z
Z -- Yes --> AB["Compute lateness and call finish"]
AB --> AC["Clear timers, session, snapshot"]
AC --> AD["Emit notExists"]
AD --> AE["Navigate early/late result or home"]
M --> AF["Timer fires at preparationStartTime"]
AF --> AG{"Already early-started same schedule?"}
AG -- Yes --> AH["No-op"]
AG -- No --> AI["Emit started and push /scheduleStart"]
AI --> U
Preconditions:
- Nearest schedule exists.
-
now < preparationStartTime. - No early-start session exists.
Requirements:
- Runtime shall emit
upcoming. - Runtime shall clear any timed snapshot for the schedule to avoid restoring stale pre-start progress.
- Runtime shall arm a timer for
preparationStartTime. - When the timer fires, runtime shall dispatch
ScheduleStarted. - If this schedule has not already been early-started, runtime shall emit
started, push/scheduleStart, and start the preparation timer.
Preconditions:
- Nearest schedule exists.
-
now < preparationStartTime. - Home tile navigation resolves to
/scheduleStartwith the early-start prompt.
Requirements:
-
/scheduleStartshall show an early-start prompt that explains how much earlier the user is starting. - Primary action shall dispatch
SchedulePreparationStarted. - Runtime shall mark an early-start session for the schedule id.
- Runtime shall cancel the official start timer.
- Runtime shall emit
started(isEarlyStarted=true). - Runtime shall save an initial timed-preparation snapshot.
- UI shall navigate to
/alarmScreen. - The preparation timer shall start at zero elapsed time, not at
now - preparationStartTime.
Preconditions:
- App receives or opens from a reminder about five minutes before preparation should start.
-
now < preparationStartTime.
Requirements:
- The program shall treat this as the same early-start flow as Flow B.
- The program should not require a separate
fiveMinutesprompt variant for runtime behavior. - The prompt should still present the same decision:
- Primary action: "Start preparing now".
- Secondary action: "Not now".
- Primary action shall dispatch
SchedulePreparationStartedand navigate to/alarmScreen. - Secondary action shall navigate to
/home. - Any existing legacy route payload such as
isFiveMinutesBefore=trueshall be treated as an early-start prompt for backward compatibility only.
Preconditions:
- Nearest schedule exists.
- Runtime state is
upcoming. - User/route opens
/alarmScreenbefore official start.
Requirements:
-
/alarmScreenshall not show an indefinite loading spinner. - It shall show an early-start-ready screen with schedule name, countdown, start button, and home button.
- Start button shall dispatch
SchedulePreparationStarted. - Home button shall navigate to
/homewithout starting preparation.
Preconditions:
- User taps a local or background notification.
- Notification payload has
typestarting withschedule_orpreparation_, or it containsscheduleId.
Requirements:
- Notification handler shall push
/alarmScreenwhen the notification is a general schedule/preparation notification. - Notification handler shall push the early-start prompt when the notification is a five-minute reminder.
- Alarm screen shall request schedule subscription on entry.
- Runtime shall classify the nearest upcoming schedule normally.
- If the schedule is still
upcoming, alarm shall show early-start-ready UI. - If the schedule is
ongoingorstarted, alarm shall show active preparation UI. - If no active schedule exists, alarm shall fall back through
notExistshandling and navigate home.
Preconditions:
- Runtime transitions through
ScheduleStarted. - User is not early-started.
Requirements:
- Runtime shall emit
started. - Runtime shall push
/scheduleStart. - Default prompt primary action shall navigate to
/alarmScreen. - Default prompt primary action shall not dispatch
SchedulePreparationStarted; the runtime has already started throughScheduleStarted.
Preconditions:
- Nearest schedule exists.
-
preparationStartTime < now < scheduleTime. - No early-start session exists.
Requirements:
- Runtime shall emit
ongoing. - Runtime shall start the preparation timer.
- Runtime shall immediately catch up preparation progress by:
now - preparationStartTime - alreadyElapsedPreparation
- Catch-up elapsed time shall complete earlier steps and partially progress the current step as needed.
- Step-change notification shall be sent only when the current step changes to a non-first step that has not already been notified for this schedule.
Preconditions:
- Early-start session exists for incoming schedule id.
- App restarts or schedule stream re-emits.
Requirements:
- Runtime shall restore the session by emitting
started(isEarlyStarted=true). - Runtime shall load a timed snapshot when available.
- Runtime shall use the snapshot only if its fingerprint equals the incoming schedule fingerprint.
- Runtime shall fast-forward restored preparation by
now - snapshot.savedAt. - Runtime shall start the preparation timer.
-
/scheduleStartbuilder shall resolve directly toAlarmScreenwhenisEarlyStarted=trueto prevent route bounce.
Preconditions:
- No early-start session exists.
-
now >= preparationStartTime. - Valid timed snapshot exists.
Requirements:
- Runtime shall restore the snapshot when fingerprint matches.
- Runtime shall fast-forward by
now - snapshot.savedAt. - Runtime shall emit
ongoingif still beforescheduleTime. - Runtime shall start the preparation timer.
Preconditions:
- Timed snapshot exists.
- Incoming schedule fingerprint differs from snapshot fingerprint.
Requirements:
- Runtime shall clear timed snapshot.
- Runtime shall clear early-start session for that schedule id.
- Runtime shall use canonical incoming preparation from the schedule stream.
- Runtime shall classify the schedule again using current time.
Fingerprint inputs:
scheduleTimemoveTimescheduleSpareTime- Preparation step id
- Preparation step name
- Preparation step duration
- Preparation step next id
Preconditions:
- Runtime state is
ongoingorstarted. - Current preparation step exists.
Requirements:
- Skip action shall mark only the current step as done.
- Runtime shall emit updated schedule state.
- Runtime shall save a timed snapshot immediately.
- Next tick shall continue from the next incomplete step.
Preconditions:
- Runtime state is
ongoingorstarted. -
preparation.isAllStepsDone == true.
Requirements:
- Alarm screen shall show completion dialog once per schedule completion state.
- Dialog shall offer finish and continue.
- The system shall not automatically finish the schedule when all preparation steps are done.
- The system shall treat preparation completion and lateness as separate concerns.
- If all preparation steps are done before the leave threshold, the user is still on time.
- If the user continues, alarm screen shall keep showing live leave countdown.
- While continuing before the leave threshold, UI should communicate "ready / time remaining to leave" rather than "late".
- If the user becomes late while continuing, UI shall switch to late-continue mode.
- Finishing remains available after continuing.
Preconditions:
- Runtime state has an active schedule.
- User taps finish from alarm screen or completion dialog.
Requirements:
- UI shall compute
timeRemainingBeforeLeaving. - If remaining leave time is negative, UI shall send positive late minutes to
ScheduleFinished. - If remaining leave time is zero or positive, UI shall send
0toScheduleFinished. - Runtime shall call
FinishScheduleUseCase(scheduleId, latenessTime). - Runtime shall cancel preparation and schedule-start timers.
- Runtime shall clear timed snapshot and early-start session.
- Runtime shall emit
notExists. - Alarm listener shall navigate to
/earlyLatewhen finish was user-initiated and late/early data is pending. - Otherwise, alarm listener shall navigate to
/home.
Preconditions:
- Active stream emits
null, or schedule time is already before now.
Requirements:
- Runtime shall clear persisted state for the stale or current schedule id when known.
- Runtime shall emit
notExists. - Runtime shall clear current id, active early-start id, snapshot timestamp, and notification tracking.
- Alarm listener shall return the user to
/homeunless a finish navigation is pending.
Preconditions:
- Current schedule id differs from newly emitted schedule id.
Requirements:
- Runtime shall clear persisted state for the previous schedule id.
- Runtime shall remove notification tracking for the previous schedule id.
- Runtime shall set the incoming schedule id as current.
- Runtime shall classify the incoming schedule from scratch.
stateDiagram-v2
[*] --> initial
initial --> notExists: no schedule / past schedule
initial --> upcoming: now before preparationStartTime
initial --> ongoing: now inside preparation window
initial --> started: restored early-start session
upcoming --> started: official timer fires
upcoming --> started: early start action
upcoming --> notExists: schedule removed / ended
ongoing --> started: official ScheduleStarted
ongoing --> notExists: finish / removed / ended
started --> started: tick / skip / snapshot save
started --> started: official timer after early start is ignored
started --> notExists: finish / removed / ended / schedule switch
notExists --> upcoming: future schedule appears
notExists --> ongoing: schedule appears inside prep window
| User/runtime state | Route behavior |
|---|---|
Home tile + upcoming + schedule exists |
Navigate to /scheduleStart with the early-start prompt. |
| Five-minute reminder notification | Navigate to /scheduleStart with the early-start prompt. |
Home tile + ongoing
|
Navigate to /alarmScreen. |
Home tile + started
|
Navigate to /alarmScreen. |
Home tile + initial/notExists/no schedule |
No navigation target. |
| Runtime official start | Push /scheduleStart. |
/scheduleStart while isEarlyStarted=true
|
Build AlarmScreen directly. |
/alarmScreen while upcoming
|
Show early-start-ready UI. |
| Finish success with pending result | Navigate to /earlyLate. |
notExists without pending result |
Navigate to /home. |
The program should model start prompts with only two semantic variants:
enum ScheduleStartPromptVariant {
officialStart,
earlyStart,
}Requirements:
-
officialStartmeans the calculated preparation time has arrived and the user should start preparing. -
earlyStartmeans the user is being offered the choice to prepare before the calculated preparation time. - Five-minute reminder entry is not a separate runtime variant; it is an early-start source.
- Legacy values such as
fiveMinutesorisFiveMinutesBefore=truemay be accepted at route boundaries, but shall normalize toearlyStart.
sequenceDiagram
participant Stream as Nearest schedule stream
participant Bloc as ScheduleBloc
participant Store as Local persistence
participant Router as Router
participant Start as ScheduleStart route
participant Alarm as Alarm route
participant Finish as FinishScheduleUseCase
Stream->>Bloc: ScheduleUpcomingReceived(schedule)
Bloc->>Store: Check early-start session
alt Future preparation and no early start
Bloc->>Store: Clear timed snapshot
Bloc-->>Router: State upcoming
Bloc->>Bloc: Arm official start timer
else Early-start session exists
Bloc->>Store: Load valid snapshot
Bloc-->>Router: State started(isEarlyStarted=true)
Bloc->>Bloc: Start preparation timer
else Inside preparation window
Bloc->>Store: Load valid snapshot
Bloc-->>Router: State ongoing
Bloc->>Bloc: Catch up elapsed time
end
alt User starts early
Start->>Bloc: SchedulePreparationStarted
Bloc->>Store: Save early-start session
Bloc->>Store: Save timed snapshot
Start->>Router: go("/alarmScreen")
end
Bloc->>Alarm: Tick updated preparation state
Alarm->>Bloc: ScheduleStepSkipped / ScheduleFinished
Bloc->>Store: Save or clear snapshot
Bloc->>Finish: Finish schedule with latenessTime
Bloc-->>Router: State notExists
Router->>Alarm: go("/earlyLate") or go("/home")
Storage key:
early_start_session_<scheduleId>
Payload:
{
"startedAt": 1773997200000
}Requirements:
- Save when user dispatches
SchedulePreparationStarted. - Load during schedule resolution.
- Clear on finish, schedule end, schedule deletion/null, fingerprint mismatch, or schedule switch.
Storage key:
preparation_with_time_<scheduleId>
Payload fields:
savedAtscheduleFingerprintsteps[].idsteps[].namesteps[].timesteps[].nextIdsteps[].elapsedsteps[].isDone
Requirements:
- Save immediately on early start.
- Save immediately when a step is skipped.
- Save immediately when the current step changes.
- Save periodically, but not more often than every five seconds unless forced.
- Restore only when schedule fingerprint matches.
- Fast-forward after restore by elapsed wall-clock time since
savedAt. - Clear when the schedule is future and no early-start session exists.
- Clear on finish, schedule end, schedule deletion/null, fingerprint mismatch, or schedule switch.
flowchart TD
A["User taps finish"] --> B["Compute remaining = scheduleTime - now - moveTime - spareTime"]
B --> C{"remaining < 0?"}
C -- Yes --> D["latenessTime = abs(remaining.inMinutes)"]
C -- No --> E["latenessTime = 0"]
D --> F["Dispatch ScheduleFinished(latenessTime)"]
E --> F
F --> G["FinishScheduleUseCase"]
G --> H["Navigate to /earlyLate with seconds and isLate"]
Requirements:
- Step completion does not determine lateness.
- Lateness is determined only by the leave threshold at finish time.
- Finishing all steps early can still become late if the user chooses to continue and crosses the leave threshold.
- Completing all steps after the expected preparation duration is not late by itself when there is still time before the leave threshold.
-
earlyLateTimepassed to/earlyLateshall preserve seconds and sign from remaining leave time. - Backend finish payload shall use minutes, with
0for non-late finish.
- Runtime shall initialize notification tracking per schedule id.
- Runtime shall notify when current step changes to a new non-first step.
- Runtime shall not notify for the first step.
- Runtime shall not notify the same step id more than once per schedule id.
- Runtime shall clear notification tracking when schedule ends or switches.
The following decisions were answerable from the codebase and are treated as the recommended answers.
| Question | Recommended answer |
|---|---|
| Should early start consume the elapsed time between early start and official start? | Yes, but from early-start moment forward. It must not fast-forward by now - preparationStartTime before official start. |
Should official start still show /scheduleStart after an early start? |
No. It is a no-op for the same early-started schedule. |
| Should all steps done automatically finish the schedule? | No. Show a completion dialog; user chooses finish or continue. |
| Should step completion decide late status? | No. Late status is calculated only at finish time. |
| Should an old snapshot restore after schedule details change? | No. Fingerprint mismatch clears snapshot and early-start session. |
Should opening /alarmScreen before official start be allowed? |
Yes. It shows early-start-ready UI. |
| Should the app recover active early-start after restart? | Yes. Restore early-start session and valid snapshot, then fast-forward. |
| Should a user be able to leave the preparation screen without finishing? | Yes. Close/leave confirmation can return to home; persisted state enables restoration. |
- Given no active schedule, runtime emits
notExists. - Given a past schedule, runtime clears persisted state and emits
notExists. - Given a future schedule before preparation start, runtime emits
upcoming. - Given a schedule inside preparation window, runtime emits
ongoingand catches up elapsed preparation. - Given a valid early-start session, runtime emits
started(isEarlyStarted=true).
- Home tile for
upcomingschedule opens early-start prompt. - Early-start prompt primary action starts preparation and navigates to alarm.
- Early start saves session and snapshot.
- Official timer does not reopen start prompt after early start.
- Route builder avoids bounce by resolving
/scheduleStartto alarm when early-started.
- Preparation timer ticks every second while active.
- Current step changes when elapsed time reaches step duration.
- Skipped step is marked done and saved immediately.
- Snapshot saves are throttled except forced saves.
- Finish calls
FinishScheduleUseCaseexactly once per finish action. - Non-late finish sends
latenessTime=0. - Late finish sends positive late minutes.
- Finish clears timers, early-start session, and timed snapshot.
- Finish redirects to
/earlyLatewith seconds and late flag.
- Valid snapshot restores elapsed step state.
- Invalid snapshot clears and uses canonical schedule preparation.
- Schedule switch clears previous persisted state.
- Schedule mutation prevents stale progress resurrection.
These are not blockers for the current implementation, but they should be decided before expanding product behavior.
- Late minute rounding: UI currently uses
abs(inMinutes), which floors partial late minutes. Decide whether backend lateness should floor or ceil partial minutes. - Exact-boundary classification:
_isPreparationOnGoinguses strictstart.isBefore(now), while the official timer handles boundary start. Keep tests around exact equality. - Multiple schedules near the same time: current runtime assumes one nearest upcoming schedule. Product should define tie-breaking on the backend/use case.
- Notification permission failure: runtime attempts step notifications through
NotificationService; product should define fallback UX if permission is unavailable. - Leaving without finishing: state persists, but product should decide whether returning home should show a stronger "preparation still active" affordance.
Primary tests:
test/presentation/app/bloc/schedule/schedule_bloc_test.darttest/presentation/alarm/screens/preparation_flow_widget_test.darttest/domain/entities/preparation_timing_entity_test.darttest/presentation/home/utils/today_tile_navigation_test.dart
Recommended additions:
- Boundary finish where remaining leave time is exactly zero.
- Partial-minute late finish rounding decision after product alignment.
- Schedule switch while a completion dialog is visible.
- Notification permission denied during step change.