fix: banking shell screen rendering artifacts and transfer panic

Fix rendering issues where content from previous screens bled through
when switching between views of different heights/widths:

- Pad every line to full terminal width (ANSI-aware) so shorter lines
  overwrite leftover content from previous renders
- Track terminal height via WindowSizeMsg and pad between content and
  footer to fill the screen
- Send tea.ClearScreen on all screen transitions for height changes
- Fix panic in transfer completion when routing number is < 4 chars

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 00:50:34 +01:00
parent 86786c9d05
commit d226c32b9b
4 changed files with 113 additions and 35 deletions

View File

@@ -100,11 +100,33 @@ func formatCurrency(cents int64) string {
return fmt.Sprintf("$%s.%02d", ds, remainder)
}
// padLine pads a single line (which may contain ANSI codes) to termWidth
// using its visual width.
func padLine(line string) string {
w := lipgloss.Width(line)
if w >= termWidth {
return line
}
return line + strings.Repeat(" ", termWidth-w)
}
// padLines pads every line in a multi-line string to termWidth so that
// shorter lines fully overwrite previous content in the terminal.
func padLines(s string) string {
lines := strings.Split(s, "\n")
for i, line := range lines {
lines[i] = padLine(line)
}
return strings.Join(lines, "\n")
}
// screenFrame wraps content in the persistent header and footer.
func screenFrame(bankName, terminalID, region, content string) string {
// The height parameter is used to pad the output to fill the terminal,
// preventing leftover lines from previous renders bleeding through.
func screenFrame(bankName, terminalID, region, content string, height int) string {
var b strings.Builder
// Header.
// Header (4 lines).
b.WriteString(divider())
b.WriteString("\n")
b.WriteString(centerText(bankName + " FEDERAL RESERVE SYSTEM"))
@@ -117,12 +139,29 @@ func screenFrame(bankName, terminalID, region, content string) string {
// Content.
b.WriteString(content)
// Footer.
// Pad with blank lines between content and footer so the footer
// stays at the bottom and the total output fills the terminal height.
if height > 0 {
const headerLines = 4
const footerLines = 2
// strings.Count gives newlines; add 1 for the line after the last \n.
contentLines := strings.Count(content, "\n") + 1
used := headerLines + contentLines + footerLines
blankLine := strings.Repeat(" ", termWidth)
for i := used; i < height; i++ {
b.WriteString(blankLine)
b.WriteString("\n")
}
}
// Footer (2 lines).
b.WriteString("\n")
b.WriteString(divider())
b.WriteString("\n")
footer := fmt.Sprintf(" TERMINAL: %s | REGION: %s | ENCRYPTED SESSION ACTIVE", terminalID, region)
b.WriteString(dimStyle.Render(padRight(footer, termWidth)))
return b.String()
// Pad every line to full terminal width so shorter lines overwrite
// leftover content from previous renders.
return padLines(b.String())
}