silvery 0.3.0 → 0.4.0
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/README.md +41 -145
- package/package.json +64 -12
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
package/README.md
CHANGED
|
@@ -2,181 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
**Polished Terminal UIs in React.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Ink-compatible React renderer for terminals — same `Box`, `Text`, `useInput` API you know. Plus everything you wish Ink had.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
npm install silvery react
|
|
9
|
-
```
|
|
7
|
+
> **Note:** Under active development. APIs may change. Feedback welcome.
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```console
|
|
10
|
+
$ npm install silvery react
|
|
11
|
+
```
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
14
|
import { useState } from "react"
|
|
15
|
-
import { render, Box, Text, useInput
|
|
15
|
+
import { render, Box, Text, useInput } from "silvery"
|
|
16
16
|
|
|
17
|
-
function
|
|
18
|
-
const { width } = useContentRect()
|
|
17
|
+
function Counter() {
|
|
19
18
|
const [count, setCount] = useState(0)
|
|
20
|
-
|
|
21
19
|
useInput((input) => {
|
|
22
20
|
if (input === "j") setCount((c) => c + 1)
|
|
23
|
-
if (input === "k") setCount((c) => c - 1)
|
|
24
|
-
if (input === "q") return "exit"
|
|
25
21
|
})
|
|
26
|
-
|
|
27
22
|
return (
|
|
28
|
-
<Box
|
|
29
|
-
<Text bold>Counter ({width} cols wide)</Text>
|
|
23
|
+
<Box borderStyle="round" padding={1}>
|
|
30
24
|
<Text>Count: {count}</Text>
|
|
31
|
-
<Text dim>j/k = change, q = quit</Text>
|
|
32
25
|
</Box>
|
|
33
26
|
)
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
await render(<App />, term).run()
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Renderer
|
|
41
|
-
|
|
42
|
-
### Responsive layout
|
|
43
|
-
|
|
44
|
-
`useContentRect()` returns actual dimensions synchronously -- no post-layout effect, no `{width: 0, height: 0}` on first render. Components adapt to their available space immediately.
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
function Responsive() {
|
|
48
|
-
const { width } = useContentRect()
|
|
49
|
-
return width > 80 ? <FullDashboard /> : <CompactView />
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
### Scrollable containers
|
|
54
|
-
|
|
55
|
-
`overflow="scroll"` with `scrollTo` -- the framework handles measurement, clipping, and scroll position. No manual virtualization needed.
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
<Box height={20} overflow="scroll" scrollTo={selectedIndex}>
|
|
59
|
-
{items.map((item) => (
|
|
60
|
-
<Card key={item.id} item={item} />
|
|
61
|
-
))}
|
|
62
|
-
</Box>
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Per-node dirty tracking
|
|
66
|
-
|
|
67
|
-
Seven independent dirty flags per node. When a user presses a key, only the affected nodes re-render -- bypassing React reconciliation entirely for unchanged subtrees. Typical interactive updates complete in ~170 microseconds for 1000 nodes, compared to full-tree re-renders.
|
|
68
|
-
|
|
69
|
-
### Multi-target rendering
|
|
70
|
-
|
|
71
|
-
Terminal today, Canvas 2D and DOM experimental. Same React components, different rendering backends.
|
|
72
|
-
|
|
73
|
-
## Framework Layers (Optional)
|
|
74
|
-
|
|
75
|
-
### Input layer stack
|
|
76
|
-
|
|
77
|
-
DOM-style event bubbling with modal isolation. Opening a dialog automatically captures input -- no manual guard checks in every handler.
|
|
78
|
-
|
|
79
|
-
```tsx
|
|
80
|
-
<InputLayerProvider>
|
|
81
|
-
<Board />
|
|
82
|
-
{isOpen && <Dialog />} {/* Dialog captures input; Board doesn't see it */}
|
|
83
|
-
</InputLayerProvider>
|
|
29
|
+
await render(<Counter />).run()
|
|
84
30
|
```
|
|
85
31
|
|
|
86
|
-
###
|
|
32
|
+
### Familiar
|
|
87
33
|
|
|
88
|
-
|
|
34
|
+
- **React 18 + 19** — hooks, refs, effects, suspense — all works
|
|
35
|
+
- **Flexbox layout** — `Box` with `flexDirection`, `padding`, `gap`, `flexGrow`, just like Ink
|
|
36
|
+
- **Ink/Chalk compatible** — same component model, `@silvery/ink` compatibility layer for migration
|
|
89
37
|
|
|
90
|
-
###
|
|
38
|
+
### Better
|
|
91
39
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
SGR mouse protocol with DOM-style event props -- `onClick`, `onMouseDown`, `onWheel`, hit testing, drag support.
|
|
104
|
-
|
|
105
|
-
### Multi-line text editing
|
|
106
|
-
|
|
107
|
-
Built-in `TextArea` with word wrap, scrolling, cursor movement, selection, and undo/redo via `EditContext`.
|
|
108
|
-
|
|
109
|
-
### 30+ built-in components
|
|
110
|
-
|
|
111
|
-
TextArea, TextInput, VirtualList, SelectList, Table, CommandPalette, ModalDialog, Tabs, TreeView, SplitView, Toast, Image, and more -- all with built-in scrolling, focus, and input handling.
|
|
112
|
-
|
|
113
|
-
### Theme system
|
|
114
|
-
|
|
115
|
-
`@silvery/theme` with 38 built-in palettes and semantic color tokens (`$primary`, `$error`, `$border`, etc.) that adapt automatically.
|
|
116
|
-
|
|
117
|
-
### TEA state machines
|
|
118
|
-
|
|
119
|
-
Optional [Elm Architecture](https://guide.elm-lang.org/architecture/) alongside React hooks. Pure `(action, state) -> [state, effects]` functions for testable, replayable, undoable UI logic.
|
|
40
|
+
- **Smaller install** — ~177 KB gzipped all included (Ink 6 pulls 16MB into node_modules)
|
|
41
|
+
- **Pure TypeScript, zero native deps** — no WASM, no build steps — works on Alpine, CI, Docker, everywhere
|
|
42
|
+
- **Incremental rendering** — per-node dirty tracking, [~100x faster interactive updates](tests/perf/render.bench.ts)
|
|
43
|
+
- **Responsive layout** — `useContentRect()` returns actual dimensions synchronously during render
|
|
44
|
+
- **Dynamic scrollback** — renders (and re-renders!) into the terminal's scroll history, not just alternate screen
|
|
45
|
+
- **Scrollable containers** — `overflow="scroll"` with automatic measurement and clipping
|
|
46
|
+
- **Theme system** — 38 palettes, semantic design/color tokens (`$primary`, `$error`), auto-detects terminal colors
|
|
47
|
+
- **30+ components** — TextInput, TextArea, SelectList, VirtualList, Table, Tabs, CommandPalette, ModalDialog, Toast, and more
|
|
48
|
+
- **Focus system** — scoped focus, arrow-key directional nav, click-to-focus
|
|
49
|
+
- **Extremely composable** — use as just a renderer (`render`), add a runtime (`run`), or build full apps (`createApp`). Mix with any React state library (useState, Zustand, Jotai, Redux). Swap terminal backends (real TTY, headless, xterm.js emulator) for testing. Embed silvery components in existing CLIs. Use the layout engine standalone. Render to terminal, or (experimental) Canvas, or DOM
|
|
50
|
+
- **Most complete terminal protocol support** — 100+ escape sequences, all auto-negotiated: 12 OSC (hyperlinks, clipboard, palette, text sizing, semantic prompts, notifications), 35+ CSI (cursor, mouse modes, paste, focus, sync output, device queries), 50+ SGR (6 underline styles, underline colors, truecolor, 256-color), full Kitty keyboard (5 flags), full SGR mouse (any-event, drag, wheel)
|
|
120
51
|
|
|
121
52
|
## Packages
|
|
122
53
|
|
|
123
|
-
| Package
|
|
124
|
-
|
|
|
125
|
-
|
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
| [`@silvery/theme`](packages/theme) | Theming with 38 palettes |
|
|
130
|
-
| [`@silvery/tea`](packages/tea) | TEA state machine store |
|
|
131
|
-
| [`@silvery/compat`](packages/compat) | Ink/Chalk compatibility layers |
|
|
132
|
-
| [`@silvery/test`](packages/test) | Testing utilities and locators |
|
|
133
|
-
|
|
134
|
-
## Compatibility
|
|
135
|
-
|
|
136
|
-
`silvery/ink` and `silvery/chalk` provide compatibility layers for existing React terminal apps. The core API (`Box`, `Text`, `useInput`, `render`) is intentionally familiar -- most existing code works with minimal changes. See the [migration guide](docs/guide/migration.md) for details.
|
|
137
|
-
|
|
138
|
-
## When to Use Silvery
|
|
139
|
-
|
|
140
|
-
Silvery is designed for **complex interactive TUIs** — dashboards, editors, kanban boards, chat interfaces. If you need scrollable containers, mouse support, spatial focus, or components that adapt to their size, Silvery provides these out of the box.
|
|
141
|
-
|
|
142
|
-
For simple one-shot CLI prompts or spinners, mature alternatives with larger plugin ecosystems may be a better fit today.
|
|
54
|
+
| Package | Description |
|
|
55
|
+
| --------------- | ------------------------------------------------------------------------------------------ |
|
|
56
|
+
| `silvery` | Components, hooks, renderer — the one package you need |
|
|
57
|
+
| `@silvery/test` | Testing utilities and locators |
|
|
58
|
+
| `@silvery/ink` | Ink compatibility layer |
|
|
59
|
+
| `@silvery/tea` | Optional [TEA](https://guide.elm-lang.org/architecture/) state management for complex apps |
|
|
143
60
|
|
|
144
61
|
## Ecosystem
|
|
145
62
|
|
|
146
|
-
| Project | What
|
|
147
|
-
| ------------------------------------------ |
|
|
148
|
-
| [Termless](https://termless.dev) | Headless terminal testing
|
|
149
|
-
| [Flexily](https://beorn.github.io/flexily) | Pure JS flexbox layout engine (Yoga-compatible, zero WASM)
|
|
150
|
-
| [Loggily](https://beorn.github.io/loggily) | Debug + structured logging + tracing
|
|
151
|
-
|
|
152
|
-
See the [roadmap](https://silvery.dev/roadmap) for what's next.
|
|
153
|
-
|
|
154
|
-
## Performance
|
|
63
|
+
| Project | What |
|
|
64
|
+
| ------------------------------------------ | ------------------------------------------------------------- |
|
|
65
|
+
| [Termless](https://termless.dev) | Headless terminal testing — like Playwright for terminal apps |
|
|
66
|
+
| [Flexily](https://beorn.github.io/flexily) | Pure JS flexbox layout engine (Yoga-compatible, zero WASM) |
|
|
67
|
+
| [Loggily](https://beorn.github.io/loggily) | Debug + structured logging + tracing |
|
|
155
68
|
|
|
156
|
-
|
|
69
|
+
## Coming
|
|
157
70
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
| Cold render (1000 components) | 463 ms | 541 ms |
|
|
162
|
-
| Typical interactive update (1000 nodes) | 169 us | 20.7 ms |
|
|
163
|
-
| Layout (50-node kanban) | 57 us | 88 us |
|
|
71
|
+
- **Renderers** — Canvas 2D, Web DOM (experimental today, production later)
|
|
72
|
+
- **Frameworks** — Svelte, Solid.js, Vue adapters
|
|
73
|
+
- **@silvery/tea** — Structured state management with commands, keybindings, effects-as-data
|
|
164
74
|
|
|
165
|
-
**
|
|
166
|
-
|
|
167
|
-
Full re-renders where the entire tree changes are comparable or faster in full-tree renderers (simpler string concatenation vs Silvery's 5-phase pipeline). That trade-off is inherent to supporting responsive layout, and full re-renders are rare in interactive apps.
|
|
168
|
-
|
|
169
|
-
## Documentation
|
|
170
|
-
|
|
171
|
-
Full docs at [silvery.dev](https://silvery.dev) -- getting started guide, API reference, component catalog, and migration guide.
|
|
172
|
-
|
|
173
|
-
## Development
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
bun install
|
|
177
|
-
bun test
|
|
178
|
-
bun run lint
|
|
179
|
-
```
|
|
75
|
+
**Runtimes:** Bun >= 1.0 and Node.js >= 18. CLI (`silvery` command) requires Bun.
|
|
180
76
|
|
|
181
77
|
## License
|
|
182
78
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silvery",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React terminal UI renderer for complex interactive apps — layout-aware rendering, flexbox, scrolling, and incremental updates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ansi",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
],
|
|
39
39
|
"files": [
|
|
40
40
|
"src",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
41
|
+
"dist",
|
|
42
|
+
"bin"
|
|
43
43
|
],
|
|
44
44
|
"type": "module",
|
|
45
45
|
"main": "src/index.ts",
|
|
@@ -49,20 +49,72 @@
|
|
|
49
49
|
"types": "./src/index.ts",
|
|
50
50
|
"import": "./src/index.ts"
|
|
51
51
|
},
|
|
52
|
+
"./runtime": {
|
|
53
|
+
"types": "./src/runtime.ts",
|
|
54
|
+
"import": "./src/runtime.ts"
|
|
55
|
+
},
|
|
56
|
+
"./theme": {
|
|
57
|
+
"types": "./src/theme.ts",
|
|
58
|
+
"import": "./src/theme.ts"
|
|
59
|
+
},
|
|
60
|
+
"./ui": {
|
|
61
|
+
"types": "./src/ui.ts",
|
|
62
|
+
"import": "./src/ui.ts"
|
|
63
|
+
},
|
|
64
|
+
"./ui/cli": {
|
|
65
|
+
"types": "./src/ui/cli.ts",
|
|
66
|
+
"import": "./src/ui/cli.ts"
|
|
67
|
+
},
|
|
68
|
+
"./ui/react": {
|
|
69
|
+
"types": "./src/ui/react.ts",
|
|
70
|
+
"import": "./src/ui/react.ts"
|
|
71
|
+
},
|
|
72
|
+
"./ui/progress": {
|
|
73
|
+
"types": "./src/ui/progress.ts",
|
|
74
|
+
"import": "./src/ui/progress.ts"
|
|
75
|
+
},
|
|
76
|
+
"./ui/wrappers": {
|
|
77
|
+
"types": "./src/ui/wrappers.ts",
|
|
78
|
+
"import": "./src/ui/wrappers.ts"
|
|
79
|
+
},
|
|
80
|
+
"./ui/ansi": {
|
|
81
|
+
"types": "./src/ui/ansi.ts",
|
|
82
|
+
"import": "./src/ui/ansi.ts"
|
|
83
|
+
},
|
|
84
|
+
"./ui/display": {
|
|
85
|
+
"types": "./src/ui/display.ts",
|
|
86
|
+
"import": "./src/ui/display.ts"
|
|
87
|
+
},
|
|
88
|
+
"./ui/input": {
|
|
89
|
+
"types": "./src/ui/input.ts",
|
|
90
|
+
"import": "./src/ui/input.ts"
|
|
91
|
+
},
|
|
92
|
+
"./ui/animation": {
|
|
93
|
+
"types": "./src/ui/animation.ts",
|
|
94
|
+
"import": "./src/ui/animation.ts"
|
|
95
|
+
},
|
|
96
|
+
"./ui/image": {
|
|
97
|
+
"types": "./src/ui/image.ts",
|
|
98
|
+
"import": "./src/ui/image.ts"
|
|
99
|
+
},
|
|
100
|
+
"./ui/utils": {
|
|
101
|
+
"types": "./src/ui/utils.ts",
|
|
102
|
+
"import": "./src/ui/utils.ts"
|
|
103
|
+
},
|
|
52
104
|
"./ink": {
|
|
53
|
-
"types": "./packages/
|
|
54
|
-
"import": "./packages/
|
|
105
|
+
"types": "./packages/ink/src/ink.ts",
|
|
106
|
+
"import": "./packages/ink/src/ink.ts"
|
|
55
107
|
},
|
|
56
108
|
"./chalk": {
|
|
57
|
-
"types": "./packages/
|
|
58
|
-
"import": "./packages/
|
|
109
|
+
"types": "./packages/ink/src/chalk.ts",
|
|
110
|
+
"import": "./packages/ink/src/chalk.ts"
|
|
59
111
|
}
|
|
60
112
|
},
|
|
61
113
|
"publishConfig": {
|
|
62
114
|
"access": "public"
|
|
63
115
|
},
|
|
64
116
|
"scripts": {
|
|
65
|
-
"build": "bun
|
|
117
|
+
"build": "bun run scripts/build.ts",
|
|
66
118
|
"test": "bunx --bun vitest run",
|
|
67
119
|
"test:fast": "bunx --bun vitest run --reporter=dot",
|
|
68
120
|
"typecheck": "tsc --noEmit",
|
|
@@ -76,9 +128,9 @@
|
|
|
76
128
|
"release": "changeset publish",
|
|
77
129
|
"theme": "bun packages/theme/src/cli.ts",
|
|
78
130
|
"demo": "bun examples/cli.ts",
|
|
79
|
-
"compat": "bun packages/
|
|
80
|
-
"compat:ink": "bun packages/
|
|
81
|
-
"compat:chalk": "bun packages/
|
|
131
|
+
"compat": "bun packages/ink/scripts/compat-check.ts",
|
|
132
|
+
"compat:ink": "bun packages/ink/scripts/compat-check.ts ink",
|
|
133
|
+
"compat:chalk": "bun packages/ink/scripts/compat-check.ts chalk"
|
|
82
134
|
},
|
|
83
135
|
"dependencies": {
|
|
84
136
|
"chalk": "^5.6.2",
|
|
@@ -101,7 +153,7 @@
|
|
|
101
153
|
"playwright": "^1.58.2",
|
|
102
154
|
"react": "^19.0.0",
|
|
103
155
|
"typescript": "^5.5.0",
|
|
104
|
-
"vimonkey": "
|
|
156
|
+
"vimonkey": "^0.2.0",
|
|
105
157
|
"vitepress": "^1.5.0",
|
|
106
158
|
"vitepress-plugin-llms": "^1.0.0",
|
|
107
159
|
"vitest": "^4.0.18",
|
package/src/index.ts
CHANGED
|
@@ -3,4 +3,70 @@
|
|
|
3
3
|
|
|
4
4
|
export const VERSION = "0.0.1"
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
// Re-export everything from @silvery/ag-react — local `render` below shadows the re-exported one
|
|
7
|
+
export * from "@silvery/ag-react"
|
|
8
|
+
|
|
9
|
+
import type { ReactElement } from "react"
|
|
10
|
+
import { render as reactRender, type RenderOptions, type TermDef } from "@silvery/ag-react"
|
|
11
|
+
import type { Term } from "@silvery/ag-react"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render a React element to the terminal.
|
|
15
|
+
*
|
|
16
|
+
* Zero-ceremony entry point — auto-detects the terminal and starts an
|
|
17
|
+
* interactive app when stdin is a TTY. No need to create a Term first.
|
|
18
|
+
*
|
|
19
|
+
* @example Hello World (2 lines)
|
|
20
|
+
* ```tsx
|
|
21
|
+
* import { render, Text } from "silvery"
|
|
22
|
+
* await render(<Text>Hello!</Text>).run()
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example Interactive counter
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import { useState } from "react"
|
|
28
|
+
* import { render, Box, Text, useInput } from "silvery"
|
|
29
|
+
*
|
|
30
|
+
* function Counter() {
|
|
31
|
+
* const [count, setCount] = useState(0)
|
|
32
|
+
* useInput((input) => {
|
|
33
|
+
* if (input === "j") setCount((c) => c + 1)
|
|
34
|
+
* })
|
|
35
|
+
* return (
|
|
36
|
+
* <Box borderStyle="round" padding={1}>
|
|
37
|
+
* <Text>Count: {count}</Text>
|
|
38
|
+
* </Box>
|
|
39
|
+
* )
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* await render(<Counter />).run()
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example Static render (explicit)
|
|
46
|
+
* ```tsx
|
|
47
|
+
* import { render, Text } from "silvery"
|
|
48
|
+
* await render(<Text>Report</Text>, { width: 120 })
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* When called without a Term or TermDef:
|
|
52
|
+
* - **TTY detected** → interactive mode (stdin + stdout auto-wired)
|
|
53
|
+
* - **No TTY** → static mode (renders once and returns)
|
|
54
|
+
*
|
|
55
|
+
* Pass a Term or TermDef explicitly to override auto-detection.
|
|
56
|
+
*/
|
|
57
|
+
export function render(
|
|
58
|
+
element: ReactElement,
|
|
59
|
+
termOrDef?: Term | TermDef,
|
|
60
|
+
options?: RenderOptions,
|
|
61
|
+
): ReturnType<typeof reactRender> {
|
|
62
|
+
// When no term/def is provided and we're in a TTY, auto-wire stdin/stdout
|
|
63
|
+
// so the app runs interactively (useInput works, app stays alive until exit).
|
|
64
|
+
if (!termOrDef && process.stdin?.isTTY && process.stdout?.isTTY) {
|
|
65
|
+
const ttyDef: TermDef = {
|
|
66
|
+
stdin: process.stdin,
|
|
67
|
+
stdout: process.stdout,
|
|
68
|
+
}
|
|
69
|
+
return reactRender(element, ttyDef, options)
|
|
70
|
+
}
|
|
71
|
+
return reactRender(element, termOrDef, options)
|
|
72
|
+
}
|
package/src/runtime.ts
ADDED
package/src/theme.ts
ADDED
package/src/ui/ansi.ts
ADDED
package/src/ui/cli.ts
ADDED
package/src/ui/image.ts
ADDED
package/src/ui/input.ts
ADDED
package/src/ui/react.ts
ADDED
package/src/ui/utils.ts
ADDED
package/src/ui.ts
ADDED
package/examples/CLAUDE.md
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Silvery Examples & Showcases
|
|
2
|
-
|
|
3
|
-
## Directory Structure
|
|
4
|
-
|
|
5
|
-
| Directory | What |
|
|
6
|
-
| -------------- | ---------------------------------------------------------- |
|
|
7
|
-
| `interactive/` | Full apps — run with `bun examples/interactive/<name>.tsx` |
|
|
8
|
-
| `inline/` | Inline mode examples (no alt screen) |
|
|
9
|
-
| `kitty/` | Kitty protocol demos |
|
|
10
|
-
| `layout/` | Layout engine examples |
|
|
11
|
-
| `runtime/` | Runtime layer demos (run, createApp, createStore) |
|
|
12
|
-
| `playground/` | Quick prototyping |
|
|
13
|
-
| `web/` | Browser renderers (DOM, Canvas2D) |
|
|
14
|
-
| `screenshots/` | Reference screenshots for visual regression |
|
|
15
|
-
|
|
16
|
-
## Making a Great Showcase
|
|
17
|
-
|
|
18
|
-
### Design Principles
|
|
19
|
-
|
|
20
|
-
1. **Show, don't tell.** A showcase should demonstrate Silvery features through working UI, not walls of text. Intro text is fine — but collapse it once the demo starts.
|
|
21
|
-
|
|
22
|
-
2. **Auto-size to content.** `ScrollbackView`/`ScrollbackList` auto-size to their content — no manual height management. The output phase caps output at terminal height independently. Content that exceeds terminal height causes natural terminal scrolling.
|
|
23
|
-
|
|
24
|
-
3. **Single status bar.** Keep the status bar to one line. Include: context bar, elapsed time, cost, and key hints. Remove anything that doesn't help the user interact.
|
|
25
|
-
|
|
26
|
-
4. **Conditional headers.** Show feature bullets before the demo starts (when there's space). Collapse to a one-liner once content fills the screen.
|
|
27
|
-
|
|
28
|
-
5. **Respect terminal width.** Boxes with borders at 120 cols should leave room for the border characters. Test at 80 and 120 cols.
|
|
29
|
-
|
|
30
|
-
6. **Streaming feels real.** For coding agent demos: thinking spinner (1-2s) → word-by-word text reveal → tool call spinner → output. Use `setInterval` at 50ms with 8-12% fraction increments.
|
|
31
|
-
|
|
32
|
-
### Scrollback Pattern
|
|
33
|
-
|
|
34
|
-
Use `ScrollbackList` (or `ScrollbackView`) — they handle terminal height, footer pinning, and overflow automatically:
|
|
35
|
-
|
|
36
|
-
```tsx
|
|
37
|
-
function App() {
|
|
38
|
-
return (
|
|
39
|
-
<ScrollbackList
|
|
40
|
-
items={items}
|
|
41
|
-
keyExtractor={(item) => item.id}
|
|
42
|
-
isFrozen={(item) => item.done}
|
|
43
|
-
markers={true}
|
|
44
|
-
footer={<StatusBar />}
|
|
45
|
-
>
|
|
46
|
-
{(item) => <ItemView item={item} />}
|
|
47
|
-
</ScrollbackList>
|
|
48
|
-
)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await render(<App />, term, { mode: "inline" })
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
`ScrollbackView` auto-sizes to its content — no manual height management. The output phase independently caps output at terminal height (via `inlineFullRender()`), so content that exceeds the terminal causes natural scrolling. The footer stays pinned at the bottom of the content.
|
|
55
|
-
|
|
56
|
-
### Theme Tokens
|
|
57
|
-
|
|
58
|
-
Use semantic `$token` colors instead of hardcoded values:
|
|
59
|
-
|
|
60
|
-
| Token | Use for |
|
|
61
|
-
| ---------- | ------------------------------------- |
|
|
62
|
-
| `$primary` | Active elements, progress bars, links |
|
|
63
|
-
| `$success` | Completed items, checkmarks |
|
|
64
|
-
| `$warning` | Caution, compaction |
|
|
65
|
-
| `$error` | Failures, diff removals |
|
|
66
|
-
| `$muted` | Secondary info, timestamps |
|
|
67
|
-
| `$border` | Default border color |
|
|
68
|
-
|
|
69
|
-
### Testing Showcases
|
|
70
|
-
|
|
71
|
-
1. **Visual check**: Run in TTY and step through all states
|
|
72
|
-
2. **Resize**: Verify layout adapts to terminal resize
|
|
73
|
-
3. **Scrollback**: After frozen items, scroll up — verify colors/borders preserved
|
|
74
|
-
4. **Width**: Test at 80 and 120 columns
|
|
75
|
-
5. **Fast mode**: `--fast` flag should skip all animation for quick validation
|
package/examples/_banner.tsx
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import React from "react"
|
|
2
|
-
import { Box, Text, Strong, Muted, ThemeProvider, getThemeByName, type Theme } from "../src/index.js"
|
|
3
|
-
|
|
4
|
-
export interface ExampleMeta {
|
|
5
|
-
name: string
|
|
6
|
-
description: string
|
|
7
|
-
/** API features showcased, e.g. ["VirtualList", "useContentRect()"] */
|
|
8
|
-
features?: string[]
|
|
9
|
-
/** Curated demo — shown in CLI viewer (`bun examples`) and web showcase */
|
|
10
|
-
demo?: boolean
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
meta: ExampleMeta
|
|
15
|
-
/** Short controls legend, e.g. "j/k navigate q quit" */
|
|
16
|
-
controls?: string
|
|
17
|
-
/** Override theme (from viewer). Falls back to SILVERY_THEME env var. */
|
|
18
|
-
theme?: Theme
|
|
19
|
-
children: React.ReactNode
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Compact header shown when examples run standalone.
|
|
24
|
-
* Wraps children in ThemeProvider for consistent theming.
|
|
25
|
-
*/
|
|
26
|
-
export function ExampleBanner({ meta, controls, theme, children }: Props) {
|
|
27
|
-
const resolvedTheme = theme ?? getThemeByName(process.env.SILVERY_THEME)
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<ThemeProvider theme={resolvedTheme}>
|
|
31
|
-
<Box flexDirection="column" flexGrow={1}>
|
|
32
|
-
{/* One-line header: dimmed to not compete with example UI */}
|
|
33
|
-
<Box paddingX={1} gap={1}>
|
|
34
|
-
<Text dim color="$warning">
|
|
35
|
-
{"▸ silvery"}
|
|
36
|
-
</Text>
|
|
37
|
-
<Strong>{meta.name}</Strong>
|
|
38
|
-
<Muted>— {meta.description}</Muted>
|
|
39
|
-
</Box>
|
|
40
|
-
{meta.features && meta.features.length > 0 && (
|
|
41
|
-
<Box paddingX={1}>
|
|
42
|
-
<Muted>
|
|
43
|
-
{" "}
|
|
44
|
-
{meta.features.join(" · ")}
|
|
45
|
-
</Muted>
|
|
46
|
-
</Box>
|
|
47
|
-
)}
|
|
48
|
-
{controls && (
|
|
49
|
-
<Box paddingX={1}>
|
|
50
|
-
<Muted>
|
|
51
|
-
{" "}
|
|
52
|
-
{controls}
|
|
53
|
-
</Muted>
|
|
54
|
-
</Box>
|
|
55
|
-
)}
|
|
56
|
-
{children}
|
|
57
|
-
</Box>
|
|
58
|
-
</ThemeProvider>
|
|
59
|
-
)
|
|
60
|
-
}
|