Skip to content

fix(tui): render the ? help as an overlay, not a full-screen replace#420

Closed
gnanam1990 wants to merge 1 commit into
mainfrom
fix/help-overlay
Closed

fix(tui): render the ? help as an overlay, not a full-screen replace#420
gnanam1990 wants to merge 1 commit into
mainfrom
fix/help-overlay

Conversation

@gnanam1990

@gnanam1990 gnanam1990 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Fixes #419.

Problem

Pressing ? replaced the entire TUI with the keyboard-shortcut block — the transcript, title bar, and composer all vanished. Every other popup (model picker, suggestions, wizards) composites over the chat instead, and the issue asks for ? to behave the same way.

Root cause: View() short-circuited on m.helpOverlay to renderKeybindingHelpOverlay, which centers the block on a blank canvas via centerRenderedBlock, so nothing else rendered.

Fix

Route the help overlay through the same viewport-overlay path the pickers use:

  • Add keybindingHelpOverlay(width) — returns "" when closed, else the centered block.
  • Include it as the highest-priority case in transcriptView's overlay switch and in twoColumnTranscriptView (so it also composites correctly with the sidebar open).
  • Drop the content-replacement branch in View().

The overlay now draws on top of the chat via the existing overlayViewportLines compositing, keeping the transcript / title bar / composer visible behind it. Input handling is unchanged — the modal key-swallow and ?/Esc/q/Enter dismiss run before render — so only the presentation changes.

Testing

  • New tests: the overlay composites over the chat chrome (title bar + composer survive) and over a populated transcript row. Red-green — restoring the old full-replace makes both fail (chrome and body gone).
  • Existing open/close/swallow/content tests unchanged and green; full internal/tui suite passes.
  • Verified live: ? on the home screen now shows the title bar, the centered shortcuts box as an overlay, footer hints, and the composer; Esc dismisses cleanly with the chat intact.

Before → after

Before: ? blanks the screen down to just the centered box.
After: the shortcuts box floats over the chat like the model picker, with the title bar above and the composer below still visible.

Summary by CodeRabbit

  • Bug Fixes
    • Keyboard Shortcuts now appears as an overlay on top of the chat screen instead of replacing the entire view.
    • Existing chat content and transcript text remain visible behind the overlay, preserving the current conversation display.

Pressing ? replaced the entire TUI with the shortcut block: View() short-
circuited to renderKeybindingHelpOverlay, which centers the block on a blank
canvas, so the transcript, title bar, and composer all vanished. Every other
popup (model picker, suggestions, wizards) composites over the chat instead.

Route the help overlay through the SAME viewport-overlay path the pickers use:
add keybindingHelpOverlay(width) (returns "" when closed, else the centered
block) as the highest-priority case in transcriptView's overlay switch and in
twoColumnTranscriptView, and drop the content-replacement branch in View(). The
overlay now draws on top of the chat via the existing overlayViewportLines
compositing, keeping the transcript/title bar/composer visible behind it. Input
handling is unchanged — the modal key-swallow + ?/Esc/q/Enter dismiss run before
render — so only the presentation changes.

Tests: the help overlay composites over the chat chrome (title bar + composer
survive) and over a populated transcript row (red-green: with the old
full-replace both the chrome and the body are gone); existing
open/close/swallow/content tests unchanged.

Fixes #419
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Tests: go test ./...
  • [pass] Build: go run ./cmd/zero-release build
  • [pass] Smoke build: go run ./cmd/zero-release smoke

Scope

Head: 5746424272c4
Changed files (3): internal/tui/keybinding_help.go, internal/tui/keybinding_help_test.go, internal/tui/model.go

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The help overlay is refactored to composite over the transcript viewport rather than fully replacing the screen. A new keybindingHelpOverlay helper is wired into transcriptView and twoColumnTranscriptView as a viewportOverlay, the old View() special-case is removed, and regression tests are added.

Changes

Help overlay composition fix

