Add multiselect board filters and letter-jump on picker menus.

Persist comma-separated list filters per project, toggle with space, and jump by first letter in board and project pickers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Skruecken
2026-10-19 10:42:59 +02:00
co-authored by Cursor
parent aa8f89b43a
commit 41feba11de
9 changed files with 305 additions and 39 deletions
+27 -11
View File
@@ -52,7 +52,7 @@ type Model struct {
projectList list.Model
statusMenu views.StatusMenu
boardMenu views.BoardMenu
boardList ticket.BoardList
boardFilter ticket.BoardFilter
input textinput.Model
width int
height int
@@ -80,7 +80,7 @@ func New(cfg *config.Config, store ticket.Store, projects project.Store, exec co
projects: projects,
exec: exec,
mode: ModeList,
boardList: ticket.ParseBoardList(cfg.ActiveBoardList(cfg.ActiveProject)),
boardFilter: ticket.ParseBoardLists(cfg.ActiveBoardLists(cfg.ActiveProject)),
tickets: views.TicketList{
Styles: views.TicketListStyles{
Card: styles.Card,
@@ -208,7 +208,7 @@ func (m Model) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
case key.Matches(msg, m.keys.Board):
m.clearStatus()
m.boardMenu = views.NewBoardMenu(m.boardList, m.width, views.StatusMenuStyles{
m.boardMenu = views.NewBoardMenu(m.boardFilter, m.width, views.StatusMenuStyles{
Box: m.styles.StatusMenuBox,
Title: m.styles.StatusMenuTitle,
Selected: m.styles.StatusMenuSelected,
@@ -329,13 +329,17 @@ func (m Model) updateBoard(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
case key.Matches(msg, m.keys.Enter):
if err := m.applyBoardList(m.boardMenu.Selected()); err != nil {
if err := m.applyBoardFilter(m.boardMenu.Selected()); err != nil {
m.setError(err)
return m, nil
}
m.mode = ModeList
return m, nil
case key.Matches(msg, m.keys.Toggle):
m.boardMenu.Toggle()
return m, nil
case key.Matches(msg, m.keys.Down):
m.boardMenu.MoveDown()
return m, nil
@@ -344,6 +348,12 @@ func (m Model) updateBoard(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
m.boardMenu.MoveUp()
return m, nil
}
if msg.Type == tea.KeyRunes && len(msg.Runes) == 1 {
if m.boardMenu.JumpToShortcut(msg.Runes[0]) {
return m, nil
}
}
return m, nil
}
@@ -475,6 +485,12 @@ func (m Model) updateProjects(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
if msg.Type == tea.KeyRunes && len(msg.Runes) == 1 {
if views.JumpProjectByLetter(&m.projectList, msg.Runes[0]) {
return m, nil
}
}
var cmd tea.Cmd
m.projectList, cmd = m.projectList.Update(msg)
return m, cmd
@@ -575,7 +591,7 @@ func (m *Model) createTicket(title, description string) error {
Title: title,
Description: description,
ProjectID: m.cfg.ActiveProject,
Status: m.boardList.DefaultStatus(),
Status: m.boardFilter.DefaultStatus(),
},
}
if err := m.exec.Do(cmd); err != nil {
@@ -661,14 +677,14 @@ func (m *Model) redo() error {
return nil
}
func (m *Model) applyBoardList(bl ticket.BoardList) error {
m.boardList = bl
m.cfg.SetActiveBoardList(m.cfg.ActiveProject, string(bl))
func (m *Model) applyBoardFilter(f ticket.BoardFilter) error {
m.boardFilter = ticket.NormalizeFilter(f)
m.cfg.SetActiveBoardLists(m.cfg.ActiveProject, ticket.FormatBoardLists(m.boardFilter))
if err := config.Save(m.cfg); err != nil {
return err
}
m.applyFilter()
m.setStatus(fmt.Sprintf("list → %s", bl.Label()))
m.setStatus(fmt.Sprintf("list → %s", m.boardFilter.Label()))
return nil
}
@@ -677,7 +693,7 @@ func (m *Model) switchProject(id string) error {
return err
}
m.cfg.ActiveProject = id
m.boardList = ticket.ParseBoardList(m.cfg.ActiveBoardList(id))
m.boardFilter = ticket.ParseBoardLists(m.cfg.ActiveBoardLists(id))
if err := config.Save(m.cfg); err != nil {
return err
}
@@ -721,7 +737,7 @@ func (m *Model) reloadTickets() error {
func (m *Model) applyFilter() {
filtered := make([]ticket.Ticket, 0, len(m.allTickets))
for _, t := range m.allTickets {
if m.boardList.Matches(t.Status) {
if m.boardFilter.Matches(t.Status) {
filtered = append(filtered, t)
}
}