Clickable Footer Shortcuts Plan

Plan for making the bottom shortcut bar clickable, adding the missing visible A purge shortcut, making confirmation y/n clickable, and hiding lower-priority shortcuts on narrow terminals.

ctop TUI src/_core.js review artifact 2026-06-06

Recommendation: implement a single shortcut descriptor list and use it for footer rendering, click hitbox registration, command-palette entries, and tests where practical. Footer clicks should dispatch the same keys that keyboard input already handles, so click behavior stays identical to existing shortcuts.

Interactive Review Mock

This browser mock is not the TUI implementation. It demonstrates the proposed priority behavior and confirmation click states.

ctop - clickable footer prototype
CTOP AI Agent Terminal Operations Panel
2 active | 1 stopped | sort age | ctx ok | notif on
PIDSTATUSCTXSTARTEDTITLECPU%
8412ACTIVE82%4m agofeature-clickable-footer2.4
8270ACTIVE61%19m agodocs-polish1.8
7921STOPPED--2h agoold-session0.0
Kill ALL stopped/dead processes?
KEYS:
Click a shortcut in the mock footer.

Current State

What already exists

  • Mouse tracking is enabled and routed through parseMouseEvent then handleMouseEvent.
  • Rows can already be clicked and scroll events already navigate the list.
  • The keyboard path already supports A for killing stopped/dead processes with confirmation.
  • The help screen already lists A; the footer does not.

Gaps to close

  • The footer is rendered as static text with no recorded hitboxes.
  • Confirmation prompts are written directly to stdout, so their y/N locations are not clickable.
  • Footer content is a long hardcoded line and can truncate unpredictably on narrow terminals.
  • Shortcut labels are duplicated between the footer, command palette, help, and keyboard switch.

Architecture

Current: renderNow() -> hardcoded footer string mouse event -> handleMouseEvent() -> row hit test only keyboard event -> handleInput(key) -> switch(key) Target: SHORTCUTS metadata -> renderFooterShortcuts(columns) -> visible shortcut text -> footerHitboxes -> command palette metadata -> tests mouse event -> handleMouseEvent() -> confirmation hitbox? -> footer hitbox? -> row hit test fallback -> handleInput(shortcut.key)

New helper responsibilities

HelperPurposeNotes
SHORTCUTSCanonical footer shortcut metadata.Key, label, command key, priority, danger flag, visibility predicate.
renderFooterShortcuts(columns)Builds the footer line and records clickable ranges.Always keeps ?. Adds A. Hides by priority as width shrinks.
findFooterShortcutAt(row, col)Maps a mouse click to a shortcut key.Uses measured visual columns, not string length with ANSI escapes.
renderConfirmPrompt(type)Renders confirmation prompts and records y/n hitboxes.Can still accept keyboard y and any other key as cancel.
findConfirmChoiceAt(row, col)Maps prompt click to y or n.Runs before footer and row hit testing.

Shortcut Priority

Priority should be explicit. Lower numbers stay visible longer. The renderer should add shortcuts in priority order, reserve room for KEYS: and ?, then stop once the next item would exceed terminal width.

PriorityShortcutReasonVisibility
0? HelpEscape hatch requested to always remain visible.Always shown, even on tiny width.
1jk Nav, x Kill, X ForceCore operational controls.Shown first after mandatory help.
2A Purge, K Kill AllDanger actions need discoverability, but remain confirmed.Shown before convenience navigation.
3/ Filter, F Search, s Sort, r RefreshFrequent list operations.Shown on normal widths.
4o Open, G Group, L Log, W Timeline, E ExportUseful but not needed every moment.Hidden earlier on medium widths.
5T Theme, d Dash, H History, C Heatmap, n Notif, P Pane, q QuitLower urgency or already discoverable through help.First to hide on narrow widths.

