Skip to content

know that you are breathing — implementation plan

status: historical. this is the original approved roadmap. nearly all of it has shipped (see CHANGELOG.md), but it is not maintained as a status document — phase numbers, checklists, and "not yet implemented" notes inside may be stale. for what the code does today, read ARCHITECTURE.md and the other dev-guide docs; for user-facing behavior, read docs/user-guide/. the phase 9 welcome tour has since shipped as a custom PageView (see "welcome tour and in-app help" in ARCHITECTURE.md); ADR 0007 ("watch as remote") remains an accepted future design that is not implemented.

A phased, dependency-first roadmap to re-brand the Flutter app, modernize its packages, fix the listed bugs, and deliver all requested features in a sequence that keeps flutter analyze, flutter test, and flutter build linux green at each milestone. Once approved, this becomes the source of truth to update docs/dev-guide/PROJECT_PLAN.md.

terminology note: this plan was written against the original vocabulary (TimerSegment, NotificationProfile, EditorScreen, DndService, prompt lists, quote lists, keepScreenOn, background sounds, timer/counter modes). The current names are segment, reminder, MeditationEditorScreen, SilencePhoneService, lists, screen behavior, soundscapes, and the meditation / recitation / breathing modes — see docs/dev-guide/terminology.md.


Summary

The plan splits the work into nine shippable phases. Phase 0 does the foundational re-brand, package modernization, and app icon generator so later phases build on a clean base. Phases 1 and 2 fix editor/UX bugs, add auto-save, and restructure settings around global defaults. Phases 3–8 add the major features in dependency order (lists, player experience, health/sessions, advanced segments, audio creation, guided breathing). Phase 9 is the welcome tour and release polish.


Decisions captured from clarifying questions

  • Re-brand scope: full package restructure — update Android Kotlin package directories, iOS/macOS bundle IDs, Linux/Windows metadata, and all packaging/CI identifiers to com.wesorick.john.knowthatyouarebreathing.
  • Package upgrades: major-version upgrade path with manual compatibility fixes, but avoid freshly-released versions (e.g. file_picker 12.0.0, which was published hours ago).
  • Lists merge: unify prompt_lists and quote_lists into a single generic lists concept, so the same list can be used for meditation prompts or notification messages.
  • Defaults toggle: a single "use defaults" switch in the meditation editor. When on, the editor uses the global defaults and hides the corresponding options; when off, all per-meditation options are shown.
  • Do Not Disturb: implement where the OS allows it (Android via notification-policy access). Hide the option on platforms where it is unsupported; prompt for the required permission when the user enables it.
  • Haptics: use the vibration package for custom patterns and intensities on Android/iOS, with HapticFeedback fallback. No haptics on desktop.
  • Guided breathing: begin with a research/design spike to decide how it relates to per-segment meditation types and visualizers before writing production code.

Dependencies and overall ordering

  1. Re-brand and package upgrades first. They touch build files, imports, generated files, and CI; doing them first avoids reworking later feature branches.
  2. Auto-save and defaults next. Auto-save changes how every editor works; the global-defaults system is needed by the meditation editor "use defaults" toggle and by player defaults.
  3. Settings restructure and lists unification. Settings groups determine where new global preferences live. Unifying lists is a schema refactor that should finish before multi-select and prompt/notification list usage.
  4. Player experience. Countdown, DND, screen off, haptics, and visualizer toggles all read the new global/per-meditation defaults.
  5. Health and session lifecycle. Incomplete-session prompting and deleting sessions both depend on the player/session stop flow.
  6. Advanced segments. Per-segment meditation types and segment repeat are deep model/player changes that should build on the already-refactored player.
  7. Audio creation. Binaural beats and recording are new sound sources; they depend on the sound-library and audio-service abstractions.
  8. Guided breathing. Depends on the segment/type model and the visualizer architecture, so it follows the research spike.
  9. Welcome tour and release polish. By definition last, and should re-use the settings screens it offers to enable.

Package modernization notes

Run flutter pub upgrade --major-versions in a branch, then apply the following guardrails:

package current constraint target reason
riverpod ^3.3.2 ^3.3.2 3.4.2 requires riverpod_annotation 4.0.6, which requires riverpod_generator 4.0.8 and freezed 4.0.0-dev.3
flutter_riverpod ^3.0.0 ^3.3.2 align with riverpod
riverpod_annotation ^4.0.0 ^4.0.3 4.0.6 requires riverpod 3.4.2, which would force freezed 4.0.0-dev.3
riverpod_generator ^4.0.4 ^4.0.4 4.0.8 requires analyzer 13.x, which requires freezed 4.0.0-dev.3
freezed ^3.2.6-dev.1 ^3.2.6-dev.1 3.2.5 cannot be used with build_runner 2.16.0 / drift_dev 2.34.5; 4.0.0-dev.3 is a major pre-release, so the dev release is held
freezed_annotation ^3.1.0 ^3.1.0 already current stable
build_runner ^2.15.1 ^2.15.1 2.16.0 requires analyzer 13.x, which requires freezed 4.0.0-dev.3
drift_dev ^2.34.0 ^2.34.0 2.34.5 requires analyzer 13.x, which requires freezed 4.0.0-dev.3
drift ^2.34.3 ^2.34.3 latest stable
go_router ^17.3.0 ^17.4.0 17.5.0 is only ~2 days old
permission_handler ^11.3.1 ^11.4.0 13.0.0 requires permission_handler_android 14.0.0, which targets Android SDK 37
share_plus ^11.0.0 ^12.0.2 13.x requires win32 6.x, which conflicts with the file_picker/wakelock_plus versions we can use
package_info_plus ^9.0.1 ^9.0.1 10.x requires win32 6.x, which conflicts with wakelock_plus 1.5.2
wakelock_plus ^1.5.2 ^1.5.2 1.6.0+ requires win32 6.x, which conflicts with the file_picker/share_plus versions we can use
flutter_local_notifications ^22.2.0 ^22.2.0 22.3.0 is too new
flutter_secure_storage ^10.3.1 ^10.3.1 11.0.0 is too new and has breaking cipher changes
file_picker ^8.0.7 ^8.3.7 11.0.3 breaks the Wear build on AGP 9; 12.0.0 is <1 day old and reworks the API
health ^13.2.0 ^13.3.1 latest stable
emoji_picker_flutter '4.4.0' pinned ^4.5.3 stop pinning, current is fine
flutter_soloud ^4.1.6 ^4.1.7 patch
sqlite3 ^3.5.0 ^3.5.1 patch
uuid ^4.4.2 ^4.6.0 patch
intl ^0.20.2 ^0.20.3 patch

