Skip to content

fix(deps): update module github.com/charmbracelet/bubbletea to v2 - #2768

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/github.com-charmbracelet-bubbletea-2.x
Open

fix(deps): update module github.com/charmbracelet/bubbletea to v2#2768
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/github.com-charmbracelet-bubbletea-2.x

Conversation

@renovate

@renovate renovate Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
github.com/charmbracelet/bubbletea v1.3.10v2.0.8 age confidence

Release Notes

charmbracelet/bubbletea (github.com/charmbracelet/bubbletea)

v2.0.8

Compare Source

Graphemes, schmraphemes

No terminal can render emojis perfectly, but we can try. This release improves some very specific edge cases around emoji rendering. Enjoy!

Changelog

  • db569ad: fix(deps): bump ultraviolet for emoji-related rendering improvements

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.7

Compare Source

A few lil’ stability patches

Hi! This is a patch release with a few solid improvements around stability and correctness.

  • @​lrstanley, one of our faves, fixed a race condition around mice in the Cursed Renderer
  • @​lawrence3699 fixed a panic that could happen when input's not available
  • We fixed a correctness issue with regard to mouse releases when Kitty Keyboard was active (thanks, @​mitchellh)

Thanks for using Bubble Tea, and if you see anything awry please do let us know!

—Charm 👋

Changelog
Fixed

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.6

Compare Source

This release fixes an issue with how Bubble Tea handled wide characters. Before, a wide character might be skipped or cause an infinite loop causing the CPU to spike. See fdcd0cf and charmbracelet/ultraviolet#109 for more details.


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.5

Compare Source

Changelog


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.4

Compare Source

Changelog


The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.3

Compare Source

Extra Extra Extended Keyboard Enhancements!

This release adds support for the full set of Keyboard Enhancements. Now you can enable any enhancements on top of the default disambiguate one.

func (m model) View() tea.View {
  var v tea.View
  v.KeyboardEnhancements.ReportAlternateKeys = true
  v.KeyboardEnhancements.ReportAllKeysAsEscapeCodes = true
  return v
}

Smarter Renderer

We also fixed a few renderer related bugs and made the Cursed Renderer smarter. Now, we always reset the terminal tab stops for the Bubble Tea program process context. People using tabs -N in their shell profiles shouldn't be affected.

See the full changelog below.

Changelog

New!
Fixed
Docs
Other stuff

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.2

Compare Source

This release contains a small patch fixing a rendering that might affect Wish users running on Unix platforms.

Changelog

Fixed

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.1

Compare Source

A small patch release to fix opening the proper default stdin file for input.

Changelog

Fixed
Docs

The Charm logo

Thoughts? Questions? We love hearing from you. Feel free to reach out on X, Discord, Slack, The Fediverse, Bluesky.

v2.0.0

Compare Source

bubbletea-v2-block

What's New in Bubble Tea v2

We're very excited to announce the second major release of Bubble Tea!

If you (or your LLM) are just looking for technical details on on migrating from v1, please check out the Upgrade Guide.

[!NOTE]
We don't take API changes lightly and strive to make the upgrade process as simple as possible. We believe the changes bring necessary improvements as well as pave the way for the future. If something feels way off, let us know.

❤️ Charm Land Import Path

We've updated our import paths to use vanity domains and use our domain to import Go packages.

// Before
import tea "github.com/charmbracelet/bubbletea"

// After
import tea "charm.land/bubbletea/v2"

Everything else stays the same 🙂

👾 The Cursed Renderer

Bubble Tea v2 ships with the all-new Cursed Renderer which was built from the ground up. It's based on the ncurses rendering algorithm and is highly optimized for speed, efficiency, and accuracy and is built on an enormous amount of research and development.

Optimized renders also means that Wish users get big performance benefits and lower bandwidth usage by orders of magnitude.

To take advantage of the new Cursed Renderer you don't need to do anything at all except keep on using the Bubble Tea you know and love.

✌️ Key handling is way better now

