Merge remote-tracking branch 'origin/main' into LPM-43

This commit is contained in:
Robin Olsen
2026-02-24 17:06:40 +01:00
77 changed files with 2465 additions and 1116 deletions

View File

@@ -9,7 +9,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
)
type TUIConfig = service.Config
type Config = service.Config
type Tui struct {
feedbackChan chan task.ValidationFeedback
@@ -20,15 +20,15 @@ func NewTui() *Tui {
return &Tui{}
}
func (t *Tui) Run(ctx context.Context, config TUIConfig) error {
svc, cleanup, err := service.NewServices(ctx, config)
func (t *Tui) Run(ctx context.Context, config Config) error {
app, cleanup, err := service.NewServices(ctx, config)
if err != nil {
return err
}
defer cleanup()
p := tea.NewProgram(views.NewDashboardView(svc, t.feedbackChan, t.quitChan),
p := tea.NewProgram(views.NewDashboardView(app, t.feedbackChan, t.quitChan),
tea.WithAltScreen(), tea.WithMouseAllMotion())
if t.quitChan != nil {

View File

@@ -17,7 +17,7 @@ import (
type IssueList struct {
list list.Model
svc *service.Services
app *service.App
width int
height int
}
@@ -77,8 +77,8 @@ func renderHeaders(cols []TableColumn) string {
return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
}
func NewIssueList(svc *service.Services, width, height int) IssueList {
issues, err := svc.Beads.AllIssues(context.Background())
func NewIssueList(app *service.App, width, height int) IssueList {
issues, err := app.Issues.AllIssues(context.Background())
if err != nil {
return IssueList{}
}
@@ -105,13 +105,13 @@ func NewIssueList(svc *service.Services, width, height int) IssueList {
return IssueList{
list: l,
svc: svc,
app: app,
width: width,
height: height,
}
}
func NewIssueListFromIssues(svc *service.Services, issues []models.Issue, width, height int) IssueList {
func NewIssueListFromIssues(app *service.App, issues []models.Issue, width, height int) IssueList {
// for making an IssueList from a pre-existing list of issues.
listIssues := make([]list.Item, len(issues))
for i, issue := range issues {
@@ -128,7 +128,7 @@ func NewIssueListFromIssues(svc *service.Services, issues []models.Issue, width,
l.FilterInput.Prompt = "🔍 "
return IssueList{
list: l,
svc: svc,
app: app,
width: width,
height: height,
}

View File

@@ -15,21 +15,21 @@ type ValidationFeedbackMsg struct {
}
type Model struct {
header Header
issueList IssueList
issueDetail IssueDetail
closedIssueList IssueList
helpBar HelpBar
keyMap DashboardKeyMap
svc *service.Services
width int
height int
focusedWindow int // 0 = main (display issues), 1 = closed issues
focusedPaneMain int // 0 = list, 1 = detail
header Header
issueList IssueList
issueDetail IssueDetail
closedIssueList IssueList
helpBar HelpBar
keyMap DashboardKeyMap
app *service.App
width int
height int
focusedWindow int // 0 = main (display issues), 1 = closed issues
focusedPaneMain int // 0 = list, 1 = detail
focusedPaneClosed int
editingTitle bool // true while we are editing a title
titleInput textinput.Model
editingIssueID string
editingTitle bool // true while we are editing a title
titleInput textinput.Model
editingIssueID string
editingDescription bool // true while editing a description
descriptionInput textarea.Model
@@ -41,36 +41,36 @@ type Model struct {
deleteConfirmID string
deleteConfirmIndex int
choosingStatus bool // true while choosing a status
choosingStatus bool // true while choosing a status
statusIssueID string
choosingPriority bool // true while choosing a priority
priorityIssueID string
choosingType bool // true while choosing a type
typeIssueID string
typeIssueID string
feedbackChan chan task.ValidationFeedback
quitChan chan bool
currentFeedback task.ValidationFeedback
showComplete bool
}
func NewDashboard(svc *service.Services, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *Model {
func NewDashboard(app *service.App, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *Model {
m := &Model{
header: NewHeader("Project Manager Dashboard"),
keyMap: defaultDashboardKeyMap,
svc: svc,
app: app,
width: 80,
height: 24,
focusedWindow: 0,
focusedPaneMain: 0,
focusedPaneClosed: 0,
feedbackChan: feedbackChan,
quitChan: quitChan,
focusedWindow: 0,
focusedPaneMain: 0,
focusedPaneClosed: 0,
feedbackChan: feedbackChan,
quitChan: quitChan,
}
allIssues, _ := svc.Beads.AllIssues(context.Background())
m.issueList = NewIssueListFromIssues(svc, OpenAndInProgressOnly(allIssues), 0, 0)
allIssues, _ := app.Issues.AllIssues(context.Background())
m.issueList = NewIssueListFromIssues(app, OpenAndInProgressOnly(allIssues), 0, 0)
m.issueDetail = NewIssueDetail()
m.closedIssueList = NewIssueListFromIssues(svc, ClosedOnly(allIssues), 0, 0)
m.closedIssueList = NewIssueListFromIssues(app, ClosedOnly(allIssues), 0, 0)
m.helpBar = NewHelpBar(m.keyMap)
ti := textinput.New()

View File

@@ -49,47 +49,63 @@ type issueDeletedMsg struct {
PreviousIndex int
}
func updateIssueTitleCmd(svc *service.Services, issueID, newTitle string) tea.Cmd {
func updateIssueTitleCmd(app *service.App, issueID, newTitle string) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"title": newTitle}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueTitleUpdatedMsg{IssueID: issueID, Err: err}
}
}
func updateIssueDescriptionCmd(svc *service.Services, issueID, newDescription string) tea.Cmd {
func updateIssueDescriptionCmd(app *service.App, issueID, newDescription string) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"description": newDescription}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueDescriptionUpdatedMsg{IssueID: issueID, Err: err}
}
}
func updateIssueStatusCmd(svc *service.Services, issueID, status string) tea.Cmd {
func updateIssueStatusCmd(app *service.App, issueID, status string) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"status": status}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueStatusUpdatedMsg{IssueID: issueID, Err: err}
}
}
func updateIssuePriorityCmd(svc *service.Services, issueID string, priority int) tea.Cmd {
func updateIssuePriorityCmd(app *service.App, issueID string, priority int) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"priority": priority}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issuePriorityUpdatedMsg{IssueID: issueID, Err: err}
}
}
func updateIssueTypeCmd(svc *service.Services, issueID string, issueType models.IssueType) tea.Cmd {
func updateIssueTypeCmd(app *service.App, 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")
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueTypeUpdatedMsg{IssueID: issueID, Err: err}
}
}
func createIssueCmd(svc *service.Services, title string) tea.Cmd {
func updateIssuePriorityCmd(app *service.App, issueID string, priority int) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"priority": priority}
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issuePriorityUpdatedMsg{IssueID: issueID, Err: err}
}
}
func updateIssueTypeCmd(app *service.App, issueID string, issueType models.IssueType) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"issue_type": string(issueType)}
err := app.Issues.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueTypeUpdatedMsg{IssueID: issueID, Err: err}
}
}
func createIssueCmd(app *service.App, title string) tea.Cmd {
return func() tea.Msg {
issue := &models.Issue{
Title: title,
@@ -97,14 +113,14 @@ func createIssueCmd(svc *service.Services, title string) tea.Cmd {
IssueType: models.TypeTask,
Priority: 2,
}
err := svc.Beads.CreateIssue(context.Background(), issue, "tui")
err := app.Issues.CreateIssue(context.Background(), issue, "tui")
return issueCreatedMsg{Issue: issue, Err: err}
}
}
func deleteIssueCmd(svc *service.Services, issueID string, currentIndex int) tea.Cmd {
func deleteIssueCmd(app *service.App, issueID string, currentIndex int) tea.Cmd {
return func() tea.Msg {
err := svc.Beads.DeleteIssue(context.Background(), issueID)
err := app.Issues.DeleteIssue(context.Background(), issueID)
return issueDeletedMsg{IssueID: issueID, Err: err, PreviousIndex: currentIndex}
}
}
@@ -113,7 +129,7 @@ func (m *Model) refreshIssueListsAndSelectIssue(issueID string) tea.Cmd {
/* update handler for issueTitleUpdatedMsg, issueDescriptionUpdatedMsg, and issueStatusUpdatedMsg to avoid using nearly identical code for refreshing the issue lists and updating the detail view
Fetch all issues, update both lists, set the detail view for the given issue, and return a command to select that issue. Returns nil if fetch fails.
*/
issues, err := m.svc.Beads.AllIssues(context.Background())
issues, err := m.app.Issues.AllIssues(context.Background())
if err != nil {
return nil
}
@@ -184,7 +200,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.Err != nil || msg.Issue == nil {
return m, nil
}
issues, err := m.svc.Beads.AllIssues(context.Background())
issues, err := m.app.Issues.AllIssues(context.Background())
if err != nil {
return m, nil
}
@@ -211,7 +227,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.Err != nil {
return m, nil
}
issues, err := m.svc.Beads.AllIssues(context.Background())
issues, err := m.app.Issues.AllIssues(context.Background())
if err != nil {
return m, nil
}
@@ -267,7 +283,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
idx := m.deleteConfirmIndex
m.confirmingDelete = false
m.deleteConfirmID = ""
return m, deleteIssueCmd(m.svc, issueID, idx)
return m, deleteIssueCmd(m.app, issueID, idx)
case "n", "N", "esc":
m.confirmingDelete = false
m.deleteConfirmID = ""
@@ -281,17 +297,17 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
issueID := m.statusIssueID
m.choosingStatus = false
m.statusIssueID = ""
return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusOpen))
return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusOpen))
case "i":
issueID := m.statusIssueID
m.choosingStatus = false
m.statusIssueID = ""
return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusInProgress))
return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress))
case "c":
issueID := m.statusIssueID
m.choosingStatus = false
m.statusIssueID = ""
return m, updateIssueStatusCmd(m.svc, issueID, string(models.StatusClosed))
return m, updateIssueStatusCmd(m.app, issueID, string(models.StatusClosed))
case "esc":
m.choosingStatus = false
m.statusIssueID = ""
@@ -356,7 +372,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.String() == "enter" {
title := m.createTitleInput.Value()
if title != "" {
return m, createIssueCmd(m.svc, title)
return m, createIssueCmd(m.app, title)
}
}
if msg.String() == "esc" {
@@ -374,7 +390,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg.String() == "enter" {
newTitle := m.titleInput.Value()
if newTitle != "" {
return m, updateIssueTitleCmd(m.svc, m.editingIssueID, newTitle)
return m, updateIssueTitleCmd(m.app, m.editingIssueID, newTitle)
}
}
if msg.String() == "esc" {
@@ -395,7 +411,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.editingDescription = false
m.editingDescIssueID = ""
m.descriptionInput.Blur()
return m, updateIssueDescriptionCmd(m.svc, issueID, newDesc)
return m, updateIssueDescriptionCmd(m.app, issueID, newDesc)
}
if msg.String() == "esc" {
m.editingDescription = false

View File

@@ -6,6 +6,6 @@ import (
"github.com/LazyBachelor/LazyPM/pkg/tui/views/dashboard"
)
func NewDashboardView(svc *service.Services, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *dashboard.Model {
return dashboard.NewDashboard(svc, feedbackChan, quitChan)
func NewDashboardView(app *service.App, feedbackChan chan task.ValidationFeedback, quitChan chan bool) *dashboard.Model {
return dashboard.NewDashboard(app, feedbackChan, quitChan)
}