keyboard_config

Review-specific keyboard building blocks for assembly into a KeyboardManager

Keyboard Parts Builder

Returns (zone, actions, modes) tuple for assembly into a KeyboardManager. Review uses simple navigation only (no sub-modes like segmentation’s split mode).


create_review_kb_parts


def create_review_kb_parts(
    ids:CardStackHtmlIds, # Card stack HTML IDs
    button_ids:CardStackButtonIds, # Card stack button IDs for navigation
    config:CardStackConfig, # Card stack configuration
)->Tuple: # (zone, actions, modes)

Create review-specific keyboard building blocks.

Zone Manager Factory

Creates a complete ZoneManager for the review step (single-zone, no zone switching).


create_review_keyboard_manager


def create_review_keyboard_manager(
    ids:CardStackHtmlIds, # Card stack HTML IDs
    button_ids:CardStackButtonIds, # Card stack button IDs for navigation
    config:CardStackConfig, # Card stack configuration
)->ZoneManager: # Configured keyboard zone manager

Create the keyboard zone manager for the review step.

# Speed cycle KeyActions wire correctly into the review zone
from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds as _IDs
from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds as _BtnIds
from cjm_fasthtml_card_stack.core.config import CardStackConfig as _Cfg

_zone, _actions, _modes = create_review_kb_parts(
    ids=_IDs(prefix="review-cs"),
    button_ids=_BtnIds(prefix="review-cs"),
    config=_Cfg(),
)

_by_key = {(a.key, frozenset(a.modifiers)): a for a in _actions}

_up = _by_key.get(("ArrowUp", frozenset({"shift"})))
_dn = _by_key.get(("ArrowDown", frozenset({"shift"})))
assert _up is not None, "Shift+ArrowUp binding missing"
assert _dn is not None, "Shift+ArrowDown binding missing"

# Scoped to the review card-stack zone, wired to web-audio's cycle helpers,
# grouped with the other audio bindings in the hint panel
assert _up.js_callback == "cycleReviewSpeedUp"
assert _dn.js_callback == "cycleReviewSpeedDown"
assert _up.zone_ids == (_zone.id,)
assert _dn.zone_ids == (_zone.id,)
assert _up.hint_group == "Audio"
assert _dn.hint_group == "Audio"

# Plain ArrowUp/ArrowDown (card-stack nav) must NOT collide — distinct modifiers frozenset
_nav_up = _by_key.get(("ArrowUp", frozenset()))
_nav_dn = _by_key.get(("ArrowDown", frozenset()))
assert _nav_up is not None and _nav_up.htmx_trigger, "Plain ArrowUp should still be the card-stack nav binding"
assert _nav_dn is not None and _nav_dn.htmx_trigger, "Plain ArrowDown should still be the card-stack nav binding"

print("Shift+Arrow speed cycle bindings test passed")
Shift+Arrow speed cycle bindings test passed