Newer terminals can now take advantage of all sorts keyboard input via progressive keyboard enhancements. You can now map all sorts of keys and modifiers like shift+enter and super+space. You can also detect key releases (we're looking at you, game developers).

It's easy to detect support for supporting terminals and add fallbacks for those that don't. For details, see keyboard enhancements below.

🥊 No more fighting

In the past, Bubble Tea and Lip Gloss would often fight over i/o. Bubble Tea wanted to read keyboard input and Lip Gloss wanted to query for the background color. This means that things could get messy. Not anymore! In v2, Lip Gloss is now pure, which means, Bubble Tea manages i/o and gives orders to Lip Gloss. In short, we only need one lib to call the shots, and in the context of this relationship, that lib is Bubble Tea.

But what about color downsampling? That's a great question.

👨🏻‍🎨 Built-in Color Downsampling

We sneakily released a little library called colorprofile that will detect the terminal's color profile and auto-downsample any ANSI styling that flows through it to the best available color profile. This means that color will "just work" (and not misbehave) no matter where the ANSI styling comes from.

Downsampling is built-into Bubble Tea and is automatically enabled.

🧘 Declarative, Not Imperative

This is a big one. In v1, you'd toggle terminal features on and off with commands like tea.EnterAltScreen, tea.EnableMouseCellMotion, tea.EnableReportFocus, and so on. In v2, all of that is gone and replaced by fields on the View struct. You just declare what you want your view to look like and Bubble Tea takes care of the rest.

This means no more fighting over startup options and commands. Just set the fields and forget about it. For example, to enter full screen mode:

func (m Model) View() tea.View {
    v := tea.NewView("Hello, full screen!")
    v.AltScreen = true
    return v
}

The same goes for mouse mode, bracketed paste, focus reporting, window title, keyboard enhancements, and more. See A Declarative View below for the full picture.

Keyboard Enhancements

Progressive keyboard enhancements allow you to receive key events not normally possible in traditional terminals. For example, you can now listen for the ctrl+m key, as well as previously unavailable key combinations like shift+enter.

Bubble Tea v2 will always try to enable basic keyboard enhancements that disambiguate keys. If your terminal supports it, your program will receive a tea.KeyboardEnhancementsMsg message that indicates support for requested features.

func (m Model) View() tea.View {
    var v tea.View
    // ...
    v.KeyboardEnhancements.ReportEventTypes = true           // Enable key release events
    return v
}

Historically, certain key combinations in terminals map to control codes. For example, ctrl+h outputs a backspace by default, which means you can't normally bind a key event to ctrl+h. With key disambiguation, you can now actually bind events to those key combinations.

You can detect if a terminal supports keyboard enhancements by listening for tea.KeyboardEnhancementsMsg.

func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyboardEnhancementsMsg:
        if msg.SupportsKeyDisambiguation() {
            // More keys, please!
        }
    }
}
Which terminals support progressive enhancement?

Key Messages

Key messages are now split into tea.KeyPressMsg and tea.KeyReleaseMsg. Use tea.KeyMsg to match against both. We've also replaced key.Type and key.Runes with key.Code and key.Text. Modifiers live in key.Mod now instead of being separate booleans. Oh, and space bar returns "space" instead of " ".

The easiest way to match against key press events is to use msg.String():

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "space":
            // Space bar returns "space" now :D
            return m, tea.Println("You pressed the space bar!")
        case "ctrl+c":
            return m, tea.SetClipboard("Howdy")
        case "shift+enter":
            // Awesome, right?
        case "ctrl+alt+super+enter":
            // Yes, you can do that now!
        }
    }
}

The Key struct also has some nice new fields:

  • key.BaseCode — the key according to a standard US PC-101 layout. Handy for international keyboards where the physical key might differ.
  • key.IsRepeat — tells you if the key is being held down and auto-repeating. Only available with the Kitty Keyboard Protocol or Windows Console API.
  • key.Keystroke() — a new method that returns the keystroke representation (e.g., "ctrl+shift+alt+a"). Unlike String(), it always includes modifier info.

For the full list of changes and before/after code samples, see the Upgrade Guide.

Paste Messages

Paste events used to arrive as tea.KeyMsg with a confusing msg.Paste flag. Now they're their own thing:

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.PasteMsg:
        // Here comes a paste!
        m.text += msg.Content
    case tea.PasteStartMsg:
        // The user started pasting.
    case tea.PasteEndMsg:
        // The user stopped pasting.
    }
}

Mouse Messages

We've improved the mouse API. Mouse messages are now split into tea.MouseClickMsg, tea.MouseReleaseMsg, tea.MouseWheelMsg, and tea.MouseMotionMsg. And mouse mode is set declaratively in your View():

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.MouseClickMsg:
        if msg.Button == tea.MouseLeft {
            // Clickety click
        }
    case tea.MouseWheelMsg:
        // Scroll, scroll, scrollllll
    }
    return m, nil
}

func (m model) View() tea.View {
    v := tea.NewView("Move that mouse around!")
    v.MouseMode = tea.MouseModeAllMotion // or tea.MouseModeCellMotion
    return v
}

A Declarative View

In v1, View() returned a string. In v2, it returns a tea.View struct that lets you declare everything about your view — content, cursor, alt screen, mouse mode, colors, window title, progress bar, and more:

type View struct {
	Content                   string
	OnMouse                   func(msg MouseMsg) Cmd
	Cursor                    *Cursor
	BackgroundColor           color.Color
	ForegroundColor           color.Color
	WindowTitle               string
	ProgressBar               *ProgressBar
	AltScreen                 bool
	ReportFocus               bool
	DisableBracketedPasteMode bool
	MouseMode                 MouseMode
	KeyboardEnhancements      KeyboardEnhancements
}

No more fighting over options and commands! Just set the fields:

func (m Model) View() tea.View {
  v := tea.NewView(fmt.Sprintf("Hello, world!"))
  v.AltScreen = true
  v.MouseMode = tea.MouseModeCellMotion
  v.ReportFocus = true
  v.WindowTitle = "My Awesome App"
  return v
}

An Actual Cursor

You can now control the cursor position, color, and shape right from your view function. Want it hidden? Just set view.Cursor = nil.

func (m Model) View() tea.View {
	var v tea.View
	if m.showCursor {
		v.Cursor = &tea.Cursor{
			Position: tea.Position{
				X: 14, // At the 14th column
				Y: 0,  // On the first row
			},
			Shape: tea.CursorBlock, // Just give me a block cursor '█'
			Blink: true,            // Blink baby, blink!
			Color: lipgloss.Green,  // Green cursor, because why not?
		}
	}
	v.SetContent(fmt.Sprintf("Hello, world!"))
	return v
}

You can also use tea.NewCursor(x, y) for a quick block cursor with default settings.

