CI (3.12, PR 232) failed test_font_zoom_keeps_frozen_column_aligned_and_reautosizes
with a pytest-qt CALL ERROR: PySide 6.9 handed both app-level filters a
QWidgetItem instead of a QEvent during layout teardown, and event.type()
raised. The wheel guard now keys on isinstance(event, QWheelEvent) and
returns False itself instead of super().eventFilter() (which type-checks
its arguments and would raise the same way); the cursor filter ignores
anything that is not a QEvent. Regression test for each.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mount next popped the mounted head before posting the exchange, so a
failed unmount dropped a sample that was still on the gonio. Now the head
stays queued and is removed only when /status shows a different (or no)
sample mounted; a failed exchange leaves the queue intact for a retry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
User decision: the drag-resize bars beside the Sample camera view are
not wanted - the side panels don't need resizing. Back to the plain
HBox with zoom-scaled fixed column widths; the View-menu collapse
toggles and the camera minimum-width clamp stay.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's basedpyright (latest from the index, newer than the local venv)
types win.daq as the real DAQWorker, so assert_not_called on the
patched-in MagicMock flagged the two new gate-test lines.
Note: the pytest job segfaults are NOT from this branch — plain
origin/main crashes 2/4 runs under PySide6 6.11.2 (what CI installs;
uv.lock pins 6.9.0 which is stable). Needs a separate dependency fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Why the width shrink only worked after a Playlist Mode round-trip:
Qt auto-pins a top-level window's minimum size to its layout minimum,
and returning from Playlist Mode hid the Information dock (and every
side dock), which happened to drop that layout minimum. The main view
now gets an explicit small minimum (320x240) which disables the
auto-pin outright, so the window shrinks directly and the dock
contents clip or scroll instead.
The Playlist Mode (Ctrl+6) and Automation View (Ctrl+5) prototypes
are deleted per user decision: panels, pages, the content stack (the
standard page is the only page left), menu actions, camera fan-out to
their view copies, portrait alert interceptors (now plain runtime
notification routers), and their styles (portraitRoot, compact*
cards, COMPACT_* and FONT_HERO knobs).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- center splitter: left controls | camera tabs | beamline column;
drag resizes the camera width, View menu check actions collapse
the fixed-width side panels (no on-screen buttons)
- font zoom restricted to a 100/125/150 percent ladder; the
fixed-width side boxes scale with it so zoomed text does not clip
- camera tab widget min width clamped to 1/3 of its natural minimum
- resize bars: short centered grip PNGs shared by QMainWindow
separators and QSplitter handles, single style source in styles.py
- window min width 1244 -> ~376: automation row and sample-list
chip row stop propagating their natural minimums (Ignored-width
hosts), portrait page fixed width removed (portrait mode pins the
window width itself)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Ctrl+plus / Ctrl+minus / Ctrl+0 (and View menu entries) scale the QSS
FONT_* ladder and the application default font together; the scale is
clamped to 0.8-1.6 and persisted in QSettings appearance/font_scale.
- Resize gutters keep a 5px hover/drag region but paint only a 2px line
with a 1px shadow (hard-stop gradients per orientation).
- WCAG contrast fixes: separators were 1.0-1.2:1 against every theme
background, now >=3.25:1 (dark theme gold @70%); scrollbar handle
1.17:1 -> 3.38:1; unselected tab text on the sky gradient 2.07:1 ->
4.71:1 via TAB_IDLE_TEXT.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's diff-coverage gate sat at 29% because the ZMQ fixes live in code no
test reached: the subscriber's socket setup, _emit_image/notify_frame_
displayed, _recv_latest and the run() frame path, plus the main window's
frame slot. The subscriber now runs against a real inproc socket (the
drain and the in-flight cap are the fix, so a mock would prove nothing);
run() is driven with a stubbed _recv_latest that hands over one JPEG and
then stops. The busy overlay gets a check that cooling is the only blue
busy state.
The subscriber fixture takes qapp so no QImage is built before the
QApplication exists.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The fix removed Moving from the motion-watch trigger but the unit test and code comment still described the old behavior, so pre-push failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Beam location, Beamstop alignment and Flux measurement grey out for
non-staff in the state strip and the status-bar transitions menu. All
"not admin" denials now show as an auto-dismissing QToolTip on a
Catppuccin red wash instead of a click-to-close QMessageBox.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Robot motion switches to the combined view; alignment beginning
switches straight back while busy is still set, and staying busy in
alignment never re-triggers. Piggybacks on the init test's window -
extra MainWindow constructions feed the PySide teardown flake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The locked-banner and Beamline-tab-bar filters were installed mid
__init__, but MainWindow.eventFilter reads sample_lists_tabs, which is
created later - every event delivered in between raised AttributeError
inside the filter ('Error calling Python override of eventFilter()'
spam) and broke widget teardown, cascading errors across CI tests and
wedging the pxiii_bec job. Filters now install late, next to
installEventFilter(self), and the filter guards its attribute reads so
construction/teardown-time events can never raise.
Also fold the staff-gate assertions into test_main_window_init: every
extra MainWindow construction raises the odds of the pre-existing
PySide SystemError flake, so don't build a window just for them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diff coverage vs main now 100 percent from the gui suite alone:
non-staff locked banners and both gated-tab popups, remote-close
bookkeeping in update_daq_status, smargon staged moves, camera More
link and Alt-wheel axis fallback, number box relimits, energy row
emit, and the gui.main() banner/graceful-exit path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diff coverage vs main was 77%, under the 80% CI gate; the new camera
help/error/badge code and the theme+layout main_window paths were the
uncovered bulk. Locally: 82%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>