Skip to content

Commit 9c6ee00

Browse files
committed
Fix full-width row highlight, rename flags, add repo dismiss
- Fix row highlight to cover entire line by applying background to each component individually via lipgloss Inherit(), preventing inner ANSI resets from killing the outer background color - Rename --show-self/--mine to --authored/--assigned for clarity - Change assigned toggle key from m to f - Add D (shift-d) to dismiss all PRs from a repo - Update help bar and empty state hints
1 parent 8cfa3a0 commit 9c6ee00

4 files changed

Lines changed: 162 additions & 97 deletions

File tree

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ pr-patrol --plain | wc -l
9898
| | `GITHUB_TOKEN` | GitHub personal access token with `repo` scope (required) |
9999
| `--org` | `GITHUB_ORG` | GitHub organization (required) |
100100
| `--plain` | | Plain text output, no TUI |
101-
| `--show-self` | | Include your own PRs (excluded by default) |
102-
| `--mine` | | Only show PRs where you are a requested reviewer |
101+
| `--authored` | | Include PRs you authored (excluded by default) |
102+
| `--assigned` | | Only show PRs assigned to you for review |
103103
| `--author` | | Show your own PRs and their review status |
104104
| `--limit` | | Maximum PRs to fetch (default 500) |
105105

@@ -110,9 +110,10 @@ pr-patrol --plain | wc -l
110110
| `j` / `k` / `` / `` | Navigate |
111111
| `Enter` | Open PR in browser |
112112
| `d` | Dismiss PR (session only) |
113+
| `D` | Dismiss entire repo (session only) |
113114
| `c` | Comment `@claude please review this PR` |
114-
| `s` | Toggle showing self-authored PRs |
115-
| `m` | Toggle filtering to requested-reviewer PRs |
115+
| `s` | Toggle showing PRs you authored |
116+
| `f` | Toggle filtering to PRs assigned to you for review |
116117
| `o` | Toggle sort order (priority / date) |
117118
| `a` | Toggle author mode (see your PRs' review status) |
118119
| `r` | Refresh data |

main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import (
1111
func main() {
1212
org := pflag.String("org", "", "GitHub organization (or set GITHUB_ORG)")
1313
plain := pflag.Bool("plain", false, "Plain text output (no TUI)")
14-
self := pflag.Bool("show-self", false, "Include self-authored PRs")
15-
mine := pflag.Bool("mine", false, "Only show PRs where you are a requested reviewer")
14+
self := pflag.Bool("authored", false, "Include PRs you authored")
15+
mine := pflag.Bool("assigned", false, "Only show PRs assigned to you for review")
1616
author := pflag.Bool("author", false, "Show your own PRs and their review status")
1717
limit := pflag.Int("limit", 500, "Maximum number of PRs to fetch")
1818
demo := pflag.Bool("demo", false, "Show demo data (for screenshots)")
@@ -87,8 +87,8 @@ func main() {
8787
loading: true,
8888
org: *org,
8989
limit: *limit,
90-
showSelf: *self,
91-
showMine: *mine,
90+
showAuthored: *self,
91+
showAssigned: *mine,
9292
showAuthor: *author,
9393
}), tea.WithAltScreen())
9494
if _, err := p.Run(); err != nil {

tui.go

Lines changed: 92 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var (
1919
styleDim = lipgloss.NewStyle().Faint(true)
2020
styleWhite = lipgloss.NewStyle().Foreground(lipgloss.Color("7"))
2121

22-
selLine = lipgloss.NewStyle().Bold(true).Background(lipgloss.Color("238"))
22+
selBg = lipgloss.NewStyle().Background(lipgloss.Color("238"))
2323
helpStyle = lipgloss.NewStyle().Faint(true)
2424

2525
// Palette of distinguishable ANSI-256 colors for repo/author hashing.
@@ -44,9 +44,10 @@ func nameColor(name string) lipgloss.Style {
4444
}
4545

4646
type model struct {
47-
items []ClassifiedPR
48-
cursor int
49-
dismissed map[string]bool
47+
items []ClassifiedPR
48+
cursor int
49+
dismissed map[string]bool
50+
dismissedRepos map[string]bool
5051
cols colWidths
5152
width int
5253
height int
@@ -55,8 +56,8 @@ type model struct {
5556
me string
5657
myTeams map[string]bool
5758

58-
showSelf bool
59-
showMine bool
59+
showAuthored bool
60+
showAssigned bool
6061
showAuthor bool
6162
sortMode SortMode
6263

@@ -72,8 +73,8 @@ type modelConfig struct {
7273
rawPRs []PRNode
7374
me string
7475
myTeams map[string]bool
75-
showSelf bool
76-
showMine bool
76+
showAuthored bool
77+
showAssigned bool
7778
showAuthor bool
7879
sortMode SortMode
7980
loading bool
@@ -135,12 +136,13 @@ func fetchDataCmd(org string, limit int) tea.Cmd {
135136

136137
func newModel(cfg modelConfig) model {
137138
m := model{
138-
dismissed: make(map[string]bool),
139+
dismissed: make(map[string]bool),
140+
dismissedRepos: make(map[string]bool),
139141
rawPRs: cfg.rawPRs,
140142
me: cfg.me,
141143
myTeams: cfg.myTeams,
142-
showSelf: cfg.showSelf,
143-
showMine: cfg.showMine,
144+
showAuthored: cfg.showAuthored,
145+
showAssigned: cfg.showAssigned,
144146
showAuthor: cfg.showAuthor,
145147
sortMode: cfg.sortMode,
146148
loading: cfg.loading,
@@ -158,13 +160,13 @@ func (m *model) reclassify() {
158160
m.items = classifyAllAuthor(m.rawPRs, m.me, m.sortMode)
159161
} else {
160162
var filter func(PRNode) bool
161-
if m.showMine {
163+
if m.showAssigned {
162164
me, teams := m.me, m.myTeams
163165
filter = func(pr PRNode) bool {
164166
return isRequestedReviewer(pr, me, teams)
165167
}
166168
}
167-
m.items = classifyAll(m.rawPRs, m.me, m.showSelf, filter, m.sortMode)
169+
m.items = classifyAll(m.rawPRs, m.me, m.showAuthored, filter, m.sortMode)
168170
}
169171
m.cols = computeColumns(m.items)
170172
}
@@ -227,13 +229,13 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
227229
m.cursor = 0
228230
case "s":
229231
if !m.showAuthor {
230-
m.showSelf = !m.showSelf
232+
m.showAuthored = !m.showAuthored
231233
m.reclassify()
232234
m.cursor = 0
233235
}
234-
case "m":
236+
case "f":
235237
if !m.showAuthor {
236-
m.showMine = !m.showMine
238+
m.showAssigned = !m.showAssigned
237239
m.reclassify()
238240
m.cursor = 0
239241
}
@@ -259,6 +261,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
259261
m.cursor--
260262
}
261263
}
264+
case "D":
265+
if pr, ok := m.selectedPR(); ok {
266+
m.dismissedRepos[pr.RepoName] = true
267+
m.statusMsg = fmt.Sprintf("Dismissed repo %s", pr.RepoName)
268+
vis := m.visibleItems()
269+
if m.cursor >= len(vis) && m.cursor > 0 {
270+
m.cursor = len(vis) - 1
271+
}
272+
}
262273
case "c":
263274
if pr, ok := m.selectedPR(); ok {
264275
m.statusMsg = fmt.Sprintf("Commenting on %s#%d...", pr.RepoName, pr.Number)
@@ -293,7 +304,7 @@ func (m model) View() string {
293304
if m.showAuthor {
294305
return "No open PRs authored by you. Press a to switch to reviewer mode.\n"
295306
}
296-
return "No PRs match current filters. Press s/m to adjust, or a for author mode.\n"
307+
return "No PRs match current filters. Press s/f to adjust, or a for author mode.\n"
297308
}
298309

299310
var b strings.Builder
@@ -322,7 +333,13 @@ func (m model) View() string {
322333

323334
for i := start; i < end; i++ {
324335
pr := vis[i]
325-
indicators := formatIndicators(pr, m.showAuthor)
336+
selected := i == m.cursor
337+
var bg *lipgloss.Style
338+
if selected {
339+
bg = &selBg
340+
}
341+
342+
indicators := formatIndicators(pr, m.showAuthor, bg)
326343
repoCol := fmt.Sprintf("%-*s", m.cols.repo, fmt.Sprintf("%s#%d", pr.RepoName, pr.Number))
327344
authorCol := fmt.Sprintf("%-*s", m.cols.author, pr.Author)
328345

@@ -340,16 +357,24 @@ func (m model) View() string {
340357
}
341358
}
342359

343-
coloredRepo := nameColor(pr.RepoName).Render(repoCol)
344-
coloredAuthor := nameColor(pr.Author).Render(authorCol)
345-
line := fmt.Sprintf("%s %s %s %s", indicators, coloredRepo, coloredAuthor, titleText)
346-
347-
if i == m.cursor {
348-
style := selLine
360+
var coloredRepo, coloredAuthor, line string
361+
if selected {
362+
coloredRepo = nameColor(pr.RepoName).Inherit(selBg).Render(repoCol)
363+
coloredAuthor = nameColor(pr.Author).Inherit(selBg).Render(authorCol)
364+
sep := selBg.Render(" ")
365+
titleRendered := selBg.Render(titleText)
366+
line = indicators + selBg.Render(" ") + coloredRepo + sep + coloredAuthor + sep + titleRendered
367+
// Pad to full width
349368
if m.width > 0 {
350-
style = style.Width(m.width)
369+
lineLen := lipgloss.Width(line)
370+
if lineLen < m.width {
371+
line += selBg.Render(strings.Repeat(" ", m.width-lineLen))
372+
}
351373
}
352-
line = style.Render(line)
374+
} else {
375+
coloredRepo = nameColor(pr.RepoName).Render(repoCol)
376+
coloredAuthor = nameColor(pr.Author).Render(authorCol)
377+
line = fmt.Sprintf("%s %s %s %s", indicators, coloredRepo, coloredAuthor, titleText)
353378
}
354379
b.WriteString(line)
355380
b.WriteString("\n")
@@ -368,21 +393,21 @@ func (m model) View() string {
368393
var help string
369394
if m.showAuthor {
370395
help = helpStyle.Render(fmt.Sprintf(
371-
"j/k: navigate enter: open d: dismiss c: @claude o: %s a: %s r: refresh ?: legend q: quit",
396+
"j/k: navigate enter: open d/D: dismiss PR/repo c: @claude o: %s a: %s r: refresh ?: legend q: quit",
372397
sortLabel, authorLabel,
373398
))
374399
} else {
375-
selfLabel := "self:off"
376-
if m.showSelf {
377-
selfLabel = "self:on"
400+
authoredLabel := "authored:off"
401+
if m.showAuthored {
402+
authoredLabel = "authored:on"
378403
}
379-
mineLabel := "mine:off"
380-
if m.showMine {
381-
mineLabel = "mine:on"
404+
assignedLabel := "assigned:off"
405+
if m.showAssigned {
406+
assignedLabel = "assigned:on"
382407
}
383408
help = helpStyle.Render(fmt.Sprintf(
384-
"j/k: navigate enter: open d: dismiss c: @claude s: %s m: %s o: %s a: %s r: refresh ?: legend q: quit",
385-
selfLabel, mineLabel, sortLabel, authorLabel,
409+
"j/k: navigate enter: open d/D: dismiss PR/repo c: @claude s: %s f: %s o: %s a: %s r: refresh ?: legend q: quit",
410+
authoredLabel, assignedLabel, sortLabel, authorLabel,
386411
))
387412
}
388413
if m.statusMsg != "" {
@@ -422,9 +447,10 @@ func (m model) renderLegend() string {
422447
func (m model) visibleItems() []ClassifiedPR {
423448
var vis []ClassifiedPR
424449
for _, pr := range m.items {
425-
if !m.dismissed[pr.URL] {
426-
vis = append(vis, pr)
450+
if m.dismissed[pr.URL] || m.dismissedRepos[pr.RepoName] {
451+
continue
427452
}
453+
vis = append(vis, pr)
428454
}
429455
return vis
430456
}
@@ -437,53 +463,64 @@ func (m model) selectedPR() (ClassifiedPR, bool) {
437463
return vis[m.cursor], true
438464
}
439465

440-
func formatIndicators(pr ClassifiedPR, authorMode bool) string {
466+
func withBg(s lipgloss.Style, bg *lipgloss.Style) lipgloss.Style {
467+
if bg != nil {
468+
return s.Inherit(*bg)
469+
}
470+
return s
471+
}
472+
473+
func formatIndicators(pr ClassifiedPR, authorMode bool, bg *lipgloss.Style) string {
441474
var col1, col2, col3 string
442475

443476
if authorMode {
444477
if pr.IsDraft {
445-
col1 = styleDim.Render("○")
478+
col1 = withBg(styleDim, bg).Render("○")
446479
} else {
447-
col1 = styleWhite.Render("●")
480+
col1 = withBg(styleWhite, bg).Render("●")
448481
}
449482
} else {
450483
switch pr.MyReview {
451484
case MyNone:
452-
col1 = styleDim.Render("·")
485+
col1 = withBg(styleDim, bg).Render("·")
453486
case MyApproved:
454-
col1 = styleGreen.Render("✓")
487+
col1 = withBg(styleGreen, bg).Render("✓")
455488
case MyChanges:
456-
col1 = styleRed.Render("✗")
489+
col1 = withBg(styleRed, bg).Render("✗")
457490
case MyStale:
458-
col1 = styleDim.Render("~")
491+
col1 = withBg(styleDim, bg).Render("~")
459492
default:
460-
col1 = styleDim.Render("·")
493+
col1 = withBg(styleDim, bg).Render("·")
461494
}
462495
}
463496

464497
switch pr.OthReview {
465498
case OthNone:
466-
col2 = styleDim.Render("·")
499+
col2 = withBg(styleDim, bg).Render("·")
467500
case OthApproved:
468-
col2 = styleGreen.Render("✓")
501+
col2 = withBg(styleGreen, bg).Render("✓")
469502
case OthChanges:
470-
col2 = styleRed.Render("✗")
503+
col2 = withBg(styleRed, bg).Render("✗")
471504
case OthMixed:
472-
col2 = styleYellow.Render("±")
505+
col2 = withBg(styleYellow, bg).Render("±")
473506
default:
474-
col2 = styleDim.Render("·")
507+
col2 = withBg(styleDim, bg).Render("·")
475508
}
476509

477510
switch pr.Activity {
478511
case ActNone:
479-
col3 = styleDim.Render("·")
512+
col3 = withBg(styleDim, bg).Render("·")
480513
case ActOthers:
481-
col3 = styleWhite.Render("○")
514+
col3 = withBg(styleWhite, bg).Render("○")
482515
case ActMine:
483-
col3 = styleCyan.Render("●")
516+
col3 = withBg(styleCyan, bg).Render("●")
484517
default:
485-
col3 = styleDim.Render("·")
518+
col3 = withBg(styleDim, bg).Render("·")
486519
}
487520

488-
return col1 + " " + col2 + " " + col3
521+
sep := " "
522+
if bg != nil {
523+
sep = bg.Render(" ")
524+
}
525+
return col1 + sep + col2 + sep + col3
489526
}

0 commit comments

Comments
 (0)