Ψ-W1-T4 remediation: gate silent arrange drops, pin surface totality, hedge unverified SDK claims, close comment/exception-safety minors
This commit is contained in:
@@ -159,7 +159,10 @@ void applyDropCue(DropCue cue) {
|
||||
case DropCue::Instrument: idc = IDC_HAND; break;
|
||||
case DropCue::ArrangeInsert: idc = IDC_IBEAM; break; // an insertion point on a timeline
|
||||
case DropCue::Refuse: idc = IDC_NO; break;
|
||||
case DropCue::OsOwned: return; // the OS drag loop draws its own copy cursor
|
||||
case DropCue::OsOwned: return; // reached once per move resolving to OsHandoff,
|
||||
// right before handOffToOs is attempted; the OS
|
||||
// drag loop (once it actually starts) draws its
|
||||
// own copy cursor, so leave the cursor alone here
|
||||
case DropCue::Internal: return; // applyDragCursor owns the in-client cue
|
||||
case DropCue::None: return;
|
||||
}
|
||||
@@ -167,8 +170,8 @@ void applyDropCue(DropCue cue) {
|
||||
}
|
||||
|
||||
// One independent evaluation of the drag's target: the resolved class plus the live facts its
|
||||
// outcome needs. Nothing is remembered between calls — that is the whole point (a transition
|
||||
// reverses, and an unresolvable evaluation cannot poison a later one).
|
||||
// outcome needs. Nothing is remembered between calls (core/ui/CLAUDE.md: "the drag-out law is
|
||||
// per-move and stateless").
|
||||
struct LiveDrop {
|
||||
DropClass cls = DropClass::None;
|
||||
MediaTrack* track = nullptr;
|
||||
@@ -185,10 +188,12 @@ LiveDrop resolveLiveDrop(int x, int y) {
|
||||
ctx.singlePayload = g_panel.dragSampleIds.size() == 1;
|
||||
|
||||
LiveDrop out;
|
||||
// The SDK hit-test is evaluated ONLY outside the client rect, so the common internal-drag
|
||||
// path costs nothing. Unlike before, it runs for multi payloads too — still per-mouse-move
|
||||
// cold, and it is what gives a multi drag a defined outcome on every surface.
|
||||
if (!(x >= cr.left && x < cr.right && y >= cr.top && y < cr.bottom)) {
|
||||
// The SDK hit-test is evaluated ONLY outside the client rect (needsSurfaceProbe — exported
|
||||
// by the pure law so this gate can't drift from decideDropClass's own inside-client check),
|
||||
// so the common internal-drag path costs nothing. Unlike before, it runs for multi payloads
|
||||
// too — still per-mouse-move cold, and it is what gives a multi drag a defined outcome on
|
||||
// every surface.
|
||||
if (needsSurfaceProbe(x, y, client)) {
|
||||
POINT sp{x, y};
|
||||
ClientToScreen(g_panel.hwnd, &sp);
|
||||
const DropProbe probe = probeDropTarget(sp.x, sp.y);
|
||||
@@ -198,6 +203,15 @@ LiveDrop resolveLiveDrop(int x, int y) {
|
||||
out.screenX = sp.x;
|
||||
}
|
||||
out.cls = decideDropClass(x, y, client, ctx);
|
||||
|
||||
// ArrangeInsert has no defined outcome once every armed sample is stale/missing — the same
|
||||
// gate handOffToOs applies via decideOsHandoff before an OS hand-off, so acceptance
|
||||
// criterion 7 (no silent no-op release) holds on this cell too, at both a cueing move and
|
||||
// the release itself (both call this function). Cheap: a few fs::exists checks, only
|
||||
// reached outside the client — already a cold path.
|
||||
if (out.cls == DropClass::ArrangeInsert && resolveDragPathsForOs().empty()) {
|
||||
out.cls = DropClass::Refuse;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -342,9 +356,8 @@ void onMouseMove(int x, int y) {
|
||||
}
|
||||
}
|
||||
if (g_panel.dragging) {
|
||||
// The class is re-resolved from scratch on every move — no first-move lock, nothing
|
||||
// latched — so a transition in either direction always reverses, and drag speed cannot
|
||||
// change where the gesture ends up.
|
||||
// Re-resolved from scratch every move — core/ui/CLAUDE.md, "the drag-out law is
|
||||
// per-move and stateless."
|
||||
const LiveDrop live = resolveLiveDrop(x, y);
|
||||
|
||||
if (live.cls != DropClass::Internal) {
|
||||
@@ -353,11 +366,11 @@ void onMouseMove(int x, int y) {
|
||||
// a drop that is not going there, then show whatever cue this class carries.
|
||||
g_panel.dropKind = DropKind::None;
|
||||
g_panel.dropBankId.clear();
|
||||
applyDropCue(cueForDropClass(live.cls)); // OsHandoff -> OsOwned, a documented no-op cue
|
||||
if (live.cls == DropClass::OsHandoff) {
|
||||
handOffToOs();
|
||||
return;
|
||||
}
|
||||
applyDropCue(cueForDropClass(live.cls));
|
||||
invalidatePanel();
|
||||
return;
|
||||
}
|
||||
@@ -446,15 +459,16 @@ void resetDragState() {
|
||||
g_panel.dragPrimaryId.clear();
|
||||
}
|
||||
|
||||
// Commits (or abandons) a drag on button-up, over the class resolved AT THE RELEASE POINT.
|
||||
// The switch is deliberately exhaustive with no default: every DropClass either performs its
|
||||
// outcome or is an explicit, already-cued refusal, so a new class cannot be added without
|
||||
// answering "what does releasing here do?".
|
||||
// Commits (or abandons) a drag on button-up, over the class resolved AT THE RELEASE POINT. Every
|
||||
// DropClass either performs its outcome or is an explicit, already-cued refusal (core/ui/
|
||||
// CLAUDE.md: "no DropClass means nothing happens"). The switch below has no default so each case
|
||||
// is spelled out by hand — but this build sets no warning flags (root CLAUDE.md), so a missing
|
||||
// case is NOT a compile error here; exhaustiveness is a review discipline, not a compiler
|
||||
// guarantee.
|
||||
void onLBtnUp(int x, int y) {
|
||||
if (g_panel.dragging) {
|
||||
// Resolve at the release point, not from anything the moves remembered: WM_MOUSEMOVE is
|
||||
// coalesced, so the last processed move can sit well away from where the button actually
|
||||
// came up, and the release point is the user's stated target.
|
||||
// Resolved at the release point, not from what the (coalesced) moves last recorded —
|
||||
// core/ui/CLAUDE.md, "the drag-out law is per-move and stateless."
|
||||
const LiveDrop live = resolveLiveDrop(x, y);
|
||||
|
||||
switch (live.cls) {
|
||||
@@ -482,9 +496,12 @@ void onLBtnUp(int x, int y) {
|
||||
case DropClass::OsHandoff:
|
||||
case DropClass::None:
|
||||
// Nothing to perform, and nothing silent about it: the refuse cursor has been
|
||||
// showing since the move that resolved this class. An OsHandoff reaching release
|
||||
// means the payload never resolved — a live hand-off consumes the drag inside
|
||||
// DoDragDrop's modal loop and never returns here.
|
||||
// showing since the move that resolved this class. OsHandoff reaching release
|
||||
// usually means a live hand-off consumed the drag inside DoDragDrop's modal loop
|
||||
// and this call never ran — but WM_MOUSEMOVE coalescing can still deliver a
|
||||
// WM_LBUTTONUP with no intervening processed move (or right after a
|
||||
// canInitiateDragOut refusal), so this case CAN be reached with a stale
|
||||
// OsHandoff/Refuse class; the no-op here is correct either way.
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user