react-retro-display-tty-ansi-ascii 0.1.23
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.
- package/LICENSE +21 -0
- package/README.md +662 -0
- package/dist/index.cjs +6086 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +872 -0
- package/dist/index.d.ts +872 -0
- package/dist/index.js +6029 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +533 -0
- package/package.json +105 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joshua Bellamy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
[](https://github.com/user-attachments/assets/c0ccd3ff-147b-4b8f-b214-47bddb2419d1)
|
|
2
|
+
|
|
3
|
+
# react-retro-display-tty-ansi-ascii
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-retro-display-tty-ansi-ascii)
|
|
6
|
+
[](https://test-station.smysnk.com/projects/react-retro-display-tty-ansi-ascii)
|
|
7
|
+
[](https://test-station.smysnk.com/projects/react-retro-display-tty-ansi-ascii)
|
|
8
|
+
[](https://test-station.smysnk.com/projects/react-retro-display-tty-ansi-ascii)
|
|
9
|
+
|
|
10
|
+
Storybook: [smysnk.github.io/react-retro-display-tty-ansi-ascii](https://smysnk.github.io/react-retro-display-tty-ansi-ascii/)
|
|
11
|
+
|
|
12
|
+
`react-retro-display-tty-ansi-ascii` is a React component for calm, terminal-flavored interfaces.
|
|
13
|
+
It can be a read-only display, a controlled editable surface, a controller-driven terminal,
|
|
14
|
+
or a small command prompt without changing visual language. It also understands ANSI styling,
|
|
15
|
+
semantic display color modes, and an xterm-checked terminal behavior surface for real control
|
|
16
|
+
character playback. It can also project itself onto either dark or light LCD glass without
|
|
17
|
+
asking the whole app shell to follow.
|
|
18
|
+
|
|
19
|
+
Latest test report: [test-station.smysnk.com/projects/react-retro-display-tty-ansi-ascii](https://test-station.smysnk.com/projects/react-retro-display-tty-ansi-ascii)
|
|
20
|
+
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
23
|
+
Install the package, bring in the shared stylesheet, and start with the simplest thing:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install react-retro-display-tty-ansi-ascii
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { RetroScreen } from "react-retro-display-tty-ansi-ascii";
|
|
31
|
+
import "react-retro-display-tty-ansi-ascii/styles.css";
|
|
32
|
+
|
|
33
|
+
export function StatusCard() {
|
|
34
|
+
return (
|
|
35
|
+
<RetroScreen
|
|
36
|
+
mode="value"
|
|
37
|
+
value="SYSTEM READY"
|
|
38
|
+
color="#97ff9b"
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That is the whole entry point.
|
|
45
|
+
You hand the component a mode, a value or controller when needed, and let it handle the grid,
|
|
46
|
+
wrapping, cursor rendering, and terminal feel.
|
|
47
|
+
|
|
48
|
+
## Display Padding
|
|
49
|
+
|
|
50
|
+
Use `displayPadding` when the screen content should sit tighter to the glass or breathe a little
|
|
51
|
+
more. The prop accepts:
|
|
52
|
+
|
|
53
|
+
- a number for uniform pixel padding
|
|
54
|
+
- a CSS length string for uniform padding
|
|
55
|
+
- an object with `block` and `inline`
|
|
56
|
+
- an object with per-side `top`, `right`, `bottom`, and `left`
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<RetroScreen mode="value" value="Tight framing" displayPadding={8} />
|
|
60
|
+
|
|
61
|
+
<RetroScreen mode="value" value="Room to breathe" displayPadding="1.25rem" />
|
|
62
|
+
|
|
63
|
+
<RetroScreen
|
|
64
|
+
mode="terminal"
|
|
65
|
+
displayPadding={{ block: 10, inline: 14 }}
|
|
66
|
+
value="measured from the padded screen area"
|
|
67
|
+
/>
|
|
68
|
+
|
|
69
|
+
<RetroScreen
|
|
70
|
+
mode="prompt"
|
|
71
|
+
displayPadding={{ top: 6, right: 10, bottom: 12, left: 10 }}
|
|
72
|
+
/>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
RetroScreen also shows a focus glow around the shell by default so editable, prompt, and terminal
|
|
76
|
+
surfaces clearly read as active. Disable it when you want a quieter shell:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
<RetroScreen mode="terminal" focusGlow={false} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Because rows and columns are measured from the visible screen area, tighter padding yields a
|
|
83
|
+
denser grid and looser padding yields fewer cells.
|
|
84
|
+
|
|
85
|
+
## Resizable Panels
|
|
86
|
+
|
|
87
|
+
Use `resizable` when the panel itself should be draggable instead of only responding to layout
|
|
88
|
+
changes around it. The live Storybook demo now shows a visible mouse cursor grabbing the real
|
|
89
|
+
handles, including the optional leading-edge handles, so the docs match the shipped interaction.
|
|
90
|
+
|
|
91
|
+
[](https://github.com/user-attachments/assets/227b03f2-23a1-43eb-91b1-e326988313da)
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<RetroScreen
|
|
95
|
+
mode="terminal"
|
|
96
|
+
resizable="both"
|
|
97
|
+
resizableLeadingEdges
|
|
98
|
+
displayPadding={{ block: 12, inline: 14 }}
|
|
99
|
+
value={[
|
|
100
|
+
"All resize handles are live here.",
|
|
101
|
+
"",
|
|
102
|
+
"Drag left, right, top, bottom, or either corner.",
|
|
103
|
+
"Rows and columns recompute as the panel changes size."
|
|
104
|
+
].join("\n")}
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Reach for:
|
|
109
|
+
|
|
110
|
+
- `resizable="width"` when the panel should only stretch sideways
|
|
111
|
+
- `resizable="height"` when it should stack or collapse vertically
|
|
112
|
+
- `resizable` or `resizable="both"` for freeform terminal panes
|
|
113
|
+
- `resizableLeadingEdges` when left, top, and top-left handles should join the same interaction surface
|
|
114
|
+
|
|
115
|
+
## Light And Dark Surface Modes
|
|
116
|
+
|
|
117
|
+
Use `displaySurfaceMode` when the LCD itself should read like a light instrument panel or a
|
|
118
|
+
dark night-ops surface. This is separate from the host page theme, so the same ANSI-rich
|
|
119
|
+
terminal content can sit inside bright docs, dark dashboards, or a side-by-side comparison view.
|
|
120
|
+
|
|
121
|
+
[](https://github.com/user-attachments/assets/fb758db8-d014-46b3-96fc-cc649bf0c475)
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<RetroScreen
|
|
125
|
+
mode="terminal"
|
|
126
|
+
value={[
|
|
127
|
+
"\u001b[1mLIGHT SURFACE\u001b[0m",
|
|
128
|
+
"\u001b[38;5;160mR\u001b[38;5;214mA\u001b[38;5;190mI\u001b[38;5;45mN\u001b[38;5;39mB\u001b[38;5;141mO\u001b[38;5;201mW\u001b[0m contrast check",
|
|
129
|
+
"\u001b[38;2;194;94;0mamber\u001b[0m \u001b[38;2;0;104;181mblue\u001b[0m \u001b[38;2;108;40;148mviolet\u001b[0m"
|
|
130
|
+
].join("\n")}
|
|
131
|
+
displaySurfaceMode="light"
|
|
132
|
+
displayColorMode="ansi-extended"
|
|
133
|
+
displayPadding={{ block: 12, inline: 14 }}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<RetroScreen
|
|
137
|
+
mode="terminal"
|
|
138
|
+
value={[
|
|
139
|
+
"\u001b[1mDARK SURFACE\u001b[0m",
|
|
140
|
+
"\u001b[38;5;160mR\u001b[38;5;214mA\u001b[38;5;190mI\u001b[38;5;45mN\u001b[38;5;39mB\u001b[38;5;141mO\u001b[38;5;201mW\u001b[0m contrast check",
|
|
141
|
+
"\u001b[38;2;255;176;86mamber\u001b[0m \u001b[38;2;102;198;255mblue\u001b[0m \u001b[38;2;214;145;255mviolet\u001b[0m"
|
|
142
|
+
].join("\n")}
|
|
143
|
+
displaySurfaceMode="dark"
|
|
144
|
+
displayColorMode="ansi-extended"
|
|
145
|
+
displayPadding={{ block: 12, inline: 14 }}
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Reach for `displaySurfaceMode="light"` when the LCD should feel like paper, enamel, or a sunlit
|
|
150
|
+
instrument panel. Keep `displaySurfaceMode="dark"` for the classic terminal-glass look. The
|
|
151
|
+
same ANSI palette will still be remapped for readable contrast against each surface.
|
|
152
|
+
|
|
153
|
+
## Modes Of Use
|
|
154
|
+
|
|
155
|
+
### 1. Quiet output
|
|
156
|
+
|
|
157
|
+
Use `mode="value"` when the display is just there to speak.
|
|
158
|
+
|
|
159
|
+
[](https://github.com/user-attachments/assets/df971b76-3de6-4686-833a-8fad19a66832)
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<RetroScreen
|
|
163
|
+
mode="value"
|
|
164
|
+
value="LINK STABLE\nAwaiting operator input."
|
|
165
|
+
/>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 2. Signal intercept
|
|
169
|
+
|
|
170
|
+
Use a controller when the display should reveal text over time and the cadence matters as much as the message.
|
|
171
|
+
|
|
172
|
+
[](https://github.com/user-attachments/assets/f05998aa-40a0-4c51-9612-025b5e5f5b29)
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { useEffect } from "react";
|
|
176
|
+
import {
|
|
177
|
+
RetroScreen,
|
|
178
|
+
createRetroScreenController
|
|
179
|
+
} from "react-retro-display-tty-ansi-ascii";
|
|
180
|
+
|
|
181
|
+
const controller = createRetroScreenController({
|
|
182
|
+
rows: 5,
|
|
183
|
+
cols: 34,
|
|
184
|
+
cursorMode: "solid"
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
export function WhiteRabbitSignal() {
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
controller.reset();
|
|
190
|
+
controller.write("Wake up, Neo...");
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
return <RetroScreen mode="terminal" controller={controller} />;
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The Storybook version uses timed writes and screen clears so the four-line sequence lands like a late-night intercepted signal instead of a static quote.
|
|
198
|
+
|
|
199
|
+
### 3. Matrix code rain
|
|
200
|
+
|
|
201
|
+
Use the terminal renderer as a dense animated display surface when you want the operators' green rain instead of a message prompt.
|
|
202
|
+
|
|
203
|
+
[](https://github.com/user-attachments/assets/df2c7fdc-cd27-4296-8f70-1373fdfb63aa)
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import type { CSSProperties } from "react";
|
|
207
|
+
import { RetroScreen, createRetroScreenController } from "react-retro-display-tty-ansi-ascii";
|
|
208
|
+
|
|
209
|
+
const controller = createRetroScreenController({ rows: 24, cols: 58 });
|
|
210
|
+
const matrixFontStyle = {
|
|
211
|
+
"--retro-screen-font-family": "\"Matrix\""
|
|
212
|
+
} as CSSProperties;
|
|
213
|
+
|
|
214
|
+
<RetroScreen
|
|
215
|
+
mode="terminal"
|
|
216
|
+
controller={controller}
|
|
217
|
+
gridMode="static"
|
|
218
|
+
rows={24}
|
|
219
|
+
cols={58}
|
|
220
|
+
displayColorMode="ansi-extended"
|
|
221
|
+
color="#8efe8e"
|
|
222
|
+
displayFontScale={1.05}
|
|
223
|
+
displayRowScale={1.08}
|
|
224
|
+
displayPadding={{ block: 12, inline: 14 }}
|
|
225
|
+
style={matrixFontStyle}
|
|
226
|
+
/>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The Storybook demo keeps the glyphs planted on the grid and moves waves of illumination through them so the effect feels closer to the movie than a simple falling-text loop. It uses the bundled `Matrix` font.
|
|
230
|
+
|
|
231
|
+
### 4. Editable drafting
|
|
232
|
+
|
|
233
|
+
Turn on `editable` when you want the same surface to behave like a controlled input.
|
|
234
|
+
|
|
235
|
+
[](https://github.com/user-attachments/assets/5670d23a-c794-46a0-bb40-784f04a2da14)
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { useState } from "react";
|
|
239
|
+
|
|
240
|
+
export function DraftPad() {
|
|
241
|
+
const [value, setValue] = useState("");
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<RetroScreen
|
|
245
|
+
mode="value"
|
|
246
|
+
value={value}
|
|
247
|
+
editable
|
|
248
|
+
autoFocus
|
|
249
|
+
placeholder="Write a line, then press Enter."
|
|
250
|
+
onChange={setValue}
|
|
251
|
+
onSubmit={(submitted) => {
|
|
252
|
+
console.log("submitted:", submitted);
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 5. Terminal output from a controller
|
|
260
|
+
|
|
261
|
+
Use a controller when the display should follow external writes over time.
|
|
262
|
+
|
|
263
|
+
[](https://github.com/user-attachments/assets/57258c51-5afd-4164-87dd-d30b85726a26)
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { useEffect } from "react";
|
|
267
|
+
import {
|
|
268
|
+
RetroScreen,
|
|
269
|
+
createRetroScreenController
|
|
270
|
+
} from "react-retro-display-tty-ansi-ascii";
|
|
271
|
+
|
|
272
|
+
const controller = createRetroScreenController({
|
|
273
|
+
rows: 9,
|
|
274
|
+
cols: 46,
|
|
275
|
+
cursorMode: "hollow"
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
export function StreamedTerminal() {
|
|
279
|
+
useEffect(() => {
|
|
280
|
+
controller.reset();
|
|
281
|
+
controller.writeln("BOOT react-retro-display-tty-ansi-ascii");
|
|
282
|
+
controller.write("\u001b[1mREADY\u001b[0m ansi parser online");
|
|
283
|
+
}, []);
|
|
284
|
+
|
|
285
|
+
return <RetroScreen mode="terminal" controller={controller} />;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
If you already have a terminal-like buffer as a string, `mode="terminal"` also accepts `value`
|
|
290
|
+
or `initialBuffer`.
|
|
291
|
+
|
|
292
|
+
## Live TTY Websocket Bridge
|
|
293
|
+
|
|
294
|
+
`RetroScreen` can also act as the browser-side surface for a real TTY session. The transport
|
|
295
|
+
stays outside the component, while the component handles geometry, keyboard capture, paste,
|
|
296
|
+
focus reporting, mouse reporting, alternate-screen rendering, title updates, and bell metadata.
|
|
297
|
+
|
|
298
|
+
[](https://github.com/user-attachments/assets/8e525d78-ddf1-4604-bdcc-d05f6d817489)
|
|
299
|
+
|
|
300
|
+
The demo sequence is recorded from a live shell session and stages the kind of workload this
|
|
301
|
+
bridge is built for: a live-updating `top` session, a fullscreen `vim` pass, and a `nano`
|
|
302
|
+
screen with help bars and cursor-owned chrome.
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
import {
|
|
306
|
+
RetroScreen,
|
|
307
|
+
createRetroScreenWebSocketSession
|
|
308
|
+
} from "react-retro-display-tty-ansi-ascii";
|
|
309
|
+
|
|
310
|
+
const session = createRetroScreenWebSocketSession({
|
|
311
|
+
url: "ws://127.0.0.1:8787",
|
|
312
|
+
openPayload: {
|
|
313
|
+
cwd: "/workspace",
|
|
314
|
+
term: "xterm-256color"
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
export function LiveShell() {
|
|
319
|
+
return (
|
|
320
|
+
<RetroScreen
|
|
321
|
+
mode="terminal"
|
|
322
|
+
session={session}
|
|
323
|
+
autoFocus
|
|
324
|
+
displayColorMode="ansi-extended"
|
|
325
|
+
displayPadding={{ block: 12, inline: 14 }}
|
|
326
|
+
/>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
For local development, the repo includes a reference `node-pty` websocket backend:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
yarn tty:server
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
By default, that example server starts a themed demo shell rooted at `~/tty-demo` with the
|
|
338
|
+
prompt `operator@retro:~/tty-demo$`, so the live story and the recorded bridge demo share the
|
|
339
|
+
same shell framing.
|
|
340
|
+
|
|
341
|
+
There is also a dedicated Storybook story for this path. It now defaults to the local example
|
|
342
|
+
server at `ws://127.0.0.1:8787`, so if `yarn tty:server` is already running you can open the
|
|
343
|
+
`Live Tty Terminal Bridge` story directly without adding any extra query params.
|
|
344
|
+
|
|
345
|
+
If you want to override the target or the open payload, use:
|
|
346
|
+
|
|
347
|
+
```js
|
|
348
|
+
window.__RETRO_SCREEN_TTY_DEMO__ = {
|
|
349
|
+
url: "ws://127.0.0.1:8787",
|
|
350
|
+
openPayload: {
|
|
351
|
+
cwd: "/Users/josh/play/react-retro-display",
|
|
352
|
+
term: "xterm-256color"
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The example server now supports token checks, origin checks, idle timeouts, payload-size limits,
|
|
358
|
+
and optional command/cwd/env override restrictions. See
|
|
359
|
+
[examples/node-tty-websocket-server/README.md](/Users/josh/play/react-retro-display/examples/node-tty-websocket-server/README.md)
|
|
360
|
+
for the available flags.
|
|
361
|
+
|
|
362
|
+
### 6. Prompt-first interaction
|
|
363
|
+
|
|
364
|
+
Use `mode="prompt"` when the interface should feel like a guided shell.
|
|
365
|
+
|
|
366
|
+
[](https://github.com/user-attachments/assets/75eec8ea-6da7-41f4-98be-f17e5284980a)
|
|
367
|
+
|
|
368
|
+
```tsx
|
|
369
|
+
<RetroScreen
|
|
370
|
+
mode="prompt"
|
|
371
|
+
autoFocus
|
|
372
|
+
promptChar="$"
|
|
373
|
+
acceptanceText="READY"
|
|
374
|
+
rejectionText="DENIED"
|
|
375
|
+
onCommand={async (command) => {
|
|
376
|
+
if (command === "status") {
|
|
377
|
+
return {
|
|
378
|
+
accepted: true,
|
|
379
|
+
response: ["grid synced", "cursor stable"]
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
accepted: false,
|
|
385
|
+
response: "unknown command"
|
|
386
|
+
};
|
|
387
|
+
}}
|
|
388
|
+
/>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Display Buffer And Follow Mode
|
|
392
|
+
|
|
393
|
+
Terminal and prompt surfaces now expose a real display buffer instead of only showing the live
|
|
394
|
+
viewport. That means you can scroll back through recent output, inspect older lines, then return
|
|
395
|
+
to the live tail when you are ready to follow the stream again.
|
|
396
|
+
|
|
397
|
+
Built-in behavior:
|
|
398
|
+
|
|
399
|
+
- `PageUp` and `PageDown` move through the display buffer
|
|
400
|
+
- mouse wheel scrolling moves through the same history
|
|
401
|
+
- `End` returns terminal mode to the live tail
|
|
402
|
+
- auto-follow starts enabled, turns off when you scroll back, and turns back on when you return to the bottom
|
|
403
|
+
|
|
404
|
+
Use `bufferSize` to control how many rows of history the component-managed terminal or prompt
|
|
405
|
+
surface keeps, and `defaultAutoFollow` if you want the view to start detached from the tail.
|
|
406
|
+
|
|
407
|
+
```tsx
|
|
408
|
+
<RetroScreen
|
|
409
|
+
mode="terminal"
|
|
410
|
+
bufferSize={400}
|
|
411
|
+
defaultAutoFollow
|
|
412
|
+
value={[
|
|
413
|
+
"line-01 warm boot",
|
|
414
|
+
"line-02 telemetry stable",
|
|
415
|
+
"line-03 waiting for operator"
|
|
416
|
+
].join("\n")}
|
|
417
|
+
/>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
If you are driving the component with your own controller, configure the underlying buffer size on
|
|
421
|
+
the controller itself:
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
const controller = createRetroScreenController({
|
|
425
|
+
rows: 9,
|
|
426
|
+
cols: 46,
|
|
427
|
+
scrollback: 400
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
<RetroScreen mode="terminal" controller={controller} />
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
The browser suite now covers this path directly, including paging, wheel scrolling, anchored
|
|
434
|
+
scrollback while new lines arrive, and auto-follow recovery back to the live tail.
|
|
435
|
+
|
|
436
|
+
## Auto Resize And Geometry Probing
|
|
437
|
+
|
|
438
|
+
When rows and columns matter to the program inside the display, listen to `onGeometryChange`,
|
|
439
|
+
turn that measurement into a terminal-style reply, and redraw from the reported size. The demo
|
|
440
|
+
below simulates a terminal app issuing `CSI 18 t`, receiving `CSI 8;<rows>;<cols>t`, then
|
|
441
|
+
repainting a full border and centered dimensions every time the panel resizes. The current demo
|
|
442
|
+
shows a visible cursor dragging the real resize handles, pauses if you intervene manually, and
|
|
443
|
+
still cycles through tight screen padding, multiple border alphabets, oversized glyph styles,
|
|
444
|
+
plus every monochrome and ANSI display mode so the same terminal program can be watched under
|
|
445
|
+
different visual projections.
|
|
446
|
+
|
|
447
|
+
[](https://github.com/user-attachments/assets/2dfd68d7-284c-4895-ad5f-fb513cb09c80)
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
import {
|
|
451
|
+
RetroScreen,
|
|
452
|
+
createRetroScreenController
|
|
453
|
+
} from "react-retro-display-tty-ansi-ascii";
|
|
454
|
+
|
|
455
|
+
const controller = createRetroScreenController({
|
|
456
|
+
rows: 9,
|
|
457
|
+
cols: 34,
|
|
458
|
+
cursorMode: "solid"
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
export function ResizingTerminalProbe() {
|
|
462
|
+
return (
|
|
463
|
+
<RetroScreen
|
|
464
|
+
mode="terminal"
|
|
465
|
+
controller={controller}
|
|
466
|
+
displayPadding={{ block: 8, inline: 10 }}
|
|
467
|
+
onGeometryChange={(geometry) => {
|
|
468
|
+
const nextReply = `\u001b[8;${geometry.rows};${geometry.cols}t`;
|
|
469
|
+
|
|
470
|
+
console.log("terminal reply:", nextReply);
|
|
471
|
+
controller.reset();
|
|
472
|
+
controller.resize(geometry.rows, geometry.cols);
|
|
473
|
+
redrawBorderAndMetrics(controller, geometry.rows, geometry.cols);
|
|
474
|
+
}}
|
|
475
|
+
/>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
This is useful for terminal-style dashboards, resize-aware prompts, or retro UIs that need to
|
|
481
|
+
center content, draw frames, or adapt layouts from the actual LCD grid instead of from CSS alone.
|
|
482
|
+
It is also a good place to project `displayColorMode` changes when you want the terminal behavior
|
|
483
|
+
to stay fixed while the display mood shifts around it.
|
|
484
|
+
|
|
485
|
+
## Terminal Color Modes
|
|
486
|
+
|
|
487
|
+
Use `displayColorMode` to decide how semantic terminal color should be projected onto the screen.
|
|
488
|
+
The phosphor modes keep the retro LCD personality even when the source emits ANSI color. The ANSI
|
|
489
|
+
modes preserve more of the source terminal palette.
|
|
490
|
+
|
|
491
|
+
[](https://github.com/user-attachments/assets/3536a8f4-fe5d-401d-af79-1f2e707f2ce5)
|
|
492
|
+
|
|
493
|
+
Available modes:
|
|
494
|
+
|
|
495
|
+
- `phosphor-green`
|
|
496
|
+
- `phosphor-amber`
|
|
497
|
+
- `phosphor-ice`
|
|
498
|
+
- `ansi-classic`
|
|
499
|
+
- `ansi-extended`
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
<RetroScreen
|
|
503
|
+
mode="terminal"
|
|
504
|
+
displayColorMode="ansi-extended"
|
|
505
|
+
value={[
|
|
506
|
+
"\u001b[31mALERT\u001b[0m \u001b[32mlink stable\u001b[0m",
|
|
507
|
+
"\u001b[38;5;196mindexed 196\u001b[0m from the 256-color palette",
|
|
508
|
+
"\u001b[38;2;255;180;120mtruecolor 255,180,120\u001b[0m"
|
|
509
|
+
].join("\n")}
|
|
510
|
+
/>
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Reach for `ansi-classic` when you want the familiar 16-color terminal profile, or
|
|
514
|
+
`ansi-extended` when 256-color and truecolor cells should survive all the way to the display.
|
|
515
|
+
|
|
516
|
+
## ANSI Art Playback
|
|
517
|
+
|
|
518
|
+
Storybook now includes a dedicated `Bad Apple ANSI` demo that loads the real ANSI release,
|
|
519
|
+
decodes the original IBM VGA / CP437 bytes outside the display component, and then feeds those
|
|
520
|
+
bytes into the reusable `RetroScreenAnsiPlayer` wrapper. The player incrementally materializes
|
|
521
|
+
stabilized full-screen `80 x 25` snapshots while the parent owns byte loading and streaming. The
|
|
522
|
+
demo uses the full `BADAPPLE.ANS` payload, not a trimmed excerpt, and tightens the glyph scale so
|
|
523
|
+
the character rows visually sit flush instead of leaving air between scanlines.
|
|
524
|
+
|
|
525
|
+
[](https://github.com/user-attachments/assets/82d505be-5296-4139-ab64-83aae59804ad)
|
|
526
|
+
|
|
527
|
+
The README clip is a 30-second capture of the real ANSI-art playback path, not a separate video renderer.
|
|
528
|
+
|
|
529
|
+
Credit for the original ANSI release goes to [Mistigris](https://mistigris.org/).
|
|
530
|
+
|
|
531
|
+
Open it here:
|
|
532
|
+
[smysnk.github.io/react-retro-display-tty-ansi-ascii/?path=/story/retroscreen-display-buffer--bad-apple-ansi](https://smysnk.github.io/react-retro-display-tty-ansi-ascii/?path=/story/retroscreen-display-buffer--bad-apple-ansi)
|
|
533
|
+
|
|
534
|
+
The Storybook demo is backed by the bundled asset at
|
|
535
|
+
[src/stories/assets/bad-apple.ans](/Users/josh/play/react-retro-display/src/stories/assets/bad-apple.ans).
|
|
536
|
+
|
|
537
|
+
The key wiring for this kind of ANSI-art playback is:
|
|
538
|
+
|
|
539
|
+
```tsx
|
|
540
|
+
<RetroScreenAnsiPlayer
|
|
541
|
+
byteStream={asset.byteStream}
|
|
542
|
+
rows={25}
|
|
543
|
+
cols={80}
|
|
544
|
+
frameDelayMs={asset.frameDelayMs}
|
|
545
|
+
complete
|
|
546
|
+
loop
|
|
547
|
+
displayColorMode="ansi-classic"
|
|
548
|
+
displayPadding={{ block: 8, inline: 12 }}
|
|
549
|
+
displayFontScale={1}
|
|
550
|
+
displayRowScale={2}
|
|
551
|
+
style={{ width: "1010px", height: "642px" }}
|
|
552
|
+
/>
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Use `RetroScreenAnsiPlayer` when a parent is responsible for supplying ANSI bytes or byte chunks,
|
|
556
|
+
including incremental streams. Keep the asset loading outside the display component, pass the
|
|
557
|
+
native `rows` and `cols` so the art is not reflowed, and use `displayFontScale` plus
|
|
558
|
+
`displayRowScale` to tune how the art occupies the grid. `BADAPPLE.ANS` uses lots of upper-half
|
|
559
|
+
and lower-half block characters (`▀` / `▄`), so the demo keeps the font scale neutral and doubles
|
|
560
|
+
the row scale to visually fuse those half-block rows into a continuous image instead of separated
|
|
561
|
+
text lines. For the Bad Apple panel, the demo also uses a container
|
|
562
|
+
size that lands on exact `12x24` cell geometry after padding and bezel chrome, which avoids
|
|
563
|
+
fractional row heights and helps eliminate subpixel seams.
|
|
564
|
+
|
|
565
|
+
## Control-Character Playback
|
|
566
|
+
|
|
567
|
+
The terminal path is now tested against an xterm oracle and can faithfully replay real control
|
|
568
|
+
character effects like carriage return rewrites, erase-in-line, scroll regions, insert-line
|
|
569
|
+
updates, ANSI 16-color, indexed 256-color, and truecolor output.
|
|
570
|
+
|
|
571
|
+
[](https://github.com/user-attachments/assets/682d8e92-7871-420e-9e6d-a6a92b60c0fe)
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
import {
|
|
575
|
+
RetroScreen,
|
|
576
|
+
createRetroScreenController
|
|
577
|
+
} from "react-retro-display-tty-ansi-ascii";
|
|
578
|
+
|
|
579
|
+
const controller = createRetroScreenController({ rows: 6, cols: 34 });
|
|
580
|
+
|
|
581
|
+
controller.write("Downloading fixtures... 12%");
|
|
582
|
+
controller.write("\rDownloading fixtures... 73%");
|
|
583
|
+
controller.write("\r\u001b[32mDownloaded fixtures.\u001b[0m\u001b[K\r\n");
|
|
584
|
+
controller.write("\u001b[2;6r");
|
|
585
|
+
controller.write(
|
|
586
|
+
"\u001b[6;1H\u001b[L\u001b[38;2;255;180;120mrecorded regression fixture\u001b[0m"
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
<RetroScreen
|
|
590
|
+
mode="terminal"
|
|
591
|
+
controller={controller}
|
|
592
|
+
displayColorMode="ansi-extended"
|
|
593
|
+
/>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
The same trace fixtures used in Storybook are also exercised in the terminal verification layers:
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
yarn test:conformance
|
|
600
|
+
yarn test:tty
|
|
601
|
+
yarn test:e2e:tty
|
|
602
|
+
yarn test:e2e
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
The TTY-specific checks skip themselves automatically in environments where `node-pty` cannot
|
|
606
|
+
allocate a TTY session, but they run normally on TTY-capable developer machines and CI runners.
|
|
607
|
+
|
|
608
|
+
## Ease Of Integration
|
|
609
|
+
|
|
610
|
+
The component is intentionally small at the edge:
|
|
611
|
+
|
|
612
|
+
- Start with `mode="value"` when all you need is a beautiful terminal-like readout.
|
|
613
|
+
- Add `editable` if the content should be controlled by React state.
|
|
614
|
+
- Switch to `mode="terminal"` when output is driven by a stream or controller.
|
|
615
|
+
- Switch to `mode="prompt"` when commands and responses should live in one transcript.
|
|
616
|
+
- Listen to `onGeometryChange` if rows and columns matter to the rest of your app.
|
|
617
|
+
|
|
618
|
+
## Storybook
|
|
619
|
+
|
|
620
|
+
Storybook now acts as the living demo surface for the package.
|
|
621
|
+
It includes stories for the main user journeys:
|
|
622
|
+
|
|
623
|
+
- read-only display
|
|
624
|
+
- editable drafting
|
|
625
|
+
- controller-fed terminal output
|
|
626
|
+
- display buffer paging and follow mode
|
|
627
|
+
- resizable live panels with scripted handle demos
|
|
628
|
+
- auto-resize geometry probing
|
|
629
|
+
- live TTY bridge wiring
|
|
630
|
+
- ANSI art playback
|
|
631
|
+
- ANSI styling
|
|
632
|
+
- display color mode projection
|
|
633
|
+
- light and dark surface modes
|
|
634
|
+
- control-character replay fixtures
|
|
635
|
+
- prompt interaction
|
|
636
|
+
- responsive geometry
|
|
637
|
+
- a capture-ready feature tour
|
|
638
|
+
|
|
639
|
+
Run it locally with:
|
|
640
|
+
|
|
641
|
+
```bash
|
|
642
|
+
npm install
|
|
643
|
+
npm run storybook
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
## Development
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
npm install
|
|
650
|
+
npm run build
|
|
651
|
+
npm run test
|
|
652
|
+
npm run test:unit
|
|
653
|
+
npm run storybook
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Useful extra checks:
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
yarn test:tty
|
|
660
|
+
yarn test:e2e:tty
|
|
661
|
+
yarn perf:terminal
|
|
662
|
+
```
|