diff --git a/cmd/beads_viewer/main.go b/cmd/beads_viewer/main.go deleted file mode 100644 index 35cc2c8..0000000 --- a/cmd/beads_viewer/main.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "log" - - "github.com/Dicklesworthstone/beads_viewer/pkg/model" - "github.com/Dicklesworthstone/beads_viewer/pkg/ui" - "github.com/LazyBachelor/LazyPM/internal/service" - tea "github.com/charmbracelet/bubbletea" -) - -func main() { - config := service.Config{ - StatisticsStoragePath: "./.pm/stats.json", - BeadsDBPath: "./.pm/db.db", - IssuePrefix: "pm", - } - - svc, close, err := service.NewServices(context.Background(), config) - if err != nil { - log.Fatal(err) - } - defer close() - - beadsIssues, err := svc.Beads.AllIssues(context.Background()) - if err != nil { - panic(err) - } - - modelIssues := make([]model.Issue, 0, len(beadsIssues)) - for _, issue := range beadsIssues { - jsonData, err := json.Marshal(issue) - if err != nil { - panic(err) - } - - var modelIssue model.Issue - if err := json.Unmarshal(jsonData, &modelIssue); err != nil { - panic(err) - } - modelIssues = append(modelIssues, modelIssue) - } - - model := ui.NewModel(modelIssues, nil, config.BeadsDBPath) - - if err := tea.NewProgram(model, tea.WithAltScreen(), - tea.WithMouseAllMotion()); err != nil { - panic(err) - } - -} diff --git a/cmd/survey.go b/cmd/survey.go index a6a9922..5bd99c5 100644 --- a/cmd/survey.go +++ b/cmd/survey.go @@ -26,7 +26,7 @@ func main() { switch os.Args[1] { case "tui": - err = tui.Run(ctx, config) + _, err = tui.Run(ctx, config) case "cli": err = cli.RunWithArgs(ctx, config, os.Args[2:]) case "repl": diff --git a/cmd/tui/main.go b/cmd/tui/main.go index 45b0974..f53021f 100644 --- a/cmd/tui/main.go +++ b/cmd/tui/main.go @@ -13,7 +13,7 @@ func main() { IssuePrefix: "pm", } - if err := tui.Run(context.Background(), config); err != nil { + if _, err := tui.Run(context.Background(), config); err != nil { panic(err) } } diff --git a/go.mod b/go.mod index 04c7cad..5419fc8 100644 --- a/go.mod +++ b/go.mod @@ -3,83 +3,76 @@ module github.com/LazyBachelor/LazyPM go 1.25.6 require ( - github.com/Dicklesworthstone/beads_viewer v0.14.3 - github.com/NYTimes/gziphandler v1.1.1 - github.com/a-h/templ v0.3.977 + github.com/google/uuid v1.6.0 + github.com/muesli/reflow v0.3.0 + github.com/steveyegge/beads v0.49.6 +) + +// Terminal dependencies +require ( github.com/c-bata/go-prompt v0.2.6 + github.com/charmbracelet/bubbles v0.21.1 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/fang v0.4.4 github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 - github.com/google/uuid v1.6.0 - github.com/muesli/reflow v0.3.0 - github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.10.2 - github.com/steveyegge/beads v0.49.3 - golang.org/x/term v0.39.0 + golang.org/x/term v0.40.0 +) + +// Web dependencies +require ( + github.com/NYTimes/gziphandler v1.1.1 + github.com/a-h/templ v0.3.977 + github.com/rs/cors v1.11.1 ) require ( charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 // indirect - git.sr.ht/~sbinet/gg v0.7.0 // indirect github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect - github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect - github.com/alecthomas/chroma/v2 v2.23.1 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/aymerick/douceur v0.2.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/charmbracelet/bubbles v0.21.1 // indirect github.com/charmbracelet/colorprofile v0.4.1 // indirect - github.com/charmbracelet/glamour v0.10.0 // indirect - github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692 // indirect - github.com/charmbracelet/x/ansi v0.11.5 // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260209111912-3cca7cf7b09b // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect - github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect - github.com/charmbracelet/x/exp/slice v0.0.0-20260203065841-a1c614051099 // indirect + github.com/charmbracelet/x/exp/charmtone v0.0.0-20260209194814-eeb2896ac759 // indirect github.com/charmbracelet/x/exp/strings v0.1.0 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect github.com/cli/browser v1.3.0 // indirect - github.com/clipperhouse/displaywidth v0.9.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.5.0 // indirect - github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/clipperhouse/displaywidth v0.10.0 // indirect + github.com/clipperhouse/uax29/v2 v2.6.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fatih/color v1.18.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/gorilla/css v1.0.1 // indirect - github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5 // indirect + github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect - github.com/mattn/go-tty v0.0.3 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect + github.com/mattn/go-tty v0.0.7 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/mango v0.1.0 // indirect - github.com/muesli/mango-cobra v1.2.0 // indirect - github.com/muesli/mango-pflag v0.1.0 // indirect + github.com/muesli/mango v0.2.0 // indirect + github.com/muesli/mango-cobra v1.3.0 // indirect + github.com/muesli/mango-pflag v0.2.0 // indirect github.com/muesli/roff v0.1.0 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/natefinch/atomic v1.0.1 // indirect github.com/ncruces/go-sqlite3 v0.30.5 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/term v1.2.0-beta.2 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/sahilm/fuzzy v0.1.1 // indirect @@ -90,24 +83,15 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tetratelabs/wazero v1.11.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yuin/goldmark v1.7.16 // indirect - github.com/yuin/goldmark-emoji v1.0.6 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect - golang.org/x/image v0.35.0 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/tools v0.41.0 // indirect - gonum.org/v1/gonum v0.17.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.67.7 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.44.3 // indirect ) tool ( diff --git a/go.sum b/go.sum index dba30f8..d774a58 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +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= -git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= -git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= -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/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= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= @@ -15,16 +8,6 @@ github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ6 github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= -github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= -github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= -github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= -github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= -github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY= -github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o= -github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= -github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -33,8 +16,6 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= -github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= -github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= @@ -49,28 +30,24 @@ github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY= github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo= -github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY= -github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk= github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE= github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= -github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692 h1:r/3jQZ1LjWW6ybp8HHfhrKrwHIWiJhUuY7wwYIWZulQ= -github.com/charmbracelet/ultraviolet v0.0.0-20251106190538-99ea45596692/go.mod h1:Y8B4DzWeTb0ama8l3+KyopZtkE8fZjwRQ3aEAPEXHE0= -github.com/charmbracelet/x/ansi v0.11.5 h1:NBWeBpj/lJPE3Q5l+Lusa4+mH6v7487OP8K0r1IhRg4= -github.com/charmbracelet/x/ansi v0.11.5/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/ultraviolet v0.0.0-20260209111912-3cca7cf7b09b h1:jyHmbVXscPtC1S4Cg2OW1Zq3bwTJoY6/q40Ahi8CqaA= +github.com/charmbracelet/ultraviolet v0.0.0-20260209111912-3cca7cf7b09b/go.mod h1:42rCfhmE+4ZM7twEctghIzlIWyPj6FCDTBiMepHE2Ss= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= -github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0= -github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20260209194814-eeb2896ac759 h1:U0li+kYsNefPhJPP9SfwQJNbQ58iXWRtidnj1Rqf//g= +github.com/charmbracelet/x/exp/charmtone v0.0.0-20260209194814-eeb2896ac759/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I= -github.com/charmbracelet/x/exp/slice v0.0.0-20260203065841-a1c614051099 h1:H09krOypYvXsPG4kYl+9J3tCRhST8WI3WEpXJELw+eg= -github.com/charmbracelet/x/exp/slice v0.0.0-20260203065841-a1c614051099/go.mod h1:vqEfX6xzqW1pKKZUUiFOKg0OQ7bCh54Q2vR/tserrRA= github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA= github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= @@ -83,20 +60,16 @@ github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGl github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk= -github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= -github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= -github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= -github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= -github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U= -github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+UraiZGD4HJQ3Y8g= +github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= +github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= +github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= -github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -109,28 +82,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= -github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5 h1:0nJIrglhp2shqGH+sM4dYcADM5w8gCT2ilE6mxdZcio= -github.com/haatos/goshipit v0.0.0-20260102021700-fcb988ab74c5/go.mod h1:LFP8N8y5ORkifb+LZuOVNZYlJuV3WqdXCjxX5pGUaNI= -github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 h1:sb7SfxZfN+U9OHC61tcS98Ge0zY9uEkW5CP6KB4YVHg= +github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320/go.mod h1:LFP8N8y5ORkifb+LZuOVNZYlJuV3WqdXCjxX5pGUaNI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -155,22 +114,21 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= -github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= -github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= +github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= +github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= -github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= -github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg= -github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= -github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= -github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= +github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ= +github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= +github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec= +github.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E= +github.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k= +github.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= @@ -181,8 +139,6 @@ github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0 github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= github.com/ncruces/go-sqlite3 v0.30.5 h1:6usmTQ6khriL8oWilkAZSJM/AIpAlVL2zFrlcpDldCE= github.com/ncruces/go-sqlite3 v0.30.5/go.mod h1:0I0JFflTKzfs3Ogfv8erP7CCoV/Z8uxigVDNOR0AQ5E= -github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= -github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= @@ -191,8 +147,6 @@ github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= @@ -217,8 +171,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/steveyegge/beads v0.49.3 h1:cMV1p5yhQZvHztnL3OwZRrwpHamPfIiJItcqJ+RSsmI= -github.com/steveyegge/beads v0.49.3/go.mod h1:CRsyKS7RrUeiv/+rCbq3PtOOo01rFlRsWOq+IAeGkkU= +github.com/steveyegge/beads v0.49.6 h1:ac/SJBYuz+hUww07pbjLfhXw8RGNyIkslTbutqHzWYQ= +github.com/steveyegge/beads v0.49.6/go.mod h1:yYUYUsF8GbLEylNiJMu2BSwAOCgRLrywfGOO8oKPmoA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -231,97 +185,35 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE= -github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= -github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs= -github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU= -golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU= -golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I= -golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= +golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis= -modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc= -modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE= -modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.67.7 h1:H+gYQw2PyidyxwxQsGTwQw6+6H+xUk+plvOKW7+d3TI= -modernc.org/libc v1.67.7/go.mod h1:UjCSJFl2sYbJbReVQeVpq/MgzlbmDM4cRHIYFelnaDk= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY= -modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/pkg/tui/styles/styles.go b/pkg/tui/styles/styles.go new file mode 100644 index 0000000..53b3dfc --- /dev/null +++ b/pkg/tui/styles/styles.go @@ -0,0 +1,80 @@ +package styles + +import "github.com/charmbracelet/lipgloss" + +var ( + Primary = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} + Secondary = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} + + Success = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} + Warning = lipgloss.AdaptiveColor{Light: "#F59E0B", Dark: "#F59E0B"} + Error = lipgloss.AdaptiveColor{Light: "#FE5F86", Dark: "#FE5F86"} + + PrimaryText = lipgloss.AdaptiveColor{Light: "#1A1A1A", Dark: "#E0E0E0"} + SecondaryText = lipgloss.AdaptiveColor{Light: "#666666", Dark: "#999999"} + FaintText = lipgloss.AdaptiveColor{Light: "#999999", Dark: "#666666"} + + PrimaryBorder = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} + SecondaryBorder = lipgloss.AdaptiveColor{Light: "#CCCCCC", Dark: "#444444"} + + SelectedBackground = lipgloss.AdaptiveColor{Light: "#E8E8E8", Dark: "#333333"} +) + +const ( + ListViewRatio = 70 // Percentage of total width allocated to the list view + LabelWidth = 14 + MarginBottomSmall = 1 +) + +var DefaultBorder = lipgloss.ThickBorder() + +var ( + HeaderStyle = lipgloss.NewStyle().Foreground(Primary).Padding(0, 1).Bold(true) + HeaderTitleStyle = lipgloss.NewStyle().Foreground(Primary).Bold(true).Padding(0) +) + +var ContainerStyle = lipgloss.NewStyle(). + Border(DefaultBorder, true, false, false, false). + BorderForeground(SecondaryBorder). + Padding(1) + +var DetailsContainerStyle = lipgloss.NewStyle(). + Border(DefaultBorder, true, false, false, true). + BorderForeground(SecondaryBorder). + Padding(1) + +var ( + RowStyle = lipgloss.NewStyle().MarginBottom(MarginBottomSmall) + TitleStyle = lipgloss.NewStyle().Foreground(Primary).Bold(true) + LabelStyle = lipgloss.NewStyle().Foreground(SecondaryText) + ValueStyle = lipgloss.NewStyle().Foreground(PrimaryText) + IssueTypeStyle = lipgloss.NewStyle().Foreground(Primary).Bold(true) +) + +var ( + FilterStyle = lipgloss.NewStyle().Foreground(Primary).Bold(true).Padding(0, 1) + FilterInputStyle = lipgloss.NewStyle().Foreground(PrimaryText).Padding(0, 1) + FilterPromptStyle = lipgloss.NewStyle().Foreground(Primary).Bold(true) +) + +func StatusStyle(status string) lipgloss.Style { + style := lipgloss.NewStyle().Bold(true) + switch status { + case "open": + return style.Foreground(Secondary) + case "closed": + return style.Foreground(FaintText) + case "in_progress": + return style.Foreground(Warning) + default: + return style.Foreground(SecondaryText) + } +} + +func HighlightKey(key string) string { + return lipgloss.NewStyle(). + Foreground(Primary). + Bold(true). + Padding(0, 1). + Render(key) +} diff --git a/pkg/tui/tui.go b/pkg/tui/tui.go index 19db02a..b0feda5 100644 --- a/pkg/tui/tui.go +++ b/pkg/tui/tui.go @@ -4,17 +4,22 @@ import ( "context" "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/tui/views" + tea "github.com/charmbracelet/bubbletea" ) type TUIConfig = service.Config -func Run(ctx context.Context, config TUIConfig) error { - _, cleanup, err := service.NewServices(ctx, config) +func Run(ctx context.Context, config TUIConfig) (tea.Model, error) { + svc, cleanup, err := service.NewServices(ctx, config) if err != nil { - return err + return nil, err } + defer cleanup() - return nil + app := tea.NewProgram(views.NewDashboardView(svc), + tea.WithAltScreen(), tea.WithMouseAllMotion()) + return app.Run() } diff --git a/pkg/tui/views/dashboard/header.go b/pkg/tui/views/dashboard/header.go new file mode 100644 index 0000000..fcd2b7e --- /dev/null +++ b/pkg/tui/views/dashboard/header.go @@ -0,0 +1,32 @@ +package dashboard + +import ( + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" + "github.com/charmbracelet/lipgloss" +) + +type Header struct { + title string +} + +func NewHeader(title string) Header { + return Header{ + title: title, + } +} + +func (h Header) View(width int) string { + title := styles.HeaderTitleStyle.Render(h.title) + + return lipgloss.PlaceHorizontal( + width, + lipgloss.Left, + title, + lipgloss.WithWhitespaceChars("─"), + lipgloss.WithWhitespaceForeground(styles.Primary), + ) +} + +func (h Header) Height() int { + return 1 +} diff --git a/pkg/tui/views/dashboard/help_bar.go b/pkg/tui/views/dashboard/help_bar.go new file mode 100644 index 0000000..c030ab1 --- /dev/null +++ b/pkg/tui/views/dashboard/help_bar.go @@ -0,0 +1,94 @@ +package dashboard + +import ( + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" + "github.com/charmbracelet/lipgloss" +) + +type HelpBar struct { + keyMap DashboardKeyMap + showAll bool + width int +} + +func NewHelpBar(keyMap DashboardKeyMap) HelpBar { + return HelpBar{keyMap: keyMap} +} + +func (h *HelpBar) SetWidth(width int) { + h.width = width +} + +func (h HelpBar) View() string { + if h.width == 0 { + return "" + } + if h.showAll { + return h.fullHelp() + } + return h.shortHelp() +} + +func (h HelpBar) shortHelp() string { + keys := []string{ + styles.HighlightKey("↑/k") + " up", + styles.HighlightKey("↓/j") + " down", + styles.HighlightKey("q") + " quit", + styles.HighlightKey("?") + " help", + } + content := lipgloss.JoinHorizontal(lipgloss.Left, keys...) + return lipgloss.NewStyle(). + Border(lipgloss.Border{Top: "─"}, true, false, false, false). + BorderForeground(styles.SecondaryBorder). + Padding(0, 1). + Width(h.width). + Render(content) +} + +func (h HelpBar) fullHelp() string { + keyStyle := lipgloss.NewStyle().Width(8).Align(lipgloss.Right) + descStyle := lipgloss.NewStyle().Width(12) + + renderHelpItem := func(key, desc string) string { + return lipgloss.JoinHorizontal( + lipgloss.Left, + keyStyle.Render(styles.HighlightKey(key)), + " ", + descStyle.Render(desc), + ) + } + + renderRow := func(leftKey, leftDesc, rightKey, rightDesc string) string { + leftItem := renderHelpItem(leftKey, leftDesc) + rightItem := renderHelpItem(rightKey, rightDesc) + return lipgloss.JoinHorizontal(lipgloss.Left, leftItem, " ", rightItem) + } + + rows := []string{ + renderRow("↑/k", "up", "enter", "view issue"), + renderRow("↓/j", "down", "b", "back to list"), + renderRow("?", "help", "q", "quit"), + } + content := lipgloss.JoinVertical(lipgloss.Left, rows...) + return lipgloss.NewStyle(). + Border(lipgloss.Border{Top: "─"}, true, false, false, false). + BorderForeground(styles.SecondaryBorder). + Padding(0, 1). + Width(h.width). + Render(content) +} + +func (h HelpBar) Height() int { + if h.width == 0 { + return 0 + } + return lipgloss.Height(h.View()) +} + +func (h HelpBar) IsExpanded() bool { + return h.showAll +} + +func (h *HelpBar) ToggleHelp() { + h.showAll = !h.showAll +} diff --git a/pkg/tui/views/dashboard/issue_detail.go b/pkg/tui/views/dashboard/issue_detail.go new file mode 100644 index 0000000..8706863 --- /dev/null +++ b/pkg/tui/views/dashboard/issue_detail.go @@ -0,0 +1,95 @@ +package dashboard + +import ( + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" + "github.com/charmbracelet/bubbles/viewport" + "github.com/charmbracelet/lipgloss" +) + +type IssueDetail struct { + viewport viewport.Model + issue models.Issue + focused bool +} + +func NewIssueDetail() IssueDetail { + vp := viewport.New(0, 0) + return IssueDetail{ + viewport: vp, + } +} + +func (i *IssueDetail) SetIssue(issue models.Issue) { + i.issue = issue + i.refreshContent() +} + +func (i *IssueDetail) SetSize(width, height int) { + i.viewport.Height = height + i.viewport.Width = width + i.refreshContent() +} + +func (i *IssueDetail) SetFocused(focused bool) { + i.focused = focused +} + +func (i *IssueDetail) refreshContent() { + + titleRow := styles.RowStyle.Render( + styles.TitleStyle.Render(i.issue.Title), + ) + + idRow := styles.RowStyle.Render( + styles.LabelStyle.Render("ID:") + styles.ValueStyle.Render(i.issue.ID), + ) + + typeRow := styles.RowStyle.Render( + styles.LabelStyle.Render("Type:") + styles.ValueStyle.Render(string(i.issue.IssueType)), + ) + + statusRow := styles.RowStyle.Render( + styles.LabelStyle.Render("Status:") + styles.StatusStyle(string(i.issue.Status)).Render(string(i.issue.Status)), + ) + + descLabel := styles.LabelStyle.Render("Description:") + descContent := styles.ValueStyle.Render(i.issue.Description) + + content := lipgloss.JoinVertical(lipgloss.Left, + titleRow, + idRow, + typeRow, + statusRow, + descLabel, + descContent, + ) + + i.viewport.SetContent(content) +} + +func (i IssueDetail) View() string { + content := i.viewport.View() + + if i.focused { + return styles.DetailsContainerStyle. + BorderForeground(styles.PrimaryBorder). + Width(i.viewport.Width). + Height(i.viewport.Height). + MaxHeight(i.viewport.Height). + Render(content) + } + return styles.DetailsContainerStyle. + Width(i.viewport.Width). + Height(i.viewport.Height). + MaxHeight(i.viewport.Height). + Render(content) +} + +func (i *IssueDetail) ScrollUp(lines int) { + i.viewport.ScrollUp(lines) +} + +func (i *IssueDetail) ScrollDown(lines int) { + i.viewport.ScrollDown(lines) +} diff --git a/pkg/tui/views/dashboard/issue_list.go b/pkg/tui/views/dashboard/issue_list.go new file mode 100644 index 0000000..18504c1 --- /dev/null +++ b/pkg/tui/views/dashboard/issue_list.go @@ -0,0 +1,256 @@ +package dashboard + +import ( + "context" + "io" + + "github.com/LazyBachelor/LazyPM/internal/models" + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/muesli/reflow/truncate" +) + +type IssueList struct { + list list.Model + svc *service.Services + width int + height int +} + +type ListIssue struct { + models.Issue +} + +func (l ListIssue) Title() string { return l.Issue.Title } +func (l ListIssue) Description() string { return l.Issue.Description } +func (l ListIssue) FilterValue() string { return l.Issue.ID + " " + l.Issue.Title } + +type TableColumn struct { + width uint + label string + key string +} + +func getTableColumns(width int) []TableColumn { + switch { + case width < 45: + return []TableColumn{ + {width: 10, label: "ID", key: "id"}, + {width: uint(width - 10), label: "TITLE", key: "title"}, + } + case width < 60: + return []TableColumn{ + {width: 10, label: "ID", key: "id"}, + {width: 20, label: "TITLE", key: "title"}, + {width: 15, label: "STATUS", key: "status"}, + } + default: + return []TableColumn{ + {width: 12, label: "ID", key: "id"}, + {width: 20, label: "TITLE", key: "title"}, + {width: 15, label: "STATUS", key: "status"}, + {width: 10, label: "TYPE", key: "type"}, + } + } +} + +func renderHeaders(cols []TableColumn) string { + var parts []string + headerStyle := lipgloss.NewStyle().Foreground(styles.FaintText).Bold(true) + + for _, col := range cols { + colWidth := col.width + if colWidth == 0 { + colWidth = 1 + } + style := lipgloss.NewStyle().Width(int(colWidth)) + headerText := headerStyle.Render(truncate.StringWithTail(col.label, colWidth, "...")) + parts = append(parts, style.Render(headerText)) + } + + return lipgloss.JoinHorizontal(lipgloss.Left, parts...) +} + +func NewIssueList(svc *service.Services, width, height int) IssueList { + issues, err := svc.Beads.AllIssues(context.Background()) + if err != nil { + return IssueList{} + } + + listIssues := []ListIssue{} + for _, issue := range issues { + listIssues = append(listIssues, ListIssue{Issue: issue}) + } + + items := make([]list.Item, len(listIssues)) + for i, issue := range listIssues { + items[i] = issue + } + + l := list.New(items, NewIssueListDelegate(width), width, height) + l.SetShowTitle(false) + l.SetShowHelp(false) + l.SetShowStatusBar(false) + l.SetFilteringEnabled(true) + l.FilterInput.PromptStyle = styles.FilterPromptStyle + l.FilterInput.Cursor.Style = styles.FilterStyle + l.FilterInput.TextStyle = styles.FilterInputStyle + l.FilterInput.Prompt = "🔍 " + + return IssueList{ + list: l, + svc: svc, + width: width, + height: height, + } +} + +func (l *IssueList) Update(msg tea.Msg) (tea.Cmd, bool) { + var cmd tea.Cmd + oldIndex := l.list.Index() + l.list, cmd = l.list.Update(msg) + changed := l.list.Index() != oldIndex + return cmd, changed +} + +func (l *IssueList) SetSize(width, height int) { + l.width = width + l.height = height + + l.list.SetSize(width, height) + l.list.SetDelegate(NewIssueListDelegate(width)) +} + +func (l IssueList) View() string { + return l.renderResponsive() +} + +func (l IssueList) renderResponsive() string { + cols := getTableColumns(l.width) + header := renderHeaders(cols) + + var content []string + + if l.list.FilterState() == list.Filtering { + filterText := l.list.FilterInput.Value() + filterView := styles.FilterStyle.Render("🔍 " + filterText) + content = append(content, filterView) + } + + itemsView := l.renderFilteredItems() + content = append(content, header, itemsView) + + return styles.ContainerStyle. + Width(l.width). + MaxWidth(l.width). + MaxHeight(l.height). + Render(lipgloss.JoinVertical(lipgloss.Left, content...)) +} + +func (l IssueList) renderFilteredItems() string { + var items []string + + var visibleItems []list.Item + if l.list.FilterState() == list.Filtering || l.list.FilterState() == list.FilterApplied { + visibleItems = l.list.VisibleItems() + } else { + visibleItems = l.list.Items() + } + + itemsPerPage := l.list.Paginator.ItemsOnPage(len(visibleItems)) + start := l.list.Paginator.Page * itemsPerPage + end := min(start+itemsPerPage, len(visibleItems)) + + cursor := l.list.Index() + + for i := start; i < end && i < len(visibleItems); i++ { + isSelected := i == cursor + if issue, ok := visibleItems[i].(ListIssue); ok { + cols := getTableColumns(l.width) + row := renderRow(issue, isSelected, cols) + items = append(items, row) + } + } + + return lipgloss.JoinVertical(lipgloss.Left, items...) +} + +func (l IssueList) SelectedItem() ListIssue { + if item, ok := l.list.SelectedItem().(ListIssue); ok { + return item + } + return ListIssue{} +} + +func (l IssueList) Index() int { + return l.list.Index() +} + +func (l IssueList) FilterState() list.FilterState { + return l.list.FilterState() +} + +type IssueListDelegate struct { + width int +} + +func NewIssueListDelegate(width int) IssueListDelegate { + return IssueListDelegate{width: width} +} + +func (d IssueListDelegate) Height() int { return 1 } +func (d IssueListDelegate) Spacing() int { return 0 } +func (d IssueListDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } + +func (d IssueListDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + issue, ok := listItem.(ListIssue) + if !ok { + return + } + + isSelected := index == m.Index() + cols := getTableColumns(d.width) + + row := renderRow(issue, isSelected, cols) + io.WriteString(w, row) +} + +func renderRow(issue ListIssue, isSelected bool, cols []TableColumn) string { + var parts []string + + for _, col := range cols { + value := getColumnValue(col, issue) + colWidth := col.width + if colWidth == 0 { + colWidth = 1 + } + + style := lipgloss.NewStyle().Width(int(colWidth)) + if isSelected { + style = style.Background(styles.SelectedBackground).Bold(true) + } + + truncated := truncate.StringWithTail(value, colWidth, "...") + parts = append(parts, style.Render(truncated)) + } + + return lipgloss.JoinHorizontal(lipgloss.Left, parts...) +} + +func getColumnValue(col TableColumn, issue ListIssue) string { + switch col.key { + case "id": + return issue.ID + case "title": + return issue.Title() + case "status": + return string(issue.Issue.Status) + case "type": + return string(issue.Issue.IssueType) + default: + return "" + } +} diff --git a/pkg/tui/views/dashboard/keys.go b/pkg/tui/views/dashboard/keys.go new file mode 100644 index 0000000..dfdeb03 --- /dev/null +++ b/pkg/tui/views/dashboard/keys.go @@ -0,0 +1,63 @@ +package dashboard + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +type DashboardKeyMap struct { + Help key.Binding + Quit key.Binding + SelectIssue key.Binding + BackToList key.Binding + ScrollUp key.Binding + ScrollDown key.Binding +} + +var defaultDashboardKeyMap = DashboardKeyMap{ + Help: key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "help"), + ), + Quit: key.NewBinding( + key.WithKeys("q", "ctrl+c"), + key.WithHelp("q", "quit"), + ), + SelectIssue: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "view issue"), + ), + BackToList: key.NewBinding( + key.WithKeys("b"), + key.WithHelp("b", "back to list"), + ), + ScrollUp: key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "up"), + ), + ScrollDown: key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "down"), + ), +} + +func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { + var cmd tea.Cmd + + switch { + case key.Matches(msg, d.keyMap.Help): + d.helpBar.ToggleHelp() + case key.Matches(msg, d.keyMap.Quit): + return tea.Quit + case d.IsFocusedOnList() && key.Matches(msg, d.keyMap.SelectIssue): + d.FocusDetail() + case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.BackToList): + d.FocusList() + case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollUp): + d.issueDetail.ScrollUp(1) + case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown): + d.issueDetail.ScrollDown(1) + } + + return cmd +} diff --git a/pkg/tui/views/dashboard/model.go b/pkg/tui/views/dashboard/model.go new file mode 100644 index 0000000..702cb8f --- /dev/null +++ b/pkg/tui/views/dashboard/model.go @@ -0,0 +1,69 @@ +package dashboard + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + tea "github.com/charmbracelet/bubbletea" +) + +type Model struct { + header Header + issueList IssueList + issueDetail IssueDetail + helpBar HelpBar + keyMap DashboardKeyMap + svc *service.Services + width int + height int + focusedPane int // 0 = list, 1 = detail +} + +func NewDashboard(svc *service.Services) *Model { + m := &Model{ + header: NewHeader("Project Manager Dashboard"), + keyMap: defaultDashboardKeyMap, + svc: svc, + width: 80, + height: 24, + focusedPane: 0, + } + + m.issueList = NewIssueList(svc, 0, 0) + m.issueDetail = NewIssueDetail() + m.helpBar = NewHelpBar(m.keyMap) + + if selected := m.issueList.SelectedItem(); selected.ID != "" { + m.issueDetail.SetIssue(selected.Issue) + } + + return m +} + +func (m *Model) Init() tea.Cmd { + return nil +} + +func (m *Model) IsFocusedOnList() bool { + return m.focusedPane == 0 +} + +func (m *Model) IsFocusedOnDetail() bool { + return m.focusedPane == 1 +} + +func (m *Model) FocusList() { + m.focusedPane = 0 + m.issueDetail.SetFocused(false) +} + +func (m *Model) FocusDetail() { + m.focusedPane = 1 + m.issueDetail.SetFocused(true) +} + +func (m *Model) ToggleFocus() { + if m.focusedPane == 0 { + m.FocusDetail() + } else { + m.FocusList() + } +} diff --git a/pkg/tui/views/dashboard/update.go b/pkg/tui/views/dashboard/update.go new file mode 100644 index 0000000..7fe15cc --- /dev/null +++ b/pkg/tui/views/dashboard/update.go @@ -0,0 +1,35 @@ +package dashboard + +import ( + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/list" +) + +func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + if m.issueList.FilterState() == list.Filtering { + cmd, _ := m.issueList.Update(msg) + return m, cmd + } + + cmd := m.handleKeyMsg(msg) + if cmd != nil { + return m, cmd + } + + case tea.WindowSizeMsg: + m.width = msg.Width + m.height = msg.Height + return m, nil + } + + cmd, changed := m.issueList.Update(msg) + if changed { + if selected := m.issueList.SelectedItem(); selected.ID != "" { + m.issueDetail.SetIssue(selected.Issue) + } + } + + return m, cmd +} diff --git a/pkg/tui/views/dashboard/view.go b/pkg/tui/views/dashboard/view.go new file mode 100644 index 0000000..18d207e --- /dev/null +++ b/pkg/tui/views/dashboard/view.go @@ -0,0 +1,36 @@ +package dashboard + +import ( + "github.com/LazyBachelor/LazyPM/pkg/tui/styles" + "github.com/charmbracelet/lipgloss" +) + +func (m *Model) View() string { + if m.width == 0 || m.height == 0 { + return "Loading..." + } + + m.helpBar.SetWidth(m.width) + + header := m.header.View(m.width) + headerHeight := m.header.Height() + + bottomView := m.helpBar.View() + bottomHeight := m.helpBar.Height() + + contentHeight := m.height - headerHeight - bottomHeight + + totalContentWidth := m.width - 1 + listWidth := totalContentWidth * styles.ListViewRatio / 100 + detailWidth := totalContentWidth - listWidth + + m.issueList.SetSize(listWidth, contentHeight) + m.issueDetail.SetSize(detailWidth, contentHeight) + + listView := m.issueList.View() + detailView := m.issueDetail.View() + + content := lipgloss.JoinHorizontal(lipgloss.Left, listView, detailView) + + return lipgloss.JoinVertical(lipgloss.Left, header, content, bottomView) +} diff --git a/pkg/tui/views/views.go b/pkg/tui/views/views.go new file mode 100644 index 0000000..c7bc138 --- /dev/null +++ b/pkg/tui/views/views.go @@ -0,0 +1,10 @@ +package views + +import ( + "github.com/LazyBachelor/LazyPM/internal/service" + "github.com/LazyBachelor/LazyPM/pkg/tui/views/dashboard" +) + +func NewDashboardView(svc *service.Services) *dashboard.Model { + return dashboard.NewDashboard(svc) +}