Progress Bar Support

Now you can ask Bubble Tea to render a native progress bar for your application. Just set the view.ProgressBar field and Bubble Tea will take care of the rest.

func (m Model) View() tea.View {
    var v tea.View
    v.SetContent("Downloading...")
    v.ProgressBar = tea.NewProgressBar(tea.ProgressBarDefault, m.downloadProgress)
    return v
}

Synchronized Updates (Mode 2026)

Bubble Tea will try and use mode 2026 to push updates to the terminal. This mode helps reduce tearing and cursor flickering by atomically updating the terminal window once all the update sequences are pushed out and read by the terminal. This is enabled by default and there's nothing you need to do.

Better Terminal Unicode Support (mode 2027)

Now Bubble Tea will automatically enable mode 2027
on terminals that support it. This mode allows the terminal to properly handle wide Unicode
characters and emojis without breaking the layout of your app. Again, this is
enabled by default and there's nothing you need to do.

Native Clipboard Support

Bubble Tea now supports native clipboard operations, also known as OSC52. This means you can even copy and paste over SSH!

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "ctrl+c":
            return m, tea.SetClipboard("Howdy")
        case "ctrl+v":
            return m, tea.ReadClipboard()
        }
    case tea.ClipboardMsg:
        fmt.Printf("Clipboard contents: %s\n", msg.String())
    }
}

X11 and Wayland users can also use tea.SetPrimaryClipboard to set the primary clipboard. Note that this is a very niche sort of thing and may or may not work on macOS, Windows, and other platforms without the notion of more than one clipboard.

Terminal Colors

You can now read and set the terminal's foreground, background, and cursor colors. To change them, set view.ForegroundColor, view.BackgroundColor, and view.Cursor.Color in your View() function.

func (m Model) Init() tea.Cmd {
    return tea.Batch(
        tea.RequestForegroundColor,
        tea.RequestBackgroundColor,
        tea.RequestCursorColor,
    )
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.BackgroundColorMsg:
        return m, tea.Printf("Background color: %s\n", msg)
    case tea.ForegroundColorMsg:
        return m, tea.Printf("Foreground color: %s\n", msg)
    case tea.CursorColorMsg:
        return m, tea.Printf("Cursor color: %s\n", msg)
    case tea.KeyPressMsg:
        switch msg.String() {
        case "enter":
            m.fg, m.bg, m.cursor = ansi.Red, ansi.Green, ansi.Blue
        case "esc":
            return m, tea.Quit
        }
    }
    return m, nil
}

func (m Model) View() tea.View {
    var v tea.View
    v.SetContent("\nPress Enter to change terminal colors, Esc to quit.")
    v.ForegroundColor = m.fg
    v.BackgroundColor = m.bg
    if m.cursor != nil {
        v.Cursor = tea.NewCursor(0, 1)
        v.Cursor.Color = m.cursor
    }
    return v
}

🌍 Environment Variables

Bubble Tea now sends you a tea.EnvMsg at startup with the environment variables. This is especially handy for SSH apps where os.Getenv would give you the server's environment, not the client's.

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.EnvMsg:
        m.term = msg.Getenv("TERM") // the client's TERM, not the server's!
    }
    return m, nil
}

🔮 Raw Escape Sequences

For the power users out there, you can now send raw escape sequences directly to the terminal with tea.Raw. This is great for querying terminal capabilities or doing things Bubble Tea doesn't have a built-in for (yet).

return m, tea.Raw(ansi.RequestPrimaryDeviceAttributes)

Responses from the terminal will come back as messages in Update. Just be sure you know what you're doing — with great power comes great terminal weirdness.

📍 Cursor Position Queries

Need to know where the cursor is? Now you can ask.

func (m model) Init() tea.Cmd {
    return tea.RequestCursorPosition
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.CursorPositionMsg:
        m.cursorX, m.cursorY = msg.X, msg.Y
    }
    return m, nil
}

📊 Terminal Mode Reports

You can query whether the terminal supports specific modes (like focus events or synchronized output) using DECRPM mode reports. Send a raw DECRQM request and listen for tea.ModeReportMsg.

func (m model) Init() tea.Cmd {
    return tea.Raw(ansi.RequestModeFocusEvent)
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.ModeReportMsg:
        if msg.Mode == ansi.ModeFocusEvent && !msg.Value.IsNotRecognized() {
            m.supportsFocus = true
        }
    }
    return m, nil
}

Terminal Version and Name

Don't know what terminal you're running in? $TERM is too vague? Bubble Tea now has a tea.RequestTerminalVersion command that queries the terminal for its name and version using the XTVERSION escape sequence.

[!NOTE]
This feature is not supported by all terminals.

func (m Model) Init() tea.Cmd {
    return tea.RequestTerminalVersion
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.TerminalVersionMsg:
        fmt.Printf("Terminal: %s\n", string(msg))
    }
}

Terminfo and Termcap Capabilities

Sometimes you need to know what capabilities the terminal has. Bubble Tea now has a tea.RequestCapability command that queries the terminal for a specific terminfo/termcap capability.

[!NOTE]
This feature is not supported by all terminals.

func (m Model) Init() tea.Cmd {
    return tea.RequestCapability("RGB") // RGB is the terminfo capability for direct colors
}

Detecting the Color Profile

