forked from eva/focus-flow
feat(ui): complete plan 1 scope guardrails
This commit is contained in:
parent
c6a6eb4297
commit
b4357ae6b5
35 changed files with 2110 additions and 23 deletions
|
|
@ -0,0 +1,7 @@
|
|||
# UI Plan 1 - Compact Today Timeline Mockup
|
||||
|
||||
**Status:** Planned
|
||||
|
||||
Start with `UI_PLAN_1_SUMMARY.md`, then execute the block files in `UI_PLAN_1_EXECUTION_ORDER.md` order.
|
||||
|
||||
This plan implements only the compact Today timeline screen and selected-task modal shown in the bundled mockups. All visible controls are intentionally no-op except task-card selection and modal dismissal.
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
# UI Plan 1 Block 01 — Repo Orientation and Scope Guardrails
|
||||
|
||||
**Status:** Complete on 2026-06-30.
|
||||
**Goal:** Establish the Flutter app workspace and guardrails before replacing/building the UI slice.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, Codex should know whether `apps/focus_flow_flutter/` exists in the actual working repo, should have a safe Flutter app target available, and should have basic guardrails preventing UI code from leaking into core or persistence internals.
|
||||
|
||||
No visual polish is required in this block beyond a runnable empty shell if a new app must be created.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1.1 — Read project rules and verify repo state
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Read `AGENTS.md` completely.
|
||||
2. Read `Codex Documentation/README.md`.
|
||||
3. Read this plan summary and all mockup references.
|
||||
4. Check whether `apps/focus_flow_flutter/` exists.
|
||||
5. Check whether root `pubspec.yaml` includes the Flutter app in the Dart workspace. It should not unless repo instructions have changed.
|
||||
6. Check whether completed plan docs claim prior UI work exists. Treat those docs as historical unless files actually exist.
|
||||
7. Record a short implementation note in the block completion summary:
|
||||
- app directory existed/missing,
|
||||
- any existing Flutter files preserved/replaced,
|
||||
- validation commands available in the environment.
|
||||
|
||||
### Design decisions
|
||||
|
||||
- Repo files win over historical planning docs for code truth.
|
||||
- If the app directory is missing, create it under `apps/focus_flow_flutter/`.
|
||||
- If the app directory exists, preserve package names and existing test harness where reasonable, but replace the provisional UI with this plan's UI.
|
||||
- Do not touch backend package code unless the app cannot compile due to a public API issue. If that happens, stop and report the mismatch rather than silently changing domain contracts.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Codex can state exactly where the Flutter app lives.
|
||||
- No backend/core files are modified in this chunk.
|
||||
- No current plan docs are moved or archived.
|
||||
|
||||
BREAKPOINT: Stop after repo orientation if the app directory state is unclear or conflicts with `AGENTS.md`.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1.2 — Create or reconcile the Flutter app target
|
||||
|
||||
### Tasks when the app is missing
|
||||
|
||||
1. Create `apps/focus_flow_flutter/` as a Flutter desktop app.
|
||||
2. Keep it outside the root Dart workspace.
|
||||
3. Add local path dependencies needed for this slice:
|
||||
- `scheduler_core` via `../../packages/scheduler_core`.
|
||||
4. Do not add SQLite, Drift, notification, backup, export, or platform-specific adapter dependencies.
|
||||
5. Remove/replace the counter-template UI.
|
||||
6. Keep generated platform folders only as required by Flutter desktop creation.
|
||||
|
||||
Suggested command if creating from scratch:
|
||||
|
||||
```sh
|
||||
flutter create --platforms=macos,windows,linux apps/focus_flow_flutter
|
||||
```
|
||||
|
||||
If Codex cannot run `flutter create`, manually create the minimal Flutter app structure only if the repository already contains enough Flutter scaffolding conventions. Otherwise stop and report the missing toolchain.
|
||||
|
||||
### Tasks when the app exists
|
||||
|
||||
1. Inspect existing `pubspec.yaml`, `lib/`, and `test/`.
|
||||
2. Preserve useful app-local package configuration.
|
||||
3. Replace only provisional UI code needed by this plan.
|
||||
4. Keep any existing app-local lint/analysis configuration unless it conflicts with repo rules.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- `apps/focus_flow_flutter/pubspec.yaml` exists.
|
||||
- The app can depend on scheduler public API packages through path dependencies.
|
||||
- Root `pubspec.yaml` remains a Dart package workspace and does not list the Flutter app.
|
||||
|
||||
BREAKPOINT: Stop if creating the app would require changing root workspace semantics.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1.3 — Establish package-boundary guardrails
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Add a small test or scriptable check in the Flutter app test suite that scans app `lib/` and `test/` files for forbidden imports.
|
||||
2. Forbidden app imports include:
|
||||
- `package:scheduler_core/src/`
|
||||
- `package:scheduler_persistence_sqlite/`
|
||||
- `package:drift/`
|
||||
- `dart:io` for UI/runtime code in this slice,
|
||||
- notification desktop/platform implementation imports,
|
||||
- backup/export implementation imports.
|
||||
3. Allow `package:scheduler_core/scheduler_core.dart`.
|
||||
4. Document this check in the app README or implementation handoff.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- A failing forbidden import produces a clear test failure.
|
||||
- The check does not require external tools beyond Flutter/Dart test execution.
|
||||
- UI Plan 1 can proceed without touching core package internals.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1.4 — Create the high-level app file layout
|
||||
|
||||
### Suggested files
|
||||
|
||||
Create or reconcile this structure:
|
||||
|
||||
```text
|
||||
apps/focus_flow_flutter/lib/
|
||||
main.dart
|
||||
app/focus_flow_app.dart
|
||||
app/demo_scheduler_composition.dart
|
||||
controllers/today_screen_controller.dart
|
||||
models/today_screen_models.dart
|
||||
theme/focus_flow_theme.dart
|
||||
theme/focus_flow_tokens.dart
|
||||
widgets/app_shell.dart
|
||||
widgets/sidebar.dart
|
||||
widgets/top_bar.dart
|
||||
widgets/required_banner.dart
|
||||
widgets/timeline/timeline_geometry.dart
|
||||
widgets/timeline/timeline_axis.dart
|
||||
widgets/timeline/timeline_view.dart
|
||||
widgets/timeline/task_timeline_card.dart
|
||||
widgets/timeline/reward_icon.dart
|
||||
widgets/timeline/difficulty_bars.dart
|
||||
widgets/task_selection_modal.dart
|
||||
```
|
||||
|
||||
Exact names may differ if existing conventions are stronger, but preserve the architectural separation:
|
||||
|
||||
- composition/controller code outside widgets,
|
||||
- theme/tokens centralized,
|
||||
- timeline geometry isolated,
|
||||
- reward/difficulty icons isolated,
|
||||
- modal isolated.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- The file layout is understandable without opening every file.
|
||||
- The widget tree does not own seed data.
|
||||
- The controller/composition layer is the only place that knows how to query scheduler application state.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- Flutter app target exists or is reconciled.
|
||||
- App is outside the root Dart workspace.
|
||||
- App dependency boundary is public and minimal.
|
||||
- Forbidden-import check exists or is planned in a concrete test file.
|
||||
- No backend package code is changed.
|
||||
- Any inability to run Flutter tooling is explicitly noted.
|
||||
|
||||
## Validation after block
|
||||
|
||||
Run what is available:
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter pub get
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
If app code is not visual yet, tests may be minimal but should pass.
|
||||
|
||||
---
|
||||
|
||||
## Completed implementation
|
||||
|
||||
1. Read `AGENTS.md`, `Codex Documentation/README.md`, the full UI Plan 1
|
||||
summary/execution order, Blocks 01-06, and all clean/annotated mockups.
|
||||
2. Verified `apps/focus_flow_flutter/` already exists in the working repo with
|
||||
Linux, macOS, and Windows desktop scaffolding.
|
||||
3. Verified the root Dart workspace does not include the Flutter app.
|
||||
4. Preserved the existing Flutter app package, local `scheduler_core` path
|
||||
dependency, lint setup, platform folders, and provisional test harness.
|
||||
5. Routed the app entry through `lib/app/` facade files and added the
|
||||
UI Plan 1 high-level folders/files for app, controller, model, theme, shell,
|
||||
timeline, icon, and modal responsibilities.
|
||||
6. Added `test/forbidden_imports_test.dart` to fail clearly on scheduler `src/`,
|
||||
SQLite/Drift, desktop notification, backup/export, and direct OS imports.
|
||||
7. Replaced the app README starter text with Block 1 scope, boundary, and
|
||||
validation notes.
|
||||
|
||||
Implementation note:
|
||||
|
||||
- App directory state: `apps/focus_flow_flutter/` existed.
|
||||
- Existing Flutter files preserved/replaced: package configuration, platform
|
||||
scaffolding, existing controllers/widgets, and tests were preserved; app entry
|
||||
imports were reconciled through the new `lib/app/` facade, and plan-oriented
|
||||
placeholder files were added for later blocks.
|
||||
- Validation commands available: `scripts/bootstrap_dev.sh`, `flutter pub get`,
|
||||
`flutter analyze`, and `flutter test`.
|
||||
|
||||
## Verification
|
||||
|
||||
1. `scripts/bootstrap_dev.sh`: passed.
|
||||
2. `flutter pub get` in `apps/focus_flow_flutter`: passed.
|
||||
3. `dart format lib test` in `apps/focus_flow_flutter`: passed.
|
||||
4. `flutter analyze` in `apps/focus_flow_flutter`: passed, no issues found.
|
||||
5. `flutter test` in `apps/focus_flow_flutter`: passed, including the new
|
||||
forbidden-import boundary test.
|
||||
6. `scripts/test.sh`: passed, including root `dart analyze`, 338 Dart tests,
|
||||
83.27% package coverage, documentation checks, and API doc generation.
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
# UI Plan 1 Block 02 — App Shell and Visual System
|
||||
|
||||
**Status:** Planned
|
||||
**Goal:** Build the dark desktop shell and static visual system that matches the compact mockup frame.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, the app should render the full static desktop frame:
|
||||
|
||||
- dark app surface,
|
||||
- sidebar,
|
||||
- header/top controls,
|
||||
- compact/normal segmented control,
|
||||
- settings button,
|
||||
- required banner placeholder,
|
||||
- timeline content area placeholder.
|
||||
|
||||
The actual seeded timeline cards are implemented in later blocks.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.1 — Define visual tokens
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `theme/focus_flow_tokens.dart` with centralized tokens for:
|
||||
|
||||
1. App background and panels:
|
||||
- near-black app background,
|
||||
- elevated panel background,
|
||||
- glass/magenta panel overlay,
|
||||
- border color variants.
|
||||
2. Text colors:
|
||||
- primary white,
|
||||
- secondary muted gray/lavender,
|
||||
- disabled/completed gray,
|
||||
- accent magenta,
|
||||
- task-type colors.
|
||||
3. Task semantic colors:
|
||||
- flexible/green,
|
||||
- required/critical pink,
|
||||
- inflexible/appointment blue,
|
||||
- free-slot/rest purple,
|
||||
- completed/surprise gray,
|
||||
- neutral outline.
|
||||
4. Radii:
|
||||
- app frame,
|
||||
- sidebar nav item,
|
||||
- banner,
|
||||
- card,
|
||||
- modal,
|
||||
- small icon button.
|
||||
5. Spacing:
|
||||
- sidebar width,
|
||||
- main content gutter,
|
||||
- header height,
|
||||
- timeline rail width,
|
||||
- card horizontal padding.
|
||||
6. Typography sizes/weights:
|
||||
- page title,
|
||||
- card title,
|
||||
- card metadata,
|
||||
- sidebar nav,
|
||||
- modal title,
|
||||
- modal button.
|
||||
|
||||
### Design guidance
|
||||
|
||||
- Do not add external font/network dependencies for this block.
|
||||
- Use Flutter text styles and system fonts.
|
||||
- It is okay if exact mockup font metrics differ slightly.
|
||||
- Keep tokens plain Dart/Flutter constants. Do not reach into scheduler core.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Colors and dimensions are not scattered as magic numbers across widgets.
|
||||
- Later card/modal widgets can reuse tokens.
|
||||
- No theme token imports any scheduler package.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.2 — Build `FocusFlowApp` and root theme
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Replace the counter template with `FocusFlowApp`.
|
||||
2. Use `MaterialApp` or `WidgetsApp` with:
|
||||
- debug banner hidden,
|
||||
- dark theme,
|
||||
- transparent/near-black scaffold background,
|
||||
- no white default Material surfaces.
|
||||
3. Render the app shell directly on launch.
|
||||
4. Ensure initial app size/responsiveness works at desktop mockup proportions.
|
||||
5. Avoid adding routes/navigation for this plan.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Launching the app shows the FocusFlow shell, not a counter app.
|
||||
- There is no white flash/background in steady state.
|
||||
- `FocusFlowApp` can receive a controller/composition later rather than creating all state inline.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.3 — Build the desktop app shell frame
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/app_shell.dart` that composes:
|
||||
|
||||
1. Outer app frame.
|
||||
2. Sidebar slot.
|
||||
3. Main content slot.
|
||||
4. Minimum desktop width behavior.
|
||||
5. Optional horizontal scroll/flexible scaling for narrower windows.
|
||||
|
||||
Suggested layout:
|
||||
|
||||
```dart
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(width: tokens.sidebarWidth, child: Sidebar(...)),
|
||||
Expanded(child: MainTodayArea(...)),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Visual requirements
|
||||
|
||||
- The shell background should match the clean mockups.
|
||||
- Main content should not touch the outer frame edge.
|
||||
- Keep sidebar and main content separated by subtle border/contrast.
|
||||
- No drop shadows that make the app feel heavy or busy.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Sidebar and main area are both visible at 1586×992.
|
||||
- The shell is stable under `flutter test` widget pumping.
|
||||
- Shell has no scheduler/domain dependency.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.4 — Build the sidebar
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/sidebar.dart` with:
|
||||
|
||||
1. Decorative window controls.
|
||||
2. Brand icon and `FocusFlow` text.
|
||||
3. Nav items:
|
||||
- Today active,
|
||||
- Backlog inactive,
|
||||
- Projects inactive,
|
||||
- Reports inactive,
|
||||
- Settings inactive below divider.
|
||||
4. Icon approximations using either built-in icons or simple custom painters.
|
||||
5. All nav rows no-op except Today's active visual state.
|
||||
|
||||
### Visual requirements
|
||||
|
||||
- Today item has magenta-tinted active background and border.
|
||||
- Inactive rows have white icon/text with lower opacity.
|
||||
- Sidebar spacing should closely follow the mockup.
|
||||
- Use no-op `onTap` callbacks rather than disabled widgets.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Widget tests can find all nav labels.
|
||||
- Today is visually marked active through a stable key/semantic or widget property.
|
||||
- Inactive nav items do not navigate or mutate app state.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.5 — Build the top bar and segmented toggle
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/top_bar.dart` with:
|
||||
|
||||
1. `Today` title.
|
||||
2. Calendar icon button.
|
||||
3. Previous arrow icon button.
|
||||
4. Date label derived from selected date display text.
|
||||
5. Next arrow icon button.
|
||||
6. Segmented control with `Compact` selected and `Normal` unselected.
|
||||
7. Settings button visible/no-op.
|
||||
|
||||
### Behavior requirements
|
||||
|
||||
- Calendar/arrow/settings/Normal controls are no-op.
|
||||
- Compact remains visually selected.
|
||||
- Date label comes from controller/display model, not a widget constant.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Tests can assert `Today`, `Compact`, `Normal`, and `Settings` are present.
|
||||
- Controls look enabled but do not alter state.
|
||||
- Date label can be fixture-controlled in tests.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2.6 — Build the required banner frame
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/required_banner.dart` that takes a display model:
|
||||
|
||||
```dart
|
||||
class RequiredBannerModel {
|
||||
final String title;
|
||||
final String timeText;
|
||||
}
|
||||
```
|
||||
|
||||
or use equivalent existing UI model.
|
||||
|
||||
For this block, allow a placeholder model. Block 3 will wire it to backend state.
|
||||
|
||||
### Visual requirements
|
||||
|
||||
- Magenta/pink glass panel with border.
|
||||
- Sparkle icon on the left.
|
||||
- Text format: `Next required task: Pay bill at 6:00 PM`.
|
||||
- `Show upcoming` button on the right, no-op.
|
||||
- Proper text emphasis: label neutral, task title accent.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Banner component can render with a null/empty model if no required task exists, but default seed path should show the banner.
|
||||
- Button looks enabled but is inert.
|
||||
- Component has no scheduler imports; it accepts display text/model.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- Static shell visually resembles the mockup frame.
|
||||
- App has no white background.
|
||||
- No-op controls look enabled.
|
||||
- Top-level components are separated into focused files.
|
||||
- Backend/core remains untouched.
|
||||
|
||||
## Validation after block
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
At this point tests should cover shell/sidebar/top bar/banner presence.
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
# UI Plan 1 Block 03 — Backend-Backed Static Today Seed
|
||||
|
||||
**Status:** Planned
|
||||
**Goal:** Seed the current day through scheduler domain/application state and query it for the UI.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, the UI should no longer render placeholder timeline data. It should query a deterministic in-memory backend/application composition and receive display-ready Today state from scheduler public APIs.
|
||||
|
||||
The UI still does not implement real mutations. The seed represents the state as if real calls had already created/scheduled/logged the tasks.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3.1 — Define current-day seed policy
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Add `app/demo_scheduler_composition.dart` or equivalent.
|
||||
2. Centralize seed creation in this file, not in widgets.
|
||||
3. Runtime selected date should default to current owner-local day.
|
||||
4. Widget tests may inject a fixed clock/date matching the mockup.
|
||||
5. Use UTC instants for seed storage/query fixture unless a repo timezone resolver already exists.
|
||||
6. Do not call `DateTime.now()` inside widgets.
|
||||
|
||||
### Date handling rule
|
||||
|
||||
Use this separation:
|
||||
|
||||
- `selectedDate`: `CivilDate.fromDateTime(clock.now())` or equivalent owner-local resolver.
|
||||
- `seed task instants`: derived from `selectedDate` at fixed local display times.
|
||||
- tests: use a `FixedClock` to make `selectedDate` deterministic.
|
||||
|
||||
If exact local timezone conversion is not available in app code yet, use `FixedOffsetTimeZoneResolver.utc()` and document that local timezone display is a later integration concern. Do not invent a full timezone service in this UI slice.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Date and seed data are deterministic under test.
|
||||
- Runtime does not recreate random tasks on every rebuild.
|
||||
- Seed data exists only in composition/controller, not widgets.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3.2 — Build the static seed domain objects
|
||||
|
||||
### Required seed records
|
||||
|
||||
Create at least one project:
|
||||
|
||||
```dart
|
||||
ProjectProfile(
|
||||
id: 'home',
|
||||
name: 'Home',
|
||||
colorKey: 'project-home',
|
||||
)
|
||||
```
|
||||
|
||||
Create owner settings:
|
||||
|
||||
```dart
|
||||
OwnerSettings(
|
||||
ownerId: 'owner-1',
|
||||
timeZoneId: 'UTC',
|
||||
compactModeEnabled: true,
|
||||
)
|
||||
```
|
||||
|
||||
Create static tasks for the selected date:
|
||||
|
||||
| ID | Title | Type | Status | Time | Reward | Difficulty | Notes |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| `clean-coffee-maker` | Clean coffee maker | flexible | planned | 4:15–4:30 PM | medium/high acceptable | veryEasy or easy | Green card; 15 min. |
|
||||
| `cleaned-kitchen-counter-early` | Cleaned kitchen counter | surprise | completed | visible near 4:45–5:00 PM | medium | medium | Completed gray row; completed at 4:20 PM. |
|
||||
| `pay-bill` | Pay bill | critical or inflexible per existing semantics | planned | 6:00–6:10 PM | low/level 2 equivalent | medium | Pink required card; next required. |
|
||||
| `doctor-appointment` | Doctor appointment | inflexible | planned | 6:30–7:15 PM | medium | hard | Blue required appointment card. |
|
||||
| `free-slot` | Free Slot | freeSlot | planned | 7:15–8:15 PM | notSet | notSet | Purple rest card; no quick actions. |
|
||||
| `cleaned-kitchen-counter-late` | Cleaned kitchen counter | surprise | completed | visible near 8:15–8:30 PM | medium | medium | Later completed gray row. |
|
||||
|
||||
### Implementation notes
|
||||
|
||||
- Prefer `Task` or `ProjectProfile.createTask` constructors from `package:scheduler_core/scheduler_core.dart`.
|
||||
- Use `durationMinutes` matching start/end.
|
||||
- Use `scheduledStart` and `scheduledEnd` for timeline visibility.
|
||||
- Use `completedAt` for completed surprise rows.
|
||||
- Use stable IDs; no random IDs.
|
||||
- Do not run scheduling algorithms in Flutter just to place these demo records.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Seed objects are valid domain objects.
|
||||
- Seed tasks appear in `GetTodayStateQuery` results.
|
||||
- The seed can be constructed for any selected date by changing only the date component.
|
||||
|
||||
BREAKPOINT: Stop if public scheduler APIs cannot represent the seed without importing `src/` internals.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3.3 — Query Today state from application/core APIs
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Use public `scheduler_core` API to create an in-memory application store.
|
||||
2. Initialize it with the seed project, settings, and tasks.
|
||||
3. Create a `GetTodayStateQuery` with a fixed/known timezone resolver.
|
||||
4. Execute `GetTodayStateRequest` for the selected current day.
|
||||
5. Store the result in a controller state that supports:
|
||||
- loading,
|
||||
- data,
|
||||
- empty,
|
||||
- failure.
|
||||
|
||||
Suggested concept:
|
||||
|
||||
```dart
|
||||
final store = InMemoryApplicationUnitOfWork(
|
||||
initialProjects: seed.projects,
|
||||
initialTasks: seed.tasks,
|
||||
initialOwnerSettings: [seed.ownerSettings],
|
||||
);
|
||||
|
||||
final todayQuery = GetTodayStateQuery(
|
||||
applicationStore: store,
|
||||
timeZoneResolver: const FixedOffsetTimeZoneResolver.utc(),
|
||||
);
|
||||
```
|
||||
|
||||
Then execute with an `ApplicationOperationContext` using a stable operation id, clock, owner/timezone, and ID generator.
|
||||
|
||||
### Design constraints
|
||||
|
||||
- UI can map `TodayState` to presentation models, but must not modify canonical schedule state.
|
||||
- Failures should be surfaced with a simple calm message in the UI if they happen.
|
||||
- No persistence adapter should be used in this plan.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Controller can produce a `TodayScreenData`/equivalent from real `TodayState`.
|
||||
- Tests can instantiate controller/composition with a fixed clock and receive the six seeded items.
|
||||
- `pay-bill` is identified as the next required item at the default read time.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3.4 — Define UI display models as a thin adapter
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `models/today_screen_models.dart` or equivalent with small UI-facing models, for example:
|
||||
|
||||
```dart
|
||||
class TodayScreenData {
|
||||
final String dateLabel;
|
||||
final RequiredBannerModel? requiredBanner;
|
||||
final List<TimelineCardModel> cards;
|
||||
final TimelineRangeModel timelineRange;
|
||||
}
|
||||
|
||||
class TimelineCardModel {
|
||||
final String id;
|
||||
final String title;
|
||||
final String subtitle;
|
||||
final String timeText;
|
||||
final TaskVisualKind visualKind;
|
||||
final int? durationMinutes;
|
||||
final RewardLevel reward;
|
||||
final DifficultyLevel difficulty;
|
||||
final bool isCompleted;
|
||||
final bool isSelectable;
|
||||
}
|
||||
```
|
||||
|
||||
Use existing read-model values where possible; this class should only bridge domain/read state to Flutter presentation.
|
||||
|
||||
### Mapping rules
|
||||
|
||||
- `TaskType.flexible` → green flexible visuals.
|
||||
- `TaskType.critical` or app-selected required type → pink required visuals.
|
||||
- `TaskType.inflexible` → blue required/appointment visuals.
|
||||
- `TaskType.freeSlot` → purple rest visuals.
|
||||
- `TaskStatus.completed` or `TaskType.surprise` completed → gray completed visuals.
|
||||
- Task card time text should come from scheduled start/end formatting.
|
||||
- Required banner comes from `TodayState.nextRequiredItem`.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Widgets consume `TodayScreenData` or equivalent rather than raw repository state.
|
||||
- The adapter is small, deterministic, and covered by tests.
|
||||
- No scheduling placement decisions occur in this adapter.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3.5 — Wire seeded state into the shell
|
||||
|
||||
### Tasks
|
||||
|
||||
1. `FocusFlowApp` receives or creates `DemoSchedulerComposition`.
|
||||
2. Main screen uses a controller/future builder/listenable to load Today data.
|
||||
3. Render loading state briefly if needed.
|
||||
4. Render failure state if query fails.
|
||||
5. Render empty state only if no cards exist.
|
||||
6. Render data state into the shell/banner/timeline.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- On launch, the compact Today screen shows seeded date, required banner, and cards.
|
||||
- Widget tests can pump the app and find seeded labels.
|
||||
- Widgets remain free of seed construction.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- The timeline seed is static, deterministic, and backend-backed.
|
||||
- The UI reads from application/domain state.
|
||||
- No Flutter widget owns canonical schedule rules.
|
||||
- `pay-bill` appears as next required in the banner.
|
||||
- Six seed items are available to timeline rendering.
|
||||
|
||||
## Validation after block
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
Tests should verify seeded query and UI adapter behavior before timeline polish begins.
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
# UI Plan 1 Block 04 — Compact Timeline Rail and Task Cards
|
||||
|
||||
**Status:** Planned
|
||||
**Goal:** Render the seeded Today timeline so it visually matches the compact mockup.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, the main screen without modal should closely match `mockups/clean/main_screen_compact.png`.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.1 — Implement timeline geometry
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/timeline_geometry.dart` with a small layout model:
|
||||
|
||||
```dart
|
||||
class TimelineGeometry {
|
||||
final TimeOfDay visibleStart;
|
||||
final TimeOfDay visibleEnd;
|
||||
final double pixelsPerMinute;
|
||||
final double minCardHeight;
|
||||
double yForMinutesSinceMidnight(int minutes);
|
||||
double heightForDuration(int minutes);
|
||||
}
|
||||
```
|
||||
|
||||
The exact API can differ, but isolate these responsibilities:
|
||||
|
||||
- visible time range,
|
||||
- time label generation,
|
||||
- y-position from scheduled time,
|
||||
- card height calculation,
|
||||
- minimum tappable card height.
|
||||
|
||||
### Visual target
|
||||
|
||||
- Visible range: 4:00 PM through 8:45 PM.
|
||||
- Grid rows: 15-minute increments.
|
||||
- Hour labels/dots prominent.
|
||||
- Quarter labels muted.
|
||||
- Cards align to scheduled start rows.
|
||||
- Use a minimum card height so 10–15 minute items remain readable/tappable, as in the mockup.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Geometry can be tested without rendering widgets.
|
||||
- Geometry is presentation-only and does not alter task schedule state.
|
||||
- Cards can be positioned from model start/end times.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.2 — Render timeline axis and grid
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/timeline_axis.dart`:
|
||||
|
||||
1. Draw or compose the left time labels.
|
||||
2. Draw the vertical rail and hour dots.
|
||||
3. Draw horizontal grid lines across content width.
|
||||
4. Keep grid colors low contrast.
|
||||
|
||||
### Implementation options
|
||||
|
||||
- Use `CustomPainter` for better performance and visual control.
|
||||
- Or compose with `Stack`/`Positioned` if simpler and testable.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Labels from 4:00 PM to 8:45 PM are visible.
|
||||
- The rail and grid align with card positions.
|
||||
- Axis does not know task semantics.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.3 — Implement reward icon painter
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/reward_icon.dart`.
|
||||
|
||||
Requirements:
|
||||
|
||||
1. Draw solid sparkle/star shapes.
|
||||
2. Support at least the visual sizes used in:
|
||||
- card metadata badge,
|
||||
- modal reward tile.
|
||||
3. Use task accent color or explicit color input.
|
||||
4. Avoid generic line-art icon if it does not match the mockup.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Reward icon is solid, not just outlined.
|
||||
- Widget tests or painter tests verify it renders for all reward states used in the seed.
|
||||
- It can be reused by cards and modal.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.4 — Implement five-bar difficulty painter
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/difficulty_bars.dart`.
|
||||
|
||||
Requirements:
|
||||
|
||||
1. Always reserve five rounded bars.
|
||||
2. Fill count mapping:
|
||||
- not set → 0,
|
||||
- very easy → 1,
|
||||
- easy → 2,
|
||||
- medium → 3,
|
||||
- hard → 4,
|
||||
- very hard → 5.
|
||||
3. Filled bars use task/accent color.
|
||||
4. Unfilled bars use outline only.
|
||||
5. Bars increase in height from left to right.
|
||||
6. Medium effort must show exactly three filled bars.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- A unit/widget test verifies fill count mapping for every difficulty value.
|
||||
- The modal's `Medium effort` tile uses the same painter as cards.
|
||||
- There are no six-bar or four-bar variants.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.5 — Implement timeline card component
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/task_timeline_card.dart` that accepts `TimelineCardModel` or equivalent.
|
||||
|
||||
Card slots:
|
||||
|
||||
1. Left status ring/check.
|
||||
2. Title.
|
||||
3. Subtitle/time/duration metadata.
|
||||
4. Right-side no-op quick action icons for visible cards.
|
||||
5. Reward icon badge.
|
||||
6. Difficulty icon badge.
|
||||
|
||||
### Visual variants
|
||||
|
||||
| Visual kind | Styling |
|
||||
|---|---|
|
||||
| flexible | Green border/glow, dark green glass fill, green status ring. |
|
||||
| required/critical | Pink border/glow, dark pink glass fill, pink status ring. |
|
||||
| inflexible/appointment | Blue border/glow, dark blue glass fill, blue status ring. |
|
||||
| free slot | Purple border/glow, dark purple glass fill, dashed/purple status ring, italic `Intentional rest`. |
|
||||
| completed surprise | Gray border/fill, muted text, check ring, strikethrough title. |
|
||||
|
||||
### Behavior
|
||||
|
||||
- Entire card gets an `onTap` callback supplied by parent.
|
||||
- Quick action icons on the card are no-op. They may share the same callback that does nothing or use inert buttons.
|
||||
- Completed cards may still be selectable, but the required acceptance path only needs planned task selection.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Cards render all visual variants from seed data.
|
||||
- `Clean coffee maker`, `Pay bill`, `Doctor appointment`, `Free Slot`, and completed rows are visible.
|
||||
- No card action performs a domain command.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.6 — Compose timeline view
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/timeline/timeline_view.dart`:
|
||||
|
||||
1. Accept `TodayScreenData.cards`.
|
||||
2. Use `TimelineGeometry` to position cards.
|
||||
3. Render axis/grid behind cards.
|
||||
4. Render cards in stable order.
|
||||
5. Avoid overlap issues by using minimum heights and explicit stacking order.
|
||||
6. Give task cards stable keys by task id.
|
||||
|
||||
### Layout guidance
|
||||
|
||||
- Left rail and labels are a fixed-width column.
|
||||
- Cards begin to the right of the rail.
|
||||
- Cards should span most of the main content width.
|
||||
- Right badges/actions align near the card edge.
|
||||
- Preserve card margins and dense vertical spacing from the mockup.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Screen at 1586×992 resembles `main_screen_compact.png`.
|
||||
- Timeline data still comes from seeded read model.
|
||||
- The app remains responsive enough not to overflow badly at moderately smaller desktop widths.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 4.7 — Polish against the clean mockup
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Run the app locally at a desktop size close to 1586×992.
|
||||
2. Compare against `mockups/clean/main_screen_compact.png`.
|
||||
3. Tune:
|
||||
- sidebar width,
|
||||
- header spacing,
|
||||
- banner height,
|
||||
- card x/y spacing,
|
||||
- card radii,
|
||||
- card border opacity,
|
||||
- timeline grid contrast,
|
||||
- icon sizes,
|
||||
- text weights.
|
||||
4. Keep changes tokenized when possible.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Visual differences are intentional and minor.
|
||||
- No white surfaces appear.
|
||||
- Icons match reward/difficulty requirements.
|
||||
- UI still passes tests after tuning.
|
||||
|
||||
BREAKPOINT: Stop here and review screenshot before implementing the modal if the visual structure substantially deviates from the mockup.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- Main compact screen visually matches the clean non-modal mockup closely.
|
||||
- Timeline rail/grid/cards render from backend-backed seed data.
|
||||
- Reward icon uses solid sparkles.
|
||||
- Difficulty icon always uses five bars.
|
||||
- No task action is wired beyond card selection callback plumbing.
|
||||
|
||||
## Validation after block
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
Add/update widget tests for card labels and difficulty fill counts.
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
# UI Plan 1 Block 05 — Task Selection Modal
|
||||
|
||||
**Status:** Planned
|
||||
**Goal:** Implement the only functional interaction for this slice: clicking a task opens the selected-task modal, and clicking outside/close dismisses it.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, the app should match `mockups/clean/task_selected_modal.png` for the selected-task state.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 5.1 — Add selected task state
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Add selected-card state to the Today screen/controller layer.
|
||||
2. Selection should store a `TimelineCardModel` or selected task id plus lookup into current data.
|
||||
3. Clicking a task card sets selection.
|
||||
4. Clearing selection closes the modal.
|
||||
5. Do not persist selection outside the UI session.
|
||||
6. Do not modify backend state when selecting.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Tapping/clicking `Pay bill` sets selected task state.
|
||||
- Selection is UI-local and does not call domain commands.
|
||||
- Tests can open the modal by tapping a card with key `pay-bill` or equivalent.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 5.2 — Build selected-task modal component
|
||||
|
||||
### Tasks
|
||||
|
||||
Create `widgets/task_selection_modal.dart`.
|
||||
|
||||
Inputs:
|
||||
|
||||
- selected task/card model,
|
||||
- close callback,
|
||||
- no-op callbacks for Done/Push/Move/Break up.
|
||||
|
||||
Modal sections:
|
||||
|
||||
1. Header:
|
||||
- status ring,
|
||||
- title (`Pay bill` for seed),
|
||||
- type/time line (`Required · 6:00 – 6:10 PM`),
|
||||
- close button.
|
||||
2. Metadata row:
|
||||
- Reward tile with reward icon and label, e.g. `Reward level 2`.
|
||||
- Effort tile with five-bar difficulty icon and label, e.g. `Medium effort`.
|
||||
3. Action grid:
|
||||
- Done.
|
||||
- Push.
|
||||
- Move to Backlog.
|
||||
- Break up.
|
||||
4. Footer:
|
||||
- `Options apply after confirmation.`
|
||||
|
||||
### Visual requirements
|
||||
|
||||
- Dark glass panel.
|
||||
- Pink border/accent for selected required task.
|
||||
- Panel width/height close to mockup.
|
||||
- Rounded corners.
|
||||
- No white Material dialog surface.
|
||||
- Buttons are enabled-looking and no-op.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Modal can render for any seeded task, but `Pay bill` must match the mockup.
|
||||
- Reward/difficulty tiles reuse the same icon widgets as timeline cards.
|
||||
- No action button mutates state or calls backend commands.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 5.3 — Implement overlay and click-away behavior
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Render modal above timeline using `Stack`, `Overlay`, or a custom dialog route.
|
||||
2. Use transparent or very light dark barrier; underlying timeline should remain visible.
|
||||
3. Clicking/tapping outside the modal clears selection.
|
||||
4. Clicking the close button clears selection.
|
||||
5. Clicking inside the modal should not close it unless close button is used.
|
||||
6. Modal buttons should not close the modal unless that is visually required later. For this slice, keep them no-op and leave modal open.
|
||||
|
||||
### Implementation guidance
|
||||
|
||||
Avoid `showDialog` default styling unless fully overridden, because default Material dialog/barrier behavior can introduce unwanted background/surface styling. A local `Stack` inside the Today screen is usually easier to control for this mockup.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Outside-click closes modal.
|
||||
- Close icon closes modal.
|
||||
- Modal action buttons do nothing and keep app stable.
|
||||
- No accidental route/navigation stack complexity.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 5.4 — Polish modal against clean mockup
|
||||
|
||||
### Tasks
|
||||
|
||||
Compare against `mockups/clean/task_selected_modal.png` and tune:
|
||||
|
||||
- modal width and position,
|
||||
- header height,
|
||||
- close button size,
|
||||
- metadata tile spacing,
|
||||
- action button height,
|
||||
- text weights,
|
||||
- border opacity,
|
||||
- barrier/scrim opacity.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Modal is centered over the timeline similarly to the mockup.
|
||||
- Underlying timeline is visible and recognizable.
|
||||
- Pay bill card remains visible behind/under modal region as in mockup.
|
||||
- No white surfaces appear.
|
||||
|
||||
BREAKPOINT: Stop here for visual review if modal diverges substantially from the clean modal mockup.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 5.5 — Add widget tests for modal behavior
|
||||
|
||||
### Tests
|
||||
|
||||
Add tests that verify:
|
||||
|
||||
1. Initial render shows no modal.
|
||||
2. Tapping `Pay bill` opens modal.
|
||||
3. Modal shows:
|
||||
- `Pay bill`,
|
||||
- `Required`,
|
||||
- `Reward level`,
|
||||
- `Medium effort`,
|
||||
- `Done`,
|
||||
- `Push`,
|
||||
- `Move to Backlog`,
|
||||
- `Break up`.
|
||||
4. Tapping close hides modal.
|
||||
5. Reopening and tapping outside hides modal.
|
||||
6. Tapping modal action buttons does not throw and does not remove seeded task labels.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Tests are stable under Flutter test.
|
||||
- Tests do not require golden rendering.
|
||||
- Tests do not require actual backend mutations.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- Selected-task modal matches clean mockup closely.
|
||||
- Opening/closing behavior works.
|
||||
- Modal actions are inert but enabled-looking.
|
||||
- No backend/domain state changes occur from modal interactions.
|
||||
- Tests cover open/close and no-op actions.
|
||||
|
||||
## Validation after block
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
# UI Plan 1 Block 06 — Testing, Validation, and Handoff
|
||||
|
||||
**Status:** Planned
|
||||
**Goal:** Harden UI Plan 1 with tests, validation commands, and handoff notes for the next functional component plans.
|
||||
|
||||
---
|
||||
|
||||
## Block outcome
|
||||
|
||||
After this block, Codex should have a validated UI slice with clear documentation of what was implemented, what is intentionally no-op, what remains for later plans, and what commands passed or could not be run.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 6.1 — Complete widget and unit test coverage for this slice
|
||||
|
||||
### Required tests
|
||||
|
||||
1. **App shell render test**
|
||||
- Finds `FocusFlow`, `Today`, `Backlog`, `Projects`, `Reports`, `Settings`.
|
||||
- Finds `Compact`, `Normal`, and top settings button.
|
||||
|
||||
2. **Seeded Today render test**
|
||||
- Uses fixed clock/date.
|
||||
- Finds all seeded task titles.
|
||||
- Finds required banner text for `Pay bill`.
|
||||
- Verifies `Free Slot` and `Intentional rest` appear.
|
||||
|
||||
3. **Visual token mapping test**
|
||||
- Verifies task type/status maps to expected visual kinds.
|
||||
- Verifies completed surprise maps to completed gray style model.
|
||||
|
||||
4. **Difficulty bars test**
|
||||
- Verifies notSet=0, veryEasy=1, easy=2, medium=3, hard=4, veryHard=5.
|
||||
- Specifically verifies medium effort uses three filled bars.
|
||||
|
||||
5. **Modal behavior test**
|
||||
- Opens from task click.
|
||||
- Closes by X.
|
||||
- Closes by outside click.
|
||||
- Modal buttons no-op without exceptions.
|
||||
|
||||
6. **Forbidden import test**
|
||||
- Scans `lib/` and `test/` for forbidden imports.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Tests are deterministic.
|
||||
- Tests do not use sleeps or random values.
|
||||
- Tests do not require real SQLite or OS notification APIs.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 6.2 — Add optional visual snapshot guidance without fragile gates
|
||||
|
||||
### Tasks
|
||||
|
||||
1. Add a README note explaining how to manually run the app at mockup-like size.
|
||||
2. If golden tests are added, keep them opt-in or very carefully controlled.
|
||||
3. Do not block core CI on platform-specific font pixel differences unless the repo already has golden conventions.
|
||||
|
||||
### Suggested local visual command
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter run -d macos
|
||||
```
|
||||
|
||||
or the appropriate desktop device.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- There is a clear way for Ashley/Codex to visually compare the screen to the mockups.
|
||||
- No brittle golden requirement is introduced unless explicitly accepted.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 6.3 — Run validation commands
|
||||
|
||||
### Commands
|
||||
|
||||
Run from app:
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter pub get
|
||||
dart format lib test
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
Run from repo root:
|
||||
|
||||
```sh
|
||||
scripts/test.sh
|
||||
```
|
||||
|
||||
Optional if available:
|
||||
|
||||
```sh
|
||||
scripts/bootstrap_dev.sh
|
||||
```
|
||||
|
||||
### Reporting requirements
|
||||
|
||||
In the final implementation report, include:
|
||||
|
||||
- files changed,
|
||||
- commands run,
|
||||
- passed/failed/unverified status,
|
||||
- any Flutter toolchain limitations,
|
||||
- screenshots/manual visual comparison notes if available,
|
||||
- any intentional deviations from mockups.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- Flutter analyze/test pass.
|
||||
- Root Dart tests pass, or any failure is reported with exact failing command/output summary.
|
||||
- No formatting drift remains.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 6.4 — Update app README/handoff notes
|
||||
|
||||
### Tasks
|
||||
|
||||
Update or create `apps/focus_flow_flutter/README.md` with:
|
||||
|
||||
1. Scope of current UI.
|
||||
2. How to run.
|
||||
3. How to test.
|
||||
4. What is intentionally no-op.
|
||||
5. Where seed data lives.
|
||||
6. How the UI consumes backend/application state.
|
||||
7. Known next plans:
|
||||
- wire functional Done/Push/Backlog/Break up,
|
||||
- backlog screen,
|
||||
- quick capture,
|
||||
- persistent SQLite composition,
|
||||
- settings/navigation,
|
||||
- Shield/Recovery later.
|
||||
|
||||
### Acceptance criteria
|
||||
|
||||
- A future Codex session can understand the UI slice without rereading all implementation code.
|
||||
- README states that scheduler core remains the source of truth.
|
||||
- README lists no-op controls explicitly to avoid accidental bug reports.
|
||||
|
||||
---
|
||||
|
||||
## Chunk 6.5 — Final scope audit
|
||||
|
||||
### Audit checklist
|
||||
|
||||
- [ ] App launches into compact Today screen.
|
||||
- [ ] Source mockups are copied into this plan folder.
|
||||
- [ ] Clean visual state matches `main_screen_compact.png` closely.
|
||||
- [ ] Selected modal state matches `task_selected_modal.png` closely.
|
||||
- [ ] No white app background.
|
||||
- [ ] Buttons look enabled but are no-op except task click/close behavior.
|
||||
- [ ] Task click opens modal.
|
||||
- [ ] Outside click closes modal.
|
||||
- [ ] Close button closes modal.
|
||||
- [ ] Difficulty icon has five bars.
|
||||
- [ ] Medium effort fills three bars.
|
||||
- [ ] Reward icon uses solid sparkle/star symbol.
|
||||
- [ ] Seed data is static and deterministic.
|
||||
- [ ] Seed data is represented as backend/domain/application state.
|
||||
- [ ] UI does not duplicate scheduling rules.
|
||||
- [ ] UI does not import scheduler `src/` files.
|
||||
- [ ] UI does not import SQLite/Drift/platform notification APIs.
|
||||
- [ ] Root backend packages remain unchanged unless explicitly justified.
|
||||
- [ ] Tests and validations are reported.
|
||||
|
||||
BREAKPOINT: Stop at this final audit and report results before starting any functional wiring plan.
|
||||
|
||||
---
|
||||
|
||||
## Block acceptance criteria
|
||||
|
||||
- All UI Plan 1 validation is complete or honestly marked unverified.
|
||||
- Documentation captures scope and next steps.
|
||||
- No unrelated functionality was pulled in.
|
||||
- The zip/repo is ready for the next plan: functional component wiring.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# UI Plan 1 Execution Order
|
||||
|
||||
**Status:** Planned
|
||||
|
||||
Run these files in order. All chunks are intended for `xhigh` execution.
|
||||
|
||||
1. `UI_PLAN_1_BLOCK_01_REPO_AND_SCOPE_GUARDRAILS.md`
|
||||
- Reconcile current repo state.
|
||||
- Create/verify Flutter app location.
|
||||
- Protect package boundaries.
|
||||
|
||||
2. `UI_PLAN_1_BLOCK_02_APP_SHELL_VISUAL_SYSTEM.md`
|
||||
- Build theme/tokens.
|
||||
- Build desktop app shell, sidebar, top bar, and required banner frame.
|
||||
|
||||
3. `UI_PLAN_1_BLOCK_03_BACKEND_SEEDED_TODAY_STATE.md`
|
||||
- Build static current-day seed using scheduler domain/application state.
|
||||
- Query backend read state for UI consumption.
|
||||
|
||||
4. `UI_PLAN_1_BLOCK_04_COMPACT_TIMELINE_CARDS.md`
|
||||
- Build timeline rail, rows, cards, reward icon, and difficulty bars.
|
||||
|
||||
5. `UI_PLAN_1_BLOCK_05_TASK_SELECTION_MODAL.md`
|
||||
- Implement task selection state and modal overlay close behavior.
|
||||
- Keep modal action buttons inert.
|
||||
|
||||
6. `UI_PLAN_1_BLOCK_06_TESTING_VALIDATION_HANDOFF.md`
|
||||
- Add tests, import scans, validation commands, README/handoff notes.
|
||||
|
||||
Do not jump ahead into real task commands or non-Today screens while executing UI Plan 1.
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
# UI Plan 1 — Compact Today Timeline Mockup Implementation
|
||||
|
||||
**Status:** Planned
|
||||
**Plan folder:** `Codex Documentation/Current Software Plan/UI Plan 1 - Compact Today Timeline Mockup/`
|
||||
**Primary target:** Replace the current/provisional Flutter UI with the first real compact Today timeline screen and selected-task modal shown in the bundled mockups.
|
||||
**Execution mode:** Run all chunks at `xhigh`; this plan intentionally does not repeat per-chunk thinking labels.
|
||||
|
||||
---
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This plan gives Codex a detailed, implementation-ready sequence for building only the current requested UI slice:
|
||||
|
||||
1. A desktop-first Flutter Today screen in compact mode.
|
||||
2. A backend-backed deterministic seed for the current day.
|
||||
3. Rendering of the seeded timeline from application/domain read state.
|
||||
4. One interaction only: clicking a task card opens the selected-task modal.
|
||||
5. Clicking outside the modal or pressing the modal close button dismisses it.
|
||||
6. All other visible controls remain present and enabled-looking, but intentionally no-op.
|
||||
|
||||
Do **not** implement Backlog screens, task creation, schedule review, editing flows, drag-and-drop, notifications, Shield/Recovery, reports, settings, persistence-backed runtime wiring, or real command behavior in this plan.
|
||||
|
||||
---
|
||||
|
||||
## 2. Source truth and repo observations
|
||||
|
||||
### Source truth
|
||||
|
||||
Use these sources, in this order, when implementing:
|
||||
|
||||
1. Repo files and `AGENTS.md`.
|
||||
2. This plan folder.
|
||||
3. Mockup images in this plan folder.
|
||||
4. Human documentation already in the repo.
|
||||
|
||||
Preserve the core architectural rule: the Flutter UI renders backend/application state and sends user intent; it does not duplicate scheduling rules.
|
||||
|
||||
### Important repo observation from this planning pass
|
||||
|
||||
The extracted zip includes:
|
||||
|
||||
- `AGENTS.md`, which says active plans live under `Codex Documentation/Current Software Plan/` and Flutter UI work starts under `apps/focus_flow_flutter/`.
|
||||
- `README.md` and completed plan docs that refer to `apps/focus_flow_flutter/`.
|
||||
- No actual `apps/focus_flow_flutter/` directory in the extracted tree.
|
||||
|
||||
So the first implementation block must verify whether the working repo has an app directory. If it is missing, create it. If it exists in Codex's working copy, replace only the provisional UI slice needed here and preserve any useful conventions.
|
||||
|
||||
---
|
||||
|
||||
## 3. Mockup references
|
||||
|
||||
Clean semi-strict references:
|
||||
|
||||
- `mockups/clean/main_screen_compact.png`
|
||||
- `mockups/clean/task_selected_modal.png`
|
||||
|
||||
Annotated implementation references:
|
||||
|
||||
- `mockups/annotated/main_screen_compact_notes.png`
|
||||
- `mockups/annotated/task_selected_modal_notes.png`
|
||||
|
||||
Mockup handling rule:
|
||||
|
||||
- Treat the clean mockups as semi-strict visual truth.
|
||||
- Use annotated versions for implementation notes only.
|
||||
- Deviations are allowed only for unavoidable Flutter/platform font metrics, pixel rounding, safe minimum hit targets, and small responsive adjustments.
|
||||
- Do not introduce a white app background.
|
||||
- Do not grey out no-op buttons. They should look usable, but their handlers should intentionally do nothing.
|
||||
|
||||
---
|
||||
|
||||
## 4. Feature scope
|
||||
|
||||
### In scope
|
||||
|
||||
- Flutter desktop app shell under `apps/focus_flow_flutter/`.
|
||||
- Dark theme visual tokens matching the mockups.
|
||||
- Compact Today top bar and sidebar.
|
||||
- Date header and static navigation controls.
|
||||
- Compact/Normal segmented toggle with Compact selected.
|
||||
- Settings button visible as a no-op.
|
||||
- Next required task banner.
|
||||
- Timeline rail from 4:00 PM through 8:45 PM.
|
||||
- Timeline rows/cards for seeded tasks.
|
||||
- Reward icon using solid sparkle/star shapes.
|
||||
- Difficulty icon with exactly five vertical bars, filled according to difficulty.
|
||||
- Selected-task modal for any task card click.
|
||||
- Close modal on outside click and close icon.
|
||||
- Widget tests for rendering and modal behavior.
|
||||
- Root/core purity checks and Flutter forbidden-import checks.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Real navigation between Today/Backlog/Projects/Reports/Settings.
|
||||
- Actual Done, Push, Move to Backlog, Break up, calendar, settings, or normal-mode behavior.
|
||||
- Task creation/editing drawers.
|
||||
- Schedule review.
|
||||
- Persistence-backed SQLite runtime composition.
|
||||
- Notifications.
|
||||
- Backup/restore or export/import UI.
|
||||
- Week/month/calendar views.
|
||||
- Drag-and-drop.
|
||||
- Shield/Recovery flows.
|
||||
|
||||
---
|
||||
|
||||
## 5. Target visual composition
|
||||
|
||||
Build at desktop density first. The mockups are 1586×992 and should be used as the primary visual reference.
|
||||
|
||||
### App shell
|
||||
|
||||
- Rounded outer app frame with a dark navy/near-black background.
|
||||
- Left sidebar approximately 250 px wide at mockup width.
|
||||
- Main content region with generous horizontal gutters.
|
||||
- Mac-style red/yellow/green window controls in the sidebar top-left. These are decorative/no-op.
|
||||
- Brand row with sparkle icon and `FocusFlow`.
|
||||
- Sidebar navigation rows:
|
||||
- Today active, magenta-tinted fill and outline.
|
||||
- Backlog, Projects, Reports, Settings inactive.
|
||||
- All sidebar rows are visible/no-op except Today is visually active.
|
||||
|
||||
### Header/top controls
|
||||
|
||||
- `Today` title.
|
||||
- Calendar icon button, previous arrow, date label, next arrow.
|
||||
- Date should be derived from selected current day in runtime, but widget tests may use a fixed date to match the mockup.
|
||||
- Segmented control with `Compact` selected and `Normal` unselected/no-op.
|
||||
- Settings button visible/no-op.
|
||||
|
||||
### Required banner
|
||||
|
||||
- Full-width magenta/pink glass panel under the header.
|
||||
- Sparkle icon on the left.
|
||||
- Text: `Next required task: Pay bill at 6:00 PM` for the default seed.
|
||||
- Text should be derived from the backend/read-model selected next required item, not hard-coded inside the widget.
|
||||
- `Show upcoming` button visible/no-op.
|
||||
|
||||
### Timeline rail
|
||||
|
||||
- Left time labels from 4:00 PM through 8:45 PM in 15-minute intervals.
|
||||
- Major hour labels brighter; quarter-hour labels muted.
|
||||
- Vertical rail with circular dots on hour positions.
|
||||
- Horizontal grid lines across the timeline.
|
||||
- Timeline is not scroll-driven for this slice at the mockup height. If viewport height is smaller, allow main content to scroll without changing card styling.
|
||||
|
||||
### Task cards
|
||||
|
||||
Render cards from the backend-backed `TodayState.timelineItems` or a UI adapter derived directly from those items.
|
||||
|
||||
Default seed must visually include:
|
||||
|
||||
1. `Clean coffee maker`, green flexible task, 15 min.
|
||||
2. Completed surprise row `Cleaned kitchen counter`, grey/disabled-looking completed styling.
|
||||
3. `Pay bill`, pink required task, 6:00–6:10 PM.
|
||||
4. `Doctor appointment`, blue required/inflexible task, 6:30–7:15 PM.
|
||||
5. `Free Slot`, purple protected rest/free slot, 7:15–8:15 PM.
|
||||
6. Later completed surprise row `Cleaned kitchen counter`, grey/disabled-looking completed styling.
|
||||
|
||||
Card buttons/icons are no-op. Clicking anywhere on a task/free-slot card may open the modal for that card, but the acceptance path only requires opening the modal for `Pay bill`.
|
||||
|
||||
### Reward and difficulty indicators
|
||||
|
||||
Reward icon:
|
||||
|
||||
- Use solid sparkle/star shapes, not only line-art outlines.
|
||||
- Pink/purple depending on task color context.
|
||||
- Badge background should match the dark glass style.
|
||||
|
||||
Difficulty icon:
|
||||
|
||||
- Exactly five rounded vertical bars.
|
||||
- Fill count is based on difficulty:
|
||||
- `notSet`: zero filled or hidden if no difficulty should be shown.
|
||||
- `veryEasy`: 1 filled.
|
||||
- `easy`: 2 filled.
|
||||
- `medium`: 3 filled.
|
||||
- `hard`: 4 filled.
|
||||
- `veryHard`: 5 filled.
|
||||
- Unfilled bars are outlines only.
|
||||
- Medium effort in the modal must show three filled bars.
|
||||
|
||||
### Selected-task modal
|
||||
|
||||
- Centered overlay on the timeline.
|
||||
- Underlying timeline remains visible; use a very light transparent scrim only if needed for click-away handling.
|
||||
- Header contains status ring, title, type label, and time range.
|
||||
- Close button in the top-right.
|
||||
- Two metadata tiles:
|
||||
- Reward level.
|
||||
- Difficulty/effort.
|
||||
- Four action buttons in a 2×2 grid:
|
||||
- Done.
|
||||
- Push.
|
||||
- Move to Backlog.
|
||||
- Break up.
|
||||
- All modal buttons are enabled-looking but no-op.
|
||||
- Footer text: `Options apply after confirmation.`
|
||||
|
||||
---
|
||||
|
||||
## 6. Architecture expectations
|
||||
|
||||
### Package and dependency boundary
|
||||
|
||||
- Flutter app lives under `apps/focus_flow_flutter/`.
|
||||
- Keep the app outside the root Dart workspace unless a later plan explicitly changes the workspace.
|
||||
- UI code may import public package entry points such as `package:scheduler_core/scheduler_core.dart`.
|
||||
- UI code must not import:
|
||||
- `package:scheduler_core/src/...`
|
||||
- Drift.
|
||||
- SQLite adapters.
|
||||
- notification platform implementations.
|
||||
- backup/export internals.
|
||||
- OS APIs directly for this slice.
|
||||
|
||||
### State ownership
|
||||
|
||||
- Use a small UI controller/composition layer that queries backend/application state.
|
||||
- The widget tree should receive display models and callbacks.
|
||||
- The widget tree must not calculate canonical schedule placement or conflict resolution.
|
||||
- Timeline geometry for painting rows is UI layout logic and is allowed.
|
||||
- Mapping backend read models to color/icon tokens is UI presentation logic and is allowed.
|
||||
|
||||
### Seed ownership
|
||||
|
||||
The seed must be static and deterministic, but not a hand-coded widget list. Use backend/application objects as if the real calls had already produced scheduled task state.
|
||||
|
||||
Acceptable approach:
|
||||
|
||||
- Build `ProjectProfile`, `OwnerSettings`, and `Task` domain objects.
|
||||
- Insert them into `InMemoryApplicationUnitOfWork` as initial state.
|
||||
- Query `GetTodayStateQuery` for the selected current day.
|
||||
- Render the returned state.
|
||||
|
||||
Do not bypass the read query by creating Flutter-only timeline items.
|
||||
|
||||
---
|
||||
|
||||
## 7. Block list
|
||||
|
||||
Execute the block files in this folder in order:
|
||||
|
||||
1. `UI_PLAN_1_BLOCK_01_REPO_AND_SCOPE_GUARDRAILS.md`
|
||||
2. `UI_PLAN_1_BLOCK_02_APP_SHELL_VISUAL_SYSTEM.md`
|
||||
3. `UI_PLAN_1_BLOCK_03_BACKEND_SEEDED_TODAY_STATE.md`
|
||||
4. `UI_PLAN_1_BLOCK_04_COMPACT_TIMELINE_CARDS.md`
|
||||
5. `UI_PLAN_1_BLOCK_05_TASK_SELECTION_MODAL.md`
|
||||
6. `UI_PLAN_1_BLOCK_06_TESTING_VALIDATION_HANDOFF.md`
|
||||
|
||||
Each block contains chunks, implementation details, acceptance criteria, and breakpoints.
|
||||
|
||||
---
|
||||
|
||||
## 8. Expected final changed areas
|
||||
|
||||
Likely implementation changes after Codex executes this plan:
|
||||
|
||||
```text
|
||||
apps/focus_flow_flutter/
|
||||
lib/
|
||||
main.dart
|
||||
app/focus_flow_app.dart
|
||||
app/demo_scheduler_composition.dart
|
||||
controllers/today_screen_controller.dart
|
||||
models/today_screen_models.dart
|
||||
theme/focus_flow_theme.dart
|
||||
theme/focus_flow_tokens.dart
|
||||
widgets/app_shell.dart
|
||||
widgets/sidebar.dart
|
||||
widgets/top_bar.dart
|
||||
widgets/required_banner.dart
|
||||
widgets/timeline/timeline_geometry.dart
|
||||
widgets/timeline/timeline_axis.dart
|
||||
widgets/timeline/timeline_view.dart
|
||||
widgets/timeline/task_timeline_card.dart
|
||||
widgets/timeline/reward_icon.dart
|
||||
widgets/timeline/difficulty_bars.dart
|
||||
widgets/task_selection_modal.dart
|
||||
test/
|
||||
today_screen_render_test.dart
|
||||
task_selection_modal_test.dart
|
||||
visual_token_test.dart
|
||||
forbidden_imports_test.dart
|
||||
```
|
||||
|
||||
Exact file names may vary if the working repo already has conventions, but keep the same boundaries.
|
||||
|
||||
---
|
||||
|
||||
## 9. Validation summary
|
||||
|
||||
Minimum validation after implementation:
|
||||
|
||||
```sh
|
||||
cd apps/focus_flow_flutter
|
||||
flutter pub get
|
||||
dart format lib test
|
||||
flutter analyze
|
||||
flutter test
|
||||
cd ../..
|
||||
scripts/test.sh
|
||||
```
|
||||
|
||||
Also run a forbidden-import scan for the app `lib/` and `test/` directories.
|
||||
|
||||
If Flutter is unavailable in the execution environment, Codex must still make the changes, run all available Dart/root validations, and explicitly report Flutter validation as unverified due to toolchain absence.
|
||||
|
||||
---
|
||||
|
||||
## 10. Risks and mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|---|---|
|
||||
| App directory may be absent despite docs referencing it. | Block 1 verifies and creates/reconciles `apps/focus_flow_flutter/`. |
|
||||
| Pixel-perfect matching can be brittle across platforms/fonts. | Match layout, color system, icon semantics, spacing, and behavior; avoid fragile exact-font assumptions. |
|
||||
| UI could accidentally duplicate scheduler logic. | Seed/query through application/domain read state and keep scheduling decisions out of widgets. |
|
||||
| No-op controls may look disabled if normal disabled widgets are used. | Use enabled-looking buttons with empty callbacks or inert callbacks. |
|
||||
| Current day requirement conflicts with mockup date. | Runtime defaults to current owner-local date; tests/demo screenshots may use a fixed clock matching the mockup. |
|
||||
| Difficulty/reward icon drift. | Implement custom painters and test fill counts/semantics. |
|
||||
|
||||
---
|
||||
|
||||
## 11. Definition of done
|
||||
|
||||
UI Plan 1 is complete when:
|
||||
|
||||
- The app launches to the compact Today screen.
|
||||
- The screen visually tracks the clean mockups closely.
|
||||
- Timeline content comes from backend/application seed/query flow.
|
||||
- Static seed data is deterministic and tied to the selected current day.
|
||||
- All visible controls are present and enabled-looking.
|
||||
- Only task-card selection, outside-click close, and close icon behavior are functional.
|
||||
- Modal action buttons are no-op.
|
||||
- Difficulty icons have five bars and correct fill counts.
|
||||
- Reward icons use solid sparkle/star forms.
|
||||
- Flutter analyze/test pass.
|
||||
- Root backend tests still pass or are explicitly reported if not runnable.
|
||||
- No forbidden imports or core purity regressions are introduced.
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# UI Plan 1 Mockup Reference
|
||||
|
||||
**Status:** Planned reference assets
|
||||
|
||||
This folder contains the semi-strict visual truth for UI Plan 1.
|
||||
|
||||
## Clean mockups
|
||||
|
||||
- `clean/main_screen_compact.png`
|
||||
- Compact Today timeline without a selected task.
|
||||
- `clean/task_selected_modal.png`
|
||||
- Compact Today timeline with a selected task modal open.
|
||||
|
||||
Use these for visual matching.
|
||||
|
||||
## Annotated mockups
|
||||
|
||||
- `annotated/main_screen_compact_notes.png`
|
||||
- `annotated/task_selected_modal_notes.png`
|
||||
|
||||
Use these for implementation notes only. The opaque note blocks are not part of the product UI.
|
||||
|
||||
## Visual fidelity rules
|
||||
|
||||
1. Preserve the dark, calm, information-dense desktop composition.
|
||||
2. Preserve the sidebar, top bar, required banner, timeline rail, card colors, and modal proportions unless Flutter constraints force minor differences.
|
||||
3. Do not add a white page/app background.
|
||||
4. Do not make no-op buttons greyed out.
|
||||
5. The reward symbol must use solid sparkles/stars.
|
||||
6. Difficulty must always be five bars, with the number of filled bars derived from task difficulty.
|
||||
7. Prefer custom painters for the reward/difficulty icons rather than generic icon fonts when generic icons do not match.
|
||||
8. Runtime date should default to current owner-local day; tests/screenshot fixtures may use the mockup date.
|
||||
|
||||
## Component truth map
|
||||
|
||||
| Component | Primary mockup truth | Notes |
|
||||
|---|---|---|
|
||||
| App frame/sidebar | `clean/main_screen_compact.png` | Desktop-first. Sidebar controls are no-op. |
|
||||
| Top bar/date/toggles | `clean/main_screen_compact.png` | Compact selected. Normal visible/no-op. |
|
||||
| Next required banner | `clean/main_screen_compact.png` | Derived from backend read model. |
|
||||
| Timeline rail/grid | `clean/main_screen_compact.png` | 4:00 PM through 8:45 PM visible. |
|
||||
| Task cards | `clean/main_screen_compact.png` | Colors and density matter more than exact font metrics. |
|
||||
| Reward/difficulty icons | Both clean mockups | Solid reward sparkles; five difficulty bars. |
|
||||
| Selected-task modal | `clean/task_selected_modal.png` | Close/outside-click functional; action buttons no-op. |
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 958 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1,011 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
|
|
@ -1,17 +1,35 @@
|
|||
# focus_flow_flutter
|
||||
# FocusFlow Flutter
|
||||
|
||||
A new Flutter project.
|
||||
Flutter desktop app target for the FocusFlow UI work.
|
||||
|
||||
## Getting Started
|
||||
## Current Scope
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
UI Plan 1 is limited to a compact Today timeline mockup and selected-task
|
||||
modal. The app target already existed before UI Plan 1 Block 1, so this package
|
||||
keeps the existing Flutter desktop scaffolding and local scheduler dependency.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
The current Block 1 work establishes structure and package-boundary guardrails.
|
||||
The visual shell, seeded Today state, timeline cards, and modal are implemented
|
||||
in later UI Plan 1 blocks.
|
||||
|
||||
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||
## Package Boundary
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
Allowed app imports include public scheduler APIs such as:
|
||||
|
||||
```dart
|
||||
import 'package:scheduler_core/scheduler_core.dart';
|
||||
```
|
||||
|
||||
The app must not import scheduler `src/` files, SQLite/Drift adapters,
|
||||
desktop notification implementations, backup/export packages, or direct OS APIs
|
||||
for this UI slice. `test/forbidden_imports_test.dart` enforces that boundary.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
flutter pub get
|
||||
flutter analyze
|
||||
flutter test
|
||||
```
|
||||
|
||||
Run these from `apps/focus_flow_flutter/`.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'composition/demo_scheduler_composition.dart';
|
||||
import 'theme/focus_flow_theme.dart';
|
||||
import 'widgets/backlog_pane.dart';
|
||||
import 'widgets/today_pane.dart';
|
||||
|
||||
|
|
@ -13,14 +14,8 @@ class FocusFlowApp extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'FocusFlow',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: const Color(0xFF2F6F73),
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
scaffoldBackgroundColor: const Color(0xFF101414),
|
||||
useMaterial3: true,
|
||||
),
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: FocusFlowTheme.dark(),
|
||||
home: SchedulerHome(composition: composition),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
export '../composition/demo_scheduler_composition.dart'
|
||||
show DemoSchedulerComposition;
|
||||
1
apps/focus_flow_flutter/lib/app/focus_flow_app.dart
Normal file
1
apps/focus_flow_flutter/lib/app/focus_flow_app.dart
Normal file
|
|
@ -0,0 +1 @@
|
|||
export '../app.dart' show FocusFlowApp;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import 'package:scheduler_core/scheduler_core.dart';
|
||||
|
||||
import 'scheduler_read_controller.dart';
|
||||
|
||||
typedef TodayScreenController = UiReadController<TodayState>;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'app.dart';
|
||||
import 'composition/demo_scheduler_composition.dart';
|
||||
import 'app/demo_scheduler_composition.dart';
|
||||
import 'app/focus_flow_app.dart';
|
||||
|
||||
export 'app.dart';
|
||||
export 'composition/demo_scheduler_composition.dart';
|
||||
export 'app/demo_scheduler_composition.dart';
|
||||
export 'app/focus_flow_app.dart';
|
||||
|
||||
void main() {
|
||||
runApp(FocusFlowApp(composition: DemoSchedulerComposition.seeded()));
|
||||
|
|
|
|||
30
apps/focus_flow_flutter/lib/models/today_screen_models.dart
Normal file
30
apps/focus_flow_flutter/lib/models/today_screen_models.dart
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
class RequiredBannerModel {
|
||||
const RequiredBannerModel({required this.title, required this.timeText});
|
||||
|
||||
final String title;
|
||||
final String timeText;
|
||||
}
|
||||
|
||||
class TodayScreenData {
|
||||
const TodayScreenData({
|
||||
required this.dateLabel,
|
||||
required this.cards,
|
||||
this.requiredBanner,
|
||||
});
|
||||
|
||||
final String dateLabel;
|
||||
final RequiredBannerModel? requiredBanner;
|
||||
final List<TimelineCardModel> cards;
|
||||
}
|
||||
|
||||
class TimelineCardModel {
|
||||
const TimelineCardModel({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.timeText,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String title;
|
||||
final String timeText;
|
||||
}
|
||||
17
apps/focus_flow_flutter/lib/theme/focus_flow_theme.dart
Normal file
17
apps/focus_flow_flutter/lib/theme/focus_flow_theme.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'focus_flow_tokens.dart';
|
||||
|
||||
abstract final class FocusFlowTheme {
|
||||
static ThemeData dark() {
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: FocusFlowTokens.accentMagenta,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
return ThemeData(
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: FocusFlowTokens.appBackground,
|
||||
useMaterial3: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
27
apps/focus_flow_flutter/lib/theme/focus_flow_tokens.dart
Normal file
27
apps/focus_flow_flutter/lib/theme/focus_flow_tokens.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract final class FocusFlowTokens {
|
||||
static const appBackground = Color(0xFF080A12);
|
||||
static const panelBackground = Color(0xFF11131E);
|
||||
static const elevatedPanel = Color(0xFF171925);
|
||||
static const accentMagenta = Color(0xFFFF5BA8);
|
||||
static const textPrimary = Color(0xFFF7F2FA);
|
||||
static const textMuted = Color(0xFFB8B1C5);
|
||||
static const completedText = Color(0xFF8F8A99);
|
||||
static const flexibleGreen = Color(0xFF4BE064);
|
||||
static const requiredPink = Color(0xFFFF4E7D);
|
||||
static const appointmentBlue = Color(0xFF38A8FF);
|
||||
static const restPurple = Color(0xFFB05CFF);
|
||||
static const completedGray = Color(0xFF777482);
|
||||
static const subtleBorder = Color(0xFF3A3A4A);
|
||||
|
||||
static const sidebarWidth = 250.0;
|
||||
static const mainGutter = 32.0;
|
||||
static const timelineRailWidth = 124.0;
|
||||
|
||||
static const appFrameRadius = 8.0;
|
||||
static const navRadius = 8.0;
|
||||
static const bannerRadius = 8.0;
|
||||
static const cardRadius = 8.0;
|
||||
static const modalRadius = 8.0;
|
||||
}
|
||||
23
apps/focus_flow_flutter/lib/widgets/app_shell.dart
Normal file
23
apps/focus_flow_flutter/lib/widgets/app_shell.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme/focus_flow_tokens.dart';
|
||||
|
||||
class AppShell extends StatelessWidget {
|
||||
const AppShell({required this.sidebar, required this.child, super.key});
|
||||
|
||||
final Widget sidebar;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(color: FocusFlowTokens.appBackground),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: FocusFlowTokens.sidebarWidth, child: sidebar),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
18
apps/focus_flow_flutter/lib/widgets/required_banner.dart
Normal file
18
apps/focus_flow_flutter/lib/widgets/required_banner.dart
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../models/today_screen_models.dart';
|
||||
|
||||
class RequiredBanner extends StatelessWidget {
|
||||
const RequiredBanner({this.model, super.key});
|
||||
|
||||
final RequiredBannerModel? model;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final banner = model;
|
||||
if (banner == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Text('Next required task: ${banner.title} at ${banner.timeText}');
|
||||
}
|
||||
}
|
||||
10
apps/focus_flow_flutter/lib/widgets/sidebar.dart
Normal file
10
apps/focus_flow_flutter/lib/widgets/sidebar.dart
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class Sidebar extends StatelessWidget {
|
||||
const Sidebar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../models/today_screen_models.dart';
|
||||
|
||||
class TaskSelectionModal extends StatelessWidget {
|
||||
const TaskSelectionModal({
|
||||
required this.card,
|
||||
required this.onClose,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TimelineCardModel card;
|
||||
final VoidCallback onClose;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(card.title),
|
||||
IconButton(onPressed: onClose, icon: const Icon(Icons.close)),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:scheduler_core/scheduler_core.dart';
|
||||
|
||||
class DifficultyBars extends StatelessWidget {
|
||||
const DifficultyBars({required this.difficulty, this.color, super.key});
|
||||
|
||||
final DifficultyLevel difficulty;
|
||||
final Color? color;
|
||||
|
||||
static int fillCount(DifficultyLevel difficulty) {
|
||||
return switch (difficulty) {
|
||||
DifficultyLevel.notSet => 0,
|
||||
DifficultyLevel.veryEasy => 1,
|
||||
DifficultyLevel.easy => 2,
|
||||
DifficultyLevel.medium => 3,
|
||||
DifficultyLevel.hard => 4,
|
||||
DifficultyLevel.veryHard => 5,
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text('${fillCount(difficulty)}/5');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class RewardIcon extends StatelessWidget {
|
||||
const RewardIcon({this.color, super.key});
|
||||
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Icon(Icons.auto_awesome, color: color);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/today_screen_models.dart';
|
||||
|
||||
class TaskTimelineCard extends StatelessWidget {
|
||||
const TaskTimelineCard({required this.card, this.onTap, super.key});
|
||||
|
||||
final TimelineCardModel card;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(onTap: onTap, child: Text(card.title));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'timeline_geometry.dart';
|
||||
|
||||
class TimelineAxis extends StatelessWidget {
|
||||
const TimelineAxis({required this.geometry, super.key});
|
||||
|
||||
final TimelineGeometry geometry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class TimelineGeometry {
|
||||
const TimelineGeometry({
|
||||
required this.visibleStart,
|
||||
required this.visibleEnd,
|
||||
required this.pixelsPerMinute,
|
||||
this.minCardHeight = 48,
|
||||
});
|
||||
|
||||
final TimeOfDay visibleStart;
|
||||
final TimeOfDay visibleEnd;
|
||||
final double pixelsPerMinute;
|
||||
final double minCardHeight;
|
||||
|
||||
double yForMinutesSinceMidnight(int minutes) {
|
||||
final start = visibleStart.hour * 60 + visibleStart.minute;
|
||||
return (minutes - start) * pixelsPerMinute;
|
||||
}
|
||||
|
||||
double heightForDuration(int minutes) {
|
||||
final height = minutes * pixelsPerMinute;
|
||||
return height < minCardHeight ? minCardHeight : height;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/today_screen_models.dart';
|
||||
|
||||
class TimelineView extends StatelessWidget {
|
||||
const TimelineView({required this.cards, super.key});
|
||||
|
||||
final List<TimelineCardModel> cards;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
for (final card in cards) Text(card.title, key: ValueKey(card.id)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
12
apps/focus_flow_flutter/lib/widgets/top_bar.dart
Normal file
12
apps/focus_flow_flutter/lib/widgets/top_bar.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class TopBar extends StatelessWidget {
|
||||
const TopBar({required this.dateLabel, super.key});
|
||||
|
||||
final String dateLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(dateLabel);
|
||||
}
|
||||
}
|
||||
79
apps/focus_flow_flutter/test/forbidden_imports_test.dart
Normal file
79
apps/focus_flow_flutter/test/forbidden_imports_test.dart
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('Flutter UI imports stay inside the UI Plan 1 package boundary', () {
|
||||
final appRoot = Directory.current;
|
||||
final dartFiles = <File>[
|
||||
..._dartFiles(Directory('${appRoot.path}/lib')),
|
||||
..._dartFiles(Directory('${appRoot.path}/test')),
|
||||
];
|
||||
|
||||
final violations = <String>[];
|
||||
for (final file in dartFiles) {
|
||||
final relativePath = file.path.substring(appRoot.path.length + 1);
|
||||
final contents = file.readAsStringSync();
|
||||
for (final forbidden in _forbiddenImportsFor(relativePath)) {
|
||||
if (forbidden.matches(contents)) {
|
||||
violations.add('$relativePath imports ${forbidden.label}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
violations,
|
||||
isEmpty,
|
||||
reason: 'Forbidden imports found:\n${violations.join('\n')}',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Iterable<File> _dartFiles(Directory directory) {
|
||||
if (!directory.existsSync()) {
|
||||
return const <File>[];
|
||||
}
|
||||
return directory
|
||||
.listSync(recursive: true)
|
||||
.whereType<File>()
|
||||
.where((file) => file.path.endsWith('.dart'));
|
||||
}
|
||||
|
||||
List<_ForbiddenImport> _forbiddenImportsFor(String relativePath) {
|
||||
return [
|
||||
const _ForbiddenImport(
|
||||
"package:scheduler_core/src/",
|
||||
'scheduler_core internals',
|
||||
),
|
||||
const _ForbiddenImport(
|
||||
"package:scheduler_persistence_sqlite/",
|
||||
'SQLite persistence adapter',
|
||||
),
|
||||
const _ForbiddenImport("package:drift/", 'Drift'),
|
||||
const _ForbiddenImport(
|
||||
"package:scheduler_notifications_desktop/",
|
||||
'desktop notification implementation',
|
||||
),
|
||||
const _ForbiddenImport("package:scheduler_backup/", 'backup package'),
|
||||
const _ForbiddenImport("package:scheduler_export/", 'export package'),
|
||||
const _ForbiddenImport(
|
||||
"package:scheduler_export_json/",
|
||||
'JSON export package',
|
||||
),
|
||||
if (relativePath != 'test/forbidden_imports_test.dart')
|
||||
const _ForbiddenImport("dart:io", 'dart:io'),
|
||||
];
|
||||
}
|
||||
|
||||
class _ForbiddenImport {
|
||||
const _ForbiddenImport(this.pattern, this.label);
|
||||
|
||||
final String pattern;
|
||||
final String label;
|
||||
|
||||
bool matches(String contents) {
|
||||
return RegExp(
|
||||
r'''import\s+['"]''' + RegExp.escape(pattern),
|
||||
).hasMatch(contents);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue