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:
- Dashboard — three vertically stacked panels: Gateways (top), Providers (middle), Sandboxes (bottom). TAB cycles between them.
- 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.
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
Focusenum 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 hardcodedKeyCode::Tab/KeyCode::BackTabarms that directly assign the next/previousFocusvariant.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
crates/openshell-tui/src/app.rscrates/openshell-tui/src/ui/mod.rscrates/openshell-tui/src/app.rs:38-49Focusenum tracking active panelTechnical Investigation
Architecture Overview
The TUI has two screen levels:
Each panel has a dedicated
handle_*_keymethod inapp.rsthat matches onKeyCodevariants and mutates state directly. No key-binding abstraction layer exists.Code References
app.rs:21-29Screenenum (Splash, Dashboard, Sandbox)app.rs:38-49Focusenum (Gateways, Providers, Sandboxes, SandboxPolicy, SandboxLogs, SandboxDraft)app.rs:1138-1139app.rs:1144-1149saturating_sub/.min()boundsapp.rs:1179-1180app.rs:1185-1190app.rs:1356-1357app.rs:1362-1367ui/mod.rs:250-284Current Behavior
saturating_sub(1)returns 0 — no-op, silently consumed..min(len - 1)clamps — no-op, silently consumed.What Would Need to Change
Modify the Up/Down handling in each dashboard panel's key handler to detect boundary overflow:
Gateways (top panel):
Focus::Providers(select first item)Focus::Sandboxes(select last item) — wraps ringProviders (middle panel):
Focus::Sandboxes(select first item)Focus::Gateways(select last item)Sandboxes (bottom panel):
Focus::Gateways(select first item) — wraps ringFocus::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 usessaturating_suband.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
app.rs,ui/mod.rs)featRisks & Open Questions
Test Considerations
Created by spike investigation. Use
build-from-issueto plan and implement.