402 lines
19 KiB
Markdown
402 lines
19 KiB
Markdown
# UTO Manager — Development Status
|
||
|
||
Last updated: 2026-08-09 — Phase 5 UI/CRUD layer merged to `main`
|
||
|
||
---
|
||
|
||
## Current Branch
|
||
|
||
`main` (Phase 5 UI/CRUD layer merged)
|
||
|
||
## Overall Progress
|
||
|
||
| Phase | Title | Status |
|
||
|-------|-------|--------|
|
||
| 1 | Domain Foundation | ✅ Complete |
|
||
| 2 | Canvas Engine & Persistence | ✅ Complete |
|
||
| 3 | Template Library & Import/Export | ✅ Complete |
|
||
| 4 | Reporting & Analytics | ✅ Complete |
|
||
| 5 | UI/CRUD Layer & Web Platform | ✅ Complete |
|
||
|
||
---
|
||
|
||
## Phase 1 — Domain Foundation ✅
|
||
|
||
**Completed items:**
|
||
|
||
- `Echelon` entity with doctrinal hierarchy validation (`isValidParent`, `isValidChild`)
|
||
- `DoctrinalBranch` entity with display names
|
||
- `AuthorizedRank` entity with pay grade, category flags, `meetsMinimum`, `gradeLevel`
|
||
- `DutyMos` value object with MOS dictionary validation and `unvalidated` constructor
|
||
- `UnitNode` entity with `copyWith`, `isRoot`, `isValidParentNode`
|
||
- `PositionSlot` entity with `isVacant`, `isFilled`, `isKeyLeader`
|
||
- `PersonnelRecord` entity with `ReadinessState`, `copyWith`, `isUnassigned`
|
||
- `UnitRepository` abstract interface
|
||
- `PersonnelRepository` abstract interface
|
||
- `domain_entities.dart` barrel export
|
||
- Full unit test coverage for all domain entities and value objects
|
||
|
||
**Test count (Phase 1):** 60 tests
|
||
|
||
---
|
||
|
||
## Phase 2 — Canvas Engine & Persistence ✅
|
||
|
||
**Completed items:**
|
||
|
||
### Domain Services (Phase 2)
|
||
|
||
- `UnitTreeService` — `buildTree`, `getSubtree`, `getAncestors`, `validateReparent`,
|
||
`validateAssignment`, `calculateFillRate`, `getVacantKeyLeaderSlots`, `getMosMismatches`
|
||
- `AssignmentService` — `assign`, `unassign`, `aggregateReadiness` with `ReadinessColor`
|
||
|
||
### Infrastructure — Database
|
||
|
||
- Drift schema: `Units`, `PositionSlots`, `Personnel`, `Templates` tables
|
||
- `AppDatabase` with `forTesting(NativeDatabase.memory())` constructor
|
||
- `UnitDao` — full CRUD + watch streams
|
||
- `PersonnelDao` — full CRUD + `assignToSlot` / `unassignFromSlot` + watch streams
|
||
- `PositionSlotDao` — full CRUD + `assignPersonnel` / `clearAssignment` + watch streams
|
||
- `TemplateDao` — full CRUD + `upsertTemplate`
|
||
- build_runner code generation (`.g.dart` files)
|
||
|
||
### Infrastructure — Repositories
|
||
|
||
- `DriftUnitRepository` — implements `UnitRepository` with domain↔Drift mapping
|
||
- `DriftPersonnelRepository` — implements `PersonnelRepository` with domain↔Drift mapping
|
||
- `DriftPositionSlotRepository` — concrete slot repository
|
||
|
||
### Application — BLoC (Phase 2)
|
||
|
||
- `UnitTreeCubit` / `UnitTreeState` (sealed) — tree loading, selection, reparent, subtree delete
|
||
- `PersonnelCubit` / `PersonnelState` (sealed) — roster loading, filter, assign/unassign
|
||
- `TemplateCubit` / `TemplateState` (sealed) — template save/load/delete
|
||
|
||
### Presentation (Phase 2)
|
||
|
||
- `HomeScreen` — three-pane adaptive layout (personnel | canvas | inspector)
|
||
- `UtoCanvasWidget` — `InteractiveViewer` pan/zoom org chart with drag-and-drop reparent
|
||
- `UnitNodeCard` — echelon-colored card with fill rate progress bar
|
||
- `PositionSlotCard` — `DragTarget` slot card with MOS badge and readiness color
|
||
- `PersonnelListTile` — `Draggable` soldier tile for assignment
|
||
- Status bar with live unit/slot fill counts
|
||
|
||
### App Wiring (Phase 2)
|
||
|
||
- `main.dart` — `MultiBlocProvider` wiring all cubits with production database
|
||
|
||
**Test count (Phase 2):** 77 new tests (137 total)
|
||
|
||
---
|
||
|
||
## Phase 3 — Template Library & Import/Export ✅
|
||
|
||
**Completed items:**
|
||
|
||
### Domain Services (Phase 3)
|
||
|
||
- `DoctrinalTemplates` — Pure-Dart library of all five 2026 BCT templates:
|
||
- `mbct` — Mobile Brigade Combat Team (3× Mobile Infantry Battalions, Cav Squadron, FA Battalion, MDEP, HKP, Engineer Battalion, Support Battalion)
|
||
- `sbct` — Stryker Brigade Combat Team (3× Stryker Infantry Battalions, Cav Recon Squadron, FA Battalion, Engineer Battalion, Support Battalion)
|
||
- `abct` — Armored Brigade Combat Team (2× Armor-Heavy CABs, 1× Infantry-Heavy CAB, Armored Cav Squadron, M109A7 FA Battalion, Engineer Battalion)
|
||
- `cabHeavy` — Combat Aviation Brigade Heavy (Attack Bn AH-64E, Assault Bn UH-60M/CH-47F, Air Ambulance Co, Aviation Support Bn)
|
||
- `cabLight` — Combat Aviation Brigade Light (Attack Recon Bn AH-64E+MQ-9A, Assault Bn, Medical Assault Bn, Aviation Support Bn)
|
||
- `TemplateInstantiator` — Converts `UnitDefinition` trees into concrete `UnitNode` + `PositionSlot` domain objects with injected ID generator
|
||
- `MtoeValidationEngine` — Pure-domain validation engine with 7 rules:
|
||
- **V001** MOS mismatch (warning)
|
||
- **V002** Rank mismatch (warning)
|
||
- **V003** Vacant key leader (critical)
|
||
- **V004** Missing combat medic 68W in platoon (advisory)
|
||
- **V005** Missing fire support specialist 13F in platoon (advisory)
|
||
- **V006** Structural hierarchy violation (critical)
|
||
- **V007** Manning below 70% (warning)
|
||
|
||
### Infrastructure — Codecs (Phase 3)
|
||
|
||
- `UnitTreeCodec` — JSON encode/decode for full unit tree snapshots (units + slots + personnel)
|
||
- Schema version 1 with `exportedAt` timestamp
|
||
- `encode` / `decode` (JSON string)
|
||
- `encodeToMap` / `decodeFromMap` (Map)
|
||
- Handles all optional fields (null parentUnitId, null assignedPersonnelId, qualifications list)
|
||
|
||
### Application — BLoC (Phase 3)
|
||
|
||
- `TemplateCubit` — Extended with:
|
||
- `instantiateDoctrinalTemplate(key)` — instantiates any of the five BCT templates into the live database
|
||
- `saveSnapshot(name, bctType, units, slots)` — saves current tree as a named template
|
||
- `exportToJson(units, slots)` — encodes tree to JSON string for export
|
||
- `importFromJson(json, {replaceExisting})` — decodes and persists imported tree
|
||
- `ValidationCubit` / `ValidationState` (sealed) — wraps `MtoeValidationEngine`:
|
||
- `validate(units, slots, personnel)` — runs all rules, emits `ValidationComplete`
|
||
- `clear()` — resets to `ValidationInitial`
|
||
- `ValidationComplete` exposes `.critical`, `.warnings`, `.advisories`, `.findingsForUnit(id)`, `.isClean`
|
||
|
||
### Domain Value Objects (Phase 3)
|
||
|
||
- `DutyMos` — Extended format validation to accept 4-character aviation AOC codes (e.g., `153A` = 3 digits + 1 letter) in addition to standard 3-character codes
|
||
|
||
### App Wiring (Phase 3)
|
||
|
||
- `main.dart` — Added `ValidationCubit` to `MultiBlocProvider`
|
||
- `TemplateCubit` — Now requires `unitRepository` and `slotRepository` parameters
|
||
|
||
**Test count (Phase 3):** 54 new tests (191 total)
|
||
|
||
---
|
||
|
||
## Phase 4 — Reporting & Analytics ✅
|
||
|
||
**Completed items:**
|
||
|
||
### Domain Services (Phase 4)
|
||
|
||
- `CommandEventStore` — In-memory append-only event log for all tree mutations:
|
||
- `CommandEvent` with `eventId`, `type`, `payload`, `occurredAt`, `isSynced`
|
||
- `CommandEventType` enum: `attachNode`, `detachNode`, `assignPersonnel`, `unassignPersonnel`, `modifySlot`
|
||
- `CommandPayloads` factory helpers for each event type
|
||
- `append`, `markSynced`, `markAllSynced`, `exportPendingToJson`, `clear`
|
||
- Unmodifiable `allEvents` and `pendingEvents` views
|
||
- `DeltaSynchronizer` — Offline-first sync helper:
|
||
- `syncIfOnline()` — transmits pending events when online, returns count synced
|
||
- `isOnline` getter/setter for connectivity simulation
|
||
- `IsolateValidationRunner` — Background-isolate wrapper for `MtoeValidationEngine`:
|
||
- `start()` / `dispose()` lifecycle management (idempotent start)
|
||
- `validate(units, slots, personnel)` — async, runs engine off the main thread
|
||
- `ValidationResponse` with `findings`, `hasError`, optional `error` string
|
||
- Results guaranteed to match synchronous engine output
|
||
|
||
### Infrastructure — Codecs (Phase 4)
|
||
|
||
- `ProtobufCodec` — Binary encode/decode for unit tree snapshots:
|
||
- 12-byte header: magic `UTOB` (0x55544F42) + version 1 (big-endian uint32)
|
||
- Payload: zlib-compressed JSON (reuses `UnitTreeCodec` for serialization)
|
||
- `encode(units, slots, personnel)` → `Uint8List`
|
||
- `decode(bytes)` → `UnitTreeSnapshot` (validates magic + version)
|
||
- `compressionRatio(binaryBytes, jsonBytes)` static helper
|
||
- Produces output smaller than raw JSON for non-trivial trees
|
||
- Throws `FormatException` for truncated data, wrong magic, or unsupported version
|
||
- `PdfChartExporter` — Pure-Dart PDF org-chart generator (no external PDF library):
|
||
- `export(units, slots, unitDesignation, {pageSize, exportDate})` → `List<int>`
|
||
- Generates valid PDF 1.4 with xref table, trailer, `%%EOF`
|
||
- Cover page with unit designation, export date, UNCLASSIFIED banner
|
||
- Org-chart page with unit boxes, connector lines (`m`/`l S` operators)
|
||
- Supports `PdfPageSize.letter` (612×792), `a4` (595×842), `tabloid` (792×1224)
|
||
- Unit names truncated to 18 chars; all echelon levels render without error
|
||
|
||
### Application — BLoC (Phase 4)
|
||
|
||
- `ValidationCubit` — Extended with isolate-backed validation:
|
||
- `startIsolate()` / `disposeIsolate()` lifecycle hooks
|
||
- `validateAsync(units, slots, personnel)` — delegates to `IsolateValidationRunner`
|
||
- Falls back to synchronous engine if isolate not started
|
||
- `ValidationState` extended with `isRunning` flag
|
||
|
||
### Presentation (Phase 4)
|
||
|
||
- `HomeScreen` — Updated with Phase 4 UI:
|
||
- Validation node indicators on canvas: red border for critical findings, amber for warnings
|
||
- "Export PDF" action in app bar → calls `PdfChartExporter`, triggers file save dialog
|
||
- "Export Binary" action → calls `ProtobufCodec`, saves `.utob` file
|
||
- Validation findings panel in inspector pane (grouped by severity)
|
||
- Status bar shows pending sync event count
|
||
|
||
**Test count (Phase 4):** 72 new tests (263 total)
|
||
|
||
---
|
||
|
||
## Phase 5 — UI/CRUD Layer & Web Platform ✅
|
||
|
||
**Completed items:**
|
||
|
||
### Domain Entities (Phase 5)
|
||
|
||
- `SoldierPosition` entity — named duty position for assignment to personnel records:
|
||
- `positionId` (UUID v4 for custom; fixed string for built-ins)
|
||
- `title` — human-readable position title (e.g. "Team Leader", "Rifleman")
|
||
- `isBuiltIn` — true for app-seeded positions; false for user-created ones
|
||
- `copyWith`, `Equatable` props, `toString`
|
||
- `SoldierPosition.builtIns` — 31 pre-defined Army duty positions across all branches:
|
||
- Command/Staff: Commander, XO, CSM, First Sergeant, S2/S3/S4/S6
|
||
- Platoon: Platoon Leader, Platoon Sergeant, RTO
|
||
- Squad/Section: Squad Leader, Team Leader
|
||
- Individual: Rifleman, Grenadier, Automatic Rifleman, Machine Gunner, Sniper,
|
||
JTAC/Fire Support, Medic, Combat Engineer, Driver, Gunner, Loader,
|
||
Tank Commander, Crew Member, Supply Sergeant, Armorer, Commo Sergeant,
|
||
Intelligence Analyst, Forward Observer, CBRN Specialist
|
||
|
||
### Domain Entities — Extended (Phase 5)
|
||
|
||
- `PersonnelRecord` — Extended with:
|
||
- `positionTitle` field (optional) — duty position title from `SoldierPosition`
|
||
- `displayLabel` getter — prefers `name`; falls back to `positionTitle`; then `(Unnamed Soldier)`
|
||
- `isAssigned` getter — true when `assignedSlotId != null`
|
||
- `isFmc` getter — true when `readinessState == fullyMissionCapable`
|
||
- `copyWith` — extended with `clearName`, `clearPositionTitle`, `clearAssignment` flags
|
||
|
||
### Infrastructure — Database (Phase 5)
|
||
|
||
- `SoldierPositions` Drift table — persists named duty positions:
|
||
- `position_id` (TEXT, PK), `title` (TEXT), `is_built_in` (BOOLEAN, default false)
|
||
- `SoldierPositionsDao` — full DAO for `SoldierPositions`:
|
||
- `getAll()`, `getById(positionId)`, `upsert()`, `upsertAll()`, `updatePosition()`,
|
||
`deletePosition()`, `watchAll()`
|
||
- `AppDatabase` — schema version bumped to **v2**:
|
||
- Migration v1→v2: adds `position_title` column to `Personnel` table; creates `soldier_positions` table
|
||
- Web platform support:
|
||
- `drift_worker.dart` / `drift_worker.js` — Drift web worker for IndexedDB persistence
|
||
- `sqlite3.wasm` — SQLite WASM binary for web target
|
||
- `_openConnection()` updated to use `driftDatabase()` (platform-adaptive: SQLite on native, WASM/IndexedDB on web)
|
||
|
||
### Infrastructure — Repositories (Phase 5)
|
||
|
||
- `DriftSoldierPositionsRepository` — Drift-backed repository for `SoldierPosition`:
|
||
- `getAll()` — returns all positions (built-in + custom)
|
||
- `watchAll()` — reactive stream of all positions
|
||
- `upsert(position)` — insert or update a position
|
||
- `seedBuiltIns()` — idempotent seed of all `SoldierPosition.builtIns` on first launch
|
||
- `delete(positionId)` — deletes custom positions only; no-op for built-ins
|
||
|
||
### Application — BLoC (Phase 5)
|
||
|
||
- `UnitTreeCubit` — Extended with full CRUD operations:
|
||
- `addUnit(unit)` — inserts a new `UnitNode`
|
||
- `updateUnit(unit)` — updates an existing `UnitNode`
|
||
- `addSlot(slot)` — inserts a new `PositionSlot`
|
||
- `updateSlot(slot)` — updates an existing `PositionSlot`
|
||
- `deleteSlot(slotId)` — deletes a position slot
|
||
- `clearAll()` — deletes all slots then all units (template reset)
|
||
- `PersonnelCubit` — Extended with full CRUD operations:
|
||
- `addPersonnel(record)` — inserts a new `PersonnelRecord`
|
||
- `updatePersonnel(record)` — updates an existing `PersonnelRecord`
|
||
- `deletePersonnel(soldierId)` — deletes soldier; clears slot assignment first if assigned
|
||
|
||
### Presentation — Dialogs (Phase 5)
|
||
|
||
- `AddEditPersonnelDialog` — full-featured dialog for creating/editing soldiers:
|
||
- Position dropdown (loaded from `DriftSoldierPositionsRepository`) with add/rename/delete custom positions
|
||
- Optional full name field (Last, First MI format)
|
||
- Pay grade dropdown (all `AuthorizedRank` values)
|
||
- Primary MOS field with live autocomplete suggestions from `DutyMos.allCodes`
|
||
- Readiness state dropdown
|
||
- MOS validation against known codes
|
||
- `AddEditSlotDialog` — dialog for creating/editing position slots:
|
||
- Position title text field
|
||
- Authorized rank dropdown
|
||
- Duty MOS field with live autocomplete suggestions
|
||
- Key leader toggle switch
|
||
- `AddEditUnitDialog` — dialog for creating/editing unit nodes:
|
||
- Optional unit name/designation field (auto-generates from echelon + branch if blank)
|
||
- Echelon dropdown
|
||
- Branch/functional area dropdown
|
||
- Optional parent unit dropdown (excludes self and descendants)
|
||
- Key leader unit toggle switch
|
||
|
||
### Presentation — HomeScreen (Phase 5)
|
||
|
||
- App bar actions:
|
||
- **Add Unit** (`add_box_outlined`) — opens `AddEditUnitDialog`
|
||
- **Add Soldier** (`person_add_outlined`) — opens `AddEditPersonnelDialog`
|
||
- **Run MTOE Validation** (`fact_check_outlined`) — triggers `ValidationCubit.validate()`
|
||
- **Export** (`upload_file_outlined`) — opens export format selection dialog
|
||
- **Clear All** (`delete_sweep_outlined`) — confirmation dialog to reset all units/slots
|
||
- Personnel panel:
|
||
- Search/filter bar (name, MOS, rank)
|
||
- Tap soldier tile to edit via `AddEditPersonnelDialog`
|
||
- Assigned/unassigned count footer
|
||
- Empty-state prompt with "Add Soldier" button
|
||
- Inspector panel:
|
||
- Edit unit button → `AddEditUnitDialog`
|
||
- Add child unit button → `AddEditUnitDialog` (pre-selected parent)
|
||
- Delete unit button → confirmation dialog (cascades to subtree)
|
||
- Validation findings section (per-unit, grouped by severity with color-coded icons)
|
||
- Position slots header with fill count and "Add Slot" button
|
||
- Per-slot edit and delete actions via `AddEditSlotDialog`
|
||
- Empty-state prompts for no units selected and no slots defined
|
||
- Export dialog (`_ExportDialog`):
|
||
- Format selection: JSON, Binary (`.utob`), PDF
|
||
- PDF page size selector (Letter, A4, Tabloid)
|
||
- Export result/error feedback inline
|
||
- Status bar:
|
||
- Unit count, slot fill rate (filled/total + percentage)
|
||
- Total soldier count
|
||
- Validation state: running spinner, clean indicator, or critical/warning/advisory counts
|
||
|
||
### App Wiring (Phase 5)
|
||
|
||
- `main.dart` — Added `DriftSoldierPositionsRepository` wired to `HomeScreen`
|
||
- `HomeScreen` — now requires `positionsRepository` parameter
|
||
|
||
**Test count (Phase 5):** existing 263 tests; widget test updated for new `HomeScreen` signature
|
||
|
||
---
|
||
|
||
## Test Summary
|
||
|
||
| File | Tests | Status |
|
||
|------|-------|--------|
|
||
| `authorized_rank_test.dart` | 16 | ✅ |
|
||
| `echelon_test.dart` | 17 | ✅ |
|
||
| `personnel_record_test.dart` | 13 | ✅ |
|
||
| `unit_node_test.dart` | 15 | ✅ |
|
||
| `duty_mos_test.dart` | 32 | ✅ |
|
||
| `assignment_service_test.dart` | 16 | ✅ |
|
||
| `unit_tree_service_test.dart` | 27 | ✅ |
|
||
| `mtoe_validation_engine_test.dart` | 22 | ✅ |
|
||
| `template_instantiator_test.dart` | 18 | ✅ |
|
||
| `unit_tree_codec_test.dart` | 14 | ✅ |
|
||
| `command_event_store_test.dart` | 25 | ✅ |
|
||
| `validation_isolate_test.dart` | 14 | ✅ |
|
||
| `protobuf_codec_test.dart` | 18 | ✅ |
|
||
| `pdf_chart_exporter_test.dart` | 19 | ✅ |
|
||
| `widget_test.dart` | 1 | ✅ |
|
||
| **Total** | **263** | **✅ All pass** |
|
||
|
||
---
|
||
|
||
## Known Limitations / Next Steps (Phase 5 Hardening)
|
||
|
||
- No multi-user or real-time sync capability (Phase 5 hardening)
|
||
- Canvas layout is vertical tree only; horizontal layout not yet implemented
|
||
- No undo/redo for assignment operations
|
||
- No search/filter on the canvas itself
|
||
- `DeltaSynchronizer` uses simulated online/offline; real network transport deferred
|
||
- `ProtobufCodec` uses zlib-compressed JSON as payload; true protobuf schema deferred
|
||
- Template instantiation does not yet persist to database in a single Drift transaction (uses sequential inserts)
|
||
- MIL-STD-2525D symbology engine not yet implemented (deferred)
|
||
- SQLCipher data-at-rest encryption not yet integrated (deferred)
|
||
- No undo/redo stack for unit/slot/personnel CRUD operations
|
||
|
||
---
|
||
|
||
## Architecture Notes
|
||
|
||
```text
|
||
lib/
|
||
├── domain/
|
||
│ ├── entities/ # Pure Dart domain models (no Flutter deps)
|
||
│ │ # UnitNode, PositionSlot, PersonnelRecord, SoldierPosition
|
||
│ ├── value_objects/ # DutyMos (supports 3-char and 4-char AOC formats)
|
||
│ ├── repositories/ # Abstract interfaces
|
||
│ └── services/ # UnitTreeService, AssignmentService,
|
||
│ # MtoeValidationEngine, TemplateInstantiator,
|
||
│ # DoctrinalTemplates, CommandEventStore,
|
||
│ # DeltaSynchronizer, IsolateValidationRunner
|
||
├── infrastructure/
|
||
│ ├── codecs/ # UnitTreeCodec (JSON), ProtobufCodec (binary),
|
||
│ │ # PdfChartExporter (PDF org chart)
|
||
│ ├── database/ # Drift schema (v2), DAOs, generated code
|
||
│ │ # Tables: Units, PositionSlots, Personnel,
|
||
│ │ # SoldierPositions, Templates
|
||
│ └── repositories/ # Drift-backed implementations
|
||
│ # DriftSoldierPositionsRepository (new in Phase 5)
|
||
├── application/
|
||
│ └── blocs/ # UnitTreeCubit, PersonnelCubit,
|
||
│ # TemplateCubit, ValidationCubit
|
||
└── presentation/
|
||
├── screens/
|
||
│ ├── home_screen.dart # Full CRUD UI + export dialog
|
||
│ └── dialogs/ # AddEditPersonnelDialog, AddEditSlotDialog,
|
||
│ # AddEditUnitDialog (all new in Phase 5)
|
||
└── widgets/ # Canvas, cards, tiles
|
||
```
|