RunLifecycleController

Substrate-level run-lifecycle bookkeeping shared by every configuration-shaped app: the "has the user edited the document since the last successful run" flag, plus the typed last-terminal- result holder it cross-flows with.

Pre-decomposition, each of the four app controllers (Single, Scenario, Experiment, Simopt) reimplemented this with the same mechanics — a MutableStateFlow<Boolean> that every editing mutator flips to true, paired with a MutableStateFlow<R?> that the run-completion path populates and that structural edits invalidate. Only the name (editedSinceLastSim vs editedSinceLastRun) and the result type (RunResult vs a tighter subtype) differ. This type captures that shared pair so each app controller composes one instance rather than re-declare four near-identical flows.

What this owns

What this deliberately does NOT own

  • App-specific staleness flags like Simopt's modelAwareStale. Those track orthogonal concerns (pre-run validation) and stay on the host.

  • Side-effect fan-out on edit — refreshers for step completion, validation, etc. are app-specific orchestration. Host controllers call markEdited / setLastResult and then fan their own side effects.

  • The host's public flag name. Three of the four current apps expose editedSinceLastSim; Simopt exposes editedSinceLastRun. The substrate uses the cleaner Run term internally; hosts re-point their existing public property to editedSinceLastRun as a name-preserving alias.

Semantics of the five mutators

MethodeditedSinceLastRunlastResultTypical caller
markEditedtrueunchangedevery editing mutator
markRunCompletedfalse→ resultterminal run-completion callback
setLastResultunchanged→ valuestructural-edit invalidation; transform-and-replace
clearEditedSinceLastRunfalseunchangedload whose loaded state matches the file (but caller wants result to survive)
resetfalsenullnew document / reset / load

Generic over the result type

Single / Scenario / Experiment compose RunLifecycleController<RunResult>; Simopt composes RunLifecycleController<RunResult.OptimizationCompleted>. The substrate never touches the result's internals — only the typed reference — so each host keeps its existing payload typing.

Substrate-level API — usable by any UI shell. Plain class — no background work owned, no scope, not AutoCloseable.

Constructors

Link copied to clipboard
constructor()

Properties

Link copied to clipboard
val editedSinceLastRun: StateFlow<Boolean>

true when the document has been edited since the last successful run (or load / reset). Cleared by markRunCompleted, clearEditedSinceLastRun, and reset.

Link copied to clipboard
val lastResult: StateFlow<R?>

The most recent terminal run result, or null after invalidation / reset / before any run has completed. Populated by markRunCompleted and setLastResult.

Functions

Link copied to clipboard

Clear editedSinceLastRun without touching lastResult. Use when the document has been loaded or its edit-tracking baseline has been re-established, but the host wants to keep whatever result is currently in lastResult (e.g. restoring a session that includes a prior result and a clean edit state).

Link copied to clipboard

Idempotently mark the document edited since the last run. No-op when already true (no spurious StateFlow emission). Hosts fan their own side effects (last-result invalidation for structural edits, validation refresh, model-aware staleness flag, etc.) off this call; this method only flips the edited-since-last-run flag.

Link copied to clipboard
fun markRunCompleted(result: R)

Record a terminal run completion: bind result as lastResult AND clear editedSinceLastRun in a single call. The two flags MUST move together at run-completion time (a stale "edited since last run" badge would mislead), which is why this is one method rather than two.

Link copied to clipboard
fun reset()

Reset to a fresh state: lastResult = null, editedSinceLastRun = false. Called by the app controller's newDocument / resetConfiguration / loadConfiguration entry points after the per-app state has been reset.

Link copied to clipboard
fun setLastResult(result: R?)

Set lastResult directly without touching the edited flag. Use for: