Skip to content

Analyzer: ReactivePage rendering a focusable SelectionListNode must claim its keyboard input #1521

Description

@Aaronontheweb

Parent epic: #304

Problem

SelectionListNode is focusable (CanFocus => true, FocusPriority 10). When a
ReactivePage renders one, ReactivePage.ApplyFocusPolicy hands it keyboard focus, and
Termina's input dispatch routes keys to the focus manager before the page's ViewModel
input stream — and SelectionListNode.HandleInput consumes UpArrow/DownArrow/Enter/
Escape (returns true). So a page that renders a selection list but expects to read those
keys off ViewModel.Input silently receives nothing: the highlight can't be driven, the
selection index sticks at 0, and Enter resumes nothing.

This shipped in SessionsPage (netclaw sessions — you couldn't select any session) and was
fixed in #1518 (commit 65365afb0). It was the only offender, but nothing catches the next
one: the headless TUI test harness doesn't hand the list focus during input processing, so it
can't reproduce the focus-eating, and the empty-state smoke tape renders a TextNode, not a
focusable list. A unit test passed against the broken code.

The two correct patterns already in the codebase:

  • subscribe to the node's SelectionConfirmed and read selection from the focused node
    (7 of 8 list pages: Approvals, ModelManager, ProviderManager, ConfigDashboard, ChatPage,
    ReminderCreate, InitExistingInstall), or
  • override HandlePageInput to claim the keys before the focus manager (SessionsPage
    after fix(tui): redo timezone loop + restore session browser selection #1518 — appropriate because it also owns paging and a ViewModel-held index).

Candidate rule

NCLW3001: ReactivePage renders a focusable SelectionListNode without claiming its input

  • Trigger: A non-abstract type deriving from ReactivePage<TViewModel> whose layout
    construction (transitively, through helpers) creates a SelectionListNode /
    Layouts.SelectionList(...), AND the page neither overrides HandlePageInput nor
    subscribes to that node's SelectionConfirmed.
  • Severity: Warning
  • Scope: Netclaw.Cli page types (exclude test projects)
  • Fix: subscribe to SelectionConfirmed, or override HandlePageInput and forward keys
    to the ViewModel.

Notes

  • A prototype source-text audit test (grep each *Page.cs for SelectionList plus
    SelectionConfirmed/HandlePageInput) works and has teeth today, but it's a brittle text
    heuristic — it can't see a list built through a helper indirection, and it's the wrong place
    for this. A semantic analyzer should resolve the node through the symbol/dataflow model and
    give IDE-time feedback. That prototype was intentionally discarded in favour of this issue.
  • Consider whether the same hazard applies to other always-focusable input nodes (e.g. a bare
    focusable list in a page that routes everything through ViewModel.Input). The general
    invariant is "a page that renders a focusable input-consuming node must account for the
    focus manager consuming keys before the ViewModel."
  • A [SuppressMessage]/opt-out path is probably warranted for a page that deliberately leaves
    a list non-interactive.

Acceptance criteria

  • Detects pages that render a focusable SelectionListNode without SelectionConfirmed or HandlePageInput
  • Resolves the node semantically (through helper indirection), not by source text
  • No false positives on the eight current list pages
  • Existing codebase passes clean

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions