FAILURE MAP
← Case archive

FA-8396 / Tree accessibility / Open access

Tree keyboard expansion: Left-arrow on a collapsed child does not move to its parent · case 01

Left-arrow on a collapsed child does not move to its parent.

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

ROOT CAUSE

The parent left operation uses `node` where the contract requires `parents.get(node, node)`.

VERIFIED REPAIR

Implement the parent left operation as `parents.get(node, node)`.

Unsuccessful approach: A missing parent must keep root focus rather than return None.

Case contract

Right expands then enters the first child; Left collapses only the current branch or focuses its parent; roots retain focus; collapse-all clears every expansion.

Why this case matters

A deterministic model of tree keyboard expansion; 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(node, expanded, parents, children, key):
    expanded = set(expanded)
    kids = children.get(node, [])
    if key == 'Right' and kids:
        if node not in expanded: expanded.add(node)
        else: node = kids[0]
    elif key == 'Left':
        if node in expanded: expanded.remove(node)
        else: node = node
    elif key == 'CollapseAll': expanded.clear()
    return (node, sorted(expanded))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('expand', solve('a', ['z'], {}, {'a':['b','c']}, 'Right'), ('a', ['a', 'z']))
check('descend', solve('a', ['a'], {}, {'a':['b','c']}, 'Right'), ('b', ['a']))
check('collapse', solve('a', ['a','z'], {}, {'a':['b']}, 'Left'), ('a', ['z']))
check('parent', solve('b', [], {'b':'a'}, {}, 'Left'), ('a', []))
check('root', solve('a', [], {}, {}, 'Left'), ('a', []))
check('all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
check('parameterized child count', solve('a', [], {}, {'a':list(range(N))}, 'Right'), ('a',['a']))
for repetition in range(N):
    check('repeat all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
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
expand['a', ['a', 'z']]['a', ['a', 'z']]Passed
descend['b', ['a']]['b', ['a']]Passed
collapse['a', ['z']]['a', ['z']]Passed
parent['b', []]['a', []]Failed
root['a', []]['a', []]Passed
all['a', []]['a', []]Passed
parameterized child count['a', ['a']]['a', ['a']]Passed
repeat all['a', []]['a', []]Passed

SHA-256 / 77d6eb5d0281c609bf8e54202955ebfefadaec29ff7f6fa9e8abaea931094cfc

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(node, expanded, parents, children, key):
    expanded = set(expanded)
    kids = children.get(node, [])
    if key == 'Right' and kids:
        if node not in expanded: expanded.add(node)
        else: node = kids[0]
    elif key == 'Left':
        if node in expanded: expanded.remove(node)
        else: node = parents.get(node)
    elif key == 'CollapseAll': expanded.clear()
    return (node, sorted(expanded))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('expand', solve('a', ['z'], {}, {'a':['b','c']}, 'Right'), ('a', ['a', 'z']))
check('descend', solve('a', ['a'], {}, {'a':['b','c']}, 'Right'), ('b', ['a']))
check('collapse', solve('a', ['a','z'], {}, {'a':['b']}, 'Left'), ('a', ['z']))
check('parent', solve('b', [], {'b':'a'}, {}, 'Left'), ('a', []))
check('root', solve('a', [], {}, {}, 'Left'), ('a', []))
check('all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
check('parameterized child count', solve('a', [], {}, {'a':list(range(N))}, 'Right'), ('a',['a']))
for repetition in range(N):
    check('repeat all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
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
expand['a', ['a', 'z']]['a', ['a', 'z']]Passed
descend['b', ['a']]['b', ['a']]Passed
collapse['a', ['z']]['a', ['z']]Passed
parent['a', []]['a', []]Passed
root[None, []]['a', []]Failed
all['a', []]['a', []]Passed
parameterized child count['a', ['a']]['a', ['a']]Passed
repeat all['a', []]['a', []]Passed

SHA-256 / 5810454dfe630f63d0a7acad536a36648fbd2460f506ce0fa19b8378740e5406

3 / The verified repair

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

N = 1
observations = []
def solve(node, expanded, parents, children, key):
    expanded = set(expanded)
    kids = children.get(node, [])
    if key == 'Right' and kids:
        if node not in expanded: expanded.add(node)
        else: node = kids[0]
    elif key == 'Left':
        if node in expanded: expanded.remove(node)
        else: node = parents.get(node, node)
    elif key == 'CollapseAll': expanded.clear()
    return (node, sorted(expanded))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('expand', solve('a', ['z'], {}, {'a':['b','c']}, 'Right'), ('a', ['a', 'z']))
check('descend', solve('a', ['a'], {}, {'a':['b','c']}, 'Right'), ('b', ['a']))
check('collapse', solve('a', ['a','z'], {}, {'a':['b']}, 'Left'), ('a', ['z']))
check('parent', solve('b', [], {'b':'a'}, {}, 'Left'), ('a', []))
check('root', solve('a', [], {}, {}, 'Left'), ('a', []))
check('all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
check('parameterized child count', solve('a', [], {}, {'a':list(range(N))}, 'Right'), ('a',['a']))
for repetition in range(N):
    check('repeat all', solve('a', ['a','z'], {}, {}, 'CollapseAll'), ('a', []))
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
expand['a', ['a', 'z']]['a', ['a', 'z']]Passed
descend['b', ['a']]['b', ['a']]Passed
collapse['a', ['z']]['a', ['z']]Passed
parent['a', []]['a', []]Passed
root['a', []]['a', []]Passed
all['a', []]['a', []]Passed
parameterized child count['a', ['a']]['a', ['a']]Passed
repeat all['a', []]['a', []]Passed

SHA-256 / d8cc0e4d8b47f71a93b2eb97d9a27e6666808598de5a935b5c45db1e6e515900

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

Case digest / 0d92b67b2a89d519a8843d9191aa8d403ede21e01425db6cdb6fcdc4277a8dc8