From 1c99f48aa82fac6f2273068bccfa29ad93c05c3b Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Sun, 24 May 2026 22:34:28 +0100 Subject: [PATCH 01/15] wip --- go.mod | 3 +- internal/git/client.go | 47 +++++++++++++++++++ internal/ui/commit_view.go | 24 +++++++--- internal/ui/model.go | 94 +++++++++++++++++++++++++++++++++++++- 4 files changed, 160 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 64b1d5b..36fde96 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,11 @@ require ( github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/huh v1.0.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 + github.com/creack/pty v1.1.24 github.com/earthboundkid/versioninfo/v2 v2.24.1 github.com/go-playground/validator/v10 v10.30.2 github.com/joho/godotenv v1.5.1 + github.com/leodido/go-conventionalcommits v0.12.0 github.com/spf13/cobra v1.10.2 github.com/stretchr/testify v1.11.1 golang.org/x/mod v0.36.0 @@ -47,7 +49,6 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-conventionalcommits v0.12.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/mattn/go-localereader v0.0.1 // indirect diff --git a/internal/git/client.go b/internal/git/client.go index 7e412ce..5565b90 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -1,12 +1,15 @@ package git import ( + "bytes" "fmt" + "io" "os" "os/exec" "strings" "github.com/cockroachdb/errors" + "github.com/creack/pty" ) type Client struct { @@ -93,6 +96,50 @@ func (c *Client) Diff() (string, error) { return string(result), nil } +func (c *Client) DiffWithColor() (string, error) { + args := []string{ + "--no-pager", + "diff", + "--color=always", + "--no-ext-diff", + "--no-textconv", + } + + if c.addAll { + args = append(args, "HEAD") + } else { + args = append(args, "--staged") + } + + args = append(args, + "--diff-filter=ACMRTUXBD", + "--", // separates options from pathspecs + ".", // include everything under the repo root + ":(exclude)*-lock.*", // package-lock.json, pnpm-lock.yaml, etc. + ":(exclude)*.lock", // yarn.lock, poetry.lock, Cargo.lock, etc. + ":(exclude)**/build/**", + ":(exclude)**/dist/**", + ":(exclude)**/target/**", + ":(exclude)**/out/**", + ":(exclude)go.sum", + ) + + cmd := exec.Command("git", args...) + + ptmx, err := pty.Start(cmd) + if err != nil { + // fallback to plain pipe if pty fails + return c.Diff() + } + defer ptmx.Close() + + var buf bytes.Buffer + _, _ = io.Copy(&buf, ptmx) + _ = cmd.Wait() + + return buf.String(), nil +} + func (c *Client) prepareCommitMessage(message string, skipCI bool) string { if skipCI && !strings.Contains(message, "[skip ci]") { lines := strings.SplitN(message, "\n", 2) diff --git a/internal/ui/commit_view.go b/internal/ui/commit_view.go index e5590bf..7e7495e 100644 --- a/internal/ui/commit_view.go +++ b/internal/ui/commit_view.go @@ -135,7 +135,6 @@ func (m *commitViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.preview = !m.preview return m, nil - } if m.preview { @@ -205,6 +204,19 @@ func (m *commitViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + case tea.WindowSizeMsg: + // Adjust textarea height + helpTextHeight := lipgloss.Height(m.helpTextView()) + borderHeight := m.boxStyle.GetVerticalBorderSize() + paddingHeight := m.boxStyle.GetVerticalPadding() + remainingHeight := msg.Height - helpTextHeight - borderHeight - paddingHeight + + m.textarea.SetHeight(max(1, remainingHeight)) + + // Adjust viewport for preview + m.viewport.Width = msg.Width - m.boxStyle.GetHorizontalBorderSize() - m.boxStyle.GetHorizontalPadding() + m.viewport.Height = max(1, remainingHeight) + case errMsg: // Use errMsg from model.go return m, tea.Quit } @@ -260,21 +272,21 @@ func (m *commitViewModel) helpTextView() string { } if m.preview { - return fmt.Sprintf("%s:commit %s:clear %s:undo %s:regen %s:editor %s:back", + return fmt.Sprintf("%s:commit %s:clear %s:regen %s:editor %s:diff %s:back", BoldYellow.Render("CTRL+A"), - Strikethrough.Render("CTRL+K"), - Strikethrough.Render("CTRL+Z"), + BoldYellow.Render("CTRL+K"), BoldYellow.Render("CTRL+R"), BoldYellow.Render("CTRL+P"), + BoldYellow.Render("CTRL+D"), BoldYellow.Render("ESC")) } - return fmt.Sprintf("%s:commit %s:clear %s:undo %s:regen %s:preview %s:abort", + return fmt.Sprintf("%s:commit %s:clear %s:regen %s:preview %s:diff %s:abort", BoldYellow.Render("CTRL+A"), BoldYellow.Render("CTRL+K"), - BoldYellow.Render("CTRL+Z"), BoldYellow.Render("CTRL+R"), BoldYellow.Render("CTRL+P"), + BoldYellow.Render("CTRL+D"), BoldYellow.Render("ESC")) } diff --git a/internal/ui/model.go b/internal/ui/model.go index b920b5d..b7c0e3d 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -7,10 +7,13 @@ import ( "time" "github.com/charmbracelet/bubbles/spinner" + "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/cockroachdb/errors" "github.com/earthboundkid/versioninfo/v2" "github.com/galactixx/stringwrap" + "github.com/rm-hull/git-commit-summary/internal/git" "github.com/rm-hull/git-commit-summary/internal/interfaces" llmprovider "github.com/rm-hull/git-commit-summary/internal/llm_provider" versionpkg "github.com/rm-hull/git-commit-summary/internal/version" @@ -59,7 +62,11 @@ type Model struct { spinner spinner.Model spinnerMessage string latestVersion string + width, height int commitView tea.Model + diffView viewport.Model + diffLoaded bool + showingDiff bool commitMessage string promptView tea.Model action Action @@ -86,6 +93,7 @@ func InitialModel( hint: hint, spinner: spinner.New(spinner.WithSpinner(spinner.MiniDot)), spinnerMessage: Magenta.Render("Checking whether a newer version exists..."), + diffView: viewport.New(72+2, 20), action: None, yolo: yolo, } @@ -104,8 +112,42 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.action = Abort return m, tea.Quit } + case tea.KeyCtrlD: + if m.state == showCommitView { + m.showingDiff = !m.showingDiff + if m.showingDiff && !m.diffLoaded { + return m, m.getDiffWithColor + } + return m, nil + } + case tea.KeyCtrlA: + if m.state == showCommitView && m.showingDiff { + _, cmd := m.commitView.Update(msg) + return m, cmd + } + case tea.KeyCtrlR: + if m.state == showCommitView && m.showingDiff { + _, cmd := m.commitView.Update(msg) + return m, cmd + } + case tea.KeyCtrlP: + if m.state == showCommitView && m.showingDiff { + m.showingDiff = false + _, cmd := m.commitView.Update(msg) + return m, cmd + } + case tea.KeyEsc: + if m.state == showCommitView && m.showingDiff { + m.showingDiff = false + return m, nil + } } + case diffColorMsg: + m.diffView.SetContent(string(msg)) + m.diffLoaded = true + return m, nil + case gitCheckMsg: if len(msg) == 0 { m.err = errors.New("no changes detected") @@ -194,6 +236,22 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.latestVersion = string(msg) m.spinnerMessage = Magenta.Render("Running git commands to determine modified files...") return m, m.checkGitStatus + + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + + // Set diffView dimensions + // Width is fixed to 72+2 for consistency with commit message constraints. + m.diffView.Width = 72 + 2 + m.diffView.Height = m.height - 3 // Account for box borders and help text + + // Propagate to commitView so it can adjust its internal textarea/viewport + var cmd tea.Cmd + if m.commitView != nil { + m.commitView, cmd = m.commitView.Update(msg) + } + return m, cmd } var cmd tea.Cmd @@ -201,7 +259,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case showSpinner: m.spinner, cmd = m.spinner.Update(msg) case showCommitView: - m.commitView, cmd = m.commitView.Update(msg) + if m.showingDiff { + m.diffView, cmd = m.diffView.Update(msg) + } else { + m.commitView, cmd = m.commitView.Update(msg) + } case showRegeneratePrompt: m.promptView, cmd = m.promptView.Update(msg) } @@ -230,6 +292,26 @@ func (m *Model) View() string { if m.commitView == nil { return m.spinner.View() + " " + m.spinnerMessage } + if m.showingDiff { + boxStyle := lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("6")). // Cyan + Padding(0, 1) + + title := " Raw diff " + titleBorder := lipgloss.RoundedBorder() + titleBorder.Top = title + strings.Repeat("─", (72+2)-lipgloss.Width(title)) + + helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", + BoldYellow.Render("CTRL+A"), + BoldYellow.Render("CTRL+R"), + BoldYellow.Render("CTRL+P"), + BoldYellow.Render("CTRL+D"), + BoldYellow.Render("ESC")) + + return boxStyle. + BorderStyle(titleBorder). + Render(m.diffView.View()) + "\n" + helpText + } return m.commitView.View() case showRegeneratePrompt: if m.commitView == nil || m.promptView == nil { @@ -253,6 +335,16 @@ func (m *Model) checkGitStatus() tea.Msg { return gitCheckMsg(modifiedFiles) } +type diffColorMsg string + +func (m *Model) getDiffWithColor() tea.Msg { + diff, err := m.gitClient.(*git.Client).DiffWithColor() + if err != nil { + return errMsg{err} + } + return diffColorMsg(diff) +} + func (m *Model) getGitDiff() tea.Msg { diff, err := m.gitClient.Diff() if err != nil { From 637f191d227719552c608cfdcd5d6a67e15d2592 Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Sun, 24 May 2026 23:17:16 +0100 Subject: [PATCH 02/15] refactor: encapsulate diff view into its own component Migrated the diff view logic from the main model into a dedicated `diffViewModel` struct to improve separation of concerns and maintainability of the Bubble Tea model. - Introduced `showDiffView` state. - Encapsulated UI rendering and message handling for the diff view. - Simplified the main `Model` update and view logic by delegating to the new component. --- internal/ui/diff_view.go | 69 ++++++++++++++++++++++++++++++++++++++ internal/ui/model.go | 72 ++++++++++++++-------------------------- 2 files changed, 94 insertions(+), 47 deletions(-) create mode 100644 internal/ui/diff_view.go diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go new file mode 100644 index 0000000..cac0a41 --- /dev/null +++ b/internal/ui/diff_view.go @@ -0,0 +1,69 @@ +package ui + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +type diffColorMsg string + +type diffViewModel struct { + viewport viewport.Model +} + +func initialDiffViewModel(width, height int) *diffViewModel { + return &diffViewModel{ + viewport: viewport.New(width, height), + } +} + +func (m *diffViewModel) Init() tea.Cmd { + return nil +} + +func (m *diffViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case diffColorMsg: + m.viewport.SetContent(string(msg)) + return m, nil + case tea.WindowSizeMsg: + m.viewport.Width = msg.Width + m.viewport.Height = msg.Height + return m, nil + } + + var cmd tea.Cmd + m.viewport, cmd = m.viewport.Update(msg) + return m, cmd +} + +func (m *diffViewModel) View() string { + boxStyle := lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("6")). // Cyan + Padding(0, 1) + + title := " Raw diff " + titleBorder := lipgloss.RoundedBorder() + + // Ensure we don't have a negative repeat count + repeatCount := (m.viewport.Width + 2) - lipgloss.Width(title) + if repeatCount < 0 { + repeatCount = 0 + } + titleBorder.Top = title + strings.Repeat("─", repeatCount) + + helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", + BoldYellow.Render("CTRL+A"), + BoldYellow.Render("CTRL+R"), + BoldYellow.Render("CTRL+P"), + BoldYellow.Render("CTRL+D"), + BoldYellow.Render("ESC")) + + return boxStyle. + BorderStyle(titleBorder). + Render(m.viewport.View()) + "\n" + helpText +} diff --git a/internal/ui/model.go b/internal/ui/model.go index b7c0e3d..2a0ab1c 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -7,9 +7,7 @@ import ( "time" "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" "github.com/cockroachdb/errors" "github.com/earthboundkid/versioninfo/v2" "github.com/galactixx/stringwrap" @@ -25,6 +23,7 @@ const ( showSpinner sessionState = iota showCommitView showRegeneratePrompt + showDiffView ) type ( @@ -64,9 +63,8 @@ type Model struct { latestVersion string width, height int commitView tea.Model - diffView viewport.Model + diffView tea.Model diffLoaded bool - showingDiff bool commitMessage string promptView tea.Model action Action @@ -93,7 +91,7 @@ func InitialModel( hint: hint, spinner: spinner.New(spinner.WithSpinner(spinner.MiniDot)), spinnerMessage: Magenta.Render("Checking whether a newer version exists..."), - diffView: viewport.New(72+2, 20), + diffView: initialDiffViewModel(72+2, 20), action: None, yolo: yolo, } @@ -114,39 +112,46 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } case tea.KeyCtrlD: if m.state == showCommitView { - m.showingDiff = !m.showingDiff - if m.showingDiff && !m.diffLoaded { + m.state = showDiffView + if !m.diffLoaded { return m, m.getDiffWithColor } return m, nil + } else if m.state == showDiffView { + m.state = showCommitView + return m, nil } case tea.KeyCtrlA: - if m.state == showCommitView && m.showingDiff { + if m.state == showCommitView { _, cmd := m.commitView.Update(msg) return m, cmd } case tea.KeyCtrlR: - if m.state == showCommitView && m.showingDiff { + if m.state == showCommitView { _, cmd := m.commitView.Update(msg) return m, cmd } case tea.KeyCtrlP: - if m.state == showCommitView && m.showingDiff { - m.showingDiff = false + if m.state == showCommitView { + // This triggers preview in commitView, but we aren't changing state here. + // However, the user might want to toggle preview while in commit view. + // The current implementation of commitView handles preview. + // If we want to keep the same behavior, we just let commitView handle it. _, cmd := m.commitView.Update(msg) return m, cmd } case tea.KeyEsc: - if m.state == showCommitView && m.showingDiff { - m.showingDiff = false + if m.state == showDiffView { + m.state = showCommitView return m, nil } } case diffColorMsg: - m.diffView.SetContent(string(msg)) m.diffLoaded = true - return m, nil + var cmd tea.Cmd + m.diffView, cmd = m.diffView.Update(msg) + return m, cmd case gitCheckMsg: if len(msg) == 0 { @@ -241,11 +246,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height - // Set diffView dimensions - // Width is fixed to 72+2 for consistency with commit message constraints. - m.diffView.Width = 72 + 2 - m.diffView.Height = m.height - 3 // Account for box borders and help text - // Propagate to commitView so it can adjust its internal textarea/viewport var cmd tea.Cmd if m.commitView != nil { @@ -259,13 +259,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case showSpinner: m.spinner, cmd = m.spinner.Update(msg) case showCommitView: - if m.showingDiff { - m.diffView, cmd = m.diffView.Update(msg) - } else { - m.commitView, cmd = m.commitView.Update(msg) - } + m.commitView, cmd = m.commitView.Update(msg) case showRegeneratePrompt: m.promptView, cmd = m.promptView.Update(msg) + case showDiffView: + m.diffView, cmd = m.diffView.Update(msg) } return m, cmd } @@ -292,32 +290,14 @@ func (m *Model) View() string { if m.commitView == nil { return m.spinner.View() + " " + m.spinnerMessage } - if m.showingDiff { - boxStyle := lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("6")). // Cyan - Padding(0, 1) - - title := " Raw diff " - titleBorder := lipgloss.RoundedBorder() - titleBorder.Top = title + strings.Repeat("─", (72+2)-lipgloss.Width(title)) - - helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", - BoldYellow.Render("CTRL+A"), - BoldYellow.Render("CTRL+R"), - BoldYellow.Render("CTRL+P"), - BoldYellow.Render("CTRL+D"), - BoldYellow.Render("ESC")) - - return boxStyle. - BorderStyle(titleBorder). - Render(m.diffView.View()) + "\n" + helpText - } return m.commitView.View() case showRegeneratePrompt: if m.commitView == nil || m.promptView == nil { return m.spinner.View() + " " + m.spinnerMessage } return m.commitView.View() + m.promptView.View() + case showDiffView: + return m.diffView.View() default: return "" } @@ -335,8 +315,6 @@ func (m *Model) checkGitStatus() tea.Msg { return gitCheckMsg(modifiedFiles) } -type diffColorMsg string - func (m *Model) getDiffWithColor() tea.Msg { diff, err := m.gitClient.(*git.Client).DiffWithColor() if err != nil { From a8030bf099ff7e77d164b924f9522274981409d6 Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Sun, 24 May 2026 23:20:41 +0100 Subject: [PATCH 03/15] style: update commit view help text style Apply strikethrough to the CTRL+K keybinding in the help text to visually indicate the action is currently unavailable or disabled. --- internal/ui/commit_view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ui/commit_view.go b/internal/ui/commit_view.go index 7e7495e..5daa802 100644 --- a/internal/ui/commit_view.go +++ b/internal/ui/commit_view.go @@ -274,7 +274,7 @@ func (m *commitViewModel) helpTextView() string { if m.preview { return fmt.Sprintf("%s:commit %s:clear %s:regen %s:editor %s:diff %s:back", BoldYellow.Render("CTRL+A"), - BoldYellow.Render("CTRL+K"), + Strikethrough.Render("CTRL+K"), BoldYellow.Render("CTRL+R"), BoldYellow.Render("CTRL+P"), BoldYellow.Render("CTRL+D"), From b0d0f448a5f82bb6d05ddb2c5844852148b0071d Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Sun, 24 May 2026 23:35:46 +0100 Subject: [PATCH 04/15] Update internal/git/client.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- internal/git/client.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/git/client.go b/internal/git/client.go index 5565b90..39c6ca1 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -135,7 +135,9 @@ func (c *Client) DiffWithColor() (string, error) { var buf bytes.Buffer _, _ = io.Copy(&buf, ptmx) - _ = cmd.Wait() + if err := cmd.Wait(); err != nil { + return "", errors.Wrap(err, "git diff failed") + } return buf.String(), nil } From 6fa85f513c0ddd27147dd8dec4df051db53118aa Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Sun, 24 May 2026 23:34:49 +0100 Subject: [PATCH 05/15] refactor: extract git diff argument builder - Consolidate duplicate diff argument construction into `diffArgs` helper - Update `diff_view` UI to mark removed shortcuts as strikethrough --- internal/git/client.go | 73 ++++++++++++++++------------------------ internal/ui/diff_view.go | 8 ++--- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/internal/git/client.go b/internal/git/client.go index 39c6ca1..acd6177 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -63,31 +63,7 @@ func (c *Client) ModifiedFiles() ([]string, error) { } func (c *Client) Diff() (string, error) { - args := []string{ - "--no-pager", - "diff", - "--no-ext-diff", - "--no-textconv", - } - - if c.addAll { - args = append(args, "HEAD") - } else { - args = append(args, "--staged") - } - - args = append(args, - "--diff-filter=ACMRTUXBD", - "--", // separates options from pathspecs - ".", // include everything under the repo root - ":(exclude)*-lock.*", // package-lock.json, pnpm-lock.yaml, etc. - ":(exclude)*.lock", // yarn.lock, poetry.lock, Cargo.lock, etc. - ":(exclude)**/build/**", - ":(exclude)**/dist/**", - ":(exclude)**/target/**", - ":(exclude)**/out/**", - ":(exclude)go.sum", - ) + args := c.diffArgs(false) result, err := exec.Command("git", args...).CombinedOutput() if err != nil { @@ -97,13 +73,38 @@ func (c *Client) Diff() (string, error) { } func (c *Client) DiffWithColor() (string, error) { + args := c.diffArgs(true) + + cmd := exec.Command("git", args...) + + ptmx, err := pty.Start(cmd) + if err != nil { + // fallback to plain pipe if pty fails + return c.Diff() + } + defer ptmx.Close() + + var buf bytes.Buffer + _, _ = io.Copy(&buf, ptmx) + if err := cmd.Wait(); err != nil { + return "", errors.Wrap(err, "git diff failed") + } + + return buf.String(), nil +} + +func (c *Client) diffArgs(color bool) []string { args := []string{ "--no-pager", "diff", - "--color=always", + } + if color { + args = append(args, "--color=always") + } + args = append(args, "--no-ext-diff", "--no-textconv", - } + ) if c.addAll { args = append(args, "HEAD") @@ -123,23 +124,7 @@ func (c *Client) DiffWithColor() (string, error) { ":(exclude)**/out/**", ":(exclude)go.sum", ) - - cmd := exec.Command("git", args...) - - ptmx, err := pty.Start(cmd) - if err != nil { - // fallback to plain pipe if pty fails - return c.Diff() - } - defer ptmx.Close() - - var buf bytes.Buffer - _, _ = io.Copy(&buf, ptmx) - if err := cmd.Wait(); err != nil { - return "", errors.Wrap(err, "git diff failed") - } - - return buf.String(), nil + return args } func (c *Client) prepareCommitMessage(message string, skipCI bool) string { diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index cac0a41..4733806 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -48,7 +48,7 @@ func (m *diffViewModel) View() string { title := " Raw diff " titleBorder := lipgloss.RoundedBorder() - + // Ensure we don't have a negative repeat count repeatCount := (m.viewport.Width + 2) - lipgloss.Width(title) if repeatCount < 0 { @@ -57,9 +57,9 @@ func (m *diffViewModel) View() string { titleBorder.Top = title + strings.Repeat("─", repeatCount) helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", - BoldYellow.Render("CTRL+A"), - BoldYellow.Render("CTRL+R"), - BoldYellow.Render("CTRL+P"), + Strikethrough.Render("CTRL+A"), + Strikethrough.Render("CTRL+R"), + Strikethrough.Render("CTRL+P"), BoldYellow.Render("CTRL+D"), BoldYellow.Render("ESC")) From 2a1a94e5d488da21aeaf5cfaaee8270a8297ccda Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 11:14:42 +0100 Subject: [PATCH 06/15] refactor: improve git diff logic and UI cleanup - Update `diffArgs` to accept an exclusion flag for better control. - Enhance error handling in `DiffWithColor` for `io.Copy` and `cmd.Wait`. - Simplify `repeatCount` calculation in `diffViewModel` using `max`. --- internal/git/client.go | 45 +++++++++++++++++++++------------------- internal/ui/diff_view.go | 5 +---- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/internal/git/client.go b/internal/git/client.go index acd6177..e1cff7a 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -63,8 +63,7 @@ func (c *Client) ModifiedFiles() ([]string, error) { } func (c *Client) Diff() (string, error) { - args := c.diffArgs(false) - + args := c.diffArgs(false, true) result, err := exec.Command("git", args...).CombinedOutput() if err != nil { return "", errors.Wrap(err, "git diff failed") @@ -73,8 +72,7 @@ func (c *Client) Diff() (string, error) { } func (c *Client) DiffWithColor() (string, error) { - args := c.diffArgs(true) - + args := c.diffArgs(true, false) cmd := exec.Command("git", args...) ptmx, err := pty.Start(cmd) @@ -82,18 +80,21 @@ func (c *Client) DiffWithColor() (string, error) { // fallback to plain pipe if pty fails return c.Diff() } - defer ptmx.Close() + defer func() { _ = ptmx.Close() }() var buf bytes.Buffer - _, _ = io.Copy(&buf, ptmx) - if err := cmd.Wait(); err != nil { - return "", errors.Wrap(err, "git diff failed") + if _, err := io.Copy(&buf, ptmx); err != nil { + return "", errors.Wrap(err, "reading git diff output failed") + } + + if err := cmd.Wait(); err != nil { + return "", errors.Wrap(err, "waiting for git diff command failed") } return buf.String(), nil } -func (c *Client) diffArgs(color bool) []string { +func (c *Client) diffArgs(color, exclude bool) []string { args := []string{ "--no-pager", "diff", @@ -112,18 +113,20 @@ func (c *Client) diffArgs(color bool) []string { args = append(args, "--staged") } - args = append(args, - "--diff-filter=ACMRTUXBD", - "--", // separates options from pathspecs - ".", // include everything under the repo root - ":(exclude)*-lock.*", // package-lock.json, pnpm-lock.yaml, etc. - ":(exclude)*.lock", // yarn.lock, poetry.lock, Cargo.lock, etc. - ":(exclude)**/build/**", - ":(exclude)**/dist/**", - ":(exclude)**/target/**", - ":(exclude)**/out/**", - ":(exclude)go.sum", - ) + if exclude { + args = append(args, + "--diff-filter=ACMRTUXBD", + "--", // separates options from pathspecs + ".", // include everything under the repo root + ":(exclude)*-lock.*", // package-lock.json, pnpm-lock.yaml, etc. + ":(exclude)*.lock", // yarn.lock, poetry.lock, Cargo.lock, etc. + ":(exclude)**/build/**", + ":(exclude)**/dist/**", + ":(exclude)**/target/**", + ":(exclude)**/out/**", + ":(exclude)go.sum", + ) + } return args } diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 4733806..f477adf 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -50,10 +50,7 @@ func (m *diffViewModel) View() string { titleBorder := lipgloss.RoundedBorder() // Ensure we don't have a negative repeat count - repeatCount := (m.viewport.Width + 2) - lipgloss.Width(title) - if repeatCount < 0 { - repeatCount = 0 - } + repeatCount := max((m.viewport.Width + 2) - lipgloss.Width(title), 0) titleBorder.Top = title + strings.Repeat("─", repeatCount) helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", From b2607cf69ad2b19af44644fc833fd793947f1dda Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 11:22:34 +0100 Subject: [PATCH 07/15] refactor: move boxStyle to diffViewModel struct Cache the `lipgloss.Style` in the `diffViewModel` struct instead of re-instantiating it on every view update to improve rendering efficiency. --- internal/ui/diff_view.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index f477adf..4c98147 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -13,11 +13,15 @@ type diffColorMsg string type diffViewModel struct { viewport viewport.Model + boxStyle lipgloss.Style } func initialDiffViewModel(width, height int) *diffViewModel { return &diffViewModel{ viewport: viewport.New(width, height), + boxStyle: lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("6")). // Cyan + Padding(0, 1), } } @@ -42,15 +46,10 @@ func (m *diffViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m *diffViewModel) View() string { - boxStyle := lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("6")). // Cyan - Padding(0, 1) - title := " Raw diff " titleBorder := lipgloss.RoundedBorder() - // Ensure we don't have a negative repeat count - repeatCount := max((m.viewport.Width + 2) - lipgloss.Width(title), 0) + repeatCount := max((m.viewport.Width+2)-lipgloss.Width(title), 0) titleBorder.Top = title + strings.Repeat("─", repeatCount) helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", @@ -60,7 +59,7 @@ func (m *diffViewModel) View() string { BoldYellow.Render("CTRL+D"), BoldYellow.Render("ESC")) - return boxStyle. + return m.boxStyle. BorderStyle(titleBorder). Render(m.viewport.View()) + "\n" + helpText } From ec54b4a08c9b6be90a6b4af892b2548be3a6d37e Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 11:40:33 +0100 Subject: [PATCH 08/15] refactor: streamline view initialization and update UI keys - Inline `viewport` initialization in `commitViewModel`. - Update `diffViewModel` help text to include new keybindings: - **CTRL+K**: Clear - **CTRL+P**: Editor (swapped from Preview) --- internal/ui/commit_view.go | 4 +--- internal/ui/diff_view.go | 3 ++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/ui/commit_view.go b/internal/ui/commit_view.go index 5daa802..a42a9cf 100644 --- a/internal/ui/commit_view.go +++ b/internal/ui/commit_view.go @@ -58,8 +58,6 @@ func initialCommitViewModel(message string, duration time.Duration) (*commitView ta.FocusedStyle.CursorLine = lipgloss.NewStyle() - vp := viewport.New(ta.Width(), ta.Height()) - customStyle := styles.DarkStyleConfig customStyle.Document.Margin = uintPtr(0) customStyle.H2.BlockSuffix = "" @@ -73,7 +71,7 @@ func initialCommitViewModel(message string, duration time.Duration) (*commitView return &commitViewModel{ textarea: ta, - viewport: vp, + viewport: viewport.New(ta.Width(), ta.Height()), history: NewHistory(message), boxStyle: lipgloss.NewStyle(). BorderForeground(lipgloss.Color("6")). // Cyan diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 4c98147..0178bc3 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -52,8 +52,9 @@ func (m *diffViewModel) View() string { repeatCount := max((m.viewport.Width+2)-lipgloss.Width(title), 0) titleBorder.Top = title + strings.Repeat("─", repeatCount) - helpText := fmt.Sprintf("%s:commit %s:regen %s:preview %s:diff %s:back", + helpText := fmt.Sprintf("%s:commit %s:clear %s:regen %s:editor %s:diff %s:back", Strikethrough.Render("CTRL+A"), + Strikethrough.Render("CTRL+K"), Strikethrough.Render("CTRL+R"), Strikethrough.Render("CTRL+P"), BoldYellow.Render("CTRL+D"), From 27cfe1fb4ea9fd5e70c5bf0ccc0f017988bd84af Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 14:29:19 +0100 Subject: [PATCH 09/15] refactor: unify diff logic in git client Consolidate `Diff` and `DiffWithColor` into a single, parameterized `Diff` method to reduce code duplication and simplify the `GitClient` interface. - Updated `GitClient` interface to support color and exclusion flags. - Simplified `Model` diff retrieval by calling the unified `Diff` method. - Cleaned up redundant UI code. --- internal/git/client.go | 21 ++++++++++----------- internal/interfaces/git.go | 2 +- internal/ui/diff_view.go | 1 - internal/ui/model.go | 13 ++++++------- internal/ui/model_test.go | 2 +- 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/internal/git/client.go b/internal/git/client.go index 007c9b3..aa4b115 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -62,23 +62,22 @@ func (c *Client) ModifiedFiles() ([]string, error) { return strings.Split(trimmed, "\n"), nil } -func (c *Client) Diff() (string, error) { - args := c.diffArgs(false, true) - result, err := exec.Command("git", args...).CombinedOutput() - if err != nil { - return "", errors.Wrap(err, "git diff failed") - } - return string(result), nil -} +func (c *Client) Diff(color, exclude bool) (string, error) { + args := c.diffArgs(color, exclude) -func (c *Client) DiffWithColor() (string, error) { - args := c.diffArgs(true, false) cmd := exec.Command("git", args...) + if !color { + result, err := cmd.CombinedOutput() + if err != nil { + return "", errors.Wrap(err, "git diff failed") + } + return string(result), nil + } ptmx, err := pty.Start(cmd) if err != nil { // fallback to plain pipe if pty fails - return c.Diff() + return c.Diff(false, exclude) } defer func() { _ = ptmx.Close() }() diff --git a/internal/interfaces/git.go b/internal/interfaces/git.go index f95c974..24c7924 100644 --- a/internal/interfaces/git.go +++ b/internal/interfaces/git.go @@ -7,6 +7,6 @@ var ErrAborted = errors.New("aborted") type GitClient interface { IsInWorkTree() error ModifiedFiles() ([]string, error) - Diff() (string, error) + Diff(color, exclude bool) (string, error) Commit(message string, skipCI bool) error } diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 13c6428..44d5f8c 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -19,7 +19,6 @@ type diffViewModel struct { func initialDiffViewModel(width, height int) *diffViewModel { vp := viewport.New(viewport.WithWidth(width), viewport.WithHeight(height)) vp.SoftWrap = false - vp.MouseWheelEnabled = true return &diffViewModel{ viewport: vp, diff --git a/internal/ui/model.go b/internal/ui/model.go index a04d695..ad53caa 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -11,7 +11,6 @@ import ( "github.com/cockroachdb/errors" "github.com/earthboundkid/versioninfo/v2" "github.com/galactixx/stringwrap" - "github.com/rm-hull/git-commit-summary/internal/git" "github.com/rm-hull/git-commit-summary/internal/interfaces" llmprovider "github.com/rm-hull/git-commit-summary/internal/llm_provider" versionpkg "github.com/rm-hull/git-commit-summary/internal/version" @@ -115,7 +114,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case showCommitView: m.state = showDiffView if !m.diffLoaded { - return m, m.getDiffWithColor + return m, m.getFullDiffWithColor } return m, nil case showDiffView: @@ -159,7 +158,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = errors.New("no changes detected") return m, tea.Quit } - return m, m.getGitDiff + return m, m.getGitDiffForLLM case gitDiffMsg: m.spinnerMessage = fmt.Sprintf("%s%s%s", @@ -318,16 +317,16 @@ func (m *Model) checkGitStatus() tea.Msg { return gitCheckMsg(modifiedFiles) } -func (m *Model) getDiffWithColor() tea.Msg { - diff, err := m.gitClient.(*git.Client).DiffWithColor() +func (m *Model) getFullDiffWithColor() tea.Msg { + diff, err := m.gitClient.Diff(true, false) if err != nil { return errMsg{err} } return diffColorMsg(diff) } -func (m *Model) getGitDiff() tea.Msg { - diff, err := m.gitClient.Diff() +func (m *Model) getGitDiffForLLM() tea.Msg { + diff, err := m.gitClient.Diff(false, true) if err != nil { return errMsg{err} } diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index 56e17b9..a392bb2 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -48,7 +48,7 @@ func (m *MockGitClient) ModifiedFiles() ([]string, error) { return args.Get(0).([]string), args.Error(1) } -func (m *MockGitClient) Diff() (string, error) { +func (m *MockGitClient) Diff(color, exclude bool) (string, error) { args := m.Called() return args.String(0), args.Error(1) } From e4bcbde36081481a11491b483a87be13c1c4be77 Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 15:29:16 +0100 Subject: [PATCH 10/15] feat: improve diff display and navigation - Replace tabs with spaces in diff output for consistent formatting. - Refactor state transitions to use custom message types for `showDiffView` and `cancelDiffView`. - Clean up `Update` logic in the main model for better maintainability. --- internal/git/client.go | 4 +-- internal/ui/commit_view.go | 3 ++ internal/ui/diff_view.go | 5 +++ internal/ui/model.go | 71 +++++++++++--------------------------- 4 files changed, 30 insertions(+), 53 deletions(-) diff --git a/internal/git/client.go b/internal/git/client.go index aa4b115..3da1c2f 100644 --- a/internal/git/client.go +++ b/internal/git/client.go @@ -71,7 +71,7 @@ func (c *Client) Diff(color, exclude bool) (string, error) { if err != nil { return "", errors.Wrap(err, "git diff failed") } - return string(result), nil + return strings.ReplaceAll(string(result), "\t", " "), nil } ptmx, err := pty.Start(cmd) @@ -90,7 +90,7 @@ func (c *Client) Diff(color, exclude bool) (string, error) { return "", errors.Wrap(err, "waiting for git diff command failed") } - return buf.String(), nil + return strings.ReplaceAll(buf.String(), "\t", " "), nil } func (c *Client) diffArgs(color, exclude bool) []string { diff --git a/internal/ui/commit_view.go b/internal/ui/commit_view.go index a836199..76abf24 100644 --- a/internal/ui/commit_view.go +++ b/internal/ui/commit_view.go @@ -113,6 +113,9 @@ func (m *commitViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textarea.Blur() return m, func() tea.Msg { return regenerateMsg{} } + case "ctrl+d": + return m, func() tea.Msg { return showDiffViewMsg{} } + case "esc": if m.preview { m.preview = false diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 44d5f8c..297f5b7 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -34,6 +34,11 @@ func (m *diffViewModel) Init() tea.Cmd { func (m *diffViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { + case tea.KeyPressMsg: + switch msg.String() { + case "esc", "ctrl+d": + return m, func() tea.Msg { return cancelDiffViewMsg{} } + } case diffColorMsg: m.viewport.SetContent(string(msg)) return m, nil diff --git a/internal/ui/model.go b/internal/ui/model.go index ad53caa..574d364 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -37,6 +37,8 @@ type ( abortMsg struct{} regenerateMsg struct{} cancelRegenPromptMsg struct{} + showDiffViewMsg struct{} + cancelDiffViewMsg struct{} userResponseMsg string ) @@ -101,58 +103,15 @@ func (m *Model) Init() tea.Cmd { } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { case tea.KeyPressMsg: - switch msg.String() { - case "ctrl+c": - if m.state == showSpinner { - m.action = Abort - return m, tea.Quit - } - case "ctrl+d": - switch m.state { - case showCommitView: - m.state = showDiffView - if !m.diffLoaded { - return m, m.getFullDiffWithColor - } - return m, nil - case showDiffView: - m.state = showCommitView - return m, nil - } - case "ctrl+a": - if m.state == showCommitView { - _, cmd := m.commitView.Update(msg) - return m, cmd - } - case "ctrl+r": - if m.state == showCommitView { - _, cmd := m.commitView.Update(msg) - return m, cmd - } - case "ctrl+p": - if m.state == showCommitView { - // This triggers preview in commitView, but we aren't changing state here. - // However, the user might want to toggle preview while in commit view. - // The current implementation of commitView handles preview. - // If we want to keep the same behavior, we just let commitView handle it. - _, cmd := m.commitView.Update(msg) - return m, cmd - } - case "esc": - if m.state == showDiffView { - m.state = showCommitView - return m, nil - } + if msg.String() == "ctrl+c" && m.state == showSpinner { + m.action = Abort + return m, tea.Quit } - case diffColorMsg: - m.diffLoaded = true - var cmd tea.Cmd - m.diffView, cmd = m.diffView.Update(msg) - return m, cmd - case gitCheckMsg: if len(msg) == 0 { m.err = errors.New("no changes detected") @@ -227,10 +186,22 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.hint = string(msg) return m, tea.Batch(m.spinner.Tick, m.generateSummary(m.diff)) - case cancelRegenPromptMsg: + case cancelRegenPromptMsg, cancelDiffViewMsg: m.state = showCommitView return m, m.commitView.Init() + case showDiffViewMsg: + m.state = showDiffView + if !m.diffLoaded { + return m, m.getFullDiffWithColor + } + return m, m.diffView.Init() + + case diffColorMsg: + m.diffLoaded = true + m.diffView, cmd = m.diffView.Update(msg) + return m, cmd + case errMsg: m.err = msg.err return m, tea.Quit @@ -249,14 +220,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.height = msg.Height // Propagate to commitView so it can adjust its internal textarea/viewport - var cmd tea.Cmd if m.commitView != nil { m.commitView, cmd = m.commitView.Update(msg) } return m, cmd } - var cmd tea.Cmd switch m.state { case showSpinner: m.spinner, cmd = m.spinner.Update(msg) From 44bc5e5998e065af596e594da5e2f8c24f30a007 Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 15:33:15 +0100 Subject: [PATCH 11/15] refactor: remove manual window resizing logic --- internal/ui/commit_view.go | 13 ------------- internal/ui/diff_view.go | 4 ---- internal/ui/model.go | 12 ------------ 3 files changed, 29 deletions(-) diff --git a/internal/ui/commit_view.go b/internal/ui/commit_view.go index 76abf24..43d33b0 100644 --- a/internal/ui/commit_view.go +++ b/internal/ui/commit_view.go @@ -210,19 +210,6 @@ func (m *commitViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } - case tea.WindowSizeMsg: - // Adjust textarea height - helpTextHeight := lipgloss.Height(m.helpTextView()) - borderHeight := m.boxStyle.GetVerticalBorderSize() - paddingHeight := m.boxStyle.GetVerticalPadding() - remainingHeight := msg.Height - helpTextHeight - borderHeight - paddingHeight - - m.textarea.SetHeight(max(1, remainingHeight)) - - // Adjust viewport for preview - m.viewport.SetWidth(msg.Width - m.boxStyle.GetHorizontalBorderSize() - m.boxStyle.GetHorizontalPadding()) - m.viewport.SetHeight(max(1, remainingHeight)) - case errMsg: // Use errMsg from model.go return m, tea.Quit } diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 297f5b7..62f5be9 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -42,10 +42,6 @@ func (m *diffViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case diffColorMsg: m.viewport.SetContent(string(msg)) return m, nil - case tea.WindowSizeMsg: - m.viewport.SetWidth(msg.Width) - m.viewport.SetHeight(msg.Height) - return m, nil } var cmd tea.Cmd diff --git a/internal/ui/model.go b/internal/ui/model.go index 574d364..035ef86 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -214,16 +214,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.latestVersion = string(msg) m.spinnerMessage = Magenta.Render("Running git commands to determine modified files...") return m, m.checkGitStatus - - case tea.WindowSizeMsg: - m.width = msg.Width - m.height = msg.Height - - // Propagate to commitView so it can adjust its internal textarea/viewport - if m.commitView != nil { - m.commitView, cmd = m.commitView.Update(msg) - } - return m, cmd } switch m.state { @@ -251,8 +241,6 @@ func (m *Model) LatestVersion() string { return m.latestVersion } -// LatestVersionMsg is handled above to chain into git checks - func (m *Model) View() tea.View { switch m.state { case showSpinner: From 0c4a405b41ec3b6f3395c913d30e983f0038027a Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 15:36:21 +0100 Subject: [PATCH 12/15] refactor: move diffColorMsg to shared model Moved `diffColorMsg` from `diff_view.go` to the main `model.go` struct to centralize state management and allow access across different UI components. --- internal/ui/diff_view.go | 2 -- internal/ui/model.go | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/ui/diff_view.go b/internal/ui/diff_view.go index 62f5be9..65097fd 100644 --- a/internal/ui/diff_view.go +++ b/internal/ui/diff_view.go @@ -9,8 +9,6 @@ import ( "charm.land/lipgloss/v2" ) -type diffColorMsg string - type diffViewModel struct { viewport viewport.Model boxStyle lipgloss.Style diff --git a/internal/ui/model.go b/internal/ui/model.go index 035ef86..79d1997 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -33,6 +33,7 @@ type ( duration time.Duration } commitMsg string + diffColorMsg string errMsg struct{ err error } abortMsg struct{} regenerateMsg struct{} From 99410d7bdcc42d3d68c239444a7ea566454195bb Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 16:07:21 +0100 Subject: [PATCH 13/15] refactor: remove unused dimension fields from Model Removed `width` and `height` from the `Model` struct as they are no longer being utilized within the UI logic. --- internal/ui/model.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/ui/model.go b/internal/ui/model.go index 79d1997..2aad8d5 100644 --- a/internal/ui/model.go +++ b/internal/ui/model.go @@ -63,7 +63,6 @@ type Model struct { spinner spinner.Model spinnerMessage string latestVersion string - width, height int commitView tea.Model diffView tea.Model diffLoaded bool From 4a6452938521f6bf33900cdf25878082c38714ed Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Mon, 25 May 2026 16:20:47 +0100 Subject: [PATCH 14/15] Update internal/ui/model_test.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- internal/ui/model_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index a392bb2..7e8e23a 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -49,7 +49,7 @@ func (m *MockGitClient) ModifiedFiles() ([]string, error) { } func (m *MockGitClient) Diff(color, exclude bool) (string, error) { - args := m.Called() + args := m.Called(color, exclude) return args.String(0), args.Error(1) } From e8b84e30f6e83900322b7c472d5109ba5ea6319a Mon Sep 17 00:00:00 2001 From: Richard Hull Date: Tue, 26 May 2026 08:54:32 +0100 Subject: [PATCH 15/15] test: update git diff mock parameters Update `TestModel_Update` to include the required boolean flags for the `Diff` method call to match the updated internal API signature. --- internal/ui/model_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ui/model_test.go b/internal/ui/model_test.go index 7e8e23a..7b94675 100644 --- a/internal/ui/model_test.go +++ b/internal/ui/model_test.go @@ -118,7 +118,7 @@ func TestModel_Update(t *testing.T) { m := initialModel() m.state = showSpinner // Ensure initial state is showSpinner - mockGit.On("Diff").Return("mocked diff content", nil).Once() + mockGit.On("Diff", false, true).Return("mocked diff content", nil).Once() updatedModel, cmd := m.Update(gitCheckMsg{"file1.go", "file2.go"})