@valyrianjs/terminal 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +105 -55
  2. package/dist/ansi.d.ts +20 -4
  3. package/dist/ansi.d.ts.map +1 -1
  4. package/dist/ansi.js +171 -47
  5. package/dist/ansi.js.map +1 -1
  6. package/dist/editor-state.d.ts +22 -0
  7. package/dist/editor-state.d.ts.map +1 -0
  8. package/dist/editor-state.js +110 -0
  9. package/dist/editor-state.js.map +1 -0
  10. package/dist/events.d.ts +1 -4
  11. package/dist/events.d.ts.map +1 -1
  12. package/dist/events.js +15 -38
  13. package/dist/events.js.map +1 -1
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/keymap.d.ts +7 -0
  19. package/dist/keymap.d.ts.map +1 -0
  20. package/dist/keymap.js +133 -0
  21. package/dist/keymap.js.map +1 -0
  22. package/dist/layout.d.ts +10 -1
  23. package/dist/layout.d.ts.map +1 -1
  24. package/dist/layout.js +97 -7
  25. package/dist/layout.js.map +1 -1
  26. package/dist/mouse.d.ts +1 -0
  27. package/dist/mouse.d.ts.map +1 -1
  28. package/dist/mouse.js +24 -1
  29. package/dist/mouse.js.map +1 -1
  30. package/dist/output-writer.d.ts +9 -0
  31. package/dist/output-writer.d.ts.map +1 -0
  32. package/dist/output-writer.js +79 -0
  33. package/dist/output-writer.js.map +1 -0
  34. package/dist/paste.d.ts +7 -0
  35. package/dist/paste.d.ts.map +1 -0
  36. package/dist/paste.js +18 -0
  37. package/dist/paste.js.map +1 -0
  38. package/dist/primitives.d.ts +8 -1
  39. package/dist/primitives.d.ts.map +1 -1
  40. package/dist/primitives.js +9 -1
  41. package/dist/primitives.js.map +1 -1
  42. package/dist/render.d.ts +8 -3
  43. package/dist/render.d.ts.map +1 -1
  44. package/dist/render.js +840 -67
  45. package/dist/render.js.map +1 -1
  46. package/dist/runtime.d.ts +29 -0
  47. package/dist/runtime.d.ts.map +1 -0
  48. package/dist/runtime.js +215 -0
  49. package/dist/runtime.js.map +1 -0
  50. package/dist/scheduler.d.ts +8 -0
  51. package/dist/scheduler.d.ts.map +1 -0
  52. package/dist/scheduler.js +24 -0
  53. package/dist/scheduler.js.map +1 -0
  54. package/dist/session.d.ts.map +1 -1
  55. package/dist/session.js +729 -199
  56. package/dist/session.js.map +1 -1
  57. package/dist/stream-log.d.ts +40 -0
  58. package/dist/stream-log.d.ts.map +1 -0
  59. package/dist/stream-log.js +73 -0
  60. package/dist/stream-log.js.map +1 -0
  61. package/dist/text.d.ts +3 -0
  62. package/dist/text.d.ts.map +1 -0
  63. package/dist/text.js +19 -0
  64. package/dist/text.js.map +1 -0
  65. package/dist/theme.d.ts +7 -0
  66. package/dist/theme.d.ts.map +1 -0
  67. package/dist/theme.js +254 -0
  68. package/dist/theme.js.map +1 -0
  69. package/dist/tree.d.ts +2 -0
  70. package/dist/tree.d.ts.map +1 -1
  71. package/dist/tree.js +42 -1
  72. package/dist/tree.js.map +1 -1
  73. package/dist/types.d.ts +183 -18
  74. package/dist/types.d.ts.map +1 -1
  75. package/docs/api-reference.md +302 -136
  76. package/docs/assets/quick-note.svg +13 -0
  77. package/docs/cookbook.md +297 -202
  78. package/docs/core-concepts.md +143 -55
  79. package/docs/getting-started.md +209 -90
  80. package/docs/interaction-model.md +95 -61
  81. package/docs/primitive-gallery.md +365 -0
  82. package/docs/session-runtime.md +132 -363
  83. package/docs/valyrian-modules.md +3196 -0
  84. package/llms-full.txt +5357 -0
  85. package/package.json +21 -8
  86. package/src/ansi.ts +269 -0
  87. package/src/clipboard.ts +76 -0
  88. package/src/editor-state.ts +162 -0
  89. package/src/events.ts +163 -0
  90. package/src/index.ts +92 -0
  91. package/src/keymap.ts +151 -0
  92. package/src/layout.ts +282 -0
  93. package/src/mouse.ts +68 -0
  94. package/src/output-writer.ts +93 -0
  95. package/src/paste.ts +23 -0
  96. package/src/primitives.ts +52 -0
  97. package/src/render.ts +1107 -0
  98. package/src/runtime.ts +273 -0
  99. package/src/scheduler.ts +33 -0
  100. package/src/session.ts +1260 -0
  101. package/src/stream-log.ts +96 -0
  102. package/src/text.ts +20 -0
  103. package/src/theme.ts +263 -0
  104. package/src/tree.ts +169 -0
  105. package/src/types.ts +523 -0
  106. package/tsconfig.json +4 -7
  107. package/docs/local-demo.md +0 -28
