28de5aa0ec941a1fcc7bb6eb95df218807b418ee

Author
Ayman Bagabas <ayman.bagabas@gmail.com>
Committer
GitHub <noreply@github.com>
Date

Message

Merge pull request #678 from charmbracelet/v2-exp

Use the new v2 stack

Diff

This diff is truncated to protect this page.

   1diff --git a/cmd/soft/browse/browse.go b/cmd/soft/browse/browse.go
   2index eebf28893885e9844dccd6787f03e613c67e8ffd..ecb3d5302d44aebee5147d5540477bd79d4ba6c6 100644
   3--- a/cmd/soft/browse/browse.go
   4+++ b/cmd/soft/browse/browse.go
   5@@ -5,9 +5,9 @@ import (
   6 	"path/filepath"
   7 	"time"
   8 
   9-	"github.com/charmbracelet/bubbles/key"
  10-	tea "github.com/charmbracelet/bubbletea"
  11-	"github.com/charmbracelet/lipgloss"
  12+	"github.com/charmbracelet/bubbles/v2/key"
  13+	tea "github.com/charmbracelet/bubbletea/v2"
  14+	"github.com/charmbracelet/lipgloss/v2"
  15 	"github.com/charmbracelet/soft-serve/git"
  16 	"github.com/charmbracelet/soft-serve/pkg/proto"
  17 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
  18@@ -39,9 +39,8 @@ var Command = &cobra.Command{
  19 
  20 		// Bubble Tea uses Termenv default output so we have to use the same
  21 		// thing here.
  22-		output := lipgloss.DefaultRenderer()
  23 		ctx := cmd.Context()
  24-		c := common.NewCommon(ctx, output, 0, 0)
  25+		c := common.NewCommon(ctx, 0, 0)
  26 		c.HideCloneCmd = true
  27 		comps := []common.TabComponent{
  28 			repo.NewReadme(c),
  29@@ -152,7 +151,7 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  30 	switch msg := msg.(type) {
  31 	case tea.WindowSizeMsg:
  32 		m.SetSize(msg.Width, msg.Height)
  33-	case tea.KeyMsg:
  34+	case tea.KeyPressMsg:
  35 		switch {
  36 		case key.Matches(msg, m.common.KeyMap.Back) && m.error != nil:
  37 			m.error = nil
  38@@ -166,12 +165,10 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  39 			m.common.Zone.Close()
  40 			return m, tea.Quit
  41 		}
  42-	case tea.MouseMsg:
  43-		if msg.Action != tea.MouseActionPress {
  44-			break
  45-		}
  46-		switch msg.Button {
  47-		case tea.MouseButtonLeft:
  48+	case tea.MouseClickMsg:
  49+		mouse := msg.Mouse()
  50+		switch mouse.Button {
  51+		case tea.MouseLeft:
  52 			switch {
  53 			case m.common.Zone.Get("footer").InBounds(msg):
  54 				cmds = append(cmds, footer.ToggleFooterCmd)
  55diff --git a/cmd/soft/hook/hook.go b/cmd/soft/hook/hook.go
  56index e9b3f1f5c1ff6c3ebf40cbf3f5d5160c53ed8953..8a6fb87dc4ca64a9eaf0da1a1ef7fdaf5a8c7faa 100644
  57--- a/cmd/soft/hook/hook.go
  58+++ b/cmd/soft/hook/hook.go
  59@@ -12,7 +12,7 @@ import (
  60 	"path/filepath"
  61 	"strings"
  62 
  63-	"github.com/charmbracelet/log"
  64+	"github.com/charmbracelet/log/v2"
  65 	"github.com/charmbracelet/soft-serve/cmd"
  66 	"github.com/charmbracelet/soft-serve/pkg/backend"
  67 	"github.com/charmbracelet/soft-serve/pkg/config"
  68diff --git a/cmd/soft/main.go b/cmd/soft/main.go
  69index 9849e8a268f22fba46644af33b1ee140f79f16a2..9b248fd37aa641d6d0707004413bf53ad959b4aa 100644
  70--- a/cmd/soft/main.go
  71+++ b/cmd/soft/main.go
  72@@ -7,7 +7,8 @@ import (
  73 	"runtime/debug"
  74 	"strconv"
  75 
  76-	"github.com/charmbracelet/log"
  77+	"github.com/charmbracelet/colorprofile"
  78+	"github.com/charmbracelet/log/v2"
  79 	"github.com/charmbracelet/soft-serve/cmd/soft/admin"
  80 	"github.com/charmbracelet/soft-serve/cmd/soft/browse"
  81 	"github.com/charmbracelet/soft-serve/cmd/soft/hook"
  82@@ -18,7 +19,6 @@ import (
  83 	"github.com/charmbracelet/soft-serve/pkg/version"
  84 	mcobra "github.com/muesli/mango-cobra"
  85 	"github.com/muesli/roff"
  86-	"github.com/muesli/termenv"
  87 	"github.com/spf13/cobra"
  88 	"go.uber.org/automaxprocs/maxprocs"
  89 )
  90@@ -67,7 +67,7 @@ var (
  91 
  92 func init() {
  93 	if noColor, _ := strconv.ParseBool(os.Getenv("SOFT_SERVE_NO_COLOR")); noColor {
  94-		common.DefaultColorProfile = termenv.Ascii
  95+		common.DefaultColorProfile = colorprofile.NoTTY
  96 	}
  97 
  98 	rootCmd.AddCommand(
  99diff --git a/cmd/soft/serve/server.go b/cmd/soft/serve/server.go
 100index 76e55f581234203600edebb4f7b7bcb5d1d155f7..56402f13e0182a0a5e9c0b089727cd398174a731 100644
 101--- a/cmd/soft/serve/server.go
 102+++ b/cmd/soft/serve/server.go
 103@@ -6,7 +6,7 @@ import (
 104 	"fmt"
 105 	"net/http"
 106 
 107-	"github.com/charmbracelet/log"
 108+	"github.com/charmbracelet/log/v2"
 109 
 110 	"github.com/charmbracelet/soft-serve/pkg/backend"
 111 	"github.com/charmbracelet/soft-serve/pkg/config"
 112diff --git a/go.mod b/go.mod
 113index bbb971c7f45e24d55c50295dd26b1354bd63a219..0a9d3ab30835bed93dc7ad4069bc81a5cca0292c 100644
 114--- a/go.mod
 115+++ b/go.mod
 116@@ -2,31 +2,33 @@ module github.com/charmbracelet/soft-serve
 117 
 118 go 1.23.0
 119 
 120-toolchain go1.24.1
 121-
 122 require (
 123-	github.com/charmbracelet/bubbles v0.21.0
 124-	github.com/charmbracelet/bubbletea v1.3.5
 125-	github.com/charmbracelet/glamour v0.10.0
 126-	github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
 127-	github.com/charmbracelet/wish v1.4.7
 128 	github.com/dustin/go-humanize v1.0.1
 129 	github.com/go-git/go-git/v5 v5.16.2
 130 	github.com/matryer/is v1.4.1
 131 	github.com/muesli/reflow v0.3.0
 132-	github.com/muesli/termenv v0.16.0
 133-	github.com/sergi/go-diff v1.4.0
 134 )
 135 
 136 require (
 137 	github.com/alecthomas/chroma/v2 v2.18.0
 138+	github.com/aymanbagabas/bubblezone/v2 v2.0.0-20250319214444-bb232f16d5e3
 139+)
 140+
 141+require (
 142 	github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2
 143 	github.com/caarlos0/duration v0.0.0-20240108180406-5d492514f3c7
 144-	github.com/caarlos0/env/v11 v11.3.1
 145+	github.com/caarlos0/env/v11 v11.2.2
 146+	github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250603123720-56bbc4a1ba66
 147+	github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250617194119-3f1d09f7d826
 148+	github.com/charmbracelet/colorprofile v0.3.1
 149 	github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20240708204110-bacbfdb68d92
 150+	github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe
 151 	github.com/charmbracelet/keygen v0.5.3
 152-	github.com/charmbracelet/log v0.4.2
 153-	github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef
 154+	github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71
 155+	github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706
 156+	github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894
 157+	github.com/charmbracelet/wish/v2 v2.0.0-20250505151211-5996fc7c1f33
 158+	github.com/charmbracelet/x/ansi v0.9.3
 159 	github.com/go-jose/go-jose/v3 v3.0.4
 160 	github.com/gobwas/glob v0.2.3
 161 	github.com/golang-jwt/jwt/v5 v5.2.2
 162@@ -37,12 +39,12 @@ require (
 163 	github.com/hashicorp/golang-lru/v2 v2.0.7
 164 	github.com/jmoiron/sqlx v1.4.0
 165 	github.com/lib/pq v1.10.9
 166-	github.com/lrstanley/bubblezone v1.0.0
 167 	github.com/muesli/mango-cobra v1.2.0
 168 	github.com/muesli/roff v0.1.0
 169 	github.com/prometheus/client_golang v1.22.0
 170 	github.com/robfig/cron/v3 v3.0.1
 171 	github.com/rogpeppe/go-internal v1.14.1
 172+	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
 173 	github.com/spf13/cobra v1.9.1
 174 	go.uber.org/automaxprocs v1.6.0
 175 	golang.org/x/crypto v0.39.0
 176@@ -54,23 +56,19 @@ require (
 177 require (
 178 	github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
 179 	github.com/atotto/clipboard v0.1.4 // indirect
 180-	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
 181 	github.com/aymerick/douceur v0.2.0 // indirect
 182 	github.com/beorn7/perks v1.0.1 // indirect
 183 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 184-	github.com/charmbracelet/colorprofile v0.3.1 // indirect
 185-	github.com/charmbracelet/x/ansi v0.8.0 // indirect
 186-	github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
 187+	github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa // indirect
 188 	github.com/charmbracelet/x/conpty v0.1.0 // indirect
 189 	github.com/charmbracelet/x/errors v0.0.0-20240725160154-f9f6568126ec // indirect
 190 	github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
 191-	github.com/charmbracelet/x/input v0.3.4 // indirect
 192+	github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 // indirect
 193 	github.com/charmbracelet/x/term v0.2.1 // indirect
 194 	github.com/charmbracelet/x/termios v0.1.0 // indirect
 195-	github.com/charmbracelet/x/windows v0.2.0 // indirect
 196+	github.com/charmbracelet/x/windows v0.2.1 // indirect
 197 	github.com/creack/pty v1.1.21 // indirect
 198 	github.com/dlclark/regexp2 v1.11.5 // indirect
 199-	github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
 200 	github.com/felixge/httpsnoop v1.0.4 // indirect
 201 	github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
 202 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 203@@ -79,11 +77,9 @@ require (
 204 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 205 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
 206 	github.com/mattn/go-isatty v0.0.20 // indirect
 207-	github.com/mattn/go-localereader v0.0.1 // indirect
 208 	github.com/mattn/go-runewidth v0.0.16 // indirect
 209 	github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 // indirect
 210 	github.com/microcosm-cc/bluemonday v1.0.27 // indirect
 211-	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
 212 	github.com/muesli/cancelreader v0.2.2 // indirect
 213 	github.com/muesli/mango v0.2.0 // indirect
 214 	github.com/muesli/mango-pflag v0.1.0 // indirect
 215@@ -102,7 +98,6 @@ require (
 216diff --git a/go.sum b/go.sum
 217index ea0a226f43db522d88b6ac8dfa9c85679932b91f..db678352e5e86f5a654a991561bd1fa8f448ac90 100644
 218--- a/go.sum
 219+++ b/go.sum
 220@@ -10,10 +10,10 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
 221 github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
 222 github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
 223 github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
 224+github.com/aymanbagabas/bubblezone/v2 v2.0.0-20250319214444-bb232f16d5e3 h1:1z2ihw0YUYUhNmRaavyXvG9ZU/9Tj0vj6sA3z5DFIJ8=
 225+github.com/aymanbagabas/bubblezone/v2 v2.0.0-20250319214444-bb232f16d5e3/go.mod h1:sJwqZoo/BSKSizmr0pSJ758RuRsnjlkrOaxPtwlWtOs=
 226 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2 h1:3w5KT+shE3hzWhORGiu2liVjEoaCEXm9uZP47+Gw4So=
 227 github.com/aymanbagabas/git-module v1.8.4-0.20231101154130-8d27204ac6d2/go.mod h1:d4gQ7/3/S2sPq4NnKdtAgUOVr6XtLpWFtxyVV5/+76U=
 228-github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
 229-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
 230 github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
 231 github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
 232 github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 233@@ -22,50 +22,50 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 234 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 235 github.com/caarlos0/duration v0.0.0-20240108180406-5d492514f3c7 h1:kJP/C2eL9DCKrCOlX6lPVmAUAb6U4u9xllgws1kP9ds=
 236 github.com/caarlos0/duration v0.0.0-20240108180406-5d492514f3c7/go.mod h1:mSkwb/eZEwOJJJ4tqAKiuhLIPe0e9+FKhlU0oMCpbf8=
 237-github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
 238-github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
 239+github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg=
 240+github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc=
 241 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 242 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 243-github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
 244-github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
 245-github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
 246-github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
 247+github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250603123720-56bbc4a1ba66 h1:LDUnLzW0DZ4riy4juBAXJWeE94vvVChgZwzhZL/4wuk=
 248+github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250603123720-56bbc4a1ba66/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
 249+github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250617194119-3f1d09f7d826 h1:pQxCWMojVjHePqGzWsANhplourZYsD6AuR6eR2Hi5yc=
 250+github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3.0.20250617194119-3f1d09f7d826/go.mod h1:bfzSaUDPMKrWDjD6wo/ato9lfDdEX83rZgwcXHYWJ98=
 251 github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
 252 github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
 253 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20240708204110-bacbfdb68d92 h1:KtQlsiHfY3K4AoIEh0yUE/wCLHteZ9EzV1hKmx+p7U8=
 254 github.com/charmbracelet/git-lfs-transfer v0.1.1-0.20240708204110-bacbfdb68d92/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
 255-github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
 256-github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
 257+github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe h1:i6ce4CcAlPpTj2ER69m1DBeLZ3RRcHnKExuwhKa3GfY=
 258+github.com/charmbracelet/glamour/v2 v2.0.0-20250516160903-6f1e2c8f9ebe/go.mod h1:p3Q+aN4eQKeM5jhrmXPMgPrlKbmc59rWSnMsSA3udhk=
 259 github.com/charmbracelet/keygen v0.5.3 h1:2MSDC62OUbDy6VmjIE2jM24LuXUvKywLCmaJDmr/Z/4=
 260 github.com/charmbracelet/keygen v0.5.3/go.mod h1:TcpNoMAO5GSmhx3SgcEMqCrtn8BahKhB8AlwnLjRUpk=
 261-github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
 262-github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
 263-github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
 264-github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
 265-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef h1:dNZwn4is5svUd+sQEGsrXtp7VwD2ipYaCkKMzcpAEIE=
 266-github.com/charmbracelet/ssh v0.0.0-20250213143314-8712ec3ff3ef/go.mod h1:hg+I6gvlMl16nS9ZzQNgBIrrCasGwEw0QiLsDcP01Ko=
 267-github.com/charmbracelet/wish v1.4.7 h1:O+jdLac3s6GaqkOHHSwezejNK04vl6VjO1A+hl8J8Yc=
 268-github.com/charmbracelet/wish v1.4.7/go.mod h1:OBZ8vC62JC5cvbxJLh+bIWtG7Ctmct+ewziuUWK+G14=
 269-github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
 270-github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
 271-github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
 272-github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
 273+github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71 h1:X0tsNa2UHCKNw+illiavosasVzqioRo32SRV35iwr2I=
 274+github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.2.0.20250703152125-8e1c474f8a71/go.mod h1:EJWvaCrhOhNGVZMvcjc0yVryl4qqpMs8tz0r9WyEkdQ=
 275+github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706 h1:WkwO6Ks3mSIGnGuSdKl9qDSyfbYK50z2wc2gGMggegE=
 276+github.com/charmbracelet/log/v2 v2.0.0-20250226163916-c379e29ff706/go.mod h1:mjJGp00cxcfvD5xdCa+bso251Jt4owrQvuimJtVmEmM=
 277+github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894 h1:Ffon9TbltLGBsT6XE//YvNuu4OAaThXioqalhH11xEw=
 278+github.com/charmbracelet/ssh v0.0.0-20250128164007-98fd5ae11894/go.mod h1:hg+I6gvlMl16nS9ZzQNgBIrrCasGwEw0QiLsDcP01Ko=
 279+github.com/charmbracelet/wish/v2 v2.0.0-20250505151211-5996fc7c1f33 h1:/TlxP6Lw9Z7NJCQ7TSbuvfO3/AL0UXGDPwH1zO9pV8U=
 280+github.com/charmbracelet/wish/v2 v2.0.0-20250505151211-5996fc7c1f33/go.mod h1:Tssk2d4IKqV3tNiPgwrYSvJMQu+nN9J4JlGmYaXpiR4=
 281+github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
 282+github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
 283+github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa h1:lphz0Z3rsiOtMYiz8axkT24i9yFiueDhJbzyNUADmME=
 284+github.com/charmbracelet/x/cellbuf v0.0.14-0.20250516160309-24eee56f89fa/go.mod h1:xBlh2Yi3DL3zy/2n15kITpg0YZardf/aa/hgUaIM6Rk=
 285 github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
 286 github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
 287 github.com/charmbracelet/x/errors v0.0.0-20240725160154-f9f6568126ec h1:O8c7pFFK0imuHH5JBqv5smlbVoFn4CZKGjtvCQKu1WE=
 288 github.com/charmbracelet/x/errors v0.0.0-20240725160154-f9f6568126ec/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
 289-github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
 290-github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
 291+github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a h1:FsHEJ52OC4VuTzU8t+n5frMjLvpYWEznSr/u8tnkCYw=
 292+github.com/charmbracelet/x/exp/golden v0.0.0-20250207160936-21c02780d27a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
 293 github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf h1:rLG0Yb6MQSDKdB52aGX55JT1oi0P0Kuaj7wi1bLUpnI=
 294 github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf/go.mod h1:B3UgsnsBZS/eX42BlaNiJkD1pPOUa+oF1IYC6Yd2CEU=
 295-github.com/charmbracelet/x/input v0.3.4 h1:Mujmnv/4DaitU0p+kIsrlfZl/UlmeLKw1wAP3e1fMN0=
 296-github.com/charmbracelet/x/input v0.3.4/go.mod h1:JI8RcvdZWQIhn09VzeK3hdp4lTz7+yhiEdpEQtZN+2c=
 297+github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86 h1:BxAEmOBIDajkgao3EsbBxKQCYvgYPGdT62WASLvtf4Y=
 298+github.com/charmbracelet/x/input v0.3.5-0.20250509021451-13796e822d86/go.mod h1:62Rp/6EtTxoeJDSdtpA3tJp3y3ZRpsiekBSje+K8htA=
 299 github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
 300 github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
 301 github.com/charmbracelet/x/termios v0.1.0 h1:y4rjAHeFksBAfGbkRDmVinMg7x7DELIGAFbdNvxg97k=
 302 github.com/charmbracelet/x/termios v0.1.0/go.mod h1:H/EVv/KRnrYjz+fCYa9bsKdqF3S8ouDK0AZEbG7r+/U=
 303-github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw=
 304-github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s=
 305+github.com/charmbracelet/x/windows v0.2.1 h1:3x7vnbpQrjpuq/4L+I4gNsG5htYoCiA5oe9hLjAij5I=
 306+github.com/charmbracelet/x/windows v0.2.1/go.mod h1:ptZp16h40gDYqs5TSawSVW+yiLB13j4kSMA0lSCHL0M=
 307 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 308 github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
 309 github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 310@@ -76,8 +76,6 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
 311 github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 312 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 313 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 314-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
 315-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
 316 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 317 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 318 github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 h1:mtDjlmloH7ytdblogrMz1/8Hqua1y8B4ID+bh3rvod0=
 319@@ -133,16 +131,12 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
 320diff --git a/pkg/backend/auth.go b/pkg/backend/auth.go
 321index 23713347883ddff6c9a2112d27db740f127052c2..7c8158f6d1ad67a580c2f6216b4ad02664f3a94b 100644
 322--- a/pkg/backend/auth.go
 323+++ b/pkg/backend/auth.go
 324@@ -5,7 +5,7 @@ import (
 325 	"crypto/sha256"
 326 	"encoding/hex"
 327 
 328-	"github.com/charmbracelet/log"
 329+	"github.com/charmbracelet/log/v2"
 330 	"golang.org/x/crypto/bcrypt"
 331 )
 332 
 333diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go
 334index ba8796b54546620df6b8377704ee5d76b1c10283..579d4cecbbc95118b8af451fdd8c9069d3c29e63 100644
 335--- a/pkg/backend/backend.go
 336+++ b/pkg/backend/backend.go
 337@@ -3,7 +3,7 @@ package backend
 338 import (
 339 	"context"
 340 
 341-	"github.com/charmbracelet/log"
 342+	"github.com/charmbracelet/log/v2"
 343 	"github.com/charmbracelet/soft-serve/pkg/config"
 344 	"github.com/charmbracelet/soft-serve/pkg/db"
 345 	"github.com/charmbracelet/soft-serve/pkg/store"
 346diff --git a/pkg/backend/webhooks.go b/pkg/backend/webhooks.go
 347index e8e1fe9224f5b2bde6e7cae237599b7912fbcee4..cd3eb07e78ce3016c835c6ae00976f5b33fd427c 100644
 348--- a/pkg/backend/webhooks.go
 349+++ b/pkg/backend/webhooks.go
 350@@ -4,7 +4,7 @@ import (
 351 	"context"
 352 	"encoding/json"
 353 
 354-	"github.com/charmbracelet/log"
 355+	"github.com/charmbracelet/log/v2"
 356 	"github.com/charmbracelet/soft-serve/pkg/db"
 357 	"github.com/charmbracelet/soft-serve/pkg/db/models"
 358 	"github.com/charmbracelet/soft-serve/pkg/proto"
 359diff --git a/pkg/cron/cron.go b/pkg/cron/cron.go
 360index 6fed17b6a15ae6a954e9d6aa8d2418521231b4be..fa290a33e1e826515aeae2fbcd24e78b5a68e95d 100644
 361--- a/pkg/cron/cron.go
 362+++ b/pkg/cron/cron.go
 363@@ -4,7 +4,7 @@ import (
 364 	"context"
 365 	"time"
 366 
 367-	"github.com/charmbracelet/log"
 368+	"github.com/charmbracelet/log/v2"
 369 	"github.com/robfig/cron/v3"
 370 )
 371 
 372diff --git a/pkg/cron/cron_test.go b/pkg/cron/cron_test.go
 373index c254191b2e45e889b5d2ce3f10fc1c7f16949c83..b8151db55fbbacb7444a87ad67e3f83ba87e6343 100644
 374--- a/pkg/cron/cron_test.go
 375+++ b/pkg/cron/cron_test.go
 376@@ -6,7 +6,7 @@ import (
 377 	"fmt"
 378 	"testing"
 379 
 380-	"github.com/charmbracelet/log"
 381+	"github.com/charmbracelet/log/v2"
 382 )
 383 
 384 func TestCronLogger(t *testing.T) {
 385diff --git a/pkg/daemon/daemon.go b/pkg/daemon/daemon.go
 386index 3afe5e766b7765fb78321e6d5e80dad1f65e65b4..30faadd7aac103ccf7110b36f402908f3ee67ed1 100644
 387--- a/pkg/daemon/daemon.go
 388+++ b/pkg/daemon/daemon.go
 389@@ -12,7 +12,7 @@ import (
 390 	"sync/atomic"
 391 	"time"
 392 
 393-	"github.com/charmbracelet/log"
 394+	"github.com/charmbracelet/log/v2"
 395 	"github.com/charmbracelet/soft-serve/pkg/access"
 396 	"github.com/charmbracelet/soft-serve/pkg/backend"
 397 	"github.com/charmbracelet/soft-serve/pkg/config"
 398diff --git a/pkg/db/db.go b/pkg/db/db.go
 399index 968d0425afeb65cf7a62d6ebc6c406922178f692..7d5fc2ebea59141c5f40ff83341303792709eebf 100644
 400--- a/pkg/db/db.go
 401+++ b/pkg/db/db.go
 402@@ -6,7 +6,7 @@ import (
 403 	"errors"
 404 	"fmt"
 405 
 406-	"github.com/charmbracelet/log"
 407+	"github.com/charmbracelet/log/v2"
 408 	"github.com/charmbracelet/soft-serve/pkg/config"
 409 	"github.com/jmoiron/sqlx"
 410 	_ "github.com/lib/pq"  // postgres driver
 411diff --git a/pkg/db/logger.go b/pkg/db/logger.go
 412index 821576d11990ebb4b14f76c2015d2a6a8c794f5d..605ec133a82c7676c3a7315904bb09a5fb219e5b 100644
 413--- a/pkg/db/logger.go
 414+++ b/pkg/db/logger.go
 415@@ -5,7 +5,7 @@ import (
 416 	"database/sql"
 417 	"strings"
 418 
 419-	"github.com/charmbracelet/log"
 420+	"github.com/charmbracelet/log/v2"
 421 	"github.com/jmoiron/sqlx"
 422 )
 423 
 424diff --git a/pkg/db/migrate/0003_migrate_lfs_objects.go b/pkg/db/migrate/0003_migrate_lfs_objects.go
 425index d7acd7be715be008c6ad8d261de0af622a6a4496..c89775d8ed0b5fc0f0d414179ff6c3c8f6fc4d4c 100644
 426--- a/pkg/db/migrate/0003_migrate_lfs_objects.go
 427+++ b/pkg/db/migrate/0003_migrate_lfs_objects.go
 428@@ -6,7 +6,7 @@ import (
 429 	"path/filepath"
 430 	"strconv"
 431 
 432-	"github.com/charmbracelet/log"
 433+	"github.com/charmbracelet/log/v2"
 434 	"github.com/charmbracelet/soft-serve/pkg/config"
 435 	"github.com/charmbracelet/soft-serve/pkg/db"
 436 	"github.com/charmbracelet/soft-serve/pkg/db/models"
 437diff --git a/pkg/db/migrate/migrate.go b/pkg/db/migrate/migrate.go
 438index 18bc1780d488d13363e0be7b73768e337a1358e6..8273e454c9b5a8b1ac28edef580147be04dcafee 100644
 439--- a/pkg/db/migrate/migrate.go
 440+++ b/pkg/db/migrate/migrate.go
 441@@ -6,7 +6,7 @@ import (
 442 	"errors"
 443 	"fmt"
 444 
 445-	"github.com/charmbracelet/log"
 446+	"github.com/charmbracelet/log/v2"
 447 	"github.com/charmbracelet/soft-serve/pkg/db"
 448 )
 449 
 450diff --git a/pkg/git/git.go b/pkg/git/git.go
 451index ed922e2649bf8c072a6197315507f465c676b85a..7b18cf22d562ebb638520501309a196a04a86cdb 100644
 452--- a/pkg/git/git.go
 453+++ b/pkg/git/git.go
 454@@ -9,7 +9,7 @@ import (
 455 	"strings"
 456 
 457 	gitm "github.com/aymanbagabas/git-module"
 458-	"github.com/charmbracelet/log"
 459+	"github.com/charmbracelet/log/v2"
 460 	"github.com/charmbracelet/soft-serve/git"
 461 	"github.com/go-git/go-git/v5/plumbing/format/pktline"
 462 )
 463diff --git a/pkg/git/lfs.go b/pkg/git/lfs.go
 464index 3d3ae6f7e78f828360ccc11c3ff010e4f59a8438..212ad08041a14b20d54acbd50bf8da942f3e4c9a 100644
 465--- a/pkg/git/lfs.go
 466+++ b/pkg/git/lfs.go
 467@@ -12,7 +12,7 @@ import (
 468 	"time"
 469 
 470 	"github.com/charmbracelet/git-lfs-transfer/transfer"
 471-	"github.com/charmbracelet/log"
 472+	"github.com/charmbracelet/log/v2"
 473 	"github.com/charmbracelet/soft-serve/pkg/config"
 474 	"github.com/charmbracelet/soft-serve/pkg/db"
 475 	"github.com/charmbracelet/soft-serve/pkg/db/models"
 476diff --git a/pkg/git/lfs_auth.go b/pkg/git/lfs_auth.go
 477index edf0785d45e60c20dea26bb998276ec3a67581ac..32a8e6cba7abbc50d7499ba858bdb4ddffc9e83b 100644
 478--- a/pkg/git/lfs_auth.go
 479+++ b/pkg/git/lfs_auth.go
 480@@ -7,7 +7,7 @@ import (
 481 	"fmt"
 482 	"time"
 483 
 484-	"github.com/charmbracelet/log"
 485+	"github.com/charmbracelet/log/v2"
 486 	"github.com/charmbracelet/soft-serve/pkg/config"
 487 	"github.com/charmbracelet/soft-serve/pkg/jwk"
 488 	"github.com/charmbracelet/soft-serve/pkg/lfs"
 489diff --git a/pkg/git/lfs_log.go b/pkg/git/lfs_log.go
 490index 979932d51df6a5cfd9700c12f7d103d2d2a8aed3..fed14ba440aacc986c99e646fbef90e455c266b2 100644
 491--- a/pkg/git/lfs_log.go
 492+++ b/pkg/git/lfs_log.go
 493@@ -2,7 +2,7 @@ package git
 494 
 495 import (
 496 	"github.com/charmbracelet/git-lfs-transfer/transfer"
 497-	"github.com/charmbracelet/log"
 498+	"github.com/charmbracelet/log/v2"
 499 )
 500 
 501 type lfsLogger struct {
 502diff --git a/pkg/git/service.go b/pkg/git/service.go
 503index 24576421d4b9a0850501a34143ae6482d4358391..af589c25719320f4c2d8581fe375de32158346b9 100644
 504--- a/pkg/git/service.go
 505+++ b/pkg/git/service.go
 506@@ -10,7 +10,7 @@ import (
 507 	"strings"
 508 	"sync"
 509 
 510-	"github.com/charmbracelet/log"
 511+	"github.com/charmbracelet/log/v2"
 512 )
 513 
 514 // Service is a Git daemon service.
 515diff --git a/pkg/hooks/gen.go b/pkg/hooks/gen.go
 516index 245e5eb69fe4844c19c721cd7573ff723b9b0aab..d06661be6010efa7ae6acc94e296fd072bae50b1 100644
 517--- a/pkg/hooks/gen.go
 518+++ b/pkg/hooks/gen.go
 519@@ -7,7 +7,7 @@ import (
 520 	"path/filepath"
 521 	"text/template"
 522 
 523-	"github.com/charmbracelet/log"
 524+	"github.com/charmbracelet/log/v2"
 525 	"github.com/charmbracelet/soft-serve/pkg/config"
 526 	"github.com/charmbracelet/soft-serve/pkg/utils"
 527 )
 528diff --git a/pkg/jobs/mirror.go b/pkg/jobs/mirror.go
 529index 16ea923418ea49d4a24bf7892257d8a93688e3a6..9ebfdd864e76494b5081bb9ba244947989b591ee 100644
 530--- a/pkg/jobs/mirror.go
 531+++ b/pkg/jobs/mirror.go
 532@@ -7,7 +7,7 @@ import (
 533 	"runtime"
 534 	"strings"
 535 
 536-	"github.com/charmbracelet/log"
 537+	"github.com/charmbracelet/log/v2"
 538 	"github.com/charmbracelet/soft-serve/git"
 539 	"github.com/charmbracelet/soft-serve/pkg/backend"
 540 	"github.com/charmbracelet/soft-serve/pkg/config"
 541diff --git a/pkg/lfs/basic_transfer.go b/pkg/lfs/basic_transfer.go
 542index 609197c1035c176f6d31495d3a8cf57de7f5830c..d3e4100cd3477d77b3fe8a5953882f847336fd53 100644
 543--- a/pkg/lfs/basic_transfer.go
 544+++ b/pkg/lfs/basic_transfer.go
 545@@ -9,7 +9,7 @@ import (
 546 	"io"
 547 	"net/http"
 548 
 549-	"github.com/charmbracelet/log"
 550+	"github.com/charmbracelet/log/v2"
 551 )
 552 
 553 // BasicTransferAdapter implements the "basic" adapter
 554diff --git a/pkg/lfs/http_client.go b/pkg/lfs/http_client.go
 555index a8b55031f083d1fd5e4b9aaccd896fb50013f41c..c78a2e4b2e061a3f8b9c4a675b4f97226ee209bb 100644
 556--- a/pkg/lfs/http_client.go
 557+++ b/pkg/lfs/http_client.go
 558@@ -8,7 +8,7 @@ import (
 559 	"fmt"
 560 	"net/http"
 561 
 562-	"github.com/charmbracelet/log"
 563+	"github.com/charmbracelet/log/v2"
 564 )
 565 
 566 // httpClient is a Git LFS client to communicate with a LFS source API.
 567diff --git a/pkg/log/log.go b/pkg/log/log.go
 568index b9134ead76d6d5a96a413cfea5d2202647054374..0c1552589506a2cd82ae11aa77e300218e47d4d6 100644
 569--- a/pkg/log/log.go
 570+++ b/pkg/log/log.go
 571@@ -5,7 +5,7 @@ import (
 572 	"strings"
 573 	"time"
 574 
 575-	"github.com/charmbracelet/log"
 576+	"github.com/charmbracelet/log/v2"
 577 	"github.com/charmbracelet/soft-serve/pkg/config"
 578 )
 579 
 580diff --git a/pkg/ssh/cmd/blob.go b/pkg/ssh/cmd/blob.go
 581index 9678a37a49ce426f3e6a910c04a7f804159d95ee..1aa8fbbe16ffc50c116c4e267f986d20663dcc8f 100644
 582--- a/pkg/ssh/cmd/blob.go
 583+++ b/pkg/ssh/cmd/blob.go
 584@@ -2,8 +2,8 @@ package cmd
 585 
 586 import (
 587 	"fmt"
 588+	"os"
 589 
 590-	"github.com/charmbracelet/lipgloss"
 591 	"github.com/charmbracelet/soft-serve/git"
 592 	"github.com/charmbracelet/soft-serve/pkg/backend"
 593 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 594@@ -12,12 +12,16 @@ import (
 595 )
 596 
 597 // blobCommand returns a command that prints the contents of a file.
 598-func blobCommand(renderer *lipgloss.Renderer) *cobra.Command {
 599+func blobCommand() *cobra.Command {
 600 	var linenumber bool
 601 	var color bool
 602 	var raw bool
 603+	var noColor bool
 604+	if testrun, ok := os.LookupEnv("SOFT_SERVE_NO_COLOR"); ok && testrun == "1" {
 605+		noColor = true
 606+	}
 607 
 608-	styles := styles.DefaultStyles(renderer)
 609+	styles := styles.DefaultStyles()
 610 	cmd := &cobra.Command{
 611 		Use:               "blob REPOSITORY [REFERENCE] [PATH]",
 612 		Aliases:           []string{"cat", "show"},
 613@@ -84,7 +88,7 @@ func blobCommand(renderer *lipgloss.Renderer) *cobra.Command {
 614 					return fmt.Errorf("binary file: use --raw to print")
 615 				}
 616 			} else {
 617-				if color {
 618+				if color && !noColor {
 619 					c, err = common.FormatHighlight(fp, c)
 620 					if err != nil {
 621 						return err
 622@@ -92,7 +96,7 @@ func blobCommand(renderer *lipgloss.Renderer) *cobra.Command {
 623 				}
 624 
 625 				if linenumber {
 626-					c, _ = common.FormatLineNumber(styles, c, color)
 627+					c, _ = common.FormatLineNumber(styles, c, color && !noColor)
 628 				}
 629 
 630 				cmd.Println(c)
 631diff --git a/pkg/ssh/cmd/commit.go b/pkg/ssh/cmd/commit.go
 632index 52af6725a53692f7310a75cc6efbce33d0c8492b..88fb1b2c8bd310a3b30492c8e67a02d3c37df72d 100644
 633--- a/pkg/ssh/cmd/commit.go
 634+++ b/pkg/ssh/cmd/commit.go
 635@@ -5,8 +5,7 @@ import (
 636 	"strings"
 637 	"time"
 638 
 639-	gansi "github.com/charmbracelet/glamour/ansi"
 640-	"github.com/charmbracelet/lipgloss"
 641+	gansi "github.com/charmbracelet/glamour/v2/ansi"
 642 	"github.com/charmbracelet/soft-serve/git"
 643 	"github.com/charmbracelet/soft-serve/pkg/backend"
 644 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 645@@ -15,7 +14,7 @@ import (
 646 )
 647 
 648 // commitCommand returns a command that prints the contents of a commit.
 649-func commitCommand(renderer *lipgloss.Renderer) *cobra.Command {
 650+func commitCommand() *cobra.Command {
 651 	var color bool
 652 	var patchOnly bool
 653 
 654@@ -55,7 +54,7 @@ func commitCommand(renderer *lipgloss.Renderer) *cobra.Command {
 655 				return err
 656 			}
 657 
 658-			commonStyle := styles.DefaultStyles(renderer)
 659+			commonStyle := styles.DefaultStyles()
 660 			style := commonStyle.Log
 661 
 662 			s := strings.Builder{}
 663diff --git a/pkg/ssh/cmd/git.go b/pkg/ssh/cmd/git.go
 664index 7265c9a762dd57ec62348b2da960d1ef7cca3651..620b9f0890e2ee4c19b202f9e97eee7ecab325b2 100644
 665--- a/pkg/ssh/cmd/git.go
 666+++ b/pkg/ssh/cmd/git.go
 667@@ -6,7 +6,7 @@ import (
 668 	"strings"
 669 	"time"
 670 
 671-	"github.com/charmbracelet/log"
 672+	"github.com/charmbracelet/log/v2"
 673 	"github.com/charmbracelet/soft-serve/pkg/access"
 674 	"github.com/charmbracelet/soft-serve/pkg/backend"
 675 	"github.com/charmbracelet/soft-serve/pkg/config"
 676diff --git a/pkg/ssh/cmd/repo.go b/pkg/ssh/cmd/repo.go
 677index 6a2bb541e62e5ac97da0516bb28440a91ccfbe19..fa3e72ff9a3098b40314acbca119c86628f9850c 100644
 678--- a/pkg/ssh/cmd/repo.go
 679+++ b/pkg/ssh/cmd/repo.go
 680@@ -4,14 +4,13 @@ import (
 681 	"fmt"
 682 	"strings"
 683 
 684-	"github.com/charmbracelet/lipgloss"
 685 	"github.com/charmbracelet/soft-serve/pkg/backend"
 686 	"github.com/charmbracelet/soft-serve/pkg/proto"
 687 	"github.com/spf13/cobra"
 688 )
 689 
 690 // RepoCommand returns a command for managing repositories.
 691-func RepoCommand(renderer *lipgloss.Renderer) *cobra.Command {
 692+func RepoCommand() *cobra.Command {
 693 	cmd := &cobra.Command{
 694 		Use:     "repo",
 695 		Aliases: []string{"repos", "repository", "repositories"},
 696@@ -19,10 +18,10 @@ func RepoCommand(renderer *lipgloss.Renderer) *cobra.Command {
 697 	}
 698 
 699 	cmd.AddCommand(
 700-		blobCommand(renderer),
 701+		blobCommand(),
 702 		branchCommand(),
 703 		collabCommand(),
 704-		commitCommand(renderer),
 705+		commitCommand(),
 706 		createCommand(),
 707 		deleteCommand(),
 708 		descriptionCommand(),
 709diff --git a/pkg/ssh/cmd/tag.go b/pkg/ssh/cmd/tag.go
 710index 009ac03083a90c6809b61d2c904ae398a59572fe..fc21ce0e4f73a2a340d61ade327ecfd157c1af0e 100644
 711--- a/pkg/ssh/cmd/tag.go
 712+++ b/pkg/ssh/cmd/tag.go
 713@@ -3,7 +3,7 @@ package cmd
 714 import (
 715 	"strings"
 716 
 717-	"github.com/charmbracelet/log"
 718+	"github.com/charmbracelet/log/v2"
 719 	"github.com/charmbracelet/soft-serve/git"
 720 	"github.com/charmbracelet/soft-serve/pkg/backend"
 721 	"github.com/charmbracelet/soft-serve/pkg/proto"
 722diff --git a/pkg/ssh/cmd/token.go b/pkg/ssh/cmd/token.go
 723index 6fb8295379189c7e42cf26907c26e08862f0da7d..6a11ebfcedbc9a5a3f2aa07ffb4cfb5f997002ad 100644
 724--- a/pkg/ssh/cmd/token.go
 725+++ b/pkg/ssh/cmd/token.go
 726@@ -6,7 +6,7 @@ import (
 727 	"time"
 728 
 729 	"github.com/caarlos0/duration"
 730-	"github.com/charmbracelet/lipgloss/table"
 731+	"github.com/charmbracelet/lipgloss/v2/table"
 732 	"github.com/charmbracelet/soft-serve/pkg/backend"
 733 	"github.com/charmbracelet/soft-serve/pkg/proto"
 734 	"github.com/dustin/go-humanize"
 735diff --git a/pkg/ssh/cmd/webhooks.go b/pkg/ssh/cmd/webhooks.go
 736index e7409ebbaeafd43b2ad37c48695d2ab1729bccf7..ca6e8978929c1ca283d3b5aff470ffed34358132 100644
 737--- a/pkg/ssh/cmd/webhooks.go
 738+++ b/pkg/ssh/cmd/webhooks.go
 739@@ -5,7 +5,7 @@ import (
 740 	"strconv"
 741 	"strings"
 742 
 743-	"github.com/charmbracelet/lipgloss/table"
 744+	"github.com/charmbracelet/lipgloss/v2/table"
 745 	"github.com/charmbracelet/soft-serve/pkg/backend"
 746 	"github.com/charmbracelet/soft-serve/pkg/webhook"
 747 	"github.com/dustin/go-humanize"
 748diff --git a/pkg/ssh/middleware.go b/pkg/ssh/middleware.go
 749index 1bd93f99c0c81122f18c956056fa07cebaedee96..32c31af933a807bef2de20dd37e53778a7cb91aa 100644
 750--- a/pkg/ssh/middleware.go
 751+++ b/pkg/ssh/middleware.go
 752@@ -2,10 +2,9 @@ package ssh
 753 
 754 import (
 755 	"fmt"
 756-	"os"
 757 	"time"
 758 
 759-	"github.com/charmbracelet/log"
 760+	"github.com/charmbracelet/log/v2"
 761 	"github.com/charmbracelet/soft-serve/pkg/backend"
 762 	"github.com/charmbracelet/soft-serve/pkg/config"
 763 	"github.com/charmbracelet/soft-serve/pkg/db"
 764@@ -14,9 +13,7 @@ import (
 765 	"github.com/charmbracelet/soft-serve/pkg/sshutils"
 766 	"github.com/charmbracelet/soft-serve/pkg/store"
 767 	"github.com/charmbracelet/ssh"
 768-	"github.com/charmbracelet/wish"
 769-	bm "github.com/charmbracelet/wish/bubbletea"
 770-	"github.com/muesli/termenv"
 771+	"github.com/charmbracelet/wish/v2"
 772 	"github.com/prometheus/client_golang/prometheus"
 773 	"github.com/prometheus/client_golang/prometheus/promauto"
 774 	"github.com/spf13/cobra"
 775@@ -91,12 +88,6 @@ func CommandMiddleware(sh ssh.Handler) ssh.Handler {
 776 		ctx := s.Context()
 777 		cfg := config.FromContext(ctx)
 778 
 779-		renderer := bm.MakeRenderer(s)
 780-		if testrun, ok := os.LookupEnv("SOFT_SERVE_NO_COLOR"); ok && testrun == "1" {
 781-			// Disable colors when running tests.
 782-			renderer.SetColorProfile(termenv.Ascii)
 783-		}
 784-
 785 		args := s.Command()
 786 		cliCommandCounter.WithLabelValues(cmd.CommandName(args)).Inc()
 787 		rootCmd := &cobra.Command{
 788@@ -111,7 +102,7 @@ func CommandMiddleware(sh ssh.Handler) ssh.Handler {
 789 			cmd.GitUploadPackCommand(),
 790 			cmd.GitUploadArchiveCommand(),
 791 			cmd.GitReceivePackCommand(),
 792-			cmd.RepoCommand(renderer),
 793+			cmd.RepoCommand(),
 794 			cmd.SettingsCommand(),
 795 			cmd.UserCommand(),
 796 			cmd.InfoCommand(),
 797diff --git a/pkg/ssh/session.go b/pkg/ssh/session.go
 798index aecd4619511e977c7367d069d2900fb4b5d1a3f1..892da8bba0de8330c6761e0e30a76adbd80afe12 100644
 799--- a/pkg/ssh/session.go
 800+++ b/pkg/ssh/session.go
 801@@ -1,19 +1,17 @@
 802 package ssh
 803 
 804 import (
 805-	"os"
 806 	"time"
 807 
 808-	tea "github.com/charmbracelet/bubbletea"
 809+	tea "github.com/charmbracelet/bubbletea/v2"
 810 	"github.com/charmbracelet/soft-serve/pkg/access"
 811 	"github.com/charmbracelet/soft-serve/pkg/backend"
 812 	"github.com/charmbracelet/soft-serve/pkg/config"
 813 	"github.com/charmbracelet/soft-serve/pkg/proto"
 814 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 815 	"github.com/charmbracelet/ssh"
 816-	"github.com/charmbracelet/wish"
 817-	bm "github.com/charmbracelet/wish/bubbletea"
 818-	"github.com/muesli/termenv"
 819+	"github.com/charmbracelet/wish/v2"
 820+	bm "github.com/charmbracelet/wish/v2/bubbletea"
 821 	"github.com/prometheus/client_golang/prometheus"
 822 	"github.com/prometheus/client_golang/prometheus/promauto"
 823 )
 824@@ -54,22 +52,18 @@ func SessionHandler(s ssh.Session) *tea.Program {
 825 		}
 826 	}
 827 
 828-	renderer := bm.MakeRenderer(s)
 829-	if testrun, ok := os.LookupEnv("SOFT_SERVE_NO_COLOR"); ok && testrun == "1" {
 830-		// Disable colors when running tests.
 831-		renderer.SetColorProfile(termenv.Ascii)
 832-	}
 833-
 834-	c := common.NewCommon(ctx, renderer, pty.Window.Width, pty.Window.Height)
 835-	c.SetValue(common.ConfigKey, cfg)
 836-	m := NewUI(c, initialRepo)
 837 	opts := bm.MakeOptions(s)
 838 	opts = append(opts,
 839 		tea.WithAltScreen(),
 840 		tea.WithoutCatchPanics(),
 841 		tea.WithMouseCellMotion(),
 842 		tea.WithContext(ctx),
 843+		tea.WithColorProfile(common.DefaultColorProfile),
 844 	)
 845+
 846+	c := common.NewCommon(ctx, pty.Window.Width, pty.Window.Height)
 847+	c.SetValue(common.ConfigKey, cfg)
 848+	m := NewUI(c, initialRepo)
 849 	p := tea.NewProgram(m, opts...)
 850 
 851 	tuiSessionCounter.WithLabelValues(initialRepo, pty.Term).Inc()
 852diff --git a/pkg/ssh/session_test.go b/pkg/ssh/session_test.go
 853index 792759c167361c4bd9c10783dda279983e6bc890..f831327450cf92c32188bde1096473274c57e78e 100644
 854--- a/pkg/ssh/session_test.go
 855+++ b/pkg/ssh/session_test.go
 856@@ -8,7 +8,7 @@ import (
 857 	"testing"
 858 	"time"
 859 
 860-	"github.com/charmbracelet/log"
 861+	"github.com/charmbracelet/log/v2"
 862 	"github.com/charmbracelet/soft-serve/pkg/backend"
 863 	"github.com/charmbracelet/soft-serve/pkg/config"
 864 	"github.com/charmbracelet/soft-serve/pkg/db"
 865@@ -17,10 +17,9 @@ import (
 866 	"github.com/charmbracelet/soft-serve/pkg/store/database"
 867 	"github.com/charmbracelet/soft-serve/pkg/test"
 868 	"github.com/charmbracelet/ssh"
 869-	bm "github.com/charmbracelet/wish/bubbletea"
 870-	"github.com/charmbracelet/wish/testsession"
 871+	bm "github.com/charmbracelet/wish/v2/bubbletea"
 872+	"github.com/charmbracelet/wish/v2/testsession"
 873 	"github.com/matryer/is"
 874-	"github.com/muesli/termenv"
 875 	gossh "golang.org/x/crypto/ssh"
 876 	_ "modernc.org/sqlite" // sqlite driver
 877 )
 878@@ -79,7 +78,7 @@ func setup(tb testing.TB) (*gossh.Session, func() error) {
 879 	be := backend.New(ctx, cfg, dbx, dbstore)
 880 	ctx = backend.WithContext(ctx, be)
 881 	return testsession.New(tb, &ssh.Server{
 882-		Handler: ContextMiddleware(cfg, dbx, dbstore, be, log.Default())(bm.MiddlewareWithProgramHandler(SessionHandler, termenv.ANSI256)(func(s ssh.Session) {
 883+		Handler: ContextMiddleware(cfg, dbx, dbstore, be, log.Default())(bm.MiddlewareWithProgramHandler(SessionHandler)(func(s ssh.Session) {
 884 			_, _, active := s.Pty()
 885 			if !active {
 886 				os.Exit(1)
 887diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go
 888index f63b0d3897f53b95af494a99ee0a4dc00d872ddf..10d5edee632048c98e83fdfb9609c0426e57f2a0 100644
 889--- a/pkg/ssh/ssh.go
 890+++ b/pkg/ssh/ssh.go
 891@@ -5,22 +5,20 @@ import (
 892 	"fmt"
 893 	"net"
 894 	"os"
 895-	"runtime"
 896 	"strconv"
 897 	"time"
 898 
 899 	"github.com/charmbracelet/keygen"
 900-	"github.com/charmbracelet/log"
 901+	"github.com/charmbracelet/log/v2"
 902 	"github.com/charmbracelet/soft-serve/pkg/backend"
 903 	"github.com/charmbracelet/soft-serve/pkg/config"
 904 	"github.com/charmbracelet/soft-serve/pkg/db"
 905 	"github.com/charmbracelet/soft-serve/pkg/proto"
 906 	"github.com/charmbracelet/soft-serve/pkg/store"
 907-	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 908 	"github.com/charmbracelet/ssh"
 909-	"github.com/charmbracelet/wish"
 910-	bm "github.com/charmbracelet/wish/bubbletea"
 911-	rm "github.com/charmbracelet/wish/recover"
 912+	"github.com/charmbracelet/wish/v2"
 913+	bm "github.com/charmbracelet/wish/v2/bubbletea"
 914+	rm "github.com/charmbracelet/wish/v2/recover"
 915 	"github.com/prometheus/client_golang/prometheus"
 916 	"github.com/prometheus/client_golang/prometheus/promauto"
 917 	gossh "golang.org/x/crypto/ssh"
 918@@ -71,7 +69,7 @@ func NewSSHServer(ctx context.Context) (*SSHServer, error) {
 919 		rm.MiddlewareWithLogger(
 920 			logger,
 921 			// BubbleTea middleware.
 922-			bm.MiddlewareWithProgramHandler(SessionHandler, common.DefaultColorProfile),
 923+			bm.MiddlewareWithProgramHandler(SessionHandler),
 924 			// CLI middleware.
 925 			CommandMiddleware,
 926 			// Logging middleware.
 927@@ -93,11 +91,10 @@ func NewSSHServer(ctx context.Context) (*SSHServer, error) {
 928 		wish.WithHostKeyPath(cfg.SSH.KeyPath),
 929 		wish.WithMiddleware(mw...),
 930 	}
 931-	if runtime.GOOS == "windows" {
 932-		opts = append(opts, ssh.EmulatePty())
 933-	} else {
 934-		opts = append(opts, ssh.AllocatePty())
 935-	}
 936+
 937+	// TODO: Support a real PTY in future version.
 938+	opts = append(opts, ssh.EmulatePty())
 939+
 940 	s.srv, err = wish.NewServer(opts...)
 941 	if err != nil {
 942 		return nil, err
 943diff --git a/pkg/ssh/ui.go b/pkg/ssh/ui.go
 944index 31f6a259cc2e677ac6b90ca2667c5ffee105cde4..204b80181e5ea48eefa9a475e9919ad9606367a3 100644
 945--- a/pkg/ssh/ui.go
 946+++ b/pkg/ssh/ui.go
 947@@ -3,10 +3,10 @@ package ssh
 948 import (
 949 	"errors"
 950 
 951-	"github.com/charmbracelet/bubbles/key"
 952-	"github.com/charmbracelet/bubbles/list"
 953-	tea "github.com/charmbracelet/bubbletea"
 954-	"github.com/charmbracelet/lipgloss"
 955+	"github.com/charmbracelet/bubbles/v2/key"
 956+	"github.com/charmbracelet/bubbles/v2/list"
 957+	tea "github.com/charmbracelet/bubbletea/v2"
 958+	"github.com/charmbracelet/lipgloss/v2"
 959 	"github.com/charmbracelet/soft-serve/git"
 960 	"github.com/charmbracelet/soft-serve/pkg/proto"
 961 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 962@@ -179,40 +179,34 @@ func (ui *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 963 				cmds = append(cmds, cmd)
 964 			}
 965 		}
 966-	case tea.KeyMsg, tea.MouseMsg:
 967-		switch msg := msg.(type) {
 968-		case tea.KeyMsg:
 969+	case tea.KeyPressMsg:
 970+		switch {
 971+		case key.Matches(msg, ui.common.KeyMap.Back) && ui.error != nil:
 972+			ui.error = nil
 973+			ui.state = readyState
 974+			// Always show the footer on error.
 975+			ui.showFooter = ui.footer.ShowAll()
 976+		case key.Matches(msg, ui.common.KeyMap.Help):
 977+			cmds = append(cmds, footer.ToggleFooterCmd)
 978+		case key.Matches(msg, ui.common.KeyMap.Quit):
 979+			if !ui.IsFiltering() {
 980+				// Stop bubblezone background workers.
 981+				ui.common.Zone.Close()
 982+				return ui, tea.Quit
 983+			}
 984+		case ui.activePage == repoPage &&
 985+			ui.pages[ui.activePage].(*repo.Repo).Path() == "" &&
 986+			key.Matches(msg, ui.common.KeyMap.Back):
 987+			ui.activePage = selectionPage
 988+			// Always show the footer on selection page.
 989+			ui.showFooter = true
 990+		}
 991+	case tea.MouseClickMsg:
 992+		switch msg.Mouse().Button {
 993+		case tea.MouseLeft:
 994 			switch {
 995-			case key.Matches(msg, ui.common.KeyMap.Back) && ui.error != nil:
 996-				ui.error = nil
 997-				ui.state = readyState
 998-				// Always show the footer on error.
 999-				ui.showFooter = ui.footer.ShowAll()
1000-			case key.Matches(msg, ui.common.KeyMap.Help):
1001+			case ui.common.Zone.Get("footer").InBounds(msg):
1002 				cmds = append(cmds, footer.ToggleFooterCmd)
1003-			case key.Matches(msg, ui.common.KeyMap.Quit):
1004-				if !ui.IsFiltering() {
1005-					// Stop bubblezone background workers.
1006-					ui.common.Zone.Close()
1007-					return ui, tea.Quit
1008-				}
1009-			case ui.activePage == repoPage &&
1010-				ui.pages[ui.activePage].(*repo.Repo).Path() == "" &&
1011-				key.Matches(msg, ui.common.KeyMap.Back):
1012-				ui.activePage = selectionPage
1013-				// Always show the footer on selection page.
1014-				ui.showFooter = true
1015-			}
1016-		case tea.MouseMsg:
1017-			if msg.Action != tea.MouseActionPress {
1018-				break
1019-			}
1020-			switch msg.Button {
1021-			case tea.MouseButtonLeft:
1022-				switch {
1023-				case ui.common.Zone.Get("footer").InBounds(msg):
1024-					cmds = append(cmds, footer.ToggleFooterCmd)
1025-				}
1026 			}
1027 		}
1028 	case footer.ToggleFooterMsg:
1029diff --git a/pkg/store/database/database.go b/pkg/store/database/database.go
1030index 02c4bd19cf25e2c3dccd59829eeb71d1947e59af..e41087c7a8dfaa8f14da34715425c0f761d380ba 100644
1031--- a/pkg/store/database/database.go
1032+++ b/pkg/store/database/database.go
1033@@ -3,7 +3,7 @@ package database
1034 import (
1035 	"context"
1036 
1037-	"github.com/charmbracelet/log"
1038+	"github.com/charmbracelet/log/v2"
1039 	"github.com/charmbracelet/soft-serve/pkg/config"
1040 	"github.com/charmbracelet/soft-serve/pkg/db"
1041 	"github.com/charmbracelet/soft-serve/pkg/store"
1042diff --git a/pkg/ui/common/common.go b/pkg/ui/common/common.go
1043index 93d40f57328aa1d191dc5115e12b16f1f86d0c9d..687939407f8af05cc703dbf0ef1a60cd01302836 100644
1044--- a/pkg/ui/common/common.go
1045+++ b/pkg/ui/common/common.go
1046@@ -5,16 +5,14 @@ import (
1047 	"fmt"
1048 
1049 	"github.com/alecthomas/chroma/v2/lexers"
1050-	"github.com/charmbracelet/lipgloss"
1051-	"github.com/charmbracelet/log"
1052+	zone "github.com/aymanbagabas/bubblezone/v2"
1053+	"github.com/charmbracelet/log/v2"
1054 	"github.com/charmbracelet/soft-serve/git"
1055 	"github.com/charmbracelet/soft-serve/pkg/backend"
1056 	"github.com/charmbracelet/soft-serve/pkg/config"
1057 	"github.com/charmbracelet/soft-serve/pkg/ui/keymap"
1058 	"github.com/charmbracelet/soft-serve/pkg/ui/styles"
1059 	"github.com/charmbracelet/ssh"
1060-	zone "github.com/lrstanley/bubblezone"
1061-	"github.com/muesli/termenv"
1062 )
1063 
1064 type contextKey struct {
1065@@ -34,27 +32,23 @@ type Common struct {
1066 	Styles        *styles.Styles
1067 	KeyMap        *keymap.KeyMap
1068 	Zone          *zone.Manager
1069-	Renderer      *lipgloss.Renderer
1070-	Output        *termenv.Output
1071 	Logger        *log.Logger
1072 	HideCloneCmd  bool
1073 }
1074 
1075 // NewCommon returns a new Common struct.
1076-func NewCommon(ctx context.Context, out *lipgloss.Renderer, width, height int) Common {
1077+func NewCommon(ctx context.Context, width, height int) Common {
1078 	if ctx == nil {
1079 		ctx = context.TODO()
1080 	}
1081 	return Common{
1082-		ctx:      ctx,
1083-		Width:    width,
1084-		Height:   height,
1085-		Renderer: out,
1086-		Output:   out.Output(),
1087-		Styles:   styles.DefaultStyles(out),
1088-		KeyMap:   keymap.DefaultKeyMap(),
1089-		Zone:     zone.New(),
1090-		Logger:   log.FromContext(ctx).WithPrefix("ui"),
1091+		ctx:    ctx,
1092+		Width:  width,
1093+		Height: height,
1094+		Styles: styles.DefaultStyles(),
1095+		KeyMap: keymap.DefaultKeyMap(),
1096+		Zone:   zone.New(),
1097+		Logger: log.FromContext(ctx).WithPrefix("ui"),
1098 	}
1099 }
1100 
1101diff --git a/pkg/ui/common/component.go b/pkg/ui/common/component.go
1102index 6fe7761f9b9c85cabae967b5246d361cb811d363..4b152a4ba81080af791446601d06133847d80102 100644
1103--- a/pkg/ui/common/component.go
1104+++ b/pkg/ui/common/component.go
1105@@ -1,13 +1,14 @@
1106 package common
1107 
1108 import (
1109-	"github.com/charmbracelet/bubbles/help"
1110-	tea "github.com/charmbracelet/bubbletea"
1111+	"github.com/charmbracelet/bubbles/v2/help"
1112+	tea "github.com/charmbracelet/bubbletea/v2"
1113 )
1114 
1115 // Component represents a Bubble Tea model that implements a SetSize function.
1116 type Component interface {
1117 	tea.Model
1118+	tea.ViewModel
1119 	help.KeyMap
1120 	SetSize(width, height int)
1121 }
1122diff --git a/pkg/ui/common/error.go b/pkg/ui/common/error.go
1123index 753f5f26f77fc778157c2dc5d3c7b06440a29b8e..926df1d9ac7610b2a34648290e62c28a7d9d9629 100644
1124--- a/pkg/ui/common/error.go
1125+++ b/pkg/ui/common/error.go
1126@@ -3,7 +3,7 @@ package common
1127 import (
1128 	"errors"
1129 
1130-	tea "github.com/charmbracelet/bubbletea"
1131+	tea "github.com/charmbracelet/bubbletea/v2"
1132 )
1133 
1134 // ErrMissingRepo indicates that the requested repository could not be found.
1135diff --git a/pkg/ui/common/format.go b/pkg/ui/common/format.go
1136index 4344e057d8cfba2478b977a34f01a1d28c1cc6d2..f82c71aec65d6366f3f4d6da770979c94a4d18e4 100644
1137--- a/pkg/ui/common/format.go
1138+++ b/pkg/ui/common/format.go
1139@@ -6,7 +6,7 @@ import (
1140 	"strings"
1141 
1142 	"github.com/alecthomas/chroma/v2/lexers"
1143-	gansi "github.com/charmbracelet/glamour/ansi"
1144+	gansi "github.com/charmbracelet/glamour/v2/ansi"
1145 	"github.com/charmbracelet/soft-serve/pkg/ui/styles"
1146 )
1147 
1148diff --git a/pkg/ui/common/style.go b/pkg/ui/common/style.go
1149index 06d7ce3d2c9e4b61f2dd827051dd351f4bdca861..1f2032f8b5e0ffbb12c067427c397e34dc55944b 100644
1150--- a/pkg/ui/common/style.go
1151+++ b/pkg/ui/common/style.go
1152@@ -1,13 +1,13 @@
1153 package common
1154 
1155 import (
1156-	gansi "github.com/charmbracelet/glamour/ansi"
1157-	"github.com/charmbracelet/glamour/styles"
1158-	"github.com/muesli/termenv"
1159+	"github.com/charmbracelet/colorprofile"
1160+	gansi "github.com/charmbracelet/glamour/v2/ansi"
1161+	"github.com/charmbracelet/glamour/v2/styles"
1162 )
1163 
1164 // DefaultColorProfile is the default color profile used by the SSH server.
1165-var DefaultColorProfile = termenv.ANSI256
1166+var DefaultColorProfile = colorprofile.ANSI256
1167 
1168 func strptr(s string) *string {
1169 	return &s
1170@@ -17,29 +17,20 @@ func strptr(s string) *string {
1171 func StyleConfig() gansi.StyleConfig {
1172 	noColor := strptr("")
1173 	s := styles.DarkStyleConfig
1174-	s.H1.BackgroundColor = noColor
1175-	s.H1.Prefix = "# "
1176-	s.H1.Suffix = ""
1177-	s.H1.Color = strptr("39")
1178-	s.Document.StylePrimitive.Color = noColor
1179-	s.CodeBlock.Chroma.Text.Color = noColor
1180-	s.CodeBlock.Chroma.Name.Color = noColor
1181 	// This fixes an issue with the default style config. For example
1182 	// highlighting empty spaces with red in Dockerfile type.
1183 	s.CodeBlock.Chroma.Error.BackgroundColor = noColor
1184 	return s
1185 }
1186 
1187-// StyleRenderer returns a new Glamour renderer with the DefaultColorProfile.
1188+// StyleRenderer returns a new Glamour renderer.
1189 func StyleRenderer() gansi.RenderContext {
1190 	return StyleRendererWithStyles(StyleConfig())
1191 }
1192 
1193-// StyleRendererWithStyles returns a new Glamour renderer with the
1194-// DefaultColorProfile and styles.
1195+// StyleRendererWithStyles returns a new Glamour renderer.
1196 func StyleRendererWithStyles(styles gansi.StyleConfig) gansi.RenderContext {
1197 	return gansi.NewRenderContext(gansi.Options{
1198-		ColorProfile: DefaultColorProfile,
1199-		Styles:       styles,
1200+		Styles: styles,
1201 	})
1202 }
1203diff --git a/pkg/ui/components/code/code.go b/pkg/ui/components/code/code.go
1204index af7697260fc7d327f1791ded0ab84f69f8c86c72..6da77c65fe8040f9bf21c266eafbcf78328613c5 100644
1205--- a/pkg/ui/components/code/code.go
1206+++ b/pkg/ui/components/code/code.go
1207@@ -6,13 +6,12 @@ import (
1208 	"sync"
1209 
1210 	"github.com/alecthomas/chroma/v2/lexers"
1211-	tea "github.com/charmbracelet/bubbletea"
1212-	"github.com/charmbracelet/glamour"
1213-	gansi "github.com/charmbracelet/glamour/ansi"
1214-	"github.com/charmbracelet/lipgloss"
1215+	tea "github.com/charmbracelet/bubbletea/v2"
1216+	"github.com/charmbracelet/glamour/v2"
1217+	gansi "github.com/charmbracelet/glamour/v2/ansi"
1218+	"github.com/charmbracelet/lipgloss/v2"
1219 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1220 	vp "github.com/charmbracelet/soft-serve/pkg/ui/components/viewport"
1221-	"github.com/muesli/termenv"
1222 )
1223 
1224 const (
1225@@ -77,7 +76,11 @@ func (r *Code) SetSideNote(s string) tea.Cmd {
1226 
1227 // Init implements tea.Model.
1228 func (r *Code) Init() tea.Cmd {
1229-	w := r.common.Width
1230+	// XXX: We probably won't need the GetHorizontalFrameSize margin
1231+	// subtraction if we get the new viewport soft wrapping to play nicely with
1232+	// Glamour. This also introduces a bug where when it soft wraps, the
1233+	// viewport scrolls left/right for 2 columns on each side of the screen.
1234+	w := r.common.Width - r.common.Styles.App.GetHorizontalFrameSize()
1235 	content := r.content
1236 	if content == "" {
1237 		r.Viewport.Model.SetContent(r.NoContentStyle.String())
1238@@ -110,7 +113,7 @@ func (r *Code) Init() tea.Cmd {
1239 
1240 	if r.sidenote != "" {
1241 		lines := strings.Split(r.sidenote, "\n")
1242-		sideNoteWidth := int(math.Ceil(float64(r.Model.Width) * r.SideNotePercent))
1243+		sideNoteWidth := int(math.Ceil(float64(r.Model.Width()) * r.SideNotePercent))
1244 		for i, l := range lines {
1245 			lines[i] = common.TruncateString(l, sideNoteWidth)
1246 		}
1247@@ -121,7 +124,7 @@ func (r *Code) Init() tea.Cmd {
1248 	// https://github.com/muesli/reflow/issues/43
1249 	//
1250 	// TODO: solve this upstream in Glamour/Reflow.
1251-	content = r.common.Renderer.NewStyle().Width(w).Render(content)
1252+	content = lipgloss.NewStyle().Width(w).Render(content)
1253 
1254 	r.Viewport.Model.SetContent(content)
1255 
1256@@ -169,26 +172,6 @@ func (r *Code) HalfViewUp() {
1257 	r.Viewport.HalfViewUp()
1258 }
1259 
1260-// ViewUp moves the viewport up by a page.
1261-func (r *Code) ViewUp() []string {
1262-	return r.Viewport.ViewUp()
1263-}
1264-
1265-// ViewDown moves the viewport down by a page.
1266-func (r *Code) ViewDown() []string {
1267-	return r.Viewport.ViewDown()
1268-}
1269-
1270-// LineUp moves the viewport up by the given number of lines.
1271-func (r *Code) LineUp(n int) []string {
1272-	return r.Viewport.LineUp(n)
1273-}
1274-
1275-// LineDown moves the viewport down by the given number of lines.
1276-func (r *Code) LineDown(n int) []string {
1277-	return r.Viewport.LineDown(n)
1278-}
1279-
1280 // ScrollPercent returns the viewport's scroll percentage.
1281 func (r *Code) ScrollPercent() float64 {
1282 	return r.Viewport.ScrollPercent()
1283@@ -244,8 +227,7 @@ func (r *Code) renderFile(path, content string) (string, error) {
1284 		var m uint
1285 		st.CodeBlock.Margin = &m
1286 		rc = gansi.NewRenderContext(gansi.Options{
1287-			ColorProfile: termenv.TrueColor,
1288-			Styles:       st,
1289+			Styles: st,
1290 		})
1291 	}
1292 	err := formatter.Render(&s, rc)
1293diff --git a/pkg/ui/components/footer/footer.go b/pkg/ui/components/footer/footer.go
1294index 44c800f420e1913c72f1289a00b7d273269a8247..05e1160dedbe717a213de8907fbbb65dccc5bd4e 100644
1295--- a/pkg/ui/components/footer/footer.go
1296+++ b/pkg/ui/components/footer/footer.go
1297@@ -1,10 +1,10 @@
1298 package footer
1299 
1300 import (
1301-	"github.com/charmbracelet/bubbles/help"
1302-	"github.com/charmbracelet/bubbles/key"
1303-	tea "github.com/charmbracelet/bubbletea"
1304-	"github.com/charmbracelet/lipgloss"
1305+	"github.com/charmbracelet/bubbles/v2/help"
1306+	"github.com/charmbracelet/bubbles/v2/key"
1307+	tea "github.com/charmbracelet/bubbletea/v2"
1308+	"github.com/charmbracelet/lipgloss/v2"
1309 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1310 )
1311 
1312diff --git a/pkg/ui/components/header/header.go b/pkg/ui/components/header/header.go
1313index 7de511426f6da83252b84884c4fa3a64fd992e65..63e47a4911f6618c31448cdbc8c902e80314be11 100644
1314--- a/pkg/ui/components/header/header.go
1315+++ b/pkg/ui/components/header/header.go
1316@@ -3,7 +3,7 @@ package header
1317 import (
1318 	"strings"
1319 
1320-	tea "github.com/charmbracelet/bubbletea"
1321+	tea "github.com/charmbracelet/bubbletea/v2"
1322 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1323 )
1324 
1325diff --git a/pkg/ui/components/selector/selector.go b/pkg/ui/components/selector/selector.go
1326index 72239d4083847a2ccd72c7d0e10ea9bfa90e10ba..e42ee3507a674a36561ae9255a7b6ea5f2e25c86 100644
1327--- a/pkg/ui/components/selector/selector.go
1328+++ b/pkg/ui/components/selector/selector.go
1329@@ -3,9 +3,9 @@ package selector
1330 import (
1331 	"sync"
1332 
1333-	"github.com/charmbracelet/bubbles/key"
1334-	"github.com/charmbracelet/bubbles/list"
1335-	tea "github.com/charmbracelet/bubbletea"
1336+	"github.com/charmbracelet/bubbles/v2/key"
1337+	"github.com/charmbracelet/bubbles/v2/list"
1338+	tea "github.com/charmbracelet/bubbletea/v2"
1339 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1340 )
1341 
1342@@ -230,16 +230,14 @@ func (s *Selector) Init() tea.Cmd {
1343 func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1344 	cmds := make([]tea.Cmd, 0)
1345 	switch msg := msg.(type) {
1346-	case tea.MouseMsg:
1347-		if msg.Action != tea.MouseActionPress {
1348-			break
1349-		}
1350-		switch msg.Button {
1351-		case tea.MouseButtonWheelUp:
1352+	case tea.MouseClickMsg:
1353+		m := msg.Mouse()
1354+		switch m.Button {
1355+		case tea.MouseWheelUp:
1356 			s.CursorUp()
1357-		case tea.MouseButtonWheelDown:
1358+		case tea.MouseWheelDown:
1359 			s.CursorDown()
1360-		case tea.MouseButtonLeft:
1361+		case tea.MouseLeft:
1362 			curIdx := s.Index()
1363 			for i, item := range s.Items() {
1364 				item, _ := item.(IdentifiableItem)
1365@@ -254,7 +252,7 @@ func (s *Selector) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1366 				}
1367 			}
1368 		}
1369-	case tea.KeyMsg:
1370+	case tea.KeyPressMsg:
1371 		filterState := s.FilterState()
1372 		switch {
1373 		case key.Matches(msg, s.common.KeyMap.Help):
1374diff --git a/pkg/ui/components/statusbar/statusbar.go b/pkg/ui/components/statusbar/statusbar.go
1375index 20a5735e12da1729616ea9ead98ccfcadfda6a47..f913e644a2230ea4a3d6f1b46f9e7850de45a02e 100644
1376--- a/pkg/ui/components/statusbar/statusbar.go
1377+++ b/pkg/ui/components/statusbar/statusbar.go
1378@@ -1,10 +1,10 @@
1379 package statusbar
1380 
1381 import (
1382-	tea "github.com/charmbracelet/bubbletea"
1383-	"github.com/charmbracelet/lipgloss"
1384+	tea "github.com/charmbracelet/bubbletea/v2"
1385+	"github.com/charmbracelet/lipgloss/v2"
1386 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1387-	"github.com/muesli/reflow/truncate"
1388+	"github.com/charmbracelet/x/ansi"
1389 )
1390 
1391 // Model is a status bar model.
1392@@ -75,12 +75,12 @@ func (s *Model) View() string {
1393 	}
1394 	branch := st.StatusBarBranch.Render(s.extra)
1395 	maxWidth := s.common.Width - w(key) - w(info) - w(branch) - w(help)
1396-	v := truncate.StringWithTail(s.value, uint(maxWidth-st.StatusBarValue.GetHorizontalFrameSize()), "…") //nolint:gosec
1397+	v := ansi.Truncate(s.value, maxWidth-st.StatusBarValue.GetHorizontalFrameSize(), "…")
1398 	value := st.StatusBarValue.
1399 		Width(maxWidth).
1400 		Render(v)
1401 
1402-	return s.common.Renderer.NewStyle().MaxWidth(s.common.Width).
1403+	return lipgloss.NewStyle().MaxWidth(s.common.Width).
1404 		Render(
1405 			lipgloss.JoinHorizontal(lipgloss.Top,
1406 				key,
1407diff --git a/pkg/ui/components/tabs/tabs.go b/pkg/ui/components/tabs/tabs.go
1408index bd31566216326910b991f732813daef756e733f2..36f0a19e85c2bda3dc172e02d8b5bb5b70919ad4 100644
1409--- a/pkg/ui/components/tabs/tabs.go
1410+++ b/pkg/ui/components/tabs/tabs.go
1411@@ -3,8 +3,8 @@ package tabs
1412 import (
1413 	"strings"
1414 
1415-	tea "github.com/charmbracelet/bubbletea"
1416-	"github.com/charmbracelet/lipgloss"
1417+	tea "github.com/charmbracelet/bubbletea/v2"
1418+	"github.com/charmbracelet/lipgloss/v2"
1419 	"github.com/charmbracelet/soft-serve/pkg/ui/common"
1420 )
1421 
1422@@ -54,7 +54,7 @@ func (t *Tabs) Init() tea.Cmd {
1423 func (t *Tabs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1424 	cmds := make([]tea.Cmd, 0)
1425 	switch msg := msg.(type) {
1426-	case tea.KeyMsg:
1427+	case tea.KeyPressMsg:
1428 		switch msg.String() {
1429 		case "tab":
1430 			t.activeTab = (t.activeTab + 1) % len(t.tabs)
1431@@ -63,12 +63,9 @@ func (t *Tabs) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
1432 			t.activeTab = (t.activeTab - 1 + len(t.tabs)) % len(t.tabs)
1433 			cmds = append(cmds, t.activeTabCmd)
1434 		}
1435-	case tea.MouseMsg:
1436-		if msg.Action != tea.MouseActionPress {
1437-			break
1438-		}
1439+	case tea.MouseClickMsg:
1440 		switch msg.Button {
1441-		case tea.MouseButtonLeft:
1442+		case tea.MouseLeft:
1443 			for i, tab := range t.tabs {
1444 				if t.common.Zone.Get(tab).InBounds(msg) {
1445 					t.activeTab = i
1446@@ -109,7 +106,7 @@ func (t *Tabs) View() string {
1447 			s.WriteString(sep.String())
1448 		}
1449 	}
1450-	return t.common.Renderer.NewStyle().
1451+	return lipgloss.NewStyle().
1452 		MaxWidth(t.common.Width).
1453 		Render(s.String())
1454 }