Repository navigation
mecatui: converge bounded scrolling and selection controls #1589
Description
Goal
Introduce a package-private, reusable bounded scrolling and cursor-selection system for mecatui, then migrate the TUI incrementally while targeted fixes continue.
This is an umbrella issue. It does not authorize a single repository-wide rewrite or replacement of the specialized conversation viewport.
Current tracking (2026-10-16)
The bounded controls already exist in cmd/mecatui/ui/internal/bounded; the proposal and initial slices below describe their origin, not work that must be repeated. Use #1742 as the detailed adoption checklist and #2237 for remaining surface ownership migrations.
- Establish bounded controls and adopt them in Agents and Models.
- Adopt in slash palette, mentions, sessions inventory, saved-memory browser, agent-definition inventory, skills, soul, Help, and Dream (including explicit retain/evaluate decisions).
-
/effort(mecatui: bound /effort picker at constrained terminal heights #2170). -
/worktrees(mecatui: bound /worktrees picker #2172; PR feat(mecatui): bound worktree picker and move it to a modal surface #2279), including the sharedsurfacelifecycle. - Remaining bounded evaluations/adoptions in mecatui: track remaining bounded browsing adoption #1742: Approval (mecatui: evaluate bounded viewports in Approval review #1902), stored-session transcript (mecatui: evaluate bounded.Viewport for stored-session transcript #1903), schedule (mecatui: bound /schedule inventory and fire-history browsing #2173), reflections (mecatui: migrate /reflections proposal browsing to bounded controls #2174), MCP pickers (mecatui: evaluate bounded MCP resource and prompt pickers #2175), and connect (mecatui: evaluate bounded /connect target picker #2176).
Next coordinated slice: #2173, bounding /schedule inventory and fire history while moving its legacy overlay onto surface. Compare the full-conversation browser interaction with /sessions and add reusable conventions to docs/tui.md. This umbrella remains open; partial migrations must not close it.
Historical proposal context
The following motivation and sketch predate the implemented controls. The current contracts live in docs/tui.md, cmd/mecatui/ui/surface.go, and the bounded package.
Why
Mecatui currently has several independent implementations of list windowing, rendered-line scrolling, cursor movement, overflow indicators, selection styling, and mouse routing. PR #1488 added the strongest multiline selectable-row implementation for the unified Agents overlay, but it remains Agents-specific and keyboard-only.
Current inconsistencies include:
- selectable rows use different markers and styles (
›,▶,>, button-active, spinner, accent); - wheel events scroll some surfaces, do nothing on others, and can reach the hidden conversation beneath legacy overlays;
- several lists render without a viewport bound;
- multiline logical items are sometimes selected as items and sometimes split into independently scrolled physical lines;
- browsing, cursor selection, keyboard focus, current/default state, hover, and text selection do not have a shared vocabulary.
Proposal sketch
Build two package-private layers under cmd/mecatui/ui rather than one monolithic widget.
1. Bounded window
- explicit width and height;
- physical-line accounting;
- bounded vertical offset, line/page movement, top/end, and wheel input;
- explicit wrap-or-clip width policy;
- overflow/range metadata;
- browsing without a cursor.
2. Selectable list over the bounded window
- optional cursor;
- logical items spanning one or more physical lines;
- keep the entire cursor item visible when possible;
- item movement plus physical-height paging;
- mouse hit regions spanning all physical lines of an item;
- click-to-cursor with activation remaining an explicit surface callback;
- filtering, actions, fetching, confirmation, and server pagination remain surface-owned.
Use distinct semantics and styles for text selection, list cursor, keyboard focus, current/active value, default value, hover, and activation. Width bounds are required; horizontal panning is not part of the initial contract without a concrete diff/table use case.
The main conversation viewport remains specialized because it owns streaming-tail following, logical text selection, reflow anchors, ANSI/grapheme handling, and drag autoscroll.
Incremental migration
Slice 1 — establish and prove the control
Create a Bounded acceptance plan and implementation PR that extracts the multiline physical-window behavior from #1488, defines the interaction/style contract, and proves it against representative consumers:
- unified Agents overlay — multiline cursor rows plus browsing-only detail panes;
/models— simple fixed-height cursor rows.
The Agents overlay proves both control modes without coupling the first slice to another surface's mixed domain behavior. Other browsing-only surfaces remain follow-ups.
Slice 2 — mechanical list adoption
After Slice 1 establishes the contract, classify each batch independently. Mechanical adoption can be Routine; behavioral changes remain Bounded.
Candidate batches:
- command palette +
@mentions; /sessionsroster +/worktrees;/schedule+ MCP lists +/connect.
Slice 3 — complex and legacy surfaces
Handle /user model, reflections, dream, help, skills, agent inventory, detail panes, and previews in focused follow-ups. Any decision about migrating legacy overlays onto the formal surface lifecycle should get its own Bounded contract rather than being hidden in a mechanical migration.
Interaction principles
- Wheel input belongs to the visible surface and never silently scrolls hidden content.
- A multiline row is one logical cursor and pointer target.
- Hover is visual only and does not move the keyboard cursor.
- Cursor movement and activation are distinct.
- Current/default status markers remain orthogonal to cursor and focus.
- Existing frame-scoped hit regions remain the pointer-dispatch mechanism.
- Existing remappable navigation actions remain authoritative where already supported.
Process
Track this issue with non-closing Relates to links from partial Plan / Interface and implementation PRs. Only the final PR that completes the umbrella should close it. Each independently shippable slice is classified separately under docs/development-process.md.
References
- fix: bound unified Agents overlay to viewport #1488
- Mecatui unified Agents overlay selection and viewport fit #1446
docs/acceptance/mecatui-unified-agents-overlay-fit.mdcmd/mecatui/ui/agents_overlay.gocmd/mecatui/ui/window.gocmd/mecatui/ui/surface.gocmd/mecatui/ui/hit_regions.go
Activity