@@ -1,93 +1,181 @@
1
1
  # Core Concepts
2
2
 
3
- Esta guia explica el modelo mental del paquete antes de entrar al detalle de runtime o referencia.
3
+ This document explains the mental model for ValyrianJS Terminal. Read it after [Getting Started](./getting-started.md), then use the Cookbook, Primitive Gallery, and interaction guides before opening the API Reference as a lookup document.
4
4
 
5
- ## 1. Primitivas terminal-first
5
+ ## Primitives
6
6
 
7
- `valyrianjs-terminal` no renderiza HTML ni componentes de navegador. Sus primitivas generan nodos terminales propios que luego se convierten en texto plano o en un frame ANSI.
7
+ ValyrianJS Terminal renders terminal nodes, not HTML. The public primitives are grouped by role:
8
8
 
9
- Las piezas base se agrupan asi:
9
+ - layout: `Screen`, `Box`, `View`, `Pane`, `Split`, `Fixed`, `Overlay`, `FocusScope`, `Table`, `Row`, `Td`
10
+ - content: `Text`, `LogView`
11
+ - interaction: `Input`, `Editor`, `Button`, `List`, `ScrollView`
10
12
 
11
- - layout: `Screen`, `Box`, `View`, `Text`, `Table`, `Row`, `Td`
12
- - interaccion: `Input`, `Button`, `List`, `ScrollView`
13
+ `Screen` is the usual root. `Box`, `View`, `Pane`, and `Split` compose rows and columns. See [Primitive Gallery](./primitive-gallery.md) for a practical map of every primitive and complete mini apps such as [`examples/docs/primitive-layout-shell.tsx`](../examples/docs/primitive-layout-shell.tsx), which runs with `bun examples/docs/primitive-layout-shell.tsx` and exits with `Ctrl+C`. `Fixed` reserves top, bottom, left, or right regions when it is a direct child of `Screen` or `Pane`. `Overlay` draws a clipped region over the current frame.
13
14
 
14
- Piensalo como un DSL de interfaces de texto, no como una adaptacion visual del DOM.
15
+ `Screen`, `Box`, `View`, `Pane`, and `Split` use block-like layout defaults inside a render or session context. `Box`, `View`, and `Pane` take available width by default and keep content height by default. `Split` takes the available render area by default because it needs both axes to divide cells. Explicit numeric `width` and `height` always win. Use `fill` when a block surface should consume the full available height instead of stacking at content height.
15
16
 
16
- ## 2. Dos modos de trabajo
17
+ ## Static render vs mounted session
17
18
 
18
- Hay dos entrypoints principales:
19
+ There are two main entry points:
19
20
 
20
- - `renderTerminal()` para generar texto plano a partir de un arbol terminal
21
- - `mountTerminal()` para crear una sesion interactiva con foco, teclado, mouse y escritura opcional a streams
21
+ - `renderTerminal()` returns plain text for a terminal tree.
22
+ - `mountTerminal()` creates a `TerminalSession` with focus, dispatch, output, lifecycle, and optional stream integration.
22
23
 
23
- Regla simple:
24
+ Use `renderTerminal()` for static output, examples, and snapshots. Use `mountTerminal()` when a UI needs interaction or repeated renders.
24
25
 
25
- - si solo quieres inspeccionar contenido, usa `renderTerminal()`
26
- - si necesitas interaccion o runtime vivo, usa `mountTerminal()`
26
+ Related example: [`examples/docs/hello.tsx`](../examples/docs/hello.tsx). Run it with `bun examples/docs/hello.tsx`, press `Enter` for details, and quit with `Ctrl+C`.
27
27
 
