Skip to content

U3+L4: generic Qt machinery leaves OpenHCS (pyqt-reactive, ObjectState, python-introspect) - #1196

Merged
trissim merged 17 commits into
mainfrom
refactor/U3-generic-qt-machinery
Oct 11, 2026
Merged

trissim merged 17 commits into
mainfrom
refactor/U3-generic-qt-machinery

Conversation

@trissim

@trissim trissim commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

Surfaces U3 + L4 of the consolidation refactor: generic Qt and ObjectState machinery leaves OpenHCS. Receipt: docs/refactor/consolidation-20261009/U3-generic-qt-machinery.md (first commit; updated at the end with what was built and the corrections).

Library PRs (release first, in this order)

  1. python-introspect 0.2.6 — Add FieldLabel, the Annotated display label for a field (0.2.6) python-introspect#21 (FieldLabel)
  2. ObjectState 1.4.0 — ScopeKey, inspection, authored-path retention; delete HistoryMigration (1.4.0) ObjectState#27 (ScopeKey, objectstate.inspection, retain_only_authored_paths, MAIN_BRANCH, is_restoring_snapshot; deletes HistoryMigration/migration=)
  3. pyqt-reactive 0.5.0 — Generic Qt machinery from OpenHCS; open path-cache family; header timer leak (0.5.0) PyQT-reactive#22 (editable table, grid selector, time travel, config windows, dock and shortcut layout, open PathCacheKey family, header-timer leak fix)

The submodules point at those branch heads and pyproject.toml bounds are bumped (objectstate>=1.4,<2, python-introspect>=0.2.6,<0.3, pyqt-reactive>=0.5,<0.6). pyqt-reactive's requirements-ci.txt pins the unreleased ObjectState and python-introspect heads until they are published. Nothing is tagged or released.

What moved (OpenHCS keeps only declarations and domain glue)

  • L4: the editable-table block of source_bindings_editor.py; core/path_cache.py (verbatim copy) deleted, PlateImport(PathCacheKey) declared beside its use; core/field_label.py → python-introspect; the pre-XDG ~/.openhcs migration in xdg_paths.py deleted.
  • Scope grammar: every :: join/split/prefix test and SCOPE_SEGMENT_SEPARATOR → objectstate.ScopeKey.
  • GlobalEventBus deleted: DualEditorWindow follows the session's PipelineChanged/PipelineImported/GlobalConfigChanged events for its own plate and its pipeline after a restore.
  • Dock and shortcuts: DockPane/DockFloatController/DockPanes/DockLayoutStore and ShortcutLifecycle[ShortcutConfig]; OpenHCS keeps window specs, topology and MainWindowEmbeddedWidgets(DockPanes).
  • Time travel: widget, snapshot browser, commands and TimeTravelNavigator (main.py's four navigation methods and time_travel_navigation.py deleted); OpenHCS supplies the hidden dataset-list scope, step_function_scope and the tab choice.
  • Grid selector: plate_view_widget.py deleted; the image browser uses GridSelectorWidget through GridAxisAddressing over the GridAddressed axis.
  • Config windows: ConfigWindow, ConfigEditSession, ConfigCodeDocumentDriver, CodeEditorFormUpdater; OpenHCS keeps OpenHCSConfigWindow (document-format adapter, window id). OPENHCS_USE_EXTERNAL_EDITOR is read in one place (form config).
  • Inspection: the bridge's field projection inherits objectstate.inspection.FieldInspection; the */_ marker rule (written four times) is field_markers/state_markers.

Corrections to the plan

  • UI-bridge server and window introspection did not move: they return openhcs.agent.dto.ui_bridge types and dispatch through UiBridgeOperation; moving them now would ship ~2.9k lines of DTO/contract code inside a Qt package for U4 to move again. Reassigned to U4 (recorded in 04-ARCHITECTURE.md's U4 row).
  • ObjectState view fields were not built: no consumer at this head (U1 moved session state onto Session; metadata contracts already hold state outside dirty detection).
  • The GlobalEventBus replacement is the session's event log, not a new ObjectState subscription API (ObjectState already had them).

Deletion certificates

  • derived: core/path_cache.py (pyqt-reactive owns it); core/field_label.py (python-introspect FieldLabel); GlobalEventBus, GuiEventBusBroadcaster, get_event_bus, AbstractManagerWidget.event_bus (session events publish the same facts; the bus fired pipeline_changed only for the pipeline editor's current plate, the subscription now follows each editor's own plate); ExternalCodeEditorPreference, external_editor_enabled, pipeline_editor_external_editor_enabled, PIPELINE_EDITOR_EXTERNAL_EDITOR_ENV (use_external_code_editor + FormGenConfig.external_editor_env_var); CodeEditorFormUpdater.patch_lazy_constructors (forwarded to objectstate); LazyDataclassFieldEmissionState.retain_only_authored_paths (objectstate); the four */_ marker helpers and the bridge's type/field-name helpers (objectstate.inspection); ObjectStateFieldProvenanceEffect family (FieldProvenance).
  • unused: get_root_from_scope_key, is_scope_affected (no callers; ScopeKey.root/contains); DualEditorWindow._on_config_changed's PipelineConfig branch (never emitted); the plate view's subdirectory selector (active_subdir never read) and partition-filter sync (partition_filter_widget never assigned); library get_cached_path, clear_cache, remove_cached_path and unused PathCacheKey members; the tree:: flash namespace (never produced).
  • contract: the ~/.openhcs legacy migration in xdg_paths.py and HistoryMigration/migration= (rule 1, no backwards compatibility; U5 decision that histories are runtime state); the library's path-cache alias functions; PathNameRole.PIPELINE/STEP/FUNCTION (F1 deleted those file formats).
  • tests-only (behaviour changes to note): after time travel with no field or function to focus, the step editor keeps its current tab instead of switching to Step Settings; the grid status reads "N wells available" instead of "N wells have images"; PatternDataManager.extract_func_and_kwargs raises on a malformed pattern entry instead of the entry being dropped on save; a path parameter named like pipeline_path now gets the default path behaviour and cache context.

Verification

  • Guards: tests/unit/pyqt_gui/test_generic_qt_machinery_guards.py 9/9 pass.
  • Structural ratchet (agent-comms-ratchet, openhcs/scripts/benchmark vs 84e7a29): exit 0, no positive delta (TypeIdentity 9→6, LongBooleanChain 12→9, StringSubscript 46→17).
  • NRA: .github/tests/test_refactor_guardrails.py 36 passed; scripts.check_refactor_r1 increased: [].
  • Offscreen (QT_QPA_PLATFORM=offscreen, no DISPLAY): tests/unit/pyqt_gui + tests/pyqt_gui 907 passed; the remaining failures (test_managed_windows.py ×2, test_image_browser_filter_controller.py ×3, test_end_to_end_workflow_foundation.py ×8) fail identically on an export of 84e7a29 or reference attributes absent there. tests/unit/agent, tests/unit/desktop and the scope/compiler/session tests: 1431 passed; test_declaration_selected_discovery/test_knowledge_lazy_conversion_boundary (also fail on 84e7a29 here), test_mcp_distribution_runtime ×2 (known failures), and three that pass when rerun alone (test_mcp_stdio_transport, test_desktop_restart, test_viewer_render_snapshot).
  • Libraries: CI green on ObjectState#27 and PyQT-reactive#22 (all OS/Python jobs, code-quality, distribution) and python-introspect#21. Locally ObjectState 225 passed; pyqt-reactive 649 passed + 1 environmental failure (local venv zmqruntime metadata 0.4.1).

U3 · PR #1196 · guards 9/9 · production −6,617 +356 · tests −1,155 +359

🤖 Generated with Claude Code

trissim and others added 15 commits October 11, 2026 03:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The editable-table block of the source-binding editor (cell editor family,
  dataclass-derived columns, record picker, table controller and layout) now
  lives in pyqt_reactive.widgets.editable_table; the editor imports it.
- FieldLabel comes from python-introspect; core/field_label.py is deleted.
- core/path_cache.py, a verbatim copy of the library module, is deleted. The
  plate manager declares PlateImport, a PathCacheKey subclass, beside its one
  use; the stored key is unchanged (plate_import).
- xdg_paths no longer migrates files out of a pre-XDG ~/.openhcs directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SCOPE_SEGMENT_SEPARATOR and the hand-written '::' joins, splits and prefix
tests in the session, step scopes, pipeline scope identity, compiler, image
browser, live measurements, plate viewer and MCP viewer renderer call ScopeKey.
Window-route regexes derive the separator from ScopeKey.SEPARATOR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GlobalEventBus restated two facts the session already publishes: a dataset's
pipeline changed (PipelineChanged/PipelineImported) and the global config
changed (GlobalConfigChanged). The pipeline editor re-broadcast the first only
for its current plate, and only DualEditorWindow listened. DualEditorWindow now
subscribes to the session's events for its own plate (and follows its pipeline
after time travel restores it); GlobalEventBus, GuiEventBusBroadcaster and
get_event_bus are deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The main window builds its panes with pyqt-reactive's DockPane, persists the
layout with DockLayoutStore under its existing key and version, and binds its
menu and time-travel shortcuts through ShortcutLifecycle[ShortcutConfig].
OpenHCS keeps the window specs, the dock topology and MainWindowEmbeddedWidgets,
a DockPanes subclass naming its panes by window id. The generic dock and
shortcut tests moved to the library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status-bar time-travel widget, the snapshot browser and the restore
commands are pyqt-reactive's; OpenHCS supplies its mutation guard and hides
the global and dataset-list scopes from history. The post-restore navigation
(main.py's four methods and services/time_travel_navigation.py) is
TimeTravelNavigator; OpenHCS supplies which scopes are a step's function
children (step_function_scope, through StepEditorScope rather than a second
'functionstep_' parser) and which dual-editor tab to show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The plate view is a GridSelectorWidget addressed by the family's
GridAddressed axis itself (it already declares the default grid, cell
positions and row labels). Deleted with plate_view_widget.py: the plate-output
subdirectory selector, whose selection nothing read, and the partition-filter
sync, whose filter widget was never assigned.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenHCSConfigWindow is pyqt-reactive's ConfigWindow with the OpenHCS
configuration document (an adapter over ConfigDocumentAuthority) and the
global-config window id. Deleted: config_edit_session.py,
config_window_code_document.py, ui/shared/code_editor_form_updater.py, the
three readings of OPENHCS_USE_EXTERNAL_EDITOR (the variable name is now the
form config's external_editor_env_var) and
LazyDataclassFieldEmissionState.retain_only_authored_paths (now
objectstate.retain_only_authored_paths). The image browser's grid selector
addresses wells through GridAxisAddressing over the GridAddressed axis.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ObjectStateFieldSemanticProjection inherits objectstate's FieldInspection
(value, edit state, provenance, types) and adds only the bridge DTO
projection; the provenance effect classes and the duplicated type/field-name
helpers are deleted. The '*'/'_' marker rule, written four times, is
objectstate.inspection's field_markers/state_markers.

tests/unit/pyqt_gui/test_generic_qt_machinery_guards.py keeps the moved
modules and names out of OpenHCS, the '::' scope grammar inside ScopeKey, the
legacy XDG migration deleted, the library families free of enums, the
external-editor switch named once and the markers in objectstate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ve 0.5.0, python-introspect 0.2.6)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…river

step_function_scope asks StepEditorScope.is_function_scope and the tab choice
goes through DualEditorWindowNavigationDriver.select_tab_for_field_path, which
already owns the field-path-to-tab rule, instead of probing their fields.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s connected

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…doff

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 11, 2026 08:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

trissim and others added 2 commits October 11, 2026 04:36
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctive 0.5.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim merged commit c8838b2 into main Oct 11, 2026
5 of 43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants