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>
Dawn: the red "file exists" tint on the path label reads as an error
across the whole panel. The Run guard's popup already reports a clash,
so the label stays in the standard text color. The popup now also asks
the user to run the data collection again after the run number bump.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The tabs are handed parent=DataCollectionSettings but
QStackedWidget.addWidget() reparents them to the stack, so the guard's
parent() lookup never found file_path_panel and every Run (rotation,
screening, raster, X-ray centering, Simple) skipped the check. Lookup
now walks up the widget tree; regression test drives all five runs
through the real DataCollectionSettings.
The "taken" test is exact again: <run>_master.h5 plus the DAQ's
derived <run>_raster2d/_raster1d_master.h5. The directory test and the
_*_master.h5 glob are gone: neither is a file a run writes, and they
painted run numbers red that nothing would ever produce.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
update_filename() still skipped to the next free run number on every
edit and refresh, so the name was always free by the time Run was
clicked and the guard never fired. The name is now exactly what the
fields say (label turns red on a clash); on Run, an existing target
pops "File already exists", bumps the run number by one and does
nothing else.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Run guard called set_scan_kind(), which runs update_filename() and
skips to the next free run number before the existence check, so the
check never fired and the scan started under a new number without a
word. Now the check runs first, then a warning box names the existing
master file and the run number it moved to, and the run is blocked.
- Simple tab "Run rotation" had no guard at all; same guard added
- "Taken" also matches <run>_*_master.h5: X-ray Centering writes
<run>_raster2d_master.h5, which the exact name missed
- Camera context-menu "Evaluate grid" presses the panel button, so it
gets the guard and stays inert while the button is disabled
- Dead next_free_run_from() removed
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dewar "#" column sat 1px above the other rows at some zoom steps: the
frozen-column overlay sizes its own header from "#" alone while the main
header also sees the symbol columns, whose fallback-font glyphs have a
taller line box. The overlay header is now pinned to the main header
height on resize and (deferred, receiver-bound) on font/style change.
Zoom also left columns at their startup widths (headers truncated) and
clipped descenders in every button/combo/entry box: the 16px height pin
in both sheets was raw px while the body font grew to 18/21px. Columns
re-autosize on FontChange and the pin scales with the font ladder.
Camera "Currently mounted" HUD: black ink on a white halo in the light
themes, white on black in Sunset, instead of white-on-black everywhere.
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>
- Fast Shutter / State / p-group / Session are addPermanentWidget now:
QStatusBar itself pins them to the right corner, so wrapping of the
other readouts never moves them
- passive readouts keep wrapping in the FlowHost on narrow windows
- grow-only min-width ratchet on every readout: changing number widths
used to re-flow the row each DAQ tick and jitter every neighbour
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Qt's takeAt contract is nullptr-if-out-of-range; the newer PySide
stubs on CI declare a non-Optional return. Suppress rather than lie.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All readouts (sharpness ... Cryo | Fast Shutter | State | p-group |
Session) now live in one FlowLayout host: when the window gets
narrow the bar grows extra rows and every value stays readable,
where QStatusBar used to clip the permanent group and hide the rest.
FlowLayout is the canonical Qt example trimmed down (Qt ships no
built-in); the host pins its minimum height to the wrapped height
because QStatusBar ignores heightForWidth. The old left/right
addWidget vs addPermanentWidget split is gone with the wrap.
Co-Authored-By: Claude Fable 5 <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>
Root cause of the long-standing cross-test SystemError flake
("QPushButton returned NULL", CI failing test_title_label after an
unrelated file): TitleLabel schedules
QTimer.singleShot(0, self._apply_collapsed) at construction; when the
banner is deleted before the event loop spins (what every panel test
does), the pending timer fires into the dead C++ object and poisons
whatever test runs next. Deterministic repro added as a regression
test. The receiver-context overload singleShot(0, self, ...) auto-
cancels the callback when the widget dies. Same fix for the two
deferred main-window callbacks (dock split, floating-dock enlarge).
Note for the record: the LOCAL segfault storm this afternoon was the
dev machine's disk hitting 100% full (uv cache at 15G) — Bus errors
from mmap on a full disk, unrelated to any code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bec plugin editable installs upgrade PySide6 to 6.11.x past the
pyproject cap, which brings the teardown segfault back in exactly the
test-with-beamline-plugins jobs. Reinstall the capped version after
the plugin install.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI installs latest index deps (it does not read uv.lock) and resolved
PySide6 6.11.2, which segfaults the unit suite in teardown on about
half the runs - reproduced on PLAIN origin/main, so it is not this
branch's code. 6.9.0 (the uv.lock pin) is stable. Cap the dependency
so CI resolves a 6.9.x build; drop the cap once the teardown crash is
hunted down. uv.lock re-locked (it was stale at aaredaq 0.22.1).
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>
User feedback: idle bars stay short centered grips, but hovering or
grabbing a resize bar brings back the full-length 2px line + shadow
gradient so the active bar reads over its whole run. The now-unused
hint grip PNGs are removed.
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>
The sample list's Mount/Gridscan/Screening/Rotation count columns show
only a glyph in the header to save width; hovering now shows the full
name via ToolTipRole. Text headers stay tooltip-free.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shared "Rotation transmission" row sat in the top rows of BOTH the
Gridscan and Rotation tabs, where it read as one setting for everything
(and as nonsense in the Gridscan tab). Now each tab places its own row:
Gridscan keeps "Transmission" with the shared resolution/distance rows,
and the Rotation tab moves "Rotation transmission" below the Screening
block and its Run button, as the first row of the Rotation section -
next to the fields it actually applies to. Screening keeps its separate
"Screening transmission". No behavior change: same field, same
spreadsheet/user-value handling, only the label and the placement.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A "Currently mounted: <sample_name>" HUD line in the top-left corner of
the sample camera, driven by the DAQ status (clears when nothing is
mounted). Painted like the other camera HUDs and sized through the text
zoom, which QSS cannot reach here.
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>