feat(ui): complete command flow slice
This commit is contained in:
parent
a799dea62e
commit
f059ba9986
3 changed files with 45 additions and 12 deletions
|
|
@ -105,7 +105,7 @@ quick-capture smoke slice.
|
||||||
## Chunk 29.3 — Command flows and vertical quick-capture slice
|
## Chunk 29.3 — Command flows and vertical quick-capture slice
|
||||||
Recommended level: **HIGH**
|
Recommended level: **HIGH**
|
||||||
|
|
||||||
Status: Open.
|
Status: Complete on 2026-06-28.
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
1. Add UI controllers for quick capture, schedule-from-backlog, and mark-done
|
1. Add UI controllers for quick capture, schedule-from-backlog, and mark-done
|
||||||
|
|
@ -126,6 +126,28 @@ Status: Open.
|
||||||
BREAKPOINT: Stop here. Confirm `high` before adding persistent SQLite-backed UI
|
BREAKPOINT: Stop here. Confirm `high` before adding persistent SQLite-backed UI
|
||||||
composition or broader screen expansion.
|
composition or broader screen expansion.
|
||||||
|
|
||||||
|
### Completed implementation
|
||||||
|
1. Added `SchedulerCommandController` command states and public-use-case-backed
|
||||||
|
command methods for quick capture, schedule-from-backlog, and mark-done.
|
||||||
|
2. Wired the command controller into the app shell, Backlog pane, and Today pane,
|
||||||
|
with successful commands refreshing both read controllers.
|
||||||
|
3. Added title-only quick capture, duration entry/schedule actions, done actions,
|
||||||
|
and command status/failure feedback in the Flutter widgets.
|
||||||
|
4. Added a vertical widget smoke test for quick capture → Backlog → schedule →
|
||||||
|
Today → done using the in-memory demo composition.
|
||||||
|
5. Bounded widget-test settling through a shared helper so `pumpAndSettle` cannot
|
||||||
|
wait indefinitely.
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
1. `scripts/bootstrap_dev.sh`: passed.
|
||||||
|
2. `cd apps/focus_flow_flutter && flutter analyze`: passed, no issues found.
|
||||||
|
3. `cd apps/focus_flow_flutter && flutter test`: passed, 5 widget tests.
|
||||||
|
4. `scripts/test.sh`: passed, including root analyze, 338 Dart tests, 83.27%
|
||||||
|
package coverage, documentation checks, and API doc generation.
|
||||||
|
5. Forbidden-import scan for app `lib/` and `test/`: no persistence adapters,
|
||||||
|
scheduler `src/` imports, Drift, SQLite, platform APIs, or `dart:io` imports
|
||||||
|
found.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,10 @@ class _SchedulerHomeState extends State<SchedulerHome> {
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: selectedIndex,
|
index: selectedIndex,
|
||||||
children: [
|
children: [
|
||||||
TodayPane(controller: todayController),
|
TodayPane(
|
||||||
|
controller: todayController,
|
||||||
|
commandController: commandController,
|
||||||
|
),
|
||||||
BacklogPane(
|
BacklogPane(
|
||||||
controller: backlogController,
|
controller: backlogController,
|
||||||
commandController: commandController,
|
commandController: commandController,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ void main() {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
|
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
|
||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(find.text('FocusFlow'), findsOneWidget);
|
expect(find.text('FocusFlow'), findsOneWidget);
|
||||||
expect(find.text('Today'), findsWidgets);
|
expect(find.text('Today'), findsWidgets);
|
||||||
|
|
@ -19,7 +19,7 @@ void main() {
|
||||||
expect(find.text('Appointment'), findsWidgets);
|
expect(find.text('Appointment'), findsWidgets);
|
||||||
|
|
||||||
await tester.tap(find.byIcon(Icons.inbox_outlined));
|
await tester.tap(find.byIcon(Icons.inbox_outlined));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(find.text('Backlog'), findsWidgets);
|
expect(find.text('Backlog'), findsWidgets);
|
||||||
expect(find.text('Send project update'), findsOneWidget);
|
expect(find.text('Send project update'), findsOneWidget);
|
||||||
|
|
@ -30,7 +30,7 @@ void main() {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
|
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
|
||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(find.text('Compact'), findsOneWidget);
|
expect(find.text('Compact'), findsOneWidget);
|
||||||
expect(find.text('Plan the day'), findsNWidgets(2));
|
expect(find.text('Plan the day'), findsNWidgets(2));
|
||||||
|
|
@ -70,7 +70,7 @@ void main() {
|
||||||
expect(find.text('Status: conflict'), findsOneWidget);
|
expect(find.text('Status: conflict'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.text('Retry'));
|
await tester.tap(find.text('Retry'));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(controller.retryCount, 1);
|
expect(controller.retryCount, 1);
|
||||||
expect(find.text('Backlog is clear'), findsOneWidget);
|
expect(find.text('Backlog is clear'), findsOneWidget);
|
||||||
|
|
@ -85,38 +85,46 @@ void main() {
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(FocusFlowApp(composition: composition));
|
await tester.pumpWidget(FocusFlowApp(composition: composition));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
await tester.tap(find.byIcon(Icons.inbox_outlined));
|
await tester.tap(find.byIcon(Icons.inbox_outlined));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
await tester.enterText(
|
await tester.enterText(
|
||||||
find.byKey(const ValueKey('quick-capture-title')),
|
find.byKey(const ValueKey('quick-capture-title')),
|
||||||
'Water plants',
|
'Water plants',
|
||||||
);
|
);
|
||||||
await tester.tap(find.text('Capture'));
|
await tester.tap(find.text('Capture'));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(find.text('Water plants'), findsOneWidget);
|
expect(find.text('Water plants'), findsOneWidget);
|
||||||
expect(composition.store.currentTasks.single.status, TaskStatus.backlog);
|
expect(composition.store.currentTasks.single.status, TaskStatus.backlog);
|
||||||
|
|
||||||
await tester.enterText(find.byType(TextField).last, '20');
|
await tester.enterText(find.byType(TextField).last, '20');
|
||||||
await tester.tap(find.byTooltip('Schedule'));
|
await tester.tap(find.byTooltip('Schedule'));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
await tester.tap(find.byIcon(Icons.today_outlined));
|
await tester.tap(find.byIcon(Icons.today_outlined));
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(find.text('Water plants'), findsWidgets);
|
expect(find.text('Water plants'), findsWidgets);
|
||||||
expect(composition.store.currentTasks.single.status, TaskStatus.planned);
|
expect(composition.store.currentTasks.single.status, TaskStatus.planned);
|
||||||
|
|
||||||
await tester.tap(find.byTooltip('Done').first);
|
await tester.tap(find.byTooltip('Done').first);
|
||||||
await tester.pumpAndSettle();
|
await _pumpAndSettle(tester);
|
||||||
|
|
||||||
expect(composition.store.currentTasks.single.status, TaskStatus.completed);
|
expect(composition.store.currentTasks.single.status, TaskStatus.completed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<int> _pumpAndSettle(WidgetTester tester) {
|
||||||
|
return tester.pumpAndSettle(
|
||||||
|
const Duration(milliseconds: 50),
|
||||||
|
EnginePhase.sendSemanticsUpdate,
|
||||||
|
const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _testShell(Widget child) {
|
Widget _testShell(Widget child) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue