# Bubble Tea Admin & Architecture Guide

This guide is for developers, architects, and maintainers who embed Bubble Tea in a product, operate Bubble Tea–based programs, or need to understand how the framework works internally. It documents **Bubble Tea v2** (`charm.land/bubbletea/v2`) at commit `a23da80`.

For task-oriented usage, see the companion **User Guide**. This document goes deeper: the runtime architecture, the event loop, the renderer, configuration, security posture, terminal-compatibility behavior, and how to extend or contribute to the project.

---

## 1. Executive Summary

**Bubble Tea** is a Go framework for terminal user interfaces, built on **The Elm Architecture (Model–Update–View)**. It is a *library*, not an application: consumers import the `tea` package and build their own binaries. There is no server, no daemon, no configuration file, and no network surface — Bubble Tea runs in-process inside the host program and talks only to the controlling terminal (stdin/stdout) and OS signals.

| Attribute | Value |
|---|---|
| **Kind** | Importable Go library / application framework |
| **Module** | `charm.land/bubbletea/v2` |
| **Language** | Go 1.25.0 |
| **License** | MIT |
| **Architecture** | The Elm Architecture (MVU), single-goroutine update loop, concurrent commands |
| **Core package** | One package (`tea`), 43 source files (~152 KB) at the repo root |
| **Rendering** | Cell-based "cursed renderer" backed by `charmbracelet/ultraviolet` |
| **Concurrency model** | One central event loop; I/O runs in commands on separate goroutines |
| **External I/O** | Controlling terminal (TTY) + OS signals only — no sockets, no files (except opt-in logging) |
| **Distribution** | `go get`; ships as part of the consumer's binary |

**What's notable in v2:** a declarative `View` struct replaces v1's imperative options/commands for terminal features; a new high-performance cell renderer (ultraviolet); first-class keyboard/mouse with the Kitty protocol; native clipboard (OSC52); and a vanity module path (`charm.land/...`).

---

## 2. System Architecture

### 2.1 The Elm Architecture

Bubble Tea structures every program around three pure-ish functions on a single state value:

```text
         ┌──────────────────────────────────────────────────────────┐
         │                       Program.Run()                       │
         │                                                           │
 stdin ─►│  TerminalReader (ultraviolet) ─► uv.Event ─► translate ─► │─► Msg
 (TTY)   │                                                           │     │
         │                          ┌────────────────┐              │     ▼
         │                          │   eventLoop()  │◄─────────────┼── p.msgs
         │   model.Update(msg) ◄────┤  (1 goroutine) │              │
         │        │                 └────────────────┘              │
         │        ├─► returns (Model, Cmd)                          │
         │        │                                                 │
         │        ▼                                                 │
         │   handleCommands(): cmd() runs in its OWN goroutine ─────┼─► p.Send(newMsg)
         │        │                                                 │
         │        ▼                                                 │
         │   render(model.View()) ─► renderer.render(View)          │
         │                              │                           │
         │                          ticker @ FPS ─► renderer.flush()│─► stdout (TTY)
         └──────────────────────────────────────────────────────────┘
```

Key properties:

- **One update goroutine.** `eventLoop` is the only place `model.Update` is called, so model mutation is serialized and race-free *by construction* — as long as you don't mutate the model from other goroutines.
- **Commands are the I/O boundary.** A `Cmd` is `func() Msg`. The loop hands each returned command to `handleCommands`, which runs it in a fresh goroutine and feeds its result back via `p.Send`. This is why slow work never blocks rendering.
- **Rendering is decoupled.** `render` just stores the latest `View`; a separate ticker goroutine flushes it to the terminal at the configured FPS (default 60, capped at 120).

### 2.2 The `Model` contract

```go
type Model interface {
	Init() Cmd               // startup command (nil = none)
	Update(Msg) (Model, Cmd) // event → next state + optional command
	View() View              // state → declarative UI description
}
```