28
- ## 3. El estado vive fuera de la libreria
28
+ ```tsx
29
+ import { Screen, Text, renderTerminal } from "@valyrianjs/terminal";
29
30
 
30
- La libreria renderiza lo que tu funcion de UI devuelve con el estado actual. No guarda un store de aplicacion por ti.
31
+ const output = renderTerminal(
32
+ <Screen title="Static">
33
+ <Text>Hello terminal</Text>
34
+ </Screen>
35
+ );
31
36
 
32
- Eso significa que:
37
+ console.log(output);
38
+ ```
33
39
 
34
- - tus handlers mutan estado externo
35
- - `mountTerminal()` vuelve a evaluar la funcion de render
36
- - `session.update()` solo hace falta cuando mutaste estado fuera de un handler que ya detona rerender
40
+ ## State lives outside the renderer
37
41
 
38
- ## 4. `id` es la llave de la interaccion
42
+ Keep application state in your app layer. The renderer turns the tree returned by your function into terminal output using the state your app provides.
39
43
 
40
- Los nodos interactivos pueden renderizarse sin `id`, pero pierdes gran parte del control programatico.
44
+ Related example: [`examples/docs/component-composition.tsx`](../examples/docs/component-composition.tsx). Run it with `bun examples/docs/component-composition.tsx`, switch cards with `N/P`, and quit with `Ctrl+C`.
41
45
 
42
- Necesitas `id` estable si quieres:
46
+ ```tsx
47
+ import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
43
48
 
44
- - enfocar con `session.focus(id)`
45
- - activar con `session.click(id)`
46
- - participar de hitboxes por coordenadas
47
- - recibir bien flujos de foco y navegacion
49
+ const state = { count: 0 };
48
50
 
49
- Recomendacion practica: da `id` a todo `Input`, `Button`, `List` y `ScrollView` que vaya a vivir mas de una prueba trivial.
51
+ function App() {
52
+ return (
53
+ <Screen title="Counter">
54
+ <Text>Count: {state.count}</Text>
55
+ <Button id="increment" onpress={() => {
56
+ state.count += 1;
57
+ }}>
58
+ Increment
59
+ </Button>
60
+ </Screen>
61
+ );
62
+ }
50
63
 
51
- ## 5. Foco e hitboxes
64
+ const session = mountTerminal(<App />);
52
65
 
53
- La sesion calcula hitboxes a partir del frame renderizado actual. Eso permite dos clases de interaccion:
66
+ session.focus("increment");
67
+ session.dispatchKey("ENTER");
54
68
 
55
- - por identificador: `focus(id)`, `click(id)`
56
- - por coordenadas: `focusAt(x, y)`, `clickAt(x, y)`
69
+ console.log(session.output());
70
+ session.destroy();
71
+ ```
57
72
 
58
- El foco secuencial tambien sale del arbol actual:
73
+ For larger UIs, Valyrian state modules can hold shared state outside the terminal tree. `createPulseStore` is useful when named transitions make state changes clearer.
59
74
 
60
- - `focusNext()`
61
- - `focusPrev()`
62
- - `dispatchKey("TAB")`
63
- - `dispatchKey("SHIFT_TAB")`
75
+ Related example: [`examples/docs/employees-list.tsx`](../examples/docs/employees-list.tsx). Run it with `bun examples/docs/employees-list.tsx`, choose a person with `J/K` or arrows, and quit with `Ctrl+C`.
64
76
 
65
- Si cambia el arbol, cambia tambien el orden y la geometria del foco.
77
+ ```tsx
78
+ import { createPulseStore } from "valyrian.js/pulses";
79
+ import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
66
80
 
67
- ## 6. Salida plana vs salida ANSI
81
+ const counter = createPulseStore(
82
+ { count: 0 },
83
+ {
84
+ increment(state) {
85
+ state.count += 1;
86
+ }
87
+ }
88
+ );
68
89
 
69
- El paquete maneja dos representaciones utiles:
90
+ function App() {
91
+ return (
92
+ <Screen title="Pulse store">
93
+ <Text>Count: {counter.state.count}</Text>
94
+ <Button id="increment" onpress={() => counter.increment()}>
95
+ Increment
96
+ </Button>
97
+ </Screen>
98
+ );
99
+ }
70
100
 
71
- - salida plana: la devuelven `renderTerminal()` y `session.output()`
72
- - salida ANSI: la devuelve `session.ansiOutput()` y, si activas `ansi: true`, tambien se escribe a `stdout`
101
+ const session = mountTerminal(<App />);
73
102
 
74
- Usa salida plana para snapshots y pruebas. Usa ANSI cuando necesitas cursor, spans visuales, foco o integracion con una terminal real.
103
+ session.focus("increment");
104
+ session.dispatchKey("ENTER");
75
105
 
76
- ## 7. Streams y cleanup
106
+ console.log(session.output());
107
+ session.destroy();
108
+ ```
77
109
 
78
- `mountTerminal()` puede trabajar sin streams, pero si conectas `stdin` y `stdout` la sesion entra en modo runtime real.
110
+ If components read Valyrian reactive state, the terminal refreshes when that state changes. Call `session.update()` for non-reactive state or for an explicit manual refresh.
79
111
 
80
- Cuando montas con `stdin`:
112
+ ## Valyrian.js modules as app-layer state and workflow
81
113
 
82
- - la sesion escucha `data`
83
- - intenta activar raw mode si el stream lo soporta
84
- - intenta restaurarlo al llamar `destroy()`
114
+ Valyrian.js modules in terminal apps stay in the app layer. The terminal adapter renders terminal nodes, handles focus, dispatches input, and exposes session output. Your app still owns routing choices, command design, data loading, persistence, and state transitions.
85
115
 
86
- Por eso `destroy()` no es opcional cuando conectaste streams reales.
116
+ Use `valyrian.js/pulses` and `valyrian.js/flux-store` for shared state, `valyrian.js/forms` for canonical input state, `valyrian.js/request` and `valyrian.js/query` for API and cached reads, `valyrian.js/tasks` for async actions, `valyrian.js/translate` and `valyrian.js/money` for user-facing formatting, and `valyrian.js/native-store` plus `valyrian.js/utils` for local settings and helper logic. When async work changes Valyrian reactive state read by components, the terminal refreshes automatically.
87
117
 
88
- ## 8. Donde seguir
118
+ Read the dedicated guide at [`docs/valyrian-modules.md`](./valyrian-modules.md) for terminal-specific module patterns. Related complete demos: **Operations Workbench** ([`examples/docs/module-state-workbench.tsx`](../examples/docs/module-state-workbench.tsx)), **API Operations Dashboard** ([`examples/docs/module-api-dashboard.tsx`](../examples/docs/module-api-dashboard.tsx)), and **Billing/Settings Wizard** ([`examples/docs/module-form-workflow.tsx`](../examples/docs/module-form-workflow.tsx)). Run them with `bun examples/docs/module-state-workbench.tsx`, `bun examples/docs/module-api-dashboard.tsx`, or `bun examples/docs/module-form-workflow.tsx`, try the keys shown in each footer, and quit with `Ctrl+C`.
89
119
 
90
- - `docs/interaction-model.md` explica que hace cada primitiva interactiva
91
- - `docs/session-runtime.md` explica lifecycle, streams, clipboard, coordenadas y mouse
92
- - `docs/cookbook.md` muestra recetas cortas para tareas comunes
93
- - `docs/api-reference.md` sirve como consulta puntual
120
+ ## Stable ids and focus
121
+
122
+ Interactive nodes can render without an `id`, but stable ids are required for most programmatic interaction.
123
+
124
+ Use stable ids when you need:
125
+
126
+ - `session.focus(id)`
127
+ - `session.click(id)`
128
+ - `focusAt(x, y)` or `clickAt(x, y)` to resolve a hitbox consistently
129
+ - keyboard traversal with predictable focus order
130
+
131
+ Practical rule: give every long-lived `Input`, `Editor`, `Button`, `List`, and `ScrollView` an `id`.
132
+
133
+ Focus belongs to the `TerminalSession`; app state remains yours. `FocusScope` can bound sequential traversal inside a local group, and `Overlay trapFocus` can keep traversal inside an overlay while it is present.
134
+
135
+ ## Styling and visual states
136
+
137
+ Use `theme.styles` for named recipes with hex colors, border, and padding. The default theme already gives `Button`, `Input`, and `List` app-like base, focus, selection, current, hover, loading, and disabled styles, so `<Button><Text>Ok</Text></Button>` renders as a styled control without per-instance chrome. Components still use `style` for their base recipe, `styles` to map visual states to recipes, and `state` when the app owns a visual status. Raw span serialization tokens belong to low-level renderer integrations; normal styling uses semantic styles and hex colors.
138
+
139
+ Related example: [`examples/docs/style-system.tsx`](../examples/docs/style-system.tsx). Run it with `bun examples/docs/style-system.tsx`, try `Tab`, `Enter`, and `W`, and quit with `Ctrl+C`.
140
+
141
+ Common states include `focus`, `hover`, `disabled`, and `loading`. Controls can use `pressed`, `checked`, `unchecked`, and `indeterminate`. Navigation can use `selected`, `current`, `expanded`, and `collapsed`. Entry fields can use `invalid`, `valid`, `readonly`, `placeholder`, `selection`, `editing`, and `submitted`. Content can use `empty`, `error`, `warning`, `success`, and `muted`. Pointer-style composition can use `dragging`, `dropTarget`, and `capturing`.
142
+
143
+ Use `style={{ background: "#111111" }}` or a theme recipe to fill a surface. `margin` is not included; compose spacing with layout, padding, and explicit text when needed.
144
+
145
+ ## Plain text vs ANSI output
146
+
147
+ The package exposes two output representations:
148
+
149
+ - plain text: `renderTerminal()` and `session.output()`
150
+ - ANSI output: `session.ansiOutput()` and app-runtime `stdout` writes
151
+
152
+ Plain text belongs to `renderTerminal()`, `session.output()`, snapshots, and scripted checks. ANSI output is for runtime integration when a terminal needs cursor movement or style spans.
153
+
154
+ Keep ordinary rendered content as text. Do not build normal app behavior by placing escape sequences in `Text`, input values, list rows, or log entries. Use semantic spans, themes, and the documented runtime output paths instead.
155
+
156
+ Style borders are opt-in through style recipes. Plain output remains readable, and ANSI output applies style spans when supported.
157
+
158
+ ## Cleanup and lifecycle
159
+
160
+ `mountTerminal()` can run without streams, which is useful for examples and scripted interaction. When you connect real streams, cleanup matters.
161
+
162
+ The session lifecycle is:
163
+
164
+ 1. mount with `mountTerminal()`
165
+ 2. read `output()` or `ansiOutput()`
166
+ 3. dispatch input or mutate state
167
+ 4. call `update()` when needed
168
+ 5. call `destroy()` when finished
169
+
170
+ When `stdin` is connected, the session may attach listeners, enable raw mode when supported, and resume the stream. `destroy()` removes listeners and restores terminal state where the connected streams support it.
171
+
172
+ Connected streams follow terminal and runtime capabilities. Use [Session Runtime](./session-runtime.md) when integrating with `stdin`, `stdout`, resize events, clipboard adapters, or mouse input.
173
+
174
+ ## Where to go next
175
+
176
+ - [Cookbook](./cookbook.md) for practical recipes.
177
+ - [Primitive Gallery](./primitive-gallery.md) for choosing and composing public terminal primitives.
178
+ - [Interaction Model](./interaction-model.md) for focus, keys, mouse, and event payloads.
179
+ - [Session Runtime](./session-runtime.md) for common usage and advanced host integration.
180
+ - [Valyrian Modules](./valyrian-modules.md) for terminal app patterns with Valyrian.js modules.
181
+ - [API Reference](./api-reference.md) for props and session methods.
@@ -1,148 +1,267 @@
1
1
  # Getting Started
2
2
 
3
- Esta guia cubre el happy path minimo para empezar con `valyrianjs-terminal`.
3
+ This guide creates a first functional terminal UI. You will configure JSX, create `main.tsx`, render a static frame, then mount an interactive session.
4
4
 
5
- ## 1. Instala los paquetes
5
+ ## What you will build
6
+
7
+ You will build a small note UI with:
8
+
9
+ - a static render path for snapshots or one-off output
10
+ - an interactive input path for keyboard-driven sessions
11
+ - a simple bridge from local state to Valyrian state modules when the UI grows
12
+
13
+ ## Install
6
14
 
7
15
  ```bash
8
- npm install valyrianjs-terminal valyrian.js
16
+ npm install @valyrianjs/terminal valyrian.js
9
17
  ```
10
18
 
11
- ## 2. Empieza con render estatico
19
+ ## Configure JSX
12
20
 
13
- Primero valida layout y contenido sin streams ni runtime interactivo.
21
+ Add this JSX config to your TypeScript configuration so TSX files can use Valyrian's automatic JSX runtime:
14
22
 
