FAILURE MAP
← Case archive

FA-8251 / Direct manipulation / Open access

Pointer-capture ownership: Lost capture leaves the UI in dragging state · case 01

Lost capture leaves the UI in dragging state.

Verified by executionVariant 1 · 10 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The lost capture operation uses `if event == 'lost': return (owner, position)` where the contract requires `if event == 'lost': return (None, position)`.

VERIFIED REPAIR

Implement the lost capture operation as `if event == 'lost': return (None, position)`.

Unsuccessful approach: Clearing coordinates moves the control unexpectedly.

Case contract

First pointer captures ownership; only its movement and release affect a drag; cancellation and capture loss clear ownership while preserving position.

Why this case matters

A deterministic model of pointer-capture ownership; this isolates one interface invariant without requiring a browser.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(owner, position, event, pointer, delta):
    if event == 'down': return (pointer if owner is None else owner, position)
    if event == 'move' and pointer == owner: return (owner, position + delta)
    if event == 'up' and pointer == owner: return (None, position)
    if event == 'cancel' and pointer == owner: return (None, position)
    if event == 'lost': return (owner, position)
    return (owner, position)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
    check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
initial down[1, 4][1, 4]Passed
second down[1, 4][1, 4]Passed
other move[1, 4][1, 4]Passed
owner move[1, 7][1, 7]Passed
other up[1, 4][1, 4]Passed
owner up[None, 4][None, 4]Passed
cancel[None, 4][None, 4]Passed
lost[1, 4][None, 4]Failed
parameterized pointer ownership[1, 2][1, 2]Passed
repeat lost[1, 4][None, 4]Failed

SHA-256 / d86cc78693deb46c38d934c2ae2040d18e90f15b0fe635e825ec59e6ab681051

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(owner, position, event, pointer, delta):
    if event == 'down': return (pointer if owner is None else owner, position)
    if event == 'move' and pointer == owner: return (owner, position + delta)
    if event == 'up' and pointer == owner: return (None, position)
    if event == 'cancel' and pointer == owner: return (None, position)
    if event == 'lost': return (None, 0)
    return (owner, position)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
    check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
initial down[1, 4][1, 4]Passed
second down[1, 4][1, 4]Passed
other move[1, 4][1, 4]Passed
owner move[1, 7][1, 7]Passed
other up[1, 4][1, 4]Passed
owner up[None, 4][None, 4]Passed
cancel[None, 4][None, 4]Passed
lost[None, 0][None, 4]Failed
parameterized pointer ownership[1, 2][1, 2]Passed
repeat lost[None, 0][None, 4]Failed

SHA-256 / 9ac324072335c2fd31f4b1d0299b01273b6eb4c8e1138ef0435d7361c8445ee6

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(owner, position, event, pointer, delta):
    if event == 'down': return (pointer if owner is None else owner, position)
    if event == 'move' and pointer == owner: return (owner, position + delta)
    if event == 'up' and pointer == owner: return (None, position)
    if event == 'cancel' and pointer == owner: return (None, position)
    if event == 'lost': return (None, position)
    return (owner, position)
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('initial down', solve(None, 4, 'down', 1, 0), (1, 4))
check('second down', solve(1, 4, 'down', 2, 0), (1, 4))
check('other move', solve(1, 4, 'move', 2, 3), (1, 4))
check('owner move', solve(1, 4, 'move', 1, 3), (1, 7))
check('other up', solve(1, 4, 'up', 2, 0), (1, 4))
check('owner up', solve(1, 4, 'up', 1, 0), (None, 4))
check('cancel', solve(1, 4, 'cancel', 1, 0), (None, 4))
check('lost', solve(1, 4, 'lost', 2, 0), (None, 4))
check('parameterized pointer ownership', solve(N, N, 'move', N, N), (N,2*N))
for repetition in range(N):
    check('repeat lost', solve(1, 4, 'lost', 2, 0), (None, 4))
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
initial down[1, 4][1, 4]Passed
second down[1, 4][1, 4]Passed
other move[1, 4][1, 4]Passed
owner move[1, 7][1, 7]Passed
other up[1, 4][1, 4]Passed
owner up[None, 4][None, 4]Passed
cancel[None, 4][None, 4]Passed
lost[None, 4][None, 4]Passed
parameterized pointer ownership[1, 2][1, 2]Passed
repeat lost[None, 4][None, 4]Passed

SHA-256 / b57d2a364c43b5fad81f31ca1ee3ae6da82ece5397810eca5948879bc52cd522

Verification & scope

This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:38:19.688494+00:00.

Case digest / 6592c18256e59521af072d15c4149cabbce6c8a5a5d659330b9a0d5a09029b81