gitstack

grindlemire/go-tui code browser

26.5 KB markdown 882 lines 2026-06-27 ยท 4be96e4 raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
# App Reference

## Overview

The `App` type is the top-level container that manages terminal setup, the event loop, rendering, and the component tree. Most programs create an `App`, set a root component, call `Run()`, and defer `Close()`. For custom event loops, use `Open()` with `Events()`, `Step()`, or `DispatchEvents()` instead of `Run()`.

```go
package main

import (
    "fmt"
    "os"

    tui "github.com/grindlemire/go-tui"
)

func main() {
    app, err := tui.NewApp(
        tui.WithRootComponent(MyApp()),
    )
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
    defer app.Close()

    if err := app.Run(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
        os.Exit(1)
    }
}
```

## Creating an App

### NewApp

```go
func NewApp(opts ...AppOption) (*App, error)
```

Creates a new application. Sets the terminal to raw mode and alternate screen (unless inline mode is configured). Pass `AppOption` functions to configure behavior.

Mouse behavior defaults:
- **Full screen mode**: mouse events enabled
- **Inline mode**: mouse events disabled (preserves terminal scrollback)
- Use `WithMouse()` or `WithoutMouse()` to override

```go
app, err := tui.NewApp(
    tui.WithRootComponent(MyApp()),
    tui.WithFrameRate(30),
)
```

### NewAppWithReader

```go
func NewAppWithReader(reader EventReader, opts ...AppOption) (*App, error)
```

Creates an App with a custom `EventReader`, typically for testing. Accepts the same options as `NewApp`.

```go
reader := tui.NewMockEventReader(
    tui.KeyEvent{Key: tui.KeyEnter},
)
app, err := tui.NewAppWithReader(reader,
    tui.WithRootComponent(MyApp()),
)
```

## AppOption Functions

Options configure the App before the event loop starts. Each returns an `AppOption` (which is `func(*App) error`).

### WithRootComponent

```go
func WithRootComponent(component Component) AppOption
```

Sets a struct component as the root. This is the most common way to wire up your UI. The component's `Render` method is called each frame when state is dirty, and the framework manages its lifecycle (key handling, mouse, watchers, mounting).

```go
tui.NewApp(tui.WithRootComponent(MyApp()))
```

### WithRoot

```go
func WithRoot(root *Element) AppOption
```

Sets a raw `*Element` as the root. Use this when you're building the element tree manually rather than through a struct component.

### WithRootView

```go
func WithRootView(view Viewable) AppOption
```

Sets a `Viewable` as the root. A `Viewable` provides both a root element and a set of watchers. The framework starts the watchers automatically when the view is set.

### WithFrameRate

```go
func WithFrameRate(fps int) AppOption
```

Sets the target render frame rate. Default is 60 fps. Valid range: 1-240.

```go
tui.WithFrameRate(30) // 30 fps, ~33ms per frame
```

### WithInputLatency

```go
func WithInputLatency(d time.Duration) AppOption
```

Sets the polling timeout for the event reader. Default is `tui.InputLatencyBlocking`, which blocks until input arrives (zero syscalls while idle). Use a positive duration like `50 * time.Millisecond` for polling mode. A value of 0 is not allowed.

```go
tui.WithInputLatency(50 * time.Millisecond) // poll every 50ms instead of blocking
```

### WithEventQueueSize

```go
func WithEventQueueSize(size int) AppOption
```

Sets the capacity of the internal event queue buffer. Default is 256. Must be at least 1.

### WithGlobalKeyHandler

```go
func WithGlobalKeyHandler(fn func(KeyEvent) bool) AppOption
```

Sets a handler that runs before key events reach the component tree (legacy path). If the handler returns `true`, the event is consumed and not dispatched further.

When using the component model (struct components with `KeyMap()`), key dispatch goes through the dispatch table instead. Prefer `KeyMap()` on your components over global handlers.

```go
tui.WithGlobalKeyHandler(func(ke tui.KeyEvent) bool {
    if ke.Key == tui.KeyEscape {
        ke.App().Stop()
        return true
    }
    return false
})
```

### WithMouse

```go
func WithMouse() AppOption
```

Explicitly enables mouse event reporting. Use this to turn on mouse support in inline mode, where it's off by default.

### WithoutMouse

```go
func WithoutMouse() AppOption
```

Explicitly disables mouse event reporting. Use this to turn off mouse support in full screen mode, where it's on by default.

### WithCursor

```go
func WithCursor() AppOption
```

Keeps the cursor visible during app execution. By default, the cursor is hidden.

### WithPreRenderHook

```go
func WithPreRenderHook(fn func()) AppOption
```

Sets a callback that runs at the start of every render cycle, after the buffer is cleared and before the component tree re-renders. Use it for state that must settle before the frame is drawn, such as syncing a layout value to the current terminal size.

```go
tui.WithPreRenderHook(func() {
    w, _ := app.Size()
    sidebar.SetWidth(tui.Fixed(w / 4))
})
```

### WithPostRenderHook

```go
func WithPostRenderHook(fn func()) AppOption
```

Sets a callback that runs after every render cycle completes. Use it for work that should follow the frame, such as measurement or frame logging.

### WithManualCursor

```go
func WithManualCursor() AppOption
```

Disables framework-driven cursor placement. By default the App moves the real terminal cursor to the focused widget's reported position at the end of each frame (see [Cursor Reporting](element.md#cursor-reporting)). Use this when your app positions the cursor itself or wants no cursor shown. This differs from `WithCursor`, which keeps the cursor visible but leaves placement to the framework.

### WithInlineHeight

```go
func WithInlineHeight(rows int) AppOption
```

Enables inline widget mode. The app manages only the bottom N rows of the terminal, and normal terminal output is preserved above. Must be at least 1.

In inline mode:
- Alternate screen is not used, so terminal history is preserved
- Mouse events are disabled by default
- Use `PrintAbove()` / `PrintAboveln()` to print scrolling content above the widget

```go
tui.WithInlineHeight(5) // 5-row widget at the bottom of the terminal
```

### WithInlineStartupMode

```go
func WithInlineStartupMode(mode InlineStartupMode) AppOption
```

Configures how inline mode handles existing visible terminal content at startup. See [InlineStartupMode Constants](#inlinestartupmode-constants) for the available modes.

### WithOnSuspend

```go
func WithOnSuspend(fn func()) AppOption
```

Sets a callback that runs before the app suspends on Ctrl+Z. Use this to save state or pause timers before the process stops.

### WithOnResume

```go
func WithOnResume(fn func()) AppOption
```

Sets a callback that runs after the app resumes from suspension (when the user runs `fg`). Use this to restart timers or refresh stale data.

## Lifecycle Methods

### Run

```go
func (a *App) Run() error
```

Starts the main event loop. Blocks until `Stop()` is called or a SIGINT (Ctrl+C) is received. The loop processes input events, re-renders when state is dirty, and sleeps for the remaining frame budget to maintain a consistent frame rate. Calls `Open()` internally if the app has not been opened yet, and calls `Close()` on exit.

```go
if err := app.Run(); err != nil {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}
```

### Open

```go
func (a *App) Open() error
```

Initializes the event loop without blocking. Registers signal handlers (SIGINT, SIGWINCH, SIGTSTP/SIGCONT), starts the input reader goroutine, and performs the initial render. Returns an error if the app is already open.

Use `Open()` instead of `Run()` when you want to drive your own event loop. After `Open()`, use `Events()`, `Dispatch()`, `Render()`, `Step()`, or `DispatchEvents()` to process events at your own pace. Call `Close()` when done.

```go
if err := app.Open(); err != nil {
    fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    os.Exit(1)
}
defer app.Close()
```

### Stop

```go
func (a *App) Stop()
```

Signals the event loop to exit gracefully. All watchers receive a stop signal and their goroutines exit. Safe to call multiple times (idempotent).

```go
tui.On(tui.KeyEscape, func(ke tui.KeyEvent) {
    ke.App().Stop()
})
```

### Close

```go
func (a *App) Close() error
```

Restores the terminal to its original state: stops goroutines, cleans up signal handlers, disables mouse, shows cursor, exits alternate screen (or clears the inline region), exits raw mode, and closes the event reader. Safe to call multiple times (idempotent via `sync.Once`).

```go
app, err := tui.NewApp(...)
if err != nil { ... }
defer app.Close()
```

## Root Management

### SetRoot

```go
func (a *App) SetRoot(root *Element)
```

Sets a raw `*Element` as the root for rendering. Clears any previous root component, discovers focusable elements and watchers in the new tree, and marks the app dirty.

### SetRootView

```go
func (a *App) SetRootView(view Viewable)
```

Sets a `Viewable` as the root. Calls `BindApp` if the view implements `AppBinder`, extracts the root element via `GetRoot()`, and starts watchers returned by `GetWatchers()`.

### SetRootComponent

```go
func (a *App) SetRootComponent(component Component)
```

Sets a struct component as the root. Calls `BindApp` if the component implements `AppBinder`, renders it to produce the element tree, and marks the app dirty. On subsequent frames, the component's `Render` method is called again whenever the app is dirty.

### Root

```go
func (a *App) Root() *Element
```

Returns the current root element.

## Rendering

### Render

```go
func (a *App) Render()
```

Checks the dirty flag and, if set, re-renders the component tree and flushes changes to the terminal. No-op if nothing has changed since the last render. After rendering, the dispatch table is rebuilt from the current component tree. Handles buffer resizing, inline mode offsets, and the mark-and-sweep cycle for mounted sub-components. Automatically performs a full redraw after a resize.

Safe to call frequently (e.g., after every select case in a manual event loop). The dirty check makes redundant calls cheap.

### RenderFull

```go
func (a *App) RenderFull()
```

Forces a complete redraw of the entire buffer to the terminal. Use this after events that may corrupt the terminal display.

### MarkDirty

```go
func (a *App) MarkDirty()
```

Marks the app as needing a render on the next frame. You rarely need to call this directly; `State.Set()` and `State.Update()` call it automatically.

### Buffer

```go
func (a *App) Buffer() *Buffer
```

Returns the underlying double-buffered character grid. For advanced use cases only.

### Size

```go
func (a *App) Size() (width, height int)
```

Returns the current terminal dimensions in columns and rows.

### SnapshotFrame

```go
func (a *App) SnapshotFrame() string
```

Returns the current frame as a trimmed string. Useful for debugging and testing.

## Event Handling

### Events

```go
func (a *App) Events() <-chan Event
```

Returns a read-only channel carrying all events: key, mouse, resize, and queued updates. Input events are prioritized over background updates internally. Use this with `select` to multiplex go-tui events with your own channels.

```go
for {
    select {
    case ev := <-app.Events():
        app.Dispatch(ev)
    case msg := <-myChannel:
        handleMsg(msg)
    case <-app.StopCh():
        return
    }
    app.Render()
}
```

### Dispatch

```go
func (a *App) Dispatch(event Event) bool
```

Routes a single event through the dispatch system. Returns `true` if the event was consumed.

- **UpdateEvent**: executes the queued closure.
- **KeyEvent**: goes through the dispatch table (component model) or global key handler (legacy path), then falls through to the focus manager.
- **MouseEvent**: translated for inline mode, dispatched to MouseListener components, then hit-tested against elements.
- **ResizeEvent**: updates buffer size, marks root dirty, schedules a full redraw.

Used in manual event loops with `Events()`, and in tests.

```go
app.Dispatch(tui.KeyEvent{Key: tui.KeyEnter})
```

### DispatchEvents

```go
func (a *App) DispatchEvents() bool
```

Reads and dispatches all pending events from the Events channel. Returns `false` if the app has been stopped, `true` otherwise. Non-blocking: returns immediately when no events are pending.

```go
if !app.DispatchEvents() {
    return // app stopped
}
app.Render()
```

### Step

```go
func (a *App) Step() bool
```

Combines `DispatchEvents()` and `Render()` into a single call. Returns `false` if the app has been stopped.

```go
for {
    <-ticker.C
    if !app.Step() {
        return
    }
}
```

### SetGlobalKeyHandler

```go
func (a *App) SetGlobalKeyHandler(fn func(KeyEvent) bool)
```

Sets (or replaces) a handler that runs before key events reach the focus manager. If the handler returns `true`, the event is consumed.

### QueueUpdate

```go
func (a *App) QueueUpdate(fn func())
```

Enqueues a function to run on the main event loop. Safe to call from any goroutine. If the updates channel is full, the update is dropped. Use this when you need to update state from a background goroutine and you are using `Run()`.

When using `Open()` with `Step()` or `Events()`, you can mutate state directly on the main goroutine without `QueueUpdate`.

```go
go func() {
    result := fetchData()
    app.QueueUpdate(func() {
        data.Set(result)
    })
}()
```

### PollEvent

```go
func (a *App) PollEvent(timeout time.Duration) (Event, bool)
```

Reads the next input event with a timeout. Returns the event and `true` if one was available, or a zero event and `false` on timeout. Convenience wrapper around the `EventReader`.

## Focus

### FocusNext

```go
func (a *App) FocusNext()
```

Moves focus to the next focusable element in document order.

### FocusPrev

```go
func (a *App) FocusPrev()
```

Moves focus to the previous focusable element.

### Focused

```go
func (a *App) Focused() Focusable
```

Returns the currently focused element, or `nil` if nothing is focused.

## Inline Mode

These methods only take effect when the app was created with `WithInlineHeight`. In full-screen mode they are no-ops.

### PrintAbove

```go
func (a *App) PrintAbove(format string, args ...any)
```

Prints formatted content above the inline widget. Does not add a trailing newline. Must be called from the app's main event loop.

### PrintAboveln

```go
func (a *App) PrintAboveln(format string, args ...any)
```

Same as `PrintAbove`, but appends a newline. Must be called from the main event loop.

### QueuePrintAbove

```go
func (a *App) QueuePrintAbove(format string, args ...any)
```

Thread-safe version of `PrintAbove`. Safe to call from any goroutine.

### QueuePrintAboveln

```go
func (a *App) QueuePrintAboveln(format string, args ...any)
```

Thread-safe version of `PrintAboveln`. Safe to call from any goroutine.

### StreamAbove

```go
func (a *App) StreamAbove() *StreamWriter
```

Returns a `*StreamWriter` that streams text character by character to the history region above the inline widget. The writer implements `io.WriteCloser` for plain byte streaming, and adds `WriteStyled` and `WriteGradient` methods for styled output. The writer is goroutine-safe: writes are queued onto the main event loop internally.

Closing the writer finalizes the current partial line, making it a permanent row in the history. If a previous stream writer is still open when `StreamAbove()` is called again, the framework finalizes and closes it before returning the new one.

Returns a no-op writer (silently discards all bytes) when not in inline mode.

```go
go func() {
    w := app.StreamAbove()
    // Plain write (backward compatible with io.WriteCloser)
    fmt.Fprint(w, "hello ")
    // Styled write
    w.WriteStyled("important", tui.NewStyle().Bold().Foreground(tui.Red))
    // Gradient write (per-character color interpolation)
    grad := tui.NewGradient(tui.Cyan, tui.Magenta)
    w.WriteGradient("gradient text", grad)
    // Gradient with base style attributes
    w.WriteGradient("bold gradient", grad, tui.NewStyle().Bold())
    w.Close()
}()
```

#### StreamWriter Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `Write` | `Write(p []byte) (int, error)` | Plain byte write (backward compatible). Does not track column position. |
| `Close` | `Close() error` | Finalizes the partial line. |
| `WriteStyled` | `WriteStyled(text string, style Style) (int, error)` | Writes text with ANSI style prefix and reset suffix. Tracks column position. |
| `WriteGradient` | `WriteGradient(text string, g Gradient, base ...Style) (int, error)` | Writes each character with an interpolated gradient foreground color. Optional base style provides attributes (bold, italic) and background. Tracks column position. |
| `WriteElement` | `WriteElement(el *Element)` | Renders an element and inserts its rows mid-stream. Finalizes the current partial line first. Resets column position. |

Column tracking: `WriteStyled` and `WriteGradient` advance an internal column counter by each character's display width (CJK-aware). Newlines reset the column to 0. The column wraps at the terminal width. This counter drives gradient color interpolation.

If `PrintAbove` or `PrintAboveln` is called while a stream writer is active, the stream's partial line is finalized first, then the new content is printed on the next line. Further writes to the finalized writer return `io.ErrClosedPipe`.

### PrintAboveElement

```go
func (a *App) PrintAboveElement(el *Element)
```

Renders an element tree and inserts the resulting rows into the inline scrollback as static ANSI text. The element is laid out at the terminal's current width using the standard flexbox layout engine, then each row is converted to ANSI escape sequences and inserted via the same machinery as `PrintAboveStyled`.

This enables inserting structured content (tables, styled cards, templ component output) into the scrollback alongside streamed text.

Must be called from the app's main event loop. No-op if not in inline mode or if the element is nil.

```go
// Insert a templ-generated table
table := DataTable(myRows)
app.PrintAboveElement(table)

// Insert a hand-built element
el := tui.NewElement(tui.WithText("result"), tui.WithBorder(tui.BorderSingle))
app.PrintAboveElement(el)
```

Any active stream writer's partial line is finalized before the element is inserted, same as `PrintAbove`.

### QueuePrintAboveElement

```go
func (a *App) QueuePrintAboveElement(el *Element)
```

Thread-safe version of `PrintAboveElement`. Safe to call from any goroutine. The element is rendered and inserted on the main event loop.

```go
go func() {
    table := buildTable(result)
    app.QueuePrintAboveElement(table)
}()
```

### SetInlineHeight

```go
func (a *App) SetInlineHeight(rows int)
```

Changes the inline widget height at runtime. The height change takes effect immediately. Capped at terminal height, minimum of 1. Should be called from render functions or the main event loop.

### InlineHeight

```go
func (a *App) InlineHeight() int
```

Returns the current inline height. Returns 0 if the app is not in inline mode.

## Alternate Screen

These methods let an inline-mode app switch to a full-screen overlay and back, for things like settings panels or help screens that shouldn't affect terminal scrollback.

### EnterAlternateScreen

```go
func (a *App) EnterAlternateScreen() error
```

Switches to alternate screen mode for a full-screen UI overlay. Saves the current inline mode state, clears the inline region, enters the alternate screen, and resizes the buffer to full terminal dimensions. No-op if already in alternate mode.

### ExitAlternateScreen

```go
func (a *App) ExitAlternateScreen() error
```

Returns from alternate screen mode and restores the previous inline mode state. The terminal content from before `EnterAlternateScreen` reappears. No-op if not in alternate mode.

### IsInAlternateScreen

```go
func (a *App) IsInAlternateScreen() bool
```

Returns `true` if the app is currently displaying in the alternate screen overlay.

## Job Control (Ctrl+Z)

Pressing Ctrl+Z suspends the app, and running `fg` in the shell resumes it. This works automatically for all go-tui apps.

On suspend, the framework automatically:
1. Disables mouse reporting
2. Shows the cursor
3. Exits the alternate screen (full-screen mode)
4. Restores normal terminal mode

On resume, it reverses all of these and forces a full redraw.

### Hooks

Use `WithOnSuspend` and `WithOnResume` to run custom logic:

```go
app, err := tui.NewApp(
    tui.WithOnSuspend(func() {
        // Save state before suspend
    }),
    tui.WithOnResume(func() {
        // Refresh data after resume
    }),
)
```

### Suspend

```go
func (a *App) Suspend()
```

Programmatically triggers a suspend (same as Ctrl+Z). Safe to call from any goroutine.

### Overriding Ctrl+Z

To prevent Ctrl+Z from suspending, bind `KeyCtrlZ` with a Stop handler in a component's `KeyMap()`:

```go
func (c *myComponent) KeyMap() tui.KeyMap {
    return tui.KeyMap{
        tui.OnStop(tui.KeyCtrlZ, func(ke tui.KeyEvent) {
            // Custom behavior instead of suspend
        }),
    }
}
```

## State Batching

### Batch

```go
func (a *App) Batch(fn func())
```

Executes `fn` with all state binding callbacks deferred until `fn` returns. Multiple `Set()` calls within a batch coalesce into a single round of binding execution, which avoids redundant intermediate renders.

```go
app.Batch(func() {
    firstName.Set("Alice")
    lastName.Set("Smith")
}) // Bindings fire once here, not twice
```

## Component Mounting

### Mount

```go
func (a *App) Mount(parent Component, key any, factory func() Component) *Element
```

Creates or retrieves a cached component instance and returns its rendered element tree. Generated code calls this; you typically don't call it directly.

On first call for a given `(parent, key)` pair: executes `factory`, caches the instance, calls `BindApp` and `Init()` (if implemented). On subsequent calls: returns the cached instance's `Render()` result. If the instance implements `PropsUpdater`, `UpdateProps` is called with a fresh instance so the cached component can pick up new props.

The key must be a comparable value. Generated code passes a plain int for a component declared once, and a `MountKey` composite for components inside loops, so each loop item gets its own cached instance.

If a cached instance's concrete type differs from what the factory produces (a key collision between two call sites), the stale instance is evicted, its cleanup runs, and the fresh component mounts in its place. The collision is logged through the debug log rather than rendering the wrong component silently.

Stale cache entries are swept after each render pass. When a component disappears from the tree, its cleanup function runs.

### MountPersistent

```go
func (a *App) MountPersistent(parent Component, key any, factory func() Component) *Element
```

Like `Mount`, but the instance survives the sweep even when it is not rendered. Generated code uses this for component elements (`<textarea>`, `<input>`, `<modal>`, `<markdown>`) declared outside loops without a `key` attribute, so a textarea hidden by an `if` keeps its draft text when it reappears. Component elements inside loops, or with a `key={...}` attribute, use plain `Mount` instead: their identity comes from data, and persisting every key ever seen would leak instances as the data changes.

### MountKey

```go
func MountKey(site int, parts ...any) any
```

Builds the comparable cache key generated code passes to `Mount` for loop call sites. The first argument identifies the call site within the component; each part is one enclosing loop's key value (a slice index, a map key, or the expression from a `key={...}` attribute). Every part must itself be comparable; a non-comparable value panics when the key enters the cache. Keys built from different call sites or different loop values never compare equal, so distinct items cannot collide.

## Other

### Terminal

```go
func (a *App) Terminal() Terminal
```

Returns the underlying `Terminal` implementation. For advanced use cases such as direct ANSI escape output.

### EventQueue

```go
func (a *App) EventQueue() chan<- func()
```

Returns the event queue channel for manual watcher setup. Prefer `WatcherProvider` on components instead.

### StopCh

```go
func (a *App) StopCh() <-chan struct{}
```

Returns a channel that closes when the app stops. For manual watcher setup. Prefer `WatcherProvider` on components instead.

## InlineStartupMode Constants

These constants control how inline mode initializes the visible terminal viewport at startup. Pass them to `WithInlineStartupMode`.

| Constant | Description |
|----------|-------------|
| `InlineStartupPreserveVisible` | Default. Keeps existing visible rows on launch. Unknown history drains naturally as new `PrintAbove` content is appended. |
| `InlineStartupFreshViewport` | Clears the visible viewport immediately. Existing visible rows are discarded. |
| `InlineStartupSoftReset` | Pushes existing visible rows into scrollback via newline flow, then clears the viewport. |

## InputLatencyBlocking Constant

```go
const InputLatencyBlocking = -1 * time.Millisecond
```

The default value for `WithInputLatency`. The event reader blocks until input arrives, producing zero syscalls while idle. The framework sets up the interrupt pipe automatically so that SIGWINCH and shutdown can wake the blocked reader.

## Interfaces

These interfaces are relevant when working with the App's root management methods.

### Viewable

```go
type Viewable interface {
    GetRoot() *Element
    GetWatchers() []Watcher
}
```

Implemented by `*Element`, generated view structs, and struct components. Allows `SetRootView` to extract the root element and start watchers. Also accepted by `PrintAboveElement` and `StreamWriter.WriteElement`.

### Component

```go
type Component interface {
    Render(app *App) *Element
}
```

The base interface for struct components. Any struct with a matching `Render` method can serve as a component.

### Optional Component Interfaces

Components can implement these additional interfaces for extended behavior:

| Interface | Method | Purpose |
|-----------|--------|---------|
| `KeyListener` | `KeyMap() KeyMap` | Keyboard handling |
| `MouseListener` | `HandleMouse(MouseEvent) bool` | Mouse handling |
| `WatcherProvider` | `Watchers() []Watcher` | Timers and channel watchers |
| `Initializer` | `Init() func()` | Setup on mount, returns cleanup function |
| `AppBinder` | `BindApp(app *App)` | Receives app reference (called automatically by mount) |
| `AppUnbinder` | `UnbindApp()` | Detaches app-bound resources on unmount or root swap |
| `PropsUpdater` | `UpdateProps(fresh Component)` | Receives updated props on re-render from cache |

See the [Component Interfaces Reference](interfaces.md) for full details on each.