List Item Preview System

The list item preview system provides declarative, per-field styling for list items in manager widgets (PlateManager, PipelineEditor). It displays configuration previews with visual indicators for dirty state and signature differences.

Overview

List items in pyqt-reactive show rich previews of configuration state:

▶ MyPlate (W:8 | Seq:C,Z)           ← Inline format
  /path/to/plate
  └─ wf:[3] | mat:ZARR | configs=[NAP, FIJI]

Visual semantics:

  • Underline = field differs from signature default (explicitly set)

  • Asterisk (*) = resolved value differs from saved (dirty/unsaved)

  • Name inherits styling if ANY field is dirty/sig-diff

Declarative Configuration

Each widget declares its format using ListItemFormat:

from pyqt_reactive.widgets.shared.abstract_manager_widget import ListItemFormat

class PlateManagerWidget(AbstractManagerWidget):
    LIST_ITEM_FORMAT = ListItemFormat(
        first_line=(),                    # Fields after name on line 1
        preview_line=(                    # Fields on └─ preview line
            'num_workers',
            'vfs_config.materialization_backend',
            'path_planning_config.well_filter',
        ),
        detail_line_field='path',         # Field for detail line
        show_config_indicators=True,      # Show NAP/FIJI/MAT configs
        formatters={                      # Custom formatters (optional)
            'my_field': lambda v: f"custom:{v}" if v else None,
        },
    )

Field abbreviations are declared on config classes via @global_pipeline_config:

@global_pipeline_config(field_abbreviations={'well_filter': 'wf', 'output_dir_suffix': 'out'})
@dataclass(frozen=True)
class PathPlanningConfig:
    well_filter: Optional[str] = None
    output_dir_suffix: str = "_pyqt_reactive"

ListItemFormat Fields

Field

Default

Description

first_line

()

Tuple of field paths shown after name on line 1

preview_line

()

Tuple of field paths shown on └─ preview line

detail_line_field

None

Single field path for detail line (e.g., 'path')

show_config_indicators

True

Whether to show config indicators (NAP, FIJI, MAT)

formatters

{}

Dict mapping field path to formatter function

Visual Layout

Multiline Format

▶ ItemName (first_line_segments)     ← Line 1: name + first_line fields
  detail_line                         ← Line 2: e.g., path
  └─ preview_segments | configs=[NAP, FIJI]  ← Line 3: preview + configs

Inline Format

▶ ItemName  (seg1 | seg2 | seg3)     ← Single line with all segments

The format is determined by the StyledTextLayout.multiline flag, set automatically based on whether detail_line or preview_line are populated.

Field Types

Permanent vs Dynamic Fields

Type

Source

Behavior

Declared

first_line, preview_line

Always shown (permanent)

Config indicators

@global_pipeline_config(preview_label='NAP')

Shown if config’s enabled=True

Sig-diff fields

Auto-detected

Dynamically added when value differs from signature default

The auto-include logic (in _build_multiline_styled_text) automatically adds any field with a non-default value to the preview line, even if not explicitly declared.

Value Formatting

All field values are formatted by format_preview_value() in config_preview_formatters.py:

def format_preview_value(value: Any) -> Optional[str]:
    if value is None:
        return None
    if isinstance(value, Enum):
        return value.name if value.value else None
    if isinstance(value, list):
        if value and isinstance(value[0], Enum):
            return ','.join(v.value for v in value)
        return f'[{len(value)}]'
    if callable(value) and not isinstance(value, type):
        return getattr(value, '__name__', str(value))
    return str(value)

Field abbreviations are declared on config classes via @global_pipeline_config(field_abbreviations=...):

@global_pipeline_config(field_abbreviations={'well_filter': 'wf', 'output_dir_suffix': 'out'})
@dataclass(frozen=True)
class PathPlanningConfig:
    well_filter: Optional[str] = None
    output_dir_suffix: str = "_pyqt_reactive"

Adding a New Preview Field

  1. Add to ListItemFormat (in widget):

LIST_ITEM_FORMAT = ListItemFormat(
    preview_line=(
        'existing_field',
        'my_new_config.some_field',  # Add field path
    ),
    formatters={
        'some_field': lambda v: f"custom:{v}" if v else None,  # Optional formatter
    },
)
  1. Add abbreviation (on config class):

@global_pipeline_config(field_abbreviations={'some_field': 'sf'})
@dataclass(frozen=True)
class MyNewConfig:
    some_field: str = "default"

Adding a Config Indicator

Config indicators (NAP, FIJI, MAT) and field abbreviations are both declared via @global_pipeline_config:

from objectstate import global_pipeline_config

@global_pipeline_config(
    preview_label='NEW',
    field_abbreviations={'some_setting': 'set'}
)
@dataclass
class MyNewConfig:
    enabled: bool = False
    some_setting: int = 10

The config indicator appears in configs=[...] when enabled=True.

Structured Rendering Architecture

The system uses structured StyledTextLayout objects instead of string parsing:

@dataclass(frozen=True)
class Segment:
    """A styled text segment with field path for dirty/sig-diff matching."""
    text: str
    field_path: Optional[str] = None  # None=no styling, ''=root path

@dataclass
class StyledTextLayout:
    """Structured layout for styled text rendering."""
    name: Segment
    status_prefix: str = ""
    first_line_segments: List[Segment] = field(default_factory=list)
    detail_line: str = ""
    preview_segments: List[Segment] = field(default_factory=list)
    config_segments: List[Segment] = field(default_factory=list)
    multiline: bool = False

The delegate iterates segments directly and applies styling per-segment without any string matching:

  1. Build StyledTextLayout with all segments

  2. Store layout in item data (LAYOUT_ROLE)

  3. Delegate reads layout and renders segments with appropriate styling

  4. No string parsing required

See Also

Implementation References:

  • pyqt_reactive/widgets/shared/list_item_delegate.py - StyledTextLayout and rendering

  • pyqt_reactive/widgets/shared/abstract_manager_widget.py - ListItemFormat and build methods

  • pyqt_reactive/utils/preview_formatters.py - Centralized formatters