screen.go

 1// Package ui provides the unified interactive session for keld's TUI.
 2//
 3// The session runs a single [tea.Program] that coordinates navigation
 4// between screens, manages the visual chrome (breadcrumb, title, help
 5// bar), and handles back navigation and cancellation.
 6package ui
 7
 8import (
 9	"charm.land/bubbles/v2/key"
10	tea "charm.land/bubbletea/v2"
11)
12
13// Screen is the interface that every step in an interactive flow must
14// satisfy. The session delegates input handling and rendering to the
15// active screen without knowing its internals.
16//
17// Screens are constructed before use and may be re-activated when the
18// user navigates back. Implementations should preserve their state
19// across deactivation/reactivation cycles so previous answers are
20// visible when the user returns.
21type Screen interface {
22	// Init returns the command to run when this screen becomes active.
23	// Called both on first activation and when the user navigates back
24	// to this screen.
25	Init() tea.Cmd
26
27	// Update handles a message and returns the updated screen plus any
28	// command. The session forwards all messages to the active screen,
29	// including Esc key presses.
30	//
31	// Screens that use Esc internally (e.g. closing a filter, navigating
32	// to a parent directory) should handle it and return normally.
33	// Screens that do not need Esc should return a [BackCmd] to signal
34	// that the session should navigate back.
35	Update(msg tea.Msg) (Screen, tea.Cmd)
36
37	// View renders the screen's content. The session wraps this with
38	// chrome (breadcrumb, title, help bar), so the screen should not
39	// render those elements itself.
40	View() string
41
42	// Title returns the screen's display title, shown below the
43	// breadcrumb.
44	Title() string
45
46	// KeyBindings returns the key bindings to display in the help bar
47	// for this screen. The session adds the global bindings (Esc, Ctrl+C)
48	// automatically.
49	//
50	// The returned slice must not be modified by the caller.
51	KeyBindings() []key.Binding
52
53	// Selection returns a short string representing the user's choice
54	// on this screen, for display in the breadcrumb. Returns "" if the
55	// screen has not been completed yet.
56	Selection() string
57}
58
59// BackMsg signals that the active screen wants the session to navigate
60// back to the previous screen. Screens return this via [BackCmd] when
61// they receive an Esc press they do not handle internally.
62type BackMsg struct{}
63
64// BackCmd is a [tea.Cmd] that produces a [BackMsg]. Screens should
65// return this from their Update method when they receive an Esc press
66// and have no internal use for it.
67func BackCmd() tea.Msg {
68	return BackMsg{}
69}
70
71// DoneMsg signals that the active screen has been completed and the
72// session should advance to the next screen. Screens return this via
73// [DoneCmd] when the user confirms their selection.
74type DoneMsg struct{}
75
76// DoneCmd is a [tea.Cmd] that produces a [DoneMsg]. Screens should
77// return this from their Update method when the user makes or confirms
78// a selection.
79func DoneCmd() tea.Msg {
80	return DoneMsg{}
81}
82
83// ExtendMsg asks the session to append screens after the current
84// cursor position, replacing any screens that were already queued
85// beyond it. This allows a screen (or external code via [tea.Cmd])
86// to build the next part of the flow dynamically — for example,
87// after resolving a config to determine which command-specific
88// screens are needed.
89//
90// If Screens is empty, the message is a no-op.
91type ExtendMsg struct {
92	Screens []Screen
93}