Need to use the detected color profile in your app? Listen to tea.ColorProfileMsg in Update:

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.ColorProfileMsg:
        m.colorProfile = msg.Profile // gottem!
    }
    return m, nil
}

Manually Applying a Color Profile

Want to manually set a color profile for testing? Now you can, on the program level.

import (
    tea "charm.land/bubbletea/v2"
    "github.com/charmbracelet/colorprofile"
)

p := colorprofile.TrueColor // i love colors. lets' use 16,777,216 of 'em
p = colorprofile.ANSI256    // jk, 256 colors are plenty
p = colorprofile.ANSI       // actually let's juse use 16 colors
p = colorprofile.Ascii      // nm, no colors, but keep things like bold, italics, etc.
p = colorprofile.NoTTY      // lol actually strip all ANSI sequences

prog := tea.NewProgram(model, tea.WithColorProfile(p))

Want to hard detect the color profile in Wish? We bet you do.

func main() {
    var s ssh.Session
    pty, _, _ := s.Pty()

    // Get the environment...
    envs := append(s.Environ(), "TERM="+pty.Term)

    // ...and give it to Bubble Tea so it can detect the color profile.
    opt := tea.WithEnvironment(envs)

    p := tea.NewProgram(model,
        tea.WithInput(pty.Slave),
        tea.WithOutput(pty.Slave),
        opt, // wow
    )
}

🪟 Window Size for Testing

When running tests or in non-interactive environments, you can now set the initial terminal size:

p := tea.NewProgram(model, tea.WithWindowSize(80, 24))

No more mocking terminals just to run your tests. Nice!

Use the Terminal's TTY

Sometimes your program will write to stdout while it's being piped or
redirected. In these cases, you might want to write directly to the terminal's
TTY instead of stdout because stdout might not be a terminal. Or your program
expects to read from stdin but stdin is being piped from another program.

In Bubble Tea v1, there wasn't a good way to do this. In the latter case, you
could use the WithInputTTY() option to read from the terminal's TTY instead
of stdin. However, there was no easy way to write to the terminal's TTY instead
of stdout without fiddling with file descriptors.

In Bubble Tea v2, you can now simply use the global OpenTTY() to open the
terminal's TTY for reading and writing. You can then pass the TTY file handles
to the WithInput() and WithOutput() options.

Note that Bubble Tea v2 will always use the TTY for input when input is not specified
via WithInput(...).

ttyIn, ttyOut, err := tea.OpenTTY()
if err != nil {
    log.Fatal(err)
}

p := tea.NewProgram(model,
    tea.WithInput(ttyIn),
    tea.WithOutput(ttyOut),
)

Changelog