New packages to add for feature work:

  • vibration: ^3.2.0 — haptic patterns.
  • do_not_disturb: ^1.0.3 — Android DND toggle.
  • record: ^7.1.1 — sound recorder.
  • introduction_screen: ^4.0.0 (or a custom equivalent) — welcome tour.
  • image: ^4.9.1 + args: ^2.7.0 — dev dependencies for the icon/logo generator.

After editing pubspec.yaml:

flutter pub upgrade
dart run build_runner build
flutter gen-l10n
flutter analyze
flutter test
flutter build linux
flutter build apk --flavor wear --release -t lib/main_wear.dart

Fix any API breakages before moving to the next phase.


Phase 0 — Re-brand, app icon generator, and package upgrade

Phase 0 — Goal

Finish the name, package ID, and asset refresh so the rest of the plan is not built on top of stale identifiers.

Phase 0 — Implementation steps

  1. Rename the app to "know that you are breathing"
  2. lib/l10n/app_en.arb: change appTitle to know that you are breathing (lowercase, per the existing English convention).
  3. Regenerate lib/l10n/generated/app_localizations*.dart with flutter gen-l10n.
  4. lib/app.dart, lib/main.dart, lib/features/wear/wear_app.dart, lib/core/widgets/about_tile.dart: update references to appTitle only; no hard-coded strings.
  5. README.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE copyright line, docs/dev-guide/PROJECT_PLAN.md, docs/dev-guide/ARCHITECTURE.md, docs/dev-guide/design-system.md, docs/user-guide/*.md.
  6. linux/runner/my_application.cc, linux/packaging/*.desktop, linux/packaging/*.metainfo.xml: update Name= and title strings.
  7. windows/runner/Runner.rc: update FileDescription, ProductName, etc.
  8. ios/Runner/Info.plist: CFBundleDisplayName to Know That You Are Breathing and CFBundleName to know_that_you_are_breathing.
  9. macos/Runner/Info.plist and macos/Runner/Configs/AppInfo.xcconfig: PRODUCT_NAME to know_that_you_are_breathing.
  10. lib/data/database/app_database.dart: update the database directory from know_that_you_are_breathing to know_that_you_are_breathing.
  11. lib/data/services/backup_sync/backup_mapper.dart: update backup bundle directory name.
  12. pubspec.yaml: change the Dart package name from know_that_you_are_breathing to know_that_you_are_breathing.
  13. Run a project-wide find/replace on package:know_that_you_are_breathing/ imports and package:know_that_you_are_breathing/. Update all Dart files and tests.
  14. .gitlab-ci.yml: update artifact/package names.

  15. Application ID and directory-name convention

  16. Use the app ID com.wesorick.john.knowthatyouarebreathing only where the OS convention is reverse-DNS package identification:
    • android/app/build.gradle.kts: namespace and applicationId.
    • Android Kotlin package path under android/app/src/main/kotlin/. Move com/wesorick/john/knowthatyouarebreathing/ to com/wesorick/john/knowthatyouarebreathing/. Update package declarations in MainActivity.kt, DataLayerPlugin.kt, DataLayerCache.kt, DataLayerListenerService.kt, HealthConnectPlugin.kt, and HealthConnectService.kt.
    • iOS/macOS PRODUCT_BUNDLE_IDENTIFIER.
  17. On desktops and in source/packaging directory names, use the app name instead of the app ID:
    • Rename the project/checkout directory to know that you are breathing.
    • linux/CMakeLists.txt: BINARY_NAME = know_that_you_are_breathing. Set APPLICATION_ID to know-that-you-are-breathing so that path_provider and GTK use a human-readable app name for data directories/window class (verify the window-class impact on first desktop build; if needed keep a reverse-DNS WM class and just set the user-visible names).
    • linux/packaging/*.desktop: Name=Know That You Are Breathing, Icon=know-that-you-are-breathing, Exec=know_that_you_are_breathing, and rename the file to know-that-you-are-breathing.desktop.
    • linux/packaging/*/*.metainfo.xml: AppStream <id> can stay reverse-DNS, but visible name tags use know that you are breathing.
    • windows/runner/CMakeLists.txt: BINARY_NAME = know_that_you_are_breathing.
    • windows/runner/Runner.rc: FileDescription and ProductName = Know That You Are Breathing; CompanyName = the author/publisher name (e.g. John Wesorick), not the app ID.
    • macos/Runner/Configs/AppInfo.xcconfig and Info.plist: PRODUCT_BUNDLE_IDENTIFIER = com.wesorick.john.knowthatyouarebreathing; PRODUCT_NAME / CFBundleDisplayName = Know That You Are Breathing.
    • lib/data/database/app_database.dart: database directory name = know_that_you_are_breathing (app name, not app ID).
    • lib/data/services/backup_sync/backup_mapper.dart: backup bundle directory name = know_that_you_are_breathing.
  18. Packaging metadata:
    • snap/snapcraft.yaml: name = know-that-you-are-breathing (already is); update title, description, and command to the new app name.
    • flatpak/com.wesorick.john.knowthatyouarebreathing.yml: rename file and update app-id to com.wesorick.john.knowthatyouarebreathing (flatpak still uses a reverse-DNS package identifier). Update desktop references.
    • linux/packaging/rpm/build-rpm.sh, .gitlab-ci.yml: artifact/package file names use know-that-you-are-breathing.
  19. tool/generate_android_keystore.sh: update keystore alias to the new app ID.

  20. App icon/logo and generator

  21. Add image and args to dev_dependencies.
  22. Create tool/generate.dart adapted from /home/deck/Nextcloud/documents/apps/pyggo/tool/generate.dart, supporting at least:
    • dart tool/generate.dart app <icon.png> to generate launcher icons.
    • dart tool/generate.dart logo <logo.png> to generate brand assets.
  23. Inputs: /home/deck/Downloads/know that you are breathing icon.png and /home/deck/Downloads/know that you are breathing logo.png.
  24. Outputs:
    • assets/images/app_icon.png (replaces current placeholder).
    • assets/brand/app_logo.png and assets/brand/android12_splash.png.
    • android/app/src/main/res/mipmap-*/ic_launcher.png.
    • ios/Runner/Assets.xcassets/AppIcon.appiconset/*.
    • macos/Runner/Assets.xcassets/AppIcon.appiconset/*.
    • windows/runner/resources/app_icon.ico.
    • linux/runner/resources/know_that_you_are_breathing.png.
    • docs/assets/know_that_you_are_breathing_logo_200.png.
  25. Update pubspec.yaml assets section and any platform manifest references to the new icon/logos.
  26. Add a short docs/dev-guide/icon-logo-generator.md documenting usage.

  27. Package modernization

  28. Edit pubspec.yaml with the target versions in the table above.
  29. Run flutter pub upgrade and resolve any breaking changes.
  30. Run dart run build_runner build, flutter gen-l10n, flutter analyze, flutter test, flutter build linux, and the Wear release build.

Phase 0 — Verification

  • [x] flutter analyze is clean.
  • [x] flutter test passes.
  • [x] flutter build linux succeeds.
  • [x] flutter build apk --flavor wear --release -t lib/main_wear.dart succeeds.
  • [x] The app displays the new name and icon on desktop and Android.
  • [x] A fresh install reports the new bundle ID / package name.

Phase 1 — Editor UX bugs, auto-save, and navigation cleanup

Phase 1 — Goal

Fix the immediate editor bugs, introduce auto-save everywhere, and clean up the meditation library navigation before adding defaults.

Phase 1 — Implementation steps

  1. Auto-save pattern
  2. Create a reusable AutoSaveDraft<T> helper or DebouncedSaver that persists after a short idle period (e.g. 1.5 s).
  3. Apply to:
    • EditorScreen (meditation)
    • PromptLibraryScreen / list editors (once unified)
    • QuoteListEditorScreen / NotificationProfileEditorScreen
  4. Keep a "cancel" action. When the user cancels, restore the original draft from the repository.
  5. Add WillPopScope/PopScope behavior: if there are unsaved local changes, prompt to save or discard.

  6. Fix single prompt space bug

  7. In lib/features/editor/editor_screen.dart, the prompt and segment label TextEditingControllers are recreated each build. Move them to State (cache keyed by segment index) and dispose in dispose().
  8. Remove .trim() from the prompt onChanged call so spaces are preserved.
  9. Update widget/golden tests and add a test that a space can be entered.

  10. Segment "name" field

  11. Rename the ARB key and UI label from "label" to "name".
  12. Replace the read-only segment x header with a tappable/editable TextField inline at the top of each segment card.
  13. Keep the default placeholder as "segment {number}" but allow the user to type any name.

  14. No total wheel for a single segment

  15. In EditorScreen._buildSegmentsSection and any player summary that uses DurationPicker, hide the total-duration wheel when meditation.segments.length == 1.

  16. Reorder segments

  17. Add a reorder icon button on the editor segments section.
  18. Open a dialog with a ReorderableListView showing segment names.
  19. On drag completion, rewrite orderIndex values and rebuild _draft.segments.
  20. Add unit and widget tests for the reorder flow.

  21. Rename library to "meditations"

  22. Update ARB strings: meditationLibraryTitle, navigationLibraryLabel, navigationLibraryTooltip to meditations.
  23. Update AppNavDestination.library and the route path. Change AppRoutes.library to /meditations and update app_router.dart.
  24. Change the navigation icon from Icons.library_books to Icons.self_improvement (a meditator) in lib/core/routing/app_navigation_shell.dart.

  25. Display a snackbar when sync is running on Backup & Sync

  26. In lib/features/backup_sync/backup_sync_screen.dart, listen to the backup/sync provider (backupSyncServiceProvider) with ref.listen.
  27. Detect any sync component (backup, localSync, webDAVSync, dataLayerSync) entering BackupSyncStatus.inProgress.
  28. On first build, if a sync is already in progress, show a SnackBar immediately explaining that a sync is in progress and the controls are disabled.
  29. When a new sync starts while the screen is visible, pop up the same snackbar.
  30. Guard against duplicate snackbars (e.g. a single visible snackbar that updates, or a _hasShownSyncSnackbar guard).
  31. Add a widget test in test/features/backup_sync/backup_sync_screen_test.dart covering "already in progress" and "starts while visible".

Phase 1 — Verification

  • [x] Space can be typed in a segment prompt and segment name.
  • [x] Saving a meditation with a single segment does not show a redundant total wheel.
  • [x] Segments can be reordered and the new order persists.
  • [x] Auto-save persists edits after a short idle pause; cancel reverts them.
  • [x] The bottom tab is labeled "meditations" and uses the meditator icon.

Phase 2 — Settings restructure and global defaults

Phase 2 — Goal

Move global preferences into grouped sub-screens and build the "use defaults" system so the meditation editor can hide options that fall back to those defaults.

Phase 2 — Implementation steps

  1. New routes and sub-screens
  2. Add AppRoutes and GoRoute entries under /settings:
    • /settings/look_and_feel
    • /settings/meditation_defaults
    • /settings/notifications
    • /settings/library
    • /settings/insights
    • /settings/health_sync
    • /settings/backup_sync (already exists)
  3. Create one screen per group. SettingsScreen becomes a landing list with tiles; each tile pushes the sub-screen.

  4. Group the existing settings per the requested order

  5. Look & feel: theme, accent color, appearance (font scale, reduce motion, compact density, amoled dark), recitation input, TTS voice.
  6. Meditation defaults: session prompts, mood presets, count direction (and later haptics/screen off/countdown/DND defaults as those features land).
  7. Notifications: move the notification profiles list here from the bottom navigation. Remove the notifications tab from AppNavigationShell.
  8. Library: sounds, binaural beats, lists (links/screens that will be built in later phases).
  9. Insights: completed-only streaks toggle.
  10. Health sync: existing HealthSyncSection.
  11. Backup & sync: existing BackupSyncScreen.

  12. Move TTS voice to settings and support per-meditation override

  13. Move the voice picker from EditorScreen to the look-and-feel sub-screen.
  14. Save the global default under SettingsRepository (_ttsVoiceKey).
  15. Add ttsVoice and useDefaultTtsVoice fields to Meditation and MeditationsTable.
  16. In the editor, when "use defaults" is on, hide the voice picker and use the global default.
  17. Update MeditationPlayerScreen, RecitationPlayerScreen, and WearPlayerScreen to resolve meditation.ttsVoice first, then global default.

  18. Rename "health sync override" to "health sync" and add a "none" option

  19. Rename the ARB string healthSyncOverrideTitle to healthSyncTitle and update EditorScreen.
  20. Add a HealthSyncTarget.none (or a sentinel value). When selected, the player skips writing a health record for this meditation (useful for sleep meditations).
  21. Update HealthService implementations and the resolve logic in meditation_player_screen.dart, recitation_player_screen.dart, wear_player_screen.dart, and health_relay_service.dart.

  22. "Use defaults" toggle in the meditation editor

  23. Add useDefaults to Meditation/MeditationsTable (default true).
  24. Add a switch at the top of EditorScreen.
  25. When on, hide the following sections that have global defaults: session options, mood presets, count direction, TTS voice, health sync.
  26. When off, show all options and save them on the meditation.
  27. Ensure the player resolves the right value: per-meditation if useDefaults is false, otherwise the matching global default.

  28. Global defaults repository/provider

  29. Introduce a MeditationDefaults model grouping: SessionOptions, MoodPresets, CountDirection, TtsVoice, HealthSyncTarget, and placeholders for HapticSettings, ScreenOff, Countdown, and SilencePhone (these fields are populated as their respective features land in Phase 4).
  30. Add readMeditationDefaults() / saveMeditationDefaults() in SettingsRepository and a provider.
  31. Use this model in the "meditation defaults" settings screen.

  32. Parse and clean up the TTS voices list

  33. Add a TtsVoiceDisplay model and a ttsVoiceDisplaysProvider that wraps the raw ttsVoicesProvider from lib/data/services/tts_service_providers.dart.
  34. Create a TtsVoiceLabelService that turns raw platform voice maps into human-readable labels:
    • Parse the BCP-47 locale string into language and region codes.
    • Map language codes (e.g. en, es, de) and region codes (e.g. US, GB, MX) to English display names.
    • Strip noise from the system voice name (_network, _high_quality, vendor prefixes, etc.).
    • Format the final label as {Language} ({Region}) – {Voice Name} or {Language} – {Voice Name} when the region is missing.
    • Sort voices by language, then region, then name.
  35. Use the cleaned list in the look-and-feel voice picker and the per-meditation voice override.
  36. Add unit tests for TtsVoiceLabelService using sample iOS and Android voice maps.

Phase 2 — Verification

  • [x] Settings is organized into the requested sub-screens.
  • [x] Notifications is removed from the bottom navigation and reachable through settings.
  • [x] TTS voice default is selected in settings and can be overridden per meditation.
  • [x] The voice list is cleaned into recognizable language/region/voice labels.
  • [x] Health sync "none" prevents any health write for that meditation.
  • [x] The "use defaults" switch hides and shows the correct editor sections.

Phase 3 — Lists unification and multi-select

Phase 3 — Goal

Merge prompt lists and quote lists into one generic list concept and allow multi-selection for prompts and notifications.

Phase 3 — Implementation steps

  1. New unified schema
  2. Create ListsTable:
    • id, name, isBuiltin, syncId, updatedAt. All lists are usable for prompts, notification messages, or both. Selection mode lives on the segment/profile link, not the list.
  3. Create ListItemsTable:
    • id, listId, orderIndex, body, updatedAt.
  4. Create TimerSegmentListLinks and NotificationProfileListLinks:
    • linking tables supporting multiple lists per segment/profile.
  5. Keep prompt_lists/prompts and quote_lists/quotes tables only for the v22 → v23 migration. Write an idempotent migration that copies existing data into the new tables, then drops the old tables.
  6. Remove the usage column and bump the schema version to 24 so all existing v23 databases drop it.

  7. Domain models and repository

  8. Replace PromptList/QuoteList and Prompt/Quote with AppList and ListItem.
  9. Update lists_repository.dart to unify CRUD, selection, and sync.
  10. Update the BackupMapper so unified lists still sync correctly.

  11. UI refresh

  12. Replace PromptLibraryScreen and QuoteListsScreen with a single ListsScreen for all lists.
  13. Replace the segment prompt list picker and the notification message-source picker with an inline selected-list editor: ordered rows, add list, reorder, edit, and remove actions.

  14. Multi-select lists

  15. Add TimerSegmentListLinks and NotificationProfileListLinks linking tables to store multiple list IDs.
  16. Update TimerSegment and NotificationProfile models.
  17. Update the segment prompt picker and notification editor to use an inline selected-list editor (ordered rows, add list, reorder, edit, remove).
  18. Update the player to merge multiple lists (respecting the segment/profile listSelectionMode, which is random or ordered) when picking prompts or notification messages.

Phase 3 — Verification

  • [x] Existing prompt lists and quote lists are migrated without data loss.
  • [x] Any list can be selected as a prompt or notification source.
  • [x] Multiple lists can be attached to a segment or notification profile.
  • [x] flutter test migration tests pass.

Phase 4 — Player experience

Phase 4 — Goal

Improve the session start and playback experience with countdown, DND, screen off, haptics, visualizer toggles, and prompt timing estimation.

Phase 4 — Implementation steps

  1. Start countdown
  2. Add countdownSeconds to MeditationDefaults (global default) and Meditation (per-meditation override, with useDefaultCountdown).
  3. In the player, if countdownSeconds > 0, show a silent full-screen countdown before the session begins.
  4. Add a countdown indicator; no sounds during this phase.

  5. Screen behavior

  6. Replace the legacy KeepScreenOn enum with ScreenBehavior (keepOn, allowTimeout, turnOff).
  7. Global default belongs in MeditationDefaults; per-meditation override belongs in Meditation and supports useDefault resolution.
  8. keepOn uses WakelockPlus; allowTimeout leaves the OS timeout in control; turnOff attempts to turn the screen off after the countdown where supported and falls back to allowTimeout on unsupported platforms.
  9. Spike first: verify whether Flutter/native APIs can truly turn the screen off. Document the fallback.

  10. Silence phone (DND)

  11. Add a silencePhone global default in MeditationDefaults and a per-meditation override in Meditation.
  12. Add a DndService interface with isSupported(), isEnabled(), requestPermission(), enable(), and disable().
  13. Android implementation uses notification-policy access (ACCESS_NOTIFICATION_POLICY).
  14. On iOS and desktop, isSupported() returns false and the UI hides the option.
  15. Before enabling, save the current interruption filter and restore it on session end, unless the user already had DND on.
  16. Integrate with MeditationPlayerScreen, RecitationPlayerScreen, WearPlayerScreen, and WearRecitationScreen start/stop.

  17. Haptics

  18. Add vibration dependency.
  19. Add HapticSettings model with enabled, pattern, intensity (high/medium/low), and durationMilliseconds. Store global default and per-meditation override.
  20. Map haptic events to:
    • bell events (start bell, end bell, segment bell);
    • timer transitions (moving into and out of a segment);
    • prompt transitions (when a new prompt is spoken);
    • recitation increments.
  21. Implement a HapticService with pattern presets (short pulse, double pulse, long pulse, etc.) and intensity via amplitude or intensities. Duration is configurable; if the device does not support variable duration, fall back to a single standard HapticFeedback.
  22. Add continuous haptic patterns that repeat for the duration of a segment or breathing phase (dot, dash, ramp up, ramp down, slow dot, slow dash, continuous double/triple pulse, and raindrops). Continuous patterns are shown in the same dropdown with a (continuous) suffix and stop automatically when the segment/phase/session ends.
  23. Add an option for haptics in recitation mode on phones.
  24. Desktop: haptics disabled/hidden.

  25. Visualizer enable/disable

  26. Add visualizerEnabled to MeditationDefaults and Meditation (with useDefaultVisualizer).
  27. When visualizerEnabled resolves to true, render SessionVisualizer.
  28. When it resolves to false, do not render SessionVisualizer and do not replace it with another circular or static circle widget.

  29. Estimate prompt timings

  30. Investigate flutter_tts completion callbacks and speech rates.
  31. Add a TtsTimingService that estimates prompt duration based on text length and configured speech rate, with a per-voice calibration fallback.
  32. In the player, if waitForStartBell / waitForEndBell / speak-prompts is on, wait for the estimated TTS duration before advancing.

Phase 4 — Milestones

  • Milestone 1 (countdown + visualizer) is implemented in schema v25 and covers the global/per-meditation model, UI, player integration, and backup/sync.
  • Milestone 2 (screen behavior, DND, haptics) is planned for schema v26.
  • Milestone 3 (TTS timing) adds TtsTimingService and controller integration.

Verification is intended for the completed Phase 4 rather than after each milestone.

Phase 4 — Verification

  • [x] Countdown displays silently and the session starts when it reaches zero.
  • [x] DND enables/disables on Android and respects the pre-session state.
  • [x] Haptics fire on start/end bells, segment transitions, prompt transitions, and recitation counts; duration is configurable.
  • [x] The visualizer can be disabled globally and per meditation.
  • [x] Player waits appropriately for longer TTS prompts.

Phase 5 — Health and session lifecycle

Phase 5 — Goal

Fix the incomplete-session health-connect bug, add session deletion, and make insights locale-aware.

Phase 5 — Implementation steps

  1. Incomplete sessions prompt
  2. Modify the player stop flow so the controller returns a draft Session without logging it. The screen asks the user whether to save the incomplete session.
  3. If the user chooses save: call SessionsRepository.logSession with completed = false and actualDurationSeconds = elapsed, then write the health record with the actual time if health sync is enabled.
  4. If the user chooses discard: do not save locally and do not write to health.
  5. For completed sessions, keep the current auto-save/auto-write behavior.

  6. Delete sessions

  7. Add a delete action to HistoryScreen and SessionDetailScreen with a confirmation dialog.
  8. If the session has healthSynced == true, prompt whether to also delete the Health Connect / Apple Health record.
  9. Extend HealthService with deleteSession(clientRecordId, start, end, target). Implement on Android using HealthConnectClient.deleteRecords.
  10. On iOS/HealthKit, attempt deletion through health package APIs if available; if not, show a message that the external record must be removed manually.
  11. Record a tombstone before deleting the local row so the deletion syncs.

  12. First day of week by locale

  13. Use intl DateFormat / DateSymbols to read FIRSTDAYOFWEEK for the current locale.
  14. Update SessionStats week-boundary logic and the insights/stats graphs.
  15. Add a test verifying week start for en_US (Sunday) vs en_GB (Monday).

  16. Meditation history/stats icon in the meditation list

  17. Add a small icon button on each MeditationListScreen tile.
  18. Tapping it opens HistoryScreen and StatsScreen pre-filtered to that meditation (pass meditationId through route extra or query parameter).
  19. Move existing icons for duplicate, archive in meditation list to meditation editor, at the top, next to export.

Phase 5 — Verification

  • [x] Stopping an incomplete session prompts save/discard.
  • [x] Saving writes the actual elapsed time to Health Connect / Apple Health.
  • [x] Discarding does not create a local or health record.
  • [x] Deleting a synced session prompts and deletes the health record where the platform supports it.
  • [x] Insights honor the locale's first day of the week.

Phase 6 — Advanced segments (per-segment type and repeat)

Phase 6 — Goal

Make meditation type a per-segment property and add segment repetition, including ordered/random list-based repetition.

Phase 6 — Implementation steps

  1. Per-segment meditation type
  2. Add TimerSegment.mode enum (timer, counter, breathing).
  3. Keep Meditation.mode as the default for new segments but allow override.
  4. Refactor MeditationPlayerController and RecitationPlayerController into a single MultiSegmentPlayerController that can run a mixed sequence of timer, counter, and guided-breathing segments.
  5. Update MeditationPlayerScreen/RecitationPlayerScreen to use the unified controller; easy mode and Wear keep working as one-segment cases.

  6. Segment repeat

  7. Add repeatCount, repeatMode, repeatListId, and repeatListOrder to TimerSegment:
    • repeatMode: none, single, group.
    • For single-segment repeat: choose samePrompt or newFromList.
    • For group repeat: choose perSegment (a,a,a,b,b,b) or inOrder (a,b,c,a,b,c).
    • For list-driven repeat: repeat the segment until the selected list is exhausted, either in order or at random.
  8. Add a SegmentExpander that turns a Meditation with repeat rules into a flat runtime sequence of concrete segments.
  9. Add an editor UI for repeat settings per segment and per group.

  10. Player integration

  11. The unified controller expands repeats before playback and tracks the runtime segment index.
  12. Prompt selection follows the repeat rules and the chosen list's selectionMode.

Phase 6 — Verification

  • [x] A meditation can mix timer, counter, and breathing segments in one session.
  • [x] Single and group repeats produce the expected sequence order.
  • [x] List-driven repeat stops when the list is exhausted.
  • [x] Unit tests cover SegmentExpander for all repeat modes.

Phase 7 — Audio creation and binaural beats

Phase 7 — Goal

Add user-created audio (recording and synthesized binaural beats) as sound sources that the existing sound library can use.

Phase 7 — Implementation steps

  1. Binaural beats
  2. Add a new sound category binaural.
  3. Add BinauralBeatConfig model with base frequency, beat frequency, and duration.
  4. Use flutter_soloud's waveform generation (WaveForm.sin) to create two sine voices at slightly different frequencies, panned left/right, for the binaural effect.
  5. Add default presets (alpha, theta, delta) and a custom editor in the settings/library group.
  6. Store binaural definitions in BackgroundSoundsTable with a JSON config field, or a new BinauralBeatsTable.

  7. Sound recorder

  8. Add record: ^7.1.1.
  9. Add RecordedSoundsTable or reuse BackgroundSoundsTable with source = 'recorded' and filePath.
  10. Add a recording UI in the sound library. Allow recording, trimming (optional), naming, and choosing a category (bell, background, prompt).
  11. Handle microphone and storage permissions through permission_handler.
  12. Make recorded sounds selectable in the meditation editor like any other sound.

Phase 7 — Verification

  • [x] Binaural beat presets play and can be used as background sounds.
  • [z] A user can record a prompt/bell/background and use it in a meditation.
  • [z] Recordings are persisted in the app documents and included in backup/sync.

Phase 8 — Guided breathing and visualizers

Phase 8 — Goal

Implement guided breathing exercises as a meditation/segment type with dedicated visualizers.

Phase 8 — Implementation steps

  1. Research/design spike
  2. Decide whether breathing exercises are:
    • a Meditation type,
    • a TimerSegment.mode value, or
    • both.
  3. Define a BreathingExercise model with inhale/hold/exhale/hold timings and optional start/end prompts.
  4. Choose whether the exercise dropdown lives on the meditation editor or per segment.

  5. Implementation

  6. Add the chosen model fields and DB migration.
  7. Add a dropdown to select the breathing exercise (box, bee, etc.).
  8. Add the breath-phase controller that drives:
    • an expanding/contracting dot visualizer,
    • a tree/flower growth/reversal visualizer, and
    • a wave grow/crash/recede visualizer.
  9. Implement each visualizer as a CustomPainter or AnimatedBuilder widget.
  10. Allow the user to pick which visualizer to use.

Phase 8 — Verification

  • [x] A breathing exercise can be selected and played.
  • [x] The chosen visualizer animates smoothly through inhale/hold/exhale phases.
  • [x] The player advances breath phases on time.

Phase 9 — Welcome tour and release polish

Phase 9 — Goal

Guide first-time users through standard options and do final documentation/QA.

Phase 9 — Implementation steps

  1. Welcome screen/tour
  2. Use introduction_screen: ^4.0.0 or a custom page view.
  3. Show on first launch only; store hasSeenWelcome in SettingsRepository.
  4. Steps (each skippable, whole thing skippable):
    • theme/accent,
    • health connect opt-in,
    • accessibility (font scale / reduce motion),
    • DND/haptics/voice overview,
    • final "get started".
  5. Add a "re-show welcome" button in the look-and-feel or about settings.

  6. Documentation and QA

  7. Update docs/user-guide/*.md and docs/dev-guide/ARCHITECTURE.md for all new features.
  8. Update docs/dev-guide/PROJECT_PLAN.md with the final phase list and implementation notes.
  9. Run the full verification suite and a manual QA pass on Linux, Android, and Wear.

Phase 9 — Verification

  • [ ] Welcome tour appears once on first launch and can be skipped entirely.
  • [ ] Each step can be skipped individually.
  • [ ] The "re-show welcome" button in settings opens the tour again.
  • [ ] All flutter analyze, flutter test, and flutter build linux/apk --flavor wear pass.

Files and components most likely to change

Build, re-brand, and packaging

  • pubspec.yaml
  • android/app/build.gradle.kts
  • android/app/src/main/kotlin/com/wesorick/john/knowthatyouarebreathing/*.kt
  • ios/Runner.xcodeproj/project.pbxproj, ios/Runner/Info.plist
  • macos/Runner.xcodeproj/project.pbxproj, macos/Runner/Info.plist, macos/Runner/Configs/AppInfo.xcconfig
  • linux/CMakeLists.txt, linux/runner/my_application.cc, linux/packaging/*
  • windows/runner/Runner.rc
  • flatpak/*, snap/snapcraft.yaml
  • tool/generate.dart, tool/generate_android_keystore.sh
  • .gitlab-ci.yml
  • README.md, CONTRIBUTING.md, CHANGELOG.md, LICENSE
  • docs/dev-guide/PROJECT_PLAN.md, docs/dev-guide/ARCHITECTURE.md

Localization and assets

  • lib/l10n/app_en.arb
  • lib/l10n/generated/app_localizations*.dart
  • assets/images/app_icon.png, assets/brand/*
  • android/app/src/main/res/mipmap-*/ic_launcher.png
  • ios/Runner/Assets.xcassets/AppIcon.appiconset/*
  • macos/Runner/Assets.xcassets/AppIcon.appiconset/*
  • windows/runner/resources/app_icon.ico
  • linux/runner/resources/know_that_you_are_breathing.png

Core routing and shell

  • lib/core/routing/app_router.dart
  • lib/core/routing/app_navigation_shell.dart
  • lib/app.dart
  • lib/main.dart

Data layer

  • lib/data/database/app_database.dart
  • lib/data/database/tables/meditations_table.dart
  • lib/data/database/tables/timer_segments_table.dart
  • lib/data/database/tables/sessions_table.dart
  • lib/data/database/tables/prompt_lists_table.dart (unified)
  • lib/data/database/tables/quotes_table.dart (unified)
  • lib/data/database/tables/quotes_table.dart
  • lib/data/database/tables/notification_profiles_table.dart
  • lib/data/database/tables/background_sounds_table.dart
  • lib/data/models/meditation.dart
  • lib/data/models/session.dart
  • lib/data/models/ui_preferences.dart
  • lib/data/models/health_sync_target.dart
  • lib/data/models/keep_screen_on.dart
  • lib/data/repositories/settings_repository.dart
  • lib/data/repositories/sessions_repository.dart
  • lib/data/repositories/meditations_repository.dart
  • lib/data/services/health_service.dart
  • lib/data/services/health_service_impl.dart
  • lib/data/services/health_service_android.dart
  • lib/data/services/health_relay_service.dart
  • lib/data/services/tts_service.dart
  • lib/data/services/flutter_tts_service.dart
  • lib/data/services/tts_service_providers.dart
  • lib/data/services/tts_voice_label_service.dart (new)
  • lib/data/services/dnd_service.dart (new)
  • lib/data/services/haptic_service.dart (new)

Features

  • lib/features/editor/editor_screen.dart
  • lib/features/editor/meditation_list_screen.dart
  • lib/features/editor/meditation_player_screen.dart
  • lib/features/editor/meditation_player_controller.dart
  • lib/features/editor/recitation_player_screen.dart
  • lib/features/editor/recitation_player_controller.dart
  • lib/features/wear/wear_player_screen.dart
  • lib/features/wear/wear_recitation_screen.dart
  • lib/features/settings/settings_screen.dart + new sub-screens
  • lib/features/history/history_screen.dart
  • lib/features/history/session_detail_screen.dart
  • lib/features/history/session_prompts.dart
  • lib/features/notifications/notifications_screen.dart
  • lib/features/notifications/notification_profile_editor_screen.dart
  • lib/features/prompts/prompt_library_screen.dart
  • lib/features/quotes/quote_lists_screen.dart
  • lib/features/quotes/quote_list_editor_screen.dart
  • lib/features/sounds/sound_library_screen.dart

Widgets

  • lib/core/widgets/session_visualizer.dart
  • lib/widgets/duration_picker.dart
  • lib/widgets/number_wheel.dart

Tests

  • Mirror every lib/ change in test/ with unit, widget, and golden tests.
  • Update test/data/database/app_database_migration_test.dart for each schema bump.

Verification (re-run after every non-trivial change)

  • [ ] flutter pub get and dart run build_runner build succeed.
  • [ ] flutter gen-l10n regenerates localizations.
  • [ ] flutter analyze is clean.
  • [ ] flutter test passes.
  • [ ] flutter build linux succeeds.
  • [ ] flutter build apk --flavor wear --release -t lib/main_wear.dart succeeds.
  • [ ] Code coverage remains at or above the 70% CI target.

Risks and considerations

  • Major package upgrades can cause analyzer/codegen failures. Phase 0 must be green before moving on, and pinning/fallback versions may be needed.
  • Full package restructure moves Android Kotlin source; an incorrect path or import will break the build. Coordinate with any in-flight native branches.
  • Unified lists is a large schema migration. Test migration from a v21 database to the new schema thoroughly and keep a rollback plan.
  • Per-segment meditation types and repeat touch the player core. A refactor of the two existing controllers into a unified MultiSegmentPlayerController is recommended but high-risk; time-box the design and keep the old controllers as fallbacks if needed.
  • DND and screen off are OS-limited. Android DND requires a special permission and may not be granted; iOS cannot toggle DND from apps. The UI must hide or disable these options where unsupported.
  • Health record deletion is straightforward on Health Connect but may not be supported by the health plugin for Apple Health. Prepare a graceful "manually delete" fallback message.
  • Guided breathing and visualizers are the largest UI scope. The research spike in Phase 8 may lead to cutting some visualizers for an initial release.
  • App ID change is safe because the app has not been released, but existing local developer installs and signing configs need to be recreated.
  • Welcome tour and new settings add many UI strings. All English text must stay lowercase per the existing design convention and pass through ARB.

Deferred work from previous plan

The following items were planned in earlier versions of this project plan but are not part of the current roadmap. They are preserved here so they are not lost.

watchOS companion app (separate future plan)

A real watchOS app requires a native SwiftUI/WatchKit target bridged to the Flutter iOS app through WatchConnectivity. It is not part of this plan; when reached, a separate plan should cover scaffolding the watch target, the bridge, and a feature subset matching the Wear OS subset from Phase 10. Apple Watch double-tap / crown-based recitation counting should also be considered as an input option once that plan begins.

Wear OS remote-audio / audio-source setting (Stage 10.9)

The "play session audio from: this device / phone / watch" setting and its remote-audio control channel over the Wearable Data Layer were deferred. The intended design uses a small command protocol (playBell(soundId), startBackground(soundId, volume), stopAll, ping) handled by a RemoteAudioService on the sending side, dispatching to the local AudioService on the receiving side. Before each session the selected route is checked with a ping and, if unavailable, falls back to local audio on the originating device.

Watch-local notification scheduling

Scheduling mindfulness reminders directly on the watch was intentionally deferred. The current implementation relies on phone-mirrored Wear OS notifications and does not expose a watch-local toggle.

Standalone WebDAV and git sync from the watch

Data Layer sync between the watch and phone is the primary sync path. A standalone WebDAV sync backend and a standalone git sync backend that can run directly from the watch are deferred to a future release.

Windows and macOS zip bundles for CI packaging

Windows and macOS zip bundle packaging jobs in CI were deferred until runners for those platforms are available.

Suggested enhancements not in the current plan

The current roadmap already covers the breathing pacer visualizer, haptic-only bell mode, guided breathing, binaural beats, and .med import/export. The following ideas from the previous ## Suggested enhancements section remain optional backlog items:

  • Home-screen widgets (Android) / Lock Screen widgets & complications (once watchOS work begins).
  • Streaks & gentle achievement badges (opt-in, not gamified in a pushy way).
  • Shortcuts / Tasker / Siri-Shortcuts style automation hooks to start a specific meditation.
  • Community sharing index for .med files (simple, optional, opt-in directory).
  • Ambient/adaptive background themes that shift subtly over a session's duration.