<!-- SPDX-FileCopyrightText: 2026 FocusFlow contributors -->
<!-- SPDX-License-Identifier: AGPL-3.0-only -->

# Persistence Plan 1 Block 04 — Flutter Runtime Composition

**Status:** Complete on 2026-07-02.
**Goal:** Build a production-like Flutter composition root that opens SQLite,
bootstraps required owner state, creates scheduler use cases, and disposes the
database cleanly.

---

## Block outcome

After this block, the Flutter app should be able to choose between a persistent
SQLite composition for normal runtime and a deterministic demo/test composition
for widget tests. Normal `main()` should be ready to use the persistent
composition, but the final demo-seed retirement happens in Block 05.

---

## Chunk 4.1 — Add runtime dependencies without polluting widgets

### Tasks

1. Add only the dependencies needed by the chosen boundary:
   - If using direct app composition, add app path dependency on
     `scheduler_persistence_sqlite` and any SQLite native support package needed
     by Flutter desktop.
   - If using a runtime package, add app path dependency on that runtime package
     only.
2. Keep widgets/controllers importing public scheduler app/controller APIs only.
3. Keep `package:drift/`, `package:scheduler_persistence_sqlite/`, and `dart:io`
   out of widgets/controllers.
4. Update forbidden import tests to enforce the exact allowed boundary.

### Acceptance criteria

- `flutter pub get` resolves.
- Forbidden import test still prevents SQL/Drift imports outside the approved
  composition/runtime boundary.
- Backend packages remain in the root Dart workspace; Flutter app remains outside
  the root workspace unless a separate plan changes that.

---

## Chunk 4.2 — Read SQLite path from configuration

### Tasks

1. Read the configured SQLite path from:

   ```dart
   const String.fromEnvironment('SCHEDULER_SQLITE_PATH')
   ```

2. If no dart define is present, use the existing default scheduler SQLite path
   convention, but keep path resolution in the runtime/composition layer.
3. In tests, allow injecting a temporary path or in-memory executor.
4. Do not call `DateTime.now()` or path APIs inside widgets.
5. Ensure parent directories are created before opening the database.
6. Ensure an empty placeholder file created by `scripts/bootstrap_dev.sh` does
   not break Drift schema creation.

### Acceptance criteria

- `scripts/dev.dart --sqlite /tmp/scheduler.sqlite` controls the app DB path.
- Tests can provide isolated temp database files.
- Runtime path resolution is not duplicated in UI widgets.

---

## Chunk 4.3 — Build persistent scheduler composition

### Suggested files

Use names that fit the chosen boundary. Possible direct-app layout:

```text
apps/focus_flow_flutter/lib/app/persistent_scheduler_composition.dart
apps/focus_flow_flutter/lib/app/runtime/sqlite_path_config.dart
apps/focus_flow_flutter/lib/app/runtime/scheduler_runtime_lifecycle.dart
```

Possible runtime-package layout:

```text
packages/scheduler_runtime/lib/scheduler_runtime.dart
packages/scheduler_runtime/lib/src/sqlite_scheduler_runtime.dart
apps/focus_flow_flutter/lib/app/persistent_scheduler_composition.dart
```

### Tasks

1. Create a persistent composition that provides the same high-level factory
   surface currently expected by `FocusFlowApp`/`FocusFlowHome`:
   - today screen controller,
   - command controller,
   - operation context factory,
   - selected date/date label,
   - read refresh callbacks.
2. Internally use:
   - SQLite-backed `ApplicationUnitOfWork`,
   - `GetTodayStateQuery`,
   - `V1ApplicationManagementUseCases`,
   - `V1ApplicationCommandUseCases`,
   - existing timezone resolver policy.
3. Preserve a fixed V1 owner id until a settings/account plan changes it.
4. Keep the current app interface small. Prefer defining an abstract composition
   protocol if both demo and persistent compositions need to satisfy it.

### Acceptance criteria

- `FocusFlowApp` no longer needs to know whether its composition is in-memory or
  SQLite-backed.
- The persistent composition can load Today state from SQLite without seeded
  tasks.
- Widget code still consumes controllers/read models.

---

## Chunk 4.4 — Bootstrap required owner state

### Tasks

1. Add a startup bootstrap step that runs before first read.
2. If owner settings do not exist, insert defaults:
   - owner id matching current app owner,
   - timezone id matching current runtime resolver,
   - compact mode enabled if this remains the current UI mode.
3. If no default project exists, create a stable default project.
4. Decide the default project id for V1 quick capture:
   - Preferred: move current UI hardcoded `home` project id into the composition
     or settings/defaults boundary.
   - Alternative: use `inbox` if that is the canonical app-layer default, and
     update quick capture wiring and tests consistently.
5. Do not bootstrap demo timeline tasks.
6. Make bootstrap idempotent across app restarts.
7. Run bootstrap inside the SQLite unit-of-work transaction boundary.

### Acceptance criteria

- First app launch on an empty DB shows an empty real Today state, not a failure.
- Quick capture has a valid project id without hardcoded widget knowledge.
- Reopening the app does not duplicate default projects/settings.

---

## Chunk 4.5 — Add lifecycle disposal

### Tasks

1. Add a `dispose` or `close` method to the persistent composition/runtime.
2. Ensure `FocusFlowHome` or the app root disposes controllers first and DB last.
3. Ensure tests can close the database deterministically before reopening the
   same path.
4. Do not leave long-lived SQLite handles open across tests.

### Acceptance criteria

- App shutdown closes the database cleanly.
- Close/reopen tests do not flake due to locked files.
- No widget owns a raw database handle.

---

## Chunk 4.6 — Add composition-level tests

### Required tests

1. Persistent composition opens an empty temp DB and loads an empty Today state.
2. Persistent composition bootstraps exactly one settings row and one default
   project row.
3. Persistent composition quick captures a task and refreshes read controllers.
4. Persistent composition can be disposed and reopened against the same file.
5. Demo composition remains available for deterministic visual tests, if kept.

### Acceptance criteria

- Tests run under Flutter only if they need Flutter bindings; otherwise prefer
  pure Dart package tests.
- No tests rely on static mockup tasks for persistence proof.

---

## Block acceptance criteria

- Persistent composition exists and can open/configure SQLite.
- Owner settings/default project bootstrap is idempotent.
- Runtime DB path is controlled by `SCHEDULER_SQLITE_PATH` or injected test path.
- Composition lifecycle closes DB resources.
- Widget/controller package boundaries remain enforced.

## Validation after block

```sh
cd apps/focus_flow_flutter
flutter pub get
flutter analyze
flutter test
cd ../..
scripts/test.sh
```
