Skip to content

Commit fcd0ae3

Browse files
committed
add vertical scroll support
1 parent e9320f8 commit fcd0ae3

18 files changed

Lines changed: 1046 additions & 248 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ Benchmark snapshot (`2026-02-20`, `terminal-rerender`, `full` profile, PTY mode)
145145
- [x] Globals ops queue (see `./docs/FULL_RUST_AUTHORITY_SPEC.md`)
146146
- [x] Text styling: markdown and syntax highlighting API
147147
- [x] Multi-line text input, text overflow and wrapping
148-
- [ ] Vertical and horizontal scrolling
148+
- [x] Persistent Taffy tree
149+
- [x] Vertical scrolling
149150
- [ ] Safer quit/cleanup when used as a library
150151
- [ ] Responsive examples for smaller terminal sizes
151-
- [ ] Persistent Taffy tree
152152
- [ ] Experiment: Neovim as text input via [Bun PTY](https://bun.com/docs/runtime/child-process#terminal-pty-support)
153153
- [ ] Refactor `flush` with `BatchWriter` pattern
154154
- [ ] Performance stats overlay

bun.lock

Lines changed: 2 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/components-and-styling.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
## Constructors
44

55
```ts
6-
Box(input: BoxProps, children: Node[]): Node
7-
Column(input: Omit<BoxProps, "direction">, children: Node[]): Node
8-
Row(input: Omit<BoxProps, "direction">, children: Node[]): Node
6+
Box(input: BoxProps & BoxEventProps, children: Node[]): Node
7+
Column(input: Omit<BoxProps, "direction"> & BoxEventProps, children: Node[]): Node
8+
Row(input: Omit<BoxProps, "direction"> & BoxEventProps, children: Node[]): Node
99
Text(input: TextProps): Node
1010
Input(input: InputProps): Node
1111
Button(input: ButtonProps, children?: Node[]): Node
@@ -36,6 +36,11 @@ Button(input: ButtonProps, children?: Node[]): Node
3636
- optional: `direction` (`"row" | "column" | "rowReverse" | "columnReverse"`)
3737
- optional: all `StyleProps`
3838

39+
- `BoxEventProps`
40+
- optional: `onWheel(event)` for vertical wheel/trackpad handling
41+
- `event`: `{ x, y, deltaY, raw }`
42+
- return `true` to consume and stop bubbling; return `false`/`void` to bubble to parent container
43+
3944
## Shared style fields (`StyleProps`)
4045

4146
- `border`: `{ color: number; style: "square" | "rounded" }`
@@ -67,6 +72,19 @@ Button(input: ButtonProps, children?: Node[]): Node
6772
- `overflow`, scrolling, and other non-exported props are not part of the public API.
6873
- Prefer `Row` / `Column` for common cases; use `Box` when you need explicit `direction`, including reverse directions.
6974

75+
## Wheel Routing
76+
77+
- wheel routing targets the deepest container with `onWheel` under the cursor
78+
- events bubble up through parent containers until one handler returns `true`
79+
- horizontal wheel is deferred; current surface exposes vertical `deltaY` only
80+
81+
## Virtual List Notes
82+
83+
- `createVirtualListController(...)` uses row-based virtualization with stable slot nodes
84+
- avoid large overscan in normal-flow containers; it can increase container height and create layout feedback loops
85+
- runtime guard now disables overscan automatically when it detects repeated viewport-growth feedback
86+
- for high overscan, prefer a clipped/fixed-height viewport container so slot count does not affect layout height
87+
7088
## Colors
7189

7290
Use `COLORS` from the library.

docs/state-events-lifecycle.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ onKey(key: string, callback: () => void): void
7272
- Keyboard input routed to focused node first
7373
- If focused node consumes key event, global `onKey` handler does not run
7474

75+
## Wheel Routing Model
76+
77+
- wheel events use SGR mouse input and dispatch to `onWheel` on `Row`/`Column` containers
78+
- dispatch starts at deepest container under cursor with a wheel handler
79+
- if handler returns `true`, bubbling stops; otherwise event bubbles to parent containers
80+
- current payload: `{ x, y, deltaY, raw }` with vertical `deltaY` only
81+
7582
## Input behavior
7683

7784
For focused `Input` node:
@@ -111,3 +118,12 @@ onKey("q", quit);
111118

112119
- Keep long-lived nodes and mutate them with `setText`, `setStyle`, or signals
113120
- Rebuilding whole subtrees every tick changes tree shape and forces Rust tree rebuilds
121+
122+
## Virtualization Rule
123+
124+
- for large scrolling lists, keep a fixed slot pool (`setChildren` only on viewport-size changes)
125+
- bind slot content by updating text/style on stable nodes instead of creating/removing nodes per scroll tick
126+
- row-based slicing is done in JS by mapping scroll rows to visible line ranges
127+
- if virtualized rows are normal-flow children, overscan can feed back into layout (more slots -> taller viewport -> more slots)
128+
- virtual list now auto-disables overscan when this runaway growth pattern is detected and logs a warning
129+
- if you need overscan for smoothness, place virtualized slots in a clipped/fixed-height viewport container

dump/codex-screen.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MODEL gpt-5.4 STATUS idle TOKENS 0 LAT — ──────────────────────────────────────────────────────────────────────────────────────────────────── │ Prompts ─────── │ … … │ … │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╭───────────────────────────────────────────────────────────╮ │ │ │ │ ╰───────────────────────────────────────────────────────────╯ │ … │ MODEL gpt-5.4STATUS idleTOKENS 0LAT —────────────────────────────────────────────────────────────────────────────────────────────────────Prompts───────│AI AGENT Ctrl+N new ↑↓ switch │ Submit a prompt to start the thread. ● New Thread 0 │Ctrl+N creates a new thread. │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │╭──────────────────────────────────────────────────────╮ │ │ █ │ │╰──────────────────────────────────────────────────────╯ │Enter send Tab focustyped█

dump/metrics.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
1200fps | 0.8ms avg (0.3-2.6, p99:2.5) | 35MB | 101 frames
2-
serialize: 0.2ms (0-1.1) [tree→rust]
3-
textSync: 0.2ms (0-1.1, p99:1.1) | ops:2.1 avg 50 max | bytes:63.2 avg 2483 max
4-
rust: 0.3ms (0.1-1.1) [layout+paint]
5-
sync: 0.1ms (0-0.3) [frames→JS]
6-
flush: 0.1ms (0-0.3) [terminal I/O]
1+
238fps | 4.2ms avg (0.7-5.8, p99:5.6) | 10MB | 70 frames
2+
serialize: 0.6ms (0.2-1.8) [tree→rust]
3+
textSync: 0.6ms (0.2-1.8, p99:1.5) | ops:5 avg 128 max | bytes:119.8 avg 1548 max
4+
rust: 1.8ms (0-3.1) [layout+paint]
5+
sync: 0.3ms (0.1-0.6) [frames→JS]
6+
flush: 0.1ms (0-0.8) [terminal I/O]

0 commit comments

Comments
 (0)