State Management
This page documents the core state primitives: objectstate.object_state.ObjectState
and objectstate.object_state.ObjectStateRegistry. The API is generic—no OpenHCS
references—and reflects the current implementation in objectstate/object_state.py.
ObjectState
Purpose
A UI-friendly model extracted from a backing object (dataclass, callable, etc.) that authoritatively stores working parameters and resolved values across a window’s lifecycle.
Core attributes
object_instance: the backing object (updated on save viato_object())parameters: flat dict of user-editable values (dotted paths for nested dataclasses)_live_resolved: last resolved values using the current ancestor stack_saved_resolved: resolved values at the last explicit save (baseline)_saved_parameters: immutable snapshot of raw parameters at save time_live_provenance: dict tracking which scope provided each inherited field valuescope_id: unique key for registry lookup
Saved vs Live
_saved_resolvedrepresents “on disk” (after last save)._live_resolvedrepresents “on screen” (after every edit and ancestor change).mark_saved()updates saved baselines from current live values.restore_saved()resets working values back to the saved snapshot.dirty_fieldstracks where_live_resolveddiffers from_saved_resolved(resolved / inherited view).is_raw_dirtyis a fast check for unsaved edits in raw parameters (parametersvs_saved_parameters).
Reading resolved values
ObjectState keeps both a live resolved snapshot and a saved resolved snapshot. Use the appropriate accessor depending on whether you want to include unsaved edits:
get_resolved_value(name): returns the live resolved value from_live_resolved(includes unsaved edits)get_saved_resolved_value(name): returns the saved resolved value from_saved_resolved(excludes unsaved edits)
Key methods
mark_saved(): set current state as the new baselinerestore_saved(): revert parameters/resolved values to saved baselinedirty_fields: resolved diffs (live vs saved) as a set of dotted field namesis_raw_dirty: true if raw parameters differ from saved parametersto_object(): materialize a concrete object from the current parameters
Initial values and nested defaults
ObjectState(..., initial_values=...) accepts authored callable kwargs at
construction. A nested dataclass value is projected through the same flat-path
owner used during normal extraction, so its container and every registered
dotted child start in agreement. ObjectState records the analyzer-derived
declared default for both nested containers and leaves. Resetting an authored
nested override therefore restores the callable’s concrete dataclass default;
a lazy container whose declared default is None still resets to None.
Callers updating an existing state should use their normal ObjectState editing
or code-document service so removed kwargs travel through
reset_parameter(). They must not flatten dataclass fields independently.
Structural subfield semantics
A tuple, list, or nested dataclass remains one writable ObjectState owner field.
subfield_semantics(DottedFieldPath(...)) projects display identities for its
structural leaves without registering those cells as a second set of state
fields. Each ObjectStateSubfieldSemantic retains the raw, live resolved,
saved resolved, and signature-default values together with explicit presence
bits, so a concrete None is not confused with a missing leaf.
Leaf dirty state compares live resolved and saved resolved values.
signature_diff compares raw state with the signature default, and
inherited_value identifies a missing raw leaf supplied by resolution. The
returned semantic_markers use * for dirty and _ for either a
signature difference or inherited value. UI tables consume this projection;
they do not recompute those predicates or write individual cells back to
ObjectState.
When an owner value changes, ObjectState reports the exact structural display
paths that changed, such as filters[0].match_type. The owner field remains
the persistence and update boundary.
Lifecycle
ObjectStates are created when an object is added, persist independently of UI windows, and are removed when unregistered from the registry.
Windows should attach to the existing state instead of creating a new state for
each editor. A caller that needs a fail-loud editing boundary can wrap the state
in ObjectStateEditSession. The session delegates updates to ObjectState and
reconstructs the edited object through to_object(); it does not introduce a
second saved/live model.
ObjectStateRegistry
Purpose
Singleton registry of all ObjectStates, keyed by scope_id. Supports lookup, ancestry
traversal, and history management.
Registration
register(state)/unregister(state)unregister_scope_and_descendants(scope_id)for an owned scope subtreeget_by_scope(scope_id)get_ancestor_objects(scope_id)/get_ancestor_objects_with_scopes(scope_id)
Saved vs Live pattern (registry-wide)
The registry coordinates saved/live baselines across all ObjectStates so that application code can distinguish “proposed” vs “committed” values while showing immediate UI feedback.
Notes
Registry methods are classmethods; the registry is effectively a singleton.
History/undo is covered separately in Undo / Redo.
Provenance tracking for inherited fields is covered in Provenance Tracking.