`Msg` is a type alias for `ultraviolet.Event` (`type Msg = uv.Event`), i.e. *any* value can be a message. `Cmd` is `func() Msg`.

> [!NOTE]
> A few doc-comment *examples* inside the source (e.g. in `color.go`, `keyboard.go`) show `Init() (Model, Cmd)`. That is a documentation inconsistency — the authoritative `Model` interface in `tea.go` defines `Init() Cmd`. The README and both tutorials use `Init() Cmd`. Implement `Init() Cmd`.

### 2.3 The `Program` runtime

`tea.NewProgram(model, opts...)` builds a `*Program`. `Program.Run()` is the blocking entry point that:

1. Resolves input (stdin or an opened TTY) and enters raw mode.
2. Detects terminal size and color profile; constructs the renderer (or `nilRenderer` if disabled).
3. Sends bootstrap messages: `ColorProfileMsg`, the initial `WindowSizeMsg`, and `EnvMsg`.
4. Runs `model.Init()` and dispatches the resulting command.
5. Starts the signal handler, resize listener, command processor, and renderer ticker.
6. Enters `eventLoop` until quit/kill/interrupt, then restores the terminal.

`Program` lifecycle methods:

| Method | Purpose |
|---|---|
| `Run() (Model, error)` | Start and block until termination; returns the final model. |
| `Send(msg Msg)` | Inject a message from any goroutine (no-op after exit). |
| `Quit()` | Graceful shutdown from outside (sends `QuitMsg`). |
| `Kill()` | Immediate shutdown, skips final render; `Run` returns `ErrProgramKilled`. |
| `Wait()` | Block until the program has fully shut down. |
| `Println(...)` / `Printf(...)` | Emit persistent lines above the UI. |
| `ReleaseTerminal()` / `RestoreTerminal()` | Temporarily yield and reclaim the terminal (used by `Exec`). |

### 2.4 Component composition

Bubble Tea has no widget system of its own; composition is a *convention*. A parent model embeds child component models, forwards messages to each child's `Update`, and stitches their `View()` strings together (typically with Lip Gloss). This recursion — "models all the way down" — is how large apps (and the entire Bubbles library) are built. See User Guide §8.

---

## 3. Source Layout

The library is a single Go package at the repository root. The rest of the tree is learning material and demos.

```text
bubbletea/
├── tea.go              Program, Model, View, Cmd, NewProgram, Run, eventLoop
├── options.go          ProgramOption constructors (With…)
├── commands.go         Batch, Sequence, Tick, Every, RequestWindowSize
├── exec.go             Exec / ExecProcess (run external processes)
├── key.go              Key, KeyPressMsg/KeyReleaseMsg, key constants
├── keyboard.go         KeyboardEnhancements + KeyboardEnhancementsMsg
├── mouse.go            Mouse, MouseClickMsg/Release/Wheel/Motion, buttons
├── screen.go           WindowSizeMsg, ClearScreen, ModeReportMsg
├── cursor.go           Position, Cursor shapes, CursorPositionMsg
├── color.go / profile.go   Color query msgs, ColorProfileMsg
├── clipboard.go        OSC52 clipboard commands/messages
├── paste.go            Bracketed-paste messages
├── focus.go            FocusMsg / BlurMsg
├── environ.go          EnvMsg (environment over SSH)
├── raw.go              Raw escape-sequence command
├── termcap.go / xterm.go   Capability + terminal-version queries
├── logging.go          LogToFile / LogToFileWith
├── cursed_renderer.go  The cell-based renderer (ultraviolet-backed)
├── nil_renderer.go     No-op renderer (WithoutRenderer)
├── renderer.go         renderer interface, Println/Printf, FPS constants
├── tty*.go / termios*.go / signals_*.go   Per-OS terminal & signal plumbing
├── mod.go              KeyMod modifier constants
├── tutorials/          basics (MVU) + commands (I/O) — the canonical learning path
└── examples/           63 runnable demos (recipe gallery; not part of the API)
```

