Status: implemented — the migration landed in the same PR as this spec; §13 records where the implementation deviates from the plan below.
Audited at commit f1eaf2d; upstream versions checked against the Go module proxy and charm.land docs on 2026-08-25.
Scope: moving the Charm stack from v1 (github.com/charmbracelet/*) to v2 (charm.land/*/v2) across every TUI chapter — tui.go, tui_picker.go, tui_querybuilder.go, tui_viewer.go, ai_tui.go, charts.go, self_update.go — plus their tests. Everything outside the TUI layer (restish, table rendering, agent mode, output contracts) is untouched by construction.
Recommendation: migrate, in one atomic PR, released as a routine patch version.
Bubble Tea v2 went stable in February 2026 and the entire ecosystem this CLI sits on has followed: bubbles, lipgloss, huh, and ntcharts all have stable v2 releases compatible with each other. The v1 line is frozen — bubbles v1.0.0 was explicitly published as an "honorary" final release before v2, and huh's v1 line stopped at v1.0.0 — so staying on v1 means no upstream fixes for the exact components dci ai and the interactive viewer are built on.
Unlike the restish v2 question (issue #20, AGENTS.md), this is not a rewrite. Restish v2 deletes the in-process library model this CLI is built on; Bubble Tea v2 keeps the model — the same Elm loop, the same Model/Update/View architecture, the same in-process library — and changes API surface: import paths, the key/mouse message types, View()'s return type, and how terminal features (alt screen, mouse) are declared. Every change lands inside our own ~2,500 lines of TUI code and ~2,300 lines of TUI tests, all in package main — the two Bubble Tea programs and the huh forms behind the tuiActive() gate, plus charts.go, whose rendering runs outside any program and is gated by its own table-output/color checks rather than tuiActive() (§7.6), so its downsampling change gets its own validation. The port is mechanical for most call sites, with a handful of genuinely changed idioms called out in §6.
Estimated effort: 2–4 focused days including cross-terminal dogfooding. Risk is contained (§9); the biggest single unknown is ntcharts v2's pending upstream patches (§9.4).
| Question | Answer |
|---|---|
| Is v2 stable? | Yes — bubbletea v2.0.9, bubbles v2.2.1, lipgloss v2.0.6, huh v2.0.3, ntcharts v2.2.0, all stable tags |
| Is this restish-v2-shaped? | No — same in-process model, API migration only |
| Can it be done incrementally? | Not usefully — one package main shares tea.Cmd/lipgloss.Style values across files (§10.1) |
| Does restish constrain us? | No — restish depends on glamour/termenv, not on bubbletea/bubbles/huh (§5) |
| User-facing changes? | None intended; rendering fidelity should improve (§4) |
The current direct Charm-stack dependencies (go.mod):
| Module | Pinned | Used by |
|---|---|---|
github.com/charmbracelet/bubbletea v1.3.10 |
tea programs |
tui_viewer.go, ai_tui.go |
github.com/charmbracelet/bubbles v1.0.0 |
table, textarea, viewport, spinner | tui_viewer.go, ai_tui.go |
github.com/charmbracelet/huh v1.0.0 |
one-shot forms | tui.go, tui_picker.go, tui_querybuilder.go, self_update.go |
github.com/charmbracelet/lipgloss v1.1.1-pre |
styles everywhere | all TUI files, charts.go |
github.com/NimbleMarkets/ntcharts v0.5.1 |
stacked bar chart | charts.go |
github.com/muesli/termenv v0.16.0 |
one call: chartColorCapable |
charts.go |
Two full Bubble Tea programs exist: the interactive table viewer (tableViewerModel, alt screen on stderr) and the dci ai session (aiModel, alt screen, optional mouse capture). Everything else is huh forms, lipgloss string styling, and a hand-rolled ANSI spinner (startTUISpinner) that uses no framework at all.
glamour (markdown in dci ai) is imported directly but resolved at v1.0.0 via restish's requirement — see §5 for why it stays.
- bubbletea v2.0.0 released 2026-02-24; current v2.0.9 (2026-08-19). Nine patch releases in six months — actively maintained, and the churn is settling.
- bubbles v2.2.1 (2026-08-24), lipgloss v2.0.6 (2026-08-11), huh v2.0.3 (2026-03-10) — all built on bubbletea v2.
- ntcharts v2.2.0 (2026-05-28) — the maintainers keep the bubbletea-v1 library on
mainand develop v2 on thev2branch, backporting fixes to both. - Charm moved to vanity import paths:
charm.land/bubbletea/v2,charm.land/bubbles/v2,charm.land/lipgloss/v2,charm.land/huh/v2. Thegithub.com/charmbracelet/*/v2paths resolve to the same code, butcharm.landis the documented canonical form — use it. - Go requirement: bubbletea v2 needs go ≥ 1.25.0, huh v2 needs go ≥ 1.25.8. Our go.mod declares 1.25.13 — no constraint.
- v1 status: bubbles v1.0.0 and huh v1.0.0 were cut as final v1 releases immediately before the v2 launch. New component work (and the fixes that come with it) lands on v2 only.
Each of these maps to a workaround or a comment in the current code:
- The OSC-11 swallowing bug is fixed at the root.
aiMarkdownStyle(ai_tui.go:1531) resolves the glamour style before the program starts because "Bubble Tea's input reader swallows the terminal's reply" to a background query, garbling the textarea. v2 makes background-color detection a first-class async protocol (tea.RequestBackgroundColor→tea.BackgroundColorMsg) handled by the input loop itself. We keep the pre-program detection (§7.4) but the entire failure class the comment warns about is gone upstream. - Alt-screen smearing.
aiFitFrame's long comment (ai_tui.go:1770) documents the v1 renderer skipping lines whose content it believes unchanged, leaving frames the terminal disturbed "smeared until every cell is repainted" — hence the ctrl+l guidance in/help. v2's rewritten renderer owns the full cell grid and enables synchronized output, which directly targets this.aiFitFramestays (its width-clamping half is still ours), but the repair-gesture UX debt should shrink. - Grapheme-correct width. v2 auto-enables Unicode mode 2027 handling; emoji or CJK in customer names, report titles, and status snippets stop mis-measuring. Our
aiTrimTo/aiStatusRowcell arithmetic gets more honest inputs fromlipgloss.Width. - A real terminal cursor is available. The
dci aitextarea currently simulates a cursor (v1 has no alternative). v2 can drive the actual terminal cursor — better for IMEs and screen readers. Deliberately deferred to a follow-up (§10.2), but only reachable from v2. - The v1 line is frozen. Every future bubbles/huh fix — table, textarea, viewport, filtering — lands on v2. The longer we wait, the more the versions we pin fall behind, and the CLI releases often.
- Windows input handling was rewritten in v2. We ship via WinGet and Scoop with zero Windows TUI coverage in CI; inheriting upstream's Windows fixes matters more for us than for projects that can test it.
What we do not gain: no feature in the current TUI-SPEC/AI-SPEC surface requires v2. This is a maintenance-position migration, not a feature unlock — which is exactly why it should happen while the diff is mechanical, not later under pressure.
| Change | From | To |
|---|---|---|
| swap | github.com/charmbracelet/bubbletea v1.3.10 |
charm.land/bubbletea/v2 v2.0.9 |
| swap | github.com/charmbracelet/bubbles v1.0.0 |
charm.land/bubbles/v2 v2.2.1 |
| swap | github.com/charmbracelet/lipgloss v1.1.1-pre |
charm.land/lipgloss/v2 v2.0.6 |
| swap | github.com/charmbracelet/huh v1.0.0 |
charm.land/huh/v2 v2.0.3 |
| swap | github.com/NimbleMarkets/ntcharts v0.5.1 |
github.com/NimbleMarkets/ntcharts/v2 v2.2.0 |
| promote | github.com/charmbracelet/colorprofile (indirect) |
direct (replaces the termenv comparison, §7.6) |
| drop direct | github.com/muesli/termenv v0.16.0 |
stays indirect via restish/glamour |
Restish does not constrain this migration. go mod graph confirms restish v0.21.2 pulls glamour and termenv — not bubbletea, bubbles, or huh. Since Go major versions are distinct module paths, our v2 modules coexist with whatever v1 code restish's tree wants; nothing restish compiles against changes.
Consequences of coexistence:
- bubbletea v1, bubbles v1, and huh v1 leave the module graph entirely (we were their only path in).
- lipgloss v1 stays in the binary via restish → glamour v1. Two lipgloss majors ship side by side. That is correct-by-construction (separate packages), costs binary size only, and ends whenever restish's stack moves — not ours to force.
- glamour stays at v1 on purpose. A glamour v2.0.1 exists, but restish keeps glamour v1 in the tree regardless; moving our one call site (
renderAIMarkdown) to v2 would add a second glamour instead of replacing the first. Glamour renders markdown to a string and touches no Bubble Tea types, so it is severable from this migration. Revisit alongside any restish bump. - Measure the release binary before/after in the PR (the goreleaser matrix already builds every platform). Expect low-single-digit MB growth from the dual lipgloss and the new renderer; flag it in the PR body.
The changes that actually touch our code, per symbol. Items marked ⚠ are behavior changes, not just renames.
| v1 (call sites) | v2 |
|---|---|
import tea "github.com/charmbracelet/bubbletea" |
import tea "charm.land/bubbletea/v2" |
Init() tea.Cmd |
unchanged |
Update(tea.Msg) (tea.Model, tea.Cmd) |
unchanged |
View() string |
⚠ View() tea.View — build the frame string as today, return tea.NewView(s) with feature fields set |
tea.NewProgram(m, tea.WithAltScreen()) |
⚠ option gone — set view.AltScreen = true in View() (declarative, per-frame) |
tea.WithMouseCellMotion() option, tea.EnableMouseCellMotion / tea.DisableMouse commands |
⚠ all gone — set view.MouseMode = tea.MouseModeCellMotion in View() when m.mouseOn |
tea.WithOutput(os.Stderr), tea.WithInput(os.Stdin) |
kept (signatures now take file handles — verify at impl) |
case tea.KeyMsg: / func(msg tea.KeyMsg) |
⚠ tea.KeyPressMsg (v2 KeyMsg is an interface over press/release; we only ever want presses) |
msg.Type == tea.KeyEnter (and Esc, Up, Down, Tab, Backspace, PgUp/PgDn, CtrlC, CtrlL, Space) |
msg.Code == tea.KeyEnter etc., or keep the existing msg.String() switches |
msg.Type == tea.KeyRunes + string(msg.Runes) |
⚠ msg.Text != "" + msg.Text (covers space too — v1 needed KeySpace special-cased, v2 space has Text == " " but String() == "space") |
tea.KeyType (test helper parameter) |
key codes are runes; helpers take rune / construct tea.KeyPressMsg{Code: ..., Text: ...} |
case tea.MouseMsg: (forwarded to viewport) |
v2 splits into MouseClickMsg/MouseWheelMsg/MouseMotionMsg/MouseReleaseMsg under a tea.MouseMsg interface — keep the interface case, forward as today |
tea.WindowSizeMsg, tea.Quit, tea.QuitMsg, tea.Batch, tea.Cmd, tea.Msg |
unchanged |
tea.ClearScreen (ctrl+l repair) |
still exists — keep |
⚠ Key-name audit: every msg.String() comparison in the codebase ("esc", "enter", "ctrl+c", "pgup", "q", "y", "/", …) must be re-verified against v2's naming table in one sitting — v2 renamed at least space (" " → "space"). Grep inventory: ~40 comparisons across tui_viewer.go and ai_tui.go.
| v1 | v2 |
|---|---|
viewport.New(80, 20) |
viewport.New(viewport.WithWidth(80), viewport.WithHeight(20)) |
m.view.Width = w / m.view.Height = h |
m.view.SetWidth(w) / m.view.SetHeight(h) |
HalfViewUp() / HalfViewDown() |
HalfPageUp() / HalfPageDown() |
SetContent, GotoBottom, AtBottom |
unchanged |
textarea.New(), Prompt, ShowLineNumbers, CharLimit, SetHeight, SetWidth, KeyMap, Focus |
unchanged |
input.FocusedStyle.CursorLine = … etc., input.BlurredStyle = … |
⚠ styles moved into a Styles struct (Focused/Blurred StyleState): start from textarea.DefaultStyles(isDark), clear the cursor-line band and placeholder exactly as today, apply with input.SetStyles(styles) |
| (virtual cursor implicit) | ⚠ v2 supports the real terminal cursor; call SetVirtualCursor(true) to preserve today's behavior byte-for-byte (§10.2 for the real-cursor follow-up) |
textarea.Blink |
kept (virtual-cursor blink command) |
table.New(table.WithFocused(true)), SetRows/SetColumns/SetWidth/SetHeight/SetCursor/Cursor/Update/View |
shape unchanged — verify option names and default styles at impl |
spinner.New(spinner.WithSpinner(spinner.MiniDot)), spinner.TickMsg, m.spin.Tick |
unchanged |
| v1 | v2 |
|---|---|
import "github.com/charmbracelet/lipgloss" |
import "charm.land/lipgloss/v2" |
NewStyle, Color("2"), Width, JoinVertical/Horizontal, RoundedBorder, MaxWidth, alignment consts |
unchanged (Color now returns color.Color; our usage is unaffected) |
lipgloss.AdaptiveColor{Light, Dark} (chart palette) |
⚠ removed from core — charm.land/lipgloss/v2/compat.AdaptiveColor{Light: lipgloss.Color(l), Dark: lipgloss.Color(d)}; the compat package does its own one-time background detection, matching v1 semantics for code that renders outside a program (exactly what charts.go is) |
lipgloss.HasDarkBackground() |
⚠ lipgloss.HasDarkBackground(stdin, stdout) (bool, error) — explicit, queried once pre-program (fits aiMarkdownStyle's existing design); compat also exposes the v1-style implicit form |
lipgloss.ColorProfile() |
⚠ removed — colorprofile.Detect(os.Stderr, os.Environ()) (§7.6) |
⚠ Downsampling moved out of Style.Render. In v1 the global renderer detected the terminal and every .Render() degraded colors to it. In v2, degradation happens at the program boundary (Bubble Tea downsamples for you) or at an explicit writer (lipgloss.Fprintln, colorprofile.Writer) — a bare fmt.Fprintln(os.Stderr, style.Render(...)) emits whatever color the style holds. Consequences for us in §7.1/§7.6.
| v1 | v2 |
|---|---|
import "github.com/charmbracelet/huh" |
import "charm.land/huh/v2" |
NewForm/NewGroup/NewSelect[T]/NewMultiSelect[T]/NewConfirm/NewOption, Title/Description/Options/Filtering/Filterable/Height/Value/Affirmative/Negative |
unchanged |
WithOutput/WithInput/WithWidth/WithShowHelp, Run, ErrUserAborted |
unchanged |
| themes | ⚠ theme constructors now take an explicit isDark bool (huh.ThemeCharm(isDark)); we use the default theme today — verify the default detects the background sanely on a stderr-rendered form, else pass WithTheme(huh.ThemeCharm(dark)) from one pre-detected value (§7.1) |
| v1 | v2 |
|---|---|
github.com/NimbleMarkets/ntcharts/barchart |
github.com/NimbleMarkets/ntcharts/v2/barchart |
barchart.New(w, h, barchart.WithNoAxis()), BarData, BarValue{Name, Value, Style}, PushAll, Draw, View |
shape unchanged; Style is now a lipgloss v2 style — which is why ntcharts must move in the same commit as lipgloss |
- Import swaps (huh v2, lipgloss v2). All styles here use ANSI colors 1–3 ("1", "2", "3") — safe on every terminal, no downsampling concern.
tuiFormkeepsWithOutput(os.Stderr).WithInput(os.Stdin).WithWidth(tuiWidth()).WithShowHelp(true)unchanged.- Add one process-wide lazily-computed
tuiDarkBackground(vialipgloss.HasDarkBackground(os.Stdin, os.Stderr), defaulting to dark on error) if dogfooding shows huh v2's default theme guessing wrong on stderr forms; wire it asWithTheme(huh.ThemeCharm(dark))intuiForm. Do not add it speculatively. startTUISpinner,spinnerTransport, quips: zero changes (hand-rolled ANSI).
- Import swap only.
huh.ErrUserAborted,Filtering(true),Filterable(true),Height(14),NewConfirmall carry over. Thehuhdegradation contract the picker relies on (renderer failure → cancel path) is unchanged.
runTableViewerProgram: droptea.WithAltScreen(); keepWithOutput/WithInput.View()returnstea.View:v := tea.NewView(frame); v.AltScreen = true; return v.- Filtering-mode key handling: replace the
msg.Typeswitch withmsg.Code(enter/esc/backspace/ctrl+c) andmsg.Textfor typed characters — this removes theKeyRunes, KeySpacespecial case. - Normal-mode handling already switches on
msg.String()— survives modulo the §6.1 key-name audit. bubbles/tablecalls: verify-only.
runAISession: options shrink totea.NewProgram(initial); alt screen and mouse move intoView()(v.AltScreen = true,v.MouseMode = tea.MouseModeCellMotionwhenm.mouseOn)./mouseverb: delete thetea.EnableMouseCellMotion/tea.DisableMousecommands — flippingm.mouseOnis now sufficient, the next frame declares it. The settings persistence stays.Update:tea.KeyMsg→tea.KeyPressMsg(alsohandleKey,handleKeyEntryKey,handlePickerKeysignatures);tea.MouseMsgstays an interface case forwarded to the viewport;spinner.TickMsg,WindowSizeMsg, custom messages unchanged.msg.Type == tea.KeyRunessites (key entry, picker filter, completion trigger):msg.Text.- textarea:
DefaultStyles(dark)+ clear cursor-line band/placeholder +SetStyles;SetVirtualCursor(true);textarea.Blinkstays inInit. - viewport: constructor options;
SetWidth/SetHeightinlayout();HalfPageUp/HalfPageDownin the PgUp/PgDn/ctrl+u/ctrl+d cases. aiMarkdownStyle:lipgloss.HasDarkBackground(os.Stdin, os.Stderr), still resolved once before the program runs; rewrite the comment — the constraint is now design preference, not upstream-bug avoidance.View(): assemble the same string, returntea.NewViewwithAltScreen/MouseMode.aiFitFrame,aiRule,statusLineunchanged.aiDoitLogo's#FC3165: inside the program, v2 downsamples per the detected profile — parity with v1. TheCLICOLOR_FORCE=1child-env contract (aiDispatchEnv) is about the children's rendering and is unaffected here; recheck §7.6 for the chart child case.
- No Charm imports in the implementation file (the selection UI lives in ai_tui.go); only its test file constructs key messages (§8).
- Palette:
lipgloss.AdaptiveColor→compat.AdaptiveColor(values wrapped inlipgloss.Color). The compat package resolves light/dark once at init from the environment — the v1 behavior this code was written against. chartColorCapable: replacelipgloss.ColorProfile() != termenv.Asciiwith acolorprofile.Detect(os.Stderr, os.Environ())comparison (> colorprofile.Ascii— i.e. ANSI or better; verify enum ordering at impl). Drop the termenv import;colorprofilebecomes a direct dependency.- ⚠ Downsampling: the stacked chart's truecolor hex palette is printed with
fmt.Fprintln(os.Stderr, …). Under v1,.Render()degraded to the detected profile; under v2 it will not. Route the chart/legend output through acolorprofile.Writeron stderr (orlipgloss.Fprintln) so 256-color terminals keep getting legal sequences. This also covers thedci aidispatch-child path (DCI_SESSION_RENDER=1,CLICOLOR_FORCE=1on a pipe): confirm during dogfooding that the child's detected profile still upgrades to truecolor for the session transcript — addCOLORTERM=truecolortoaiDispatchEnvif v2's env detection needs it where termenv didn't. - ntcharts v2 import; re-verify the partial-block background-compensation workaround (the
segments[k].Style.Background(...)loop) against v2 — it may be fixed upstream, in which case delete it with a comment pointing at the upstream fix. - asciigraph line chart and
augmentTableViewColumns: untouched.
- Apply §5. Run
go mod tidy; confirm bubbletea/bubbles/huh v1 vanish from go.sum. - AGENTS.md: add one line to Project Context noting the Charm stack is on v2 (
charm.landimport paths) so future agents don't "fix" the imports backwards. - TUI-SPEC.md / AI-SPEC.md are historical design docs — leave them; they describe features, not import paths.
All TUI behavior tests drive Update with hand-built messages and assert on rendered strings — they port mechanically:
tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("s")}→tea.KeyPressMsg{Code: 's', Text: "s"};tea.KeyMsg{Type: tea.KeyEnter}→tea.KeyPressMsg{Code: tea.KeyEnter}(≈25 sites acrosstui_viewer_test.go,ai_tui_test.go,ai_picker_test.go).- The
aiPress(m, tea.KeyType)helper becomesaiPress(m, rune); add anaiType(m, string)sibling if it shortens the diff. View()assertions: add one test helper that extracts the content string fromtea.View(accessor per the v2 docs at impl time) so the existingstrings.Containsassertions survive unchanged.tea.WindowSizeMsgconstruction: unchanged.chartColorCapableis already a test-fakeable var; charts tests need no changes beyond compilation.- huh-based flows are already tested through fakeable vars (
pickerSelectEntry,confirmDestructiveInteractively,maybeRunQueryBuilder) — untouched.
Nothing in the suite exercises a real PTY, so CI proves compilation and model logic; rendering fidelity is on the dogfood checklist (§11).
- Key-name drift (§6.1 audit). A missed rename silently kills one keybinding. Mitigation: the string-comparison audit plus the ported unit tests, which press every binding the specs promise.
- huh v2 theming on stderr. Forms render on stderr while detection conventionally probes stdin/stdout; if the default theme guesses wrong where v1 guessed right, wire the explicit theme (§7.1). Low blast radius — colors, not behavior.
- Renderer regressions on exotic terminals. v2's renderer is a ground-up rewrite; its failure modes will not be v1's. Mitigations: the
DCI_NO_TUIescape hatch already bypasses every TUI path; ctrl+l is kept; dogfood across the checklist terminals before tagging. - ntcharts v2.2.0 ships a
replaceof bubbletea to a fork (github.com/neomantra/bubbletea/v2, "awaiting upstream merges") in its go.mod. Replace directives are ignored in dependencies, so we build it against the real charm.land bubbletea — meaning ntcharts v2 is not tested by its authors in exactly the configuration we compile. Our exposure is minimal —barchartused render-to-string, no tea program — but verify the stacked chart renders correctly and check whether the pending patches touch barchart before pinning. If v2.2.0 misbehaves, the fallback is staying on ntcharts v1 for one release (it needs lipgloss v1 styles, so the palette code would carry a temporary dual-import — ugly but legal) or dropping the stacked view to the asciigraph fallback until upstream settles. Neither fallback blocks the rest of the migration. - Windows. No TUI CI coverage; v2's input rewrite is a net positive long-term but a regression risk at the moment of switching. Dogfood
dci aiand the viewer in Windows Terminal before release. - Version skew during review. Pin exact versions in the PR; do not let unrelated dependency bumps ride along.
huh v2 forms could move first (they only exchange strings with the rest of the code), but bubbles v1's textarea style fields take lipgloss v1 styles while ntcharts v2 takes lipgloss v2 styles — a split migration forces both lipgloss majors as direct imports of the same package with alias gymnastics. In one package main the honest unit is one PR, reviewed commit-by-commit (§11).
- Real terminal cursor in the
dci aiinput (dropSetVirtualCursor(true), positionview.Cursorfrom the layout) — IME and accessibility win. - In-program background detection (
tea.RequestBackgroundColor) replacing the pre-program probe, enabling live light/dark switching. - glamour v2 — bundled with any restish-stack move (§5).
- v2-only niceties (kitty keyboard enhancements for shift+enter multiline input, clipboard OSC52 for
/export-to-clipboard) — note them in the ideas pile, not this migration.
- One PR on this branch, commits ordered for review: (1) go.mod swap + mechanical import renames; (2) bubbletea program changes (
tui_viewer.go,ai_tui.go); (3) charts/lipgloss compat + colorprofile; (4) tests; (5) docs line in AGENTS.md. - Commit prefixes:
chore:for the dependency/migration commits (not user-facing per the changelog conventions),test:/docs:where applicable. If dogfooding surfaces a genuine user-visible fix riding on v2 (e.g. the smear repair), it may earn afix:line of its own so the changelog tells users why the release exists. - Versioning: routine patch bump per the 2026-08-18 policy — this is not a command-surface milestone.
- Dogfood checklist before tagging (macOS Terminal, iTerm2/Ghostty, VS Code terminal, Windows Terminal; light and dark backgrounds; one 256-color TERM):
dci ai: banner, completion popup, picker, destructive y/N, /mouse on+off, /export, ctrl+l, wheel + PgUp/PgDn, narrow-pane resize- zero-arg picker + ambiguity select + destructive confirm (huh forms on stderr)
dci querybuilder end-to-end--table-mode interactive: sort, filter, column scroll, enter-prints-id (stdout cleanliness!)--chartline + stacked on truecolor and 256-color; the same viadci aidispatchDCI_NO_TUI=1and piped/agent-mode runs byte-identical to today (the contract tests should already prove this)
post-release-verify.ymlcovers install-channel sanity as usual.
- Timing: land now, or after the next feature release so the patch release is migration-only? (Spec assumes migration-only.)
- Is the ntcharts fallback posture in §9.4 acceptable, or is "stacked chart blocks the migration" the bar?
- Any known-exotic terminal among users (tmux+screen-256color, PuTTY?) to add to the dogfood list?
Where the implementation deviates from or resolves the plan above:
- Pastes are their own message type in v2 (
tea.PasteMsg), no longer key messages — a gap in §6.1's map. Without handling, pasting an API key into the guided setup or text into the picker filter would silently do nothing.aiModel.handlePasteroutes pastes to whichever text sink owns the keyboard (key entry, picker filter, or the textarea, which handlesPasteMsgitself); answer-only states ignore them. compat.AdaptiveColorwas not used (§6.3 suggested it): the compat package probes the terminal at package init — an OSC round trip on everydciinvocation, keyed on stdout rather than stderr where charts render.charts.goinstead defines a localchartAdaptiveColorresolving lazily through async.OnceValuearoundlipgloss.HasDarkBackground(os.Stdin, os.Stderr), so building a palette never queries the terminal and non-chart invocations never pay for detection.- Styled stderr output outside programs goes through a
colorprofile.Writer(tuiStyledStderrin tui.go): v1'sStyle.Renderdownsampled and stripped implicitly; in v2 that responsibility is the writer's. Routed sites: the login notice, the destructive-confirm box, the update notice, and the stacked chart. This restores NO_COLOR/non-TTY stripping and degrades the truecolor chart palette on 16/256-color terminals. - The §6.1 key-name audit came back clean: only space changed (
"space"vs" "), and no dci binding matched" "viaString()— every existingmsg.String()comparison stands, withtea.KeyEsc→tea.KeyEscapeas the one constant rename in tests. - textarea's virtual cursor is on by default in v2 — the planned
SetVirtualCursor(true)call (§6.2) is unnecessary; the custom styles moved intotextarea.Styles/StyleStateviaSetStylesas planned, keeping the blinking reverse-video cursor. - The §9.4 ntcharts risk did not bite: v2.2.0 compiles and passes the chart tests against upstream
charm.land/bubbletea/v2v2.0.9. The partial-block background-compensation workaround inrenderStackedChartis kept as-is. - huh v2 needed no theme wiring (§7.1's contingency): forms detect the background themselves via
tea.BackgroundColorMsg; confirm on light terminals during dogfooding. - Module graph after
go mod tidy: bubbletea, bubbles, huh, and ntcharts v1 are gone entirely; lipgloss v1 and termenv remain only as restish/glamour indirects, per §5. - Binary size (linux/amd64,
-trimpathdev build): 68.5 MB → 70.4 MB, +1.9 MB (+2.7%) — within §5's expectation. - The §11 dogfood checklist (real terminals, light and dark, Windows Terminal) remains to be run before tagging a release.