15
- ```tsx
16
- /** @jsx v */
17
- /** @jsxFrag v.fragment */
23
+ ```json
24
+ {
25
+ "compilerOptions": {
26
+ "jsx": "react-jsx",
27
+ "jsxImportSource": "valyrian.js"
28
+ }
29
+ }
30
+ ```
31
+
32
+ Manual `v(...)` nodes still work, but the examples below use TSX.
18
33
 
19
- import { v } from "valyrian.js";
20
- import { Screen, Text, renderTerminal } from "valyrianjs-terminal";
34
+ ## Create `main.tsx`
35
+
36
+ Create `main.tsx` with a render-only first pass:
37
+
38
+ ```tsx
39
+ import { Screen, Text, renderTerminal } from "@valyrianjs/terminal";
21
40
 
22
41
  const output = renderTerminal(
23
- <Screen title="Status">
42
+ <Screen title="First terminal UI">
24
43
  <Text>Hello terminal</Text>
25
- <Text>Status: ok</Text>
44
+ <Text>Status: ready</Text>
26
45
  </Screen>
27
46
  );
28
47
 
29
48
  console.log(output);
30
49
  ```
31
50
 
32
- Usa este camino cuando quieras:
51
+ ## Run it
33
52
 
34
- - generar snapshots
35
- - validar layout
36
- - probar salida plana sin conectar `stdin` ni `stdout`
53
+ Use your TypeScript runner of choice for the run command. With Bun:
37
54
 
38
- ## 3. Sube a una sesion interactiva minima
55
+ ```bash
56
+ bun run main.tsx
57
+ ```
39
58
 
40
- Cuando ya necesitas editar texto, manejar foco o reaccionar a teclado, cambia a `mountTerminal()`.
59
+ Expected plain output:
41
60
 
42
- ```tsx
43
- /** @jsx v */
44
- /** @jsxFrag v.fragment */
61
+ ```text
62
+ First terminal UI
63
+ Hello terminal
64
+ Status: ready
65
+ ```
66
+
67
+ ## Static rendering with `renderTerminal`
68
+
69
+ Use `renderTerminal()` for deterministic plain-text output:
70
+
71
+ - snapshots
72
+ - examples in docs
73
+ - layout checks
74
+ - non-interactive command output
75
+
76
+ This path fits snapshots, documentation examples, layout checks, and non-interactive command output. Move to `mountTerminal()` when the experience needs focus, keyboard input, clipboard behavior, mouse behavior, or repeated renders.
77
+
78
+ ## Interactive sessions with `mountTerminal`
45
79
 
46
- import { v } from "valyrian.js";
47
- import { Button, Input, Screen, Text, mountTerminal } from "valyrianjs-terminal";
80
+ Switch to `mountTerminal()` when the UI needs focus, keyboard input, clipboard behavior, mouse behavior, or repeated renders.
81
+
82
+ Replace `main.tsx` with this interactive version:
83
+
84
+ Full example: [`examples/docs/interactive-note.tsx`](../examples/docs/interactive-note.tsx). Run it with `bun examples/docs/interactive-note.tsx`, type a note, press `Enter` to save, and quit with `Ctrl+C`.
85
+
86
+ ```tsx
87
+ import { Button, Input, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
48
88
 
49
89
  const state = { value: "", saved: "" };
50
90
 
51
- const session = mountTerminal(() => (
52
- <Screen title="Quick Note">
53
- <Input
54
- id="note"
55
- value={state.value}
56
- placeholder="Write a quick note"
57
- onchange={(event) => {
58
- state.value = event.value;
59
- }}
60
- onsubmit={(event) => {
61
- state.saved = event.value;
62
- }}
63
- />
64
- <Button
65
- id="save"
66
- onpress={() => {
67
- state.saved = state.value;
68
- }}
69
- >
70
- Save
71
- </Button>
72
- <Text>{state.saved || "Nothing saved yet"}</Text>
73
- </Screen>
74
- ));
91
+ function App() {
92
+ return (
93
+ <Screen title="Quick note">
94
+ <Input
95
+ id="note"
96
+ value={state.value}
97
+ placeholder="Write a quick note"
98
+ onchange={(event) => {
99
+ state.value = event.value;
100
+ }}
101
+ onsubmit={(event) => {
102
+ state.saved = event.value;
103
+ }}
104
+ />
105
+ <Button
106
+ id="save"
107
+ onpress={() => {
108
+ state.saved = state.value;
109
+ }}
110
+ >
111
+ Save
112
+ </Button>
113
+ <Text>{state.saved || "Nothing saved yet"}</Text>
114
+ </Screen>
115
+ );
116
+ }
117
+
118
+ const session = mountTerminal(<App />);
75
119
 
76
120
  session.focus("note");
77
121
  session.dispatchKey("H");
78
122
  session.dispatchKey("i");
123
+ session.dispatchKey("ENTER");
79
124
 
80
125
  console.log(session.output());
126
+ session.destroy();
81
127
  ```
