Skip to content

cmdiotest: pin promptui baselines for bubbletea migration (reference)#5231

Draft
pietern wants to merge 20 commits into
mainfrom
cmdiotest
Draft

cmdiotest: pin promptui baselines for bubbletea migration (reference)#5231
pietern wants to merge 20 commits into
mainfrom
cmdiotest

Conversation

@pietern
Copy link
Copy Markdown
Contributor

@pietern pietern commented May 11, 2026

Note

Reference branch only — not for merge. Parked on origin so the bubbletea-based prompt rewrite can pull individual tests as it lands matching behavior.

Summary

  • 41 pty-based baselines under libs/cmdio/cmdiotest/ pinning promptui's current RunPrompt, RunSelect, and Secret behavior (key handling, filter typing, navigation, validation, masking, templates).
  • Snapshots are captured via vt10x golden files in termtest/, so a future implementation can be diffed step-by-step, not just at submit.

pietern added 20 commits May 8, 2026 13:58
Adds libs/cmdio/cmdiotest, an external test package that drives
cmdio.Select / SelectOrdered / Secret / RunPrompt through a real pty
and snapshots the rendered screen via vt10x. 26 tests cover the
prompt matrix as it behaves on origin/main today (still promptui
under the hood): basic navigation, scrolling, filter typing and
no-match, vim-key fallthrough, Esc/Tab inertness, Ctrl+C with and
without filter text, single-item rendering, long descriptions,
cursor editing keys (Home/End/Ctrl+W/Ctrl+U), 20-item filter
scroll, UTF-8 input, and the empty-list rejection path.

Lives on its own branch as a one-shot test capsule: never merged
to main, copied into the bubbletea migration branch to verify each
widget swap leaves the goldens byte-identical. Once promptui is
gone the harness is rewritten on charmbracelet/x/exp/teatest,
which lets us drop creack/pty and x/term entirely.

Adds three test-only deps with license annotations:
- creack/pty (MIT)
- hinshun/vt10x (MIT)
- golang.org/x/term (BSD-3-Clause)

Co-authored-by: Isaac
Adds TestSelectBaseline_SelectedTemplate, the only golden test that
exercises the post-submit render path of cmdio.RunSelect. The existing
select baseline tests all go through cmdio.Select / cmdio.SelectOrdered,
which set HideSelected:true and skip the Selected template branch.
Real callers (cmd/auth/profile_picker.go,
libs/databrickscfg/profile/select.go,
libs/databrickscfg/cfgpickers/clusters.go) all set Selected:; without
this test, breaking the post-submit render or the Selected template
goes undetected.

Co-authored-by: Isaac
…Delete-as-EOF separately

The Delete key (\x1b[3~) maps to chzyer/readline's CharDelete (=4), the
same rune as Ctrl+D. CharDelete's handler treats a non-empty buffer as
forward-delete and an empty buffer as EOF. Promptui's listener resets
readline's buffer to empty after every keystroke (cur is the source of
truth), so from readline's perspective the buffer is always empty when
Delete arrives — every Delete press takes the EOF path and exits the
prompt. The Delete step in TestPromptBaseline_CursorEditing was
unintentionally pinning post-exit screen residue, which presented as a
load-dependent flake (vt10x snapshot timing relative to exit cleanup).

Replace the Delete step with Backspace, which goes through promptui's
Cursor.Listen and works correctly. The Ctrl+W and Ctrl+U steps stay —
they're known no-ops in promptui (no case in Cursor.Listen), and the
post-Backspace goldens after them being identical pins that.

Add TestPromptBaseline_DeleteKeyExits to document the Delete-as-EOF
behavior so any future change (e.g. a promptui or readline upgrade
that splits the two keys) is intentional.

Co-authored-by: Isaac
vt10x's Write decodes the input as UTF-8 and treats partial-rune bytes
at the tail as invalid: it logs each leading byte of an incomplete
sequence as "invalid utf8 sequence" and reports written-1 back. Since
that count doesn't capture how many bytes the parser actually advanced
past, the caller can't reconstruct the dropped bytes for the next call.

When a pty read happens to split a multi-byte rune (✔, █, etc.) under
load, the lost bytes corrupt vt10x's downstream parser state and the
escape sequences that follow can land on the wrong line — producing a
screen that diverges from the real byte stream and a flaky golden.

Fix the pump to detect a partial UTF-8 sequence at the end of each
read using utf8.RuneStart / utf8.FullRune, hold those bytes back, and
prepend them to the next read. Bytes from any genuinely invalid
sequence in the middle are still passed through; vt10x logs and skips
them (which doesn't cause divergence).

Co-authored-by: Isaac
Adds two tests that pin the post-submit screen for cmdio.RunPrompt:

- HideEntered=false (the default): the success template is shown with
  the entered value (e.g. "Workspace name: hello").
- HideEntered=true: the prompt frame is cleared, leaving no trace of
  the entered value. This is the path used by cmdio.Secret.

Existing prompt and secret tests verified the typing UX and the
returned value but never snapshotted the post-Enter state, so neither
HideEntered branch was pinned.

Co-authored-by: Isaac
Adds TestPromptBaseline_AltKeyNoop. Alt+f is the readline binding for
"move forward by word", which chzyer/readline does process — it calls
o.buf.MoveToNextWord and fires the listener with key=MetaForward. But
promptui's Cursor.Listen has no case for MetaForward and the listener
wrapper returns (nil, 0, true), which makes readline overwrite its
buffer with empty. Net effect on the user-visible state (promptui's
own cur): nothing changes.

The same shape applies to Alt+b, Alt+d, Alt+Backspace, and other
modified keys promptui doesn't explicitly handle. Pinning Alt+f
covers the class — if a future promptui or readline change starts
letting these slip through (insert, move cursor, etc.), the goldens
diverge.

Co-authored-by: Isaac
Adds TestPromptBaseline_CtrlFCtrlB and the missing KeyCtrlB / KeyCtrlF
constants in termtest. chzyer/readline maps Ctrl+F to CharForward and
Ctrl+B to CharBackward — the same runes the right and left arrow keys
decode to — and promptui's Cursor.Listen dispatches both via its
KeyForward / KeyBackward cases. So the emacs-style bindings are
de-facto aliases for the arrow keys; this test pins that equivalence
so a future implementation that drops Ctrl+F / Ctrl+B handling fails
the goldens.

Co-authored-by: Isaac
Adds two select baselines and the missing KeyCtrlN / KeyCtrlP
constants in termtest:

- TestSelectBaseline_CtrlNCtrlP: small list, asserts that Ctrl+N and
  Ctrl+P move the highlighted item down and up by one — the same as
  the down and up arrow keys (promptui exposes both as
  readline.CharNext / readline.CharPrev).
- TestSelectBaseline_CtrlFCtrlB: 12-item list against promptui's
  default 5-row window, asserts that Ctrl+F and Ctrl+B page the
  selection (skip ~5 items) rather than moving by one. Promptui maps
  these to KeyForward / KeyBackward, distinct from KeyNext / KeyPrev,
  and the select widget treats them as page-down / page-up.

These pin behavior the existing baselines didn't cover (DownEnter and
Scroll only exercise the arrow keys, never the emacs-style aliases).

Co-authored-by: Isaac
Adds three Select baselines that close gaps surfaced by an audit of
promptui's select.go key handling:

- TestSelectBaseline_VimNavOutsideSearch: pins j/k/h/l navigation
  when StartInSearchMode=false. The existing TestSelectBaseline_VimKeys
  runs with search mode on, so its goldens record letters going into
  the filter — the actual nav branch (key == 'j' && !searchMode etc.)
  was never pinned. Real callers that hit this branch with small
  lists: cmd/auth/resolve.go, cmd/auth/profile_picker.go.

- TestSelectBaseline_SlashEntersSearch: pins the "/" toggle into
  search mode. cmdio.SelectOrdered sets StartInSearchMode=true so
  the toggle path was unreachable from the existing wrappers. Same
  callers as above set StartInSearchMode based on len(items) > 5,
  so for small lists "/" is the only way to filter.

- TestSelectBaseline_ArrowPageNav: pins that Left/Right arrows page
  through the list, mirroring TestSelectBaseline_CtrlFCtrlB but via
  the arrow keys. Promptui maps both pairs to KeyForward/KeyBackward
  which the select widget treats as page-down/page-up.

Co-authored-by: Isaac
Adds TestSelectBaseline_DefaultTemplates, mirroring the data shape and
options of the `databricks selftest tui run-select` plain mode
(cmd/selftest/tui/select.go: runSelectPlain). It calls cmdio.RunSelect
with only Label and Items set, so promptui falls back to its built-in
defaults, which render the whole item via {{.}} — for cmdio.Tuple that
prints as "{Name Id}".

The goldens make that ugly default visible: a future change to the
defaults — or an accidental loss of a custom template at a real call
site — produces a diff. The post-Enter golden (default Selected
template "✔ {Name Id}") closes the corresponding gap on the exit
render path.

Co-authored-by: Isaac
…t and select

Ctrl+H sends BS (0x08) and Ctrl+J sends LF (0x0a); chzyer/readline treats
them as aliases for Backspace (DEL, 0x7f) and Enter (CR, 0x0d). RunPrompt
honours both aliases in full. Select honours Ctrl+H inside the search
buffer but diverges on Ctrl+J: it ends the prompt cleanly, yet resets
the highlighted item to the first one, so Down + Ctrl+J returns "a"
where Down + Enter (TestSelectBaseline_DownEnter) returns "b". The
select_baseline_ctrl_j baseline locks that quirk in.

Co-authored-by: Isaac
The previous assertion pinned promptui's bug where Ctrl+J on a non-first
item resets the highlight to the first item before returning, locking in
the broken "a" value. A future Select implementation should be free to
fix that (mapping Ctrl+J to Enter), so the test now only requires that
submission succeeds and the returned id is one of the valid items.

Co-authored-by: Isaac
PromptOptions.Default is rendered like a placeholder (cursor sits at
column 0 with the default text to its right), but the readline buffer
is actually pre-filled. The dismissal trigger is "first typed rune" —
if the user instead presses Right or Ctrl+F first, the cursor moves
*into* the pre-filled default and subsequent typing inserts there. So
Right×2 + Ctrl+F + "e" on default "us-west-2" returns "us-ewest-2", not
"e".

Adds DefaultCursorMovementEntersBuffer to pin that path and
DefaultEnterAccepts to pin that bare Enter returns the default verbatim.

Co-authored-by: Isaac
- gocritic (24): replace context.Background() with t.Context() in test files
- gofmt (19): fix import ordering after the context replacement
- forbidigo (1): annotate os.Getenv in termtest.go (test-only UPDATE flag, no ctx available in the helper)

Co-authored-by: Isaac
Adds five baselines for the first-key handling of PromptOptions.Default,
covering the cases not exercised by the existing DefaultNoEdit and
DefaultCursorMovementEntersBuffer tests:

- Backspace/Ctrl+H erase the entire default (promptui's eraseDefault path),
  leaving an empty buffer.
- Left/Ctrl+B is visually inert at column 0 and does not opt into editing
  the default — a subsequent printable rune still replaces the default
  rather than inserting before it.
- After Right has loaded the default into the editable buffer, Backspace
  deletes one character within it (Right + Backspace on "us-west-2"
  yields "s-west-2"), not the whole default.

Co-authored-by: Isaac
No production caller sets it. databricks auth login renders the default
in the label and substitutes it manually on empty input (PR #3252), and
no other prompt uses it. Also drop the --default flag from selftest tui
prompt, which only existed to exercise the field.

Co-authored-by: Isaac
The merged commit removes the Default field from cmdio.PromptOptions
(no production caller sets it; databricks auth login renders the
default in the label and substitutes it manually). The cmdiotest
baselines that pinned Default's promptui behavior are no longer
applicable, so this merge also drops:

  - prompt_default_no_edit_baseline_test.go
  - prompt_default_placeholder_baseline_test.go
  - prompt_default_first_key_baseline_test.go

and their associated testdata directories.

Co-authored-by: Isaac
@pietern pietern temporarily deployed to test-trigger-is May 11, 2026 12:14 — with GitHub Actions Inactive
@pietern pietern temporarily deployed to test-trigger-is May 11, 2026 12:14 — with GitHub Actions Inactive
pietern added a commit that referenced this pull request May 11, 2026
Drops the manifoldco/promptui (and transitive chzyer/readline)
dependency in favor of hand-rolled bubbletea models that reproduce
promptui's rendering and key handling.

RunPrompt and Secret share a single-line editor that pins the
cursor-block visuals, mask, validate (with inline "✗" glyph and a
red ">> <err>" line surfaced after a failed Enter), HideEntered
post-submit clearing, Ctrl+B/F as left/right, Ctrl+H as backspace,
Ctrl+J as Enter, and Delete/Ctrl+D as EOF.

RunSelect, Select, and SelectOrdered share a templated list with
viewport scroll and ↑/↓ gutters, a search filter with vim-style
nav and "/" toggle, Ctrl+P/N as item-up/down, Ctrl+B/F (and the
left/right arrows) as page-up/down, default Active / Inactive /
Selected templates that match promptui's defaults, and an empty
final frame when HideSelected is set.

Both primitives refuse to draw on a non-interactive terminal so
callers no longer have to gate on IsPromptSupported themselves;
SelectOrdered drops its now-redundant guard. SelectOptions.Items
is now validated at construction and normalized to []any so the
render path doesn't reflect on every row.

The behavior pinned above is verified against the cmdiotest pty-
and vt10x-based baseline suite developed in #5231. That suite is
kept on a separate branch — and not merged here — because it pulls
in test-only dependencies (creack/pty, hinshun/vt10x, x/term) that
we'd prefer not to land in the main module.

Co-authored-by: Isaac
pietern added a commit that referenced this pull request May 11, 2026
Drops the manifoldco/promptui (and transitive chzyer/readline)
dependency in favor of hand-rolled bubbletea models that reproduce
promptui's rendering and key handling.

RunPrompt and Secret share a single-line editor that pins the
cursor-block visuals, mask, validate (with inline "✗" glyph and a
red ">> <err>" line surfaced after a failed Enter), HideEntered
post-submit clearing, Ctrl+B/F as left/right, Ctrl+H as backspace,
Ctrl+J as Enter, and Delete/Ctrl+D as EOF.

RunSelect, Select, and SelectOrdered share a templated list with
viewport scroll and ↑/↓ gutters, a search filter with vim-style
nav and "/" toggle, Ctrl+P/N as item-up/down, Ctrl+B/F (and the
left/right arrows) as page-up/down, default Active / Inactive /
Selected templates that match promptui's defaults, and an empty
final frame when HideSelected is set.

Both primitives refuse to draw on a non-interactive terminal so
callers no longer have to gate on IsPromptSupported themselves;
SelectOrdered drops its now-redundant guard. SelectOptions.Items
is now validated at construction and normalized to []any so the
render path doesn't reflect on every row.

The behavior pinned above is verified against the cmdiotest pty-
and vt10x-based baseline suite developed in #5231. That suite is
kept on a separate branch — and not merged here — because it pulls
in test-only dependencies (creack/pty, hinshun/vt10x, x/term) that
we'd prefer not to land in the main module.

Co-authored-by: Isaac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant