From 9943d5f7b73f9265a9c1110db42e7b33aebededc Mon Sep 17 00:00:00 2001 From: jb Date: Fri, 19 Jun 2026 12:28:34 -0300 Subject: [PATCH] fix: render full placeholder text in form inputs bubbles/v2 textinput truncates the placeholder to its first rune when the input width is 0 (the default): it sizes the placeholder buffer to Width()+1 and early-returns after the first character. Set a non-zero width on every text input (defaultInputWidth) and resize inputs responsively to the terminal on WindowSizeMsg via FormStep.SetWidth, so placeholders render in full and inputs fill the available width. Adds a regression test asserting the placeholder renders past its first rune. Co-Authored-By: Claude Opus 4.8 --- internal/tui/form.go | 19 +++++++++++++++++++ internal/tui/form_test.go | 19 ++++++++++++++++++- internal/tui/update.go | 13 +++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/internal/tui/form.go b/internal/tui/form.go index 7af2ef2..4b97da4 100644 --- a/internal/tui/form.go +++ b/internal/tui/form.go @@ -17,6 +17,11 @@ const ( FieldTypeNumber ) +// defaultInputWidth is the visible width of text inputs before the first window-size +// message arrives. It must be > 0 (and ideally >= the longest placeholder) so the +// placeholder renders in full; it is overridden responsively via FormStep.SetWidth. +const defaultInputWidth = 50 + type FormField struct { Id string Label string @@ -51,6 +56,9 @@ func NewFormStep(title string, fields []FormField) FormStep { ti := textinput.New() ti.Placeholder = f.Fields[i].Placeholder + // A non-zero width is required for the placeholder to render in full: with the + // default width of 0 the textinput truncates the placeholder to its first rune. + ti.SetWidth(defaultInputWidth) if f.Fields[i].Default != "" { ti.SetValue(f.Fields[i].Default) } @@ -129,6 +137,17 @@ func (f *FormStep) Values() map[string]string { return out } +// SetWidth resizes every text input to w (selects are unaffected). Called on window +// resize so inputs fill the available terminal width and placeholders render in full. +func (f *FormStep) SetWidth(w int) { + for i := range f.Fields { + if f.Fields[i].Type == FieldTypeSelect { + continue + } + f.Fields[i].input.SetWidth(w) + } +} + // View func (f FormStep) View() string { pad := strings.Repeat(" ", padding) diff --git a/internal/tui/form_test.go b/internal/tui/form_test.go index 76602e1..acea513 100644 --- a/internal/tui/form_test.go +++ b/internal/tui/form_test.go @@ -1,6 +1,23 @@ package tui -import "testing" +import ( + "strings" + "testing" +) + +// Guards against the bubbles bug where a text input with width 0 renders only the +// first rune of its placeholder. NewFormStep must set a non-zero width. +func TestPlaceholderRendersBeyondFirstRune(t *testing.T) { + const placeholder = "postgres://user@host/db" + f := NewFormStep("t", []FormField{ + {Id: "url", Label: "URL", Placeholder: placeholder, Type: FieldTypeText}, + }) + + view := f.Fields[0].input.View() + if !strings.Contains(view, "ostgres://user@host/db") { + t.Errorf("placeholder appears truncated; rendered view = %q", view) + } +} func TestFormStepValues(t *testing.T) { f := NewFormStep("Test", []FormField{ diff --git a/internal/tui/update.go b/internal/tui/update.go index 6d0b474..709d8ef 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -33,6 +33,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height + + // Fit inputs to the terminal: subtract the left padding, the "> " prompt and + // the cursor cell, with a sensible floor for very narrow terminals. + inputWidth := msg.Width - (padding * 2) - 4 + if inputWidth < 20 { + inputWidth = 20 + } + for _, f := range []*FormStep{ + &m.loginForm, &m.wireguardForm, &m.appForm, + &m.serverForm, &m.dbForm, &m.certForm, + } { + f.SetWidth(inputWidth) + } case spinner.TickMsg: var cmd tea.Cmd m.spinner, cmd = m.spinner.Update(msg)