Skip to content

feat(tui): navigate panels via Up/Down arrow overflow at list boundaries #2273

Description

@r3v5

Problem Statement

Navigating between top-level dashboard panels (Gateways, Providers, Sandboxes) currently requires TAB. These panels are stacked vertically, so users expect Up/Down arrows to move between them. TAB should continue working, but Up/Down should overflow to the adjacent panel when the cursor reaches a list boundary.

Technical Context

The TUI uses a Focus enum to track which dashboard panel is active. TAB/BackTab (Shift+Tab) cycle through the ring: Gateways → Providers → Sandboxes → Gateways. Each panel's key handler has hardcoded KeyCode::Tab / KeyCode::BackTab arms that directly assign the next/previous Focus variant.

Up/Down arrows currently scroll within each panel's list (aliased with j/k). When the cursor is at the first item and Up is pressed, or at the last item and Down is pressed, nothing happens — the input is silently consumed.

Affected Components

Component Key Files Role
Key handlers crates/openshell-tui/src/app.rs Per-panel keyboard input dispatch
Nav bar UI crates/openshell-tui/src/ui/mod.rs Bottom status bar with key hints
Focus model crates/openshell-tui/src/app.rs:38-49 Focus enum tracking active panel

Technical Investigation

Architecture Overview

The TUI has two screen levels:

  1. Dashboard — three vertically stacked panels: Gateways (top), Providers (middle), Sandboxes (bottom). TAB cycles between them.
  2. Detail views (e.g., Sandbox detail) — sub-tabs like Policy/Settings within a panel.

Each panel has a dedicated handle_*_key method in app.rs that matches on KeyCode variants and mutates state directly. No key-binding abstraction layer exists.

Code References

Location Description
app.rs:21-29 Screen enum (Splash, Dashboard, Sandbox)
app.rs:38-49 Focus enum (Gateways, Providers, Sandboxes, SandboxPolicy, SandboxLogs, SandboxDraft)
app.rs:1138-1139 Gateways: TAB → Providers, BackTab → Sandboxes
app.rs:1144-1149 Gateways: Up/Down/j/k scroll list with saturating_sub / .min() bounds
app.rs:1179-1180 Providers: TAB → Sandboxes, BackTab → Gateways
app.rs:1185-1190 Providers: Up/Down/j/k scroll list
app.rs:1356-1357 Sandboxes: TAB → Gateways, BackTab → Providers
app.rs:1362-1367 Sandboxes: Up/Down/j/k scroll list
ui/mod.rs:250-284 Nav bar rendering for Gateways/Sandboxes focus

Current Behavior

  • Up at first item: saturating_sub(1) returns 0 — no-op, silently consumed.
  • Down at last item: .min(len - 1) clamps — no-op, silently consumed.
  • Panel switching: TAB only.

What Would Need to Change

Modify the Up/Down handling in each dashboard panel's key handler to detect boundary overflow:

Gateways (top panel):

  • Down at last item → Focus::Providers (select first item)
  • Up at first item → Focus::Sandboxes (select last item) — wraps ring

Providers (middle panel):

  • Down at last item → Focus::Sandboxes (select first item)
  • Up at first item → Focus::Gateways (select last item)

Sandboxes (bottom panel):

  • Down at last item → Focus::Gateways (select first item) — wraps ring
  • Up at first item → Focus::Providers (select last item)

Each handler needs to check current index against boundary before applying scroll, and switch focus + set cursor position in the target panel when at boundary.

Patterns to Follow

All key bindings are KeyCode::* match arms in per-handler methods. The scroll pattern uses saturating_sub and .min() — the overflow check wraps around these existing bounds checks.

Proposed Approach

When Up/Down would be a no-op (cursor already at list boundary), instead switch focus to the adjacent panel and position the cursor at the nearest edge (last item when moving up, first item when moving down). This gives natural "overflow" navigation — the cursor flows through panels as if they were one continuous list. TAB continues to work as the universal panel switcher.

Scope Assessment

  • Complexity: Low-Medium
  • Confidence: High — clear path, existing bounds checks provide the overflow detection point
  • Estimated files to change: 2 (app.rs, ui/mod.rs)
  • Issue type: feat

Risks & Open Questions

  • Cursor placement on overflow: When overflowing into adjacent panel, should cursor land on first/last item (nearest edge) or preserve some positional context? Nearest edge is simplest and most intuitive.
  • Empty panels: If a panel has zero items, overflow should skip through to the next panel. Edge case to handle.
  • j/k behavior: Should j/k also overflow, or only arrow keys? Vim convention is j/k don't overflow between windows. Recommend: both overflow for consistency since they're aliased.
  • No key handler tests exist. Pre-existing tech debt. Manual testing required.

Test Considerations

  • Manual testing: verify Up at top of Gateways overflows to Sandboxes (bottom), Down at bottom of Sandboxes overflows to Gateways (top)
  • Verify mid-list Up/Down still scrolls normally
  • Verify TAB still works in all panels
  • Test with empty panel lists (0 gateways, 0 providers, etc.)
  • Verify behavior doesn't affect Sandbox detail view (different screen)

Created by spike investigation. Use build-from-issue to plan and implement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    state:triage-neededOpened without agent diagnostics and needs triage

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions