Documentation
¶
Index ¶
- Variables
- func ApplyUpdate[S any](s *S, tracer Tracer, update StateUpdate[S])
- func Async[S, T any](ctx context.Context, updates chan<- StateUpdate[S], spawn func(func()), ...)
- func Cancel[S any](fx Effects[S], name string)
- func Every[S any](fx Effects[S], name string, interval time.Duration, fold func(*S, time.Time))
- func Exec[S any](fx Effects[S], name string, cmd *exec.Cmd, fold func(*S, error))
- func Go[S, T any](fx Effects[S], name string, work func(context.Context) (T, error), ...)
- func Latest[S, T any](fx Effects[S], name string, work func(context.Context) (T, error), ...)
- func LoadState[S any](path string, defaultState S) S
- func Run[S any](ctx context.Context, app App[S], opts ...Option) error
- func SaveState[S any](path string, state S) error
- func ScopeEvery[S any](scope *Scope, fx Effects[S], interval time.Duration, fold func(*S, time.Time))
- func ScopeGo[S, T any](scope *Scope, fx Effects[S], work func(context.Context) (T, error), ...)
- func ScopeStream[S, T any](scope *Scope, fx Effects[S], source func(context.Context, func(T)), ...)
- func SelectFile[S any](fx Effects[S], name string, cmd *exec.Cmd, fold func(*S, FileSelection))
- func Stream[S, T any](fx Effects[S], name string, source func(context.Context, func(T)), ...)
- type App
- type ClipboardEvent
- type Clock
- type Cursor
- type CursorShape
- type CursorStyle
- type Effects
- type Event
- type FileSelection
- type FocusEvent
- type Frame
- type Key
- type KeyEvent
- type Mod
- type MouseAction
- type MouseButton
- type MouseEvent
- type MouseMode
- type NoopTracer
- type Option
- type PasteEvent
- type RenderContext
- type ResizeEvent
- type Scope
- type StateUpdate
- type Tracer
- type UpdateTracer
Constants ¶
This section is empty.
Variables ¶
var ErrNoSelection = errors.New("flat: no file selected")
ErrNoSelection is returned by SelectFile when the selector command exits successfully without printing a selected path.
Functions ¶
func ApplyUpdate ¶
func ApplyUpdate[S any](s *S, tracer Tracer, update StateUpdate[S])
func Cancel ¶
Cancel stops in-flight Latest work under name, if any. It does not affect Every tickers — those stop through the Scope that started them (see Every's doc).
func Every ¶
Every sends a named update on a fixed interval until the loop context is cancelled. Timing comes from the Clock (real ticker by default; a fake clock drives it deterministically under test).
Every has no name-based cancellation of its own — Cancel only stops Latest work. To stop or re-arm a ticker (pause/resume, changing the interval), start it through a Scope (ScopeEvery) and cancel the scope; flat-game dogfoods exactly this for pause/resume/restart.
func Exec ¶
Exec releases the terminal (cooked mode, main screen), runs cmd attached to it, restores the terminal, and applies the named fold with the command's error — all synchronously on the loop goroutine: the TUI is paused while cmd runs, exactly like shelling out to $EDITOR. cmd's stdin/stdout default to Run's input/output and stderr to os.Stderr, each only when unset. Loop-goroutine-only, like all effects; no-op on a zero Effects value.
func Go ¶
func Go[S, T any](fx Effects[S], name string, work func(context.Context) (T, error), fold func(*S, T, error))
Go runs work off-loop and folds its result back into state as one named update. It is Async spelled through Effects.
func Latest ¶
func Latest[S, T any](fx Effects[S], name string, work func(context.Context) (T, error), fold func(*S, T, error))
Latest is Go with supersede-by-name semantics: starting new work under a name cancels any in-flight work under the same name, and a superseded result is dropped even if it was already queued. This replaces manual generation counters for request/response races.
On a zero Effects value (no registry) it degrades to Go.
func LoadState ¶
LoadState gob-decodes state from path, returning defaultState if the file is missing or fails to decode. Struct shape changes during iteration will invalidate old state — the decode-error fallback handles this gracefully instead of crashing.
func SaveState ¶
SaveState gob-encodes state to path. The state struct must contain only gob-serializable fields — paths not open file handles, queries not DB connections. Anything live gets reopened on boot via a rehydrate step.
func ScopeEvery ¶
func ScopeEvery[S any](scope *Scope, fx Effects[S], interval time.Duration, fold func(*S, time.Time))
ScopeEvery sends a named update on a fixed interval until the scope is cancelled. Timing comes from the Clock (real ticker by default; fake clock under test).
func ScopeGo ¶
func ScopeGo[S, T any](scope *Scope, fx Effects[S], work func(context.Context) (T, error), fold func(*S, T, error))
ScopeGo runs work off-loop using the scope's context. When the scope is cancelled, the context passed to work is cancelled. The fold runs on the loop goroutine as a named update.
func ScopeStream ¶
func ScopeStream[S, T any](scope *Scope, fx Effects[S], source func(context.Context, func(T)), fold func(*S, T))
ScopeStream runs a long-lived source that emits many values over time. Each emitted value becomes one named update. When the scope is cancelled, both the source's context and the send-channel select see the cancellation.
func SelectFile ¶
SelectFile releases the terminal, runs cmd as an external file selector, restores the terminal, and applies fold with the selected path. The selected path is read from stdout and trimmed. If cmd already has Stdout, output is still forwarded there while also being captured for the selection result.
This is intentionally terminal-delegated rather than an in-TUI file browser: apps can plug in fzf, yazi, ranger, or another command while Flatte owns the terminal handoff.
Types ¶
type App ¶
type App[S any] struct { State *S Init func(*S, Effects[S]) Handle func(*S, Event, Effects[S]) View func(*S, RenderContext) Frame Tracer Tracer // OnExit is called after the loop ends, on every exit path (clean quit, // context cancel, signal). Use it to persist state before the process // exits. It fires before terminal restoration. OnExit func(*S) }
type ClipboardEvent ¶
type ClipboardEvent struct{ Text string }
ClipboardEvent delivers the terminal's answer to fx.ReadClipboard (OSC52, system selection). Unsupported terminals never answer — treat the event as optional and do not wait for it.
type Clock ¶
type Clock interface {
// Tick calls cb on each interval until ctx is cancelled. Real
// implementations own a goroutine; fake ones fire synchronously.
Tick(ctx context.Context, interval time.Duration, cb func(time.Time))
}
Clock abstracts the timing source for interval effects so tests can drive them deterministically. The real clock uses time.Ticker; flatest provides a fake one.
type Cursor ¶
type Cursor struct {
X, Y int
Style *CursorStyle
}
Cursor is a hardware-cursor position in frame cell coordinates.
type CursorShape ¶
type CursorShape int
const ( CursorShapeDefault CursorShape = iota CursorShapeBlock CursorShapeUnderline CursorShapeBar )
type CursorStyle ¶
type CursorStyle struct {
Shape CursorShape
Blink bool
Color color.Color
}
CursorStyle configures the terminal hardware cursor when supported. A nil style leaves the terminal default in place.
type Effects ¶
type Effects[S any] struct { Context context.Context Updates chan<- StateUpdate[S] // contains filtered or unexported fields }
func NewEffects ¶
func NewEffects[S any](ctx context.Context, updates chan<- StateUpdate[S], quit func()) Effects[S]
NewEffects builds an Effects value with an observable quit callback. Run uses it internally; tests use it to assert quit requests.
func NewHarnessEffects ¶
func NewHarnessEffects[S any](ctx context.Context, updates chan<- StateUpdate[S], quit func(), dispatch func(func()), clock Clock) Effects[S]
NewHarnessEffects builds an Effects wired for deterministic testing: dispatch controls how async bodies are spawned (nil = real goroutine) and clock controls time-based effects (nil = real clock). For flatest; not app API.
func (Effects[S]) Ctx ¶
Ctx returns the loop's context, defaulting to context.Background() if none is set (e.g., in tests using the zero Effects value). Use this to derive child contexts for scoped async work — context.WithCancel(fx.Ctx()) gives you a cancellable handle for per-screen or per-selection goroutines.
func (Effects[S]) Print ¶
Print writes s into the terminal's scrollback above the live frame, then repaints the frame below it — the Claude-Code "message stream + pinned input" model: emitted lines flow into the real terminal's history (which the user scrolls with the terminal/mouse) while the frame stays put at the bottom. Requires inline rendering (WithInline); in alt-screen mode it is a no-op, because the prepended lines would be overwritten by the next frame. The content is the app's to format (no trailing newline is added). Newlines in s produce multiple scrollback lines. Loop-goroutine-only, like Quit. Safe on a zero Effects value.
func (Effects[S]) Quit ¶
func (fx Effects[S]) Quit()
Quit requests a clean exit of the Run loop. Safe on a zero Effects value. Call it from Init, Handle, or a fold — they all run on the loop goroutine; calling it from an app-spawned goroutine is a data race.
func (Effects[S]) ReadClipboard ¶
func (fx Effects[S]) ReadClipboard()
ReadClipboard asks the terminal for its clipboard content via OSC52. A supporting terminal answers with a ClipboardEvent; unsupported terminals never answer — treat the event as optional and do not wait for it. Loop-goroutine-only, like Quit. Safe on a zero Effects value.
func (Effects[S]) SetClipboard ¶
SetClipboard writes text to the system clipboard via OSC52 on the next flush. Loop-goroutine-only, like Quit. Terminals without OSC52 support ignore it. Safe on a zero Effects value.
func (Effects[S]) Suspend ¶
func (fx Effects[S]) Suspend()
Suspend releases the terminal (cooked mode, main screen, cursor visible), suspends the process like the shell's Ctrl-Z would (SIGTSTP to the process group), and on resume (fg/SIGCONT) restores the terminal and repaints. On platforms without job control it is a release/restore round trip. The framework never binds a key to this — apps decide what (if anything) triggers it. Loop-goroutine-only, like Quit. Safe on a zero Effects value.
type Event ¶
type Event interface {
// contains filtered or unexported methods
}
Event is the closed set of terminal inputs the loop delivers to Handle. It is sealed: the framework defines every implementation, apps only consume them with a type switch. This is not TEA — events are terminal inputs, never app-defined messages; async results remain StateUpdates.
type FileSelection ¶
FileSelection is the result of a terminal-delegated file picker.
type FocusEvent ¶
type FocusEvent struct{ Focused bool }
FocusEvent reports terminal focus changes (see WithReportFocus).
type Frame ¶
type Frame struct {
// Content is the styled frame text.
Content string
// Cursor places the hardware cursor, in frame cell coordinates
// ((0,0) is the frame's top-left). nil hides the cursor.
Cursor *Cursor
// Title sets the terminal window title when non-empty. It is emitted
// only when it changes, and reset on exit if it was ever set.
Title string
}
Frame is what View returns: rendered content plus terminal metadata. The zero value is a blank frame with no cursor and no title.
type MouseAction ¶
type MouseAction int
const ( MousePress MouseAction = iota MouseRelease MouseMotion )
type MouseButton ¶
type MouseButton int
const ( MouseNone MouseButton = iota MouseLeft MouseMiddle MouseRight MouseWheelUp MouseWheelDown )
type MouseEvent ¶
type MouseEvent struct {
X, Y int
Button MouseButton
Action MouseAction
Mod Mod
}
MouseEvent is a mouse press/release/motion/wheel; X/Y are zero-based cell coordinates from the top-left of the frame (see WithMouse).
type NoopTracer ¶
type NoopTracer struct{}
func (NoopTracer) Event ¶
func (NoopTracer) Event(Event)
func (NoopTracer) Update ¶
func (NoopTracer) Update(string)
type Option ¶
type Option func(*runConfig)
Option configures Run behaviour.
func WithInline ¶
func WithInline() Option
WithInline renders below the shell prompt instead of in the alternate screen: the frame occupies exactly its own lines, the terminal's scrollback stays intact, and on exit the final frame remains visible with the prompt landing below it.
func WithOutput ¶
WithOutput sets the render sink. Default: os.Stdout.
func WithReportFocus ¶
func WithReportFocus() Option
WithReportFocus enables focus reporting; terminal focus changes arrive as FocusEvent. Some terminals and multiplexers need configuration to report focus (tmux: focus-events).
func WithoutBracketedPaste ¶
func WithoutBracketedPaste() Option
WithoutBracketedPaste disables bracketed paste mode. It is on by default: without it a paste arrives as a flood of individual key events instead of one PasteEvent.
func WithoutDefaultQuit ¶
func WithoutDefaultQuit() Option
WithoutDefaultQuit delivers Ctrl-C to the app instead of exiting the loop. The app must call fx.Quit(), close the input, or cancel the context to exit.
type PasteEvent ¶
type PasteEvent struct{ Text string }
PasteEvent is a bracketed paste (paste mode is on by default; see WithoutBracketedPaste).
type RenderContext ¶
type RenderContext struct {
Width int
ColorProfile colorprofile.Profile
}
func RenderContextFor ¶
func RenderContextFor(out io.Writer) RenderContext
RenderContextFor reports the terminal facts available to View. It mirrors renderer color-profile detection so apps can pick theme styles without owning terminal probing.
type ResizeEvent ¶
ResizeEvent reports the terminal size in cells. The loop delivers one at startup and one per SIGWINCH; sizes fall back to 72×24 when the output is not a terminal.
type Scope ¶
type Scope struct {
// contains filtered or unexported fields
}
Scope groups async work under a shared cancellable context. Cancel() cancels all in-flight work started through the scope's Go, Stream, and Every helpers. This closes the "Every/Stream can't be cancelled by name" gap that the flat-docker dogfood found (Task 4 — Logs streaming needed a 30-line hand-rolled goroutine because flatte.Stream runs for the app's entire loop lifetime).
Usage:
scope := flatte.NewScope(fx, "logs")
scope.Stream(fx, func(ctx context.Context, send func(string)) { ... }, fold)
// On selection change or screen leave:
scope.Cancel()
scope = flatte.NewScope(fx, "logs") // fresh scope for new selection
func NewScope ¶
NewScope creates a scope whose context is derived from fx.Ctx(). The name prefixes the Named updates produced by the scope's helpers.
type StateUpdate ¶
func Named ¶
func Named[S any](name string, apply func(*S)) StateUpdate[S]
type UpdateTracer ¶
type UpdateTracer func(string)
func (UpdateTracer) Event ¶
func (UpdateTracer) Event(Event)
func (UpdateTracer) Update ¶
func (f UpdateTracer) Update(name string)
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package flatest is a deterministic, synchronous test harness for flat apps: it drives an App through scripted events and controlled time, exercising real async folds without goroutine races, real clocks, or a terminal.
|
Package flatest is a deterministic, synchronous test harness for flat apps: it drives an App through scripted events and controlled time, exercising real async folds without goroutine races, real clocks, or a terminal. |
|
layout
Package layout is a flexbox-style layout engine for Flatte.
|
Package layout is a flexbox-style layout engine for Flatte. |