> [!NOTE]
> The `examples/` and `tutorials/` directories are **separate Go modules** (`examples`, `tutorial`) with their own `go.mod` and a `replace` directive pointing at the parent. They are not imported by consumers and are excluded from the published library. They are, however, the best executable reference for real-world patterns.

---

## 4. The Event Loop in Detail

`eventLoop` (in `tea.go`) is the heart of the runtime. For each message it:

1. **Translates** raw input events (`translateInputEvent`) into typed `tea.Msg` values (key, mouse, paste, focus, color, capability, etc.).
2. **Filters** the message through the optional `WithFilter` function (which can rewrite or drop it).
3. **Handles framework-internal messages** before the model sees them — including `QuitMsg`, `InterruptMsg`, `SuspendMsg`, `BatchMsg`, `sequenceMsg`, `WindowSizeMsg`, clipboard/color requests, `ClearScreen`, `Println`, and `ColorProfileMsg`.
4. **Calls `model.Update(msg)`**, capturing the returned model and command.
5. **Dispatches the command** to the command channel.
6. **Renders** the new `View`.

### 4.1 Concurrency & ordering

| Mechanism | Behavior |
|---|---|
| `Batch(cmds…)` | Commands run **concurrently** in separate goroutines; results arrive in any order. |
| `Sequence(cmds…)` | Commands run **serially, in order**; each completes before the next starts. Nested `Batch`/`Sequence` are handled recursively. |
| Plain returned `Cmd` | Runs in its own goroutine via `handleCommands`; its `Msg` is `Send`-ed back to the loop. |
| `p.Send(msg)` | Thread-safe injection from any goroutine; blocks until the loop accepts it (or no-ops after shutdown). |

> [!IMPORTANT]
> Command goroutines are intentionally **not awaited** at shutdown — a long `Tick` could otherwise stall exit. They are allowed to leak until their function returns. Design commands to be cancellation-tolerant (e.g. honor a context you close).

### 4.2 Panic handling