82
128
 
83
- Regla practica: da `id` estables a `Input`, `Button`, `List` y `ScrollView` si quieres usar foco, coordenadas o interaccion programatica.
129
+ This example uses programmatic key dispatch for a complete first project. In a real terminal session, pass compatible `stdin` and `stdout` options and call `destroy()` when the session is done.
84
130
 
85
- ## 4. Entiende el lifecycle minimo
131
+ For styled surfaces and visual states, run `bun examples/docs/style-system.tsx`, try `Tab`, `Enter`, and `W`, and quit with `Ctrl+C`; or open [`examples/docs/style-system.tsx`](../examples/docs/style-system.tsx). For responsive columns and rows, run `bun examples/docs/responsive-split.tsx`, resize the terminal to change the layout, and quit with `Ctrl+C`; or open [`examples/docs/responsive-split.tsx`](../examples/docs/responsive-split.tsx).
86
132
 
87
- La sesion no guarda tu estado de negocio; vuelve a evaluar la funcion de render con el estado externo actual.
133
+ ## Add state when the UI grows
88
134
 
89
- ```tsx
90
- /** @jsx v */
91
- /** @jsxFrag v.fragment */
135
+ Small examples can use local state. As the UI grows, keep application state in your app layer and use Valyrian state modules such as `createPulseStore` when named transitions fit your app.
136
+
137
+ Related example: [`examples/docs/component-composition.tsx`](../examples/docs/component-composition.tsx). Run it with `bun examples/docs/component-composition.tsx`, switch cards with `N/P`, and quit with `Ctrl+C`.
92
138
 
93
- import { v } from "valyrian.js";
94
- import { Screen, Text, mountTerminal } from "valyrianjs-terminal";
139
+ ```tsx
140
+ import { createPulseStore } from "valyrian.js/pulses";
141
+ import { Button, Screen, Text, mountTerminal } from "@valyrianjs/terminal";
142
+
143
+ const counter = createPulseStore(
144
+ { count: 0 },
145
+ {
146
+ increment(state) {
147
+ state.count += 1;
148
+ }
149
+ }
150
+ );
95
151
 
96
- const state = { count: 0 };
152
+ function App() {
153
+ return (
154
+ <Screen title="Counter">
155
+ <Text>Count: {counter.state.count}</Text>
156
+ <Button id="increment" onpress={() => counter.increment()}>
157
+ Increment
158
+ </Button>
159
+ </Screen>
160
+ );
161
+ }
97
162
 
98
- const session = mountTerminal(() => (
99
- <Screen>
100
- <Text>Count: {state.count}</Text>
101
- </Screen>
102
- ));
163
+ const session = mountTerminal(<App />);
103
164
 
104
- state.count += 1;
105
- session.update();
165
+ session.focus("increment");
166
+ session.dispatchKey("ENTER");
106
167
 
107
168
  console.log(session.output());
108
169
  session.destroy();
109
170
  ```
110
171
 
111
- Resumen rapido:
172
+ ValyrianJS Terminal renders the current state and reports events while your app layer owns product state, routing, and workflow choices.
112
173
 
113
- - `output()` devuelve el frame actual en texto plano
114
- - `ansiOutput()` devuelve el frame actual como ANSI completo
115
- - `update()` reevalua la funcion de render
116
- - `destroy()` desmonta listeners y limpia `stdin` cuando aplica
174
+ ## Compose reusable components
117
175
 
118
- ## 5. Cuando conectar streams
176
+ Terminal UIs are regular Valyrian component trees. Extract small components when a screen repeats a pattern, pass scalar props for simple labels, pass object props for domain rows, and render arrays as lists of components.
119
177
 
120
- Conecta `stdin`, `stdout` y `ansi: true` solo cuando ya vas a correr una UI real en terminal.
178
+ Full example: [`examples/docs/component-composition.tsx`](../examples/docs/component-composition.tsx). Run it with `bun examples/docs/component-composition.tsx`, switch cards with `N/P`, and quit with `Ctrl+C`.
121
179
 
122
180
  ```tsx
