feat(ui): complete command flow slice

This commit is contained in:
Ashley Venn 2026-06-27 19:01:26 -07:00
parent a799dea62e
commit f059ba9986
3 changed files with 45 additions and 12 deletions

View file

@ -105,7 +105,7 @@ quick-capture smoke slice.
## Chunk 29.3 — Command flows and vertical quick-capture slice
Recommended level: **HIGH**
Status: Open.
Status: Complete on 2026-06-28.
### Tasks
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
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.
---
---

View file

@ -67,7 +67,10 @@ class _SchedulerHomeState extends State<SchedulerHome> {
body: IndexedStack(
index: selectedIndex,
children: [
TodayPane(controller: todayController),
TodayPane(
controller: todayController,
commandController: commandController,
),
BacklogPane(
controller: backlogController,
commandController: commandController,

View file

@ -11,7 +11,7 @@ void main() {
await tester.pumpWidget(
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
);
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(find.text('FocusFlow'), findsOneWidget);
expect(find.text('Today'), findsWidgets);
@ -19,7 +19,7 @@ void main() {
expect(find.text('Appointment'), findsWidgets);
await tester.tap(find.byIcon(Icons.inbox_outlined));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(find.text('Backlog'), findsWidgets);
expect(find.text('Send project update'), findsOneWidget);
@ -30,7 +30,7 @@ void main() {
await tester.pumpWidget(
FocusFlowApp(composition: DemoSchedulerComposition.seeded()),
);
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(find.text('Compact'), findsOneWidget);
expect(find.text('Plan the day'), findsNWidgets(2));
@ -70,7 +70,7 @@ void main() {
expect(find.text('Status: conflict'), findsOneWidget);
await tester.tap(find.text('Retry'));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(controller.retryCount, 1);
expect(find.text('Backlog is clear'), findsOneWidget);
@ -85,38 +85,46 @@ void main() {
);
await tester.pumpWidget(FocusFlowApp(composition: composition));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
await tester.tap(find.byIcon(Icons.inbox_outlined));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
await tester.enterText(
find.byKey(const ValueKey('quick-capture-title')),
'Water plants',
);
await tester.tap(find.text('Capture'));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(find.text('Water plants'), findsOneWidget);
expect(composition.store.currentTasks.single.status, TaskStatus.backlog);
await tester.enterText(find.byType(TextField).last, '20');
await tester.tap(find.byTooltip('Schedule'));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
await tester.tap(find.byIcon(Icons.today_outlined));
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
expect(find.text('Water plants'), findsWidgets);
expect(composition.store.currentTasks.single.status, TaskStatus.planned);
await tester.tap(find.byTooltip('Done').first);
await tester.pumpAndSettle();
await _pumpAndSettle(tester);
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) {
return MaterialApp(
theme: ThemeData(