Layer / File(s) Summary
Overlay helper and View() removal
internal/tui/keybinding_help.go, internal/tui/model.go
Adds model.keybindingHelpOverlay(width) returning rendered overlay or empty string, removes the View()-level branch that fully replaced the screen when helpOverlay was true, and includes a minor comment update in the keypress handler.
Overlay wiring into transcript views
internal/tui/model.go
transcriptView() and twoColumnTranscriptView() now compute and prioritize helpOverlay as the viewportOverlay/overlayForViewport, compositing it over chat content like other popups.
Regression tests
internal/tui/keybinding_help_test.go
Adds tests confirming the overlay composites over chat chrome and that transcript content remains visible behind the centered overlay.

Estimated code review effort: 2 (Simple) | ~12 minutes

Possibly related PRs

  • Gitlawb/zero#160: Both PRs modify the transcriptView overlay rendering pipeline in internal/tui/model.go.
  • Gitlawb/zero#168: Related refactor of the same transcript overlay compositing system.
  • Gitlawb/zero#300: Touches the keybinding help entries and two-column/managed overlay logic.

Suggested reviewers: Vasanthdev2004

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: rendering the help screen as an overlay instead of replacing the TUI.
Linked Issues check ✅ Passed The PR fixes #419 by compositing the help overlay over the existing chat UI and adds regression tests for the overlay behavior.
Out of Scope Changes check ✅ Passed No meaningful out-of-scope code changes are present beyond a minor comment tweak.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/help-overlay

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/model.go (1)

2183-2220: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Block mouse clicks while the help overlay is openhandleMouse still routes through transcript hit-testing, so a click on a specialist card or AGENTS row can drill into subchat while ? remains visible. Add a m.helpOverlay guard before transcript/subchat mouse handling, or make the help overlay absorb clicks like the keyboard path does.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model.go` around lines 2183 - 2220, The mouse handling path
still processes transcript hit-testing while the help overlay is visible, so
clicks can trigger transcript or subchat actions instead of being absorbed by
the overlay. Update handleMouse to check m.helpOverlay (or the same state used
by keybindingHelpOverlay) before any transcript/subchat click routing, and
short-circuit so the help overlay blocks mouse interactions just like the
keyboard flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/tui/keybinding_help_test.go`:
- Around line 108-160: Add a regression test for the two-column help overlay
path in the existing help overlay tests. The current coverage only exercises the
single-column transcript render, so create an alt-screen model with
sidebarActive() behavior enabled and helpOverlay set to true, then verify the
overlay still appears while the surrounding chat chrome/transcript remains
visible. Use the transcriptView() and twoColumnTranscriptView() paths as the key
symbols to target this branch.

---

Outside diff comments:
In `@internal/tui/model.go`:
- Around line 2183-2220: The mouse handling path still processes transcript
hit-testing while the help overlay is visible, so clicks can trigger transcript
or subchat actions instead of being absorbed by the overlay. Update handleMouse
to check m.helpOverlay (or the same state used by keybindingHelpOverlay) before
any transcript/subchat click routing, and short-circuit so the help overlay
blocks mouse interactions just like the keyboard flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6bdccb43-1d79-4526-a9d3-57cf60a30617

📥 Commits

Reviewing files that changed from the base of the PR and between b5a78dc and 5746424.

📒 Files selected for processing (3)
  • internal/tui/keybinding_help.go
  • internal/tui/keybinding_help_test.go
  • internal/tui/model.go

Comment on lines +108 to +160

// #419: the `?` help overlay must render ON TOP of the chat (like the model
// picker), not REPLACE the whole screen. The old full-screen replace produced
// only the centered shortcut block on a blank canvas — no title bar, no
// composer. So with the overlay open, the surrounding chat chrome that is NOT
// covered by the centered box (the model title bar at top, the composer at
// bottom) must still be present alongside "Keyboard Shortcuts".
func TestHelpOverlayCompositesOverChatNotReplacingIt(t *testing.T) {
m := newModel(context.Background(), Options{ModelName: "gpt-4o"})
m.width = 100
m.height = 40
m.altScreen = true

base := plainRender(t, m.View()) // no overlay: baseline chrome
m.helpOverlay = true
over := plainRender(t, m.View())

if !strings.Contains(over, "Keyboard Shortcuts") {
t.Fatalf("help overlay not rendered:\n%s", over)
}
// Chrome that renders in the baseline (and sits outside the centered overlay
// box) must survive behind the overlay. The full-screen replace showed none
// of it.
for _, marker := range []string{"gpt-4o", "describe a task"} {
if !strings.Contains(base, marker) {
t.Fatalf("precondition: baseline chat should contain %q:\n%s", marker, base)
}
if !strings.Contains(over, marker) {
t.Fatalf("#419: help replaced the chat instead of overlaying it; %q is gone:\n%s", marker, over)
}
}
}

