State Management

This page documents how pyqt-reactive manages form state and integrates with ObjectState for hierarchical configuration management.

Form State Management

Purpose

pyqt-reactive manages form state through the ParameterFormManager and FieldChangeDispatcher. Forms are automatically generated from dataclasses and maintain bidirectional binding with the underlying data model.

Key Components

ParameterFormManager
  • Generates PyQt6 forms from dataclass definitions

  • Manages widget creation and layout

  • Collects and validates user input

  • Integrates with ObjectState for lazy configuration

FieldChangeDispatcher
  • Broadcasts field changes across the form

  • Enables reactive updates when one field changes

  • Supports conditional field visibility and enablement

  • Triggers validation and preview updates

ValueCollectionService
  • Gathers current values from all widgets

  • Handles type conversion and validation

  • Returns typed dataclass instances

ObjectState Integration

Purpose

pyqt-reactive integrates with ObjectState to support lazy configuration and hierarchical inheritance. Forms can display placeholder text showing inherited values from parent contexts.

Key Features

  • Lazy Resolution: Fields with None values inherit from context hierarchy

  • Provenance Tracking: UI shows where each value comes from (global, pipeline, step)

  • Placeholder Text: Empty fields display inherited values as placeholders

  • Dirty Tracking: Automatic detection of unsaved changes

  • Undo/Redo: Git-style history with branching timelines

Integration Pattern

When a form is created from a dataclass with ObjectState context:

  1. Form widgets are created for each field

  2. Placeholder text shows inherited values from parent scopes

  3. User edits update ObjectState’s live parameters

  4. Dirty fields are tracked automatically

  5. Save commits changes to ObjectState baseline

Widget Protocols

Purpose

pyqt-reactive uses ABC-based protocols to define type-safe widget contracts. This eliminates duck typing in favor of explicit, fail-loud inheritance-based architecture.

Core Protocols

ValueGettable
  • Widgets that can return their current value

  • Method: get_value() -> Any

ValueSettable
  • Widgets that can accept a new value

  • Method: set_value(value: Any) -> None

PlaceholderCapable
  • Widgets that display placeholder text for inherited values

  • Method: set_placeholder_text(text: str) -> None

RangeConfigurable
  • Widgets with min/max constraints (spinboxes, sliders)

  • Methods: set_minimum(), set_maximum()

EnumSelectable
  • Widgets that display enum options (comboboxes)

  • Method: set_enum_values(values: List[str]) -> None

ChangeSignalEmitter
  • Widgets that emit signals when values change

  • Signal: value_changed

Form Lifecycle

Purpose

Understanding the form lifecycle helps you integrate custom logic at the right points.

Stages

  1. Creation: ParameterFormManager(ConfigClass) - Analyzes dataclass fields - Creates widgets based on types - Sets up signal connections

  2. Display: form.show() - Renders widgets in layout - Initializes placeholder text (if ObjectState context) - Connects change signals

  3. User Interaction - User edits fields - Widgets emit value_changed signals - FieldChangeDispatcher broadcasts changes - Dependent fields update reactively

  4. Collection: form.collect_values() - Gathers current values from all widgets - Validates types and constraints - Returns typed dataclass instance

  5. Cleanup: form.closeEvent() - Unregisters from cross-window updates - Cleans up signal connections - Removes flash overlays

ObjectStateRegistry

Purpose

The registry coordinates saved/live baselines across all ObjectStates so that application code can distinguish “proposed” vs “committed” values while showing immediate UI feedback.

Key Methods

Baseline Management
  • get_baseline(scope_id): Get the saved baseline for a scope

  • set_baseline(scope_id, value): Update the saved baseline

  • get_live(scope_id): Get current live (proposed) values

History Navigation
  • time_travel_back(): Revert to previous state

  • time_travel_forward(): Move forward in history

  • time_travel_to_snapshot(id): Jump to specific snapshot

  • time_travel_to_head(): Jump to latest state

  • create_branch(name): Create experiment branch

  • switch_branch(name): Switch to different branch

Dirty Tracking
  • get_dirty_states(): Get all modified scopes

  • is_dirty(scope_id): Check if scope has unsaved changes

Flash Callbacks

ObjectState notifies pyqt-reactive of parameter changes through the on_resolved_changed callback mechanism. This enables visual feedback (flash animations) when values change.

See Flash Callback System for details on how the flash callback system works.

Notes

  • Registry methods are classmethods; the registry is effectively a singleton.

  • History/undo is covered separately in Time Travel and Branching.

  • Forms automatically register with the registry when created in ObjectState context.