diff --git a/cmd/pm/tasks/sprintPlanning.go b/cmd/pm/tasks/sprintPlanning.go
index 572fa41..9d5a5fd 100644
--- a/cmd/pm/tasks/sprintPlanning.go
+++ b/cmd/pm/tasks/sprintPlanning.go
@@ -48,6 +48,7 @@ func (t *SprintPlanningTask) Questions(interfaceType InterfaceType) Questions {
return BaseQuestions(interfaceType).With(
huh.NewGroup(
huh.NewSelect[int]().
+ Key("sprint-planning-selected-issues").
Title("How many issues did you select for the sprint?").
Options(
huh.NewOption("2-3 issues", 1),
@@ -58,6 +59,10 @@ func (t *SprintPlanningTask) Questions(interfaceType InterfaceType) Questions {
)
}
+func (t *SprintPlanningTask) QuestionnaireKeys(_ InterfaceType) []string {
+ return []string{"task_completed", "task_difficulty", "sprint-planning-selected-issues"}
+}
+
func (t *SprintPlanningTask) Setup(ctx context.Context) error {
if err := ClearIssues(t.app); err != nil {
return err
@@ -116,6 +121,50 @@ func (t *SprintPlanningTask) Setup(ctx context.Context) error {
func (t *SprintPlanningTask) Validate(ctx context.Context) ValidationFeedback {
expect := check.NewExpector()
- return expect.Complete()
+ issues, err := FetchIssues(ctx, t.app, t.setupIssue)
+ if err != nil {
+ return expect.ValidationFeedback
+ }
+ if len(issues) == 0 {
+ expect.Fail("No backlog issues found to plan a sprint with.")
+ return expect.ValidationFeedback
+ }
+
+ // Sort by priority ascending (0 is highest priority).
+ sorted := make([]*models.Issue, len(issues))
+ copy(sorted, issues)
+ for i := 0; i < len(sorted); i++ {
+ for j := i + 1; j < len(sorted); j++ {
+ if sorted[j].Priority < sorted[i].Priority {
+ sorted[i], sorted[j] = sorted[j], sorted[i]
+ }
+ }
+ }
+
+ topN := 5
+ if len(sorted) < topN {
+ topN = len(sorted)
+ }
+ top := sorted[:topN]
+
+ var plannedCount int
+ var readyToSprintCount int
+ for _, issue := range top {
+ if issue.Status == models.StatusReadyToSprint ||
+ issue.Status == models.StatusInProgress ||
+ issue.Status == models.StatusClosed {
+ plannedCount++
+ }
+ if issue.Status == models.StatusReadyToSprint {
+ readyToSprintCount++
+ }
+ }
+
+ expect.Assert(plannedCount >= 3,
+ "Expected at least 3 of the 5 highest-priority issues to be moved into 'ready_to_sprint', 'in_progress', or 'closed' for the sprint.")
+ expect.Assert(readyToSprintCount >= 1,
+ "Expected at least one of the highest-priority issues to be marked as 'ready_to_sprint' to indicate it is planned for the sprint.")
+
+ return expect.Complete()
}
diff --git a/internal/commands/issues/completion.go b/internal/commands/issues/completion.go
index 0a532e1..19c8a35 100644
--- a/internal/commands/issues/completion.go
+++ b/internal/commands/issues/completion.go
@@ -11,7 +11,7 @@ import (
// Variables for completion options and functions.
var (
typeOptions = []string{"bug", "feature", "task", "chore"}
- statusOptions = []string{"open", "closed", "in_progress"}
+ statusOptions = []string{"open", "closed", "in_progress", "ready_to_sprint"}
priorityRange = []string{"0", "1", "2", "3", "4"}
)
diff --git a/internal/commands/issues/create.go b/internal/commands/issues/create.go
index 2deafed..238e295 100644
--- a/internal/commands/issues/create.go
+++ b/internal/commands/issues/create.go
@@ -112,7 +112,7 @@ func runCreateInteractive() error {
func init() {
//CreateCmd.Flags().BoolVarP(&createFlags.interactive, "interactive", "i", false, "Create issue interactively")
CreateCmd.Flags().StringVarP(&createFlags.description, "desc", "d", "", "Issue description")
- CreateCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress)")
+ CreateCmd.Flags().StringVarP(&createFlags.status, "status", "s", "open", "Issue status(open, closed, in_progress, ready_to_sprint)")
CreateCmd.Flags().StringVarP(&createFlags.issueType, "type", "t", "task", "Issue type(bug, feature, task)")
CreateCmd.Flags().IntVarP(&createFlags.priority, "priority", "p", 0, "Issue priority(0-4)")
CreateCmd.Flags().StringVarP(&createFlags.assignee, "assignee", "a", "", "Issue assignee")
diff --git a/internal/commands/issues/list.go b/internal/commands/issues/list.go
index f61d869..e9eae24 100644
--- a/internal/commands/issues/list.go
+++ b/internal/commands/issues/list.go
@@ -76,7 +76,7 @@ func runGetIssuesCmd(cmd *cobra.Command, args []string) error {
func init() {
ListCmd.Flags().StringVar(&listFlags.title, "title", "", "Filter issues by title")
ListCmd.Flags().StringVarP(&listFlags.description, "desc", "d", "", "Filter issues by description")
- ListCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress)")
+ ListCmd.Flags().StringVarP(&listFlags.status, "status", "s", "", "Filter issues by status (open, closed, in_progress, ready_to_sprint)")
ListCmd.Flags().StringVarP(&listFlags.issueType, "type", "t", "", "Filter issues by type (bug, feature, task)")
ListCmd.Flags().IntVarP(&listFlags.priority, "priority", "p", 0, "Filter issues by priority (0-4)")
ListCmd.Flags().StringVarP(&listFlags.assignee, "assignee", "a", "", "Filter issues by assignee")
diff --git a/internal/commands/issues/update.go b/internal/commands/issues/update.go
index f1dac02..29a6b29 100644
--- a/internal/commands/issues/update.go
+++ b/internal/commands/issues/update.go
@@ -58,7 +58,7 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error {
func init() {
UpdateCmd.Flags().StringVar(&updateFlags.title, "title", "", "New issue title")
UpdateCmd.Flags().StringVarP(&updateFlags.description, "desc", "d", "", "New issue description")
- UpdateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress)")
+ UpdateCmd.Flags().StringVarP(&updateFlags.status, "status", "s", "", "New issue status(open, closed, in_progress, ready_to_sprint)")
UpdateCmd.Flags().StringVarP(&updateFlags.issueType, "type", "t", "", "New issue type(bug, feature, task)")
UpdateCmd.Flags().IntVarP(&updateFlags.priority, "priority", "p", 0, "New issue priority(0-4)")
UpdateCmd.Flags().StringVarP(&updateFlags.assignee, "assignee", "a", "", "New issue assignee")
diff --git a/internal/models/beads.go b/internal/models/beads.go
index a25d480..624ed6d 100644
--- a/internal/models/beads.go
+++ b/internal/models/beads.go
@@ -3,7 +3,9 @@ package models
import (
"fmt"
"os"
+ "strings"
"text/tabwriter"
+ "unicode"
"charm.land/lipgloss/v2"
"github.com/muesli/reflow/truncate"
@@ -33,11 +35,12 @@ type (
// Status constants
const (
- StatusOpen = beads.StatusOpen
- StatusInProgress = beads.StatusInProgress
- StatusBlocked = beads.StatusBlocked
- StatusDeferred = beads.StatusDeferred
- StatusClosed = beads.StatusClosed
+ StatusOpen = beads.StatusOpen
+ StatusInProgress = beads.StatusInProgress
+ StatusBlocked = beads.StatusBlocked
+ StatusDeferred = beads.StatusDeferred
+ StatusClosed = beads.StatusClosed
+ StatusReadyToSprint Status = "ready_to_sprint"
)
// IssueType constants
@@ -113,20 +116,39 @@ func IssuesPtrToIssues(issuePtr []*Issue) []Issue {
return issues
}
+func sanitizeCell(s string) string {
+ s = strings.Map(func(r rune) rune {
+ switch r {
+ case '\n', '\r', '\t':
+ return ' '
+ default:
+ return r
+ }
+ }, s)
+
+ return strings.Join(strings.FieldsFunc(s, unicode.IsSpace), " ")
+}
+
func FormatIssueRow(issue Issue) string {
+ id := truncate.String(sanitizeCell(issue.ID), 10)
+ title := truncate.StringWithTail(sanitizeCell(issue.Title), 35, "...")
+ description := truncate.StringWithTail(sanitizeCell(issue.Description), 40, "...")
+ status := sanitizeCell(string(issue.Status))
+ issueType := sanitizeCell(string(issue.IssueType))
+
return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%d",
- truncate.String(issue.ID, 10),
- truncate.StringWithTail(issue.Title, 25, "..."),
- truncate.StringWithTail(issue.Description, 40, "..."),
- issue.Status,
- issue.IssueType,
+ id,
+ title,
+ description,
+ status,
+ issueType,
issue.Priority,
)
}
func PrintIssues(issues []Issue) {
- w := tabwriter.NewWriter(os.Stdout, 8, 10, 5, ' ', 0)
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "ID\tTITLE\tDESCRIPTION\tSTATUS\tTYPE\tPRIORITY")
@@ -134,5 +156,5 @@ func PrintIssues(issues []Issue) {
fmt.Fprintln(w, FormatIssueRow(issue))
}
- w.Flush()
+ _ = w.Flush()
}
diff --git a/internal/storage/beads.go b/internal/storage/beads.go
index 3d9ecf9..294da1f 100644
--- a/internal/storage/beads.go
+++ b/internal/storage/beads.go
@@ -21,6 +21,8 @@ func NewBeadsIssueStorage(ctx context.Context, storage beads.Storage, prefix str
}
}
+ storage.SetConfig(ctx, "status.custom", "ready_to_sprint")
+
return &BeadsService{
Storage: storage,
}, nil
diff --git a/pkg/repl/suggestions.go b/pkg/repl/suggestions.go
index 07e36a6..8785944 100644
--- a/pkg/repl/suggestions.go
+++ b/pkg/repl/suggestions.go
@@ -79,6 +79,7 @@ var statusValues = []prompt.Suggest{
{Text: "open", Description: "Open status"},
{Text: "closed", Description: "Closed status"},
{Text: "in_progress", Description: "In progress status"},
+ {Text: "ready_to_sprint", Description: "Ready to sprint status"},
}
// typeValues is a list of prompt suggestions for issue types
diff --git a/pkg/tui/components/issue_list.go b/pkg/tui/components/issue_list.go
index 1667e34..2f237d4 100644
--- a/pkg/tui/components/issue_list.go
+++ b/pkg/tui/components/issue_list.go
@@ -213,7 +213,9 @@ func OpenAndInProgressOnly(issues []*models.Issue) []*models.Issue {
// used to display open & in-progress issues in the first window in the dashboard
out := make([]*models.Issue, 0, len(issues))
for _, issue := range issues {
- if issue.Status == models.StatusOpen || issue.Status == models.StatusInProgress {
+ if issue.Status == models.StatusOpen ||
+ issue.Status == models.StatusInProgress ||
+ issue.Status == models.StatusReadyToSprint {
out = append(out, issue)
}
}
@@ -237,7 +239,8 @@ func ClosedOnly(issues []*models.Issue) []*models.Issue {
func StatusOnly(issues []*models.Issue, status models.Status) []*models.Issue {
out := make([]*models.Issue, 0, len(issues))
for _, issue := range issues {
- if issue.Status == status {
+ if issue.Status == status ||
+ (status == models.StatusOpen && issue.Status == models.StatusReadyToSprint) {
out = append(out, issue)
}
}
diff --git a/pkg/tui/components/modals.go b/pkg/tui/components/modals.go
index b2b5d75..2171d73 100644
--- a/pkg/tui/components/modals.go
+++ b/pkg/tui/components/modals.go
@@ -90,7 +90,7 @@ func RenderChooseStatus(width, height int, issueID string) string {
}
statusContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Change status for "+issueID+":"),
- lipgloss.NewStyle().Foreground(styles.FaintText).Render("o = open i = in_progress c = closed"),
+ lipgloss.NewStyle().Foreground(styles.FaintText).Render("o = open i = in_progress c = closed ready_to_sprint = r"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
statusBoxWidth := modalBoxWidth(50, width)
diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go
index 305f24f..2ad7524 100644
--- a/pkg/tui/views/dashboard/operations.go
+++ b/pkg/tui/views/dashboard/operations.go
@@ -343,6 +343,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.choosingStatus = false
m.statusIssueID = ""
return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress))
+ case "r":
+ m.logAction("tui selected issue status ready_to_sprint")
+ issueID := m.statusIssueID
+ m.choosingStatus = false
+ m.statusIssueID = ""
+ return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusReadyToSprint))
case "c":
m.logAction("tui selected issue status closed")
issueID := m.statusIssueID
diff --git a/pkg/tui/views/dashboard/view.go b/pkg/tui/views/dashboard/view.go
index b395f51..f32ac91 100644
--- a/pkg/tui/views/dashboard/view.go
+++ b/pkg/tui/views/dashboard/view.go
@@ -135,7 +135,7 @@ func (m *Model) View() string {
if m.choosingStatus {
statusContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Change status for "+m.statusIssueID+":"),
- lipgloss.NewStyle().Foreground(styles.FaintText).Render("o = open i = in_progress c = closed"),
+ lipgloss.NewStyle().Foreground(styles.FaintText).Render("o = open i = in_progress c = closed ready_to_sprint = r"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
statusBoxWidth := min(50, m.width-4)
diff --git a/pkg/tui/views/kanban/operations.go b/pkg/tui/views/kanban/operations.go
index 03a6beb..8a0e040 100644
--- a/pkg/tui/views/kanban/operations.go
+++ b/pkg/tui/views/kanban/operations.go
@@ -244,6 +244,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.choosingStatus = false
m.statusIssueID = ""
return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress))
+ case "r":
+ issueID := m.statusIssueID
+ m.choosingStatus = false
+ m.statusIssueID = ""
+ return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusReadyToSprint))
case "c":
issueID := m.statusIssueID
m.choosingStatus = false
diff --git a/pkg/web/components/issue.templ b/pkg/web/components/issue.templ
index 894bdbd..faedc3c 100644
--- a/pkg/web/components/issue.templ
+++ b/pkg/web/components/issue.templ
@@ -57,6 +57,7 @@ templ IssueForm(props IssueFormProps) {
Options: []base.SelectOption{
{Label: "Open", Value: "open", Selected: props.Status == "open"},
{Label: "In Progress", Value: "in_progress", Selected: props.Status == "in_progress"},
+ {Label: "Ready to sprint", Value: "ready_to_sprint", Selected: props.Status == "ready_to_sprint"},
{Label: "Closed", Value: "closed", Selected: props.Status == "closed"},
},
Size: "md",
@@ -159,7 +160,9 @@ templ IssueRows(issues []*models.Issue) {
hx-push-url="true"
>{ issue.Title }
-
{ string(issue.Status) } |
+
+ @StatusBadge(issue.Status)
+ |
{ string(issue.IssueType) } |
{ fmt.Sprintf("P%d", issue.Priority) } |
diff --git a/pkg/web/components/issue_detail.templ b/pkg/web/components/issue_detail.templ
index f8b811a..04f101a 100644
--- a/pkg/web/components/issue_detail.templ
+++ b/pkg/web/components/issue_detail.templ
@@ -60,6 +60,8 @@ templ StatusBadge(status models.Status) {
Open
case "in_progress":
In Progress
+ case "ready_to_sprint":
+ Ready to sprint
case "closed":
Closed
case "blocked":
diff --git a/pkg/web/components/issue_detail_templ.go b/pkg/web/components/issue_detail_templ.go
index 45b2592..c405ee5 100644
--- a/pkg/web/components/issue_detail_templ.go
+++ b/pkg/web/components/issue_detail_templ.go
@@ -211,36 +211,41 @@ func StatusBadge(status models.Status) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+ case "ready_to_sprint":
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Ready to sprint")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
case "closed":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Closed")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "Closed")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "blocked":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "Blocked")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Blocked")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "deferred":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "Deferred")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "Deferred")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(string(status))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 70, Col: 60}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 72, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -272,45 +277,45 @@ func TypeBadge(issueType models.IssueType) templ.Component {
ctx = templ.ClearChildren(ctx)
switch issueType {
case "bug":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "Bug")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Bug")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "feature":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "Feature")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Feature")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "task":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "Task")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "Task")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "chore":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "Chore")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Chore")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case "epic":
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "Epic")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "Epic")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(string(issueType))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 87, Col: 65}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 89, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -342,45 +347,45 @@ func PriorityBadge(priority int) templ.Component {
ctx = templ.ClearChildren(ctx)
switch priority {
case 0:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "Irrelevant")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "Irrelevant")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 1:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "Low")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "Low")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "Normal")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "Normal")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "High")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "High")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 4:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "Critical")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "Critical")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
default:
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", priority))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 104, Col: 74}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue_detail.templ`, Line: 106, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/web/components/issue_templ.go b/pkg/web/components/issue_templ.go
index d055a8e..1e57c18 100644
--- a/pkg/web/components/issue_templ.go
+++ b/pkg/web/components/issue_templ.go
@@ -180,6 +180,7 @@ func IssueForm(props IssueFormProps) templ.Component {
Options: []base.SelectOption{
{Label: "Open", Value: "open", Selected: props.Status == "open"},
{Label: "In Progress", Value: "in_progress", Selected: props.Status == "in_progress"},
+ {Label: "Ready to sprint", Value: "ready_to_sprint", Selected: props.Status == "ready_to_sprint"},
{Label: "Closed", Value: "closed", Selected: props.Status == "closed"},
},
Size: "md",
@@ -248,7 +249,7 @@ func IssueForm(props IssueFormProps) templ.Component {
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.DeleteAction)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 107, Col: 31}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 108, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
@@ -348,7 +349,7 @@ func IssueRows(issues []*models.Issue) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 147, Col: 35}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 148, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -361,7 +362,7 @@ func IssueRows(issues []*models.Issue) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 151, Col: 15}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 152, Col: 15}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -374,7 +375,7 @@ func IssueRows(issues []*models.Issue) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 156, Col: 35}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 157, Col: 35}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@@ -387,7 +388,7 @@ func IssueRows(issues []*models.Issue) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 160, Col: 18}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 161, Col: 18}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -397,12 +398,7 @@ func IssueRows(issues []*models.Issue) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var15 string
- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.Status))
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 162, Col: 54}
- }
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
+ templ_7745c5c3_Err = StatusBadge(issue.Status).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -410,12 +406,12 @@ func IssueRows(issues []*models.Issue) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var16 string
- templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.IssueType))
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.IssueType))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 163, Col: 57}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 166, Col: 57}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -423,12 +419,12 @@ func IssueRows(issues []*models.Issue) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var17 string
- templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", issue.Priority))
+ var templ_7745c5c3_Var16 string
+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", issue.Priority))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 164, Col: 68}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 167, Col: 68}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -436,12 +432,12 @@ func IssueRows(issues []*models.Issue) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var18 string
- templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID)
+ var templ_7745c5c3_Var17 string
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 169, Col: 38}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 172, Col: 38}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go
index 3306c7a..aad3f63 100644
--- a/pkg/web/handler/issues.go
+++ b/pkg/web/handler/issues.go
@@ -17,7 +17,7 @@ const commentsKey = "comments"
type IssueForm struct {
Title string `form:"title" validate:"required,max=255"`
Description string `form:"description" validate:"max=2000"`
- Status models.Status `form:"status" validate:"required,oneof=open in_progress closed"`
+ Status models.Status `form:"status" validate:"required,oneof=open in_progress ready_to_sprint closed"`
IssueType models.IssueType `form:"issue_type" validate:"required,oneof=task bug feature chore"`
Priority int `form:"priority" validate:"gte=0,lte=4"`
}
@@ -25,7 +25,7 @@ type IssueForm struct {
type UpdateIssueForm struct {
Title *string `form:"title" validate:"omitempty,max=255"`
Description *string `form:"description" validate:"omitempty,max=2000"`
- Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress closed"`
+ Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress ready_to_sprint closed"`
CloseReason *string `form:"close_reason" validate:"omitempty,max=2000"`
IssueType *models.IssueType `form:"issue_type" validate:"omitempty,oneof=task bug feature chore"`
Priority *int `form:"priority" validate:"omitempty,gte=0,lte=4"`
diff --git a/pkg/web/routes/boardview.templ b/pkg/web/routes/boardview.templ
index 13927dc..e926309 100644
--- a/pkg/web/routes/boardview.templ
+++ b/pkg/web/routes/boardview.templ
@@ -62,6 +62,7 @@ templ BoardColumns(issues []*models.Issue) {
{{
openIssues := []*models.Issue{}
inProgressIssues := []*models.Issue{}
+ readyToSprintIssues := []*models.Issue{}
closedIssues := []*models.Issue{}
for _, issue := range issues {
@@ -70,6 +71,8 @@ templ BoardColumns(issues []*models.Issue) {
openIssues = append(openIssues, issue)
case "in_progress":
inProgressIssues = append(inProgressIssues, issue)
+ case "ready_to_sprint":
+ readyToSprintIssues = append(readyToSprintIssues, issue)
case "closed":
closedIssues = append(closedIssues, issue)
}
@@ -78,6 +81,7 @@ templ BoardColumns(issues []*models.Issue) {
@BoardColumn("Open", "open", openIssues, "badge-info")
@BoardColumn("In Progress", "in_progress", inProgressIssues, "badge-warning")
+ @BoardColumn("Ready to sprint", "ready_to_sprint", readyToSprintIssues, "badge-primary")
@BoardColumn("Closed", "closed", closedIssues, "badge-success")
}
diff --git a/pkg/web/routes/boardview_templ.go b/pkg/web/routes/boardview_templ.go
index 406eedf..a7322a5 100644
--- a/pkg/web/routes/boardview_templ.go
+++ b/pkg/web/routes/boardview_templ.go
@@ -140,6 +140,7 @@ func BoardColumns(issues []*models.Issue) templ.Component {
ctx = templ.ClearChildren(ctx)
openIssues := []*models.Issue{}
inProgressIssues := []*models.Issue{}
+ readyToSprintIssues := []*models.Issue{}
closedIssues := []*models.Issue{}
for _, issue := range issues {
@@ -148,6 +149,8 @@ func BoardColumns(issues []*models.Issue) templ.Component {
openIssues = append(openIssues, issue)
case "in_progress":
inProgressIssues = append(inProgressIssues, issue)
+ case "ready_to_sprint":
+ readyToSprintIssues = append(readyToSprintIssues, issue)
case "closed":
closedIssues = append(closedIssues, issue)
}
@@ -164,6 +167,10 @@ func BoardColumns(issues []*models.Issue) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
+ templ_7745c5c3_Err = BoardColumn("Ready to sprint", "ready_to_sprint", readyToSprintIssues, "badge-primary").Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
templ_7745c5c3_Err = BoardColumn("Closed", "closed", closedIssues, "badge-success").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
@@ -204,7 +211,7 @@ func BoardColumn(title string, status string, issues []*models.Issue, badgeClass
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(status)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 86, Col: 77}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 90, Col: 77}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -217,7 +224,7 @@ func BoardColumn(title string, status string, issues []*models.Issue, badgeClass
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 88, Col: 44}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 92, Col: 44}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
@@ -252,7 +259,7 @@ func BoardColumn(title string, status string, issues []*models.Issue, badgeClass
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(issues)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 89, Col: 71}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 93, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
@@ -265,7 +272,7 @@ func BoardColumn(title string, status string, issues []*models.Issue, badgeClass
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(status)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 91, Col: 110}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 95, Col: 110}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -323,7 +330,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 106, Col: 26}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 110, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
@@ -336,7 +343,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 110, Col: 80}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 114, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
if templ_7745c5c3_Err != nil {
@@ -349,7 +356,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID + "?from=board")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 114, Col: 52}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 118, Col: 52}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
@@ -362,7 +369,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID + "/edit?from=board")
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 126, Col: 57}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 130, Col: 57}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
@@ -375,7 +382,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 138, Col: 65}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 142, Col: 65}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
@@ -405,7 +412,7 @@ func BoardCard(issue *models.Issue) templ.Component {
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Description)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 143, Col: 81}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/boardview.templ`, Line: 147, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
|