Troubleshooting
Fixes for common problems - a blank screen, clicks that don't arrive or arrive twice, animations that don't move, text that doesn't wrap, and more.
See what the layout did
bloom.Debug lays out a tree like Solve and returns it as
text: every node, how it's sized, its ID, and where it went. Nodes that were collapsed, scrolled away, or only partly
on screen are marked. Write it somewhere you can read while the program runs, such as a log file:
func (m model) View() tea.View {
if m.debug {
log.Print(bloom.Debug(m.layout(), m.width, m.height))
}
return m.ui.View(m.width, m.height, m.layout())
}Column 0,0 30x5
├─ Row 0,0 30x3
│ ├─ Fixed(10) id="sidebar" hidden collapsible=1
│ │ └─ Box hidden
│ │ └─ Text "Inbox" hidden
│ └─ Fill id="reader" 0,0 30x3 min=24
│ └─ main.reader 0,0 30x3
└─ Text "ready" 0,3 30x1 truncateRectangles are x,y then width x height. Debug is stateless, so layout animations show where they're headed, not
where they are mid-flight, and effects that move a node, such as fx.Slide, aren't applied. The format is for
reading, and may change between versions.
The screen is blank
m.ui.View draws nothing while the width is zero, which it is until Bubble Tea sends the first
tea.WindowSizeMsg. Store the size from that message in your model and pass it to View:
case tea.WindowSizeMsg:
m.width, m.height = msg.Width, msg.HeightClicks don't arrive
Check each of these:
- The view turns on mouse reporting:
v.MouseMode = tea.MouseModeCellMotion(ortea.MouseModeAllMotionfor hover). - The handler is on the node, or on something it's inside, with
On(...). - A node inside it doesn't have a handler of the same kind. The innermost one takes the event; see which node gets the event.
- A click runs when the button comes up over the node it went down on. Releasing it elsewhere cancels the click.
- A modal layer isn't open, taking the events.
- The view comes from
m.ui.View(...), which installs theOnMousehandler that runs your handlers. If you build thetea.Viewyourself or replace itsOnMouse, pass every message tom.ui.Update, which then runs them.
A handler runs, but nothing changes
Handlers are closures made in View, so they change whatever View captured. If that's a copy, the change is
lost:
Viewhas a value receiver,func (m model) View(). Use a pointer receiver, or have the handler send a message withon.Msg(msg)and change the state inUpdate.- The handler is built in a component kept by value in its parent, such as a Bubbles-style list updated with
m.list, cmd = m.list.Update(msg). Have the component send a message withon.Msgand handle it in its ownUpdate, or build the handler in the parent over&m.list.
See change state, or send a message.
Every click happens twice
Your handlers run, and the original tea.MouseClickMsg reaches Update too. If you also handle the raw mouse
messages, don't act on an event a handler already handled.
The screen freezes after a click
A handler runs on the Bubble Tea event loop, the same as Update, so it must not block: no sleeping, waiting, or
I/O in the handler itself. Return a command for that work instead, which runs in the background.
Animations don't move, or stop partway
- Return the command from
Show,Hide,To,Play, andLoop. Without it, the first frame is never scheduled. - Pass every message to every animation, with
anim.Update(msg, &m.a, &m.b), and return its command. Passing only some message types drops the frame messages. - For layout animation (
Animate), pass every message tom.ui.Updateand return its command, and give the animated slot or layer an ID. - Animations in a slice need to be updated through pointers to the elements, as in stacking toasts.
Effects look wrong on a light terminal
Dim fades toward the terminal's background, and some effects use its foreground. Until the terminal reports its
colors, bloom assumes white on black. Return m.ui.Init() from your model's Init and pass every message to
m.ui.Update so the root hears the reply. For terminals that never answer, set defaults with bloom.WithColors.
Text doesn't wrap, or wraps when it shouldn't
bloom.Text(s)on its own is clipped, not wrapped. Add.Style(...), even an emptylipgloss.NewStyle(), to wrap it to its node's width.- A status bar or title that should stay on one line needs
.Truncate(). - A string you rendered yourself with Lip Gloss has a fixed size already. Pass the style to
Text(...).Style(...)orBox(...)instead, so bloom renders it at the right size.
A panel disappears
A Collapsible slot is dropped when the minimum sizes in its row or column don't fit. Lower another slot's Min,
raise the collapsible slot's priority, or make the terminal bigger. frame.Rect(id) reports false for a collapsed
slot, which you can use to tell the user the panel is hidden.
A component has the wrong size
Components that size themselves internally, such as a Bubbles viewport, need to be told the size their slot has.
Find it with bloom.Solve when the window size or layout changes; see
Using Bubbles components. Remember that a slot's rectangle
includes the border of a Box inside it.
Wide characters look misaligned
bloom measures text with grapheme clustering by default, as Lip Gloss does. If your terminal measures emoji or other
wide characters differently, set the method to match with bloom.WithWidthMethod.
Still stuck?
Ask in GitHub Discussions, report a bug in issues, or chat on Discord.
Last updated on