[ux] Fix Aetherial Audio frameless edge resizing#4266
Conversation
There was a problem hiding this comment.
Clean, well-scoped refactor — this swaps 78 lines of widget-local frameless-resize handling for the shared FramelessResizer, exactly matching the pattern already used by ConnectionPanel, FloatingContainerWindow, PanFloatingWindow, and 6 other call sites. The root-cause writeup is correct: child-heavy content covered the window margins, so edge presses went to child widgets and never reached startSystemResize(); installing at the QWindow boundary fixes exactly that. Nice cleanup.
I verified the mechanical concerns and they're all fine:
- No dangling references to the removed members (
edgesAt,updateResizeCursor,kResizeMargin,setMouseTracking) anywhere in the PR. - Dropping
#include <QWindow>is safe — grep confirms no remainingQWindow/windowHandle/startSystemResizeusage in the file after the deletion. - Frameless gating is preserved: the helper checks
Qt::FramelessWindowHintand hands resize back to the OS when native decorations are on, so the native-decorated path is unchanged. - Runtime frameless toggle (
MainWindow::setFramelessMode→m_aetherialStrip->setFramelessMode(on)) is safe: the filter lives on the persistentQWidgetWindow(only the platform handle is recreated bysetWindowFlags), same as every other adopter.
No blocking issues, no polish items. LGTM — thanks for routing this through the standard helper rather than carrying yet another copy of the resize logic.
🤖 aethersdr-agent · cost: $5.2783 · model: claude-opus-4-8
NF0T
left a comment
There was a problem hiding this comment.
Right architectural call — migrating off the widget-local resize handling onto the shared FramelessResizer (already adopted at 9 other sites: ConnectionPanel, FloatingContainerWindow, PanFloatingWindow, PersistentDialog, MainWindow itself, and others) is exactly the fix this bug class needs, and the root-cause diagnosis (child-heavy content eating edge presses before they reach the top-level widget) is correct. CI is green and the commit is signed.
Two concrete regressions before this can merge, both verified against the actual geometry and event flow:
1. Dragging the title bar near its top edge now resizes instead of moves. m_titleBar is 18px tall, full-width, at y=0. FramelessResizer::install(this) uses the default 6px margin, so the top third of the title bar is now inside the resize zone. FramelessResizer's filter sits on the QWindow and sees mouse events before Qt's widget dispatch does, so it now consumes the press and calls startSystemResize() there before the title bar's own move-drag handling (FramelessMoveHelper::start, via the class's existing eventFilter() for m_titleBar) ever gets a look. Pre-PR this same band correctly started a move, since the old widget-level mousePressEvent lost to the title bar (a child widget) under normal Qt dispatch. This is always-reachable, not an edge case — any grab in the title bar's top third now misfires as a resize.
2. The maximize/fullscreen guard didn't survive the migration. The old edgesAt() explicitly returned no edges when isMaximized() || isFullScreen(). The shared FramelessResizer::edgesAt() has no such check — it's gated only on Qt::FramelessWindowHint, which stays set regardless of window state. Since Aetherial Audio supports maximize (title-bar button + double-click), a maximized strip can now attempt startSystemResize() from a screen-edge hover. Since this gap lives in FramelessResizer.cpp itself rather than this PR's own code, the cleaner fix is probably adding the check there — it'd close the same latent gap for every current and future adopter, not just this one.
Investigated and ruled out: whether toggling the global "Frameless Window" setting after the strip has already been shown once could orphan FramelessResizer's QWindow-migrated filter and silently kill resize for the rest of the session. It can't — MainWindow itself is a FramelessResizer adopter and runs this exact runtime-toggle-while-visible pattern on itself, with a comment (MainWindow.cpp:992) already reasoning through it ("Stays installed across frameless toggles... our filter no-ops"). Nine adopters exercise this safely; not a risk here.
Non-blocking, separate follow-up: ClientEqEditor/ClientCompEditor/ClientGateEditor/ClientTubeEditor/ClientPuduEditor share the same frameless/child-heavy shape but have no resize handling at all yet (not even the old buggy version) — worth the same one-line FramelessResizer::install(this) in a follow-up PR, not something to hold this one for.
Happy to re-review quickly once the title-bar band is resolved and the maximize guard lands (here or in FramelessResizer directly) — this is close, and the reuse win is worth landing.
…melessResizer (aethersdr#4266). Principle VIII. Fixes @NF0T's two regressions from migrating Aetherial Audio onto the shared FramelessResizer: - Maximize/fullscreen guard: the old AetherialAudioStrip::edgesAt() returned no edges when isMaximized()/isFullScreen(); FramelessResizer::edgesAt() had no such check, so a maximized/fullscreen strip could startSystemResize() from a screen-edge hover. Centralize the guard in FramelessResizer — closes the same latent gap for all nine adopters, not just this one. - Title-bar grab misfiring as a resize: AetherialAudioStrip's title bar is an 18px edge-to-edge strip at y=0, so the default 6px top resize margin overlapped its top third; the QWindow-level filter consumed the press before the title bar's own move-drag ran. Add an optional topMoveReserve to FramelessResizer: the top strip is reserved for the window's move handling (edgesAt returns none there) while resize still works everywhere below it. Aetherial Audio installs with a reserve of its title-bar height, preserving the edge-to-edge look (unlike ConnectionPanel, which insets its identical 18px title bar to clear the edges). Other adopters default to 0 — no behavior change. Full app builds clean; default install() args keep every existing adopter call-site unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ten9876
left a comment
There was a problem hiding this comment.
Approving — @NF0T's two regressions are addressed (d7a50f3c)
Both fixes landed in FramelessResizer so every adopter benefits, per @NF0T's steer:
- Maximize/fullscreen guard — centralized into
FramelessResizer::edgesAt()(isMaximized() || isFullScreen() → {}). Closes the gap the deletedAetherialAudioStrip::edgesAt()used to cover, for all 9 adopters. - Title-bar grab misfiring as resize — added an optional
topMoveReservetoFramelessResizer: the top move-handle strip returns no edges, so the press falls through to the title bar'sFramelessMoveHelpermove, while resize still works everywhere below it. Aetherial Audio installs withtopMoveReserve = kTitleBarHeight (18)— preserving the intended edge-to-edge look (rather than ConnectionPanel's inset-the-title-bar approach for its identical 18px bar). Other adopters default to0: no behavior change, and the defaultinstall()args keep every existing call-site unchanged.
Full app builds clean. @NF0T — this is the "here or in FramelessResizer directly" path you preferred for the maximize guard; when you get a moment, a re-review to clear your changes-requested would unblock it. (Your non-blocking Client*Editor follow-up is noted for a separate PR.) Thanks for the precise geometry/event-flow diagnosis.
## Summary - replace Aetherial Audio's widget-local frameless resize handler with the shared native-window `FramelessResizer` - let edge and corner presses reach `QWindow::startSystemResize()` even when child panels cover the window margins - retain the existing 960/1620 opening-height policy, minimum size, geometry persistence, and native-decorated behavior ## Root cause `AetherialAudioStrip` listened for edge presses on the top-level `QWidget`. Its child-heavy content covers the window margins, so those mouse events were dispatched to child widgets instead and the strip never reached `startSystemResize()`. The shared resizer installs its filter on the native `QWindow`, before widget-tree dispatch, which is the project-standard solution for this exact event-routing problem. ## Verification - `cmake --build build -j8` - `ctest --test-dir build -R '^(help_dialog_test|pan_layout_dialog_size_test|flex_control_dialog_size_test)$' --output-on-failure` (2/2 registered matches passed) - `python3 tools/check_engine_boundary.py --strict` (0 blocking findings) - `git diff --check` - automation bridge reproduced the reporter layout at 1140x960, then resized the fixed strip to 1140x1700 and reported zero visible scrollbars ## Proof Before — reporter layout at 1140x960 with the vertical scrollbar:  After — full content at 1140x1700 with no visible scrollbar:  ## Automation follow-up The bridge's current `drag` verb always starts at a widget center. A targeted `dragAt` or `window edgeDrag` verb with an explicit start offset would let agents prove native frameless edge gestures directly and safely. Fixes aethersdr#3876 💻 Generated with Codex with architecture by @jensenpat 👨🏼💻 Generated with OpenAI Codex (GPT-5.6 Sol) and tested by @jensenpat --------- Co-authored-by: Jeremy [KK7GWY] <kk7gwy@aethersdr.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
FramelessResizerQWindow::startSystemResize()even when child panels cover the window marginsRoot cause
AetherialAudioStriplistened for edge presses on the top-levelQWidget. Its child-heavy content covers the window margins, so those mouse events were dispatched to child widgets instead and the strip never reachedstartSystemResize(). The shared resizer installs its filter on the nativeQWindow, before widget-tree dispatch, which is the project-standard solution for this exact event-routing problem.Verification
cmake --build build -j8ctest --test-dir build -R '^(help_dialog_test|pan_layout_dialog_size_test|flex_control_dialog_size_test)$' --output-on-failure(2/2 registered matches passed)python3 tools/check_engine_boundary.py --strict(0 blocking findings)git diff --checkProof
Before — reporter layout at 1140x960 with the vertical scrollbar:
After — full content at 1140x1700 with no visible scrollbar:
Automation follow-up
The bridge's current
dragverb always starts at a widget center. A targeteddragAtorwindow edgeDragverb with an explicit start offset would let agents prove native frameless edge gestures directly and safely.Fixes #3876
💻 Generated with Codex with architecture by @jensenpat
👨🏼💻 Generated with OpenAI Codex (GPT-5.6 Sol) and tested by @jensenpat