// A populated transcript row also survives behind the overlay (peeking out to
// the left of the centered box), proving the chat body — not just the chrome —
// is composited under the overlay rather than discarded.
func TestHelpOverlayKeepsTranscriptBodyBehindIt(t *testing.T) {
m := newModel(context.Background(), Options{ModelName: "gpt-4o"})
m.width = 120
m.height = 40
m.altScreen = true
m.transcript = appendTranscriptRow(m.transcript, transcriptRow{kind: rowUser, text: "hello there this is a chat line"})
m.helpOverlay = true

view := plainRender(t, m.View())
if !strings.Contains(view, "Keyboard Shortcuts") {
t.Fatalf("help overlay not rendered:\n%s", view)
}
// The start of the transcript line peeks to the left of the centered box.
if !strings.Contains(view, "hello") {
t.Fatalf("#419: transcript body was replaced by the help overlay:\n%s", view)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find what conditions gate sidebarActive()/two-column rendering, to help craft a targeted test.
rg -n 'func \(m model\) sidebarActive' internal/tui -A20

Repository: Gitlawb/zero

Length of output: 1692


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the view dispatch and the two-column path.
ast-grep outline internal/tui/model.go --view expanded | sed -n '1,220p'
printf '\n--- sidebar ---\n'
cat -n internal/tui/sidebar.go | sed -n '1,220p'
printf '\n--- tests mentioning help overlay or two-column ---\n'
rg -n 'helpOverlay|Keyboard Shortcuts|twoColumnTranscriptView|sidebarActive|sidebarAvailable' internal/tui -g '*_test.go' -A4 -B4

Repository: Gitlawb/zero

Length of output: 37675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the main view dispatch and the two-column transcript implementation.
rg -n 'func \(m model\) View|func \(m model\) transcriptView|func \(m model\) twoColumnTranscriptView|helpOverlay' internal/tui/model.go internal/tui/sidebar.go -A80 -B20

Repository: Gitlawb/zero

Length of output: 38802


Add a regression test for the two-column help overlay path.
The new cases only cover the single-column transcript render; when sidebarActive() is true, transcriptView() switches to twoColumnTranscriptView(), and that branch now composites ? separately. Add one alt-screen model with an active sidebar and helpOverlay = true so this path stays covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/keybinding_help_test.go` around lines 108 - 160, Add a
regression test for the two-column help overlay path in the existing help
overlay tests. The current coverage only exercises the single-column transcript
render, so create an alt-screen model with sidebarActive() behavior enabled and
helpOverlay set to true, then verify the overlay still appears while the
surrounding chat chrome/transcript remains visible. Use the transcriptView() and
twoColumnTranscriptView() paths as the key symbols to target this branch.

@gnanam1990

Copy link
Copy Markdown
Collaborator Author

Closing in favor of #421 by @N1xev — the reporter's own fix.

Both PRs converged on the same core approach (route the help through the viewportOverlay compositing path the pickers/wizards already use, instead of replacing the whole View()), but #421 is the more idiomatic version:

  • It adds helpOverlay to sidebarAvailable(), so the overlay suppresses the sidebar and renders full-width exactly like the model picker, provider wizard, and suggestions popups — which is precisely the behavior the help overlay hides the whole screen #419 asked for ("like the other popups").
  • It drops the now-dead height parameter from renderKeybindingHelpOverlay.

Thanks @N1xev for diagnosing the layering root cause and fixing it. The one thing this PR carried that #421 doesn't is a regression test for the composite-not-replace behavior — I've left those two tests as a suggestion on #421 so the fix ships guarded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

the help overlay hides the whole screen

1 participant