diff --git a/go.mod b/go.mod index ec485d2ff0a..4e7e9a0d876 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/github/gh-aw go 1.25.0 require ( - github.com/charmbracelet/bubbles v1.0.0 - github.com/charmbracelet/bubbletea v1.3.10 + charm.land/bubbles/v2 v2.0.0 + charm.land/bubbletea/v2 v2.0.2 + charm.land/lipgloss/v2 v2.0.2 github.com/charmbracelet/huh v0.8.0 - github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc github.com/charmbracelet/x/exp/golden v0.0.0-20251215102626-e0db08df7383 github.com/cli/go-gh/v2 v2.13.0 github.com/creack/pty v1.1.24 @@ -33,22 +33,27 @@ require ( github.com/anthropics/anthropic-sdk-go v1.26.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/aymanbagabas/go-udiff v0.3.1 // indirect + github.com/aymanbagabas/go-udiff v0.4.1 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/ccojocar/zxcvbn-go v1.0.4 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/bubbles v1.0.0 // indirect + github.com/charmbracelet/bubbletea v1.3.10 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect github.com/charmbracelet/harmonica v0.2.0 // indirect + github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc // indirect + github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20251106172358-54469c29c2bc // 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/safeexec v1.0.1 // indirect github.com/cli/shurcooL-graphql v0.0.4 // indirect - github.com/clipperhouse/displaywidth v0.9.0 // indirect - github.com/clipperhouse/stringish v0.1.1 // indirect - github.com/clipperhouse/uax29/v2 v2.6.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect @@ -70,7 +75,7 @@ require ( 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-runewidth v0.0.20 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect @@ -103,7 +108,7 @@ require ( golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect + golang.org/x/sys v0.42.0 // indirect golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect golang.org/x/text v0.34.0 // indirect golang.org/x/tools v0.42.0 // indirect diff --git a/go.sum b/go.sum index 4fbec5e26ce..001d786a1ea 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s= +charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI= +charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0= +charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ= +charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs= +charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM= cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= @@ -14,8 +20,8 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 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/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= +github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= @@ -28,14 +34,16 @@ github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5f github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= 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.20250319133953-166f707985bc h1:nFRtCfZu/zkltd2lsLUPlVNv3ej/Atod9hcdbRZtlys= github.com/charmbracelet/lipgloss v1.1.1-0.20250319133953-166f707985bc/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA= +github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= 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= @@ -52,6 +60,8 @@ github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSg github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= +github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM= +github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/cli/go-gh/v2 v2.13.0 h1:jEHZu/VPVoIJkciK3pzZd3rbT8J90swsK5Ui4ewH1ys= @@ -60,12 +70,10 @@ github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00= github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY= github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk= -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.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= -github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= 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= @@ -142,8 +150,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -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-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= +github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= @@ -247,8 +255,8 @@ golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= diff --git a/pkg/console/banner.go b/pkg/console/banner.go index f0ff3e6241b..6667a520e5c 100644 --- a/pkg/console/banner.go +++ b/pkg/console/banner.go @@ -8,7 +8,7 @@ import ( "os" "strings" - "github.com/charmbracelet/lipgloss" + lipgloss "charm.land/lipgloss/v2" "github.com/github/gh-aw/pkg/styles" ) diff --git a/pkg/console/console.go b/pkg/console/console.go index 7834373bd54..128163ea56a 100644 --- a/pkg/console/console.go +++ b/pkg/console/console.go @@ -9,8 +9,8 @@ import ( "strconv" "strings" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/lipgloss/table" + lipgloss "charm.land/lipgloss/v2" + "charm.land/lipgloss/v2/table" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/styles" "github.com/github/gh-aw/pkg/tty" @@ -203,11 +203,16 @@ func RenderTable(config TableConfig) string { PaddingRight(1) } + borderStyle := lipgloss.NewStyle() + if isTTY() { + borderStyle = styles.TableBorder + } + t := table.New(). Headers(config.Headers...). Rows(allRows...). Border(styles.RoundedBorder). - BorderStyle(styles.TableBorder). + BorderStyle(borderStyle). StyleFunc(styleFunc) output.WriteString(t.String()) diff --git a/pkg/console/list.go b/pkg/console/list.go index 2e0f6471f11..1a514bcc1c3 100644 --- a/pkg/console/list.go +++ b/pkg/console/list.go @@ -9,9 +9,9 @@ import ( "os" "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "charm.land/bubbles/v2/list" + tea "charm.land/bubbletea/v2" + lipgloss "charm.land/lipgloss/v2" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/styles" "github.com/github/gh-aw/pkg/tty" @@ -34,7 +34,7 @@ func (m listModel) Init() tea.Cmd { // Update handles messages and updates the model func (m listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case tea.KeyMsg: + case tea.KeyPressMsg: switch msg.String() { case "ctrl+c", "q", "esc": m.quitting = true @@ -56,11 +56,13 @@ func (m listModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } // View renders the list -func (m listModel) View() string { +func (m listModel) View() tea.View { if m.quitting { - return "" + return tea.View{} } - return m.list.View() + v := tea.NewView(m.list.View()) + v.AltScreen = true + return v } // itemDelegate is a custom delegate for list items @@ -163,15 +165,15 @@ func ShowInteractiveList(title string, items []ListItem) (string, error) { Bold(true). Padding(0, 0, 1, 0) - l.Styles.FilterPrompt = lipgloss.NewStyle(). + l.Styles.Filter.Focused.Prompt = lipgloss.NewStyle(). Foreground(styles.ColorInfo) - - l.Styles.FilterCursor = lipgloss.NewStyle(). - Foreground(styles.ColorSuccess) + l.Styles.Filter.Blurred.Prompt = lipgloss.NewStyle(). + Foreground(styles.ColorComment) + l.Styles.Filter.Cursor.Color = styles.ColorSuccess // Create the model and run the program m := listModel{list: l} - p := tea.NewProgram(m, tea.WithAltScreen()) + p := tea.NewProgram(m) finalModel, err := p.Run() if err != nil { diff --git a/pkg/console/progress.go b/pkg/console/progress.go index 96cd9a92060..397f0fa7089 100644 --- a/pkg/console/progress.go +++ b/pkg/console/progress.go @@ -5,7 +5,8 @@ package console import ( "fmt" - "github.com/charmbracelet/bubbles/progress" + "charm.land/bubbles/v2/progress" + lipgloss "charm.land/lipgloss/v2" "github.com/github/gh-aw/pkg/logger" ) @@ -19,9 +20,9 @@ var progressLog = logger.New("console:progress") // - Indeterminate: When total size is unknown (shows activity indicator) // // Visual Features: -// - Scaled gradient effect from purple (#BD93F9) to cyan (#8BE9FD) -// - Smooth color transitions using bubbles v0.21.0+ gradient capabilities -// - Gradient scales with filled portion for enhanced visual feedback +// - Scaled color blend effect from purple (#BD93F9) to cyan (#8BE9FD) +// - Smooth color transitions using bubbles v2 blend capabilities +// - Blend scales with filled portion for enhanced visual feedback // - Works well in both light and dark terminal themes // // The gradient provides visual appeal without affecting functionality: @@ -39,10 +40,10 @@ type ProgressBar struct { // The progress bar automatically adapts to TTY/non-TTY environments func NewProgressBar(total int64) *ProgressBar { progressLog.Printf("Creating determinate progress bar: total=%d bytes", total) - // Use scaled gradient for improved visual effect - // The gradient blends from purple to cyan, creating a smooth - // color transition as progress advances. WithScaledGradient - // ensures the gradient scales with the filled portion for better + // Use scaled color blend for improved visual effect + // The blend transitions from purple to cyan, creating a smooth + // color transition as progress advances. WithScaled(true) + // ensures the blend scales with the filled portion for better // visual feedback. // // Color choices: @@ -50,12 +51,13 @@ func NewProgressBar(total int64) *ProgressBar { // - End (100%): #8BE9FD (cyan) - cool, completion feeling // These colors work well in both light and dark terminal themes prog := progress.New( - progress.WithScaledGradient("#BD93F9", "#8BE9FD"), + progress.WithColors(lipgloss.Color("#BD93F9"), lipgloss.Color("#8BE9FD")), + progress.WithScaled(true), progress.WithWidth(40), ) // Use muted color for empty portion to maintain focus on progress - prog.EmptyColor = "#6272A4" // Muted purple-gray + prog.EmptyColor = lipgloss.Color("#6272A4") // Muted purple-gray return &ProgressBar{ progress: prog, diff --git a/pkg/console/spinner.go b/pkg/console/spinner.go index 803e83a0a73..19e6ba4c601 100644 --- a/pkg/console/spinner.go +++ b/pkg/console/spinner.go @@ -43,8 +43,8 @@ import ( "os" "sync" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" + "charm.land/bubbles/v2/spinner" + tea "charm.land/bubbletea/v2" "github.com/github/gh-aw/pkg/logger" "github.com/github/gh-aw/pkg/styles" "github.com/github/gh-aw/pkg/tty" @@ -63,8 +63,8 @@ type spinnerModel struct { output *os.File } -func (m spinnerModel) Init() tea.Cmd { return m.spinner.Tick } -func (m spinnerModel) View() string { return "" } // Not used with WithoutRenderer +func (m spinnerModel) Init() tea.Cmd { return m.spinner.Tick } +func (m spinnerModel) View() tea.View { return tea.View{} } // Not used with WithoutRenderer func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { @@ -72,7 +72,7 @@ func (m spinnerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.message = string(msg) m.render() return m, nil - case tea.KeyMsg: + case tea.KeyPressMsg: if msg.String() == "ctrl+c" { return m, tea.Quit } diff --git a/pkg/console/spinner_test.go b/pkg/console/spinner_test.go index e719faecd17..1766fd13a97 100644 --- a/pkg/console/spinner_test.go +++ b/pkg/console/spinner_test.go @@ -138,11 +138,11 @@ func TestSpinnerBubbleTeaModel(t *testing.T) { t.Error("Update should return spinnerModel") } - // Note: View() returns empty string with WithoutRenderer() mode + // Note: View() returns an empty View with WithoutRenderer() mode // because rendering is done manually in Update() via render() view := model.View() - if view != "" { - t.Errorf("View should return empty string with WithoutRenderer mode, got '%s'", view) + if view.Content != "" { + t.Errorf("View should return empty content with WithoutRenderer mode, got '%s'", view.Content) } } diff --git a/pkg/styles/theme.go b/pkg/styles/theme.go index e543df0c472..fb9932662c1 100644 --- a/pkg/styles/theme.go +++ b/pkg/styles/theme.go @@ -4,7 +4,7 @@ // // # Adaptive Color System // -// This package uses lipgloss.AdaptiveColor to automatically adapt colors based on the +// This package uses compat.AdaptiveColor to automatically adapt colors based on the // terminal background, ensuring good readability in both light and dark terminal themes. // Each color constant includes both Light and Dark variants that are automatically // selected based on the user's terminal configuration. @@ -46,76 +46,107 @@ // fmt.Println(customStyle.Render("Custom styled text")) package styles -import "github.com/charmbracelet/lipgloss" +import ( + lipgloss "charm.land/lipgloss/v2" + "charm.land/lipgloss/v2/compat" +) + +// Hex color constants for light and dark variants. +// These are used both to build the AdaptiveColor values at runtime and to +// enable straightforward assertions in tests (same package). +const ( + hexColorErrorLight = "#D73737" + hexColorErrorDark = "#FF5555" + hexColorWarningLight = "#E67E22" + hexColorWarningDark = "#FFB86C" + hexColorSuccessLight = "#27AE60" + hexColorSuccessDark = "#50FA7B" + hexColorInfoLight = "#2980B9" + hexColorInfoDark = "#8BE9FD" + hexColorPurpleLight = "#8E44AD" + hexColorPurpleDark = "#BD93F9" + hexColorYellowLight = "#B7950B" + hexColorYellowDark = "#F1FA8C" + hexColorCommentLight = "#6C7A89" + hexColorCommentDark = "#6272A4" + hexColorForegroundLight = "#2C3E50" + hexColorForegroundDark = "#F8F8F2" + hexColorBackgroundLight = "#ECF0F1" + hexColorBackgroundDark = "#282A36" + hexColorBorderLight = "#BDC3C7" + hexColorBorderDark = "#44475A" + hexColorTableAltRowLight = "#F5F5F5" + hexColorTableAltRowDark = "#1A1A1A" +) // Adaptive colors that work well in both light and dark terminal themes. // Light variants use darker, more saturated colors for visibility on light backgrounds. // Dark variants use brighter colors (Dracula theme inspired) for dark backgrounds. var ( // ColorError is used for error messages and critical issues. - ColorError = lipgloss.AdaptiveColor{ - Light: "#D73737", // Darker red for light backgrounds - Dark: "#FF5555", // Bright red for dark backgrounds (Dracula) + ColorError = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorErrorLight), // Darker red for light backgrounds + Dark: lipgloss.Color(hexColorErrorDark), // Bright red for dark backgrounds (Dracula) } // ColorWarning is used for warning messages and cautionary information. - ColorWarning = lipgloss.AdaptiveColor{ - Light: "#E67E22", // Darker orange for light backgrounds - Dark: "#FFB86C", // Bright orange for dark backgrounds (Dracula) + ColorWarning = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorWarningLight), // Darker orange for light backgrounds + Dark: lipgloss.Color(hexColorWarningDark), // Bright orange for dark backgrounds (Dracula) } // ColorSuccess is used for success messages and confirmations. - ColorSuccess = lipgloss.AdaptiveColor{ - Light: "#27AE60", // Darker green for light backgrounds - Dark: "#50FA7B", // Bright green for dark backgrounds (Dracula) + ColorSuccess = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorSuccessLight), // Darker green for light backgrounds + Dark: lipgloss.Color(hexColorSuccessDark), // Bright green for dark backgrounds (Dracula) } // ColorInfo is used for informational messages - ColorInfo = lipgloss.AdaptiveColor{ - Light: "#2980B9", // Darker cyan/blue for light backgrounds - Dark: "#8BE9FD", // Bright cyan for dark backgrounds (Dracula) + ColorInfo = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorInfoLight), // Darker cyan/blue for light backgrounds + Dark: lipgloss.Color(hexColorInfoDark), // Bright cyan for dark backgrounds (Dracula) } // ColorPurple is used for file paths, commands, and highlights - ColorPurple = lipgloss.AdaptiveColor{ - Light: "#8E44AD", // Darker purple for light backgrounds - Dark: "#BD93F9", // Bright purple for dark backgrounds (Dracula) + ColorPurple = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorPurpleLight), // Darker purple for light backgrounds + Dark: lipgloss.Color(hexColorPurpleDark), // Bright purple for dark backgrounds (Dracula) } // ColorYellow is used for progress messages and attention-grabbing content - ColorYellow = lipgloss.AdaptiveColor{ - Light: "#B7950B", // Darker yellow/gold for light backgrounds - Dark: "#F1FA8C", // Bright yellow for dark backgrounds (Dracula) + ColorYellow = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorYellowLight), // Darker yellow/gold for light backgrounds + Dark: lipgloss.Color(hexColorYellowDark), // Bright yellow for dark backgrounds (Dracula) } // ColorComment is used for secondary/muted information like line numbers - ColorComment = lipgloss.AdaptiveColor{ - Light: "#6C7A89", // Muted gray-blue for light backgrounds - Dark: "#6272A4", // Muted purple-gray for dark backgrounds (Dracula) + ColorComment = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorCommentLight), // Muted gray-blue for light backgrounds + Dark: lipgloss.Color(hexColorCommentDark), // Muted purple-gray for dark backgrounds (Dracula) } // ColorForeground is used for primary text content - ColorForeground = lipgloss.AdaptiveColor{ - Light: "#2C3E50", // Dark gray for light backgrounds - Dark: "#F8F8F2", // Light gray/white for dark backgrounds (Dracula) + ColorForeground = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorForegroundLight), // Dark gray for light backgrounds + Dark: lipgloss.Color(hexColorForegroundDark), // Light gray/white for dark backgrounds (Dracula) } // ColorBackground is used for highlighted backgrounds - ColorBackground = lipgloss.AdaptiveColor{ - Light: "#ECF0F1", // Light gray for light backgrounds - Dark: "#282A36", // Dark purple/gray for dark backgrounds (Dracula) + ColorBackground = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorBackgroundLight), // Light gray for light backgrounds + Dark: lipgloss.Color(hexColorBackgroundDark), // Dark purple/gray for dark backgrounds (Dracula) } // ColorBorder is used for table borders and dividers - ColorBorder = lipgloss.AdaptiveColor{ - Light: "#BDC3C7", // Light gray border for light backgrounds - Dark: "#44475A", // Dark purple border for dark backgrounds (Dracula) + ColorBorder = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorBorderLight), // Light gray border for light backgrounds + Dark: lipgloss.Color(hexColorBorderDark), // Dark purple border for dark backgrounds (Dracula) } // ColorTableAltRow is used for alternating row backgrounds in tables (zebra striping) - ColorTableAltRow = lipgloss.AdaptiveColor{ - Light: "#F5F5F5", // Subtle light gray for light backgrounds - Dark: "#1A1A1A", // Subtle darker background for dark backgrounds + ColorTableAltRow = compat.AdaptiveColor{ + Light: lipgloss.Color(hexColorTableAltRowLight), // Subtle light gray for light backgrounds + Dark: lipgloss.Color(hexColorTableAltRowDark), // Subtle darker background for dark backgrounds } ) diff --git a/pkg/styles/theme_test.go b/pkg/styles/theme_test.go index 7325d9b8916..fcdbec0acbe 100644 --- a/pkg/styles/theme_test.go +++ b/pkg/styles/theme_test.go @@ -6,37 +6,42 @@ import ( "strings" "testing" - "github.com/charmbracelet/lipgloss" + lipgloss "charm.land/lipgloss/v2" + "charm.land/lipgloss/v2/compat" ) // TestAdaptiveColorsHaveBothVariants verifies that all adaptive colors // have both Light and Dark variants defined func TestAdaptiveColorsHaveBothVariants(t *testing.T) { - colors := map[string]lipgloss.AdaptiveColor{ - "ColorError": ColorError, - "ColorWarning": ColorWarning, - "ColorSuccess": ColorSuccess, - "ColorInfo": ColorInfo, - "ColorPurple": ColorPurple, - "ColorYellow": ColorYellow, - "ColorComment": ColorComment, - "ColorForeground": ColorForeground, - "ColorBackground": ColorBackground, - "ColorBorder": ColorBorder, - "ColorTableAltRow": ColorTableAltRow, + colorDefs := []struct { + name string + light string + dark string + }{ + {"ColorError", hexColorErrorLight, hexColorErrorDark}, + {"ColorWarning", hexColorWarningLight, hexColorWarningDark}, + {"ColorSuccess", hexColorSuccessLight, hexColorSuccessDark}, + {"ColorInfo", hexColorInfoLight, hexColorInfoDark}, + {"ColorPurple", hexColorPurpleLight, hexColorPurpleDark}, + {"ColorYellow", hexColorYellowLight, hexColorYellowDark}, + {"ColorComment", hexColorCommentLight, hexColorCommentDark}, + {"ColorForeground", hexColorForegroundLight, hexColorForegroundDark}, + {"ColorBackground", hexColorBackgroundLight, hexColorBackgroundDark}, + {"ColorBorder", hexColorBorderLight, hexColorBorderDark}, + {"ColorTableAltRow", hexColorTableAltRowLight, hexColorTableAltRowDark}, } - for name, color := range colors { - t.Run(name, func(t *testing.T) { - if color.Light == "" { - t.Errorf("%s has empty Light variant", name) + for _, def := range colorDefs { + t.Run(def.name, func(t *testing.T) { + if def.light == "" { + t.Errorf("%s has empty Light variant", def.name) } - if color.Dark == "" { - t.Errorf("%s has empty Dark variant", name) + if def.dark == "" { + t.Errorf("%s has empty Dark variant", def.name) } // Ensure Light and Dark are different (otherwise adaptive isn't needed) - if color.Light == color.Dark { - t.Errorf("%s has identical Light and Dark variants: %s", name, color.Light) + if def.light == def.dark { + t.Errorf("%s has identical Light and Dark variants: %s", def.name, def.light) } }) } @@ -44,18 +49,22 @@ func TestAdaptiveColorsHaveBothVariants(t *testing.T) { // TestColorFormats verifies all color values are valid hex colors func TestColorFormats(t *testing.T) { - colors := map[string]lipgloss.AdaptiveColor{ - "ColorError": ColorError, - "ColorWarning": ColorWarning, - "ColorSuccess": ColorSuccess, - "ColorInfo": ColorInfo, - "ColorPurple": ColorPurple, - "ColorYellow": ColorYellow, - "ColorComment": ColorComment, - "ColorForeground": ColorForeground, - "ColorBackground": ColorBackground, - "ColorBorder": ColorBorder, - "ColorTableAltRow": ColorTableAltRow, + colorDefs := []struct { + name string + light string + dark string + }{ + {"ColorError", hexColorErrorLight, hexColorErrorDark}, + {"ColorWarning", hexColorWarningLight, hexColorWarningDark}, + {"ColorSuccess", hexColorSuccessLight, hexColorSuccessDark}, + {"ColorInfo", hexColorInfoLight, hexColorInfoDark}, + {"ColorPurple", hexColorPurpleLight, hexColorPurpleDark}, + {"ColorYellow", hexColorYellowLight, hexColorYellowDark}, + {"ColorComment", hexColorCommentLight, hexColorCommentDark}, + {"ColorForeground", hexColorForegroundLight, hexColorForegroundDark}, + {"ColorBackground", hexColorBackgroundLight, hexColorBackgroundDark}, + {"ColorBorder", hexColorBorderLight, hexColorBorderDark}, + {"ColorTableAltRow", hexColorTableAltRowLight, hexColorTableAltRowDark}, } isValidHex := func(s string) bool { @@ -73,15 +82,15 @@ func TestColorFormats(t *testing.T) { return true } - for name, color := range colors { - t.Run(name+"_Light", func(t *testing.T) { - if !isValidHex(color.Light) { - t.Errorf("%s.Light is not a valid hex color: %s", name, color.Light) + for _, def := range colorDefs { + t.Run(def.name+"_Light", func(t *testing.T) { + if !isValidHex(def.light) { + t.Errorf("%s.Light is not a valid hex color: %s", def.name, def.light) } }) - t.Run(name+"_Dark", func(t *testing.T) { - if !isValidHex(color.Dark) { - t.Errorf("%s.Dark is not a valid hex color: %s", name, color.Dark) + t.Run(def.name+"_Dark", func(t *testing.T) { + if !isValidHex(def.dark) { + t.Errorf("%s.Dark is not a valid hex color: %s", def.name, def.dark) } }) } @@ -180,37 +189,37 @@ func TestStylesRenderNonEmpty(t *testing.T) { // TestDarkColorsAreOriginalDracula verifies that dark variants match the original Dracula theme colors func TestDarkColorsAreOriginalDracula(t *testing.T) { - // These are the original colors used in the codebase before the adaptive color migration + // These are the original Dracula palette colors hardcoded for comparison. + // If any hex constant in theme.go changes, this test will catch the drift. expectedDarkColors := map[string]string{ - "ColorError": "#FF5555", - "ColorWarning": "#FFB86C", - "ColorSuccess": "#50FA7B", - "ColorInfo": "#8BE9FD", - "ColorPurple": "#BD93F9", - "ColorYellow": "#F1FA8C", - "ColorComment": "#6272A4", - // ColorForeground and ColorBackground are Dracula theme colors + "ColorError": "#FF5555", + "ColorWarning": "#FFB86C", + "ColorSuccess": "#50FA7B", + "ColorInfo": "#8BE9FD", + "ColorPurple": "#BD93F9", + "ColorYellow": "#F1FA8C", + "ColorComment": "#6272A4", "ColorForeground": "#F8F8F2", "ColorBackground": "#282A36", "ColorBorder": "#44475A", } - actualColors := map[string]lipgloss.AdaptiveColor{ - "ColorError": ColorError, - "ColorWarning": ColorWarning, - "ColorSuccess": ColorSuccess, - "ColorInfo": ColorInfo, - "ColorPurple": ColorPurple, - "ColorYellow": ColorYellow, - "ColorComment": ColorComment, - "ColorForeground": ColorForeground, - "ColorBackground": ColorBackground, - "ColorBorder": ColorBorder, + actualDarkHex := map[string]string{ + "ColorError": hexColorErrorDark, + "ColorWarning": hexColorWarningDark, + "ColorSuccess": hexColorSuccessDark, + "ColorInfo": hexColorInfoDark, + "ColorPurple": hexColorPurpleDark, + "ColorYellow": hexColorYellowDark, + "ColorComment": hexColorCommentDark, + "ColorForeground": hexColorForegroundDark, + "ColorBackground": hexColorBackgroundDark, + "ColorBorder": hexColorBorderDark, } for name, expected := range expectedDarkColors { t.Run(name, func(t *testing.T) { - actual := actualColors[name].Dark + actual := actualDarkHex[name] if actual != expected { t.Errorf("%s.Dark = %s, want %s (original Dracula color)", name, actual, expected) } @@ -218,6 +227,36 @@ func TestDarkColorsAreOriginalDracula(t *testing.T) { } } +// TestAdaptiveColorVarsUseHexConstants verifies that the exported AdaptiveColor vars +// are backed by the expected hex constants (spot-check a few key colors). +func TestAdaptiveColorVarsUseHexConstants(t *testing.T) { + // Verify that the compat.AdaptiveColor vars hold non-nil color values. + colors := map[string]compat.AdaptiveColor{ + "ColorError": ColorError, + "ColorWarning": ColorWarning, + "ColorSuccess": ColorSuccess, + "ColorInfo": ColorInfo, + "ColorPurple": ColorPurple, + "ColorYellow": ColorYellow, + "ColorComment": ColorComment, + "ColorForeground": ColorForeground, + "ColorBackground": ColorBackground, + "ColorBorder": ColorBorder, + "ColorTableAltRow": ColorTableAltRow, + } + + for name, c := range colors { + t.Run(name, func(t *testing.T) { + if c.Light == nil { + t.Errorf("%s.Light is nil", name) + } + if c.Dark == nil { + t.Errorf("%s.Dark is nil", name) + } + }) + } +} + // TestBordersExist verifies that all expected border definitions are defined func TestBordersExist(t *testing.T) { borders := map[string]lipgloss.Border{