Skip to content

Schedule Preparation SRD

jjoonleo edited this page May 5, 2026 · 1 revision

Schedule Preparation Flow 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.

Scope

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.

Terms

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.

Actors

  • 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.

Source of Truth

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.dart
  • lib/domain/entities/preparation_with_time_entity.dart
  • lib/presentation/app/bloc/schedule/schedule_bloc.dart
  • lib/presentation/alarm/screens/schedule_start_screen.dart
  • lib/presentation/alarm/screens/alarm_screen.dart
  • lib/presentation/home/utils/today_tile_navigation.dart
  • lib/presentation/shared/router/go_router.dart

Timing Requirements

  1. The system shall calculate preparationStartTime as:
scheduleTime - moveTime - preparation.totalDuration - scheduleSpareTime
  1. The system shall calculate remaining leave time as:
scheduleTime - now - moveTime - scheduleSpareTime
  1. 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
  1. At the exact official start boundary, the system shall transition from upcoming to started through ScheduleStarted.

End-to-End Flow

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
Loading

Entry Flows

Flow A: Upcoming Schedule, User Does Nothing

Preconditions:

  • Nearest schedule exists.
  • now < preparationStartTime.
  • No early-start session exists.

Requirements:

  1. Runtime shall emit upcoming.
  2. Runtime shall clear any timed snapshot for the schedule to avoid restoring stale pre-start progress.
  3. Runtime shall arm a timer for preparationStartTime.
  4. When the timer fires, runtime shall dispatch ScheduleStarted.
  5. If this schedule has not already been early-started, runtime shall emit started, push /scheduleStart, and start the preparation timer.

Flow B: Upcoming Schedule, User Starts Early From Home

Preconditions:

  • Nearest schedule exists.
  • now < preparationStartTime.
  • Home tile navigation resolves to /scheduleStart with the early-start prompt.

Requirements:

  1. /scheduleStart shall show an early-start prompt that explains how much earlier the user is starting.
  2. Primary action shall dispatch SchedulePreparationStarted.
  3. Runtime shall mark an early-start session for the schedule id.
  4. Runtime shall cancel the official start timer.
  5. Runtime shall emit started(isEarlyStarted=true).
  6. Runtime shall save an initial timed-preparation snapshot.
  7. UI shall navigate to /alarmScreen.
  8. The preparation timer shall start at zero elapsed time, not at now - preparationStartTime.

Flow C: Five-Minute Reminder Starts The Same Early-Start Flow

Preconditions:

  • App receives or opens from a reminder about five minutes before preparation should start.
  • now < preparationStartTime.

Requirements:

  1. The program shall treat this as the same early-start flow as Flow B.
  2. The program should not require a separate fiveMinutes prompt variant for runtime behavior.
  3. The prompt should still present the same decision:
    • Primary action: "Start preparing now".
    • Secondary action: "Not now".
  4. Primary action shall dispatch SchedulePreparationStarted and navigate to /alarmScreen.
  5. Secondary action shall navigate to /home.
  6. Any existing legacy route payload such as isFiveMinutesBefore=true shall be treated as an early-start prompt for backward compatibility only.

Flow D: Upcoming Schedule, User Opens Alarm Screen Directly

Preconditions:

  • Nearest schedule exists.
  • Runtime state is upcoming.
  • User/route opens /alarmScreen before official start.

Requirements:

  1. /alarmScreen shall not show an indefinite loading spinner.
  2. It shall show an early-start-ready screen with schedule name, countdown, start button, and home button.
  3. Start button shall dispatch SchedulePreparationStarted.
  4. Home button shall navigate to /home without starting preparation.

Flow E: Notification Opens Preparation Runtime

Preconditions:

  • User taps a local or background notification.
  • Notification payload has type starting with schedule_ or preparation_, or it contains scheduleId.

Requirements:

  1. Notification handler shall push /alarmScreen when the notification is a general schedule/preparation notification.
  2. Notification handler shall push the early-start prompt when the notification is a five-minute reminder.
  3. Alarm screen shall request schedule subscription on entry.
  4. Runtime shall classify the nearest upcoming schedule normally.
  5. If the schedule is still upcoming, alarm shall show early-start-ready UI.
  6. If the schedule is ongoing or started, alarm shall show active preparation UI.
  7. If no active schedule exists, alarm shall fall back through notExists handling and navigate home.

Flow F: Official Start Prompt

Preconditions:

  • Runtime transitions through ScheduleStarted.
  • User is not early-started.

Requirements:

  1. Runtime shall emit started.
  2. Runtime shall push /scheduleStart.
  3. Default prompt primary action shall navigate to /alarmScreen.
  4. Default prompt primary action shall not dispatch SchedulePreparationStarted; the runtime has already started through ScheduleStarted.

