diff --git a/Codex Documentation/Current Software Plan/UI Plan 2 - Backlog Board Implementation/UI_PLAN_2_IMPLEMENTATION_NOTES.md b/Codex Documentation/Current Software Plan/UI Plan 2 - Backlog Board Implementation/UI_PLAN_2_IMPLEMENTATION_NOTES.md index d0cc9df..414539f 100644 --- a/Codex Documentation/Current Software Plan/UI Plan 2 - Backlog Board Implementation/UI_PLAN_2_IMPLEMENTATION_NOTES.md +++ b/Codex Documentation/Current Software Plan/UI Plan 2 - Backlog Board Implementation/UI_PLAN_2_IMPLEMENTATION_NOTES.md @@ -83,3 +83,20 @@ Freeform Tags Metadata.md` for a later migration-backed feature. 4. Focused application-management tests cover stable empty columns, bucket and summary consistency, search/filter behavior, child previews, not-found detail responses, read-only slot suggestions, and missing-duration handling. + +## Block 02 Demo Seed Data + +1. The Flutter demo composition now seeds Backlog board data as real scheduler + `Task` and `ProjectProfile` records, not widget-local mock rows. +2. The seed matches the feasible textual targets from Chunk 2.7: 24 backlog + tasks, project counts of Home 5 / Work 8 / Personal 5 / Learning 4 / Side + Project 2, priority counts of High 7 / Medium 9 / Low 8, and 12h 15m total + estimated time. +3. The plan/mockup contains a count conflict: the mockup summary says 24 tasks, + while the listed column badge counts add to 22. The current seed keeps 24 + repository-backed backlog tasks so the summary target stays truthful through + public read models. +4. `Review Q3 budget` is seeded with Work, 60 minutes, High priority, + medium reward, and easy difficulty. Finance/review freeform tags, notes text, + and exact drawer slot copy remain deferred with the approved metadata scope + decision above. diff --git a/apps/focus_flow_flutter/lib/app/demo/demo_seed_tasks.dart b/apps/focus_flow_flutter/lib/app/demo/demo_seed_tasks.dart index 6aadc5d..85255c6 100644 --- a/apps/focus_flow_flutter/lib/app/demo/demo_seed_tasks.dart +++ b/apps/focus_flow_flutter/lib/app/demo/demo_seed_tasks.dart @@ -3,6 +3,30 @@ part of '../demo_scheduler_composition.dart'; +/// Top-level helper that performs the `_demoProjects` operation for this file. +/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. +List _demoProjects() { + return [ + ProjectProfile(id: 'home', name: 'Home', colorKey: 'project-home'), + ProjectProfile(id: 'work', name: 'Work', colorKey: 'project-work'), + ProjectProfile( + id: 'personal', + name: 'Personal', + colorKey: 'project-personal', + ), + ProjectProfile( + id: 'learning', + name: 'Learning', + colorKey: 'project-learning', + ), + ProjectProfile( + id: 'side-project', + name: 'Side Project', + colorKey: 'project-side-project', + ), + ]; +} + /// Top-level helper that performs the `_todaySeedTasks` operation for this file. /// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. List _todaySeedTasks(CivilDate date, DateTime createdAt) { @@ -123,6 +147,284 @@ List _todaySeedTasks(CivilDate date, DateTime createdAt) { ]; } +/// Top-level helper that performs the `_backlogSeedTasks` operation for this file. +/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. +List _backlogSeedTasks(CivilDate date, DateTime createdAt) { + return [ + _backlogSeedTask( + id: 'reply-to-3-emails', + title: 'Reply to 3 emails', + projectId: 'home', + order: 1, + priority: PriorityLevel.medium, + reward: RewardLevel.low, + difficulty: DifficultyLevel.easy, + durationMinutes: 10, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'update-project-status', + title: 'Update project status', + projectId: 'work', + order: 2, + priority: PriorityLevel.medium, + reward: RewardLevel.low, + difficulty: DifficultyLevel.easy, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'schedule-dentist-appt', + title: 'Schedule dentist appt', + projectId: 'personal', + order: 3, + priority: PriorityLevel.medium, + reward: RewardLevel.low, + difficulty: DifficultyLevel.easy, + durationMinutes: 10, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'clean-up-downloads-folder', + title: 'Clean up downloads folder', + projectId: 'home', + order: 4, + priority: PriorityLevel.medium, + reward: RewardLevel.low, + difficulty: DifficultyLevel.easy, + durationMinutes: 10, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'plan-tomorrow', + title: 'Plan tomorrow', + projectId: 'work', + order: 5, + priority: PriorityLevel.medium, + reward: RewardLevel.low, + difficulty: DifficultyLevel.easy, + durationMinutes: 10, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'review-q3-budget', + title: 'Review Q3 budget', + projectId: 'work', + order: 6, + priority: PriorityLevel.high, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.easy, + durationMinutes: 60, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'plan-next-week', + title: 'Plan next week', + projectId: 'work', + order: 7, + priority: PriorityLevel.high, + reward: RewardLevel.high, + difficulty: DifficultyLevel.medium, + durationMinutes: 30, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'define-weekly-goals', + title: 'Define weekly goals', + projectId: 'work', + order: 8, + priority: PriorityLevel.high, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.medium, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'time-block-schedule', + title: 'Time block schedule', + projectId: 'work', + order: 9, + priority: PriorityLevel.high, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.medium, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'organize-kitchen-drawers', + title: 'Organize kitchen drawers', + projectId: 'home', + order: 10, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 45, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'renew-car-registration', + title: 'Renew car registration', + projectId: 'home', + order: 11, + priority: PriorityLevel.high, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 25, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'build-analytics-dashboard', + title: 'Build analytics dashboard', + projectId: 'work', + order: 12, + priority: PriorityLevel.high, + reward: RewardLevel.high, + difficulty: DifficultyLevel.hard, + durationMinutes: 120, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'write-marketing-strategy', + title: 'Write marketing strategy', + projectId: 'work', + order: 13, + priority: PriorityLevel.high, + reward: RewardLevel.high, + difficulty: DifficultyLevel.medium, + durationMinutes: 90, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'complete-online-course', + title: 'Complete online course', + projectId: 'learning', + order: 14, + priority: PriorityLevel.medium, + reward: RewardLevel.high, + difficulty: DifficultyLevel.medium, + durationMinutes: 90, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'module-1-foundations', + title: 'Module 1: Foundations', + projectId: 'learning', + order: 15, + priority: PriorityLevel.medium, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.hard, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'module-2-deep-dive', + title: 'Module 2: Deep Dive', + projectId: 'learning', + order: 16, + priority: PriorityLevel.low, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.hard, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'module-3-practice', + title: 'Module 3: Practice', + projectId: 'learning', + order: 17, + priority: PriorityLevel.low, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.hard, + durationMinutes: 15, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'learn-guitar', + title: 'Learn guitar', + projectId: 'personal', + order: 18, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 30, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'start-youtube-channel', + title: 'Start YouTube channel', + projectId: 'side-project', + order: 19, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 20, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'travel-to-japan', + title: 'Travel to Japan', + projectId: 'personal', + order: 20, + priority: PriorityLevel.medium, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.medium, + durationMinutes: 30, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'home-office-upgrade', + title: 'Home office upgrade', + projectId: 'home', + order: 21, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 20, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'read-2-books', + title: 'Read 2 books', + projectId: 'personal', + order: 22, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 15, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'sketch-app-icon-ideas', + title: 'Sketch app icon ideas', + projectId: 'side-project', + order: 23, + priority: PriorityLevel.medium, + reward: RewardLevel.medium, + difficulty: DifficultyLevel.easy, + durationMinutes: 15, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + _backlogSeedTask( + id: 'archive-photo-backlog', + title: 'Archive photo backlog', + projectId: 'personal', + order: 24, + priority: PriorityLevel.low, + reward: RewardLevel.low, + difficulty: DifficultyLevel.medium, + durationMinutes: 15, + backlogTags: const {BacklogTag.wishlist}, + createdAt: createdAt, + ), + ]; +} + /// Top-level helper that performs the `_seedTask` operation for this file. /// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. Task _seedTask({ @@ -161,3 +463,34 @@ Task _seedTask({ updatedAt: completedAt ?? createdAt, ); } + +/// Top-level helper that performs the `_backlogSeedTask` operation for this file. +/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. +Task _backlogSeedTask({ + required String id, + required String title, + required String projectId, + required int order, + required PriorityLevel priority, + required RewardLevel reward, + required DifficultyLevel difficulty, + required int durationMinutes, + required DateTime createdAt, + Set backlogTags = const {}, +}) { + final orderedCreatedAt = createdAt.add(Duration(minutes: order)); + return Task( + id: id, + title: title, + projectId: projectId, + type: TaskType.flexible, + status: TaskStatus.backlog, + priority: priority, + reward: reward, + difficulty: difficulty, + durationMinutes: durationMinutes, + backlogTags: backlogTags, + createdAt: orderedCreatedAt, + updatedAt: orderedCreatedAt, + ); +} diff --git a/apps/focus_flow_flutter/lib/app/demo_scheduler_composition.dart b/apps/focus_flow_flutter/lib/app/demo_scheduler_composition.dart index 60a5a51..3b36dc3 100644 --- a/apps/focus_flow_flutter/lib/app/demo_scheduler_composition.dart +++ b/apps/focus_flow_flutter/lib/app/demo_scheduler_composition.dart @@ -91,24 +91,13 @@ class DemoSchedulerComposition implements SchedulerAppComposition { CivilDate.fromDateTime((clock ?? const SystemClock()).now()); final readAt = _instant(resolvedDate, 15, 55); final createdAt = _instant(resolvedDate, 8); - final project = ProjectProfile( - id: projectId, - name: 'Home', - colorKey: 'project-home', - ); + final projects = _demoProjects(); final tasks = [ if (includeTodayItems) ..._todaySeedTasks(resolvedDate, createdAt), - if (includeBacklogItems) - Task.quickCapture( - id: 'later-backlog-note', - title: 'Review reusable grocery list', - projectId: projectId, - createdAt: createdAt, - updatedAt: createdAt, - ), + if (includeBacklogItems) ..._backlogSeedTasks(resolvedDate, createdAt), ]; final store = InMemoryApplicationUnitOfWork( - initialProjects: [project], + initialProjects: projects, initialOwnerSettings: [ OwnerSettings( ownerId: ownerId, @@ -191,6 +180,22 @@ class DemoSchedulerComposition implements SchedulerAppComposition { ); } + /// Creates a generic read controller for the Backlog board. + UiReadController createBacklogBoardController() { + return ApplicationReadController( + read: () { + return managementUseCases.getBacklogBoard( + GetBacklogBoardRequest( + context: context('ui-read-backlog-board'), + localDate: date, + sortKey: BacklogSortKey.age, + ), + ); + }, + isEmpty: (state) => state.summary.totalTaskCount == 0, + ); + } + /// Creates the command controller used by interactive scheduler controls. @override SchedulerCommandController createCommandController({ diff --git a/apps/focus_flow_flutter/test/app/demo_backlog_seed_test.dart b/apps/focus_flow_flutter/test/app/demo_backlog_seed_test.dart new file mode 100644 index 0000000..fe57096 --- /dev/null +++ b/apps/focus_flow_flutter/test/app/demo_backlog_seed_test.dart @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2026 FocusFlow contributors +// SPDX-License-Identifier: AGPL-3.0-only + +/// Tests Demo Backlog Seed behavior in the FocusFlow Flutter app. +library; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:scheduler_core/scheduler_core.dart'; + +import 'package:focus_flow_flutter/app/demo_scheduler_composition.dart'; + +/// Runs demo Backlog seed tests. +void main() { + test('seeded backlog board exposes mockup-scale domain data', () async { + final composition = DemoSchedulerComposition.seeded( + selectedDate: CivilDate(2025, 5, 20), + ); + addTearDown(composition.dispose); + + final board = (await composition.managementUseCases.getBacklogBoard( + GetBacklogBoardRequest( + context: composition.context('demo-backlog-board'), + localDate: composition.date, + sortKey: BacklogSortKey.age, + ), + )).requireValue; + final items = board.columns.expand((column) => column.items).toList(); + + expect(board.summary.totalTaskCount, 24); + expect(board.summary.totalEstimatedMinutes, 735); + expect(_distributionCounts(board.summary.projectDistribution), { + 'Home': 5, + 'Learning': 4, + 'Personal': 5, + 'Side Project': 2, + 'Work': 8, + }); + expect(_distributionCounts(board.summary.priorityDistribution), { + 'High': 7, + 'Medium': 9, + 'Low': 8, + }); + expect(items.map((item) => item.title), containsAll(_visibleSeedTitles)); + + final review = items.singleWhere( + (item) => item.title == 'Review Q3 budget', + ); + expect(review.projectName, 'Work'); + expect(review.durationMinutes, 60); + expect(review.priority, PriorityLevel.high); + expect(review.reward, RewardLevel.medium); + expect(review.difficulty, DifficultyLevel.easy); + }); + + test('seeded review detail stays within deferred metadata scope', () async { + final composition = DemoSchedulerComposition.seeded( + selectedDate: CivilDate(2025, 5, 20), + ); + addTearDown(composition.dispose); + + final detail = (await composition.managementUseCases.getBacklogTaskDetail( + GetBacklogTaskDetailRequest( + context: composition.context('demo-review-detail'), + localDate: composition.date, + taskId: 'review-q3-budget', + ), + )).requireValue; + + expect(detail.item.title, 'Review Q3 budget'); + expect(detail.notes, isNull); + expect(detail.tags, isEmpty); + expect(detail.suggestedSlots, isNotEmpty); + }); +} + +/// Visible task titles from the Backlog board mockup that must be present in the seed. +const _visibleSeedTitles = [ + 'Reply to 3 emails', + 'Update project status', + 'Schedule dentist appt', + 'Clean up downloads folder', + 'Plan tomorrow', + 'Review Q3 budget', + 'Plan next week', + 'Define weekly goals', + 'Time block schedule', + 'Organize kitchen drawers', + 'Build analytics dashboard', + 'Write marketing strategy', + 'Complete online course', + 'Module 1: Foundations', + 'Module 2: Deep Dive', + 'Module 3: Practice', + 'Learn guitar', + 'Start YouTube channel', + 'Travel to Japan', + 'Home office upgrade', + 'Read 2 books', +]; + +/// Top-level helper that performs the `_distributionCounts` operation for this file. +/// Keeping the helper private makes the surrounding flow easier to read while preventing unrelated libraries from depending on implementation detail. +Map _distributionCounts( + BacklogDistributionReadModel distribution, +) { + return {for (final entry in distribution.entries) entry.label: entry.count}; +}