silvery 0.3.0 → 0.4.1
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/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- 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
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Benchmark: ScrollbackList typing performance
|
|
3
|
-
* Measures per-keystroke cost across React reconciliation + silvery pipeline.
|
|
4
|
-
*
|
|
5
|
-
* Usage: bun examples/scrollback-perf.tsx
|
|
6
|
-
*
|
|
7
|
-
* Modes:
|
|
8
|
-
* --simple Simple items (Box + 2 Text nodes)
|
|
9
|
-
* --complex Complex items matching ai-chat (default)
|
|
10
|
-
* --timers Add pulse/elapsed timers like the real demo
|
|
11
|
-
*/
|
|
12
|
-
import React, { useState, useEffect, useRef } from "react"
|
|
13
|
-
import { createRenderer } from "../src/testing/index.js"
|
|
14
|
-
import { Box, Text, ScrollbackList, TextInput, Spinner } from "../src/index.js"
|
|
15
|
-
|
|
16
|
-
interface Item {
|
|
17
|
-
id: string
|
|
18
|
-
text: string
|
|
19
|
-
role: string
|
|
20
|
-
frozen: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const useSimple = process.argv.includes("--simple")
|
|
24
|
-
const useTimers = process.argv.includes("--timers")
|
|
25
|
-
|
|
26
|
-
/** Simple item — matches original benchmark (Box + 2-3 Text nodes) */
|
|
27
|
-
function SimpleItem({ item, isLatest }: { item: Item; isLatest: boolean }) {
|
|
28
|
-
return (
|
|
29
|
-
<Box flexDirection="column" borderStyle={item.role === "assistant" ? "single" : undefined}>
|
|
30
|
-
<Text bold color={item.role === "user" ? "green" : "blue"}>
|
|
31
|
-
{item.role === "user" ? "❯" : "◆"} {item.role}
|
|
32
|
-
</Text>
|
|
33
|
-
<Text>{item.text}</Text>
|
|
34
|
-
{isLatest && <Text dimColor>Latest item</Text>}
|
|
35
|
-
</Box>
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** Complex item — matches ai-chat's ExchangeItem */
|
|
40
|
-
function ComplexItem({ item }: { item: Item }) {
|
|
41
|
-
if (item.role === "user") {
|
|
42
|
-
return (
|
|
43
|
-
<Box flexDirection="column">
|
|
44
|
-
<Text> </Text>
|
|
45
|
-
<Box paddingX={1}>
|
|
46
|
-
<Text>
|
|
47
|
-
<Text bold color="blue">
|
|
48
|
-
{"❯ "}
|
|
49
|
-
</Text>
|
|
50
|
-
{item.text}
|
|
51
|
-
</Text>
|
|
52
|
-
</Box>
|
|
53
|
-
<Text> </Text>
|
|
54
|
-
</Box>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
return (
|
|
58
|
-
<Box flexDirection="column" borderStyle="round" borderColor="green" paddingX={1}>
|
|
59
|
-
<Text>
|
|
60
|
-
<Text bold color="green">
|
|
61
|
-
◆ Agent
|
|
62
|
-
</Text>
|
|
63
|
-
<Text color="gray" dim>
|
|
64
|
-
{" "}
|
|
65
|
-
624 tokens
|
|
66
|
-
</Text>
|
|
67
|
-
</Text>
|
|
68
|
-
<Text> </Text>
|
|
69
|
-
<Text>{item.text}</Text>
|
|
70
|
-
<Text> </Text>
|
|
71
|
-
<Box flexDirection="column">
|
|
72
|
-
<Text>
|
|
73
|
-
<Text color="green">{"✓ "}</Text>
|
|
74
|
-
<Text color="cyan" bold>
|
|
75
|
-
Read
|
|
76
|
-
</Text>{" "}
|
|
77
|
-
src/auth.ts
|
|
78
|
-
</Text>
|
|
79
|
-
<Box
|
|
80
|
-
borderStyle="bold"
|
|
81
|
-
borderColor="green"
|
|
82
|
-
borderLeft
|
|
83
|
-
borderRight={false}
|
|
84
|
-
borderTop={false}
|
|
85
|
-
borderBottom={false}
|
|
86
|
-
paddingLeft={1}
|
|
87
|
-
>
|
|
88
|
-
<Text>export async function login(token: string)</Text>
|
|
89
|
-
</Box>
|
|
90
|
-
</Box>
|
|
91
|
-
</Box>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function StatusBar() {
|
|
96
|
-
return (
|
|
97
|
-
<Box flexDirection="row" justifyContent="space-between" paddingX={1}>
|
|
98
|
-
<Text color="gray" dim>
|
|
99
|
-
<Text color="blue">0:00</Text>
|
|
100
|
-
{" ⏎ send tab auto ^L clear esc quit"}
|
|
101
|
-
</Text>
|
|
102
|
-
<Text color="gray" dim>
|
|
103
|
-
ctx {"█".repeat(4)}
|
|
104
|
-
{"░".repeat(16)} 20% · $0.12
|
|
105
|
-
</Text>
|
|
106
|
-
</Box>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** Footer that owns its own inputText state (lifted down from parent). */
|
|
111
|
-
function LiftedFooter() {
|
|
112
|
-
const [inputText, setInputText] = useState("")
|
|
113
|
-
return (
|
|
114
|
-
<Box flexDirection="column">
|
|
115
|
-
<Box borderStyle="round" paddingX={1}>
|
|
116
|
-
<TextInput value={inputText} onChange={setInputText} prompt="> " isActive={true} />
|
|
117
|
-
</Box>
|
|
118
|
-
{!useSimple && <StatusBar />}
|
|
119
|
-
</Box>
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const useLifted = process.argv.includes("--lifted")
|
|
124
|
-
|
|
125
|
-
function TestApp({ itemCount }: { itemCount: number }) {
|
|
126
|
-
const [items] = useState<Item[]>(() =>
|
|
127
|
-
Array.from({ length: itemCount }, (_, i) => ({
|
|
128
|
-
id: `item-${i}`,
|
|
129
|
-
text:
|
|
130
|
-
i % 2 === 0
|
|
131
|
-
? `Fix the login bug in auth.ts — expired tokens throw instead of refreshing.`
|
|
132
|
-
: `Found it. The expiry check compares seconds (jwt.exp) to milliseconds (Date.now()). Fixing now.`,
|
|
133
|
-
role: i % 2 === 0 ? "user" : "assistant",
|
|
134
|
-
frozen: false,
|
|
135
|
-
})),
|
|
136
|
-
)
|
|
137
|
-
// When NOT lifted: inputText lives in parent (causes full re-render)
|
|
138
|
-
const [inputText, setInputText] = useState("")
|
|
139
|
-
|
|
140
|
-
// Optional timers (like ai-chat)
|
|
141
|
-
const [_pulse, setPulse] = useState(false)
|
|
142
|
-
const [_elapsed, setElapsed] = useState(0)
|
|
143
|
-
const startRef = useRef(Date.now())
|
|
144
|
-
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
if (!useTimers) return
|
|
147
|
-
const t1 = setInterval(() => setPulse((p) => !p), 800)
|
|
148
|
-
const t2 = setInterval(() => setElapsed(Math.floor((Date.now() - startRef.current) / 1000)), 1000)
|
|
149
|
-
return () => {
|
|
150
|
-
clearInterval(t1)
|
|
151
|
-
clearInterval(t2)
|
|
152
|
-
}
|
|
153
|
-
}, [])
|
|
154
|
-
|
|
155
|
-
return (
|
|
156
|
-
<Box flexDirection="column">
|
|
157
|
-
<ScrollbackList
|
|
158
|
-
items={items}
|
|
159
|
-
keyExtractor={(item) => item.id}
|
|
160
|
-
isFrozen={(item) => item.frozen}
|
|
161
|
-
footer={
|
|
162
|
-
useLifted ? (
|
|
163
|
-
<LiftedFooter />
|
|
164
|
-
) : (
|
|
165
|
-
<Box flexDirection="column">
|
|
166
|
-
<Box borderStyle="round" paddingX={1}>
|
|
167
|
-
<TextInput value={inputText} onChange={setInputText} prompt="> " isActive={true} />
|
|
168
|
-
</Box>
|
|
169
|
-
{!useSimple && <StatusBar />}
|
|
170
|
-
</Box>
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
footerHeight={useSimple ? 3 : 4}
|
|
174
|
-
width={120}
|
|
175
|
-
stdout={{ write: () => true }}
|
|
176
|
-
>
|
|
177
|
-
{(item, index) => {
|
|
178
|
-
const isLatest = index === items.length - 1
|
|
179
|
-
return useSimple ? <SimpleItem item={item} isLatest={isLatest} /> : <ComplexItem item={item} />
|
|
180
|
-
}}
|
|
181
|
-
</ScrollbackList>
|
|
182
|
-
</Box>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async function benchmark(itemCount: number, label: string) {
|
|
187
|
-
const render = createRenderer({ cols: 120, rows: 40 })
|
|
188
|
-
const app = render(<TestApp itemCount={itemCount} />)
|
|
189
|
-
|
|
190
|
-
// Warm up
|
|
191
|
-
await app.press("x")
|
|
192
|
-
await app.press("Backspace")
|
|
193
|
-
|
|
194
|
-
const chars = "the quick brown fox jumps over the lazy dog"
|
|
195
|
-
const times: number[] = []
|
|
196
|
-
|
|
197
|
-
for (const char of chars) {
|
|
198
|
-
const t0 = performance.now()
|
|
199
|
-
await app.press(char)
|
|
200
|
-
times.push(performance.now() - t0)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
times.sort((a, b) => a - b)
|
|
204
|
-
const avg = times.reduce((a, b) => a + b) / times.length
|
|
205
|
-
const p50 = times[Math.floor(times.length * 0.5)]!
|
|
206
|
-
const p95 = times[Math.floor(times.length * 0.95)]!
|
|
207
|
-
const max = times[times.length - 1]!
|
|
208
|
-
|
|
209
|
-
// Pipeline breakdown from last frame
|
|
210
|
-
const pipeline = (globalThis as any).__silvery_last_pipeline
|
|
211
|
-
const pipelineStr = pipeline
|
|
212
|
-
? Object.entries(pipeline)
|
|
213
|
-
.filter(([, v]) => typeof v === "number" && (v as number) > 0.05)
|
|
214
|
-
.map(([k, v]) => `${k}=${(v as number).toFixed(1)}ms`)
|
|
215
|
-
.join(" ")
|
|
216
|
-
: "n/a"
|
|
217
|
-
|
|
218
|
-
console.log(
|
|
219
|
-
`${label.padEnd(20)} avg=${avg.toFixed(1)}ms p50=${p50.toFixed(1)}ms p95=${p95.toFixed(1)}ms max=${max.toFixed(1)}ms pipeline=[${pipelineStr}]`,
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
console.log("=== ScrollbackList Typing Performance ===\n")
|
|
224
|
-
await benchmark(1, "1 item")
|
|
225
|
-
await benchmark(5, "5 items")
|
|
226
|
-
await benchmark(10, "10 items")
|
|
227
|
-
await benchmark(20, "20 items")
|
|
228
|
-
await benchmark(50, "50 items")
|
|
229
|
-
await benchmark(100, "100 items")
|
|
230
|
-
console.log("\nDone.")
|