detached 3d7f80e

Add spacing around dialog frames

commit
3d7f80eebbe72adafce52bc6d14899a6670dec86
author
patriceckhart · · 2026-05-22 16:53 UTC
parent
8cd8410
signature
unsigned
refs
tag: v0.1.90
3 files · +42 -2
M internal/agent/modes/dialog_frame.go +37 -0
@@ -33,6 +33,43 @@ func frameRule(th tui.Theme, width int) string {
3333 return th.FG256(th.Muted, strings.Repeat("─", width))
3434 }
3535
36+// padDialogFrame inserts breathing room between the shared dialog frame
37+// chrome and its body while keeping frameHeader/frameRule as single-row
38+// primitives for callers that need exact row accounting.
39+func padDialogFrame(lines []string) []string {
40+ if len(lines) == 0 {
41+ return lines
42+ }
43+
44+ out := append([]string(nil), lines...)
45+ if isFrameHeaderLine(out[0]) && (len(out) == 1 || strings.TrimSpace(stripANSIBytes(out[1])) != "") {
46+ out = append(out[:1], append([]string{""}, out[1:]...)...)
47+ }
48+
49+ last := len(out) - 1
50+ if last > 0 && isFrameRuleLine(out[last]) && strings.TrimSpace(stripANSIBytes(out[last-1])) != "" {
51+ out = append(out[:last], append([]string{""}, out[last:]...)...)
52+ }
53+ return out
54+}
55+
56+func isFrameHeaderLine(line string) bool {
57+ return strings.HasPrefix(stripANSIBytes(line), "── ")
58+}
59+
60+func isFrameRuleLine(line string) bool {
61+ plain := stripANSIBytes(line)
62+ if plain == "" {
63+ return false
64+ }
65+ for _, r := range plain {
66+ if r != '─' {
67+ return false
68+ }
69+ }
70+ return true
71+}
72+
3673 // frameHeaderColor is like frameHeader but renders in a caller-supplied
3774 // 256-color code. Used by the update-available banner which wants a
3875 // yellow accent on the rules and title.
@@ -33,6 +33,43 @@ func frameRule(th tui.Theme, width int) string {
33 return th.FG256(th.Muted, strings.Repeat("─", width))33 return th.FG256(th.Muted, strings.Repeat("─", width))
34 }34 }
35 35
36+// padDialogFrame inserts breathing room between the shared dialog frame
37+// chrome and its body while keeping frameHeader/frameRule as single-row
38+// primitives for callers that need exact row accounting.
39+func padDialogFrame(lines []string) []string {
40+ if len(lines) == 0 {
41+ return lines
42+ }
43+
44+ out := append([]string(nil), lines...)
45+ if isFrameHeaderLine(out[0]) && (len(out) == 1 || strings.TrimSpace(stripANSIBytes(out[1])) != "") {
46+ out = append(out[:1], append([]string{""}, out[1:]...)...)
47+ }
48+
49+ last := len(out) - 1
50+ if last > 0 && isFrameRuleLine(out[last]) && strings.TrimSpace(stripANSIBytes(out[last-1])) != "" {
51+ out = append(out[:last], append([]string{""}, out[last:]...)...)
52+ }
53+ return out
54+}
55+
56+func isFrameHeaderLine(line string) bool {
57+ return strings.HasPrefix(stripANSIBytes(line), "── ")
58+}
59+
60+func isFrameRuleLine(line string) bool {
61+ plain := stripANSIBytes(line)
62+ if plain == "" {
63+ return false
64+ }
65+ for _, r := range plain {
66+ if r != '─' {
67+ return false
68+ }
69+ }
70+ return true
71+}
72+
36 // frameHeaderColor is like frameHeader but renders in a caller-supplied73 // frameHeaderColor is like frameHeader but renders in a caller-supplied
37 // 256-color code. Used by the update-available banner which wants a74 // 256-color code. Used by the update-available banner which wants a
38 // yellow accent on the rules and title.75 // yellow accent on the rules and title.
M internal/agent/modes/help.go +2 -2
@@ -69,7 +69,7 @@ func renderHelpBlock(th tui.Theme, width int) []string {
6969 }
7070
7171 var out []string
72- out = append(out, frameHeader(th, "zot help", width))
72+ out = append(out, frameHeader(th, "zot help", width), "")
7373
7474 // commands section
7575 out = append(out, tui.Bold("slash commands:"))
@@ -87,6 +87,6 @@ func renderHelpBlock(th tui.Theme, width int) []string {
8787 th.FG256(th.Muted, k[1])))
8888 }
8989
90- out = append(out, frameRule(th, width), "")
90+ out = append(out, "", frameRule(th, width), "")
9191 return out
9292 }
@@ -69,7 +69,7 @@ func renderHelpBlock(th tui.Theme, width int) []string {
69 }69 }
70 70
71 var out []string71 var out []string
72- out = append(out, frameHeader(th, "zot help", width))72+ out = append(out, frameHeader(th, "zot help", width), "")
73 73
74 // commands section74 // commands section
75 out = append(out, tui.Bold("slash commands:"))75 out = append(out, tui.Bold("slash commands:"))
@@ -87,6 +87,6 @@ func renderHelpBlock(th tui.Theme, width int) []string {
87 th.FG256(th.Muted, k[1])))87 th.FG256(th.Muted, k[1])))
88 }88 }
89 89
90- out = append(out, frameRule(th, width), "")90+ out = append(out, "", frameRule(th, width), "")
91 return out91 return out
92 }92 }
M internal/agent/modes/interactive.go +3 -0
@@ -931,6 +931,9 @@ func (i *Interactive) redraw() {
931931 case i.extPanel.Active():
932932 dialog = i.extPanel.Render(i.cfg.Theme, cols)
933933 }
934+ if len(dialog) > 0 {
935+ dialog = padDialogFrame(dialog)
936+ }
934937
935938 // Slash-command autocomplete: popup above the status line, only
936939 // when the editor starts with "/" and no dialog is already open.
@@ -931,6 +931,9 @@ func (i *Interactive) redraw() {
931 case i.extPanel.Active():931 case i.extPanel.Active():
932 dialog = i.extPanel.Render(i.cfg.Theme, cols)932 dialog = i.extPanel.Render(i.cfg.Theme, cols)
933 }933 }
934+ if len(dialog) > 0 {
935+ dialog = padDialogFrame(dialog)
936+ }
934 937
935 // Slash-command autocomplete: popup above the status line, only938 // Slash-command autocomplete: popup above the status line, only
936 // when the editor starts with "/" and no dialog is already open.939 // when the editor starts with "/" and no dialog is already open.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close