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:
@@ -33,6 +33,7 @@ type model struct {
|
||||
state *bankState
|
||||
screen screen
|
||||
quitting bool
|
||||
height int
|
||||
|
||||
login loginModel
|
||||
menu menuModel
|
||||
@@ -76,8 +77,12 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
if keyMsg.Type == tea.KeyCtrlC {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
case tea.KeyMsg:
|
||||
if msg.Type == tea.KeyCtrlC {
|
||||
m.quitting = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
@@ -130,7 +135,7 @@ func (m *model) View() string {
|
||||
content = m.admin.View()
|
||||
}
|
||||
|
||||
return screenFrame(m.bankName, m.terminalID, m.region, content)
|
||||
return screenFrame(m.bankName, m.terminalID, m.region, content, m.height)
|
||||
}
|
||||
|
||||
// --- Screen update handlers ---
|
||||
@@ -142,8 +147,8 @@ func (m *model) updateLogin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if m.login.stage == 2 {
|
||||
// Login always succeeds — this is a honeypot.
|
||||
logCmd := logAction(m.sess, fmt.Sprintf("LOGIN acct=%s", m.login.accountNum), "ACCESS GRANTED")
|
||||
m.goToMenu()
|
||||
return m, tea.Batch(cmd, logCmd)
|
||||
clearCmd := m.goToMenu()
|
||||
return m, tea.Batch(cmd, logCmd, clearCmd)
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
@@ -158,36 +163,36 @@ func (m *model) updateMenu(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case "1":
|
||||
m.screen = screenAccountSummary
|
||||
m.summary = newAccountSummaryModel(m.state.Accounts)
|
||||
return m, logAction(m.sess, "MENU 1", "ACCOUNT SUMMARY")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 1", "ACCOUNT SUMMARY"))
|
||||
case "2":
|
||||
m.screen = screenAccountDetail
|
||||
m.detail = newAccountDetailModel(m.state.Accounts, m.state.Transactions)
|
||||
return m, logAction(m.sess, "MENU 2", "ACCOUNT DETAIL")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 2", "ACCOUNT DETAIL"))
|
||||
case "3":
|
||||
m.screen = screenTransfer
|
||||
m.transfer = newTransferModel()
|
||||
return m, logAction(m.sess, "MENU 3", "WIRE TRANSFER")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 3", "WIRE TRANSFER"))
|
||||
case "4":
|
||||
m.screen = screenHistory
|
||||
m.history = newHistoryModel(m.state.Accounts, m.state.Transactions)
|
||||
return m, logAction(m.sess, "MENU 4", "TRANSACTION HISTORY")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 4", "TRANSACTION HISTORY"))
|
||||
case "5":
|
||||
m.screen = screenMessages
|
||||
m.messages = newMessagesModel(m.state.Messages)
|
||||
return m, logAction(m.sess, "MENU 5", "SECURE MESSAGES")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 5", "SECURE MESSAGES"))
|
||||
case "6":
|
||||
m.screen = screenChangePin
|
||||
m.pinInput = ""
|
||||
m.pinStage = 0
|
||||
m.pinMessage = ""
|
||||
return m, logAction(m.sess, "MENU 6", "CHANGE PIN")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "MENU 6", "CHANGE PIN"))
|
||||
case "7":
|
||||
m.quitting = true
|
||||
return m, tea.Batch(logAction(m.sess, "LOGOUT", "SESSION ENDED"), tea.Quit)
|
||||
case "99", "admin", "ADMIN":
|
||||
m.screen = screenAdmin
|
||||
m.admin = newAdminModel()
|
||||
return m, logAction(m.sess, "ADMIN ACCESS ATTEMPT", "ADMIN SCREEN SHOWN")
|
||||
return m, tea.Batch(tea.ClearScreen, logAction(m.sess, "ADMIN ACCESS ATTEMPT", "ADMIN SCREEN SHOWN"))
|
||||
}
|
||||
// Invalid choice, reset.
|
||||
m.menu.choice = ""
|
||||
@@ -197,7 +202,7 @@ func (m *model) updateMenu(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
func (m *model) updateAccountSummary(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if _, ok := msg.(tea.KeyMsg); ok {
|
||||
m.goToMenu()
|
||||
return m, m.goToMenu()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -206,7 +211,7 @@ func (m *model) updateAccountDetail(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.detail, cmd = m.detail.Update(msg)
|
||||
if m.detail.choice == "back" {
|
||||
m.goToMenu()
|
||||
return m, tea.Batch(cmd, m.goToMenu())
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
@@ -218,8 +223,14 @@ func (m *model) updateTransfer(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// Transfer cancelled.
|
||||
if m.transfer.confirm == "cancelled" {
|
||||
m.goToMenu()
|
||||
return m, logAction(m.sess, "WIRE TRANSFER CANCELLED", "USER CANCELLED")
|
||||
clearCmd := m.goToMenu()
|
||||
return m, tea.Batch(clearCmd, logAction(m.sess, "WIRE TRANSFER CANCELLED", "USER CANCELLED"))
|
||||
}
|
||||
|
||||
// Clear screen when transfer steps change content height significantly
|
||||
// (e.g. confirm→authcode, fields→confirm, authcode→complete).
|
||||
if m.transfer.step != prevStep {
|
||||
cmd = tea.Batch(cmd, tea.ClearScreen)
|
||||
}
|
||||
|
||||
// Transfer completed — log it.
|
||||
@@ -234,7 +245,7 @@ func (m *model) updateTransfer(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Completed screen → any key goes back.
|
||||
if m.transfer.step == transferStepComplete {
|
||||
if _, ok := msg.(tea.KeyMsg); ok && prevStep == transferStepComplete {
|
||||
m.goToMenu()
|
||||
return m, tea.Batch(cmd, m.goToMenu())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +256,7 @@ func (m *model) updateHistory(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.history, cmd = m.history.Update(msg)
|
||||
if m.history.choice == "back" {
|
||||
m.goToMenu()
|
||||
return m, tea.Batch(cmd, m.goToMenu())
|
||||
}
|
||||
return m, cmd
|
||||
}
|
||||
@@ -254,8 +265,7 @@ func (m *model) updateMessages(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.messages, cmd = m.messages.Update(msg)
|
||||
if m.messages.choice == "back" {
|
||||
m.goToMenu()
|
||||
return m, cmd
|
||||
return m, tea.Batch(cmd, m.goToMenu())
|
||||
}
|
||||
// Log when viewing a message.
|
||||
if m.messages.viewing >= 0 {
|
||||
@@ -274,8 +284,7 @@ func (m *model) updateChangePin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
if m.pinStage == 3 {
|
||||
m.goToMenu()
|
||||
return m, nil
|
||||
return m, m.goToMenu()
|
||||
}
|
||||
|
||||
switch keyMsg.Type {
|
||||
@@ -302,8 +311,7 @@ func (m *model) updateChangePin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.pinStage = 1
|
||||
}
|
||||
case tea.KeyEscape:
|
||||
m.goToMenu()
|
||||
return m, nil
|
||||
return m, m.goToMenu()
|
||||
case tea.KeyBackspace:
|
||||
if len(m.pinInput) > 0 {
|
||||
m.pinInput = m.pinInput[:len(m.pinInput)-1]
|
||||
@@ -367,8 +375,7 @@ func (m *model) updateAdmin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// Check for ESC before delegating.
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok && keyMsg.Type == tea.KeyEscape && !m.admin.locked {
|
||||
m.goToMenu()
|
||||
return m, nil
|
||||
return m, m.goToMenu()
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
@@ -383,7 +390,7 @@ func (m *model) updateAdmin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
// Log lockout.
|
||||
if m.admin.locked && !prevLocked {
|
||||
cmd = tea.Batch(cmd, logAction(m.sess,
|
||||
cmd = tea.Batch(cmd, tea.ClearScreen, logAction(m.sess,
|
||||
"ADMIN LOCKOUT",
|
||||
"TERMINAL LOCKED - INCIDENT LOGGED"))
|
||||
}
|
||||
@@ -391,14 +398,14 @@ func (m *model) updateAdmin(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// If locked and any key pressed, go back.
|
||||
if m.admin.locked {
|
||||
if _, ok := msg.(tea.KeyMsg); ok && prevLocked {
|
||||
m.goToMenu()
|
||||
return m, tea.Batch(cmd, m.goToMenu())
|
||||
}
|
||||
}
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m *model) goToMenu() {
|
||||
func (m *model) goToMenu() tea.Cmd {
|
||||
unread := 0
|
||||
for _, msg := range m.state.Messages {
|
||||
if msg.Unread {
|
||||
@@ -407,6 +414,7 @@ func (m *model) goToMenu() {
|
||||
}
|
||||
m.screen = screenMenu
|
||||
m.menu = newMenuModel(m.bankName, unread)
|
||||
return tea.ClearScreen
|
||||
}
|
||||
|
||||
// logAction returns a tea.Cmd that logs an action to the session store.
|
||||
|
||||
Reference in New Issue
Block a user