123
- /** @jsx v */
124
- /** @jsxFrag v.fragment */
181
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
125
182
 
126
- import { v } from "valyrian.js";
127
- import { Screen, Text, mountTerminal } from "valyrianjs-terminal";
183
+ function StatusBadge() {
184
+ return <Text>Status: Ready</Text>;
185
+ }
128
186
 
129
- const session = mountTerminal(() => (
130
- <Screen title="Live App">
131
- <Text>Streaming to the terminal</Text>
132
- </Screen>
133
- ), {
134
- stdin: process.stdin,
135
- stdout: process.stdout,
136
- ansi: true
137
- });
187
+ function Greeting({ name }: { name: string }) {
188
+ return <Text>Hello, {name}</Text>;
189
+ }
190
+
191
+ function App() {
192
+ return (
193
+ <Screen title="Components">
194
+ <StatusBadge />
195
+ <Greeting name="Maya" />
196
+ </Screen>
197
+ );
198
+ }
199
+
200
+ const session = mountTerminal(<App />);
201
+
202
+ console.log(session.output());
203
+ session.destroy();
138
204
  ```
139
205
 
140
- Para pruebas o snapshots, normalmente basta con `renderTerminal()` o `mountTerminal()` sin streams.
206
+ `StatusBadge` demonstrates a component with no props: it always renders the same terminal content. `Greeting` demonstrates a scalar prop: the parent keeps the value and passes a plain string into the reusable component.
207
+
208
+ Full example: [`examples/docs/employees-list.tsx`](../examples/docs/employees-list.tsx). Run it with `bun examples/docs/employees-list.tsx`, choose a person with `J/K` or arrows, and quit with `Ctrl+C`.
209
+
210
+ ```tsx
211
+ import { Screen, Text, mountTerminal } from "@valyrianjs/terminal";
212
+
213
+ type EmployeeData = {
214
+ name: string;
215
+ role: string;
216
+ };
217
+
218
+ const employees: EmployeeData[] = [
219
+ { name: "Ana", role: "Design" },
220
+ { name: "Luis", role: "Support" },
221
+ { name: "Maya", role: "Engineering" }
222
+ ];
223
+
224
+ function Employee({ data }: { data: EmployeeData }) {
225
+ return <Text>{`${data.name} — ${data.role}`}</Text>;
226
+ }
227
+
228
+ function Employees({ items }: { items: EmployeeData[] }) {
229
+ return (
230
+ <>
231
+ {items.map((employee) => (
232
+ <Employee data={employee} />
233
+ ))}
234
+ </>
235
+ );
236
+ }
237
+
238
+ function App() {
239
+ return (
240
+ <Screen title="Team">
241
+ <Employees items={employees} />
242
+ </Screen>
243
+ );
244
+ }
245
+
246
+ const session = mountTerminal(<App />);
247
+
248
+ console.log(session.output());
249
+ session.destroy();
250
+ ```
251
+
252
+ `Employee data={...}` demonstrates an object prop for a domain row. `Employees items={...}` demonstrates list composition: the parent owns the array and maps each item into a reusable child component.
253
+
254
+ Use the same composition rules as any declarative terminal tree:
255
+
256
+ - Treat components as small JSX functions that receive props and return terminal elements.
257
+ - The app owns state, data loading, routing decisions, and effects. When components read Valyrian reactive state, external async changes refresh the terminal automatically. Use `session.update()` for non-reactive state or an explicit manual refresh.
258
+ - Use stable `id` values when focus or programmatic dispatch targets a specific interactive node.
259
+ - Keep terminal-specific behavior in public primitives, props, and events instead of hidden component side effects.
141
260
 
142
- ## Siguiente lectura
261
+ ## Next steps
143
262
 
144
- - `docs/core-concepts.md`
145
- - `docs/interaction-model.md`
146
- - `docs/session-runtime.md`
147
- - `docs/cookbook.md`
148
- - `docs/api-reference.md`
263
+ - [Core Concepts](./core-concepts.md) for the package mental model.
264
+ - [Cookbook](./cookbook.md) for practical recipes after the first project.
265
+ - [Interaction Model](./interaction-model.md) for focus, keyboard, mouse, and events.
266
+ - [Session Runtime](./session-runtime.md) for stream and host integration.
267
+ - [API Reference](./api-reference.md) for props, payloads, and session methods.