From 90dd69a019c6186146a3fbabc258dbb55c7c4e45 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Sun, 8 Feb 2026 15:03:45 +0100 Subject: [PATCH 01/29] add introduction to the survey add lib to read toml files embed fs for reading toml files add form for introduction add form for choosing interface --- cmd/survey.go | 44 ---------------------- cmd/survey/assets/intro.toml | 10 +++++ cmd/survey/forms/intro.go | 56 ++++++++++++++++++++++++++++ cmd/survey/survey.go | 72 ++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 2 + 6 files changed, 141 insertions(+), 44 deletions(-) delete mode 100644 cmd/survey.go create mode 100644 cmd/survey/assets/intro.toml create mode 100644 cmd/survey/forms/intro.go create mode 100644 cmd/survey/survey.go diff --git a/cmd/survey.go b/cmd/survey.go deleted file mode 100644 index a6a9922..0000000 --- a/cmd/survey.go +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/pkg" - "github.com/LazyBachelor/LazyPM/pkg/cli" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" - "github.com/LazyBachelor/LazyPM/pkg/tui" - "github.com/LazyBachelor/LazyPM/pkg/web" -) - -func main() { - config := pkg.SurveyConfig{ - RootCmd: "pm", - WebAddress: "localhost:8080", - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - - ctx := context.Background() - var err error - - switch os.Args[1] { - case "tui": - err = tui.Run(ctx, config) - case "cli": - err = cli.RunWithArgs(ctx, config, os.Args[2:]) - case "repl": - err = repl.RunREPL(ctx, config) - case "web": - err = web.Run(ctx, config) - default: - err = fmt.Errorf("unknown command: %s", os.Args[1]) - } - - if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) - } -} diff --git a/cmd/survey/assets/intro.toml b/cmd/survey/assets/intro.toml new file mode 100644 index 0000000..845895b --- /dev/null +++ b/cmd/survey/assets/intro.toml @@ -0,0 +1,10 @@ +Title = "Welcome to the PM CLI Survey!" +Description = "Thank you for taking the time to participate in our survey. Your feedback is crucial in helping us improve the PM CLI and make it a more effective tool for project management. Please read the following information about the PM CLI before proceeding with the survey." + +[About] +Title = "About the PM CLI" +Description = "YO PM CLI is a tool designed to help you manage your projects more efficiently. It provides features such as task management, issue tracking, and team collaboration, all through a simple command-line interface." + +[Disclaimer] +Title = "Disclaimer" +Description = "Please note that the PM CLI is currently in its early stages of development. We are actively seeking feedback to improve the tool, so your input is highly valuable to us. By participating in this survey, you agree to provide honest and constructive feedback to help shape the future of the PM CLI." \ No newline at end of file diff --git a/cmd/survey/forms/intro.go b/cmd/survey/forms/intro.go new file mode 100644 index 0000000..fd4be96 --- /dev/null +++ b/cmd/survey/forms/intro.go @@ -0,0 +1,56 @@ +package forms + +import ( + "embed" + "fmt" + + "github.com/BurntSushi/toml" + "github.com/charmbracelet/huh" +) + +type Introduction struct { + Title string + Description string + About struct { + Title string + Description string + } + Disclaimer struct { + Title string + Description string + } +} + +func NewIntroduction(fs embed.FS) (Introduction, error) { + var intro Introduction + if _, err := toml.DecodeFS(fs, "assets/intro.toml", &intro); err != nil { + return Introduction{}, err + } + + return intro, nil +} + +func (i Introduction) Run() error { + fmt.Print("\033[H\033[2J") + + form := huh.NewForm( + huh.NewGroup( + huh.NewNote(). + Title(i.Title). + Description(i.Description), + ), + huh.NewGroup( + huh.NewNote(). + Title(i.About.Title). + Description(i.About.Description), + ), + huh.NewGroup( + huh.NewNote(). + Title(i.Disclaimer.Title). + Description(i.Disclaimer.Description), + ), + ).WithLayout(huh.LayoutStack). + WithTheme(huh.ThemeBase16()) + + return form.Run() +} diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go new file mode 100644 index 0000000..71cd91f --- /dev/null +++ b/cmd/survey/survey.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "embed" + "fmt" + "os" + + "github.com/LazyBachelor/LazyPM/cmd/survey/forms" + "github.com/LazyBachelor/LazyPM/pkg" + "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" + "github.com/charmbracelet/huh" +) + +//go:embed assets/* +var assetsFS embed.FS + +func main() { + intro, err := forms.NewIntroduction(assetsFS) + if err != nil { + fmt.Fprintf(os.Stderr, "Error loading introduction: %v\n", err) + os.Exit(1) + } + + if err := intro.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running introduction: %v\n", err) + os.Exit(1) + } + + var selected string + prompt := huh.NewSelect[string]().Options( + huh.NewOption("Start CLI in REPL Mode", "repl"), + huh.NewOption("Start Web Interface", "web"), + huh.NewOption("Start TUI Interface", "tui"), + ).Value(&selected).WithTheme(huh.ThemeBase16()) + + if err := prompt.Run(); err != nil { + fmt.Fprintf(os.Stderr, "Error running prompt: %v\n", err) + os.Exit(1) + } + + config := pkg.SurveyConfig{ + RootCmd: "pm", + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + WebAddress: "localhost:8080", + } + + ctx := context.Background() + switch selected { + case "repl": + fmt.Println("Starting CLI in REPL mode...") + err = repl.RunREPL(ctx, config) + case "web": + fmt.Println("Starting Web Interface...") + err = web.Run(ctx, config) + case "tui": + fmt.Println("Starting TUI Interface...") + err = tui.Run(ctx, config) + default: + fmt.Println("Invalid selection. Exiting.") + } + + if err != nil { + fmt.Fprintf(os.Stderr, "Error running selected interface: %v\n", err) + os.Exit(1) + } + +} diff --git a/go.mod b/go.mod index 04c7cad..fad1402 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/LazyBachelor/LazyPM go 1.25.6 require ( + github.com/BurntSushi/toml v1.6.0 github.com/Dicklesworthstone/beads_viewer v0.14.3 github.com/NYTimes/gziphandler v1.1.1 github.com/a-h/templ v0.3.977 diff --git a/go.sum b/go.sum index dba30f8..14f3663 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dk git.sr.ht/~sbinet/gg v0.7.0 h1:YmNf7YKd7diDMTPm86hZa1EM3pbkOyD/zzjl0LZUdNM= git.sr.ht/~sbinet/gg v0.7.0/go.mod h1:VYeli15tpMM4EvqlivlVbbyvWZlOU+EZn4XZmfBGUdM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Dicklesworthstone/beads_viewer v0.14.3 h1:DxeICQESYvrH+8cn/Pymlb7l9Xm5XL6BTyi6KWiY3H8= github.com/Dicklesworthstone/beads_viewer v0.14.3/go.mod h1:5oEV2h+PVmBTdQpOijnlWMlG/qi+zheXbgOUlqKmWKI= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= From 0b840f87d8a36975a83d54e76f7cdd65870e9436 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 19:56:26 +0100 Subject: [PATCH 02/29] remove survery intro, will find better way --- cmd/survey/assets/intro.toml | 10 ------- cmd/survey/forms/intro.go | 56 ------------------------------------ 2 files changed, 66 deletions(-) delete mode 100644 cmd/survey/assets/intro.toml delete mode 100644 cmd/survey/forms/intro.go diff --git a/cmd/survey/assets/intro.toml b/cmd/survey/assets/intro.toml deleted file mode 100644 index 845895b..0000000 --- a/cmd/survey/assets/intro.toml +++ /dev/null @@ -1,10 +0,0 @@ -Title = "Welcome to the PM CLI Survey!" -Description = "Thank you for taking the time to participate in our survey. Your feedback is crucial in helping us improve the PM CLI and make it a more effective tool for project management. Please read the following information about the PM CLI before proceeding with the survey." - -[About] -Title = "About the PM CLI" -Description = "YO PM CLI is a tool designed to help you manage your projects more efficiently. It provides features such as task management, issue tracking, and team collaboration, all through a simple command-line interface." - -[Disclaimer] -Title = "Disclaimer" -Description = "Please note that the PM CLI is currently in its early stages of development. We are actively seeking feedback to improve the tool, so your input is highly valuable to us. By participating in this survey, you agree to provide honest and constructive feedback to help shape the future of the PM CLI." \ No newline at end of file diff --git a/cmd/survey/forms/intro.go b/cmd/survey/forms/intro.go deleted file mode 100644 index fd4be96..0000000 --- a/cmd/survey/forms/intro.go +++ /dev/null @@ -1,56 +0,0 @@ -package forms - -import ( - "embed" - "fmt" - - "github.com/BurntSushi/toml" - "github.com/charmbracelet/huh" -) - -type Introduction struct { - Title string - Description string - About struct { - Title string - Description string - } - Disclaimer struct { - Title string - Description string - } -} - -func NewIntroduction(fs embed.FS) (Introduction, error) { - var intro Introduction - if _, err := toml.DecodeFS(fs, "assets/intro.toml", &intro); err != nil { - return Introduction{}, err - } - - return intro, nil -} - -func (i Introduction) Run() error { - fmt.Print("\033[H\033[2J") - - form := huh.NewForm( - huh.NewGroup( - huh.NewNote(). - Title(i.Title). - Description(i.Description), - ), - huh.NewGroup( - huh.NewNote(). - Title(i.About.Title). - Description(i.About.Description), - ), - huh.NewGroup( - huh.NewNote(). - Title(i.Disclaimer.Title). - Description(i.Disclaimer.Description), - ), - ).WithLayout(huh.LayoutStack). - WithTheme(huh.ThemeBase16()) - - return form.Run() -} From 08ded5384cff5f317b6fc20fa8538287c4488dea Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 19:57:52 +0100 Subject: [PATCH 03/29] change signature of interfaces to fit Interface interface --- pkg/cli/cli.go | 10 ++++++++-- pkg/cli/repl/repl.go | 8 +++++++- pkg/tui/tui.go | 18 +++++++++++++----- pkg/web/web.go | 8 +++++++- 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c3f515c..27dd22a 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -11,8 +11,14 @@ import ( // CLIConfig is an alias for service.Config, used to configure the CLI. type CLIConfig = service.Config +type CLI struct{} + +func NewCli() *CLI { + return &CLI{} +} + // Run initializes the services and executes the CLI commands. -func Run(ctx context.Context, config CLIConfig) error { +func (c *CLI) Run(ctx context.Context, config CLIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err @@ -30,7 +36,7 @@ func Run(ctx context.Context, config CLIConfig) error { } // RunWithArgs initializes the services and executes the CLI commands with the provided arguments. -func RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { +func (c *CLI) RunWithArgs(ctx context.Context, config CLIConfig, args []string) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err diff --git a/pkg/cli/repl/repl.go b/pkg/cli/repl/repl.go index cba9569..1e9ffbd 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/cli/repl/repl.go @@ -22,8 +22,14 @@ You can also run shell commands directly. Type 'exit' or 'quit' to leave.` ReplTitle = "Welcome to Project Management CLI! " + ReplHelp ) +type REPL struct{} + +func NewRepl() *REPL { + return &REPL{} +} + // RunREPL starts the interactive Read-Eval-Print Loop for the PM CLI. -func RunREPL(ctx context.Context, config cli.CLIConfig) error { +func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { // Set terminal to raw mode to capture input properly in the REPL. // This allows us to handle input character by character and provide a better user experience. // We also ensure that the terminal state is restored when the REPL exits, even if an error occurs. diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index b0feda5..7a26ed7 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -10,16 +10,24 @@ import ( type TUIConfig = service.Config -func Run(ctx context.Context, config TUIConfig) (tea.Model, error) { +type Tui struct{} + +func NewTui() *Tui { + return &Tui{} +} + +func (t *Tui) Run(ctx context.Context, config TUIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { - return nil, err + return nil } defer cleanup() - app := tea.NewProgram(views.NewDashboardView(svc), - tea.WithAltScreen(), tea.WithMouseAllMotion()) + if _, err := tea.NewProgram(views.NewDashboardView(svc), + tea.WithAltScreen(), tea.WithMouseAllMotion()).Run(); err != nil { + return err + } - return app.Run() + return nil } diff --git a/pkg/web/web.go b/pkg/web/web.go index 0670da8..f38215c 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -11,10 +11,16 @@ import ( type WebConfig = service.Config +type Web struct{} + +func NewWeb() *Web { + return &Web{} +} + //go:embed assets/* var assets embed.FS -func Run(ctx context.Context, config WebConfig) error { +func (w Web) Run(ctx context.Context, config WebConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err From f9fe03a45135d77784f209634a2188e12c99a406 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 19:58:23 +0100 Subject: [PATCH 04/29] add delete issues for deleting all issues add db for interactig directly with db --- internal/service/service.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/internal/service/service.go b/internal/service/service.go index f36730e..8210ae7 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -2,6 +2,7 @@ package service import ( "context" + "database/sql" "fmt" "os" "time" @@ -24,6 +25,7 @@ type Config struct { type Services struct { Config Config + DB *sql.DB Beads *BeadsService Statistics *StatisticsService } @@ -36,6 +38,12 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) os.Exit(0) } + db, err := sql.Open("sqlite3", config.BeadsDBPath) + if err != nil { + return nil, nil, err + } + cleanupFuncs = append(cleanupFuncs, func() { db.Close() }) + store, err := beads.NewSQLiteStorage(ctx, config.BeadsDBPath) if err != nil { return nil, nil, err @@ -59,6 +67,7 @@ func NewServices(ctx context.Context, config Config) (*Services, func(), error) } return &Services{ + DB: db, Beads: beadsSvc, Statistics: statSvc, Config: config, @@ -92,3 +101,13 @@ func initialized(beadsPath string) bool { } return true } + +func (s *Services) DeleteIssues() error { + + var deleteIssues = "DELETE FROM issues;" + + if _, err := s.DB.Exec(deleteIssues); err != nil { + return err + } + return nil +} From ba4394ecdca1990537e37646ca7d928bdfef87fa Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 19:59:05 +0100 Subject: [PATCH 05/29] construct structs --- cmd/pm/main.go | 2 ++ cmd/tui/main.go | 4 +++- cmd/web/main.go | 7 +++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cmd/pm/main.go b/cmd/pm/main.go index 5ad70e1..d067ef4 100644 --- a/cmd/pm/main.go +++ b/cmd/pm/main.go @@ -14,6 +14,8 @@ func main() { StatisticsStoragePath: "./.pm/stats.json", } + cli := cli.NewCli() + if err := cli.Run(context.Background(), config); err != nil { return } diff --git a/cmd/tui/main.go b/cmd/tui/main.go index f53021f..824e6aa 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -13,7 +13,9 @@ func main() { IssuePrefix: "pm", } - if _, err := tui.Run(context.Background(), config); err != nil { + tui := tui.NewTui() + + if err := tui.Run(context.Background(), config); err != nil { panic(err) } } diff --git a/cmd/web/main.go b/cmd/web/main.go index bf8542b..1cb7518 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -1,14 +1,17 @@ package main import ( - "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/web" "context" "fmt" "os" + + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/web" ) func main() { + web := web.NewWeb() + config := service.Config{ WebAddress: "localhost:8080", BeadsDBPath: "./.pm/db.db", From 5d97ef1fe7772902271c8f3b98cb07625693c26d Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:38:28 +0100 Subject: [PATCH 06/29] remove test main --- main.go | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 main.go diff --git a/main.go b/main.go deleted file mode 100644 index 25d2bc4..0000000 --- a/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - - "github.com/LazyBachelor/LazyPM/internal/models" - "github.com/LazyBachelor/LazyPM/internal/service" -) - -func main() { - ctx := context.Background() - - config := service.Config{ - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - } - svc, cleanup, err := service.NewServices(ctx, config) - checkErr(err) - - defer cleanup() - - issue := &models.Issue{ - IssueType: models.TypeTask, - Title: "Sample Issue", - Description: "This is a sample issue created for testing.", - Status: models.StatusOpen, - } - - err = svc.Beads.CreateIssue(ctx, issue, "") - checkErr(err) - - fetchedIssues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) - checkErr(err) - - for _, iss := range fetchedIssues { - fmt.Printf("Issue ID: %s, Title: %s, Status: %s\n", iss.ID, iss.Title, iss.Status) - } - - stats, err := svc.Statistics.GetStatistics() - checkErr(err) - - fmt.Printf("\nStatistics: %v\n", stats) -} - -func checkErr(err error) { - if err != nil { - fmt.Println("Error:", err) - os.Exit(1) - } -} From ae13ccfdab3eb6874842c57fba8b1e37b0ef8469 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:38:46 +0100 Subject: [PATCH 07/29] update deps --- go.mod | 3 +-- go.sum | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 458060d..521c31d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/LazyBachelor/LazyPM go 1.25.6 require ( - github.com/BurntSushi/toml v1.6.0 + charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 github.com/google/uuid v1.6.0 github.com/muesli/reflow v0.3.0 github.com/steveyegge/beads v0.49.6 @@ -29,7 +29,6 @@ require ( ) require ( - charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect diff --git a/go.sum b/go.sum index 6d444a0..d774a58 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 h1:D9PbaszZYpB4nj+d6HTWr1onlmlyuGVNfL9gAi8iB3k= charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU= -github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= -github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= From da8ab0833ea3c5d1a837c5ceb10e501381fa3c84 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:39:00 +0100 Subject: [PATCH 08/29] delete survey pkg --- pkg/survey.go | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 pkg/survey.go diff --git a/pkg/survey.go b/pkg/survey.go deleted file mode 100644 index 49636e1..0000000 --- a/pkg/survey.go +++ /dev/null @@ -1,19 +0,0 @@ -package pkg - -import ( - "context" - - "github.com/LazyBachelor/LazyPM/internal/service" -) - -type SurveyConfig = service.Config - -func Run(ctx context.Context, config SurveyConfig) error { - _, cleanup, err := service.NewServices(ctx, config) - if err != nil { - return err - } - defer cleanup() - - return nil -} From 183f13895b127b7e6dd0dfad509beb4a2946aab7 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:40:55 +0100 Subject: [PATCH 09/29] add task system and task model for viewing task details --- cmd/survey/tasks/runner.go | 22 ++++++++++++ cmd/survey/tasks/task.go | 45 +++++++++++++++++++++++++ cmd/survey/tasks/types.go | 29 ++++++++++++++++ cmd/survey/ui/help.go | 27 +++++++++++++++ cmd/survey/ui/style.go | 14 ++++++++ cmd/survey/ui/task.go | 69 ++++++++++++++++++++++++++++++++++++++ cmd/survey/ui/types.go | 28 ++++++++++++++++ 7 files changed, 234 insertions(+) create mode 100644 cmd/survey/tasks/runner.go create mode 100644 cmd/survey/tasks/task.go create mode 100644 cmd/survey/tasks/types.go create mode 100644 cmd/survey/ui/help.go create mode 100644 cmd/survey/ui/style.go create mode 100644 cmd/survey/ui/task.go create mode 100644 cmd/survey/ui/types.go diff --git a/cmd/survey/tasks/runner.go b/cmd/survey/tasks/runner.go new file mode 100644 index 0000000..e407efe --- /dev/null +++ b/cmd/survey/tasks/runner.go @@ -0,0 +1,22 @@ +package tasks + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +func (t *Task) IntroduceTask() error { + _, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() + return err +} + +func (t *Task) StartInterface(ctx context.Context, cfg service.Config) error { + return t.interfaceType.Run(ctx, cfg) +} + +func (t *Task) StartQuestionnaire() error { + _, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() + return err +} diff --git a/cmd/survey/tasks/task.go b/cmd/survey/tasks/task.go new file mode 100644 index 0000000..71b54eb --- /dev/null +++ b/cmd/survey/tasks/task.go @@ -0,0 +1,45 @@ +// task.go +package tasks + +import ( + "context" + "errors" + + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +func NewTask(interfaceType Interface, aboutScreen tea.Model, questionnaire tea.Model) *Task { + return &Task{ + interfaceType: interfaceType, + aboutScreen: aboutScreen, + questionnaire: questionnaire, + } +} + +func (t *Task) SetValidateFunc(fn ValidateFunc) { + t.validateFunc = fn +} + +func (t *Task) SetDbStateFunc(fn DbStateFunc) { + t.dbStateFunc = fn +} + +func (t *Task) SetInterface(interfaceType Interface) { + t.interfaceType = interfaceType +} + +func (t *Task) Validate(ctx context.Context, svc *service.Services) (bool, error) { + if t.validateFunc == nil { + return false, errors.New("validateFunc is not set") + } + return t.validateFunc(ctx, svc) +} + +func (t *Task) MigrateToTask(ctx context.Context, svc *service.Services, task *Task) error { + t = task + if t.dbStateFunc == nil { + return errors.New("dbStateFunc is not set") + } + return t.dbStateFunc(ctx, svc) +} diff --git a/cmd/survey/tasks/types.go b/cmd/survey/tasks/types.go new file mode 100644 index 0000000..bfc9e87 --- /dev/null +++ b/cmd/survey/tasks/types.go @@ -0,0 +1,29 @@ +package tasks + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +type Interface interface { + Run(context.Context, service.Config) error +} + +type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) +type DbStateFunc func(context.Context, *service.Services) error + +type Task struct { + interfaceType Interface + aboutScreen tea.Model + questionnaire tea.Model + + validateFunc ValidateFunc + dbStateFunc DbStateFunc +} + +type TaskList struct { + Todo []*Task + Done []*Task +} diff --git a/cmd/survey/ui/help.go b/cmd/survey/ui/help.go new file mode 100644 index 0000000..15b58a6 --- /dev/null +++ b/cmd/survey/ui/help.go @@ -0,0 +1,27 @@ +package ui + +import "github.com/charmbracelet/bubbles/key" + +type TaskHelpKeys struct { + Quit key.Binding + Continue key.Binding +} + +var DefaultTaskKeys = TaskHelpKeys{ + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q", "Quit"), + ), + Continue: key.NewBinding( + key.WithKeys(" "), + key.WithHelp("space", "Continue"), + ), +} + +func (h TaskHelpKeys) ShortHelp() []key.Binding { + return []key.Binding{h.Continue, h.Quit} +} + +func (h TaskHelpKeys) FullHelp() [][]key.Binding { + return [][]key.Binding{{h.Continue, h.Quit}} +} diff --git a/cmd/survey/ui/style.go b/cmd/survey/ui/style.go new file mode 100644 index 0000000..9a2b4f4 --- /dev/null +++ b/cmd/survey/ui/style.go @@ -0,0 +1,14 @@ +package ui + +import ( + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" +) + +func theme() *huh.Theme { + theme := huh.ThemeBase16() + + theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) + + return theme +} diff --git a/cmd/survey/ui/task.go b/cmd/survey/ui/task.go new file mode 100644 index 0000000..18c4011 --- /dev/null +++ b/cmd/survey/ui/task.go @@ -0,0 +1,69 @@ +package ui + +import ( + "charm.land/lipgloss/v2" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +func NewTaskModel(details TaskDetails) TaskModel { + return TaskModel{ + TaskDetails: details, + keys: DefaultTaskKeys, + help: help.New(), + } +} + +func (m TaskModel) Init() tea.Cmd { + return nil +} + +func (t TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + t.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch { + case key.Matches(msg, t.keys.Quit): + return t, tea.Quit + case key.Matches(msg, t.keys.Continue): + return t, tea.Quit + } + } + return t, nil +} + +func (m TaskModel) View() string { + padding := 3 + + header := lipgloss.NewStyle(). + PaddingTop(padding).Width(m.width).Align(lipgloss.Center). + Bold(true).Render(m.Title) + + headerHeight := lipgloss.Height(header) + + helpView := lipgloss.NewStyle(). + PaddingBottom(padding). + Width(m.width).Align(lipgloss.Center). + Render(m.help.View(m.keys)) + + helpHeigh := lipgloss.Height(helpView) + + details := lipgloss.NewStyle().Align(lipgloss.Center). + Width(m.width).PaddingBottom(1).Render("Time to complete:", m.TimeToComplete, "Difficulty:", m.Difficulty) + + detailsHeight := lipgloss.Height(details) + + content := lipgloss.NewStyle(). + Width(m.width).Height(m.height-headerHeight-helpHeigh-detailsHeight). + Align(lipgloss.Center, lipgloss.Center). + Render(m.Description) + + return lipgloss.JoinVertical(lipgloss.Top, header, content, details, helpView) + +} + +func (m *TaskModel) SetSize(width, height int) { + m.width, m.height = width, height +} diff --git a/cmd/survey/ui/types.go b/cmd/survey/ui/types.go new file mode 100644 index 0000000..ec382be --- /dev/null +++ b/cmd/survey/ui/types.go @@ -0,0 +1,28 @@ +package ui + +import ( + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/huh" +) + +type TaskDetails struct { + Title string + Description string + TimeToComplete string + Difficulty string +} + +type TaskModel struct { + TaskDetails + keys TaskHelpKeys + help help.Model + width, height int +} + +type Questions []*huh.Group + +type QuestionnaireModel struct { + Questions + form *huh.Form + width, height int +} From 964e9234be459a6b72bc921cf6766d76729b857b Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:41:19 +0100 Subject: [PATCH 10/29] add questionare model for survey after task --- cmd/survey/ui/questionare.go | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 cmd/survey/ui/questionare.go diff --git a/cmd/survey/ui/questionare.go b/cmd/survey/ui/questionare.go new file mode 100644 index 0000000..9849a60 --- /dev/null +++ b/cmd/survey/ui/questionare.go @@ -0,0 +1,53 @@ +package ui + +import ( + "charm.land/lipgloss/v2" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/huh" +) + +func NewQuestionnaireModel(questions Questions) *QuestionnaireModel { + form := huh.NewForm(questions...). + WithTheme(theme()).WithLayout(huh.LayoutGrid(1, 1)) + + return &QuestionnaireModel{ + Questions: questions, + form: form, + } +} + +func (q *QuestionnaireModel) Init() tea.Cmd { + return q.form.Init() +} + +func (q *QuestionnaireModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + q.SetSize(msg.Width, msg.Height) + case tea.KeyMsg: + switch msg.String() { + case "q", "ctrl+c": + return q, tea.Quit + } + } + + form, cmd := q.form.Update(msg) + if f, ok := form.(*huh.Form); ok { + q.form = f + } + return q, cmd +} + +func (q *QuestionnaireModel) View() string { + form := lipgloss.NewStyle(). + Width(q.width).Align(lipgloss.Center). + Render(q.form.View()) + + return lipgloss.Place( + q.width, q.height, lipgloss.Center, lipgloss.Center, form, + ) +} + +func (q *QuestionnaireModel) SetSize(width, height int) { + q.width, q.height = width, height +} From e74c784b5dfc1bbda58a255b58f5e73dd6667f11 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 20:41:47 +0100 Subject: [PATCH 11/29] use new tasks and questionare with interfaces add a create issue task WIP --- cmd/survey/survey.go | 83 +++++++++++++++------------------ cmd/survey/tasks/createIssue.go | 74 +++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 46 deletions(-) create mode 100644 cmd/survey/tasks/createIssue.go diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index bc58ef9..e689b5d 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -2,71 +2,62 @@ package main import ( "context" - "embed" "fmt" "os" - "github.com/LazyBachelor/LazyPM/cmd/survey/forms" - "github.com/LazyBachelor/LazyPM/pkg" + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/cli/repl" "github.com/LazyBachelor/LazyPM/pkg/tui" "github.com/LazyBachelor/LazyPM/pkg/web" - "github.com/charmbracelet/huh" ) -//go:embed assets/* -var assetsFS embed.FS - func main() { - intro, err := forms.NewIntroduction(assetsFS) + ctx := context.Background() + + svc, close, err := initializeServices(ctx) if err != nil { - fmt.Fprintf(os.Stderr, "Error loading introduction: %v\n", err) - os.Exit(1) + fatal("Failed to initialize services: %v\n", err) + } + defer close() + + tui := tui.NewTui() + web := web.NewWeb() + repl := repl.NewRepl() + + createIssueTask := tasks.NewCreateIssueTask(web) + + task := createIssueTask + + task.SetInterface(tui) + task.SetInterface(repl) + + task.MigrateToTask(ctx, svc, task) + + if err := task.IntroduceTask(); err != nil { + fatal("Failed to introduce task: %v\n", err) } - if err := intro.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running introduction: %v\n", err) - os.Exit(1) + if err := task.StartInterface(ctx, svc.Config); err != nil { + fatal("Failed to start interface: %v\n", err) } - var selected string - prompt := huh.NewSelect[string]().Options( - huh.NewOption("Start CLI in REPL Mode", "repl"), - huh.NewOption("Start Web Interface", "web"), - huh.NewOption("Start TUI Interface", "tui"), - ).Value(&selected).WithTheme(huh.ThemeBase16()) - - if err := prompt.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error running prompt: %v\n", err) - os.Exit(1) + if err := task.StartQuestionnaire(); err != nil { + fatal("Failed to start questionnaire: %v\n", err) } +} - config := pkg.SurveyConfig{ - RootCmd: "pm", +func initializeServices(ctx context.Context) (*service.Services, func(), error) { + config := service.Config{ IssuePrefix: "pm", BeadsDBPath: "./.pm/db.db", StatisticsStoragePath: "./.pm/stats.json", WebAddress: "localhost:8080", } - - ctx := context.Background() - switch selected { - case "repl": - fmt.Println("Starting CLI in REPL mode...") - err = repl.RunREPL(ctx, config) - case "web": - fmt.Println("Starting Web Interface...") - err = web.Run(ctx, config) - case "tui": - fmt.Println("Starting TUI Interface...") - _, err = tui.Run(ctx, config) - default: - fmt.Println("Invalid selection. Exiting.") - } - - if err != nil { - fmt.Fprintf(os.Stderr, "Error running selected interface: %v\n", err) - os.Exit(1) - } - + return service.NewServices(ctx, config) +} + +func fatal(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format, args...) + os.Exit(1) } diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go new file mode 100644 index 0000000..4932a77 --- /dev/null +++ b/cmd/survey/tasks/createIssue.go @@ -0,0 +1,74 @@ +package tasks + +import ( + "context" + "errors" + + "github.com/LazyBachelor/LazyPM/cmd/survey/ui" + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/charmbracelet/huh" +) + +func NewCreateIssueTask(interfaceType Interface) *Task { + aboutScreen := ui.NewTaskModel(createIssueDetails()) + questionare := ui.NewQuestionnaireModel(createIssueQuestionare()) + + task := NewTask(interfaceType, aboutScreen, questionare) + task.SetDbStateFunc(createIssueDbState) + task.SetValidateFunc(createIssueValidate) + + return task +} + +func createIssueDetails() ui.TaskDetails { + return ui.TaskDetails{ + Title: "Create Issue Task", + Description: `Description for create issue task`, + TimeToComplete: "15m", + Difficulty: "Hard", + } +} + +func createIssueQuestionare() ui.Questions { + return ui.Questions{ + huh.NewGroup( + huh.NewConfirm().Title("Was this good"), + ), + huh.NewGroup( + huh.NewSelect[int]().Options( + huh.NewOption("Very good", 1), + huh.NewOption("Very Bad", 2), + ).Title("How good was it?"), + ), + } +} + +func createIssueDbState(ctx context.Context, svc *service.Services) error { + if err := svc.DeleteIssues(); err != nil { + return err + } + + issues := []*models.Issue{ + {Title: "Test Issue", Description: "Long Description", IssueType: models.TypeBug, Status: models.StatusBlocked}, + } + + if err := svc.Beads.CreateIssues(ctx, issues, "actor"); err != nil { + return err + } + + return nil +} + +func createIssueValidate(ctx context.Context, svc *service.Services) (ok bool, errorMsg error) { + issues, err := svc.Beads.SearchIssues(ctx, "", models.IssueFilter{}) + if err != nil { + return false, err + } + + if len(issues) == 0 { + return false, errors.New("No issues found. Please create an issue to proceed.") + } + + return true, nil +} From fe5494eaee044cb4d23b1e47b9097bb26d323299 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:05:25 +0100 Subject: [PATCH 12/29] format time and difficulty better use m --- cmd/survey/ui/task.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd/survey/ui/task.go b/cmd/survey/ui/task.go index 18c4011..5e5314e 100644 --- a/cmd/survey/ui/task.go +++ b/cmd/survey/ui/task.go @@ -1,6 +1,8 @@ package ui import ( + "fmt" + "charm.land/lipgloss/v2" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" @@ -19,19 +21,19 @@ func (m TaskModel) Init() tea.Cmd { return nil } -func (t TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m TaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: - t.SetSize(msg.Width, msg.Height) + m.SetSize(msg.Width, msg.Height) case tea.KeyMsg: switch { - case key.Matches(msg, t.keys.Quit): - return t, tea.Quit - case key.Matches(msg, t.keys.Continue): - return t, tea.Quit + case key.Matches(msg, m.keys.Quit): + return m, tea.Quit + case key.Matches(msg, m.keys.Continue): + return m, tea.Quit } } - return t, nil + return m, nil } func (m TaskModel) View() string { @@ -50,8 +52,9 @@ func (m TaskModel) View() string { helpHeigh := lipgloss.Height(helpView) + detailsText := fmt.Sprintf("Time to complete: %s | Difficulty: %s", m.TimeToComplete, m.Difficulty) details := lipgloss.NewStyle().Align(lipgloss.Center). - Width(m.width).PaddingBottom(1).Render("Time to complete:", m.TimeToComplete, "Difficulty:", m.Difficulty) + Width(m.width).PaddingBottom(1).Render(detailsText) detailsHeight := lipgloss.Height(details) From 18eb113f71c7c0231c4290e5923748ebb4ca7f12 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:05:44 +0100 Subject: [PATCH 13/29] add questionare --- cmd/survey/ui/{questionare.go => questionnaire.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmd/survey/ui/{questionare.go => questionnaire.go} (100%) diff --git a/cmd/survey/ui/questionare.go b/cmd/survey/ui/questionnaire.go similarity index 100% rename from cmd/survey/ui/questionare.go rename to cmd/survey/ui/questionnaire.go From 7dc6420854000dc1e0c8b44466e41813cc21db73 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:06:10 +0100 Subject: [PATCH 14/29] move around types for better understanding --- cmd/survey/tasks/types.go | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/cmd/survey/tasks/types.go b/cmd/survey/tasks/types.go index bfc9e87..74af23c 100644 --- a/cmd/survey/tasks/types.go +++ b/cmd/survey/tasks/types.go @@ -4,26 +4,14 @@ import ( "context" "github.com/LazyBachelor/LazyPM/internal/service" - tea "github.com/charmbracelet/bubbletea" ) +type TaskConfig = service.Config + type Interface interface { - Run(context.Context, service.Config) error + Run(context.Context, TaskConfig) error } +type ConfigFunc func() TaskConfig type ValidateFunc func(context.Context, *service.Services) (ok bool, err error) type DbStateFunc func(context.Context, *service.Services) error - -type Task struct { - interfaceType Interface - aboutScreen tea.Model - questionnaire tea.Model - - validateFunc ValidateFunc - dbStateFunc DbStateFunc -} - -type TaskList struct { - Todo []*Task - Done []*Task -} From 412b2fe0c2b7da008061124630e9737295569c25 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:06:48 +0100 Subject: [PATCH 15/29] add config to tasks use fmt.Errorf --- cmd/survey/tasks/task.go | 46 +++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/cmd/survey/tasks/task.go b/cmd/survey/tasks/task.go index 71b54eb..9e6553b 100644 --- a/cmd/survey/tasks/task.go +++ b/cmd/survey/tasks/task.go @@ -3,43 +3,55 @@ package tasks import ( "context" - "errors" + "fmt" "github.com/LazyBachelor/LazyPM/internal/service" tea "github.com/charmbracelet/bubbletea" ) -func NewTask(interfaceType Interface, aboutScreen tea.Model, questionnaire tea.Model) *Task { +type Task struct { + Config TaskConfig + interfaceType Interface + aboutScreen tea.Model + questionnaire tea.Model + + validateFunc ValidateFunc + dbStateFunc DbStateFunc +} + +func NewTask(aboutScreen tea.Model, questionnaire tea.Model) *Task { return &Task{ - interfaceType: interfaceType, aboutScreen: aboutScreen, questionnaire: questionnaire, } } -func (t *Task) SetValidateFunc(fn ValidateFunc) { - t.validateFunc = fn -} - -func (t *Task) SetDbStateFunc(fn DbStateFunc) { - t.dbStateFunc = fn +func (t *Task) SetConfigFunc(fn ConfigFunc) { + t.Config = fn() } func (t *Task) SetInterface(interfaceType Interface) { t.interfaceType = interfaceType } -func (t *Task) Validate(ctx context.Context, svc *service.Services) (bool, error) { - if t.validateFunc == nil { - return false, errors.New("validateFunc is not set") - } - return t.validateFunc(ctx, svc) +func (t *Task) SetDbStateFunc(fn DbStateFunc) { + t.dbStateFunc = fn } -func (t *Task) MigrateToTask(ctx context.Context, svc *service.Services, task *Task) error { - t = task +func (t *Task) SetValidateFunc(fn ValidateFunc) { + t.validateFunc = fn +} + +func (t *Task) Initialize(ctx context.Context, svc *service.Services) error { if t.dbStateFunc == nil { - return errors.New("dbStateFunc is not set") + return fmt.Errorf("dbStateFunc is not set") } return t.dbStateFunc(ctx, svc) } + +func (t *Task) Validate(ctx context.Context, svc *service.Services) (bool, error) { + if t.validateFunc == nil { + return false, fmt.Errorf("validateFunc is not set") + } + return t.validateFunc(ctx, svc) +} From cbfbb6a0bbc25cc74d2d57514af4c45723bc205a Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:07:03 +0100 Subject: [PATCH 16/29] add init file for better separation --- cmd/survey/init.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 cmd/survey/init.go diff --git a/cmd/survey/init.go b/cmd/survey/init.go new file mode 100644 index 0000000..84f7609 --- /dev/null +++ b/cmd/survey/init.go @@ -0,0 +1,32 @@ +package main + +import ( + "context" + + "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/tui" + "github.com/LazyBachelor/LazyPM/pkg/web" +) + +func initializeServices(ctx context.Context) (*service.Services, func(), error) { + config := service.Config{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/stats.json", + WebAddress: "localhost:8080", + } + return service.NewServices(ctx, config) +} + +func initTasks() []*tasks.Task { + return []*tasks.Task{ + tasks.NewCreateIssueTask(), + tasks.NewCreateIssueTask(), + } +} + +func initInterfaces() []tasks.Interface { + return []tasks.Interface{repl.NewRepl(), tui.NewTui(), web.NewWeb()} +} From 0e9bbbc3bd7f429d01b87ea21277588d808030d8 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:07:15 +0100 Subject: [PATCH 17/29] reformat --- cmd/survey/survey.go | 84 ++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index e689b5d..d913507 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -3,61 +3,67 @@ package main import ( "context" "fmt" - "os" + "log" + "math/rand" + "time" "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/internal/service" - "github.com/LazyBachelor/LazyPM/pkg/cli/repl" - "github.com/LazyBachelor/LazyPM/pkg/tui" - "github.com/LazyBachelor/LazyPM/pkg/web" ) func main() { + rand.Seed(time.Now().UnixNano()) ctx := context.Background() svc, close, err := initializeServices(ctx) if err != nil { - fatal("Failed to initialize services: %v\n", err) + log.Fatalf("Failed to initialize services: %v\n", err) } defer close() - tui := tui.NewTui() - web := web.NewWeb() - repl := repl.NewRepl() + tasks := initTasks() + interfaces := initInterfaces() - createIssueTask := tasks.NewCreateIssueTask(web) - - task := createIssueTask - - task.SetInterface(tui) - task.SetInterface(repl) - - task.MigrateToTask(ctx, svc, task) - - if err := task.IntroduceTask(); err != nil { - fatal("Failed to introduce task: %v\n", err) - } - - if err := task.StartInterface(ctx, svc.Config); err != nil { - fatal("Failed to start interface: %v\n", err) - } - - if err := task.StartQuestionnaire(); err != nil { - fatal("Failed to start questionnaire: %v\n", err) + if err := taskLoop(ctx, svc, tasks, interfaces); err != nil { + log.Fatalf("Task loop failed: %v\n", err) } } -func initializeServices(ctx context.Context) (*service.Services, func(), error) { - config := service.Config{ - IssuePrefix: "pm", - BeadsDBPath: "./.pm/db.db", - StatisticsStoragePath: "./.pm/stats.json", - WebAddress: "localhost:8080", - } - return service.NewServices(ctx, config) -} +func taskLoop(ctx context.Context, svc *service.Services, tasks []*tasks.Task, interfaces []tasks.Interface) error { + interfaceIndex := rand.Int() % len(interfaces) -func fatal(format string, args ...interface{}) { - fmt.Fprintf(os.Stderr, format, args...) - os.Exit(1) + for _, task := range tasks { + + task.SetInterface(interfaces[interfaceIndex]) + + if err := task.Initialize(ctx, svc); err != nil { + return fmt.Errorf("Failed to initialize task: %w", err) + } + + if err := task.IntroduceTask(); err != nil { + return fmt.Errorf("failed to display task introduction screen: %w", err) + } + + if err := task.StartInterface(ctx, task.Config); err != nil { + return fmt.Errorf("failed to start task interface: %w", err) + } + + ok, err := task.Validate(ctx, svc) + if err != nil { + return fmt.Errorf("validation error: %w", err) + } + if !ok { + return fmt.Errorf("task validation failed: task did not meet requirements") + } + + if err := task.StartQuestionnaire(); err != nil { + return fmt.Errorf("failed to start questionnaire: %w", err) + } + + interfaceIndex++ + if interfaceIndex >= len(interfaces) { + interfaceIndex = 0 + } + } + return nil } From 93c523d4905662320b210d64d9b8cd3260d3f038 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:07:29 +0100 Subject: [PATCH 18/29] add config and reformat --- cmd/survey/tasks/createIssue.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go index 4932a77..98fbb76 100644 --- a/cmd/survey/tasks/createIssue.go +++ b/cmd/survey/tasks/createIssue.go @@ -10,27 +10,36 @@ import ( "github.com/charmbracelet/huh" ) -func NewCreateIssueTask(interfaceType Interface) *Task { +func NewCreateIssueTask() *Task { aboutScreen := ui.NewTaskModel(createIssueDetails()) - questionare := ui.NewQuestionnaireModel(createIssueQuestionare()) + questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) - task := NewTask(interfaceType, aboutScreen, questionare) + task := NewTask(aboutScreen, questionnaire) + task.SetConfigFunc(createIssueConfig) task.SetDbStateFunc(createIssueDbState) task.SetValidateFunc(createIssueValidate) - return task } +func createIssueConfig() TaskConfig { + return TaskConfig{ + IssuePrefix: "pm", + BeadsDBPath: "./.pm/db.db", + StatisticsStoragePath: "./.pm/task-1-stats.json", + WebAddress: "localhost:8080", + } +} + func createIssueDetails() ui.TaskDetails { return ui.TaskDetails{ Title: "Create Issue Task", - Description: `Description for create issue task`, + Description: "Create a new issue in the project management system to test the issue creation workflow.", TimeToComplete: "15m", Difficulty: "Hard", } } -func createIssueQuestionare() ui.Questions { +func createIssueQuestionnaire() ui.Questions { return ui.Questions{ huh.NewGroup( huh.NewConfirm().Title("Was this good"), From d670ec0dc22d1c67279e9e22a9d0718771f06b60 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Thu, 12 Feb 2026 22:07:43 +0100 Subject: [PATCH 19/29] check if nil --- cmd/survey/tasks/runner.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmd/survey/tasks/runner.go b/cmd/survey/tasks/runner.go index e407efe..2e8c89f 100644 --- a/cmd/survey/tasks/runner.go +++ b/cmd/survey/tasks/runner.go @@ -2,21 +2,32 @@ package tasks import ( "context" + "fmt" "github.com/LazyBachelor/LazyPM/internal/service" tea "github.com/charmbracelet/bubbletea" ) func (t *Task) IntroduceTask() error { + if t.aboutScreen == nil { + return fmt.Errorf("aboutScreen is not set") + } _, err := tea.NewProgram(t.aboutScreen, tea.WithAltScreen()).Run() return err } func (t *Task) StartInterface(ctx context.Context, cfg service.Config) error { + if t.interfaceType == nil { + return fmt.Errorf("interfaceType is not set") + } + return t.interfaceType.Run(ctx, cfg) } func (t *Task) StartQuestionnaire() error { + if t.questionnaire == nil { + return fmt.Errorf("questionnaire is not set") + } _, err := tea.NewProgram(t.questionnaire, tea.WithAltScreen()).Run() return err } From 0bbe5ed3757a163054fb12a64c6a579e27662a0a Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:58:06 +0100 Subject: [PATCH 20/29] Update pkg/cli/repl/repl.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/cli/repl/repl.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/repl/repl.go b/pkg/cli/repl/repl.go index 1e9ffbd..5f0faa8 100644 --- a/pkg/cli/repl/repl.go +++ b/pkg/cli/repl/repl.go @@ -28,7 +28,7 @@ func NewRepl() *REPL { return &REPL{} } -// RunREPL starts the interactive Read-Eval-Print Loop for the PM CLI. +// Run starts the interactive Read-Eval-Print Loop for the PM CLI. func (r *REPL) Run(ctx context.Context, config cli.CLIConfig) error { // Set terminal to raw mode to capture input properly in the REPL. // This allows us to handle input character by character and provide a better user experience. From 6fc888347a2f1bed653b544966ad0e67c98222e5 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:58:24 +0100 Subject: [PATCH 21/29] Update cmd/survey/tasks/createIssue.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/survey/tasks/createIssue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go index 98fbb76..9f0d11d 100644 --- a/cmd/survey/tasks/createIssue.go +++ b/cmd/survey/tasks/createIssue.go @@ -76,7 +76,7 @@ func createIssueValidate(ctx context.Context, svc *service.Services) (ok bool, e } if len(issues) == 0 { - return false, errors.New("No issues found. Please create an issue to proceed.") + return false, errors.New("no issues found. Please create an issue to proceed.") } return true, nil From d12146462f3849cf2cf40718e55d66c6c1693297 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:58:36 +0100 Subject: [PATCH 22/29] Update cmd/survey/survey.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/survey/survey.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index d913507..74ac98a 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -37,7 +37,7 @@ func taskLoop(ctx context.Context, svc *service.Services, tasks []*tasks.Task, i task.SetInterface(interfaces[interfaceIndex]) if err := task.Initialize(ctx, svc); err != nil { - return fmt.Errorf("Failed to initialize task: %w", err) + return fmt.Errorf("failed to initialize task: %w", err) } if err := task.IntroduceTask(); err != nil { From fdfe4aa62034c1646309cd563df7c223f8cb0b04 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Thu, 12 Feb 2026 23:59:12 +0100 Subject: [PATCH 23/29] Update cmd/survey/survey.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/survey/survey.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index 74ac98a..a270790 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -21,18 +21,18 @@ func main() { } defer close() - tasks := initTasks() + surveyTasks := initTasks() interfaces := initInterfaces() - if err := taskLoop(ctx, svc, tasks, interfaces); err != nil { + if err := taskLoop(ctx, svc, surveyTasks, interfaces); err != nil { log.Fatalf("Task loop failed: %v\n", err) } } -func taskLoop(ctx context.Context, svc *service.Services, tasks []*tasks.Task, interfaces []tasks.Interface) error { +func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*tasks.Task, interfaces []tasks.Interface) error { interfaceIndex := rand.Int() % len(interfaces) - for _, task := range tasks { + for _, task := range surveyTasks { task.SetInterface(interfaces[interfaceIndex]) From fc5156a0ad8d5be35f60c1ebad2794220380d744 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Fri, 13 Feb 2026 00:00:05 +0100 Subject: [PATCH 24/29] Update pkg/tui/tui.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/tui/tui.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 7a26ed7..f9a704d 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -19,7 +19,7 @@ func NewTui() *Tui { func (t *Tui) Run(ctx context.Context, config TUIConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { - return nil + return err } defer cleanup() From e994c890f460f7b5f53a276ae3af968507c9c9b2 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Fri, 13 Feb 2026 10:13:41 +0100 Subject: [PATCH 25/29] refactor survery cmd move package files to pkg --- cmd/survey/init.go | 9 +++++---- cmd/survey/survey.go | 6 ++---- cmd/survey/tasks/createIssue.go | 11 ++++++----- cmd/survey/ui/style.go | 14 -------------- {cmd/survey/tasks => pkg/task}/runner.go | 5 ++--- {cmd/survey/tasks => pkg/task}/task.go | 3 +-- {cmd/survey/tasks => pkg/task}/types.go | 2 +- {cmd/survey => pkg/task}/ui/help.go | 2 +- {cmd/survey => pkg/task}/ui/questionnaire.go | 5 +++-- {cmd/survey => pkg/task}/ui/task.go | 2 +- {cmd/survey => pkg/task}/ui/types.go | 2 +- 11 files changed, 23 insertions(+), 38 deletions(-) delete mode 100644 cmd/survey/ui/style.go rename {cmd/survey/tasks => pkg/task}/runner.go (80%) rename {cmd/survey/tasks => pkg/task}/task.go (97%) rename {cmd/survey/tasks => pkg/task}/types.go (96%) rename {cmd/survey => pkg/task}/ui/help.go (97%) rename {cmd/survey => pkg/task}/ui/questionnaire.go (88%) rename {cmd/survey => pkg/task}/ui/task.go (99%) rename {cmd/survey => pkg/task}/ui/types.go (96%) diff --git a/cmd/survey/init.go b/cmd/survey/init.go index 84f7609..772af6f 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -6,6 +6,7 @@ import ( "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/pkg/cli/repl" + "github.com/LazyBachelor/LazyPM/pkg/task" "github.com/LazyBachelor/LazyPM/pkg/tui" "github.com/LazyBachelor/LazyPM/pkg/web" ) @@ -20,13 +21,13 @@ func initializeServices(ctx context.Context) (*service.Services, func(), error) return service.NewServices(ctx, config) } -func initTasks() []*tasks.Task { - return []*tasks.Task{ +func initTasks() []*task.Task { + return []*task.Task{ tasks.NewCreateIssueTask(), tasks.NewCreateIssueTask(), } } -func initInterfaces() []tasks.Interface { - return []tasks.Interface{repl.NewRepl(), tui.NewTui(), web.NewWeb()} +func initInterfaces() []task.Interface { + return []task.Interface{repl.NewRepl(), tui.NewTui(), web.NewWeb()} } diff --git a/cmd/survey/survey.go b/cmd/survey/survey.go index a270790..64e36e2 100644 --- a/cmd/survey/survey.go +++ b/cmd/survey/survey.go @@ -5,14 +5,12 @@ import ( "fmt" "log" "math/rand" - "time" - "github.com/LazyBachelor/LazyPM/cmd/survey/tasks" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" ) func main() { - rand.Seed(time.Now().UnixNano()) ctx := context.Background() svc, close, err := initializeServices(ctx) @@ -29,7 +27,7 @@ func main() { } } -func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*tasks.Task, interfaces []tasks.Interface) error { +func taskLoop(ctx context.Context, svc *service.Services, surveyTasks []*task.Task, interfaces []task.Interface) error { interfaceIndex := rand.Int() % len(interfaces) for _, task := range surveyTasks { diff --git a/cmd/survey/tasks/createIssue.go b/cmd/survey/tasks/createIssue.go index 9f0d11d..9a9a2d4 100644 --- a/cmd/survey/tasks/createIssue.go +++ b/cmd/survey/tasks/createIssue.go @@ -4,25 +4,26 @@ import ( "context" "errors" - "github.com/LazyBachelor/LazyPM/cmd/survey/ui" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/task" + ui "github.com/LazyBachelor/LazyPM/pkg/task/ui" "github.com/charmbracelet/huh" ) -func NewCreateIssueTask() *Task { +func NewCreateIssueTask() *task.Task { aboutScreen := ui.NewTaskModel(createIssueDetails()) questionnaire := ui.NewQuestionnaireModel(createIssueQuestionnaire()) - task := NewTask(aboutScreen, questionnaire) + task := task.NewTask(aboutScreen, questionnaire) task.SetConfigFunc(createIssueConfig) task.SetDbStateFunc(createIssueDbState) task.SetValidateFunc(createIssueValidate) return task } -func createIssueConfig() TaskConfig { - return TaskConfig{ +func createIssueConfig() task.TaskConfig { + return task.TaskConfig{ IssuePrefix: "pm", BeadsDBPath: "./.pm/db.db", StatisticsStoragePath: "./.pm/task-1-stats.json", diff --git a/cmd/survey/ui/style.go b/cmd/survey/ui/style.go deleted file mode 100644 index 9a2b4f4..0000000 --- a/cmd/survey/ui/style.go +++ /dev/null @@ -1,14 +0,0 @@ -package ui - -import ( - "github.com/charmbracelet/huh" - "github.com/charmbracelet/lipgloss" -) - -func theme() *huh.Theme { - theme := huh.ThemeBase16() - - theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) - - return theme -} diff --git a/cmd/survey/tasks/runner.go b/pkg/task/runner.go similarity index 80% rename from cmd/survey/tasks/runner.go rename to pkg/task/runner.go index 2e8c89f..9ad8b0a 100644 --- a/cmd/survey/tasks/runner.go +++ b/pkg/task/runner.go @@ -1,10 +1,9 @@ -package tasks +package task import ( "context" "fmt" - "github.com/LazyBachelor/LazyPM/internal/service" tea "github.com/charmbracelet/bubbletea" ) @@ -16,7 +15,7 @@ func (t *Task) IntroduceTask() error { return err } -func (t *Task) StartInterface(ctx context.Context, cfg service.Config) error { +func (t *Task) StartInterface(ctx context.Context, cfg TaskConfig) error { if t.interfaceType == nil { return fmt.Errorf("interfaceType is not set") } diff --git a/cmd/survey/tasks/task.go b/pkg/task/task.go similarity index 97% rename from cmd/survey/tasks/task.go rename to pkg/task/task.go index 9e6553b..2a9e548 100644 --- a/cmd/survey/tasks/task.go +++ b/pkg/task/task.go @@ -1,5 +1,4 @@ -// task.go -package tasks +package task import ( "context" diff --git a/cmd/survey/tasks/types.go b/pkg/task/types.go similarity index 96% rename from cmd/survey/tasks/types.go rename to pkg/task/types.go index 74af23c..90427d5 100644 --- a/cmd/survey/tasks/types.go +++ b/pkg/task/types.go @@ -1,4 +1,4 @@ -package tasks +package task import ( "context" diff --git a/cmd/survey/ui/help.go b/pkg/task/ui/help.go similarity index 97% rename from cmd/survey/ui/help.go rename to pkg/task/ui/help.go index 15b58a6..ebc31e4 100644 --- a/cmd/survey/ui/help.go +++ b/pkg/task/ui/help.go @@ -1,4 +1,4 @@ -package ui +package taskui import "github.com/charmbracelet/bubbles/key" diff --git a/cmd/survey/ui/questionnaire.go b/pkg/task/ui/questionnaire.go similarity index 88% rename from cmd/survey/ui/questionnaire.go rename to pkg/task/ui/questionnaire.go index 9849a60..e7dc01d 100644 --- a/cmd/survey/ui/questionnaire.go +++ b/pkg/task/ui/questionnaire.go @@ -1,14 +1,15 @@ -package ui +package taskui import ( "charm.land/lipgloss/v2" + "github.com/LazyBachelor/LazyPM/internal/style" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/huh" ) func NewQuestionnaireModel(questions Questions) *QuestionnaireModel { form := huh.NewForm(questions...). - WithTheme(theme()).WithLayout(huh.LayoutGrid(1, 1)) + WithTheme(style.HuhCenterTheme()).WithLayout(huh.LayoutGrid(1, 1)) return &QuestionnaireModel{ Questions: questions, diff --git a/cmd/survey/ui/task.go b/pkg/task/ui/task.go similarity index 99% rename from cmd/survey/ui/task.go rename to pkg/task/ui/task.go index 5e5314e..81da4e6 100644 --- a/cmd/survey/ui/task.go +++ b/pkg/task/ui/task.go @@ -1,4 +1,4 @@ -package ui +package taskui import ( "fmt" diff --git a/cmd/survey/ui/types.go b/pkg/task/ui/types.go similarity index 96% rename from cmd/survey/ui/types.go rename to pkg/task/ui/types.go index ec382be..dc6a47e 100644 --- a/cmd/survey/ui/types.go +++ b/pkg/task/ui/types.go @@ -1,4 +1,4 @@ -package ui +package taskui import ( "github.com/charmbracelet/bubbles/help" From b1a72a5a2ef092a1bfa4b18bc8d8bd015fcdaedc Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Fri, 13 Feb 2026 10:14:05 +0100 Subject: [PATCH 26/29] add global style package --- internal/style/styles.go | 28 ++++++++++++++++++++++++++++ internal/style/themes.go | 14 ++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 internal/style/styles.go create mode 100644 internal/style/themes.go diff --git a/internal/style/styles.go b/internal/style/styles.go new file mode 100644 index 0000000..7dd47c8 --- /dev/null +++ b/internal/style/styles.go @@ -0,0 +1,28 @@ +package style + +import "github.com/charmbracelet/lipgloss" + +// Color palette +var ( + PrimaryColor = lipgloss.AdaptiveColor{Light: "#007acc", Dark: "#1e90ff"} + SecondaryColor = lipgloss.AdaptiveColor{Light: "#ff6f61", Dark: "#ff6347"} + AccentColor = lipgloss.AdaptiveColor{Light: "#6a5acd", Dark: "#9370db"} + Background = lipgloss.AdaptiveColor{Light: "#ffffff", Dark: "#1e1e1e"} + TextColor = lipgloss.AdaptiveColor{Light: "#000000", Dark: "#ffffff"} +) + +var ( + AppStyle = lipgloss.NewStyle().Padding(1, 2).Background(Background).Foreground(TextColor) +) + +var ( + DefaultBorder = lipgloss.NormalBorder() + BorderStyle = lipgloss.NewStyle().Border(DefaultBorder).BorderForeground(PrimaryColor) +) + +var ( + TitleStyle = lipgloss.NewStyle().Foreground(PrimaryColor).Bold(true) + DescriptionStyle = lipgloss.NewStyle().Foreground(TextColor).Italic(true) + DetailStyle = lipgloss.NewStyle().Foreground(SecondaryColor) + HelpStyle = lipgloss.NewStyle().Foreground(AccentColor) +) diff --git a/internal/style/themes.go b/internal/style/themes.go new file mode 100644 index 0000000..c6861bd --- /dev/null +++ b/internal/style/themes.go @@ -0,0 +1,14 @@ +package style + +import ( + "github.com/charmbracelet/huh" + "github.com/charmbracelet/lipgloss" +) + +func HuhCenterTheme() *huh.Theme { + theme := huh.ThemeBase16() + + theme.Focused.Base = lipgloss.NewStyle().Align(lipgloss.Center) + + return theme +} From 400a989454d1f0f826ff536cb3fcacdbe6c39a55 Mon Sep 17 00:00:00 2001 From: Robin Olsen Date: Fri, 13 Feb 2026 10:16:43 +0100 Subject: [PATCH 27/29] fix typo --- pkg/task/ui/task.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/task/ui/task.go b/pkg/task/ui/task.go index 81da4e6..c416ad3 100644 --- a/pkg/task/ui/task.go +++ b/pkg/task/ui/task.go @@ -50,7 +50,7 @@ func (m TaskModel) View() string { Width(m.width).Align(lipgloss.Center). Render(m.help.View(m.keys)) - helpHeigh := lipgloss.Height(helpView) + helpHeight := lipgloss.Height(helpView) detailsText := fmt.Sprintf("Time to complete: %s | Difficulty: %s", m.TimeToComplete, m.Difficulty) details := lipgloss.NewStyle().Align(lipgloss.Center). @@ -59,7 +59,7 @@ func (m TaskModel) View() string { detailsHeight := lipgloss.Height(details) content := lipgloss.NewStyle(). - Width(m.width).Height(m.height-headerHeight-helpHeigh-detailsHeight). + Width(m.width).Height(m.height-headerHeight-helpHeight-detailsHeight). Align(lipgloss.Center, lipgloss.Center). Render(m.Description) From c3181e0f777eedbfbc47edcec6fbe1e77df7d359 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:23:57 +0100 Subject: [PATCH 28/29] Update cmd/survey/init.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/survey/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/survey/init.go b/cmd/survey/init.go index 772af6f..533976b 100644 --- a/cmd/survey/init.go +++ b/cmd/survey/init.go @@ -24,7 +24,6 @@ func initializeServices(ctx context.Context) (*service.Services, func(), error) func initTasks() []*task.Task { return []*task.Task{ tasks.NewCreateIssueTask(), - tasks.NewCreateIssueTask(), } } From f396ae5b37b280c6b229d108832ba92ac9617db7 Mon Sep 17 00:00:00 2001 From: Robin Olsen <129996395+Telikz@users.noreply.github.com> Date: Fri, 13 Feb 2026 10:25:52 +0100 Subject: [PATCH 29/29] Update pkg/web/web.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/web/web.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/web/web.go b/pkg/web/web.go index f38215c..8c85f36 100644 --- a/pkg/web/web.go +++ b/pkg/web/web.go @@ -20,7 +20,7 @@ func NewWeb() *Web { //go:embed assets/* var assets embed.FS -func (w Web) Run(ctx context.Context, config WebConfig) error { +func (w *Web) Run(ctx context.Context, config WebConfig) error { svc, cleanup, err := service.NewServices(ctx, config) if err != nil { return err