Flow G: Delayed Entry Into Preparation Window

Preconditions:

  • Nearest schedule exists.
  • preparationStartTime < now < scheduleTime.
  • No early-start session exists.

Requirements:

  1. Runtime shall emit ongoing.
  2. Runtime shall start the preparation timer.
  3. Runtime shall immediately catch up preparation progress by:
now - preparationStartTime - alreadyElapsedPreparation
  1. Catch-up elapsed time shall complete earlier steps and partially progress the current step as needed.
  2. 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.

Flow H: Restored Early-Start Session

Preconditions:

  • Early-start session exists for incoming schedule id.
  • App restarts or schedule stream re-emits.

Requirements:

  1. Runtime shall restore the session by emitting started(isEarlyStarted=true).
  2. Runtime shall load a timed snapshot when available.
  3. Runtime shall use the snapshot only if its fingerprint equals the incoming schedule fingerprint.
  4. Runtime shall fast-forward restored preparation by now - snapshot.savedAt.
  5. Runtime shall start the preparation timer.
  6. /scheduleStart builder shall resolve directly to AlarmScreen when isEarlyStarted=true to prevent route bounce.

Flow I: Restored Ongoing Session Without Early Start

Preconditions:

  • No early-start session exists.
  • now >= preparationStartTime.
  • Valid timed snapshot exists.

Requirements:

  1. Runtime shall restore the snapshot when fingerprint matches.
  2. Runtime shall fast-forward by now - snapshot.savedAt.
  3. Runtime shall emit ongoing if still before scheduleTime.
  4. Runtime shall start the preparation timer.

Flow J: Stale Snapshot Or Schedule Mutation

Preconditions:

  • Timed snapshot exists.
  • Incoming schedule fingerprint differs from snapshot fingerprint.

Requirements:

  1. Runtime shall clear timed snapshot.
  2. Runtime shall clear early-start session for that schedule id.
  3. Runtime shall use canonical incoming preparation from the schedule stream.
  4. Runtime shall classify the schedule again using current time.

Fingerprint inputs:

  • scheduleTime
  • moveTime
  • scheduleSpareTime
  • Preparation step id
  • Preparation step name
  • Preparation step duration
  • Preparation step next id

Flow K: User Skips Current Step

Preconditions:

  • Runtime state is ongoing or started.
  • Current preparation step exists.

Requirements:

  1. Skip action shall mark only the current step as done.
  2. Runtime shall emit updated schedule state.
  3. Runtime shall save a timed snapshot immediately.
  4. Next tick shall continue from the next incomplete step.

Flow L: All Preparation Steps Complete

Preconditions:

  • Runtime state is ongoing or started.
  • preparation.isAllStepsDone == true.

Requirements:

  1. Alarm screen shall show completion dialog once per schedule completion state.
  2. Dialog shall offer finish and continue.
  3. The system shall not automatically finish the schedule when all preparation steps are done.
  4. The system shall treat preparation completion and lateness as separate concerns.
  5. If all preparation steps are done before the leave threshold, the user is still on time.
  6. If the user continues, alarm screen shall keep showing live leave countdown.
  7. While continuing before the leave threshold, UI should communicate "ready / time remaining to leave" rather than "late".
  8. If the user becomes late while continuing, UI shall switch to late-continue mode.
  9. Finishing remains available after continuing.

Flow M: User Finishes Preparation

Preconditions:

  • Runtime state has an active schedule.
  • User taps finish from alarm screen or completion dialog.

Requirements:

  1. UI shall compute timeRemainingBeforeLeaving.
  2. If remaining leave time is negative, UI shall send positive late minutes to ScheduleFinished.
  3. If remaining leave time is zero or positive, UI shall send 0 to ScheduleFinished.
  4. Runtime shall call FinishScheduleUseCase(scheduleId, latenessTime).
  5. Runtime shall cancel preparation and schedule-start timers.
  6. Runtime shall clear timed snapshot and early-start session.
  7. Runtime shall emit notExists.
  8. Alarm listener shall navigate to /earlyLate when finish was user-initiated and late/early data is pending.
  9. Otherwise, alarm listener shall navigate to /home.

Flow N: Schedule Ends, Is Deleted, Or Stream Emits Null

Preconditions:

  • Active stream emits null, or schedule time is already before now.

Requirements:

  1. Runtime shall clear persisted state for the stale or current schedule id when known.
  2. Runtime shall emit notExists.
  3. Runtime shall clear current id, active early-start id, snapshot timestamp, and notification tracking.
  4. Alarm listener shall return the user to /home unless a finish navigation is pending.