Implementation Plan

  1. Introduce footer shortcut metadata. Add a FOOTER_SHORTCUTS array near the command metadata in src/_core.js. Include A Purge and optionally K All so all destructive footer shortcuts are visible when width allows.
  2. Render footer from metadata. Replace the hardcoded footer lines in renderNow() with renderFooterShortcuts(columns, rows). The helper returns ANSI-rendered text and updates a module-level footerHitboxes array.
  3. Measure click ranges in terminal columns. Track the start and end column for each shortcut token while building the plain footer text. Make the whole label clickable, not only the key character, so x Kill and F Search feel easy to hit.
  4. Dispatch footer clicks through keyboard handling. In handleMouseEvent, before row hit testing, check whether the click is on the footer row. If so, call handleInput(hit.key) and return.
  5. Move confirmation prompts into render state. Replace direct process.stdout.write(... (y/N)) confirmation output with a small confirmation state object, for example confirmPrompt = { type, message }. Render it inside renderNow() or through a focused prompt render helper that records confirmHitboxes.
  6. Make y and n clickable. Let confirmation click hitboxes dispatch handleInput('y') or handleInput('n'). Keep current keyboard behavior where y/Y confirms and other keys cancel.
  7. Export small pure helpers for tests. Export the footer selection and hitbox helpers, or expose them through existing test internals. Avoid making tests depend on full-screen rendering side effects.
  8. Update docs. Add A to README keyboard shortcuts if it should be user-facing there too. The help text already includes it.

Detailed Design

Footer descriptor shape

{ key: "A", label: "Purge", priority: 2, danger: true, always: false, enabledWhen: () => true }

Hitbox shape

{ row: process.stdout.rows, colStart: 31, colEnd: 39, key: "A", kind: "footer-shortcut" }

Width behavior

The algorithm should not rely on breakpoint-specific hardcoded strings. It should greedily fit items by priority after reserving space for the mandatory help item. This keeps behavior stable across terminal widths and label edits.

renderFooterShortcuts(columns): mandatory = [? Help] candidates = shortcuts excluding mandatory, sorted by priority then order visible = [] for candidate in candidates: if fits("KEYS:" + visible + candidate + mandatory): visible.push(candidate) return render("KEYS:" + visible + mandatory)

Confirmation behavior

The current flags can remain, but direct prompt writes should become renderable state. This avoids stale prompts and gives mouse handling a stable row/column model.

beginConfirmation("purge-stopped"): confirmKillStopped = true confirmPrompt = { message: "Kill ALL 3 stopped/dead processes?", yesKey: "y", noKey: "n" } render()

Files To Modify

FileChangeReason
src/_core.jsModifyAdd shortcut metadata, footer renderer, hitbox lookup, confirmation prompt rendering, and mouse dispatch.
src/input.jsMaybe modifyOnly if new pure helpers should be re-exported from the input facade.
test/mouse.test.jsModifyAdd footer hitbox mapping, priority hiding, and confirmation click tests.
test/status-bar.test.js or new test/footer-shortcuts.test.jsModify or addKeep render and width-fit tests focused.
README.mdModifyAdd A purge stopped/dead sessions to the public shortcut table.

Validation Plan

Unit tests

  • ? appears at all widths that can render any footer.
  • A Purge appears at normal width and maps to key A.
  • Lower-priority items disappear before higher-priority items.
  • Hitbox lookup ignores ANSI escape lengths.

Mouse tests

  • Clicking footer r Refresh dispatches r.
  • Clicking footer A Purge enters stopped/dead confirmation when stopped processes exist.
  • Footer clicks do not select list rows.
  • Mouse release events still do nothing.

Confirmation tests

  • Clicking y executes the same path as pressing y.
  • Clicking n cancels the prompt.
  • Keyboard confirmation still works for K, A, and bulk x/X.
  • Prompt hitboxes clear after resolution.

Final command: npm test. Manual smoke check: run npm start, click footer shortcuts in a terminal with mouse reporting, resize to narrow widths, and confirm ? remains visible.

Risks And Decisions

RiskDecision
Directly calling handleInput from mouse dispatch can recurse if mouse sequences are passed through.Only dispatch plain shortcut keys from hitboxes, never raw mouse escape sequences.
Terminal column width differs from JavaScript string length with ANSI codes.Track hitboxes from plain labels while rendering ANSI separately.
Destructive actions become easier to trigger by click.Keep confirmation prompts for K, A, and bulk kill paths. Make n clickable and keep non-yes keys as cancel.
Hidden shortcuts could reduce discoverability on narrow terminals.Always keep ? visible and route users to the full help screen.

Source Notes

Observed in the current code: command palette metadata at src/_core.js:614, static footer rendering at src/_core.js:4810, row-only mouse click fallback at src/_core.js:5739, confirmation handling at src/_core.js:5931, and existing A keyboard handling at src/_core.js:6327.