Skip to content

Commit 5913ae9

Browse files
maxbeizerCopilot
andcommitted
fix: pin footer to bottom of terminal across all views
- Use lipgloss.PlaceVertical to pad body content to fill available height, pushing the powerline footer to the last terminal line - Reserve 2 extra lines in all view height calculations for footer (Height-4 → Height-6 for panel views, Height-8 → Height-10 for viewport views) - Footer now visible in mission, kanban, active, and all other views Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d314e82 commit 5913ae9

3 files changed

Lines changed: 40 additions & 36 deletions

File tree

internal/tui/helpers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func (m *Model) updateSplitLayout() {
8585
m.taskList.SetSize(m.ctx.Width, m.ctx.Height)
8686
m.taskList.SetSplitMode(false)
8787
}
88-
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-4)
88+
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-6)
8989
}
9090

9191
// updateFooterHints updates footer hints based on current view mode and state

internal/tui/keyhandlers.go

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (m Model) handleListKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
117117
case "esc":
118118
m.viewMode = ViewModeMission
119119
m.mission.SetSessions(m.visibleSessions())
120-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
120+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
121121
case "j", "down":
122122
m.taskList.MoveCursor(1)
123123
case "k", "up":
@@ -208,17 +208,17 @@ func (m Model) handleListKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
208208
case "K":
209209
m.viewMode = ViewModeKanban
210210
m.kanban.SetSessions(m.visibleSessions())
211-
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-4)
211+
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-6)
212212
return m, nil
213213
case "M":
214214
m.viewMode = ViewModeMission
215215
m.mission.SetSessions(m.visibleSessions())
216-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
216+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
217217
return m, nil
218218
case "A":
219219
m.viewMode = ViewModeActive
220220
m.activeView.SetSessions(m.visibleSessions())
221-
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-4)
221+
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-6)
222222
return m, nil
223223
case "!":
224224
return m, m.openSessionRepo()
@@ -262,7 +262,7 @@ func (m Model) handleDetailKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
262262
case "esc":
263263
m.viewMode = ViewModeMission
264264
m.mission.SetSessions(m.visibleSessions())
265-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
265+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
266266
case "l":
267267
session := m.taskList.SelectedTask()
268268
if session != nil {
@@ -329,7 +329,7 @@ func (m Model) handleDiffKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
329329
case "esc":
330330
m.viewMode = ViewModeMission
331331
m.mission.SetSessions(m.visibleSessions())
332-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
332+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
333333
return m, nil
334334
}
335335

@@ -345,7 +345,7 @@ func (m Model) handleGitActivityKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
345345
case "esc":
346346
m.viewMode = ViewModeMission
347347
m.mission.SetSessions(m.visibleSessions())
348-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
348+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
349349
return m, nil
350350
case "r":
351351
// Manual refresh
@@ -368,7 +368,7 @@ func (m Model) handleLogKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
368368
case "esc":
369369
m.viewMode = ViewModeMission
370370
m.mission.SetSessions(m.visibleSessions())
371-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
371+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
372372
m.logView.SetLive(false)
373373
m.logView.SetFollowMode(false)
374374
m.showConversation = false
@@ -445,7 +445,7 @@ func (m Model) handleKanbanKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
445445
case "esc", "K":
446446
m.viewMode = ViewModeMission
447447
m.mission.SetSessions(m.visibleSessions())
448-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
448+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
449449
case "h", "left":
450450
m.kanban.MoveColumn(-1)
451451
case "l", "right":
@@ -494,7 +494,7 @@ func (m Model) handleKanbanKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
494494
case "A":
495495
m.viewMode = ViewModeActive
496496
m.activeView.SetSessions(m.visibleSessions())
497-
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-4)
497+
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-6)
498498
}
499499
return m, nil
500500
}
@@ -561,11 +561,11 @@ func (m Model) handleMissionKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
561561
case "K":
562562
m.viewMode = ViewModeKanban
563563
m.kanban.SetSessions(m.visibleSessions())
564-
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-4)
564+
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-6)
565565
case "A":
566566
m.viewMode = ViewModeActive
567567
m.activeView.SetSessions(m.visibleSessions())
568-
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-4)
568+
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-6)
569569
case "r":
570570
return m, m.fetchTasks
571571
}
@@ -578,7 +578,7 @@ func (m Model) handleActiveKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
578578
case "esc", "A":
579579
m.viewMode = ViewModeMission
580580
m.mission.SetSessions(m.visibleSessions())
581-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
581+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
582582
case "j", "down":
583583
m.activeView.MoveCursor(1)
584584
case "k", "up":
@@ -623,11 +623,11 @@ func (m Model) handleActiveKeys(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
623623
case "K":
624624
m.viewMode = ViewModeKanban
625625
m.kanban.SetSessions(m.visibleSessions())
626-
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-4)
626+
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-6)
627627
case "M":
628628
m.viewMode = ViewModeMission
629629
m.mission.SetSessions(m.visibleSessions())
630-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
630+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
631631
}
632632
return m, nil
633633
}

