# 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` - 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 ```