New!
  • 742b944f78af9b62e774a98855cb324dfe3313be: feat(render): enable Unicode mode (2027) for accurate width calculation (#​1584) (@​aymanbagabas)
  • 724479d6d5be45005d5a405e3cb6c0b6e8b1fd0e: feat(renderer): add modifyOtherKeys support (#​1579) (@​erikstmartin)
  • 1f48289368a38a670c6cb47929a4e33a2e7d5c1b: feat(renderer): enable both modifyOtherKeys 1 and 2 (@​aymanbagabas)
  • 0b472523eae41c772236ceab8531878f20972e31: feat: add view callback support (@​aymanbagabas)
Fixed
  • 323a3936e4b5f8c08aa638ca2917097c7c58bdeb: fix(ci): use local golangci-lint config (@​aymanbagabas)
  • b65daeb9d46142d2f428303eb77de3d3b65c2956: fix(examples): capability make sure input is focused on start (@​aymanbagabas)
  • 76f2e6d81219acce9d8eccb61845d20a77216cf1: fix(render): always assume raw mode for terminal output (@​aymanbagabas)
  • 61a3f5ccf812adb44eb1d62a2a3032af52fe4330: fix(render): execute insert above immediately (#​1576) (@​aymanbagabas)
  • fdb86513b5e15c6d4a7886221aa2e452738c0e57: fix(renderer): always move cursor to bottom on close (@​aymanbagabas)
  • 819e2e89c62ee26dc01a81546f099e633ab3c704: fix(renderer): flush after moving cursor on close (@​aymanbagabas)
  • fb790535cc28bec18b58c9947d46cb39515a0a74: fix(renderer): make sure we don't skip prepended lines on no-op frames (@​aymanbagabas)
  • 99c33bc3007094d114b7b5e412be03fae71728f8: fix(renderer): move cursor to bottom before disabling alt screen (@​aymanbagabas)
  • 6595041da277654876da2e5458eeed687ba4687c: fix(renderer): no need to enable both modifyOtherKeys protocols (@​aymanbagabas)
  • 59807cf07c8fd071cb252d2319604b22c711dd29: fix(renderer): reset kitty keyboard protocol on alt screen switch (#​1554) (@​aymanbagabas)
  • 2a0096c500a7ee38a5f244f6a80a1d50e6a76014: fix(renderer): restore state when restarting cursed renderer (#​1553) (@​aymanbagabas)
  • 1ff0a470e0d938dc6e8a1b3c659f98a73d042df8: fix(tea): don't query for synchronized output if the renderer is disabled (@​aymanbagabas)
  • ec0d820c3439f58cc92d4461edfc9fd8482f34de: fix(tea): only send actual mouse events to renderer on mouse callback (@​aymanbagabas)
  • b3661ce3d63f003545323b721212562c28647e0f: fix: always open the TTY for input (@​aymanbagabas)
  • ba8f60582376c0b45a93c0277de1949325cbca9c: fix: go.mod and go.sum to use lipgloss v2 (@​aymanbagabas)
  • faa0b9c36ba2d2ed00d8b9e16b8ff61bd7f28e0b: fix: lint issues (@​aymanbagabas)
Docs
  • fd7b0071c582d18d7e059c81d17fda03804ccade: docs(readme): add note about v2 upgrade guide (@​meowgorithm)
  • 07a69fbcf1e3649a797a929d724f94170d6affc3: docs(readme): update bubbles callout image (@​meowgorithm)
  • 3499dac84886f5a54f7ccfc7f3446a361bf2da2e: docs(readme): update header image (@​meowgorithm)
  • ceab368db0d8fe42f8beac3c28bc14f6d95a34d2: docs(readme): updates for v2 (#​1589) (@​aymanbagabas)
  • 59ca08b21ca50c466cba0af717158df20aef5043: docs: add v2 upgrade and changes guide (#​1585) (@​aymanbagabas)
  • 46b608f1528a7a8f51e2ba767c884acfa75c0269: docs: update OnMouse callback to use msg parameter (@​aymanbagabas)
  • 105959b37b4f80600cff83b67187a266cbd5b0be: docs: update mascot header image with a cleaner one (@​aymanbagabas)
  • 5a41615aa4ff78da82ca901b540c3202fa810df3: docs: view cursor api typo (#​1557) (@​bountis)
Other stuff
  • 764923436993564e6e31ae39746e8eaf0e7ad5e6: ci: sync dependabot config (#​1505) (@​charmcli)
  • d1cf96d0d3c9119e422383c2993705fd148cdd86: refactor: limit view callback to onMouse for mouse events (@​aymanbagabas)
  • 14519253d503b2782910f5527f35f08cda5df5de: refactor: omit unnecessary reassignment (#​1515) (@​goldlinker)
  • ece00b4f40c5863709daa28786d0887560811fa5: refactor: remove unused max func (#​1537) (@​sunnyraindy)

🌈 More on Bubble Tea v2

Ready to migrate? Head over to the Upgrade Guide for the full migration checklist.

Feedback

Have thoughts on Bubble Tea v2? We'd love to hear about it. Let us know on…


Part of Charm.

The Charm logo

Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate

renovate Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: go.sum
Command failed: go get -t ./...
go: github.com/charmbracelet/bubbletea/v2@v2.0.8: parsing go.mod:
	module declares its path as: charm.land/bubbletea/v2
	        but was required as: github.com/charmbracelet/bubbletea/v2

@elysia-best elysia-best added AUTO Automatically created by robots/AIs dependencies labels Jul 8, 2026
@xrgzs xrgzs added the invalid Invalid Content/Cannot Reproduce label Jul 9, 2026
@renovate
renovate Bot force-pushed the renovate/github.com-charmbracelet-bubbletea-2.x branch from e71e507 to a2a0da2 Compare July 12, 2026 09:51
@renovate
renovate Bot force-pushed the renovate/github.com-charmbracelet-bubbletea-2.x branch from a2a0da2 to 3f98b6a Compare July 20, 2026 16:29
@renovate
renovate Bot force-pushed the renovate/github.com-charmbracelet-bubbletea-2.x branch from 3f98b6a to 457279e Compare July 22, 2026 09:58

@pikachuren pikachuren left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙏 感谢贡献

感谢 @app/renovate 提交此自动依赖更新 PR!我已完成代码评审,以下是评审结果。


🤖 AI 自动审核声明

本评审报告由 AI 自动生成,当前使用 Claude Opus 5 模型进行分析,部分复杂场景可能辅助使用 ChatGPT、DeepSeek 等模型进行交叉验证。

⚠️ AI 分析结果仅供参考,可能存在误判或遗漏。如您发现任何问题或有不同意见,欢迎随时提出讨论和纠正。

⚠️ 重要提醒:即使 AI 评审认为代码质量良好且建议合并,最终是否合并仍需由项目维护者进行人工判定。项目维护者会综合考虑代码质量、项目规划、技术方向、团队资源等多方面因素做出决策。


📖 PR背景与需求

PR标题:fix(deps): update module github.com/charmbracelet/bubbletea to v2

依赖更新类型:主版本升级(Major Version Update)

需求说明
Renovate Bot 自动创建的依赖更新 PR,将 github.com/charmbracelet/bubbletea 从 v1.3.10 升级到 v2.0.8(主版本升级)。

预期目标

  • 使用 Bubble Tea v2 的新架构和功能
  • 获得性能提升(Cursed Renderer、带宽优化)
  • 支持增强键盘输入(Kitty Keyboard Protocol)
  • 更好的声明式 View API

📋 问题摘要

  • ⚠️ 破坏性变更风险:主版本升级包含大量不兼容的 API 变更⚠️⚠️⚠️ 极高风险)
  • ⚠️ 不完整迁移:同时保留 v1 和 v2 两个版本,依赖冗余(⚠️ 重要)
  • ⚠️ 缺少兼容性验证:未检查代码是否需要适配新 API(⚠️ 关键)
  • ⚠️ 依赖链问题:bubbles 和 lipgloss 也需要升级到 v2(⚠️ 阻塞性)
  • 💡 需要大规模代码重构:View()、Key、Mouse、Paste 等核心 API 全部变更

📂 逐文件分析

go.mod

改动意图
将 Bubble Tea 依赖从 v1.3.10 升级到 v2.0.8。

代码修改逻辑

-	github.com/charmbracelet/bubbletea v1.3.10
+	github.com/charmbracelet/bubbletea/v2 v2.0.8

在 Go modules 中,主版本 v2+ 需要在模块路径中包含版本后缀(/v2)。

合理性评估

优点

  1. 符合 Go modules 规范:正确使用了 /v2 路径后缀
  2. 版本选择合理:v2.0.8 是 v2 系列的稳定版本,包含多个 bug 修复
  3. 上游质量良好:Bubble Tea v2 经过充分测试,由 Charm 团队维护
  4. 性能提升显著:Cursed Renderer 将带宽使用降低数个数量级

⚠️ 重要疑问

  1. 依赖冗余问题⚠️ 关键)

    • 当前 go.mod 中同时保留了 v1 和 v2 两个版本
    • 这会导致编译后的二进制文件包含两份 Bubble Tea 代码,显著增大体积
    • 必须检查并移除 v1 依赖
  2. 依赖链不兼容问题⚠️⚠️⚠️ 阻塞性)

    • 当前 go.mod

      • bubbles 仍是 v0.21.1(v1 系列,依赖 bubbletea v1)
      • lipgloss 仍是 v1.1.0(v1 系列)
    • Bubble Tea v2 要求

      • 必须使用 bubbles/v2(兼容 bubbletea v2)
      • 必须使用 lipgloss/v2(Bubble Tea v2 已集成)
    • 结果此 PR 单独合并会导致编译失败,因为依赖版本不匹配

  3. 代码兼容性问题⚠️⚠️⚠️ 极高风险)

    Bubble Tea v2 包含大量破坏性 API 变更

    v1 API v2 API 影响范围
    View() string View() tea.View 所有组件的 View 方法
    tea.KeyMsg tea.KeyPressMsg / tea.KeyReleaseMsg 所有键盘处理逻辑
    msg.Type, msg.Runes msg.Code, msg.Text 所有按键判断
    msg.Alt, msg.Ctrl, msg.Shift msg.Mod 所有修饰键判断
    tea.MouseMsg tea.MouseClickMsg / tea.MouseReleaseMsg / tea.MouseWheelMsg / tea.MouseMotionMsg 所有鼠标处理
    tea.KeyMsg with msg.Paste tea.PasteMsg / tea.PasteStartMsg / tea.PasteEndMsg 粘贴事件处理
    tea.EnterAltScreen() view.AltScreen = true 终端模式切换
    tea.EnableMouseCellMotion() view.MouseMode = tea.MouseModeCellMotion 鼠标模式设置

    必须修改的代码位置(影响全局):

    • 所有实现了 View() string 的组件 → 改为 View() tea.View
    • 所有 case tea.KeyMsg: → 改为 case tea.KeyPressMsg:
    • 所有 switch msg.Type: → 改为 switch msg.String():
    • 所有 msg.Altmsg.Ctrl → 改为 msg.Mod.Has(tea.ModAlt)
    • 所有 case tea.MouseMsg: → 拆分为多种鼠标消息类型
    • 所有使用 tea.EnterAltScreen 等命令 → 改为声明式 View 字段
  4. 迁移策略不明确

    • 是完全迁移到 v2?(推荐,但工作量巨大)
    • 还是渐进式迁移?(需要明确计划)
    • 还是意外引入?(需要回退)

明确问题

  1. 缺少配套依赖升级

    • 必须同时升级 bubbles 到 v2
    • 必须同时升级 lipgloss 到 v2
    • 否则会出现版本冲突和编译失败
  2. 缺少兼容性验证

    • PR 描述中没有提到是否检查了代码兼容性
    • 没有提到是否运行了测试套件
    • 没有提到是否查阅了 v2 的 breaking changes
  3. 缺少迁移说明

    • 没有说明哪些代码需要修改
    • 没有提供迁移 checklist
    • 没有说明预计的迁移工作量

🎯 总体评价

功能性⚠️⚠️ - 主版本升级,需大规模代码重构
安全性:⭐⭐⭐⭐ - Bubble Tea 不涉及安全关键逻辑,上游可信
代码质量⚠️⚠️ - 不完整的迁移,会导致编译失败
实现方案⚠️ - 需要制定完整的迁移方案和计划

建议操作

  • ✅ Approve(建议合并)
  • 🔄 Request Changes(需要修改)
  • ❌ Close(建议关闭)

理由

此 PR 是一个主版本升级,包含大量破坏性 API 变更,需要极其谨慎处理。当前状态存在以下阻塞性问题

  1. 依赖链不完整,无法编译

    • 仅升级了 bubbletea 到 v2,但 bubbleslipgloss 仍是 v1
    • bubbles v0.21.1 依赖 bubbletea v1,与 bubbletea v2 不兼容
    • 此 PR 单独合并会导致编译失败
  2. 未进行代码迁移

    • 仅修改了 go.mod,但没有任何代码层面的修改
    • Bubble Tea v2 的核心 API 完全重构,所有使用 Bubble Tea 的代码都需要修改
    • 如果没有修改代码,即使能编译,运行时也会出错
  3. 影响范围巨大

    • Bubble Tea 是整个 TUI 应用的核心框架
    • 所有组件、所有 Update/View 函数、所有事件处理都需要修改
    • 这是一个全局性的重大重构,不是简单的依赖更新

📝 详细建议

必须完成的步骤(按顺序):

1. 评估迁移工作量

# 统计使用 Bubble Tea API 的文件数量
grep -r "bubbletea" . --include="*.go" | wc -l

# 统计所有 View() 方法(需要全部改为 tea.View)
grep -r "func.*View().*string" . --include="*.go" | wc -l

# 统计所有 KeyMsg 处理(需要改为 KeyPressMsg)
grep -r "tea.KeyMsg" . --include="*.go" | wc -l

# 统计所有 MouseMsg 处理(需要拆分为多种消息类型)
grep -r "tea.MouseMsg" . --include="*.go" | wc -l

# 统计所有命令式调用(需要改为声明式 View 字段)
grep -r "tea.EnterAltScreen\|tea.EnableMouse\|tea.EnableReportFocus" . --include="*.go" | wc -l

2. 决定迁移策略

方案 A:完全迁移到 v2(✅ 推荐,但工作量巨大)

优点:

  • 获得 v2 的所有新功能和性能提升
  • 长期维护更简单
  • 清理了依赖,减小二进制体积

缺点:

  • 工作量极大:需要修改数百个文件
  • 高风险:容易引入 bug 和回归
  • 耗时长:可能需要数周甚至数月

步骤:

  1. 制定详细的迁移计划,分阶段进行
  2. 同时升级所有相关依赖
    go get charm.land/bubbletea/v2@latest
    go get charm.land/bubbles/v2@latest
    go get charm.land/lipgloss/v2@latest
  3. 修改所有导入语句
    // Before
    import tea "github.com/charmbracelet/bubbletea"
    
    // After
    import tea "charm.land/bubbletea/v2"
  4. 重构所有 View() 方法
    // Before (v1)
    func (m model) View() string {
        return "Hello, World!"
    }
    
    // After (v2)
    func (m model) View() tea.View {
        return tea.NewView("Hello, World!")
    }
  5. 重构所有事件处理
    // Before (v1)
    case tea.KeyMsg:
        switch msg.Type {
        case tea.KeyEnter:
            if msg.Alt {
                // Alt+Enter
            }
        }
    
    // After (v2)
    case tea.KeyPressMsg:
        switch msg.String() {
        case "enter":
            // Enter
        case "alt+enter":
            // Alt+Enter
        }
  6. 将命令式调用改为声明式
    // Before (v1)
    func (m model) Init() tea.Cmd {
        return tea.Batch(
            tea.EnterAltScreen,
            tea.EnableMouseCellMotion,
        )
    }
    
    // After (v2)
    func (m model) View() tea.View {
        v := tea.NewView(m.content)
        v.AltScreen = true
        v.MouseMode = tea.MouseModeCellMotion
        return v
    }
  7. 运行完整测试go test ./... -v
  8. 手动测试所有 TUI 功能

方案 B:保持 v1(✅ 如果迁移成本不可接受)

如果迁移工作量过大或时机不合适:

  1. 关闭此 PR(以及 #2769 lipgloss v2 PR)
  2. renovate.json 中配置忽略 v2 升级:
    {
      "packageRules": [
        {
          "matchPackageNames": [
            "github.com/charmbracelet/bubbletea",
            "github.com/charmbracelet/lipgloss",
            "github.com/charmbracelet/bubbles"
          ],
          "allowedVersions": "< 2.0.0"
        }
      ]
    }
  3. 继续使用 v1 系列的最新版本,等待合适的迁移时机

方案 C:渐进式迁移(❌ 不推荐)

由于依赖链的强关联性,渐进式迁移几乎不可行:

  • 不能只升级 bubbletea 而不升级 bubbleslipgloss
  • 不能让部分组件使用 v1,部分使用 v2(类型不兼容)
  • 同时维护两个版本会导致极大的代码复杂度

3. 查阅官方迁移指南

必读文档

关键变更摘要(Bubble Tea v2)

类别 v1 API v2 API
View 方法 View() string View() tea.View
按键消息 tea.KeyMsg tea.KeyPressMsg / tea.KeyReleaseMsg
按键字段 msg.Type, msg.Runes msg.Code, msg.Text
修饰键 msg.Alt, msg.Ctrl, msg.Shift msg.Mod.Has(tea.ModAlt)
空格键 " " "space"
鼠标消息 tea.MouseMsg tea.MouseClickMsg / tea.MouseReleaseMsg / tea.MouseWheelMsg / tea.MouseMotionMsg
粘贴消息 tea.KeyMsg with msg.Paste tea.PasteMsg / tea.PasteStartMsg / tea.PasteEndMsg
终端模式 tea.EnterAltScreen() 命令 view.AltScreen = true 字段
鼠标模式 tea.EnableMouseCellMotion() 命令 view.MouseMode = tea.MouseModeCellMotion 字段
焦点报告 tea.EnableReportFocus() 命令 view.ReportFocus = true 字段

4. 配套依赖升级

必须同时升级(作为一个原子操作):

# 升级整个 Charm 生态到 v2
go get charm.land/bubbletea/v2@latest
go get charm.land/bubbles/v2@latest
go get charm.land/lipgloss/v2@latest

# 清理依赖
go mod tidy

# 验证没有 v1 残留
go mod graph | grep "bubbletea@v1\|lipgloss@v1\|bubbles@v0"

5. 运行测试并验证

# 编译检查
go build ./...

# 运行单元测试
go test ./... -v

# 运行集成测试(如果有)
go test ./... -tags=integration

# 手动测试 TUI 功能
# 启动应用,逐一测试:
# - 键盘输入(包括特殊组合键)
# - 鼠标交互
# - 全屏模式
# - 粘贴功能
# - 样式渲染
# - 所有 UI 组件

💡 迁移示例

示例 1:View() 方法重构

// Before (v1)
type model struct {
    content string
}

func (m model) View() string {
    return lipgloss.NewStyle().
        Bold(true).
        Foreground(lipgloss.Color("#FF0000")).
        Render(m.content)
}

// After (v2)
import (
    tea "charm.land/bubbletea/v2"
    "charm.land/lipgloss/v2"
)

type model struct {
    content string
}

func (m model) View() tea.View {
    style := lipgloss.NewStyle().
        Bold(true).
        Foreground(lipgloss.Color("#FF0000"))
    
    content := style.Render(m.content)
    
    v := tea.NewView(content)
    v.AltScreen = true  // 如果需要全屏
    v.MouseMode = tea.MouseModeCellMotion  // 如果需要鼠标
    return v
}

示例 2:按键处理重构

// Before (v1)
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyMsg:
        switch msg.Type {
        case tea.KeyEnter:
            if msg.Alt {
                return m, m.doAltEnter()
            }
            return m, m.doEnter()
        case tea.KeySpace:
            return m, m.doSpace()
        case tea.KeyCtrlC:
            return m, tea.Quit
        }
    }
    return m, nil
}

// After (v2)
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.KeyPressMsg:
        switch msg.String() {
        case "enter":
            return m, m.doEnter()
        case "alt+enter":
            return m, m.doAltEnter()
        case "space":  // 注意:v2 中空格是 "space" 而不是 " "
            return m, m.doSpace()
        case "ctrl+c":
            return m, tea.Quit
        }
    }
    return m, nil
}

示例 3:鼠标处理重构

// Before (v1)
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.MouseMsg:
        switch msg.Type {
        case tea.MouseLeft:
            m.handleClick(msg.X, msg.Y)
        case tea.MouseWheelUp:
            m.scrollUp()
        case tea.MouseWheelDown:
            m.scrollDown()
        }
    }
    return m, nil
}

// After (v2)
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    switch msg := msg.(type) {
    case tea.MouseClickMsg:
        if msg.Button == tea.MouseLeft {
            m.handleClick(msg.X, msg.Y)
        }
    case tea.MouseWheelMsg:
        if msg.Direction == tea.MouseWheelUp {
            m.scrollUp()
        } else {
            m.scrollDown()
        }
    }
    return m, nil
}

🔍 需要回答的问题

在合并此 PR 之前,请确认以下问题:

  1. 是否已评估迁移工作量和所需时间?
  2. 是否制定了详细的迁移计划和里程碑?
  3. 是否同时升级了 bubbles 和 lipgloss 到 v2?
  4. 是否查阅了 Bubble Tea v2 的升级指南和 breaking changes?
  5. 是否修改了所有 View() 方法返回类型?
  6. 是否重构了所有事件处理逻辑(Key、Mouse、Paste)?
  7. 是否将命令式调用改为声明式 View 字段?
  8. 是否运行了 go mod tidy 并确认 v1 已被移除?
  9. 是否运行了完整的测试套件并通过?
  10. 是否手动测试了所有 TUI 功能,确认无回归?

如果以上任何一项为 ❌,此 PR 不应合并


🚨 特别警告

此 PR 单独合并会导致项目无法编译!

原因:

  • bubbletea v2bubbles v0.21.1 不兼容
  • bubbles v0.21.1 依赖 bubbletea v1
  • Go modules 会尝试同时引入 v1 和 v2,导致类型冲突

必须同时合并以下 PR(或创建新的 PR)

  1. 升级 bubbletea 到 v2.0.8(当前 PR)
  2. 升级 bubbles 到 v2.x
  3. 升级 lipgloss 到 v2.x(PR #2769
  4. 修改所有使用 Bubble Tea 的代码

建议操作

  1. 关闭当前所有单独的依赖升级 PR
  2. 创建一个完整的 v2 迁移 PR,包含:
    • 所有依赖升级
    • 所有代码修改
    • 完整的测试验证
  3. 或者,如果暂时不迁移,配置 Renovate 忽略 v2 升级

📚 参考资料

官方文档

社区资源


总结

这是一个需要极其谨慎处理的重大重构 PR。Bubble Tea v2 是一个激动人心的升级,但不是简单的依赖更新,而是需要大规模代码重构的架构变更

强烈建议

  1. 暂不合并,先评估工作量和影响范围
  2. 制定完整的迁移计划,分配足够的人力和时间
  3. 同时升级所有相关依赖,作为一个原子操作
  4. 全面测试,确保无回归
  5. 或者,暂时保持 v1,在合适的时机进行迁移

如果需要帮助迁移代码,请随时提问!我可以协助分析具体的代码修改需求和最佳实践。

特别提醒:此 PR 与 #2769(lipgloss v2)、可能的 bubbles v2 PR 互相关联,必须作为一个整体来处理。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AUTO Automatically created by robots/AIs dependencies invalid Invalid Content/Cannot Reproduce

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants