443 lines
19 KiB
Markdown
443 lines
19 KiB
Markdown
|
|
# `pytest-qt` chart UI E2E handoff
|
||
|
|
|
||
|
|
## Purpose
|
||
|
|
|
||
|
|
Build a layered, deterministic UI test system for Piker's PyQt6 and
|
||
|
|
PyQtGraph chart interface. The first goal is to audit existing behavior with
|
||
|
|
real Qt objects and synthetic data. Later phases compose the full chart UI,
|
||
|
|
Trio guest run and selected Tractor actors without making live brokers,
|
||
|
|
credentials or network access part of the default gate.
|
||
|
|
|
||
|
|
This is a strategy and agent handoff, not an implementation record. Do not
|
||
|
|
change user-owned task markers while executing it.
|
||
|
|
|
||
|
|
## Branch and PR context
|
||
|
|
|
||
|
|
The chart-local gap stack is the immediate reference implementation:
|
||
|
|
|
||
|
|
- Base: `2aa66c2cf6c76b8e270af9367d27ca16f63e7024`
|
||
|
|
(`ib_methproxy_refinery`).
|
||
|
|
- Head: `335190a4e3ff5712626288ac5e81693b9646c140`
|
||
|
|
(`chart_local_gapper`).
|
||
|
|
- Range: five commits, `2aa66c2c..335190a4`.
|
||
|
|
- Both cached `gitea/chart_local_gapper` and
|
||
|
|
`dev/chart_local_gapper` refs resolve to the head.
|
||
|
|
- The intended prospective PR base is `ib_methproxy_refinery`, not `main`.
|
||
|
|
|
||
|
|
The root checkout has unrelated human changes in `.gitignore` and untracked
|
||
|
|
`.modden/`, `todo/` and `wkts/`. Future UI-test work should use an isolated
|
||
|
|
worktree and leave those paths untouched.
|
||
|
|
|
||
|
|
## Current evidence
|
||
|
|
|
||
|
|
`tests/test_gap_overlays.py` proves that small real-Qt integration tests can
|
||
|
|
find bugs below Piker's own API surface:
|
||
|
|
|
||
|
|
- Real `PlotWidget`, `PlotItem`, `ViewBox` and `QGraphicsScene` objects found
|
||
|
|
stale PyQtGraph registries after direct scene removal
|
||
|
|
(`tests/test_gap_overlays.py:293-409`).
|
||
|
|
- Scene spatial-index lookups found that
|
||
|
|
`prepareGeometryChange()` ran after geometry mutation
|
||
|
|
(`tests/test_gap_overlays.py:412-482`).
|
||
|
|
- Duplicate FQMEs found that chart-local state must include concrete chart
|
||
|
|
identity (`tests/test_gap_overlays.py:485-540`).
|
||
|
|
- Startup and focus tests found unrelated cached charts being treated as
|
||
|
|
realtime targets (`tests/test_gap_overlays.py:543-648`).
|
||
|
|
- A real `QKeyEvent` traverses `EventRelay` and the async chart handler
|
||
|
|
(`tests/test_gap_overlays.py:651-735`).
|
||
|
|
- A real Tractor child tests endpoint/client correlation, cancellation and
|
||
|
|
owner cleanup (`tests/test_gap_overlays.py:738-1025`). That is cross-process
|
||
|
|
IPC integration, not full chart UI E2E, because rendering is stubbed.
|
||
|
|
|
||
|
|
The reusable rule is: keep Qt and PyQtGraph real; fake the feed, SHM and
|
||
|
|
service edges at the narrowest useful boundary.
|
||
|
|
|
||
|
|
## Adoption prerequisites
|
||
|
|
|
||
|
|
### Dependency and pytest configuration
|
||
|
|
|
||
|
|
1. Add `pytest-qt` to the `testing` dependency group beside `pytest` in
|
||
|
|
`pyproject.toml:150-152`, then regenerate `uv.lock`.
|
||
|
|
2. Resolve the release against the locked pytest version rather than guessing
|
||
|
|
a version constraint.
|
||
|
|
3. Remove or replace the comments-only `pytest.ini`. Its presence currently
|
||
|
|
disables the intended `[tool.pytest.ini_options]` configuration in
|
||
|
|
`pyproject.toml:169-180`.
|
||
|
|
4. Configure `qt_api = "pyqt6"`. Also set `PYTEST_QT_API=pyqt6` in the
|
||
|
|
targeted CI job as an explicit process-level guard.
|
||
|
|
5. Keep `-p no:xonsh` until collection behavior is deliberately changed.
|
||
|
|
6. Register a `qt` marker only if it will be used consistently for selection;
|
||
|
|
do not invent broad `gui`, `network` or `offline` markers.
|
||
|
|
|
||
|
|
Official pytest-qt behavior to rely on:
|
||
|
|
|
||
|
|
- `qtbot` owns application creation and widget tracking.
|
||
|
|
- `qtbot.addWidget()` closes registered widgets after each test.
|
||
|
|
- `qtbot.waitSignal()`, `waitSignals()` and `waitUntil()` provide bounded,
|
||
|
|
postcondition-oriented synchronization.
|
||
|
|
- `qtbot.waitExposed()` and `waitActive()` cover real focus/window tests.
|
||
|
|
- Qt virtual-method and slot exceptions are captured as test failures.
|
||
|
|
- `qtbot.screenshot()` is useful as a failure artifact, not an initial
|
||
|
|
pixel-golden assertion system.
|
||
|
|
|
||
|
|
References:
|
||
|
|
|
||
|
|
- <https://pytest-qt.readthedocs.io/en/latest/intro.html>
|
||
|
|
- <https://pytest-qt.readthedocs.io/en/latest/reference.html>
|
||
|
|
- <https://pytest-qt.readthedocs.io/en/latest/signals.html>
|
||
|
|
|
||
|
|
### Headless process contract
|
||
|
|
|
||
|
|
Set these before Python imports Qt or creates `QApplication`:
|
||
|
|
|
||
|
|
- `QT_QPA_PLATFORM=offscreen`
|
||
|
|
- `PYTEST_QT_API=pyqt6`
|
||
|
|
- isolated `XDG_CONFIG_HOME`
|
||
|
|
|
||
|
|
Do not set the platform only inside an individual test module. The current
|
||
|
|
assignment at `tests/test_gap_overlays.py:13` is too late for broad collection
|
||
|
|
and makes ordering significant.
|
||
|
|
|
||
|
|
Use the current Nix/uv PyQt6 environment. `flake.nix:47-89` supplies Qt6 and
|
||
|
|
plugin paths; uv should supply pytest-qt. Do not also add a separate Nix
|
||
|
|
pytest-qt package, and do not update the obsolete Python 3.11/PyQt5
|
||
|
|
`develop.nix` stack.
|
||
|
|
|
||
|
|
The following legacy modules remain outside the initial audit because they
|
||
|
|
still import PyQt5:
|
||
|
|
|
||
|
|
- `piker/ui/_signalling.py`
|
||
|
|
- `piker/ui/_orm.py`
|
||
|
|
- `piker/ui/quantdom/charts.py`
|
||
|
|
- `piker/ui/quantdom/_equity.py`
|
||
|
|
|
||
|
|
### Minimal project fixtures
|
||
|
|
|
||
|
|
Do not override pytest-qt's `qapp` fixture. Remove the module-local fixture at
|
||
|
|
`tests/test_gap_overlays.py:145-159` during migration.
|
||
|
|
|
||
|
|
Start with non-autouse project fixtures:
|
||
|
|
|
||
|
|
- `qapp_args`: stable application name only.
|
||
|
|
- `piker_qt`: set `setQuitOnLastWindowClosed(False)`, expose `qtbot`, process
|
||
|
|
only documented final events and verify no top-level widget leaks.
|
||
|
|
- `isolated_qsettings`: isolate `XDG_CONFIG_HOME`, clear test-owned settings
|
||
|
|
and restore process-global config references.
|
||
|
|
- `ohlcv_factory`: deterministic structured arrays with configurable indexes,
|
||
|
|
timestamps, gaps, duplicates, nulls and ordering defects.
|
||
|
|
- `pg_chart_factory`: typed harness around a real `pg.PlotWidget`, `PlotItem`,
|
||
|
|
`ViewBox`, scene and minimal Viz/SHM adapter.
|
||
|
|
- `display_state_factory`: paired realtime/history chart harnesses, including
|
||
|
|
duplicate-FQME cases.
|
||
|
|
|
||
|
|
Prefer small dataclasses or protocols over nested `SimpleNamespace` graphs.
|
||
|
|
Fixture finalizers must fail on leaked widgets, graphics items, event filters,
|
||
|
|
SHM objects or child actors instead of merely trying to clean them.
|
||
|
|
|
||
|
|
Do not hand a production `MainWindow` to `qtbot.addWidget()` until its close
|
||
|
|
path is test-safe. `MainWindow.closeEvent()` currently sends `SIGINT` to the
|
||
|
|
process (`piker/ui/_window.py:331-348`), so ordinary pytest-qt teardown can
|
||
|
|
interrupt pytest. Layer 4 must first separate widget closure from CLI process
|
||
|
|
signalling.
|
||
|
|
|
||
|
|
## Test architecture
|
||
|
|
|
||
|
|
### Layer 1: Qt and PyQtGraph primitives
|
||
|
|
|
||
|
|
No Trio, Tractor, feeds, SHM or service actors.
|
||
|
|
|
||
|
|
First audit targets:
|
||
|
|
|
||
|
|
1. `CompleterView` section insertion, selection and navigation
|
||
|
|
(`piker/ui/_search.py:82-525`).
|
||
|
|
2. `SearchWidget` focus, cached results and dispatch to a stub GodWidget
|
||
|
|
(`piker/ui/_search.py:556-825`).
|
||
|
|
3. `MultiStatus` plain/grouped status creation and cleanup
|
||
|
|
(`piker/ui/_window.py:143-253`).
|
||
|
|
4. `GapAnnotations` and `SelectRect` geometry, attachment, repositioning and
|
||
|
|
removal (`piker/ui/_annotate.py:307-785`,
|
||
|
|
`piker/ui/_editors.py:377-710`).
|
||
|
|
|
||
|
|
Acceptance gate:
|
||
|
|
|
||
|
|
- repeated same-process runs leave no top-level widgets, scene items,
|
||
|
|
settings or signals behind;
|
||
|
|
- Qt warnings and virtual-method exceptions fail the test;
|
||
|
|
- no arbitrary sleeps or screenshot comparisons.
|
||
|
|
|
||
|
|
### Layer 2: chart interaction and overlays
|
||
|
|
|
||
|
|
Real `ChartView`, PyQtGraph scenes and Qt input; synthetic display state and
|
||
|
|
no service actors.
|
||
|
|
|
||
|
|
First audit targets:
|
||
|
|
|
||
|
|
1. `ChartView.open_async_input_handler()` for Ctrl-G, Ctrl-R, Escape and
|
||
|
|
search-focus input (`piker/ui/_interaction.py:147-548`, `:690-720`).
|
||
|
|
2. Wheel and drag behavior, fake-feed pause/resume and graphics cache restore
|
||
|
|
(`piker/ui/_interaction.py:650-688`, `:731-1012`).
|
||
|
|
3. `PlotItemOverlay` y-axis independence, x-linking, focus ownership and
|
||
|
|
resize geometry (`piker/ui/_overlay.py:301-523`).
|
||
|
|
4. Cursor activation, labels and rounded data coordinates
|
||
|
|
(`piker/ui/_cursor.py:341-639`).
|
||
|
|
|
||
|
|
Use `qtbot.keyClick()`, mouse helpers, `waitExposed()` and `waitUntil()` for
|
||
|
|
user-visible behavior. `ChartView` is a PyQtGraph `ViewBox`, not a `QWidget`,
|
||
|
|
so deliver input to the owning `ChartPlotWidget` viewport, map scene/data
|
||
|
|
coordinates into viewport coordinates and let `GraphicsScene` synthesize the
|
||
|
|
PyQtGraph event. Keep low-level event dispatch for `EventRelay` conversion and
|
||
|
|
wheel behavior where QtBot has no suitable high-level helper.
|
||
|
|
|
||
|
|
The existing `trio.run()` Ctrl-G test can remain as a focused relay test, but
|
||
|
|
it must not stand in for production Trio guest-mode integration.
|
||
|
|
|
||
|
|
### Layer 3: composed charts with deterministic SHM
|
||
|
|
|
||
|
|
Real `LinkedSplits`, `ChartPlotWidget`, `ChartView`, Viz objects, cursor,
|
||
|
|
stickies and graphics update cycles. No datad, broker, EMS, search-provider or
|
||
|
|
FSP actor.
|
||
|
|
|
||
|
|
First audit targets:
|
||
|
|
|
||
|
|
1. Build a main OHLC chart from deterministic realtime/history arrays
|
||
|
|
(`piker/ui/_chart.py:133-520`, `:555-1025`).
|
||
|
|
2. Compose history and realtime splitters and verify x-linking/region movement
|
||
|
|
(`piker/ui/_display.py:1124-1288`, `:1409-1416`).
|
||
|
|
3. Mutate the final SHM row, call `graphics_update_cycle()` and assert the last
|
||
|
|
bar, sticky labels, L1 labels, x tread and y range
|
||
|
|
(`piker/ui/_display.py:594-1121`).
|
||
|
|
4. Exercise local order lines/arrows with fake trackers and client state
|
||
|
|
(`piker/ui/order_mode.py:107-659`, `piker/ui/_lines.py:549-827`).
|
||
|
|
|
||
|
|
Use a real disposable `ShmArray` because chart drawing asserts that interface
|
||
|
|
(`piker/ui/_chart.py:914-938`). A local Tractor root runtime is mandatory for
|
||
|
|
allocation (`piker/data/_sharedmem.py:146-159`); allocation without it raises
|
||
|
|
`NoRuntime` (`tests/test_shm_cleanup.py:18-46`). Reuse the exact SHM cleanup
|
||
|
|
discipline in `tests/conftest.py:97-239`. Nest chart and SHM lifetimes inside
|
||
|
|
the runtime, release Qt/Viz references first, then close SHM and the runtime.
|
||
|
|
Service actors are not required.
|
||
|
|
|
||
|
|
### Layer 4: full in-process UI with fake services
|
||
|
|
|
||
|
|
Real `MainWindow`, `GodWidget`, search, chart hierarchy, input handlers and
|
||
|
|
Trio guest run. Replace feed, FSP and EMS boundaries with deterministic async
|
||
|
|
harnesses.
|
||
|
|
|
||
|
|
First audit targets:
|
||
|
|
|
||
|
|
1. Initial symbol boot and history/realtime chart hierarchy
|
||
|
|
(`piker/ui/_widget.py:157-284`, `piker/ui/_display.py:1296-1711`).
|
||
|
|
2. Search-driven symbol switch and side-pane movement
|
||
|
|
(`piker/ui/_search.py:745-795`).
|
||
|
|
3. Cached A-to-B-to-A switch with widget reuse, feed resume and no duplicate
|
||
|
|
display task (`piker/ui/_widget.py:225-253`).
|
||
|
|
4. Overlapping uncached A-to-B startup: block A at a typed fake-feed barrier,
|
||
|
|
start B, release A and assert each display task retains its own linked
|
||
|
|
widgets, feed and cache identity. Exercise both completion and cancellation
|
||
|
|
ordering (`piker/ui/_widget.py:157-284`,
|
||
|
|
`piker/ui/_display.py:1296-1416`).
|
||
|
|
5. Real keyboard/mouse order staging plus fake open/fill/cancel EMS messages
|
||
|
|
(`piker/ui/_interaction.py:459-579`,
|
||
|
|
`piker/ui/order_mode.py:1058-1329`).
|
||
|
|
6. Graceful cancellation: filters removed, cached charts closed, fake
|
||
|
|
consumers exited and guest-run outcome observed.
|
||
|
|
|
||
|
|
Patch names imported into `_display`, not their original definitions:
|
||
|
|
|
||
|
|
- `open_feed` (`piker/ui/_display.py:44-50`, `:1384-1392`)
|
||
|
|
- `open_order_mode` (`piker/ui/_display.py:87-90`, `:1663-1673`)
|
||
|
|
- `start_fsp_displays` (`piker/ui/_display.py:72-76`, `:1522-1530`)
|
||
|
|
- `open_sample_stream` / `increment_history_view`
|
||
|
|
(`piker/ui/_display.py:214-320`)
|
||
|
|
|
||
|
|
`_async_main()` also starts provider search independently of `_display`
|
||
|
|
(`piker/ui/_app.py:46-74`, `:125-173`). Inject or patch
|
||
|
|
`_app.load_provider_search` and isolate symcache/config paths. Add guards which
|
||
|
|
fail if real `maybe_spawn_datad`, backend client creation, credentials or
|
||
|
|
network entrypoints are reached.
|
||
|
|
|
||
|
|
### Layer 5: offline actor-backed UI
|
||
|
|
|
||
|
|
Use real Tractor contexts only after widget and in-process composition are
|
||
|
|
stable. Keep all default cases credential-free and network-free.
|
||
|
|
|
||
|
|
Candidate coverage:
|
||
|
|
|
||
|
|
1. `pikerd` plus a deterministic fake datad/feed endpoint.
|
||
|
|
2. Remote chart-control endpoint discovery and chart-local rendering.
|
||
|
|
3. Actor cancellation while the window is open.
|
||
|
|
4. Child failure propagation into the guest-run outcome and pytest failure.
|
||
|
|
5. UI teardown proving no actor, registry, stream, SHM or Qt object survives.
|
||
|
|
|
||
|
|
Actor barriers must be typed messages or explicit events. Do not use logger
|
||
|
|
text, timing sleeps or implicit task scheduling as readiness evidence.
|
||
|
|
|
||
|
|
### Layer 6: opt-in system and visual qualification
|
||
|
|
|
||
|
|
Keep these outside the default deterministic gate:
|
||
|
|
|
||
|
|
- live broker/feed sessions;
|
||
|
|
- real credentials and account state;
|
||
|
|
- compositor-specific Wayland/X11 behavior;
|
||
|
|
- multi-monitor DPI and persistent window placement;
|
||
|
|
- long-run rendering performance and visual fidelity.
|
||
|
|
|
||
|
|
Use screenshots and logs as failure artifacts first. Introduce pixel or image
|
||
|
|
golden assertions only after fonts, DPI, styles and renderer versions are
|
||
|
|
controlled across supported environments.
|
||
|
|
|
||
|
|
## Production seams likely required
|
||
|
|
|
||
|
|
`run_qtractor()` currently creates global application/window state, starts
|
||
|
|
Trio guest mode, shows the window and calls blocking `app.exec_()`
|
||
|
|
(`piker/ui/_exec.py:86-211`). pytest-qt owns the Qt loop, so the full in-process
|
||
|
|
layer needs a nonblocking production seam rather than calling
|
||
|
|
`run_qtractor()` directly.
|
||
|
|
|
||
|
|
Prefer a minimal extraction that separates:
|
||
|
|
|
||
|
|
1. application policy and style setup;
|
||
|
|
2. `MainWindow` / main-widget construction;
|
||
|
|
3. Trio guest-run installation and observable completion;
|
||
|
|
4. blocking `app.exec_()` used only by the CLI path.
|
||
|
|
|
||
|
|
The test seam should return typed handles for the window, GodWidget, guest-run
|
||
|
|
outcome and cancellation scope. Do not build a second test-only event-loop
|
||
|
|
implementation.
|
||
|
|
|
||
|
|
Window closure is part of this extraction. Prefer a close-request signal or
|
||
|
|
injected shutdown callback owned by the guest-run harness. The CLI adapter may
|
||
|
|
translate that request into its existing SIGINT behavior; tests must cancel
|
||
|
|
and await the guest run, remove filters/connections and then close the window
|
||
|
|
without signalling the pytest process.
|
||
|
|
|
||
|
|
Other likely extractions should follow the gap-overlay precedent: pull one
|
||
|
|
deterministic transition out of a long-running loop only when a real test
|
||
|
|
needs it. Avoid broad UI rewrites before behavior is captured.
|
||
|
|
|
||
|
|
## Synchronization rules
|
||
|
|
|
||
|
|
1. Set environment and binding before importing Qt.
|
||
|
|
2. Let pytest-qt own the single `QApplication`.
|
||
|
|
3. Register every test-owned widget immediately.
|
||
|
|
4. Show/focus widgets before user-input assertions.
|
||
|
|
5. Wait on a signal or postcondition, never a guessed delay.
|
||
|
|
6. Use bounded timeouts and assert the final state after every wait.
|
||
|
|
7. Treat `processEvents()` as a documented low-level exception, not a general
|
||
|
|
synchronization primitive.
|
||
|
|
8. Use deterministic typed barriers for Trio/Tractor transitions.
|
||
|
|
9. Assert both visible behavior and ownership registries for PyQtGraph items.
|
||
|
|
10. Assert cleanup after cancellation and failure paths, not only happy paths.
|
||
|
|
|
||
|
|
For graphics geometry, preserve the dual test used by gap overlays:
|
||
|
|
|
||
|
|
- white-box arrays/bounds have moved;
|
||
|
|
- black-box `QGraphicsScene.items(point)` finds the new location and no longer
|
||
|
|
finds the old one.
|
||
|
|
|
||
|
|
## Global state and cleanup audit
|
||
|
|
|
||
|
|
Every fixture touching these globals must snapshot, restore and assert:
|
||
|
|
|
||
|
|
- `_window._qt_win`;
|
||
|
|
- `_remote_ctl._dss`, `_annots` and `_gapman`;
|
||
|
|
- `GodWidget` linked-chart caches, cursor and root nursery;
|
||
|
|
- Qt event filters and signal connections;
|
||
|
|
- `QApplication.topLevelWidgets()`;
|
||
|
|
- `QSettings`, config paths and module-global fonts;
|
||
|
|
- PyQtGraph `PlotItem.items`, `ViewBox.addedItems` and scene membership;
|
||
|
|
- SHM names/tokens and Tractor actor/registry identities.
|
||
|
|
|
||
|
|
Use unique actor names and registry addresses per test session. Never reap Qt
|
||
|
|
objects, actors or SHM by broad process/name patterns.
|
||
|
|
|
||
|
|
## Suggested test layout
|
||
|
|
|
||
|
|
Keep protocol-only tests separate from Qt tests:
|
||
|
|
|
||
|
|
- `tests/ui/conftest.py`: Qt/data/chart fixtures and leak assertions.
|
||
|
|
- `tests/ui/test_widgets.py`: search, completer, status and forms.
|
||
|
|
- `tests/ui/test_graphics_items.py`: annotations, selection and overlays.
|
||
|
|
- `tests/ui/test_chart_interaction.py`: keyboard, mouse, cursor and focus.
|
||
|
|
- `tests/ui/test_chart_composition.py`: linked charts, Viz and SHM updates.
|
||
|
|
- `tests/ui/test_godwidget.py`: symbol boot/switch/cache lifecycle.
|
||
|
|
- `tests/ui/test_order_mode.py`: fake EMS order UI transitions.
|
||
|
|
- `tests/ui/test_actor_runtime.py`: explicitly offline actor-backed UI.
|
||
|
|
|
||
|
|
Retain schema/msgspec tests in their current non-Qt modules so they remain
|
||
|
|
fast and diagnosable.
|
||
|
|
|
||
|
|
## CI progression
|
||
|
|
|
||
|
|
The tracked `.github/workflows/ci.yml` is stale: it uses Python 3.10,
|
||
|
|
`setup.py`, missing requirements files and an uncontrolled full-suite command.
|
||
|
|
Do not claim UI CI coverage until a targeted job provisions the current
|
||
|
|
Python 3.13 uv/Nix-compatible stack.
|
||
|
|
|
||
|
|
Initial job contract:
|
||
|
|
|
||
|
|
- Python 3.13 and frozen uv environment;
|
||
|
|
- `QT_QPA_PLATFORM=offscreen`;
|
||
|
|
- `PYTEST_QT_API=pyqt6`;
|
||
|
|
- isolated `XDG_CONFIG_HOME`;
|
||
|
|
- explicit `tests/ui/` plus migrated gap/DPI paths;
|
||
|
|
- screenshots, Qt logs and pytest output retained on failure.
|
||
|
|
|
||
|
|
Keep live feeds, account tests and compositor-specific tests out of this job.
|
||
|
|
|
||
|
|
## Incremental PR boundaries
|
||
|
|
|
||
|
|
1. Harness/migration PR: dependency/lock, pytest configuration, fixtures,
|
||
|
|
targeted CI command and migration of existing gap real-Qt cases onto
|
||
|
|
`qtbot` without changing their ownership/spatial-index assertions.
|
||
|
|
2. Primitive audit PR: search, completer, status and graphics-item behavior.
|
||
|
|
3. Interaction PR: input, cursor and overlay audit.
|
||
|
|
4. Composition PR: deterministic SHM, linked charts and graphics update cycle.
|
||
|
|
5. Full in-process PR: nonblocking `run_qtractor()` seam, safe window closure,
|
||
|
|
isolated provider search, overlapping startup, GodWidget switching and
|
||
|
|
fake EMS/feed lifecycle.
|
||
|
|
6. Actor-backed PR: offline service tree, failure propagation and teardown.
|
||
|
|
|
||
|
|
Each PR should keep tests with the production seam or defect they prove. Do
|
||
|
|
not batch unrelated UI corrections simply because one audit pass found them.
|
||
|
|
|
||
|
|
## Eventual repo-local skill
|
||
|
|
|
||
|
|
Distill a `piker-ui-e2e` skill only after the harness and first component
|
||
|
|
suite provide empirical commands and failure modes. The skill should encode:
|
||
|
|
|
||
|
|
1. environment and Qt-binding validation;
|
||
|
|
2. supported test tiers and escalation rules;
|
||
|
|
3. fixture ownership and global-state restoration;
|
||
|
|
4. signal/postcondition synchronization rules;
|
||
|
|
5. PyQtGraph attachment, detachment and scene-index assertions;
|
||
|
|
6. Trio guest-run and Tractor actor boundaries;
|
||
|
|
7. SHM/actor/widget leak checks;
|
||
|
|
8. headless CI commands and opt-in visual/live commands;
|
||
|
|
9. failure classification and artifact collection;
|
||
|
|
10. change-to-test mapping for UI modules.
|
||
|
|
|
||
|
|
Do not bake speculative APIs into the skill. First record the real harness,
|
||
|
|
then turn repeated practice into instructions.
|
||
|
|
|
||
|
|
## First-agent assignment
|
||
|
|
|
||
|
|
The next agent should implement only the harness/migration PR and stop after
|
||
|
|
that foundation is demonstrably stable:
|
||
|
|
|
||
|
|
1. open an isolated worktree at `335190a4`, stacking on the pushed
|
||
|
|
`chart_local_gapper` head; do not branch from `2aa66c2c`, where the gap
|
||
|
|
implementation and tests do not exist;
|
||
|
|
2. add pytest-qt and activate one pytest configuration source;
|
||
|
|
3. establish process-level offscreen/PyQt6/config isolation;
|
||
|
|
4. migrate the gap tests from the custom `qapp` to `qtbot`;
|
||
|
|
5. add typed chart/data fixtures and centralized PyQtGraph ownership helpers;
|
||
|
|
6. run repeated same-process tests and prove cleanup;
|
||
|
|
7. add the targeted CI command if the current workflow can provision it
|
||
|
|
without broad unrelated CI repair;
|
||
|
|
8. document exact commands and discovered failure modes for the future skill.
|
||
|
|
|
||
|
|
The first agent should not yet launch live feeds, rewrite `run_qtractor()`,
|
||
|
|
add new widget audits, add screenshot goldens or attempt the complete
|
||
|
|
GodWidget/actor stack.
|