You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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
Parent epic: #304
Problem
SelectionListNodeis focusable (CanFocus => true,FocusPriority 10). When aReactivePagerenders one,ReactivePage.ApplyFocusPolicyhands it keyboard focus, andTermina's input dispatch routes keys to the focus manager before the page's ViewModel
input stream — and
SelectionListNode.HandleInputconsumesUpArrow/DownArrow/Enter/Escape(returnstrue). So a page that renders a selection list but expects to read thosekeys off
ViewModel.Inputsilently receives nothing: the highlight can't be driven, theselection index sticks at 0, and Enter resumes nothing.
This shipped in
SessionsPage(netclaw sessions— you couldn't select any session) and wasfixed in #1518 (commit
65365afb0). It was the only offender, but nothing catches the nextone: 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 afocusable list. A unit test passed against the broken code.
The two correct patterns already in the codebase:
SelectionConfirmedand read selection from the focused node(7 of 8 list pages: Approvals, ModelManager, ProviderManager, ConfigDashboard, ChatPage,
ReminderCreate, InitExistingInstall), or
HandlePageInputto claim the keys before the focus manager (SessionsPageafter 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
ReactivePage<TViewModel>whose layoutconstruction (transitively, through helpers) creates a
SelectionListNode/Layouts.SelectionList(...), AND the page neither overridesHandlePageInputnorsubscribes to that node's
SelectionConfirmed.Netclaw.Clipage types (exclude test projects)SelectionConfirmed, or overrideHandlePageInputand forward keysto the ViewModel.
Notes
*Page.csforSelectionListplusSelectionConfirmed/HandlePageInput) works and has teeth today, but it's a brittle textheuristic — 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.
focusable list in a page that routes everything through
ViewModel.Input). The generalinvariant is "a page that renders a focusable input-consuming node must account for the
focus manager consuming keys before the ViewModel."
[SuppressMessage]/opt-out path is probably warranted for a page that deliberately leavesa list non-interactive.
Acceptance criteria
SelectionListNodewithoutSelectionConfirmedorHandlePageInput