feat(milestone): milestone tables, views, forms and one selection model

milestone view printed the list row, and milestone list hid the due date
and progress. milestone list now shows ID TITLE [STATE] DUE PROGRESS, open
milestones first and by due date; milestone view shows the state, due
date, progress, description and the milestone's web page (also --web).

Every milestone command addresses milestones by title or ID. Mutations
report ✓ lines: milestone create also prints the new milestone's URL and
takes the title as an argument; close and reopen skip milestones already
in that state; delete names the milestone in its confirmation; milestone
issue add and remove report one line such as "✓ Added #1, #2 to
milestone "v1.0" in alice/x". milestone issue list uses the issue table.
milestone edit without flags on a terminal opens the milestone form with
the current values, and every milestone command completes milestone
titles and has help text with examples.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-12 00:17:02 +02:00
1 parent 2ba02cf915
commit c161a63eb6
14 files changed
+606 -257

No files matched your search

+13 -8
View File
@@ -11,25 +11,30 @@ import (
"charm.land/huh/v2"
)
// MilestoneFormValues holds interactively collected milestone create fields.
// MilestoneFormValues holds interactively collected milestone fields.
type MilestoneFormValues struct {
Title, Description string
Deadline *time.Time
}
// MilestoneForm collects interactive milestone create input.
// MilestoneForm collects interactive milestone input.
type MilestoneForm interface {
CreateMilestone(context.Context) (MilestoneFormValues, error)
// EditMilestone asks for the fields of a milestone, starting from current, under heading, such
// as "Editing milestone v1.0". Empty values create a milestone.
EditMilestone(ctx context.Context, heading string, current MilestoneFormValues) (MilestoneFormValues, error)
}
// CreateMilestone runs the interactive milestone create form on stderr.
func (p *FormPrompter) CreateMilestone(ctx context.Context) (MilestoneFormValues, error) {
title, description, deadline := "", "", ""
// EditMilestone runs the interactive milestone form on stderr.
func (p *FormPrompter) EditMilestone(ctx context.Context, heading string, current MilestoneFormValues) (MilestoneFormValues, error) {
title, description, deadline := current.Title, current.Description, ""
if current.Deadline != nil {
deadline = current.Deadline.Format(time.DateOnly)
}
if err := p.runForm(ctx, huh.NewForm(huh.NewGroup(
huh.NewInput().Title("Milestone title (required):").Value(&title).Validate(huh.ValidateNotEmpty()),
huh.NewText().Title("Milestone description (markdown):").Value(&description),
bodyField("Milestone description (markdown):", &description),
dueDateInput("Milestone deadline:", &deadline),
))); err != nil {
).Title(heading))); err != nil {
return MilestoneFormValues{}, err
}
parsed, err := parseDueDate(deadline)