Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Deploy Pages

on:
push:
branches: [main]
paths:
- "site/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: site/package-lock.json
- name: Install dependencies
working-directory: site
run: npm ci
- name: Build site
working-directory: site
run: npm run build
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: site/dist

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- id: deployment
uses: actions/deploy-pages@v4
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/coauthor-cleaner
/dist/
site/.astro/
site/dist/
site/node_modules/
24 changes: 24 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.PHONY: install build clean test help

# Build with CGO disabled (required for macOS compatibility)
GOFLAGS ?= -trimpath
LDFLAGS ?=

install: ## Install coauthor-cleaner binary
CGO_ENABLED=0 go install $(GOFLAGS) -ldflags "$(LDFLAGS)" ./cmd/coauthor-cleaner

build: ## Build coauthor-cleaner binary to ./bin/
CGO_ENABLED=0 go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o ./bin/coauthor-cleaner ./cmd/coauthor-cleaner

clean: ## Remove build artifacts
rm -rf bin/
go clean

test: ## Run tests
go test -v ./...

help: ## Show this help message
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-15s %s\n", $$1, $$2}'
15 changes: 13 additions & 2 deletions internal/tui/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package tui
const (
headerLines = 3
footerLines = 1
leftPct = 42
leftPct = 43
)

type layout struct {
Expand All @@ -12,6 +12,8 @@ type layout struct {
bodyHeight int
leftWidth int
rightWidth int
listWidth int
panelHeight int
innerH int
}

Expand All @@ -28,12 +30,21 @@ func computeLayout(w, h int) layout {
leftW = w - 24
}
rightW := w - leftW - 1
listW := leftW - 8
if listW < 20 {
listW = 20
}
innerH := bodyH - 2
if innerH < 3 {
innerH = 3
}
panelH := bodyH - 4
if panelH < 3 {
panelH = 3
}
return layout{
width: w, height: h, bodyHeight: bodyH,
leftWidth: leftW, rightWidth: rightW, innerH: innerH,
leftWidth: leftW, rightWidth: rightW,
listWidth: listW, panelHeight: panelH, innerH: innerH,
}
}
60 changes: 44 additions & 16 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,17 @@ func New(g git.Runner, opts scan.Options, findings []detect.Finding) Model {

delegate := list.NewDefaultDelegate()
delegate.ShowDescription = true
delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.Foreground(colorAccent).Bold(true)
delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.Foreground(colorAccent)
delegate.Styles.NormalTitle = delegate.Styles.NormalTitle.Foreground(colorText).Bold(true)
delegate.Styles.NormalDesc = delegate.Styles.NormalDesc.Foreground(colorMuted)
delegate.Styles.SelectedTitle = delegate.Styles.SelectedTitle.
Foreground(colorText).
Background(lipgloss.Color("#271821")).
BorderLeftForeground(colorError).
Bold(true)
delegate.Styles.SelectedDesc = delegate.Styles.SelectedDesc.
Foreground(colorText).
Background(lipgloss.Color("#271821")).
BorderLeftForeground(colorError)

l := list.New(items, delegate, 0, 0)
l.Title = ""
Expand Down Expand Up @@ -108,10 +117,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.layout = computeLayout(msg.Width, msg.Height)
m.list.SetWidth(m.layout.leftWidth - 4)
m.list.SetHeight(m.layout.innerH)
m.preview.Width = m.layout.rightWidth - 4
m.preview.Height = m.layout.innerH
m.list.SetWidth(m.layout.listWidth)
m.list.SetHeight(maxInt(3, m.layout.panelHeight-3))
m.preview.Width = maxInt(20, m.layout.rightWidth-8)
m.preview.Height = maxInt(3, m.layout.panelHeight-3)
return m, nil

case pushDoneMsg:
Expand Down Expand Up @@ -283,7 +292,6 @@ func (m *Model) syncPreview() {
}
f := m.findings[idx]
var b strings.Builder
b.WriteString(panelTitlePassive.Render("Preview") + "\n\n")
b.WriteString(PreviewDiff(f))
if f.Selected {
b.WriteString("\n" + okStyle.Render("● selected for cleanup"))
Expand Down Expand Up @@ -341,7 +349,6 @@ func (m Model) renderMain() string {
func (m Model) renderStatusBar() string {
st := m.repoState
parts := []string{
titleStyle.Render("Coauthor Cleaner"),
fmt.Sprintf("branch %s", st.Branch),
}
if st.UpstreamExists {
Expand All @@ -359,7 +366,15 @@ func (m Model) renderStatusBar() string {
} else {
parts = append(parts, fmt.Sprintf("%d findings · %d selected", count, sel))
}
return statusBarStyle.Width(m.layout.width - 2).Render(strings.Join(parts, " │ "))
brand := lipgloss.JoinHorizontal(
lipgloss.Center,
logoMarkStyle.Render("▣"),
" ",
titleStyle.Render("Coauthor Cleaner"),
)
status := subStyle.Render(strings.Join(parts, " │ "))
line := lipgloss.JoinHorizontal(lipgloss.Center, brand, " ", badgeStyle.Render("git history hygiene"), " ", status)
return statusBarStyle.Width(maxInt(20, m.layout.width-4)).Render(line)
}

func (m Model) renderSplit() string {
Expand All @@ -368,26 +383,39 @@ func (m Model) renderSplit() string {
ly = computeLayout(80, 24)
}

leftTitle := panelTitleActive.Render("Findings")
leftTitle := lipgloss.JoinHorizontal(
lipgloss.Center,
panelTitleActive.Render("FINDINGS"),
" ",
subStyle.Render(fmt.Sprintf("%d total", len(m.findings))),
)

leftBody := m.list.View()
if len(m.findings) == 0 {
leftBody = subStyle.Render("✓ No AI attribution markers found.\n\nPress r to rescan.")
}

left := panelActiveStyle.
Width(ly.leftWidth).
Height(ly.bodyHeight).
Render(leftTitle + "\n" + leftBody)
Width(maxInt(20, ly.leftWidth-6)).
Height(ly.panelHeight).
Render(leftTitle + "\n\n" + leftBody)

rightTitle := panelTitlePassive.Render("LIVE PREVIEW")
right := panelPassiveStyle.
Width(ly.rightWidth).
Height(ly.bodyHeight).
Render(m.preview.View())
Width(maxInt(20, ly.rightWidth-6)).
Height(ly.panelHeight).
Render(rightTitle + "\n\n" + m.preview.View())

return lipgloss.JoinHorizontal(lipgloss.Top, left, " ", right)
}

func maxInt(a, b int) int {
if a > b {
return a
}
return b
}

func (m Model) renderFooter() string {
keys := m.contextKeys()
return footerStyle.Width(m.layout.width - 2).Render(keys)
Expand Down
16 changes: 11 additions & 5 deletions internal/tui/preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import (
"strings"

"github.com/ericdahl-dev/coauthor-cleaner/internal/detect"
"github.com/charmbracelet/lipgloss"
)

func PreviewDiff(f detect.Finding) string {
var b strings.Builder
b.WriteString("Before / after\n\n")
b.WriteString(fmt.Sprintf(" - %s\n", f.Match))
b.WriteString(subStyle.Render("Before / after") + "\n\n")

var diff strings.Builder
diff.WriteString(lipgloss.NewStyle().Foreground(colorError).Render(fmt.Sprintf("- %s", f.Match)) + "\n")
if f.Replacement != "" {
b.WriteString(fmt.Sprintf(" + %s\n", f.Replacement))
diff.WriteString(lipgloss.NewStyle().Foreground(colorOK).Render(fmt.Sprintf("+ %s", f.Replacement)) + "\n")
} else {
b.WriteString(" +\n")
diff.WriteString(lipgloss.NewStyle().Foreground(colorOK).Render("+ <removed>") + "\n")
}
b.WriteString(fmt.Sprintf("\nRule: %s (%s confidence)\n", f.RuleName, f.Confidence))
b.WriteString(codeBoxStyle.Render(diff.String()))
b.WriteString("\n\n")
b.WriteString(subStyle.Render("Rule") + " " + titleStyle.Render(f.RuleName) + "\n")
b.WriteString(subStyle.Render("Confidence") + " " + okStyle.Render(string(f.Confidence)) + "\n")
return b.String()
}
65 changes: 49 additions & 16 deletions internal/tui/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,75 @@ package tui

import "github.com/charmbracelet/lipgloss"

// Palette inspired by tui-design: muted base, accent for focus, warn for danger.
// Dark terminal palette aligned with the Pages hero mock.
var (
colorAccent = lipgloss.Color("205")
colorMuted = lipgloss.Color("240")
colorDim = lipgloss.Color("238")
colorWarn = lipgloss.Color("214")
colorError = lipgloss.Color("196")
colorOK = lipgloss.Color("42")
colorBorder = lipgloss.Color("63")
colorPanel = lipgloss.Color("60")
colorAccent = lipgloss.Color("#D6FF5F")
colorViolet = lipgloss.Color("#9B7CFF")
colorMuted = lipgloss.Color("#8B8791")
colorDim = lipgloss.Color("#3C3A43")
colorWarn = lipgloss.Color("#FFD166")
colorError = lipgloss.Color("#FF6B81")
colorOK = lipgloss.Color("#D6FF5F")
colorBorder = lipgloss.Color("#363341")
colorPanel = lipgloss.Color("#1A1B22")
colorPanel2 = lipgloss.Color("#101116")
colorText = lipgloss.Color("#F7F5EF")
)

var (
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(colorAccent)
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(colorText)
subStyle = lipgloss.NewStyle().Foreground(colorMuted)
helpStyle = lipgloss.NewStyle().Foreground(colorMuted)
warnStyle = lipgloss.NewStyle().Foreground(colorWarn).Bold(true)
okStyle = lipgloss.NewStyle().Foreground(colorOK)
okStyle = lipgloss.NewStyle().Foreground(colorOK).Bold(true)
errorStyle = lipgloss.NewStyle().Foreground(colorError).Bold(true)

statusBarStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
Border(lipgloss.RoundedBorder(), true).
BorderForeground(colorBorder).
Background(colorPanel2).
Padding(0, 1)

panelActiveStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(colorAccent).
Padding(0, 1)
Background(colorPanel2).
Padding(1, 2)

panelPassiveStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(colorPanel).
Padding(0, 1)
Background(colorPanel2).
Padding(1, 2)

panelTitleActive = lipgloss.NewStyle().
Bold(true).
Foreground(colorAccent)
panelTitlePassive = lipgloss.NewStyle().
Bold(true).
Foreground(colorMuted)

badgeStyle = lipgloss.NewStyle().
Foreground(colorAccent).
Border(lipgloss.RoundedBorder()).
BorderForeground(colorViolet).
Padding(0, 1)

logoMarkStyle = lipgloss.NewStyle().
Foreground(colorAccent).
Border(lipgloss.RoundedBorder()).
BorderForeground(colorBorder).
Padding(0, 1)

panelTitleActive = lipgloss.NewStyle().Bold(true).Foreground(colorAccent)
panelTitlePassive = lipgloss.NewStyle().Bold(true).Foreground(colorMuted)
keyStyle = lipgloss.NewStyle().
Bold(true).
Foreground(colorText)

codeBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(colorBorder).
Background(lipgloss.Color("#08090D")).
Padding(1, 2)

footerStyle = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, true, false).
Expand All @@ -49,6 +81,7 @@ var (
modalStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(colorAccent).
Background(colorPanel2).
Padding(1, 2).
Width(56)

Expand Down
9 changes: 9 additions & 0 deletions internal/tui/view_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/ericdahl-dev/coauthor-cleaner/internal/detect"
"github.com/ericdahl-dev/coauthor-cleaner/internal/git"
"github.com/charmbracelet/lipgloss"
)

func TestPreviewDiff(t *testing.T) {
Expand Down Expand Up @@ -44,3 +45,11 @@ func TestComputeLayout(t *testing.T) {
t.Fatalf("%+v", ly)
}
}

func TestRenderSplitFitsTerminalWidth(t *testing.T) {
m := Model{layout: computeLayout(100, 30)}
out := m.renderSplit()
if got := lipgloss.Width(out); got > 100 {
t.Fatalf("rendered split width = %d, want <= 100\n%s", got, out)
}
}
6 changes: 6 additions & 0 deletions site/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { defineConfig } from "astro/config";

export default defineConfig({
site: "https://ericdahl-dev.github.io",
base: "/coauthor-cleaner",
});
Loading
Loading