internal/tui/ui.go

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -223,14 +223,15 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
223223
return m, nil
224224

225225
case resizeDebouncedMsg:
226-
m.logView.SetSize(m.ctx.Width-4, m.ctx.Height-8)
227-
m.toolTimeline.SetSize(m.ctx.Width-4, m.ctx.Height-8)
228-
m.conversationView.SetSize(m.ctx.Width-4, m.ctx.Height-8)
229-
m.diffView.SetSize(m.ctx.Width-4, m.ctx.Height-8)
230-
m.gitActivity.SetSize(m.ctx.Width-4, m.ctx.Height-8)
231-
m.mission.SetSize(m.ctx.Width, m.ctx.Height-4)
232-
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-4)
233-
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-4)
226+
// Reserve space for header chrome (4 lines) + footer (2 lines)
227+
m.logView.SetSize(m.ctx.Width-4, m.ctx.Height-10)
228+
m.toolTimeline.SetSize(m.ctx.Width-4, m.ctx.Height-10)
229+
m.conversationView.SetSize(m.ctx.Width-4, m.ctx.Height-10)
230+
m.diffView.SetSize(m.ctx.Width-4, m.ctx.Height-10)
231+
m.gitActivity.SetSize(m.ctx.Width-4, m.ctx.Height-10)
232+
m.mission.SetSize(m.ctx.Width, m.ctx.Height-6)
233+
m.activeView.SetSize(m.ctx.Width, m.ctx.Height-6)
234+
m.kanban.SetSize(m.ctx.Width, m.ctx.Height-6)
234235
return m, nil
235236

236237
case tea.KeyPressMsg:
@@ -518,21 +519,24 @@ func (m Model) View() tea.View {
518519
searchView = searchStyle.Render(fmt.Sprintf(" 🔍 Filter: %s", queryDisplay)) + "\n"
519520
}
520521

521-
// Compute chrome heights to pin footer at bottom
522-
chromeHeight := lipgloss.Height(chrome)
522+
// Assemble content without footer
523+
body := chrome + mainView + toastView + searchView
524+
525+
// Pin footer to bottom by placing the body at the top of the full terminal
526+
// height minus the footer, then appending footer below
527+
bodyHeight := lipgloss.Height(body)
523528
footerHeight := lipgloss.Height(footerView)
524-
toastHeight := lipgloss.Height(toastView)
525-
searchHeight := lipgloss.Height(searchView)
526-
527-
// Pad mainView so footer is pinned to the bottom of the terminal
528-
mainHeight := lipgloss.Height(mainView)
529-
targetMainHeight := m.ctx.Height - chromeHeight - footerHeight - toastHeight - searchHeight
530-
if targetMainHeight > mainHeight {
531-
padding := targetMainHeight - mainHeight
532-
mainView += strings.Repeat("\n", padding)
529+
availForBody := m.ctx.Height - footerHeight
530+
if availForBody < 1 {
531+
availForBody = 1
532+
}
533+
534+
// Pad body to fill available space, pushing footer to the bottom
535+
if bodyHeight < availForBody {
536+
body = lipgloss.PlaceVertical(availForBody, lipgloss.Top, body)
533537
}
534538

535-
result := chrome + mainView + toastView + searchView + footerView
539+
result := body + footerView
536540

537541
// Overlay help panel when visible
538542
if m.help.Visible() {

0 commit comments

Comments
 (0)