By default Bubble Tea recovers from panics in the main loop *and* in command goroutines, restores the terminal, prints the stack trace (with `\r\n` line endings so it's readable in raw mode), and returns `ErrProgramPanic` wrapped in `ErrProgramKilled`. `WithoutCatchPanics()` disables this — which leaves the terminal in raw mode on panic, so use it only for debugging.

If `TEA_DEBUG` is truthy, the panic and stack are also written to `bubbletea-panic-<unixtime>.log`.

---

## 5. The Renderer

### 5.1 Cursed renderer (default)

`cursed_renderer.go` implements the production renderer. It is **cell-based**: it maintains a screen buffer (`ultraviolet.ScreenBuffer`), draws the View's styled content into it, and diffs against the last frame to emit the minimal set of escape sequences. Highlights:

- **Frame-rate–driven flush.** A ticker flushes at the configured FPS (default `defaultFPS = 60`, hard cap `maxFPS = 120`). Frames with no changes are skipped (`viewEquals`).
- **Inline vs. alt screen.** Inline mode renders relative to the cursor and grows/shrinks with content height; alt-screen mode uses a fixed full-window buffer and forces full redraws on resize.
- **Synchronized output (mode 2026).** On terminals known to support it, updates are wrapped in synchronized-output sequences to render atomically (no tearing). Apple Terminal and unreliable SSH sessions are excluded (`shouldQuerySynchronizedOutput`).
- **Unicode core (mode 2027).** When available, grapheme-width mode is enabled for correct wide-character/emoji widths.
- **Cursor-movement optimizations.** Hard tabs and backspace are used to shorten cursor moves where termios indicates support; scroll optimization is disabled on Windows due to terminal bugs.
- **Keyboard protocol negotiation.** Emits `modifyOtherKeys` and Kitty keyboard sequences; resets them when switching between main and alt screens (which have separate keyboard registries).
- **Native progress bar, window title, FG/BG/cursor colors, bracketed paste, focus reporting, mouse modes** are all reconciled by diffing the new `View` against the last one and emitting only the deltas.

### 5.2 Nil renderer

`WithoutRenderer()` installs `nilRenderer`, a no-op that discards all drawing. Output and logs then go straight to the writer with no redraw logic — for daemon-style programs or a non-interactive fallback when output isn't a TTY.

---

## 6. Configuration Reference

Bubble Tea has **no config files**. All configuration is via `ProgramOption` values passed to `NewProgram`, plus two environment variables read by the library.

### 6.1 Program options

| Option | Effect | Default |
|---|---|---|
| `WithContext(ctx)` | Cancel the program from outside; cancellation yields `ErrProgramKilled`. | `context.Background()` |
| `WithOutput(w io.Writer)` | Where to render. | `os.Stdout` |
| `WithInput(r io.Reader)` | Where to read input. Pass `nil` to disable input entirely. | `os.Stdin` (opens TTY if stdin isn't one) |
| `WithEnvironment(env []string)` | Environment used for terminal/color detection (essential over SSH). | `os.Environ()` |
| `WithColorProfile(p)` | Force a color profile instead of auto-detecting. | auto-detected |
| `WithWindowSize(w, h)` | Set initial size (great for testing/non-interactive runs). | queried from the TTY |
| `WithFPS(n)` | Max render frame rate. `<1` → 60; `>120` → 120. | 60 |
| `WithFilter(func(Model, Msg) Msg)` | Intercept/rewrite/drop messages before the model sees them. | none |
| `WithoutSignalHandler()` | Don't install the SIGINT/SIGTERM handler. | handler installed |
| `WithoutSignals()` | Ignore OS signals entirely (mainly for tests). | signals honored |
| `WithoutCatchPanics()` | Disable panic recovery (leaves terminal raw on panic). | panics caught |
| `WithoutRenderer()` | Disable rendering (use the architecture headless). | renderer on |

### 6.2 Environment variables (read by the library)

| Variable | Read at | Effect |
|---|---|---|
| `TEA_TRACE` | `NewProgram` | If set to a path, Bubble Tea opens it and writes an internal debug trace log there. |
| `TEA_DEBUG` | panic recovery | If truthy, panic details + stack are written to `bubbletea-panic-<unixtime>.log`. |

> [!CAUTION]
> **Negative knowledge:** `TEA_LOGFILE`, `HELP_DEBUG`, `BUBBLETEA_LOG`, and `EDITOR` appear in the codebase, but they are read by the **example programs**, not by the Bubble Tea library. Do not treat them as framework configuration. The library reads only `TEA_TRACE` and `TEA_DEBUG`.

### 6.3 Removed in v2 (negative knowledge)

These v1 APIs **no longer exist**. They are listed so you don't reach for them or document them as available:

- **Options:** `WithAltScreen`, `WithMouseCellMotion`, `WithMouseAllMotion`, `WithReportFocus`, `WithoutBracketedPaste`, `WithInputTTY`, `WithANSICompressor`.
- **Commands:** `EnterAltScreen`/`ExitAltScreen`, `EnableMouseCellMotion`/`EnableMouseAllMotion`/`DisableMouse`, `HideCursor`/`ShowCursor`, `EnableBracketedPaste`/`DisableBracketedPaste`, `EnableReportFocus`/`DisableReportFocus`, `SetWindowTitle`.
- **Methods:** `p.Start()`, `p.StartReturningModel()`, `p.EnterAltScreen()`/`ExitAltScreen()`, `p.Enable/DisableMouse…()`, `p.SetWindowTitle()`.
- **Renames:** `Sequentially` → `Sequence`; `WindowSize()` (Cmd) → `RequestWindowSize` (Msg).

All terminal-feature toggles moved onto the `View` struct (`AltScreen`, `MouseMode`, `WindowTitle`, `Cursor`, `ReportFocus`, `DisableBracketedPasteMode`, `KeyboardEnhancements`).

---

## 7. Terminal Compatibility & Platform Behavior

Bubble Tea negotiates capabilities at runtime and degrades gracefully.

| Concern | Behavior |
|---|---|
| **Raw mode** | Entered via `x/term`; restored on exit, suspend, and `Exec`. |
| **Windows** | Uses the Console VT API (`ENABLE_VIRTUAL_TERMINAL_INPUT/PROCESSING`). No `SIGWINCH`, so continuous resize events aren't available (resize handled differently from Unix). |
| **Unix/BSD** | Resize via `SIGWINCH`; cursor-movement optimizations from termios flags. |
| **Color** | Auto-detected (`colorprofile.Detect`); downsampled to the terminal's capability. Query/upgrade via `RequestCapability("RGB"/"Tc")`. |
| **Synchronized output (2026)** | Enabled on capable terminals; **excluded** on Apple Terminal.app and unreliable SSH sessions. |
| **Apple Terminal.app** | Limited: alt screen and capability queries are quirky; treated conservatively in `shouldQuerySynchronizedOutput`. |
| **Kitty keyboard protocol** | Used for enhanced key reporting (release/repeat, shifted/base codes); ignored by terminals that don't support it. |
| **Suspend (Ctrl+Z)** | Supported on Unix (`SIGTSTP`); **not supported on Windows** (`suspendSupported = false`). |

> [!TIP]
> Recommend modern terminals (Ghostty, kitty, WezTerm, Alacritty, iTerm2, Windows Terminal) to users who need alt screen, true color, synchronized output, and key-release events. Test the same UI in Terminal.app and a basic `xterm` to confirm graceful degradation.

---

## 8. Security Analysis

Bubble Tea has a deliberately small attack surface — it is an in-process library with no network, no persistence, and no privileged operations. Still, integrators should be aware of:

| Area | Assessment |
|---|---|
| **Network** | None. The library opens no sockets. (Your *commands* may; that's your code, e.g. the HTTP example in the commands tutorial.) |
| **Filesystem** | Minimal and opt-in: `LogToFile`/`LogToFileWith` open a log file with mode `0o600`; panic logs (`TEA_DEBUG`) and trace logs (`TEA_TRACE`) write to disk. Nothing else is read or written. |
| **External processes** | `Exec`/`ExecProcess` run arbitrary commands you specify, with the terminal handed over. **Validate any user-influenced command/path** before passing it to `exec.Command` (the examples disable the gosec lint with `//nolint:gosec` precisely because the command is developer-controlled). |
| **Clipboard** | OSC52 read/write (`SetClipboard`, `ReadClipboard`, primary selection). Reading the clipboard exposes whatever the user has copied; treat that content as untrusted input. OSC52 isn't supported in all terminals. |
| **Raw escape sequences** | `Raw(...)` writes sequences directly to the terminal. Never build these from untrusted input — a hostile sequence could manipulate the user's terminal. |
| **Environment over SSH** | `WithEnvironment` lets you pass a remote client's env. Treat `TERM` and friends as untrusted; the library already handles unknown values defensively. |
| **Raw mode & signal handling** | The library installs SIGINT/SIGTERM handling and restores terminal state on exit/panic. If you disable panic catching (`WithoutCatchPanics`) and panic, the terminal is left in raw mode — a usability/DoS-of-session risk, not a privilege issue. |
| **Secrets** | The framework stores none. Anything sensitive in your model stays in your process memory. |

> [!WARNING]
> The highest-risk integration points are `Exec`/`ExecProcess` (command injection if you interpolate untrusted input) and `Raw` (terminal-sequence injection). Both require *you* to supply the dangerous value, so the mitigation is input validation in your application code.

---

## 9. Operations

### 9.1 Building and shipping

A Bubble Tea program is a normal Go binary. Cross-compile with `GOOS`/`GOARCH`; ship the single artifact.

```bash
GOOS=linux  GOARCH=amd64 go build -o myapp-linux  .
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin .
GOOS=windows GOARCH=amd64 go build -o myapp.exe   .
```

The repo itself releases via **GoReleaser** (`.goreleaser.yml` includes the shared Charm config) and runs lint/test through a **Taskfile**:

```bash
task lint   # golangci-lint run
task test   # go test -race -count 4 -cpu 1,4 ./...
```

### 9.2 Running over SSH

Serve TUIs remotely by wiring input/output to the SSH session and passing the client environment:

```go
pty, _, _ := sess.Pty()
environ := append(sess.Environ(), "TERM="+pty.Term)
p := tea.NewProgram(model,
	tea.WithEnvironment(environ),
	tea.WithInput(sess),
	tea.WithOutput(sess),
)
```

Pair with **Wish** for the SSH server, auth, and session plumbing.

### 9.3 Graceful shutdown & external control

- Exit cleanly with the `tea.Quit` command (from `Update`) or `p.Quit()` (from outside).
- `p.Kill()` forces immediate teardown (returns `ErrProgramKilled`, skips the final render).
- `WithContext(ctx)` lets orchestration cancel the program; cancellation surfaces as `ErrProgramKilled`.
- Use `p.Wait()` to block until shutdown completes when coordinating multiple goroutines.

> [!CAUTION]
> Known issue #1689: calling `Kill()` *immediately* after `Run()` can race. Prefer letting the program start, then `Quit()`/`Kill()`; or coordinate via a channel/`WithContext`.

### 9.4 Inspecting the runtime

- **Trace log:** set `TEA_TRACE=/path/to/trace.log` to capture an internal output trace.
- **Panic log:** set `TEA_DEBUG=1` to write `bubbletea-panic-*.log` on panic.
- **App log:** use `tea.LogToFile("debug.log", "debug")` and `tail -f` it (you can't log to stdout — the TUI owns it).

---

## 10. Error & Message Reference

### 10.1 Sentinel errors from `Run()`

| Error | Meaning |
|---|---|
| `ErrProgramPanic` | The program recovered from a panic (wrapped inside `ErrProgramKilled`). |
| `ErrProgramKilled` | The program was killed (`Kill()`, context cancel, or internal teardown). |
| `ErrInterrupted` | The program received SIGINT or an `InterruptMsg`. |
| `bubbletea: InitialModel cannot be nil` | `NewProgram` was given a `nil` model. |

Other wrapped errors surface from terminal setup/teardown (opening the TTY, entering raw mode, restoring console state, getting terminal size) and from renderer flush/close paths — all prefixed `bubbletea:` or describing the failing terminal operation.

### 10.2 Framework messages (selected)

| Message | Notes |
|---|---|
| `QuitMsg` / `SuspendMsg` / `ResumeMsg` / `InterruptMsg` | Lifecycle control (send via `Quit`/`Suspend`/`Interrupt`). |
| `WindowSizeMsg{Width, Height}` | Auto-sent at startup and on resize. |
| `KeyPressMsg` / `KeyReleaseMsg` (+ `KeyMsg` interface) | Keyboard events. |
| `MouseClickMsg` / `MouseReleaseMsg` / `MouseWheelMsg` / `MouseMotionMsg` (+ `MouseMsg` interface) | Mouse events. |
| `PasteMsg` / `PasteStartMsg` / `PasteEndMsg` | Bracketed paste. |
| `FocusMsg` / `BlurMsg` | Focus reporting. |
| `ColorProfileMsg`, `ForegroundColorMsg`, `BackgroundColorMsg`, `CursorColorMsg`, `CursorPositionMsg` | Color/cursor introspection (`IsDark()` on color messages). |
| `EnvMsg` | Program environment (SSH-aware). |
| `KeyboardEnhancementsMsg` | Terminal keyboard-enhancement support. |
| `ModeReportMsg`, `CapabilityMsg`, `TerminalVersionMsg`, `ClipboardMsg`, `RawMsg` | Terminal capability/mode introspection and raw I/O. |

---

## 11. Code Quality & Conventions

| Aspect | Observation |
|---|---|
| **Linting** | Strict `golangci-lint` config (`.golangci.yml`): `gosec`, `errcheck`-style `wrapcheck`, `revive`, `prealloc`, `nestif`, `exhaustive`, `gofumpt`, `goimports`, and more. Generated/false-positive exclusions are scoped narrowly. |
| **Testing** | Race-enabled, multi-CPU test runs (`go test -race -count 4 -cpu 1,4`). Golden-file tests (`x/exp/golden`) assert exact terminal output for view/screen behavior (`testdata/*.golden`). Several tests explicitly reproduce fixed bug numbers (e.g. mouse-vs-flush race #1690). |
| **Error wrapping** | Consistent `%w` wrapping with a `bubbletea:` prefix on terminal-facing errors. |
| **Build tags** | Clean per-OS separation (`tty_unix.go`/`tty_windows.go`, `termios_*.go`, `signals_*.go`) keeps platform code isolated. |
| **Concurrency discipline** | The renderer guards shared state with a mutex; the program serializes model updates through a single loop. Command goroutines are deliberately fire-and-forget. |
| **Docs** | Extensive package and symbol doc comments with runnable examples (a minor inconsistency around `Init`'s example signature, noted in §2.2). |

---

## 12. Potential Improvements & Known Issues

From the issue tracker and source review at `a23da80`:

- **`Kill()`-after-`Run()` data race (#1689, open).** Tighten the startup/teardown handshake so an immediate kill is safe.
- **Textarea infinite loop on `alt+left` with empty input (#1652, open).** A component-level edge case (Bubbles) exposed through Bubble Tea input.
- **Emoji/flag width breaks tables (#1646, open) & hard-tab cursor optimization misaligns columns (#1614, closed).** Display-width correctness across terminals remains the thorniest area; the Unicode-core (2027) negotiation mitigates but doesn't fully solve it.
- **Renderer mangles output on some terminals (#1564, open) / sneaky rendering (#1671, closed).** Long tail of terminal-specific rendering quirks; the cell renderer + synchronized output reduces but doesn't eliminate them.
- **Garbage chars on too-early quit (#1590, open).** Ensure all queued sequences flush before exit when the program quits very quickly.
- **`examples/go.mod` `replace` directive breaks `go run …/examples/...@latest` (#1681, open).** Packaging nit for the examples module.
- **Proposed tooling (#1655 devtools inspector, #1654 testing framework).** Community interest in a Bubble Tea inspector and an official testing harness — promising directions for operability.

> [!NOTE]
> None of these are security defects; they are rendering-correctness and lifecycle edge cases inherent to driving heterogeneous terminals. Track the open issues for your target terminals before relying on bleeding-edge features (key release, synchronized output, native progress bars).

---

## 13. Extending & Contributing

- **Build a component:** implement the same `Init`/`Update`/`View` shape as a sub-model and expose it for embedding — the Bubbles library is the reference implementation pattern.
- **Add terminal features:** they generally surface as (a) a new `View` field reconciled in `cursed_renderer.go`, plus (b) a message type translated in `input.go`. Follow the existing diff-and-emit pattern.
- **Custom renderer:** implement the unexported `renderer` interface (`render`, `flush`, `resize`, `setColorProfile`, `insertAbove`, `onMouse`, …) if you need a bespoke backend; `nilRenderer` is the minimal template.
- **Local development:** use the `Taskfile` (`task lint`, `task test`). Tests are race-enabled with golden files — regenerate goldens deliberately, never blindly.
- **Contribution entry point:** the repo's `contribute` page (linked from the README). Match the strict lint config and add golden/race tests for rendering changes.

The Admin-level takeaway: Bubble Tea is small, composable, and terminal-faithful. Its complexity lives almost entirely in the renderer and per-terminal negotiation; the application-facing API is a tight, well-tested MVU core that you extend by composition, not configuration.

---

_Generated June 2026 from charmbracelet/bubbletea at a23da80. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
