From 011a00eac231b78d0fe8c14e0407451e565d7d25 Mon Sep 17 00:00:00 2001 From: Jeremy Smart Date: Thu, 25 Jun 2026 14:15:38 -0400 Subject: [PATCH] fix click error --- .../client/screens/localmoveinput_test.go | 32 +++++++++++++++++++ internal/client/screens/puzzle.go | 6 ++-- internal/client/screens/replay.go | 2 ++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/internal/client/screens/localmoveinput_test.go b/internal/client/screens/localmoveinput_test.go index 9f581fb..23bb047 100644 --- a/internal/client/screens/localmoveinput_test.go +++ b/internal/client/screens/localmoveinput_test.go @@ -167,6 +167,38 @@ func TestLocalMoveInput_HandleMsg_MouseClick_ConvertsToSAN(t *testing.T) { } } +func TestLocalMoveInput_HandleMsg_MouseClick_AccountsForBoardTopY(t *testing.T) { + // Screens that draw a title above the board (puzzle, replay-branch) render + // the board starting at screen row 2. The click mapping must subtract that + // offset, otherwise every click lands one or more ranks too high. + game := chess.NewGame() + board := render.NewBoard(game.Position(), false) + li := NewLocalMoveInput(false) + li.SetBoardOriginY(2) + + // e2 is at y=18 when the board top is row 0; with a top offset of 2 the same + // square is drawn at y=20. + msg := tea.MouseMsg{X: 26, Y: 20, Action: tea.MouseActionPress, Button: tea.MouseButtonLeft} + if _, handled, _ := li.HandleMsg(msg, board, game); !handled { + t.Fatalf("expected handled=true for click on piece") + } + if !li.hasSelected { + t.Fatalf("expected hasSelected=true after clicking own piece at the offset board") + } + if li.selectedSq != chess.E2 { + t.Fatalf("expected selectedSq=E2, got %v", li.selectedSq) + } + + // Regression guard: the un-offset coordinate (y=18) must no longer resolve to + // E2 when the board is shifted down — it maps two screen rows higher. + li2 := NewLocalMoveInput(false) + li2.SetBoardOriginY(2) + stale := tea.MouseMsg{X: 26, Y: 18, Action: tea.MouseActionPress, Button: tea.MouseButtonLeft} + if _, _, _ = li2.HandleMsg(stale, board, game); li2.hasSelected && li2.selectedSq == chess.E2 { + t.Fatalf("y=18 should not select E2 when board top is offset by 2") + } +} + func TestLocalMoveInput_HandleMsg_FlippedBoard_MapsSquaresCorrectly(t *testing.T) { game := chess.NewGame() board := render.NewBoard(game.Position(), true) diff --git a/internal/client/screens/puzzle.go b/internal/client/screens/puzzle.go index 1654436..c79ae62 100644 --- a/internal/client/screens/puzzle.go +++ b/internal/client/screens/puzzle.go @@ -516,8 +516,10 @@ func (m *PuzzleModel) View() string { var sb strings.Builder sb.WriteString(puzzleTitleStyle.Render("Puzzle Mode")) sb.WriteString("\n\n") - // Title on line 0, blank on line 1, so the board's top cell is on line 2. - m.input.SetBoardOriginY(2) + // Title on line 0, blank on line 1, board starts at line 2. + if m.input != nil { + m.input.SetBoardOriginY(2) + } boardView := m.board.View() right := m.rightPanel() sb.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, boardView, " ", right)) diff --git a/internal/client/screens/replay.go b/internal/client/screens/replay.go index 3100486..17c0a3d 100644 --- a/internal/client/screens/replay.go +++ b/internal/client/screens/replay.go @@ -426,6 +426,8 @@ func (m *ReplayModel) View() string { if m.atBranchTip() { inputY := 2 + m.board.CellRows()*8 + 2 m.input.SetPromoPopupY(inputY) + // Title on line 0, blank on line 1, board starts at line 2. + m.input.SetBoardOriginY(2) sb.WriteString(m.input.View(m.board, m.branchGame)) } else { sb.WriteString(replayStepStyle.Render(" (navigate to tip to play)"))