Flow O: Schedule Switch While Preparing

Preconditions:

  • Current schedule id differs from newly emitted schedule id.

Requirements:

  1. Runtime shall clear persisted state for the previous schedule id.
  2. Runtime shall remove notification tracking for the previous schedule id.
  3. Runtime shall set the incoming schedule id as current.
  4. Runtime shall classify the incoming schedule from scratch.

State Model

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
Loading

Route Requirements

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.

Start Prompt Variants

The program should model start prompts with only two semantic variants:

enum ScheduleStartPromptVariant {
  officialStart,
  earlyStart,
}

Requirements:

  1. officialStart means the calculated preparation time has arrived and the user should start preparing.
  2. earlyStart means the user is being offered the choice to prepare before the calculated preparation time.
  3. Five-minute reminder entry is not a separate runtime variant; it is an early-start source.
  4. Legacy values such as fiveMinutes or isFiveMinutesBefore=true may be accepted at route boundaries, but shall normalize to earlyStart.

Runtime Sequence

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")
Loading

Persistence Requirements

Early-start session

Storage key:

early_start_session_<scheduleId>

Payload:

{
  "startedAt": 1773997200000
}

Requirements:

  1. Save when user dispatches SchedulePreparationStarted.
  2. Load during schedule resolution.
  3. Clear on finish, schedule end, schedule deletion/null, fingerprint mismatch, or schedule switch.

Timed preparation snapshot

Storage key:

preparation_with_time_<scheduleId>

Payload fields:

  • savedAt
  • scheduleFingerprint
  • steps[].id
  • steps[].name
  • steps[].time
  • steps[].nextId
  • steps[].elapsed
  • steps[].isDone

Requirements:

  1. Save immediately on early start.
  2. Save immediately when a step is skipped.
  3. Save immediately when the current step changes.
  4. Save periodically, but not more often than every five seconds unless forced.
  5. Restore only when schedule fingerprint matches.
  6. Fast-forward after restore by elapsed wall-clock time since savedAt.
  7. Clear when the schedule is future and no early-start session exists.
  8. Clear on finish, schedule end, schedule deletion/null, fingerprint mismatch, or schedule switch.

Late/Early Result Requirements

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"]
Loading

Requirements:

  1. Step completion does not determine lateness.
  2. Lateness is determined only by the leave threshold at finish time.
  3. Finishing all steps early can still become late if the user chooses to continue and crosses the leave threshold.
  4. Completing all steps after the expected preparation duration is not late by itself when there is still time before the leave threshold.
  5. earlyLateTime passed to /earlyLate shall preserve seconds and sign from remaining leave time.
  6. Backend finish payload shall use minutes, with 0 for non-late finish.

Notification Requirements

  1. Runtime shall initialize notification tracking per schedule id.
  2. Runtime shall notify when current step changes to a new non-first step.
  3. Runtime shall not notify for the first step.
  4. Runtime shall not notify the same step id more than once per schedule id.
  5. Runtime shall clear notification tracking when schedule ends or switches.

Resolved Design Questions

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.

Acceptance Criteria

Schedule classification

  • 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 ongoing and catches up elapsed preparation.
  • Given a valid early-start session, runtime emits started(isEarlyStarted=true).

Early start

  • Home tile for upcoming schedule 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 /scheduleStart to alarm when early-started.

Runtime timer

  • 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

  • Finish calls FinishScheduleUseCase exactly 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 /earlyLate with seconds and late flag.

Persistence

  • 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.

Open Risks And Follow-Up Questions

These are not blockers for the current implementation, but they should be decided before expanding product behavior.

  1. Late minute rounding: UI currently uses abs(inMinutes), which floors partial late minutes. Decide whether backend lateness should floor or ceil partial minutes.
  2. Exact-boundary classification: _isPreparationOnGoing uses strict start.isBefore(now), while the official timer handles boundary start. Keep tests around exact equality.
  3. Multiple schedules near the same time: current runtime assumes one nearest upcoming schedule. Product should define tie-breaking on the backend/use case.
  4. Notification permission failure: runtime attempts step notifications through NotificationService; product should define fallback UX if permission is unavailable.
  5. Leaving without finishing: state persists, but product should decide whether returning home should show a stronger "preparation still active" affordance.

Test Coverage Map

Primary tests:

  • test/presentation/app/bloc/schedule/schedule_bloc_test.dart
  • test/presentation/alarm/screens/preparation_flow_widget_test.dart
  • test/domain/entities/preparation_timing_entity_test.dart
  • test/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.

Clone this wiki locally