FA-7851 / Keyboard accessibility / Open access
Sequential keyboard focus: Shift-tab advances in the wrong direction · case 01
Shift-tab advances in the wrong direction.
ROOT CAUSE
The backward operation uses ` + 1)` where the contract requires ` + direction)`.
VERIFIED REPAIR
Implement the backward operation as ` + direction)`.
Unsuccessful approach: Always reversing direction breaks forward navigation.
Case contract
Visit visible enabled nonnegative tabindex controls; positive tabindex precedes zero; ties retain document order and navigation wraps.
Why this case matters
A deterministic model of sequential keyboard focus; 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(items, current, direction):
eligible = [i for i, item in enumerate(items) if item['visible'] and not item['disabled'] and item['tab'] >= 0]
eligible.sort(key=lambda i: (items[i]['tab'] == 0, items[i]['tab'] if items[i]['tab'] > 0 else 0, i))
if not eligible: return None
if current not in eligible: return eligible[0] if direction > 0 else eligible[-1]
return eligible[(eligible.index(current) + 1) % len(eligible)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('hidden', solve([dict(visible=False, disabled=False, tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('disabled', solve([dict(visible=True,disabled=True,tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('negative', solve([dict(visible=True,disabled=False,tab=-1),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('priority', solve([dict(visible=True,disabled=False,tab=0),dict(visible=True,disabled=False,tab=2)], None, 1), 1)
check('reverse', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, -1), 0)
check('forward', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, 1), 2)
check('empty', solve([], None, 1), None)
check('parameterized focus sequence', solve([dict(visible=True, disabled=False, tab=0)]*(N+2), N, 1), N+1)
for repetition in range(N):
check('repeat empty', solve([], None, 1), 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hidden | 1 | 1 | Passed |
| disabled | 1 | 1 | Passed |
| negative | 1 | 1 | Passed |
| priority | 1 | 1 | Passed |
| reverse | 2 | 0 | Failed |
| forward | 2 | 2 | Passed |
| empty | None | None | Passed |
| parameterized focus sequence | 2 | 2 | Passed |
| repeat empty | None | None | Passed |
SHA-256 / ac9d70fb16be0a836c3e64ce17bc82d3a9cafa3aa0b2e81345cfe7e3de4e0329
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(items, current, direction):
eligible = [i for i, item in enumerate(items) if item['visible'] and not item['disabled'] and item['tab'] >= 0]
eligible.sort(key=lambda i: (items[i]['tab'] == 0, items[i]['tab'] if items[i]['tab'] > 0 else 0, i))
if not eligible: return None
if current not in eligible: return eligible[0] if direction > 0 else eligible[-1]
return eligible[(eligible.index(current) - direction) % len(eligible)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('hidden', solve([dict(visible=False, disabled=False, tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('disabled', solve([dict(visible=True,disabled=True,tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('negative', solve([dict(visible=True,disabled=False,tab=-1),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('priority', solve([dict(visible=True,disabled=False,tab=0),dict(visible=True,disabled=False,tab=2)], None, 1), 1)
check('reverse', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, -1), 0)
check('forward', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, 1), 2)
check('empty', solve([], None, 1), None)
check('parameterized focus sequence', solve([dict(visible=True, disabled=False, tab=0)]*(N+2), N, 1), N+1)
for repetition in range(N):
check('repeat empty', solve([], None, 1), 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hidden | 1 | 1 | Passed |
| disabled | 1 | 1 | Passed |
| negative | 1 | 1 | Passed |
| priority | 1 | 1 | Passed |
| reverse | 2 | 0 | Failed |
| forward | 0 | 2 | Failed |
| empty | None | None | Passed |
| parameterized focus sequence | 0 | 2 | Failed |
| repeat empty | None | None | Passed |
SHA-256 / f8f43782600c569f10ebb95166580297ec7132cbd30bf3561e70de8d7ced4357
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(items, current, direction):
eligible = [i for i, item in enumerate(items) if item['visible'] and not item['disabled'] and item['tab'] >= 0]
eligible.sort(key=lambda i: (items[i]['tab'] == 0, items[i]['tab'] if items[i]['tab'] > 0 else 0, i))
if not eligible: return None
if current not in eligible: return eligible[0] if direction > 0 else eligible[-1]
return eligible[(eligible.index(current) + direction) % len(eligible)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('hidden', solve([dict(visible=False, disabled=False, tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('disabled', solve([dict(visible=True,disabled=True,tab=0),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('negative', solve([dict(visible=True,disabled=False,tab=-1),dict(visible=True,disabled=False,tab=0)], None, 1), 1)
check('priority', solve([dict(visible=True,disabled=False,tab=0),dict(visible=True,disabled=False,tab=2)], None, 1), 1)
check('reverse', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, -1), 0)
check('forward', solve([dict(visible=True,disabled=False,tab=0)]*3, 1, 1), 2)
check('empty', solve([], None, 1), None)
check('parameterized focus sequence', solve([dict(visible=True, disabled=False, tab=0)]*(N+2), N, 1), N+1)
for repetition in range(N):
check('repeat empty', solve([], None, 1), 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| hidden | 1 | 1 | Passed |
| disabled | 1 | 1 | Passed |
| negative | 1 | 1 | Passed |
| priority | 1 | 1 | Passed |
| reverse | 0 | 0 | Passed |
| forward | 2 | 2 | Passed |
| empty | None | None | Passed |
| parameterized focus sequence | 2 | 2 | Passed |
| repeat empty | None | None | Passed |
SHA-256 / b57c62a1fcaf2d8083d523782e366fd00a799e24974a856b307d0486e8c13a14
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:15.869837+00:00.
Case digest / 43864bb4bae72e78554f9fe82c1288ef879e0cb582fb6293ccac46787a926fe7