02. Central App State and Workflow Ownership
The Core Runtime Decision
The most important runtime decision in the repo is documented in ADR-001: Use Central App State Store:
- there is one canonical
AppState - the application layer owns state transitions
- view models project state rather than owning parallel truth
- command guards derive from canonical state
This choice appears in:
src/InspectionPrototype.Application/State/AppState.cssrc/InspectionPrototype.Application/Services/AppStateStore.cssrc/InspectionPrototype.Application/Guards/CommandGuards.cssrc/InspectionPrototype.Application/Services/WorkflowService.cs
Why One Canonical AppState
AppState is intentionally broad. It contains:
- connection, workflow, motion, and camera state
- safety signals
- stage position
- loaded recipe and recipe catalog
- active run, last run summary, and run history
- active alarms and diagnostics
- latest telemetry and latest frame
- pipeline counters and operational counters
- simulator profile catalog and selected profile
That breadth can look heavy at first, but it solves a real problem: the runtime has many background producers, and the UI still needs one coherent truth.
Without a canonical state model, this app would be unusually easy to break:
- command rules would drift
- machine readiness would be inconsistent
- view models would start caching their own flags
- tests would need more UI bootstrapping to verify behavior
Why AppStateStore Is So Small
The mutation owner is:
src/InspectionPrototype.Application/Services/AppStateStore.cs
It is deliberately simple:
- one current snapshot
- one lock
- one
Updatemethod - one
StateChangedevent
That simplicity is a feature. The repo does not need a full event-sourcing or reducer framework to teach good state ownership. It just needs one place where everyone knows runtime mutation goes.
Command Guards Depend On Shared Truth
Command semantics live in:
src/InspectionPrototype.Application/Guards/CommandGuards.cs
Examples:
CanStartdepends on connection, recipe, homing, safety, no critical fault, and workflow stateCanStopdepends on active workflow stateCanAbortis valid in a broader set of transitional statesCanRecoverdepends on bothFaultedstate and fault clearance
This is a great teaching example because it shows how command legality should come from runtime truth, not from scattered button logic.
Why WorkflowService Owns Orchestration
WorkflowService is the runtime coordinator.
It owns:
- connection lifecycle
- homing
- recipe promotion into active runtime state
- run startup
- stop and abort
- fault handling and recovery
- terminal summary creation
- history updates
This makes it a high-context service, but that is preferable to spreading workflow rules across view models, code-behind, and simulator classes.
Trade-Offs
Benefits
- one shared runtime vocabulary
- clearer testing seams
- predictable command behavior
- thinner presentation layer
Costs
AppStatebecomes largeWorkflowServicedemands more careful reading- changes to runtime semantics often touch several state-related files together
This is still a good fit for the repo. The app is intentionally stateful, so its architecture should make state explicit.
Related Lessons
- 08. State Management in .NET - Real World
- 06. MVVM in Industrial Systems - Real World
- 07. DI in Industrial WPF Systems - Real World
Diagram Brief
Title: Canonical state and workflow ownershipPurpose: Show howAppState,AppStateStore,CommandGuards, andWorkflowServicecooperateAudience: newcomer developer learning ownership boundariesNodes: AppState, AppStateStore, WorkflowService, CommandGuards, MainViewModel, Background ServicesEdges: background services and workflow service update AppState through AppStateStore; command guards evaluate AppState; MainViewModel projects AppStateGrouping: State storage, orchestration, guard evaluation, projectionCaption: Runtime correctness comes from one shared state model and one clear mutation pathDestination file path:docs/diagrams/source/course-04-02-central-app-state-and-workflow-ownership.drawio