on main help sign in
chore: bump bubbletea and ultraviolet dependencies
04d05e1Ayman Bagabas
go.mod+3−3
@@ -4,7 +4,7 @@ go 1.25.5
4
4
5require (
5require (
6 charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66
6 charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66
7− charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e
7+ charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0
8 charm.land/catwalk v0.17.1
8 charm.land/catwalk v0.17.1
9 charm.land/fantasy v0.7.1
9 charm.land/fantasy v0.7.1
10 charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b
10 charm.land/glamour/v2 v2.0.0-20260123212943-6014aa153a9b
@@ -22,8 +22,8 @@ require (
22 github.com/charlievieth/fastwalk v1.0.14
22 github.com/charlievieth/fastwalk v1.0.14
23 github.com/charmbracelet/colorprofile v0.4.1
23 github.com/charmbracelet/colorprofile v0.4.1
24 github.com/charmbracelet/fang v0.4.4
24 github.com/charmbracelet/fang v0.4.4
25− github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560
25+ github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8
26− github.com/charmbracelet/x/ansi v0.11.4
26+ github.com/charmbracelet/x/ansi v0.11.6
27 github.com/charmbracelet/x/editor v0.2.0
27 github.com/charmbracelet/x/editor v0.2.0
28 github.com/charmbracelet/x/etag v0.2.0
28 github.com/charmbracelet/x/etag v0.2.0
29 github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f
29 github.com/charmbracelet/x/exp/charmtone v0.0.0-20260109001716-2fbdffcb221f
go.sum+6−6
@@ -1,7 +1,7 @@
1charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66 h1:2BdJynsAW+8rv9xq6ZS+x0mtacfxpxjIK1KUIeTqBOs=
1charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66 h1:2BdJynsAW+8rv9xq6ZS+x0mtacfxpxjIK1KUIeTqBOs=
2charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
2charm.land/bubbles/v2 v2.0.0-rc.1.0.20260109112849-ae99f46cec66/go.mod h1:5AbN6cEd/47gkEf8TgiQ2O3RZ5QxMS14l9W+7F9fPC4=
3−charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e h1:tXwTmgGpwZT7ParKF5xbEQBVjM2e1uKhKi/GpfU3mYQ=
3+charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0 h1:HAbpM9TPjZM18D677ww3VnkKXdd2hyMQtHUsVV0HcPQ=
4−charm.land/bubbletea/v2 v2.0.0-rc.2.0.20251216153312-819e2e89c62e/go.mod h1:pDM18flq3Z4njKZPA3zCvyVSSIJbMcoqlE82BdGUtL8=
4+charm.land/bubbletea/v2 v2.0.0-rc.2.0.20260209074636-30878e43d7b0/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
5charm.land/catwalk v0.17.1 h1:UsHvBi3S7CxONiIZTWKTXM+H9qla8I0fCb/SVru33ms=
5charm.land/catwalk v0.17.1 h1:UsHvBi3S7CxONiIZTWKTXM+H9qla8I0fCb/SVru33ms=
6charm.land/catwalk v0.17.1/go.mod h1:kAdk/GjAJbl1AjRjmfU5c9lZfs7PeC3Uy9TgaVtlN64=
6charm.land/catwalk v0.17.1/go.mod h1:kAdk/GjAJbl1AjRjmfU5c9lZfs7PeC3Uy9TgaVtlN64=
7charm.land/fantasy v0.7.1 h1:JOCYeLz32PM11y1u08/YgWl3LfPwhjOIuoyjBXjFofI=
7charm.land/fantasy v0.7.1 h1:JOCYeLz32PM11y1u08/YgWl3LfPwhjOIuoyjBXjFofI=
@@ -102,10 +102,10 @@ github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco
102github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
102github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
103github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY=
103github.com/charmbracelet/fang v0.4.4 h1:G4qKxF6or/eTPgmAolwPuRNyuci3hTUGGX1rj1YkHJY=
104github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
104github.com/charmbracelet/fang v0.4.4/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
105−github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560 h1:j3PW2hypGoPKBy3ooKzW0TFxaxhyHK3NbkLLn4KeRFc=
105+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
106−github.com/charmbracelet/ultraviolet v0.0.0-20251212194010-b927aa605560/go.mod h1:VWATWLRwYP06VYCEur7FsNR2B1xAo7Y+xl1PTbd1ePc=
106+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
107−github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
107+github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
108−github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
108+github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
109github.com/charmbracelet/x/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk=
109github.com/charmbracelet/x/editor v0.2.0 h1:7XLUKtaRaB8jN7bWU2p2UChiySyaAuIfYiIRg8gGWwk=
110github.com/charmbracelet/x/editor v0.2.0/go.mod h1:p3oQ28TSL3YPd+GKJ1fHWcp+7bVGpedHpXmo0D6t1dY=
110github.com/charmbracelet/x/editor v0.2.0/go.mod h1:p3oQ28TSL3YPd+GKJ1fHWcp+7bVGpedHpXmo0D6t1dY=
111github.com/charmbracelet/x/etag v0.2.0 h1:Euj1VkheoHfTYA9y+TCwkeXF/hN8Fb9l4LqZl79pt04=
111github.com/charmbracelet/x/etag v0.2.0 h1:Euj1VkheoHfTYA9y+TCwkeXF/hN8Fb9l4LqZl79pt04=
internal/ui/common/capabilities.go+2−1
@@ -47,7 +47,7 @@ func (c *Capabilities) Update(msg any) {
47 case tea.WindowSizeMsg:
47 case tea.WindowSizeMsg:
48 c.Columns = m.Width
48 c.Columns = m.Width
49 c.Rows = m.Height
49 c.Rows = m.Height
50− case uv.WindowPixelSizeEvent:
50+ case uv.PixelSizeEvent:
51 c.PixelX = m.Width
51 c.PixelX = m.Width
52 c.PixelY = m.Height
52 c.PixelY = m.Height
53 case uv.KittyGraphicsEvent:
53 case uv.KittyGraphicsEvent:
@@ -71,6 +71,7 @@ func (c *Capabilities) Update(msg any) {
71func QueryCmd(env uv.Environ) tea.Cmd {
71func QueryCmd(env uv.Environ) tea.Cmd {
72 var sb strings.Builder
72 var sb strings.Builder
73 sb.WriteString(ansi.RequestPrimaryDeviceAttributes)
73 sb.WriteString(ansi.RequestPrimaryDeviceAttributes)
74+ sb.WriteString(ansi.QueryModifyOtherKeys)
74
75
75 // Queries that should only be sent to "smart" normal terminals.
76 // Queries that should only be sent to "smart" normal terminals.
76 shouldQueryFor := shouldQueryCapabilities(env)
77 shouldQueryFor := shouldQueryCapabilities(env)
internal/ui/model/landing.go+2−2
@@ -4,7 +4,7 @@ import (
4 "charm.land/lipgloss/v2"
4 "charm.land/lipgloss/v2"
5 "github.com/charmbracelet/crush/internal/agent"
5 "github.com/charmbracelet/crush/internal/agent"
6 "github.com/charmbracelet/crush/internal/ui/common"
6 "github.com/charmbracelet/crush/internal/ui/common"
7− uv "github.com/charmbracelet/ultraviolet"
7+ "github.com/charmbracelet/ultraviolet/layout"
8)
8)
9
9
10// selectedLargeModel returns the currently selected large language model from
10// selectedLargeModel returns the currently selected large language model from
@@ -31,7 +31,7 @@ func (m *UI) landingView() string {
31 parts = append(parts, "", m.modelInfo(width))
31 parts = append(parts, "", m.modelInfo(width))
32 infoSection := lipgloss.JoinVertical(lipgloss.Left, parts...)
32 infoSection := lipgloss.JoinVertical(lipgloss.Left, parts...)
33
33
34− _, remainingHeightArea := uv.SplitVertical(m.layout.main, uv.Fixed(lipgloss.Height(infoSection)+1))
34+ _, remainingHeightArea := layout.SplitVertical(m.layout.main, layout.Fixed(lipgloss.Height(infoSection)+1))
35
35
36 mcpLspSectionWidth := min(30, (width-1)/2)
36 mcpLspSectionWidth := min(30, (width-1)/2)
37
37
internal/ui/model/sidebar.go+2−1
@@ -8,6 +8,7 @@ import (
8 "github.com/charmbracelet/crush/internal/ui/common"
8 "github.com/charmbracelet/crush/internal/ui/common"
9 "github.com/charmbracelet/crush/internal/ui/logo"
9 "github.com/charmbracelet/crush/internal/ui/logo"
10 uv "github.com/charmbracelet/ultraviolet"
10 uv "github.com/charmbracelet/ultraviolet"
11+ "github.com/charmbracelet/ultraviolet/layout"
11 "golang.org/x/text/cases"
12 "golang.org/x/text/cases"
12 "golang.org/x/text/language"
13 "golang.org/x/text/language"
13)
14)
@@ -134,7 +135,7 @@ func (m *UI) drawSidebar(scr uv.Screen, area uv.Rectangle) {
134 blocks...,
135 blocks...,
135 )
136 )
136
137
137− _, remainingHeightArea := uv.SplitVertical(m.layout.sidebar, uv.Fixed(lipgloss.Height(sidebarHeader)))
138+ _, remainingHeightArea := layout.SplitVertical(m.layout.sidebar, layout.Fixed(lipgloss.Height(sidebarHeader)))
138 remainingHeight := remainingHeightArea.Dy() - 10
139 remainingHeight := remainingHeightArea.Dy() - 10
139 maxFiles, maxLSPs, maxMCPs := getDynamicHeightLimits(remainingHeight)
140 maxFiles, maxLSPs, maxMCPs := getDynamicHeightLimits(remainingHeight)
140
141
internal/ui/model/ui.go+40−39
@@ -47,6 +47,7 @@ import (
47 "github.com/charmbracelet/crush/internal/ui/util"
47 "github.com/charmbracelet/crush/internal/ui/util"
48 "github.com/charmbracelet/crush/internal/version"
48 "github.com/charmbracelet/crush/internal/version"
49 uv "github.com/charmbracelet/ultraviolet"
49 uv "github.com/charmbracelet/ultraviolet"
50+ "github.com/charmbracelet/ultraviolet/layout"
50 "github.com/charmbracelet/ultraviolet/screen"
51 "github.com/charmbracelet/ultraviolet/screen"
51 "github.com/charmbracelet/x/editor"
52 "github.com/charmbracelet/x/editor"
52)
53)
@@ -135,7 +136,7 @@ type UI struct {
135 // The width and height of the terminal in cells.
136 // The width and height of the terminal in cells.
136 width int
137 width int
137 height int
138 height int
138− layout layout
139+ layout uiLayout
139
140
140 isTransparent bool
141 isTransparent bool
141
142
@@ -2235,7 +2236,7 @@ func (m *UI) updateSize() {
2235
2236
2236// generateLayout calculates the layout rectangles for all UI components based
2237// generateLayout calculates the layout rectangles for all UI components based
2237// on the current UI state and terminal dimensions.
2238// on the current UI state and terminal dimensions.
2238−func (m *UI) generateLayout(w, h int) layout {
2239+func (m *UI) generateLayout(w, h int) uiLayout {
2239 // The screen area we're working with
2240 // The screen area we're working with
2240 area := image.Rect(0, 0, w, h)
2241 area := image.Rect(0, 0, w, h)
2241
2242
@@ -2256,7 +2257,7 @@ func (m *UI) generateLayout(w, h int) layout {
2256 }
2257 }
2257
2258
2258 // Add app margins
2259 // Add app margins
2259− appRect, helpRect := uv.SplitVertical(area, uv.Fixed(area.Dy()-helpHeight))
2260+ appRect, helpRect := layout.SplitVertical(area, layout.Fixed(area.Dy()-helpHeight))
2260 appRect.Min.Y += 1
2261 appRect.Min.Y += 1
2261 appRect.Max.Y -= 1
2262 appRect.Max.Y -= 1
2262 helpRect.Min.Y -= 1
2263 helpRect.Min.Y -= 1
@@ -2269,7 +2270,7 @@ func (m *UI) generateLayout(w, h int) layout {
2269 appRect.Max.X -= 1
2270 appRect.Max.X -= 1
2270 }
2271 }
2271
2272
2272− layout := layout{
2273+ uiLayout := uiLayout{
2273 area: area,
2274 area: area,
2274 status: helpRect,
2275 status: helpRect,
2275 }
2276 }
@@ -2285,9 +2286,9 @@ func (m *UI) generateLayout(w, h int) layout {
2285 // ------
2286 // ------
2286 // help
2287 // help
2287
2288
2288− headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(landingHeaderHeight))
2289+ headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(landingHeaderHeight))
2289− layout.header = headerRect
2290+ uiLayout.header = headerRect
2290− layout.main = mainRect
2291+ uiLayout.main = mainRect
2291
2292
2292 case uiLanding:
2293 case uiLanding:
2293 // Layout
2294 // Layout
@@ -2299,14 +2300,14 @@ func (m *UI) generateLayout(w, h int) layout {
2299 // editor
2300 // editor
2300 // ------
2301 // ------
2301 // help
2302 // help
2302− headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(landingHeaderHeight))
2303+ headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(landingHeaderHeight))
2303− mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight))
2304+ mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight))
2304 // Remove extra padding from editor (but keep it for header and main)
2305 // Remove extra padding from editor (but keep it for header and main)
2305 editorRect.Min.X -= 1
2306 editorRect.Min.X -= 1
2306 editorRect.Max.X += 1
2307 editorRect.Max.X += 1
2307− layout.header = headerRect
2308+ uiLayout.header = headerRect
2308− layout.main = mainRect
2309+ uiLayout.main = mainRect
2309− layout.editor = editorRect
2310+ uiLayout.editor = editorRect
2310
2311
2311 case uiChat:
2312 case uiChat:
2312 if m.isCompact {
2313 if m.isCompact {
@@ -2320,28 +2321,28 @@ func (m *UI) generateLayout(w, h int) layout {
2320 // ------
2321 // ------
2321 // help
2322 // help
2322 const compactHeaderHeight = 1
2323 const compactHeaderHeight = 1
2323− headerRect, mainRect := uv.SplitVertical(appRect, uv.Fixed(compactHeaderHeight))
2324+ headerRect, mainRect := layout.SplitVertical(appRect, layout.Fixed(compactHeaderHeight))
2324 detailsHeight := min(sessionDetailsMaxHeight, area.Dy()-1) // One row for the header
2325 detailsHeight := min(sessionDetailsMaxHeight, area.Dy()-1) // One row for the header
2325− sessionDetailsArea, _ := uv.SplitVertical(appRect, uv.Fixed(detailsHeight))
2326+ sessionDetailsArea, _ := layout.SplitVertical(appRect, layout.Fixed(detailsHeight))
2326− layout.sessionDetails = sessionDetailsArea
2327+ uiLayout.sessionDetails = sessionDetailsArea
2327− layout.sessionDetails.Min.Y += compactHeaderHeight // adjust for header
2328+ uiLayout.sessionDetails.Min.Y += compactHeaderHeight // adjust for header
2328 // Add one line gap between header and main content
2329 // Add one line gap between header and main content
2329 mainRect.Min.Y += 1
2330 mainRect.Min.Y += 1
2330− mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight))
2331+ mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight))
2331 mainRect.Max.X -= 1 // Add padding right
2332 mainRect.Max.X -= 1 // Add padding right
2332− layout.header = headerRect
2333+ uiLayout.header = headerRect
2333 pillsHeight := m.pillsAreaHeight()
2334 pillsHeight := m.pillsAreaHeight()
2334 if pillsHeight > 0 {
2335 if pillsHeight > 0 {
2335 pillsHeight = min(pillsHeight, mainRect.Dy())
2336 pillsHeight = min(pillsHeight, mainRect.Dy())
2336− chatRect, pillsRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-pillsHeight))
2337+ chatRect, pillsRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-pillsHeight))
2337− layout.main = chatRect
2338+ uiLayout.main = chatRect
2338− layout.pills = pillsRect
2339+ uiLayout.pills = pillsRect
2339 } else {
2340 } else {
2340− layout.main = mainRect
2341+ uiLayout.main = mainRect
2341 }
2342 }
2342 // Add bottom margin to main
2343 // Add bottom margin to main
2343− layout.main.Max.Y -= 1
2344+ uiLayout.main.Max.Y -= 1
2344− layout.editor = editorRect
2345+ uiLayout.editor = editorRect
2345 } else {
2346 } else {
2346 // Layout
2347 // Layout
2347 //
2348 //
@@ -2352,40 +2353,40 @@ func (m *UI) generateLayout(w, h int) layout {
2352 // ----------
2353 // ----------
2353 // help
2354 // help
2354
2355
2355− mainRect, sideRect := uv.SplitHorizontal(appRect, uv.Fixed(appRect.Dx()-sidebarWidth))
2356+ mainRect, sideRect := layout.SplitHorizontal(appRect, layout.Fixed(appRect.Dx()-sidebarWidth))
2356 // Add padding left
2357 // Add padding left
2357 sideRect.Min.X += 1
2358 sideRect.Min.X += 1
2358− mainRect, editorRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-editorHeight))
2359+ mainRect, editorRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-editorHeight))
2359 mainRect.Max.X -= 1 // Add padding right
2360 mainRect.Max.X -= 1 // Add padding right
2360− layout.sidebar = sideRect
2361+ uiLayout.sidebar = sideRect
2361 pillsHeight := m.pillsAreaHeight()
2362 pillsHeight := m.pillsAreaHeight()
2362 if pillsHeight > 0 {
2363 if pillsHeight > 0 {
2363 pillsHeight = min(pillsHeight, mainRect.Dy())
2364 pillsHeight = min(pillsHeight, mainRect.Dy())
2364− chatRect, pillsRect := uv.SplitVertical(mainRect, uv.Fixed(mainRect.Dy()-pillsHeight))
2365+ chatRect, pillsRect := layout.SplitVertical(mainRect, layout.Fixed(mainRect.Dy()-pillsHeight))
2365− layout.main = chatRect
2366+ uiLayout.main = chatRect
2366− layout.pills = pillsRect
2367+ uiLayout.pills = pillsRect
2367 } else {
2368 } else {
2368− layout.main = mainRect
2369+ uiLayout.main = mainRect
2369 }
2370 }
2370 // Add bottom margin to main
2371 // Add bottom margin to main
2371− layout.main.Max.Y -= 1
2372+ uiLayout.main.Max.Y -= 1
2372− layout.editor = editorRect
2373+ uiLayout.editor = editorRect
2373 }
2374 }
2374 }
2375 }
2375
2376
2376− if !layout.editor.Empty() {
2377+ if !uiLayout.editor.Empty() {
2377 // Add editor margins 1 top and bottom
2378 // Add editor margins 1 top and bottom
2378 if len(m.attachments.List()) == 0 {
2379 if len(m.attachments.List()) == 0 {
2379− layout.editor.Min.Y += 1
2380+ uiLayout.editor.Min.Y += 1
2380 }
2381 }
2381− layout.editor.Max.Y -= 1
2382+ uiLayout.editor.Max.Y -= 1
2382 }
2383 }
2383
2384
2384− return layout
2385+ return uiLayout
2385}
2386}
2386
2387
2387−// layout defines the positioning of UI elements.
2388+// uiLayout defines the positioning of UI elements.
2388−type layout struct {
2389+type uiLayout struct {
2389 // area is the overall available area.
2390 // area is the overall available area.
2390 area uv.Rectangle
2391 area uv.Rectangle
2391
2392