FAILURE MAP
← Case archive

FA-7901 / Keyboard accessibility / Open access

Roving tabindex navigation: Arrow navigation stops at an endpoint instead of wrapping · case 01

Arrow navigation stops at an endpoint instead of wrapping.

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

ROOT CAUSE

The wrap operation uses `min(len(enabled)-1, max(0, enabled.index(current) + step))` where the contract requires `(enabled.index(current) + step) % len(enabled)`.

VERIFIED REPAIR

Implement the wrap operation as `(enabled.index(current) + step) % len(enabled)`.

Unsuccessful approach: Reversing arrows changes navigation throughout the collection.

Case contract

Home and End select enabled endpoints; horizontal arrows honor directionality and wrap; a removed current item resets to the first enabled item.

Why this case matters

A deterministic model of roving tabindex navigation; 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(enabled, current, key, rtl):
    if not enabled: return None
    if key == 'Home': return enabled[0]
    if key == 'End': return enabled[-1]
    step = (-1 if key == 'Left' else 1) * (-1 if rtl else 1)
    if current not in enabled: return enabled[0]
    return enabled[min(len(enabled)-1, max(0, enabled.index(current) + step))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('home', solve([1,3,7], 3, 'Home', False), 1)
check('end', solve([1,3,7], 3, 'End', False), 7)
check('rtl right', solve([1,3,7], 3, 'Right', True), 1)
check('ltr right', solve([1,3,7], 3, 'Right', False), 7)
check('removed', solve([1,3,7], 8, 'Right', False), 1)
check('wrap', solve([1,3,7], 7, 'Right', False), 1)
check('empty', solve([], 0, 'Home', False), None)
check('parameterized arrow', solve(list(range(N+3)), N, 'Right', False), N+1)
for repetition in range(N):
    check('repeat empty', solve([], 0, 'Home', False), None)
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
home11Passed
end77Passed
rtl right11Passed
ltr right77Passed
removed11Passed
wrap71Failed
emptyNoneNonePassed
parameterized arrow22Passed
repeat emptyNoneNonePassed

SHA-256 / c92ba67cc9fec092d2823da64e151d6d7b02a7642dc888b3e2f32d22d4498a22

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(enabled, current, key, rtl):
    if not enabled: return None
    if key == 'Home': return enabled[0]
    if key == 'End': return enabled[-1]
    step = (-1 if key == 'Left' else 1) * (-1 if rtl else 1)
    if current not in enabled: return enabled[0]
    return enabled[(enabled.index(current) - step) % len(enabled)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('home', solve([1,3,7], 3, 'Home', False), 1)
check('end', solve([1,3,7], 3, 'End', False), 7)
check('rtl right', solve([1,3,7], 3, 'Right', True), 1)
check('ltr right', solve([1,3,7], 3, 'Right', False), 7)
check('removed', solve([1,3,7], 8, 'Right', False), 1)
check('wrap', solve([1,3,7], 7, 'Right', False), 1)
check('empty', solve([], 0, 'Home', False), None)
check('parameterized arrow', solve(list(range(N+3)), N, 'Right', False), N+1)
for repetition in range(N):
    check('repeat empty', solve([], 0, 'Home', False), None)
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
home11Passed
end77Passed
rtl right71Failed
ltr right17Failed
removed11Passed
wrap31Failed
emptyNoneNonePassed
parameterized arrow02Failed
repeat emptyNoneNonePassed

SHA-256 / 2f988ef55b5ffa631eef275e3f79d39b2009404982fbd83bb59a6e972309b885

3 / The verified repair

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

N = 1
observations = []
def solve(enabled, current, key, rtl):
    if not enabled: return None
    if key == 'Home': return enabled[0]
    if key == 'End': return enabled[-1]
    step = (-1 if key == 'Left' else 1) * (-1 if rtl else 1)
    if current not in enabled: return enabled[0]
    return enabled[(enabled.index(current) + step) % len(enabled)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('home', solve([1,3,7], 3, 'Home', False), 1)
check('end', solve([1,3,7], 3, 'End', False), 7)
check('rtl right', solve([1,3,7], 3, 'Right', True), 1)
check('ltr right', solve([1,3,7], 3, 'Right', False), 7)
check('removed', solve([1,3,7], 8, 'Right', False), 1)
check('wrap', solve([1,3,7], 7, 'Right', False), 1)
check('empty', solve([], 0, 'Home', False), None)
check('parameterized arrow', solve(list(range(N+3)), N, 'Right', False), N+1)
for repetition in range(N):
    check('repeat empty', solve([], 0, 'Home', False), None)
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
home11Passed
end77Passed
rtl right11Passed
ltr right77Passed
removed11Passed
wrap11Passed
emptyNoneNonePassed
parameterized arrow22Passed
repeat emptyNoneNonePassed

SHA-256 / e682b0bd63403014ba0e32c92b4cff3ac8d294f86c791f9d2b91ae33a2741024

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:16.268155+00:00.

Case digest / 8054a83dd568f4f2c92ce25419d1b22ee6896e4d402876bfa923105b59136952