From a8aada16460ebe33926b41a88dfd6e30d5dafa99 Mon Sep 17 00:00:00 2001 From: viljarb Date: Tue, 24 Feb 2026 11:19:01 +0100 Subject: [PATCH] using codenames for priority levels mapped to digits --- pkg/tui/views/dashboard/help_bar.go | 6 +-- pkg/tui/views/dashboard/issue_detail.go | 4 +- pkg/tui/views/dashboard/issue_list.go | 19 +++++++- pkg/tui/views/dashboard/keys.go | 21 ++++++--- pkg/tui/views/dashboard/model.go | 13 +++-- pkg/tui/views/dashboard/operations.go | 63 +++++++++++++++++++++++++ pkg/tui/views/dashboard/view.go | 16 ++++++- 7 files changed, 124 insertions(+), 18 deletions(-) diff --git a/pkg/tui/views/dashboard/help_bar.go b/pkg/tui/views/dashboard/help_bar.go index ac43e75..3ad63cc 100644 --- a/pkg/tui/views/dashboard/help_bar.go +++ b/pkg/tui/views/dashboard/help_bar.go @@ -36,7 +36,7 @@ func (h HelpBar) shortHelp() string { styles.HighlightKey("↓/j") + " down", styles.HighlightKey("pgup/pgdn") + " page", styles.HighlightKey("a") + " add", - styles.HighlightKey("e/d/s/p") + " edit", + styles.HighlightKey("e/d/s/p/t") + " edit", styles.HighlightKey("x") + " delete", styles.HighlightKey("q") + " quit", styles.HighlightKey("?") + " help", @@ -76,8 +76,8 @@ func (h HelpBar) fullHelp() string { renderRow("b", "back to list", "a", "add issue"), renderRow("e", "edit title", "d", "edit desc"), renderRow("s", "change status", "p", "change priority"), - renderRow("x", "delete issue", "?", "help"), - renderRow("q", "quit", "", ""), + renderRow("t", "change type", "x", "delete issue"), + renderRow("?", "help", "q", "quit"), } content := lipgloss.JoinVertical(lipgloss.Left, rows...) return lipgloss.NewStyle(). diff --git a/pkg/tui/views/dashboard/issue_detail.go b/pkg/tui/views/dashboard/issue_detail.go index 49a452e..8681800 100644 --- a/pkg/tui/views/dashboard/issue_detail.go +++ b/pkg/tui/views/dashboard/issue_detail.go @@ -1,8 +1,6 @@ package dashboard import ( - "fmt" - "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/tui/styles" "github.com/charmbracelet/bubbles/viewport" @@ -56,7 +54,7 @@ func (i *IssueDetail) refreshContent() { ) priorityRow := styles.RowStyle.Render( - styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(fmt.Sprintf("%d", i.issue.Priority)), + styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(priorityCodeName(i.issue.Priority)), ) descLabel := styles.LabelStyle.Render("Description:") diff --git a/pkg/tui/views/dashboard/issue_list.go b/pkg/tui/views/dashboard/issue_list.go index 7ec4648..30163be 100644 --- a/pkg/tui/views/dashboard/issue_list.go +++ b/pkg/tui/views/dashboard/issue_list.go @@ -158,8 +158,8 @@ func ClosedOnly(issues []models.Issue) []models.Issue { return out } -// sortByPriorityDesc sorts issues by priority, highest first. func sortByPriorityDesc(issues []models.Issue) { + // sorts issues by priority, highest first. sort.Slice(issues, func(i, j int) bool { return issues[i].Priority > issues[j].Priority }) @@ -313,6 +313,21 @@ func renderRow(issue ListIssue, isSelected bool, cols []TableColumn) string { return lipgloss.JoinHorizontal(lipgloss.Left, parts...) } +var priorityCodeNames = map[int]string{ + 0: "irrelevant", + 1: "low", + 2: "normal", + 3: "high", + 4: "critical", +} + +func priorityCodeName(priority int) string { + if name, ok := priorityCodeNames[priority]; ok { + return name + } + return fmt.Sprintf("%d", priority) +} + func getColumnValue(col TableColumn, issue ListIssue) string { switch col.key { case "id": @@ -324,7 +339,7 @@ func getColumnValue(col TableColumn, issue ListIssue) string { case "type": return string(issue.Issue.IssueType) case "priority": - return fmt.Sprintf("%d", issue.Issue.Priority) + return priorityCodeName(issue.Issue.Priority) default: return "" } diff --git a/pkg/tui/views/dashboard/keys.go b/pkg/tui/views/dashboard/keys.go index 4e35599..34760f5 100644 --- a/pkg/tui/views/dashboard/keys.go +++ b/pkg/tui/views/dashboard/keys.go @@ -17,6 +17,7 @@ type DashboardKeyMap struct { EditDescription key.Binding ChangeStatus key.Binding ChangePriority key.Binding + ChangeType key.Binding AddIssue key.Binding DeleteIssue key.Binding } @@ -66,6 +67,10 @@ var defaultDashboardKeyMap = DashboardKeyMap{ key.WithKeys("p"), key.WithHelp("p", "change priority"), ), + ChangeType: key.NewBinding( + key.WithKeys("t"), + key.WithHelp("t", "change type"), + ), AddIssue: key.NewBinding( key.WithKeys("a"), key.WithHelp("a", "add issue"), @@ -94,28 +99,32 @@ func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { d.issueDetail.ScrollUp(1) case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown): d.issueDetail.ScrollDown(1) - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditTitle): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.EditTitle): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startEditTitle(selected) cmd = d.titleInput.Focus() } - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditDescription): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.EditDescription): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startEditDescription(selected) cmd = d.descriptionInput.Focus() } - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangeStatus): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangeStatus): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startChooseStatus(selected) } - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangePriority): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangePriority): if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { d.startChoosePriority(selected) } - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.AddIssue): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangeType): + if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { + d.startChooseType(selected) + } + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.AddIssue): d.startCreateIssue() cmd = d.createTitleInput.Focus() - case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.DeleteIssue): + case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.DeleteIssue): fl := d.FocusedIssueList() if selected := fl.SelectedItem(); selected.ID != "" { d.startConfirmDelete(selected.ID, fl.Index()) diff --git a/pkg/tui/views/dashboard/model.go b/pkg/tui/views/dashboard/model.go index 93c1391..763b600 100644 --- a/pkg/tui/views/dashboard/model.go +++ b/pkg/tui/views/dashboard/model.go @@ -41,10 +41,12 @@ type Model struct { deleteConfirmID string deleteConfirmIndex int - choosingStatus bool + choosingStatus bool // true while choosing a status statusIssueID string - choosingPriority bool - priorityIssueID string + choosingPriority bool // true while choosing a priority + priorityIssueID string + choosingType bool // true while choosing a type + typeIssueID string feedbackChan chan task.ValidationFeedback quitChan chan bool currentFeedback task.ValidationFeedback @@ -132,6 +134,11 @@ func (m *Model) startChoosePriority(selected ListIssue) { m.priorityIssueID = selected.ID } +func (m *Model) startChooseType(selected ListIssue) { + m.choosingType = true + m.typeIssueID = selected.ID +} + func (m *Model) Init() tea.Cmd { return m.listenForValidation() } diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index e764571..a5da17e 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -29,6 +29,11 @@ type issuePriorityUpdatedMsg struct { Err error } +type issueTypeUpdatedMsg struct { + IssueID string + Err error +} + type selectIssueMsg struct { IssueID string } @@ -76,12 +81,21 @@ func updateIssuePriorityCmd(svc *service.Services, issueID string, priority int) } } +func updateIssueTypeCmd(svc *service.Services, issueID string, issueType models.IssueType) tea.Cmd { + return func() tea.Msg { + updates := map[string]interface{}{"issue_type": string(issueType)} + err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui") + return issueTypeUpdatedMsg{IssueID: issueID, Err: err} + } +} + func createIssueCmd(svc *service.Services, title string) tea.Cmd { return func() tea.Msg { issue := &models.Issue{ Title: title, Status: models.StatusOpen, IssueType: models.TypeTask, + Priority: 2, } err := svc.Beads.CreateIssue(context.Background(), issue, "tui") return issueCreatedMsg{Issue: issue, Err: err} @@ -150,6 +164,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) + case issueTypeUpdatedMsg: + m.choosingType = false + m.typeIssueID = "" + if msg.Err != nil { + return m, nil + } + return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) + case selectIssueMsg: m.issueList.SelectIssueID(msg.IssueID) m.closedIssueList.SelectIssueID(msg.IssueID) @@ -294,6 +316,42 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + if m.choosingType { + switch msg.String() { + case "b": + issueID := m.typeIssueID + m.choosingType = false + m.typeIssueID = "" + return m, updateIssueTypeCmd(m.svc, issueID, models.TypeBug) + case "f": + issueID := m.typeIssueID + m.choosingType = false + m.typeIssueID = "" + return m, updateIssueTypeCmd(m.svc, issueID, models.TypeFeature) + case "t": + issueID := m.typeIssueID + m.choosingType = false + m.typeIssueID = "" + return m, updateIssueTypeCmd(m.svc, issueID, models.TypeTask) + case "e": + issueID := m.typeIssueID + m.choosingType = false + m.typeIssueID = "" + return m, updateIssueTypeCmd(m.svc, issueID, models.TypeEpic) + case "c": + issueID := m.typeIssueID + m.choosingType = false + m.typeIssueID = "" + return m, updateIssueTypeCmd(m.svc, issueID, models.TypeChore) + case "esc": + m.choosingType = false + m.typeIssueID = "" + return m, nil + default: + return m, nil + } + } + if m.creatingIssue { if msg.String() == "enter" { title := m.createTitleInput.Value() @@ -356,6 +414,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } + // On main dashboard, ESC does nothing; only q quits; like in lazybeads. + if msg.String() == "esc" { + return m, nil + } + cmd := m.handleKeyMsg(msg) if cmd != nil { return m, cmd diff --git a/pkg/tui/views/dashboard/view.go b/pkg/tui/views/dashboard/view.go index fbdf37d..2df8781 100644 --- a/pkg/tui/views/dashboard/view.go +++ b/pkg/tui/views/dashboard/view.go @@ -129,7 +129,7 @@ func (m *Model) View() string { if m.choosingPriority { priorityContent := lipgloss.JoinVertical(lipgloss.Left, styles.LabelStyle.Render("Change priority for "+m.priorityIssueID+":"), - lipgloss.NewStyle().Foreground(styles.FaintText).Render("0 = lowest 1 = low 2 = medium 3 = high 4 = highest"), + lipgloss.NewStyle().Foreground(styles.FaintText).Render("0 = irrelevant 1 = low 2 = normal 3 = high 4 = critical"), lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"), ) priorityBoxWidth := min(60, m.width-4) @@ -140,6 +140,20 @@ func (m *Model) View() string { return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, priorityBox) } + if m.choosingType { + typeContent := lipgloss.JoinVertical(lipgloss.Left, + styles.LabelStyle.Render("Change type for "+m.typeIssueID+":"), + lipgloss.NewStyle().Foreground(styles.FaintText).Render("b = bug f = feature t = task e = epic c = chore"), + lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"), + ) + typeBoxWidth := min(65, m.width-4) + typeBox := styles.ContainerStyle. + Width(typeBoxWidth). + BorderForeground(styles.PrimaryBorder). + Render(typeContent) + return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, typeBox) + } + return mainView }