diff --git a/internal/editor/editor.go b/internal/editor/editor.go index 0732eda..a5f3c00 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -1709,10 +1709,16 @@ func (m Model) update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } if m.viewMode { - // Track hover state for checklist/embed visual feedback. - hoverY := m.viewport.YOffset() + msg.Y - idx := m.blockIndexAtLine(hoverY) + // Track hover state for checklist/embed visual feedback. Drop + // hover when the cursor is in the centered-column margin so + // the highlight matches click activation bounds. + leftPad, contentWidth := m.viewContentXRange() oldHover := m.hoverBlock + idx := -1 + if msg.X >= leftPad && msg.X < leftPad+contentWidth { + hoverY := m.viewport.YOffset() + msg.Y + idx = m.blockIndexAtLine(hoverY) + } m.hoverBlock = idx // Re-render if hovering over a different interactive block. @@ -1765,6 +1771,14 @@ func (m Model) update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } if m.viewMode { + // Reject clicks that land in the centered-column margin so + // stray clicks outside the rendered block content don't fire + // checklist toggles or embed opens. + leftPad, contentWidth := m.viewContentXRange() + if msg.X < leftPad || msg.X >= leftPad+contentWidth { + m.hoverBlock = -1 + return m, nil + } // Track hover state for checklist visual feedback. hoverY := m.viewport.YOffset() + msg.Y idx := m.blockIndexAtLine(hoverY) @@ -3164,22 +3178,28 @@ func (m *Model) renderAllBlocks() string { return strings.Join(parts, "\n") } -// renderViewContent builds the full view-mode content: centered, max-width, -// with generous spacing for a clean reading experience. -func (m Model) renderViewContent() string { - contentWidth := viewMaxWidth +// viewContentXRange returns the [leftPad, leftPad+contentWidth) X range of +// the centered view-mode content column. Click and hover handlers use this +// to reject coordinates that fall in the surrounding margin. +func (m Model) viewContentXRange() (leftPad, contentWidth int) { + contentWidth = viewMaxWidth if m.width < contentWidth { - contentWidth = m.width - 4 // leave some margin even on small terms + contentWidth = m.width - 4 if contentWidth < 20 { contentWidth = 20 } } - - // Horizontal padding to center the content column. - leftPad := (m.width - contentWidth) / 2 + leftPad = (m.width - contentWidth) / 2 if leftPad < 0 { leftPad = 0 } + return leftPad, contentWidth +} + +// renderViewContent builds the full view-mode content: centered, max-width, +// with generous spacing for a clean reading experience. +func (m Model) renderViewContent() string { + leftPad, contentWidth := m.viewContentXRange() padStr := strings.Repeat(" ", leftPad) var parts []string diff --git a/internal/editor/editor_test.go b/internal/editor/editor_test.go index 58607d1..4604815 100644 --- a/internal/editor/editor_test.go +++ b/internal/editor/editor_test.go @@ -2030,5 +2030,46 @@ func TestBlockIndexAtLineReturnsCorrectBlock(t *testing.T) { } } +func TestMouseClickInLeftMarginDoesNotToggle(t *testing.T) { + content := "- [ ] buy milk" + m := New(Config{Title: "test", Content: content}) + + updated, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 24}) + m = updated.(Model) + updated, _ = m.Update(tea.KeyPressMsg{Code: 'r', Mod: tea.ModCtrl}) + m = updated.(Model) + if !m.viewMode { + t.Fatal("expected view mode") + } + + // width=80, contentWidth=72, leftPad=4 — X=1 falls in the left margin. + clickY := m.blockLineOffsets[0] - m.viewport.YOffset() + updated, _ = m.Update(tea.MouseClickMsg{X: 1, Y: clickY, Button: tea.MouseLeft}) + m = updated.(Model) + + if m.blocks[0].Checked { + t.Fatal("click in left margin should not toggle checklist") + } +} + +func TestMouseClickInRightMarginDoesNotToggle(t *testing.T) { + content := "- [ ] buy milk" + m := New(Config{Title: "test", Content: content}) + + updated, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 24}) + m = updated.(Model) + updated, _ = m.Update(tea.KeyPressMsg{Code: 'r', Mod: tea.ModCtrl}) + m = updated.(Model) + + // leftPad=4, contentWidth=72 → content range [4,76). X=78 is right margin. + clickY := m.blockLineOffsets[0] - m.viewport.YOffset() + updated, _ = m.Update(tea.MouseClickMsg{X: 78, Y: clickY, Button: tea.MouseLeft}) + m = updated.(Model) + + if m.blocks[0].Checked { + t.Fatal("click in right margin should not toggle checklist") + } +} + // Verify strings import is used. var _ = strings.Contains