mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-08 01:09:46 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5746424272 |
No files matched your search
@@ -133,6 +133,18 @@ func (m model) renderKeybindingHelpOverlay(width int, height int) string {
|
||||
return centerRenderedBlock(block, width)
|
||||
}
|
||||
|
||||
// keybindingHelpOverlay returns the `?` shortcut overlay as a centered block to
|
||||
// COMPOSITE over the transcript — the same viewport-overlay path the model
|
||||
// picker and suggestions use — or "" when it is not open. Rendering it this way
|
||||
// (rather than replacing the whole View) keeps the chat visible behind it, so
|
||||
// `?` behaves like every other popup instead of blanking the screen (#419).
|
||||
func (m model) keybindingHelpOverlay(width int) string {
|
||||
if !m.helpOverlay {
|
||||
return ""
|
||||
}
|
||||
return m.renderKeybindingHelpOverlay(width, m.height)
|
||||
}
|
||||
|
||||
// keybindingHelpOverlayWidth picks the overlay width: wide enough that the
|
||||
// descriptions don't truncate next to the key column, capped, and never wider
|
||||
// than the terminal.
|
||||
|
||||
@@ -105,3 +105,56 @@ func TestKeybindingGroupsAreWellFormed(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #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)
|
||||
}
|
||||
}
|
||||
@@ -1068,7 +1068,7 @@ func (m model) updateModel(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m = m.disarmCancelConfirmation()
|
||||
}
|
||||
// The `?` help overlay is modal: `?`, Esc, q, or Enter close it; every
|
||||
// other key is swallowed so nothing types into the hidden composer.
|
||||
// other key is swallowed so nothing types into the composer behind it.
|
||||
if m.helpOverlay {
|
||||
if keyText(msg) == "?" || keyText(msg) == "q" || keyIs(msg, tea.KeyEsc) || keyIs(msg, tea.KeyEnter) || keyCtrl(msg, 'c') {
|
||||
m.helpOverlay = false
|
||||
@@ -2118,8 +2118,6 @@ func (m model) View() tea.View {
|
||||
var content string
|
||||
if m.setup.visible {
|
||||
content = m.setupView(chatWidth(m.width))
|
||||
} else if m.helpOverlay {
|
||||
content = m.renderKeybindingHelpOverlay(chatWidth(m.width), m.height)
|
||||
} else if m.transcriptDetailed {
|
||||
content = m.detailedTranscriptView()
|
||||
} else {
|
||||
@@ -2194,6 +2192,7 @@ func (m model) transcriptView() string {
|
||||
return body + footer
|
||||
}
|
||||
|
||||
helpOverlay := m.keybindingHelpOverlay(width)
|
||||
suggestionOverlay := m.suggestionOverlay(width)
|
||||
providerOverlay := m.providerWizardOverlay(width)
|
||||
mcpAddOverlay := m.mcpAddWizardOverlay(width)
|
||||
@@ -2201,6 +2200,8 @@ func (m model) transcriptView() string {
|
||||
pickerOverlay := m.pickerOverlay(width)
|
||||
viewportOverlay := ""
|
||||
switch {
|
||||
case helpOverlay != "":
|
||||
viewportOverlay = helpOverlay
|
||||
case providerOverlay != "":
|
||||
viewportOverlay = providerOverlay
|
||||
case mcpAddOverlay != "":
|
||||
@@ -2262,6 +2263,11 @@ func (m model) twoColumnTranscriptView() string {
|
||||
if m.transcriptEmpty() && !m.pending {
|
||||
overlayForViewport = ""
|
||||
}
|
||||
// The `?` help overlay composites over the chat like the pickers — set it
|
||||
// after the empty-transcript clear so a user-toggled overlay always shows.
|
||||
if helpOverlay := m.keybindingHelpOverlay(width); helpOverlay != "" {
|
||||
overlayForViewport = helpOverlay
|
||||
}
|
||||
|
||||
header := m.pinnedTitleBar(width)
|
||||
chatBlock := viewLines(m.scrollableTranscriptItemsView(header, bodyItems, footer, width, overlayForViewport))
|
||||
|
||||
Reference in new issue
Block a user