@silvery/examples 0.4.4 → 0.5.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 ADDED
@@ -0,0 +1,7 @@
1
+ # @silvery/examples
2
+
3
+ Example apps and component demos for silvery.
4
+
5
+ ```console
6
+ $ bunx @silvery/examples <name>
7
+ ```
@@ -4,7 +4,7 @@ import { Box, Text, Strong, Muted, ThemeProvider, getThemeByName, type Theme } f
4
4
  export interface ExampleMeta {
5
5
  name: string
6
6
  description: string
7
- /** API features showcased, e.g. ["VirtualList", "useContentRect()"] */
7
+ /** API features showcased, e.g. ["ListView", "useBoxRect()"] */
8
8
  features?: string[]
9
9
  /** Curated demo — shown in CLI viewer (`bun examples`) and web showcase */
10
10
  demo?: boolean
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * AI Chat — Coding Agent Demo
3
3
  *
4
- * Showcases ScrollbackList with streaming, tool calls, context tracking.
5
- * TEA state machine drives all animation; ScrollbackList freezes completed
6
- * exchanges into real terminal scrollback with colors, borders, and hyperlinks.
4
+ * Showcases ListView with streaming, tool calls, context tracking.
5
+ * TEA state machine drives all animation; ListView caches completed
6
+ * exchanges while live content stays in the React tree.
7
7
  *
8
8
  * Flags: --auto (auto-advance) --fast (skip animation) --stress (200 exchanges)
9
9
  */
10
10
 
11
- import React, { useEffect, useRef, useMemo } from "react"
12
- import { Box, Text, Spinner, ScrollbackList, useTea } from "silvery"
11
+ import React, { useCallback, useEffect, useRef, useMemo } from "react"
12
+ import { Box, Text, Spinner, ListView, useTea } from "silvery"
13
+ import type { ListItemMeta } from "silvery"
13
14
  import { run, useInput, useExit, type Key } from "silvery/runtime"
14
15
  import type { ExampleMeta } from "../../_banner.js"
15
16
  import type { ScriptEntry } from "./types.js"
@@ -32,13 +33,14 @@ export type { Exchange, ToolCall } from "./types.js"
32
33
 
33
34
  export const meta: ExampleMeta = {
34
35
  name: "AI Coding Agent",
35
- description: "Coding agent showcase — ScrollbackList, streaming, context tracking",
36
+ description: "Coding agent showcase — ListView, streaming, context tracking",
36
37
  demo: true,
37
- features: ["ScrollbackList", "auto-freeze", "inline mode", "streaming", "OSC 8 links", "OSC 133 markers"],
38
+ features: ["ListView", "cache", "inline mode", "streaming", "OSC 8 links"],
39
+ // TODO: Add OSC 133 marker support to ListView (km-silvery.listview-markers)
38
40
  }
39
41
 
40
42
  // ============================================================================
41
- // AIChat — TEA state machine + ScrollbackList
43
+ // AIChat — TEA state machine + ListView
42
44
  // ============================================================================
43
45
 
44
46
  export function AIChat({
@@ -60,13 +62,45 @@ export function AIChat({
60
62
  useAutoExit(autoStart, state.done, exit)
61
63
  useKeyBindings(state, send, footerControlRef)
62
64
 
65
+ const renderExchange = useCallback(
66
+ (exchange: (typeof state.exchanges)[number], index: number, _meta: ListItemMeta) => {
67
+ const isLatest = index === state.exchanges.length - 1
68
+ return (
69
+ <Box flexDirection="column">
70
+ {index > 0 && <Text> </Text>}
71
+ {state.compacting && isLatest && <CompactingOverlay />}
72
+ {state.done && autoStart && isLatest && <SessionComplete />}
73
+ <ExchangeItem
74
+ exchange={exchange}
75
+ streamPhase={state.streamPhase}
76
+ revealFraction={state.revealFraction}
77
+ pulse={state.pulse}
78
+ isLatest={isLatest}
79
+ isFirstInGroup={exchange.role !== (index > 0 ? state.exchanges[index - 1]!.role : null)}
80
+ isLastInGroup={
81
+ exchange.role !== (index < state.exchanges.length - 1 ? state.exchanges[index + 1]!.role : null)
82
+ }
83
+ />
84
+ </Box>
85
+ )
86
+ },
87
+ [state, autoStart],
88
+ )
89
+
63
90
  return (
64
91
  <Box flexDirection="column" paddingX={1}>
65
- <ScrollbackList
92
+ <ListView
66
93
  items={state.exchanges}
67
- keyExtractor={(ex) => ex.id}
68
- markers={true}
69
- footer={
94
+ getKey={(ex) => ex.id}
95
+ height={process.stdout.rows ?? 24}
96
+ estimateHeight={6}
97
+ renderItem={renderExchange}
98
+ scrollTo={state.exchanges.length - 1}
99
+ cache={{
100
+ mode: "virtual",
101
+ isCacheable: (_ex, index) => index < state.exchanges.length - 1,
102
+ }}
103
+ listFooter={
70
104
  <DemoFooter
71
105
  controlRef={footerControlRef}
72
106
  onSubmit={(text) => send({ type: "submit", text })}
@@ -80,29 +114,7 @@ export function AIChat({
80
114
  autoTypingText={state.autoTyping ? state.autoTyping.full.slice(0, state.autoTyping.revealed) : null}
81
115
  />
82
116
  }
83
- >
84
- {(exchange, index) => {
85
- const isLatest = index === state.exchanges.length - 1
86
- return (
87
- <Box flexDirection="column">
88
- {index > 0 && <Text> </Text>}
89
- {state.compacting && isLatest && <CompactingOverlay />}
90
- {state.done && autoStart && isLatest && <SessionComplete />}
91
- <ExchangeItem
92
- exchange={exchange}
93
- streamPhase={state.streamPhase}
94
- revealFraction={state.revealFraction}
95
- pulse={state.pulse}
96
- isLatest={isLatest}
97
- isFirstInGroup={exchange.role !== (index > 0 ? state.exchanges[index - 1]!.role : null)}
98
- isLastInGroup={
99
- exchange.role !== (index < state.exchanges.length - 1 ? state.exchanges[index + 1]!.role : null)
100
- }
101
- />
102
- </Box>
103
- )
104
- }}
105
- </ScrollbackList>
117
+ />
106
118
  </Box>
107
119
  )
108
120
  }
@@ -56,11 +56,10 @@ export type DemoResult = TeaResult<DemoState, DemoEffect>
56
56
  // ============================================================================
57
57
 
58
58
  const INTRO_TEXT = [
59
- "Coding agent simulation showcasing ScrollbackList:",
60
- " • ScrollbackListdeclarative list with automatic scrollback",
61
- " • Auto-freezeitems freeze when they scroll off-screen",
59
+ "Coding agent simulation showcasing ListView:",
60
+ " • ListViewunified virtualized list with cache",
61
+ " • Cache mode completed exchanges cached for performance",
62
62
  " • OSC 8 hyperlinks — clickable file paths and URLs",
63
- " • OSC 133 markers — Cmd+↑/↓ to jump between exchanges",
64
63
  " • $token theme colors — semantic color tokens",
65
64
  ].join("\n")
66
65
 
@@ -24,8 +24,8 @@
24
24
 
25
25
  import React from "react"
26
26
  import { Box, Text, Muted, Kbd } from "silvery"
27
- import { createApp, useApp, type AppHandle } from "@silvery/tea/create-app"
28
- import { pipe, withReact, withTerminal } from "@silvery/tea/plugins"
27
+ import { createApp, useApp, type AppHandle } from "@silvery/create/create-app"
28
+ import { pipe, withReact, withTerminal } from "@silvery/create/plugins"
29
29
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
30
30
 
31
31
  export const meta: ExampleMeta = {
@@ -8,19 +8,7 @@
8
8
  */
9
9
 
10
10
  import React, { Suspense, useState, use } from "react"
11
- import {
12
- render,
13
- Box,
14
- Text,
15
- H1,
16
- Kbd,
17
- Muted,
18
- useInput,
19
- useApp,
20
- createTerm,
21
- ErrorBoundary,
22
- type Key,
23
- } from "silvery"
11
+ import { render, Box, Text, H1, Kbd, Muted, useInput, useApp, createTerm, ErrorBoundary, type Key } from "silvery"
24
12
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
25
13
 
26
14
  export const meta: ExampleMeta = {
@@ -87,8 +87,8 @@ function TypographyTab() {
87
87
 
88
88
  <H3>Use Built-in Components</H3>
89
89
  <P>
90
- <Code>silvery/ui</Code> ships 30+ components. They handle keyboard navigation, theming,
91
- mouse support, and dozens of edge cases.
90
+ <Code>silvery/ui</Code> ships 30+ components. They handle keyboard navigation, theming, mouse support, and
91
+ dozens of edge cases.
92
92
  </P>
93
93
  <UL>
94
94
  <LI>
@@ -133,8 +133,8 @@ function TypographyTab() {
133
133
 
134
134
  <H3>Think in Flexbox</H3>
135
135
  <P>
136
- Silvery uses CSS flexbox via Flexily. Components know their size via <Code>useContentRect()</Code> —
137
- synchronous, during render. No effects, no flash.
136
+ Silvery uses CSS flexbox via Flexily. Components know their size via <Code>useBoxRect()</Code> — synchronous,
137
+ during render. No effects, no flash.
138
138
  </P>
139
139
 
140
140
  <Small>Last updated: silvery v0.0.1 — see silvery.dev for full documentation</Small>
@@ -2,9 +2,9 @@
2
2
  * Data Explorer — Process Table Example
3
3
  *
4
4
  * A process explorer with a searchable, scrollable table demonstrating:
5
- * - Table-like display with responsive column widths via useContentRect()
5
+ * - Table-like display with responsive column widths via useBoxRect()
6
6
  * - TextInput for live search/filter with useDeferredValue
7
- * - VirtualList for smooth scrolling through 500+ rows
7
+ * - ListView for smooth scrolling through 500+ rows
8
8
  * - Keyboard navigation with j/k and vim-style jumps
9
9
  * - Color-coded status indicators
10
10
  *
@@ -24,10 +24,10 @@ import {
24
24
  render,
25
25
  Box,
26
26
  Text,
27
- VirtualList,
27
+ ListView,
28
28
  TextInput,
29
29
  Divider,
30
- useContentRect,
30
+ useBoxRect,
31
31
  useInput,
32
32
  useApp,
33
33
  createTerm,
@@ -40,8 +40,8 @@ import { ExampleBanner, type ExampleMeta } from "../_banner.js"
40
40
 
41
41
  export const meta: ExampleMeta = {
42
42
  name: "Data Explorer",
43
- description: "Process explorer table with search, VirtualList, and responsive column widths",
44
- features: ["useContentRect()", "TextInput", "useInput()", "responsive layout", "useDeferredValue"],
43
+ description: "Process explorer table with search, ListView, and responsive column widths",
44
+ features: ["useBoxRect()", "TextInput", "useInput()", "responsive layout", "useDeferredValue"],
45
45
  }
46
46
 
47
47
  // ============================================================================
@@ -300,13 +300,13 @@ function SummaryBar({ processes, query }: { processes: ProcessInfo[]; query: str
300
300
 
301
301
  /** Inner component that reads the flex container's height */
302
302
  function ProcessListArea({ processes, cursor, width }: { processes: ProcessInfo[]; cursor: number; width: number }) {
303
- const { height } = useContentRect()
303
+ const { height } = useBoxRect()
304
304
 
305
305
  return (
306
- <VirtualList
306
+ <ListView
307
307
  items={processes}
308
308
  height={height}
309
- itemHeight={1}
309
+ estimateHeight={1}
310
310
  scrollTo={cursor}
311
311
  overscan={5}
312
312
  renderItem={(proc, index) => (
@@ -322,7 +322,7 @@ function ProcessListArea({ processes, cursor, width }: { processes: ProcessInfo[
322
322
 
323
323
  export function DataExplorer() {
324
324
  const { exit } = useApp()
325
- const { width } = useContentRect()
325
+ const { width } = useBoxRect()
326
326
  const [cursor, setCursor] = useState(0)
327
327
  const [searchMode, setSearchMode] = useState(false)
328
328
  const [query, setQuery] = useState("")
@@ -2,7 +2,7 @@
2
2
  * Dev Tools — Log Viewer Example
3
3
  *
4
4
  * A live log viewer demonstrating:
5
- * - VirtualList for efficient rendering of thousands of log entries
5
+ * - ListView for efficient rendering of thousands of log entries
6
6
  * - Keyboard shortcuts to add log entries at different severity levels
7
7
  * - Color-coded severity levels (DEBUG, INFO, WARN, ERROR)
8
8
  * - j/k navigation through log history
@@ -26,9 +26,9 @@ import {
26
26
  render,
27
27
  Box,
28
28
  Text,
29
- VirtualList,
29
+ ListView,
30
30
  Divider,
31
- useContentRect,
31
+ useBoxRect,
32
32
  useInput,
33
33
  useApp,
34
34
  createTerm,
@@ -42,8 +42,8 @@ import { ExampleBanner, type ExampleMeta } from "../_banner.js"
42
42
 
43
43
  export const meta: ExampleMeta = {
44
44
  name: "Dev Tools",
45
- description: "Log viewer with severity levels, VirtualList, and keyboard-driven log injection",
46
- features: ["VirtualList", "useInput()", "useContentRect()", "keyboard navigation"],
45
+ description: "Log viewer with severity levels, ListView, and keyboard-driven log injection",
46
+ features: ["ListView", "useInput()", "useBoxRect()", "keyboard navigation"],
47
47
  }
48
48
 
49
49
  // ============================================================================
@@ -215,15 +215,15 @@ function LevelCounts({ entries }: { entries: LogEntry[] }) {
215
215
  )
216
216
  }
217
217
 
218
- /** Inner component that reads the flex container's height via useContentRect */
218
+ /** Inner component that reads the flex container's height via useBoxRect */
219
219
  function LogListArea({ entries, cursor }: { entries: LogEntry[]; cursor: number }) {
220
- const { height } = useContentRect()
220
+ const { height } = useBoxRect()
221
221
 
222
222
  return (
223
- <VirtualList
223
+ <ListView
224
224
  items={entries}
225
225
  height={height}
226
- itemHeight={1}
226
+ estimateHeight={1}
227
227
  scrollTo={cursor}
228
228
  overscan={5}
229
229
  renderItem={(entry, index) => <LogRow key={entry.id} entry={entry} isSelected={index === cursor} />}
@@ -240,7 +240,7 @@ const rng = seededRandom(12345)
240
240
 
241
241
  export function DevTools() {
242
242
  const { exit } = useApp()
243
- const { width } = useContentRect()
243
+ const { width } = useBoxRect()
244
244
  const [entries, setEntries] = useState<LogEntry[]>(() => generateInitialLogs(INITIAL_COUNT))
245
245
  const [cursor, setCursor] = useState(INITIAL_COUNT - 1)
246
246
  const [autoScroll, setAutoScroll] = useState(true)
@@ -5,7 +5,7 @@
5
5
  * - Streaming log viewer with ~2000 lines, severity-level coloring, and level toggles
6
6
  * - Sortable process table with ~50 processes, live CPU/MEM jitter, and responsive columns
7
7
  * - Shared TextInput search bar with useDeferredValue for non-blocking filtering
8
- * - VirtualList with interactive scrolling for both tabs
8
+ * - ListView with interactive scrolling for both tabs
9
9
  *
10
10
  * Usage: bun vendor/silvery/examples/apps/explorer.tsx
11
11
  *
@@ -26,13 +26,13 @@ import {
26
26
  render,
27
27
  Box,
28
28
  Text,
29
- VirtualList,
29
+ ListView,
30
30
  TextInput,
31
31
  Tabs,
32
32
  TabList,
33
33
  Tab,
34
34
  Divider,
35
- useContentRect,
35
+ useBoxRect,
36
36
  useInput,
37
37
  useApp,
38
38
  createTerm,
@@ -44,9 +44,9 @@ import { ExampleBanner, type ExampleMeta } from "../_banner.js"
44
44
 
45
45
  export const meta: ExampleMeta = {
46
46
  name: "Explorer",
47
- description: "Log viewer and process explorer with VirtualList search",
47
+ description: "Log viewer and process explorer with ListView search",
48
48
  demo: true,
49
- features: ["VirtualList", "TextInput", "useContentRect()", "useDeferredValue", "2000+ rows"],
49
+ features: ["ListView", "TextInput", "useBoxRect()", "useDeferredValue", "2000+ rows"],
50
50
  }
51
51
 
52
52
  // ============================================================================
@@ -297,13 +297,13 @@ function LogRow({ entry, isSelected }: { entry: LogEntry; isSelected: boolean })
297
297
  }
298
298
 
299
299
  function LogListArea({ entries, cursor }: { entries: LogEntry[]; cursor: number }) {
300
- const { height } = useContentRect()
300
+ const { height } = useBoxRect()
301
301
 
302
302
  return (
303
- <VirtualList
303
+ <ListView
304
304
  items={entries}
305
305
  height={height}
306
- itemHeight={1}
306
+ estimateHeight={1}
307
307
  scrollTo={cursor}
308
308
  overscan={5}
309
309
  renderItem={(entry, index) => <LogRow key={entry.id} entry={entry} isSelected={index === cursor} />}
@@ -390,13 +390,13 @@ function ProcessRow({ proc, isSelected, width }: { proc: ProcessInfo; isSelected
390
390
  }
391
391
 
392
392
  function ProcessListArea({ processes, cursor, width }: { processes: ProcessInfo[]; cursor: number; width: number }) {
393
- const { height } = useContentRect()
393
+ const { height } = useBoxRect()
394
394
 
395
395
  return (
396
- <VirtualList
396
+ <ListView
397
397
  items={processes}
398
398
  height={height}
399
- itemHeight={1}
399
+ estimateHeight={1}
400
400
  scrollTo={cursor}
401
401
  overscan={5}
402
402
  renderItem={(proc, index) => (
@@ -412,7 +412,7 @@ function ProcessListArea({ processes, cursor, width }: { processes: ProcessInfo[
412
412
 
413
413
  export function Explorer() {
414
414
  const { exit } = useApp()
415
- const { width } = useContentRect()
415
+ const { width } = useBoxRect()
416
416
 
417
417
  // Tab state
418
418
  const [activeTab, setActiveTab] = useState("logs")
@@ -26,7 +26,7 @@ import {
26
26
  H2,
27
27
  useInput,
28
28
  useApp,
29
- useContentRect,
29
+ useBoxRect,
30
30
  createTerm,
31
31
  type Key,
32
32
  } from "silvery"
@@ -222,7 +222,7 @@ function generateCheckerPattern(w: number, h: number): Buffer {
222
222
  // ============================================================================
223
223
 
224
224
  function ImagesTab() {
225
- const rect = useContentRect()
225
+ const rect = useBoxRect()
226
226
  const w = Math.max(20, rect.width - 4)
227
227
  const imgH = Math.max(5, rect.height - 6)
228
228
 
@@ -311,7 +311,7 @@ const PAINT_PRESETS: { name: string; color: RGB }[] = [
311
311
  ]
312
312
 
313
313
  function PaintTab() {
314
- const rect = useContentRect()
314
+ const rect = useBoxRect()
315
315
  const canvasW = Math.max(10, rect.width - 2)
316
316
  const canvasTermH = Math.max(4, rect.height - 7)
317
317
  const canvasPixH = canvasTermH * 2
@@ -457,7 +457,7 @@ function PaintTab() {
457
457
  // ============================================================================
458
458
 
459
459
  function TruecolorTab() {
460
- const rect = useContentRect()
460
+ const rect = useBoxRect()
461
461
  const w = Math.max(20, rect.width - 4)
462
462
  const availH = Math.max(10, rect.height - 3)
463
463
 
@@ -9,8 +9,8 @@
9
9
  * bun examples/apps/inline-bench.tsx
10
10
  */
11
11
 
12
- import { TerminalBuffer } from "silvery"
13
- import { createOutputPhase, outputPhase } from "silvery/pipeline/output-phase"
12
+ import { TerminalBuffer } from "@silvery/ag-term/buffer"
13
+ import { createOutputPhase, outputPhase } from "@silvery/ag-term/pipeline/output-phase"
14
14
 
15
15
  const RUNS = 500
16
16
 
@@ -8,25 +8,13 @@
8
8
  */
9
9
 
10
10
  import React, { useRef, useState, useEffect } from "react"
11
- import {
12
- render,
13
- Box,
14
- Text,
15
- H1,
16
- Kbd,
17
- Muted,
18
- useInput,
19
- useApp,
20
- createTerm,
21
- type BoxHandle,
22
- type Key,
23
- } from "silvery"
11
+ import { render, Box, Text, H1, Kbd, Muted, useInput, useApp, createTerm, type BoxHandle, type Key } from "silvery"
24
12
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
25
13
 
26
14
  export const meta: ExampleMeta = {
27
15
  name: "Layout Ref",
28
- description: "useContentRect + useScreenRect for imperative layout measurement",
29
- features: ["forwardRef", "BoxHandle", "onLayout", "getContentRect()"],
16
+ description: "useBoxRect + useScrollRect for imperative layout measurement",
17
+ features: ["forwardRef", "BoxHandle", "onLayout", "getBoxRect()"],
30
18
  }
31
19
 
32
20
  // ============================================================================
@@ -76,8 +64,8 @@ function ImperativeAccessDemo() {
76
64
  return
77
65
  }
78
66
 
79
- const content = boxRef.current.getContentRect()
80
- const screen = boxRef.current.getScreenRect()
67
+ const content = boxRef.current.getBoxRect()
68
+ const screen = boxRef.current.getScrollRect()
81
69
  const node = boxRef.current.getNode()
82
70
 
83
71
  setInfo(
@@ -9,30 +9,19 @@
9
9
  * - Left panel: Box with borderStyle — content area is smaller
10
10
  * - Right panel: Box with outlineStyle — content starts at edge
11
11
  * - Toggle between styles with Tab
12
- * - Live content dimensions via useContentRect()
12
+ * - Live content dimensions via useBoxRect()
13
13
  *
14
14
  * Run: bun vendor/silvery/examples/apps/outline.tsx
15
15
  */
16
16
 
17
17
  import React, { useState } from "react"
18
- import {
19
- render,
20
- Box,
21
- Text,
22
- Kbd,
23
- Muted,
24
- useInput,
25
- useApp,
26
- useContentRect,
27
- createTerm,
28
- type Key,
29
- } from "silvery"
18
+ import { render, Box, Text, Kbd, Muted, useInput, useApp, useBoxRect, createTerm, type Key } from "silvery"
30
19
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
31
20
 
32
21
  export const meta: ExampleMeta = {
33
22
  name: "Outline vs Border",
34
23
  description: "Side-by-side comparison showing outline (no layout impact) vs border",
35
- features: ["outlineStyle", "borderStyle", "useContentRect()", "layout dimensions"],
24
+ features: ["outlineStyle", "borderStyle", "useBoxRect()", "layout dimensions"],
36
25
  }
37
26
 
38
27
  // ============================================================================
@@ -48,7 +37,7 @@ const STYLES: StyleVariant[] = ["single", "double", "round", "bold"]
48
37
  // ============================================================================
49
38
 
50
39
  function ContentWithSize({ label }: { label: string }) {
51
- const { width, height } = useContentRect()
40
+ const { width, height } = useBoxRect()
52
41
 
53
42
  return (
54
43
  <Box flexDirection="column">
@@ -9,7 +9,7 @@
9
9
 
10
10
  import React, { useState, useEffect, useMemo } from "react"
11
11
  import { Box, Text, ListView } from "silvery"
12
- import { SurfaceRegistryProvider, SearchProvider, SearchBar, useSearch } from "silvery"
12
+ import { SearchProvider, SearchBar, useSearch } from "silvery"
13
13
  import { run, useInput, type Key } from "silvery/runtime"
14
14
  import type { ExampleMeta } from "../../_banner.js"
15
15
  import { SCRIPT } from "../aichat/script.js"
@@ -85,11 +85,11 @@ function ChatPane({
85
85
  scrollTo={exchanges.length - 1}
86
86
  active={active}
87
87
  surfaceId={surfaceId}
88
- history={{
88
+ cache={{
89
89
  mode: "virtual",
90
- freezeWhen: (_ex: Exchange, idx: number) => idx < exchanges.length - 1,
90
+ isCacheable: (_ex: Exchange, idx: number) => idx < exchanges.length - 1,
91
91
  }}
92
- textAdapter={{ getItemText: (ex: Exchange) => ex.content }}
92
+ search={{ getText: (ex: Exchange) => ex.content }}
93
93
  renderItem={(exchange: Exchange, _index: number, _meta: ListItemMeta) => (
94
94
  <ExchangeItem
95
95
  exchange={exchange}
@@ -190,11 +190,9 @@ export async function main() {
190
190
  const rows = process.stdout.rows ?? 40
191
191
 
192
192
  using handle = await run(
193
- <SurfaceRegistryProvider>
194
- <SearchProvider>
195
- <PanesApp fastMode={fastMode} rows={rows} />
196
- </SearchProvider>
197
- </SurfaceRegistryProvider>,
193
+ <SearchProvider>
194
+ <PanesApp fastMode={fastMode} rows={rows} />
195
+ </SearchProvider>,
198
196
  { mode: "fullscreen", kitty: false, textSizing: false },
199
197
  )
200
198
  await handle.waitUntilExit()
@@ -15,20 +15,7 @@
15
15
  */
16
16
 
17
17
  import React, { useState } from "react"
18
- import {
19
- render,
20
- Box,
21
- Text,
22
- H1,
23
- Small,
24
- Kbd,
25
- Muted,
26
- Lead,
27
- useInput,
28
- useApp,
29
- createTerm,
30
- type Key,
31
- } from "silvery"
18
+ import { render, Box, Text, H1, Small, Kbd, Muted, Lead, useInput, useApp, createTerm, type Key } from "silvery"
32
19
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
33
20
 
34
21
  export const meta: ExampleMeta = {
@@ -15,7 +15,7 @@ import { ExampleBanner, type ExampleMeta } from "../_banner.js"
15
15
  export const meta: ExampleMeta = {
16
16
  name: "Task List",
17
17
  description: "Scrollable list with priority badges, toggles, and expandable subtasks",
18
- features: ["VirtualList", "variable itemHeight", "Box overflow"],
18
+ features: ["ListView", "variable estimateHeight", "Box overflow"],
19
19
  }
20
20
 
21
21
  // ============================================================================
@@ -161,7 +161,7 @@ export function TaskList() {
161
161
  const [cursor, setCursor] = useState(0)
162
162
  const [expandedTasks, setExpandedTasks] = useState<Set<number>>(new Set())
163
163
 
164
- // Fixed visible count (in a real app, this would use useContentRect)
164
+ // Fixed visible count (in a real app, this would use useBoxRect)
165
165
  const visibleCount = 15
166
166
 
167
167
  // Calculate scroll offset to keep cursor visible
@@ -10,25 +10,13 @@
10
10
  */
11
11
 
12
12
  import React, { useState } from "react"
13
- import {
14
- render,
15
- Box,
16
- Text,
17
- H1,
18
- Strong,
19
- Muted,
20
- TextArea,
21
- useInput,
22
- useApp,
23
- createTerm,
24
- type Key,
25
- } from "silvery"
13
+ import { render, Box, Text, H1, Strong, Muted, TextArea, useInput, useApp, createTerm, type Key } from "silvery"
26
14
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
27
15
 
28
16
  export const meta: ExampleMeta = {
29
17
  name: "TextArea",
30
18
  description: "Multi-line text input with word wrap, scrolling, and kill operations",
31
- features: ["TextArea", "useContentRect()", "Ctrl+Enter submit"],
19
+ features: ["TextArea", "useBoxRect()", "Ctrl+Enter submit"],
32
20
  }
33
21
 
34
22
  export function NoteEditor() {
@@ -14,20 +14,7 @@
14
14
  */
15
15
 
16
16
  import React, { useState } from "react"
17
- import {
18
- render,
19
- Box,
20
- Text,
21
- H1,
22
- Small,
23
- Kbd,
24
- Muted,
25
- Transform,
26
- useInput,
27
- useApp,
28
- createTerm,
29
- type Key,
30
- } from "silvery"
17
+ import { render, Box, Text, H1, Small, Kbd, Muted, Transform, useInput, useApp, createTerm, type Key } from "silvery"
31
18
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
32
19
 
33
20
  export const meta: ExampleMeta = {
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Virtual Scroll Benchmark — 10,000 Items
3
3
  *
4
- * Demonstrates that VirtualList handles massive datasets with instant scrolling.
4
+ * Demonstrates that ListView handles massive datasets with instant scrolling.
5
5
  * Only visible items + overscan are rendered, regardless of total count.
6
6
  *
7
7
  * Demonstrates:
8
- * - VirtualList with 10,000 items and variable heights
8
+ * - ListView with 10,000 items and variable heights
9
9
  * - Smooth j/k navigation with position indicator
10
- * - useContentRect() for adaptive column count
10
+ * - useBoxRect() for adaptive column count
11
11
  * - Page up/down with large jumps
12
12
  * - Visual item variety (priorities, tags, progress bars)
13
13
  *
@@ -22,14 +22,14 @@
22
22
  */
23
23
 
24
24
  import React, { useState, useCallback, useMemo } from "react"
25
- import { Box, Text, Strong, Kbd, Muted, Divider, VirtualList, useContentRect } from "silvery"
25
+ import { Box, Text, Strong, Kbd, Muted, Divider, ListView, useBoxRect } from "silvery"
26
26
  import { run, useInput, type Key } from "silvery/runtime"
27
27
  import { ExampleBanner, type ExampleMeta } from "../_banner.js"
28
28
 
29
29
  export const meta: ExampleMeta = {
30
30
  name: "Virtual 10K",
31
- description: "VirtualList scrolling through 10,000 items with instant navigation",
32
- features: ["VirtualList", "10K items", "useContentRect()", "variable itemHeight"],
31
+ description: "ListView scrolling through 10,000 items with instant navigation",
32
+ features: ["ListView", "10K items", "useBoxRect()", "variable estimateHeight"],
33
33
  }
34
34
 
35
35
  // ============================================================================
@@ -287,7 +287,7 @@ function StatsBar({ items }: { items: Item[] }) {
287
287
  // ============================================================================
288
288
 
289
289
  function VirtualBenchmark() {
290
- const { width, height } = useContentRect()
290
+ const { width, height } = useBoxRect()
291
291
  const [cursor, setCursor] = useState(0)
292
292
  const [showDetail, setShowDetail] = useState(false)
293
293
 
@@ -296,8 +296,8 @@ function VirtualBenchmark() {
296
296
  const listHeight = Math.max(5, height - 5)
297
297
  const halfPage = Math.max(1, Math.floor(listHeight / 2))
298
298
 
299
- const itemHeight = useCallback(
300
- (_item: Item, index: number) => {
299
+ const estimateHeight = useCallback(
300
+ (index: number) => {
301
301
  if (showDetail && index === cursor) return 2
302
302
  return 1
303
303
  },
@@ -356,10 +356,10 @@ function VirtualBenchmark() {
356
356
 
357
357
  {/* Virtual list */}
358
358
  <Box flexGrow={1}>
359
- <VirtualList
359
+ <ListView
360
360
  items={ALL_ITEMS}
361
361
  height={listHeight}
362
- itemHeight={itemHeight}
362
+ estimateHeight={estimateHeight}
363
363
  scrollTo={cursor}
364
364
  overscan={5}
365
365
  renderItem={(item, index) => (
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VirtualList — Efficient scrollable list
2
+ * ListView — Efficient scrollable list
3
3
  *
4
4
  * Renders 200 items but only materializes visible rows.
5
5
  * Built-in j/k navigation, page up/down, Home/End.
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import React from "react"
11
- import { Box, Text, VirtualList } from "silvery"
11
+ import { Box, Text, ListView } from "silvery"
12
12
  import { run, useInput } from "silvery/runtime"
13
13
 
14
14
  const items = Array.from({ length: 200 }, (_, i) => ({
@@ -16,7 +16,7 @@ const items = Array.from({ length: 200 }, (_, i) => ({
16
16
  name: `Item ${i + 1}`,
17
17
  }))
18
18
 
19
- function VirtualListDemo() {
19
+ function ListViewDemo() {
20
20
  useInput((input, key) => {
21
21
  if (input === "q" || key.escape) return "exit"
22
22
  })
@@ -24,14 +24,14 @@ function VirtualListDemo() {
24
24
  return (
25
25
  <Box flexDirection="column" padding={1} gap={1}>
26
26
  <Text bold>200 items (virtualized)</Text>
27
- <VirtualList
27
+ <ListView
28
28
  items={items}
29
29
  height={12}
30
- itemHeight={1}
31
- interactive
30
+ estimateHeight={1}
31
+ nav
32
32
  renderItem={(item, _index, meta) => (
33
- <Text key={item.id} color={meta?.isSelected ? "$primary" : undefined} bold={meta?.isSelected}>
34
- {meta?.isSelected ? "> " : " "}
33
+ <Text key={item.id} color={meta.isCursor ? "$primary" : undefined} bold={meta.isCursor}>
34
+ {meta.isCursor ? "> " : " "}
35
35
  {item.name}
36
36
  </Text>
37
37
  )}
@@ -47,6 +47,6 @@ export const meta = {
47
47
  }
48
48
 
49
49
  if (import.meta.main) {
50
- const handle = await run(<VirtualListDemo />)
50
+ const handle = await run(<ListViewDemo />)
51
51
  await handle.waitUntilExit()
52
52
  }
package/package.json CHANGED
@@ -1,27 +1,28 @@
1
1
  {
2
2
  "name": "@silvery/examples",
3
- "version": "0.4.4",
4
- "description": "Example apps and component demos for silvery \u2014 bunx @silvery/examples <name>",
3
+ "version": "0.5.1",
4
+ "description": "Example apps and component demos for silvery bunx @silvery/examples <name>",
5
5
  "license": "MIT",
6
- "author": "Bj\u00f8rn Stabell <bjorn@stabell.org>",
6
+ "author": "Bjørn Stabell <bjorn@stabell.org>",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/beorn/silvery.git",
10
- "directory": "packages/cli"
10
+ "directory": "packages/examples"
11
+ },
12
+ "bin": {
13
+ "silvery-examples": "./bin/cli.ts"
11
14
  },
12
15
  "files": [
13
16
  "bin",
14
17
  "examples"
15
18
  ],
16
19
  "type": "module",
17
- "bin": {
18
- "silvery-examples": "./bin/cli.ts"
19
- },
20
20
  "publishConfig": {
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "silvery": ">=0.4.0",
25
- "@silvery/tea": ">=0.4.0"
24
+ "@silvery/ag-term": "0.5.0",
25
+ "@silvery/create": "0.5.0",
26
+ "silvery": "0.11.1"
26
27
  }
27
- }
28
+ }