termcast 1.3.53 → 1.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.
Files changed (196) hide show
  1. package/dist/action-utils.d.ts.map +1 -1
  2. package/dist/action-utils.js +17 -132
  3. package/dist/action-utils.js.map +1 -1
  4. package/dist/apis/cache.d.ts +8 -30
  5. package/dist/apis/cache.d.ts.map +1 -1
  6. package/dist/apis/cache.js +9 -271
  7. package/dist/apis/cache.js.map +1 -1
  8. package/dist/apis/clipboard.d.ts +4 -2
  9. package/dist/apis/clipboard.d.ts.map +1 -1
  10. package/dist/apis/clipboard.js +18 -31
  11. package/dist/apis/clipboard.js.map +1 -1
  12. package/dist/apis/environment.d.ts.map +1 -1
  13. package/dist/apis/environment.js +14 -49
  14. package/dist/apis/environment.js.map +1 -1
  15. package/dist/apis/localstorage.d.ts +7 -12
  16. package/dist/apis/localstorage.d.ts.map +1 -1
  17. package/dist/apis/localstorage.js +7 -184
  18. package/dist/apis/localstorage.js.map +1 -1
  19. package/dist/app.d.ts.map +1 -1
  20. package/dist/app.js +46 -20
  21. package/dist/app.js.map +1 -1
  22. package/dist/cli.js +7 -6
  23. package/dist/cli.js.map +1 -1
  24. package/dist/components/actions.d.ts.map +1 -1
  25. package/dist/components/actions.js +13 -2
  26. package/dist/components/actions.js.map +1 -1
  27. package/dist/components/candle-chart.d.ts +110 -0
  28. package/dist/components/candle-chart.d.ts.map +1 -0
  29. package/dist/components/candle-chart.js +295 -0
  30. package/dist/components/candle-chart.js.map +1 -0
  31. package/dist/components/extension-preferences.d.ts.map +1 -1
  32. package/dist/components/extension-preferences.js +7 -8
  33. package/dist/components/extension-preferences.js.map +1 -1
  34. package/dist/components/form/file-autocomplete.js +2 -2
  35. package/dist/components/form/file-autocomplete.js.map +1 -1
  36. package/dist/components/list.d.ts.map +1 -1
  37. package/dist/components/list.js +242 -14
  38. package/dist/components/list.js.map +1 -1
  39. package/dist/components/table.d.ts +2 -0
  40. package/dist/components/table.d.ts.map +1 -1
  41. package/dist/components/table.js +41 -4
  42. package/dist/components/table.js.map +1 -1
  43. package/dist/e2e-node.d.ts.map +1 -1
  44. package/dist/e2e-node.js +5 -4
  45. package/dist/e2e-node.js.map +1 -1
  46. package/dist/examples/simple-candle-chart-data.d.ts +9064 -0
  47. package/dist/examples/simple-candle-chart-data.d.ts.map +1 -0
  48. package/dist/examples/simple-candle-chart-data.js +12683 -0
  49. package/dist/examples/simple-candle-chart-data.js.map +1 -0
  50. package/dist/examples/simple-candle-chart.d.ts +2 -0
  51. package/dist/examples/simple-candle-chart.d.ts.map +1 -0
  52. package/dist/examples/simple-candle-chart.js +125 -0
  53. package/dist/examples/simple-candle-chart.js.map +1 -0
  54. package/dist/extensions/dev.d.ts.map +1 -1
  55. package/dist/extensions/dev.js +5 -2
  56. package/dist/extensions/dev.js.map +1 -1
  57. package/dist/globals.d.ts.map +1 -1
  58. package/dist/globals.js +2 -1
  59. package/dist/globals.js.map +1 -1
  60. package/dist/index.d.ts +2 -0
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +2 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/internal/error-handler.d.ts.map +1 -1
  65. package/dist/internal/error-handler.js +21 -19
  66. package/dist/internal/error-handler.js.map +1 -1
  67. package/dist/internal/providers.d.ts.map +1 -1
  68. package/dist/internal/providers.js +41 -1
  69. package/dist/internal/providers.js.map +1 -1
  70. package/dist/logger.d.ts.map +1 -1
  71. package/dist/logger.js +31 -29
  72. package/dist/logger.js.map +1 -1
  73. package/dist/platform/browser/cache.d.ts +41 -0
  74. package/dist/platform/browser/cache.d.ts.map +1 -0
  75. package/dist/platform/browser/cache.js +262 -0
  76. package/dist/platform/browser/cache.js.map +1 -0
  77. package/dist/platform/browser/localstorage.d.ts +20 -0
  78. package/dist/platform/browser/localstorage.d.ts.map +1 -0
  79. package/dist/platform/browser/localstorage.js +102 -0
  80. package/dist/platform/browser/localstorage.js.map +1 -0
  81. package/dist/platform/browser/runtime.d.ts +51 -0
  82. package/dist/platform/browser/runtime.d.ts.map +1 -0
  83. package/dist/platform/browser/runtime.js +164 -0
  84. package/dist/platform/browser/runtime.js.map +1 -0
  85. package/dist/platform/bun/sqlite.d.ts +17 -0
  86. package/dist/platform/bun/sqlite.d.ts.map +1 -0
  87. package/dist/platform/bun/sqlite.js +6 -0
  88. package/dist/platform/bun/sqlite.js.map +1 -0
  89. package/dist/platform/node/cache.d.ts +35 -0
  90. package/dist/platform/node/cache.d.ts.map +1 -0
  91. package/dist/platform/node/cache.js +269 -0
  92. package/dist/platform/node/cache.js.map +1 -0
  93. package/dist/platform/node/localstorage.d.ts +17 -0
  94. package/dist/platform/node/localstorage.d.ts.map +1 -0
  95. package/dist/platform/node/localstorage.js +186 -0
  96. package/dist/platform/node/localstorage.js.map +1 -0
  97. package/dist/platform/node/runtime.d.ts +52 -0
  98. package/dist/platform/node/runtime.d.ts.map +1 -0
  99. package/dist/platform/node/runtime.js +230 -0
  100. package/dist/platform/node/runtime.js.map +1 -0
  101. package/dist/platform/node/sqlite.d.ts +27 -0
  102. package/dist/platform/node/sqlite.d.ts.map +1 -0
  103. package/dist/platform/node/sqlite.js +21 -0
  104. package/dist/platform/node/sqlite.js.map +1 -0
  105. package/dist/state.d.ts +5 -0
  106. package/dist/state.d.ts.map +1 -1
  107. package/dist/state.js +6 -28
  108. package/dist/state.js.map +1 -1
  109. package/dist/utils/file-system.d.ts.map +1 -1
  110. package/dist/utils/file-system.js +17 -22
  111. package/dist/utils/file-system.js.map +1 -1
  112. package/dist/utils.d.ts +1 -1
  113. package/dist/utils.d.ts.map +1 -1
  114. package/dist/utils.js +42 -47
  115. package/dist/utils.js.map +1 -1
  116. package/dist/vim-mode.d.ts +40 -0
  117. package/dist/vim-mode.d.ts.map +1 -0
  118. package/dist/vim-mode.js +135 -0
  119. package/dist/vim-mode.js.map +1 -0
  120. package/fonts/Inconsolata.otf +0 -0
  121. package/fonts/SIL Open Font License.txt +41 -0
  122. package/package.json +60 -8
  123. package/src/action-utils.tsx +27 -124
  124. package/src/apis/cache.test.ts +1 -1
  125. package/src/apis/cache.tsx +9 -373
  126. package/src/apis/clipboard.tsx +29 -38
  127. package/src/apis/environment.tsx +25 -52
  128. package/src/apis/localstorage.tsx +8 -214
  129. package/src/app.tsx +51 -20
  130. package/src/cli.tsx +14 -15
  131. package/src/compile.vitest.tsx +2 -2
  132. package/src/components/actions.tsx +19 -1
  133. package/src/components/candle-chart.tsx +410 -0
  134. package/src/components/extension-preferences.tsx +7 -8
  135. package/src/components/form/file-autocomplete.tsx +2 -2
  136. package/src/components/list.tsx +279 -14
  137. package/src/components/table.tsx +46 -4
  138. package/src/e2e-node.tsx +7 -7
  139. package/src/examples/action-shortcut.vitest.tsx +2 -2
  140. package/src/examples/actions-context.vitest.tsx +1 -1
  141. package/src/examples/bar-graph-weekly.vitest.tsx +10 -36
  142. package/src/examples/detail-metadata-showcase.vitest.tsx +36 -36
  143. package/src/examples/form-basic.vitest.tsx +21 -17
  144. package/src/examples/github.vitest.tsx +4 -4
  145. package/src/examples/graph-bar-chart.vitest.tsx +13 -11
  146. package/src/examples/graph-polymarket.vitest.tsx +2 -2
  147. package/src/examples/graph-row.vitest.tsx +66 -66
  148. package/src/examples/graph-styles.vitest.tsx +12 -12
  149. package/src/examples/internal/simple-scrollbox.vitest.tsx +14 -48
  150. package/src/examples/list-detail-metadata.vitest.tsx +5 -5
  151. package/src/examples/list-fetch-data.vitest.tsx +3 -3
  152. package/src/examples/list-item-accessories.vitest.tsx +2 -2
  153. package/src/examples/list-loading-empty-view.vitest.tsx +1 -1
  154. package/src/examples/list-no-actions.vitest.tsx +2 -2
  155. package/src/examples/list-scrollbox.vitest.tsx +5 -5
  156. package/src/examples/list-spacing-mode.vitest.tsx +3 -3
  157. package/src/examples/list-with-detail.vitest.tsx +68 -68
  158. package/src/examples/list-with-dropdown.vitest.tsx +5 -5
  159. package/src/examples/list-with-sections.vitest.tsx +27 -27
  160. package/src/examples/simple-candle-chart-data.ts +12683 -0
  161. package/src/examples/simple-candle-chart.tsx +363 -0
  162. package/src/examples/simple-candle-chart.vitest.tsx +269 -0
  163. package/src/examples/simple-detail-markdown.vitest.tsx +8 -8
  164. package/src/examples/simple-detail-table.vitest.tsx +10 -10
  165. package/src/examples/simple-graph.vitest.tsx +3 -3
  166. package/src/examples/simple-grid.vitest.tsx +14 -14
  167. package/src/examples/simple-heatmap.vitest.tsx +1 -1
  168. package/src/examples/simple-navigation.vitest.tsx +17 -17
  169. package/src/examples/simple-progress-bar.vitest.tsx +1 -1
  170. package/src/examples/simple-table-wrap.vitest.tsx +19 -19
  171. package/src/examples/store.vitest.tsx +1 -1
  172. package/src/examples/swift-extension.vitest.tsx +2 -2
  173. package/src/examples/table-edge-cases.vitest.tsx +18 -18
  174. package/src/examples/table-flex-grow.vitest.tsx +8 -8
  175. package/src/examples/toast-action.vitest.tsx +2 -2
  176. package/src/extensions/dev.tsx +5 -2
  177. package/src/extensions/dev.vitest.tsx +3 -3
  178. package/src/globals.ts +2 -1
  179. package/src/index.tsx +7 -0
  180. package/src/internal/error-handler.tsx +19 -21
  181. package/src/internal/providers.tsx +39 -0
  182. package/src/logger.tsx +38 -41
  183. package/src/platform/browser/cache.ts +327 -0
  184. package/src/platform/browser/localstorage.ts +119 -0
  185. package/src/platform/browser/runtime.ts +209 -0
  186. package/src/platform/bun/sqlite.ts +19 -0
  187. package/src/platform/node/cache.ts +372 -0
  188. package/src/platform/node/localstorage.ts +214 -0
  189. package/src/platform/node/runtime.ts +264 -0
  190. package/src/platform/node/sqlite.ts +43 -0
  191. package/src/state.tsx +17 -28
  192. package/src/utils/file-system.ts +17 -22
  193. package/src/utils.test.tsx +1 -1
  194. package/src/utils.tsx +56 -47
  195. package/src/vim-mode.tsx +153 -0
  196. package/src/apis/sqlite.ts +0 -14
@@ -0,0 +1,363 @@
1
+ // Example: CandleChart component showing realistic crypto OHLC data.
2
+ // List of markets with candlestick charts in the side detail panel.
3
+ // Green bars = bullish (close >= open), red bars = bearish (close < open).
4
+ // Wicks extend from body to high/low extremes.
5
+ //
6
+ // Demonstrates:
7
+ // - CandleChart in List.Item.Detail (side panel)
8
+ // - CandleChart in full-page Detail (pushed on Enter)
9
+ // - Mixed component types: CandleChart + Graph overlay, CandleChart + BarChart volume,
10
+ // Row for side-by-side comparisons, metadata with tags
11
+ // - Realistic crypto market action from frozen Coinbase daily OHLCV data
12
+
13
+ import React from 'react'
14
+ import { Action, ActionPanel, List, Detail, Color, CandleChart, Graph, BarChart, Row } from 'termcast'
15
+ import type { CandleData } from 'termcast'
16
+ import { useNavigation } from 'termcast/src/internal/navigation'
17
+ import { renderWithProviders } from '../utils'
18
+ import { cryptoMarketData } from './simple-candle-chart-data'
19
+
20
+ interface Market {
21
+ symbol: string
22
+ displaySymbol: string
23
+ name: string
24
+ sector: string
25
+ currentPrice: number
26
+ change: number
27
+ priceDecimals: number
28
+ candles: CandleData[]
29
+ closingPrices: number[]
30
+ volume: number[]
31
+ }
32
+
33
+ interface MixedItem {
34
+ title: string
35
+ subtitle: string
36
+ market: Market
37
+ mode: 'candle-only' | 'candle-with-line' | 'candle-with-volume' | 'side-by-side'
38
+ }
39
+
40
+ const tickers: Market[] = cryptoMarketData.map((market) => {
41
+ const candles: CandleData[] = market.candles.map((candle) => {
42
+ return {
43
+ open: candle.open,
44
+ close: candle.close,
45
+ high: candle.high,
46
+ low: candle.low,
47
+ }
48
+ })
49
+
50
+ return {
51
+ symbol: market.symbol,
52
+ displaySymbol: market.symbol.replace('-USD', ''),
53
+ name: market.name,
54
+ sector: market.sector,
55
+ currentPrice: market.currentPrice,
56
+ change: market.change,
57
+ priceDecimals: market.priceDecimals,
58
+ candles,
59
+ closingPrices: candles.map((candle) => {
60
+ return candle.close
61
+ }),
62
+ volume: [...market.volume],
63
+ }
64
+ })
65
+
66
+ const mixedItems: MixedItem[] = [
67
+ { title: 'BTC - Candles', subtitle: 'Real BTC/USD hourly candles', market: tickers[0]!, mode: 'candle-only' },
68
+ { title: 'ETH - Candle + Line', subtitle: 'Candles plus closing line', market: tickers[1]!, mode: 'candle-with-line' },
69
+ { title: 'SOL - Candle + Volume', subtitle: 'Candles plus volume split', market: tickers[2]!, mode: 'candle-with-volume' },
70
+ { title: 'BTC vs ETH', subtitle: 'Side-by-side crypto leaders', market: tickers[0]!, mode: 'side-by-side' },
71
+ { title: 'DOGE - Candle + Line', subtitle: 'Low-priced asset formatting', market: tickers[4]!, mode: 'candle-with-line' },
72
+ ]
73
+
74
+ // 300 hourly candles ≈ 12.5 days
75
+ const xLabels = ['12d', '8d', '4d', 'Now']
76
+
77
+ function formatNumber({ value, decimals }: { value: number; decimals: number }): string {
78
+ return new Intl.NumberFormat('en-US', {
79
+ minimumFractionDigits: decimals,
80
+ maximumFractionDigits: decimals,
81
+ }).format(value)
82
+ }
83
+
84
+ function formatPrice({ value, decimals }: { value: number; decimals: number }): string {
85
+ return `$${formatNumber({ value, decimals })}`
86
+ }
87
+
88
+ function formatPercent(change: number): string {
89
+ return change >= 0 ? `+${change.toFixed(1)}%` : `${change.toFixed(1)}%`
90
+ }
91
+
92
+ function changeColor(change: number): Color.ColorLike {
93
+ if (change > 0) return Color.Green
94
+ if (change < 0) return Color.Red
95
+ return Color.SecondaryText
96
+ }
97
+
98
+ function yAxisFormatter({ market }: { market: Market }): (value: number) => string {
99
+ return (value) => {
100
+ return formatPrice({ value, decimals: market.priceDecimals })
101
+ }
102
+ }
103
+
104
+ function ChartForMode({ item, height }: { item: MixedItem; height: number }): any {
105
+ const { market } = item
106
+ const yFormat = yAxisFormatter({ market })
107
+
108
+ switch (item.mode) {
109
+ case 'candle-only': {
110
+ return (
111
+ <CandleChart
112
+ data={market.candles}
113
+ height={height}
114
+ xLabels={xLabels}
115
+ yTicks={4}
116
+ yFormat={yFormat}
117
+ />
118
+ )
119
+ }
120
+ case 'candle-with-line': {
121
+ return (
122
+ <>
123
+ <CandleChart
124
+ data={market.candles}
125
+ height={height}
126
+ xLabels={xLabels}
127
+ yTicks={4}
128
+ yFormat={yFormat}
129
+ />
130
+ <Graph
131
+ height={Math.max(5, height - 3)}
132
+ xLabels={xLabels}
133
+ yTicks={3}
134
+ yFormat={yFormat}
135
+ variant="area"
136
+ >
137
+ <Graph.Line data={market.closingPrices} color={Color.Blue} title="Close" />
138
+ </Graph>
139
+ </>
140
+ )
141
+ }
142
+ case 'candle-with-volume': {
143
+ const firstHalf = market.volume.slice(0, 15).reduce((sum, value) => {
144
+ return sum + value
145
+ }, 0)
146
+ const secondHalf = market.volume.slice(15).reduce((sum, value) => {
147
+ return sum + value
148
+ }, 0)
149
+
150
+ return (
151
+ <>
152
+ <CandleChart
153
+ data={market.candles}
154
+ height={height}
155
+ xLabels={xLabels}
156
+ yTicks={4}
157
+ yFormat={yFormat}
158
+ />
159
+ <BarChart height={1}>
160
+ <BarChart.Segment value={firstHalf} label="First half" color={Color.Blue} />
161
+ <BarChart.Segment value={secondHalf} label="Second half" color={Color.Orange} />
162
+ </BarChart>
163
+ </>
164
+ )
165
+ }
166
+ case 'side-by-side': {
167
+ const eth = tickers[1]!
168
+ return (
169
+ <Row>
170
+ <CandleChart
171
+ data={market.candles}
172
+ height={height}
173
+ xLabels={['30d', 'Now']}
174
+ yTicks={3}
175
+ yFormat={yAxisFormatter({ market })}
176
+ />
177
+ <CandleChart
178
+ data={eth.candles}
179
+ height={height}
180
+ xLabels={['30d', 'Now']}
181
+ yTicks={3}
182
+ yFormat={yAxisFormatter({ market: eth })}
183
+ />
184
+ </Row>
185
+ )
186
+ }
187
+ }
188
+ }
189
+
190
+ function CandleDetailView({ item }: { item: MixedItem }): any {
191
+ const { pop } = useNavigation()
192
+ const { market } = item
193
+ const changeStr = formatPercent(market.change)
194
+
195
+ const markdown = [
196
+ `# ${market.displaySymbol} - ${market.name}`,
197
+ '',
198
+ `**Category:** ${market.sector}`,
199
+ '',
200
+ `**Price:** ${formatPrice({ value: market.currentPrice, decimals: market.priceDecimals })} `,
201
+ `**24h change:** ${changeStr}`,
202
+ '',
203
+ `**Mode:** \`${item.mode}\``,
204
+ '',
205
+ '300 hourly candles from Coinbase Exchange, frozen so the example stays deterministic.',
206
+ ].join('\n')
207
+
208
+ return (
209
+ <Detail
210
+ navigationTitle={`${market.displaySymbol} Detail`}
211
+ markdown={markdown}
212
+ metadata={
213
+ <Detail.Metadata>
214
+ <ChartForMode item={item} height={15} />
215
+ <Detail.Metadata.Separator />
216
+ <Detail.Metadata.Label
217
+ title="Price"
218
+ text={{ value: formatPrice({ value: market.currentPrice, decimals: market.priceDecimals }), color: changeColor(market.change) }}
219
+ />
220
+ <Detail.Metadata.Label
221
+ title="Change"
222
+ text={{ value: changeStr, color: changeColor(market.change) }}
223
+ />
224
+ <Detail.Metadata.Label title="Category" text={market.sector} />
225
+ <Detail.Metadata.Separator />
226
+ <Detail.Metadata.TagList title="Components">
227
+ <Detail.Metadata.TagList.Item text="CandleChart" color={Color.Green} />
228
+ {item.mode === 'candle-with-line' && (
229
+ <Detail.Metadata.TagList.Item text="Graph" color={Color.Blue} />
230
+ )}
231
+ {item.mode === 'candle-with-volume' && (
232
+ <Detail.Metadata.TagList.Item text="BarChart" color={Color.Orange} />
233
+ )}
234
+ {item.mode === 'side-by-side' && (
235
+ <Detail.Metadata.TagList.Item text="Row" color={Color.Purple} />
236
+ )}
237
+ </Detail.Metadata.TagList>
238
+ </Detail.Metadata>
239
+ }
240
+ actions={
241
+ <ActionPanel>
242
+ <Action title="Go Back" onAction={() => { pop() }} />
243
+ </ActionPanel>
244
+ }
245
+ />
246
+ )
247
+ }
248
+
249
+ function SimpleCandleChart() {
250
+ const { push } = useNavigation()
251
+
252
+ return (
253
+ <List
254
+ navigationTitle="Crypto Markets"
255
+ searchBarPlaceholder="Search markets..."
256
+ isShowingDetail={true}
257
+ >
258
+ <List.Section title="Watchlist">
259
+ {tickers.map((market) => {
260
+ const changeStr = formatPercent(market.change)
261
+ return (
262
+ <List.Item
263
+ key={market.symbol}
264
+ id={market.symbol}
265
+ title={market.displaySymbol}
266
+ subtitle={market.name}
267
+ accessories={[
268
+ { text: { value: formatPrice({ value: market.currentPrice, decimals: market.priceDecimals }), color: changeColor(market.change) } },
269
+ { text: { value: changeStr, color: changeColor(market.change) } },
270
+ ]}
271
+ detail={
272
+ <List.Item.Detail
273
+ metadata={
274
+ <List.Item.Detail.Metadata>
275
+ <CandleChart
276
+ data={market.candles}
277
+ height={10}
278
+ xLabels={xLabels}
279
+ yTicks={4}
280
+ yFormat={yAxisFormatter({ market })}
281
+ />
282
+ <List.Item.Detail.Metadata.Label
283
+ title="Price"
284
+ text={{ value: formatPrice({ value: market.currentPrice, decimals: market.priceDecimals }), color: changeColor(market.change) }}
285
+ />
286
+ <List.Item.Detail.Metadata.Label
287
+ title="Change"
288
+ text={{ value: changeStr, color: changeColor(market.change) }}
289
+ />
290
+ <List.Item.Detail.Metadata.Label title="Category" text={market.sector} />
291
+ <List.Item.Detail.Metadata.Separator />
292
+ <List.Item.Detail.Metadata.Label title={`${market.symbol} Hourly OHLC`} />
293
+ </List.Item.Detail.Metadata>
294
+ }
295
+ />
296
+ }
297
+ actions={
298
+ <ActionPanel>
299
+ <Action
300
+ title="Open Detail"
301
+ onAction={() => {
302
+ push(
303
+ <CandleDetailView
304
+ item={{ title: market.displaySymbol, subtitle: market.name, market, mode: 'candle-only' }}
305
+ />,
306
+ )
307
+ }}
308
+ />
309
+ </ActionPanel>
310
+ }
311
+ />
312
+ )
313
+ })}
314
+ </List.Section>
315
+
316
+ <List.Section title="Mixed Components">
317
+ {mixedItems.map((item) => {
318
+ const { market } = item
319
+ const changeStr = formatPercent(market.change)
320
+ return (
321
+ <List.Item
322
+ key={item.title}
323
+ id={item.title}
324
+ title={item.title}
325
+ subtitle={item.subtitle}
326
+ accessories={[
327
+ { text: { value: formatPrice({ value: market.currentPrice, decimals: market.priceDecimals }), color: changeColor(market.change) } },
328
+ ]}
329
+ detail={
330
+ <List.Item.Detail
331
+ metadata={
332
+ <List.Item.Detail.Metadata>
333
+ <ChartForMode item={item} height={8} />
334
+ <List.Item.Detail.Metadata.Separator />
335
+ <List.Item.Detail.Metadata.Label
336
+ title="Price"
337
+ text={{ value: formatPrice({ value: market.currentPrice, decimals: market.priceDecimals }), color: changeColor(market.change) }}
338
+ />
339
+ <List.Item.Detail.Metadata.Label
340
+ title="Change"
341
+ text={{ value: changeStr, color: changeColor(market.change) }}
342
+ />
343
+ </List.Item.Detail.Metadata>
344
+ }
345
+ />
346
+ }
347
+ actions={
348
+ <ActionPanel>
349
+ <Action
350
+ title="Open Detail"
351
+ onAction={() => { push(<CandleDetailView item={item} />) }}
352
+ />
353
+ </ActionPanel>
354
+ }
355
+ />
356
+ )
357
+ })}
358
+ </List.Section>
359
+ </List>
360
+ )
361
+ }
362
+
363
+ renderWithProviders(<SimpleCandleChart />)
@@ -0,0 +1,269 @@
1
+ // E2E tests for CandleChart component showing realistic crypto OHLC candles.
2
+ // Verifies candle body (▌/▘/▖) and wick (│) characters render,
3
+ // Y-axis labels, X-axis labels, and navigation between tickers.
4
+ // Also tests full-page Detail view and mixed component types
5
+ // (CandleChart + Graph, CandleChart + BarChart, Row side-by-side).
6
+
7
+ import { test, expect, afterEach, beforeEach } from 'vitest'
8
+ import { launchTerminal, Session } from 'tuistory/src'
9
+
10
+ let session: Session
11
+
12
+ beforeEach(async () => {
13
+ session = await launchTerminal({
14
+ command: 'bun',
15
+ args: ['src/examples/simple-candle-chart.tsx'],
16
+ cols: 100,
17
+ rows: 30,
18
+ })
19
+ })
20
+
21
+ afterEach(() => {
22
+ session?.close()
23
+ })
24
+
25
+ test('candle chart renders in list detail with axes', async () => {
26
+ const text = await session.text({
27
+ waitFor: (text) => {
28
+ return text.includes('BTC') && text.includes('$') && text.includes('12d')
29
+ },
30
+ timeout: 10000,
31
+ })
32
+
33
+ expect(text).toMatchInlineSnapshot(`
34
+ "
35
+
36
+
37
+ Crypto Markets ───────────────────────────────────────────────────────────────────────────────
38
+
39
+ > Search markets...
40
+
41
+ Watchlist │ $74,678│ │
42
+ ›BTC Bitcoin │ │ ▌▌▖│
43
+ ETH Ethereum │ │ ▌│▘▌▖│
44
+ SOL Solana │ $70,438│ │ │ ▌ │▘▘▌
45
+ XRP XRP │ │ ▖▖▖▖ ▌▌ ▖▖▌ ▌││
46
+ DOGE Dogecoin │ │ ▌││▌▖▖ ▖▖│││▌▘▌▌│ ▘▘▘▌
47
+ BNB BNB │ $66,197│▖▖ │▌ ▘▘▘▌▖▖▌▘▌▖▌▌▌ ▘▘
48
+ │ ││▌▖ │▌▘ ▘▘▌▌ ▘▘▘▘
49
+ Mixed Components │ │ │▌▌▘▘ ▘▘
50
+ BTC - Candles Real BTC/USD hourly candles │ $61,957│ ││
51
+ ETH - Candle + Line Candles plus closing line │ 12d 8d 4d Now
52
+ SOL - Candle + Volume Candles plus volume spli │
53
+ BTC vs ETH Side-by-side crypto leaders │ Price: $67,641
54
+ DOGE - Candle + Line Low-priced asset formatti │
55
+ │ Change: -0.2%
56
+
57
+ │ Category: Store of Value
58
+
59
+ │ ────────────────────────────────────────────
60
+
61
+ ↵ open detail ↑↓ navigate ^k actions :vi │ BTC-USD Hourly OHLC
62
+
63
+
64
+ "
65
+ `)
66
+
67
+ expect(text).toContain('BTC')
68
+ expect(text).toContain('12d')
69
+ expect(text).toContain('Now')
70
+ expect(text).toContain('$')
71
+ }, 30000)
72
+
73
+ test('push to full-page detail view on Enter', async () => {
74
+ await session.text({ waitFor: (t) => t.includes('BTC'), timeout: 10000 })
75
+ // Press Enter to push to Detail view
76
+ session.sendKey('return')
77
+
78
+ const text = await session.text({
79
+ waitFor: (t) => t.includes('candle-only') && t.includes('Go Back'),
80
+ timeout: 10000,
81
+ })
82
+
83
+ expect(text).toMatchInlineSnapshot(`
84
+ "
85
+
86
+
87
+
88
+
89
+ BTC - Bitcoin
90
+
91
+
92
+ Category: Store of Value
93
+ Price: $67,641
94
+ 24h change: -0.2%
95
+ Mode: candle-only
96
+ 300 hourly candles from Coinbase Exchange, frozen so the example stays deterministic.
97
+
98
+ $74,678│ │
99
+ │ ▖▌▌│ │
100
+ │ ▌│▘▌▖▖▌▌
101
+ │ │▌ ││ ▌│││
102
+ │ ▌▘ ▘▌▖▌▌▖▖
103
+ $70,438│ │ │ │▌ │││▌
104
+ │ ▌▌ │ ▌▌▖▖ ││││▌▘ ▘▌
105
+ │ ▌▘▌▌▘▌▖│ ││ │ ▌││▘▌▖│▌▌▖▖▌ ▌▖▖▖▖▖▖▖
106
+ │ ▖▌ │││ ▌▌▘▌▌▌ │▖▖▖ ││ ││ ▌ │▌│▌││▘▘ ││ ▘▘│▘
107
+ $66,197│▖▖▖ │ ▌ ▘▘ │ ▘▌▖ │▌▘ ▘▌▌▌│▖▌▌▖▌ ▘▌▌
108
+ │▘│▌▖ ▖▖▖▌ │▘▌▌▘▌ │▌▘│ ▘▌▌││▌▌
109
+ │ │▌▖▖ ││ ▌││ │││ ▌ ▖▌ ││
110
+
111
+
112
+ esc go back ^k actions ↵ Go Back powered by termcast.app
113
+
114
+ "
115
+ `)
116
+
117
+ // Full-page detail has larger chart and markdown content
118
+ expect(text).toContain('Bitcoin')
119
+ expect(text).toContain('candle-only')
120
+ }, 30000)
121
+
122
+ test('candle + line overlay (mixed components)', async () => {
123
+ await session.text({ waitFor: (t) => t.includes('Mixed Components'), timeout: 10000 })
124
+ // Navigate past Watchlist (6 items) to Mixed Components section,
125
+ // then one more item to ETH - Candle + Line.
126
+ for (let i = 0; i < 7; i++) {
127
+ session.sendKey('down')
128
+ }
129
+
130
+ const text = await session.text({
131
+ waitFor: (t) => t.includes('›ETH - Candle + Line') && t.includes('closing line'),
132
+ timeout: 10000,
133
+ })
134
+
135
+ expect(text).toMatchInlineSnapshot(`
136
+ "
137
+
138
+
139
+ Crypto Markets ───────────────────────────────────────────────────────────────────────────────
140
+
141
+ > Search markets...
142
+
143
+ Watchlist │ $2,220│ │
144
+ BTC Bitcoin │ │ │ ▌▌▖▖
145
+ ETH Ethereum │ $2,073│ ▖▖▖ │ ▖▖ ▖▌▘▘▌▖▖▖
146
+ SOL Solana │ │ │▌│▘▌▌▌ ▖▖ ▌▘▌││▌│ │││▌
147
+ XRP XRP │ │ │▌▘ │ ▌▖ ▖▌▘▌▖▌ ▌▌▘▘ ▌▌▘▘
148
+ DOGE Dogecoin │ $1,927│▖▖ ▖▌ │▘▌▖▌ │││ │
149
+ BNB BNB │ │▘▘▌▖▌▘│ ▘▘
150
+ │ $1,780│ ▘▘
151
+ Mixed Components │ 12d 8d 4d Now
152
+ BTC - Candles Real BTC/USD hourly candles │
153
+ ›ETH - Candle + Line Candles plus closing line │ $2,197│ ⢠⣆⣠⡀
154
+ SOL - Candle + Volume Candles plus volume spli │ │ ⣴⣤⣤ ⣀ ⢠⣀ ⢠⣿⣿⣿⣷⣶⣦⡀
155
+ BTC vs ETH Side-by-side crypto leaders │ $1,997│ ⢰⣿⣿⣿⣿⣿⣇ ⣠⣷⣴⣄⣀⢸⣿⣶⣠⣤⣼⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤
156
+ DOGE - Candle + Line Low-priced asset formatti │ │⣶⡄ ⣦⣾⣿⣿⣿⣿⣿⣿⣶⣶⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
157
+ │ $1,797│⣿⣿⣷⣦⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿
158
+ │ 12d 8d 4d Now
159
+
160
+ │ ────────────────────────────────────────────
161
+
162
+ │ Price: $1,971
163
+
164
+ ↵ open detail ↑↓ navigate ^k actions :vi │ Change: -0.3%
165
+
166
+ "
167
+ `)
168
+
169
+ expect(text).toContain('Candle + Line')
170
+ }, 30000)
171
+
172
+ test('candle + volume bar chart (mixed components)', async () => {
173
+ await session.text({ waitFor: (t) => t.includes('Mixed Components'), timeout: 10000 })
174
+ // Navigate to "Candle + Volume" item (8 downs from top)
175
+ for (let i = 0; i < 8; i++) {
176
+ session.sendKey('down')
177
+ }
178
+
179
+ const text = await session.text({
180
+ waitFor: (t) => t.includes('›SOL - Candle + Volume') && t.includes('half'),
181
+ timeout: 10000,
182
+ })
183
+
184
+ expect(text).toMatchInlineSnapshot(`
185
+ "
186
+
187
+
188
+ Crypto Markets ───────────────────────────────────────────────────────────────────────────────
189
+
190
+ > Search markets...
191
+
192
+ Watchlist │ $95.03│ ││
193
+ BTC Bitcoin │ │ │ │ ▖▖▖▖
194
+ ETH Ethereum │ $88.26│ ▌▌▖ ││ │ ▖▖ ▌▘▘▘▌▌▌│
195
+ SOL Solana │ │ ▖▌│▘▌▌▌ │▌▌▖▖▌▘▌▖▌▘ ▘▌│││
196
+ XRP XRP │ │ │▌ │ ▌▖│ ▌▘▘▘▌▌ ▘▘││ ▘▘▘▌
197
+ DOGE Dogecoin │ $81.48│▖▖ ▌▘ │▘▌▌▘ │
198
+ BNB BNB │ │▘▘▌▖▌▘ ▘▘
199
+ │ $74.71│ ▘▘│
200
+ Mixed Components │ 12d 8d 4d Now
201
+ BTC - Candles Real BTC/USD hourly candles │
202
+ ETH - Candle + Line Candles plus closing line │ ┌Second half: 95.3%┐
203
+ ›SOL - Candle + Volume Candles plus volume spli │
204
+ BTC vs ETH Side-by-side crypto leaders │
205
+ DOGE - Candle + Line Low-priced asset formatti │ ────────────────────────────────────────────
206
+
207
+ │ Price: $83.31
208
+
209
+ ↵ open detail ↑↓ navigate ^k actions :vi │ Change: -0.4%
210
+
211
+
212
+
213
+
214
+
215
+ "
216
+ `)
217
+
218
+ expect(text).toContain('Candle + Volume')
219
+ }, 30000)
220
+
221
+ test('side-by-side candle charts in Row', async () => {
222
+ await session.text({ waitFor: (t) => t.includes('Mixed Components'), timeout: 10000 })
223
+ // Navigate to "BTC vs ETH" item (9 downs from top)
224
+ for (let i = 0; i < 9; i++) {
225
+ session.sendKey('down')
226
+ }
227
+
228
+ const text = await session.text({
229
+ waitFor: (t) => t.includes('›BTC vs ETH'),
230
+ timeout: 10000,
231
+ })
232
+
233
+ expect(text).toMatchInlineSnapshot(`
234
+ "
235
+
236
+
237
+ Crypto Markets ───────────────────────────────────────────────────────────────────────────────
238
+
239
+ > Search markets...
240
+
241
+ Watchlist │ $74,678│ ││ $2,220│ │
242
+ BTC Bitcoin │ │ ▌▌ │ │ ││
243
+ ETH Ethereum │ │ │ │ ▌▘▌ │ ▖▖│ │ ▌▌▖
244
+ SOL Solana │ │ ▖▖ ││▌ ▌│ │ ▌▘▌ ││││▌│▌
245
+ XRP XRP │ $68,318│ ▌▘▌ ││▌▘▘ ▘▘ $2,000│ ▌│▌ ▌▌▌▌▌ ▘▘
246
+ DOGE Dogecoin │ │▖ ▌│▌▖▌▘▘│ ││ ▌ ▘▌▌│││
247
+ BNB BNB │ │▌▌▘ ▘▘ │▌▖▌ ││
248
+ │ $61,957│ │ │ $1,780│││
249
+ Mixed Components │ 30d Now 30d Now
250
+ BTC - Candles Real BTC/USD hourly candles │
251
+ ETH - Candle + Line Candles plus closing line │ ────────────────────────────────────────────
252
+ SOL - Candle + Volume Candles plus volume spli │
253
+ ›BTC vs ETH Side-by-side crypto leaders │ Price: $67,641
254
+ DOGE - Candle + Line Low-priced asset formatti │
255
+ │ Change: -0.2%
256
+
257
+
258
+ ↵ open detail ↑↓ navigate ^k actions :vi │
259
+
260
+
261
+
262
+
263
+
264
+ "
265
+ `)
266
+
267
+ expect(text).toContain('BTC vs ETH')
268
+ expect(text).toContain('Side-by-side')
269
+ }, 30000)
@@ -34,21 +34,20 @@ test('detail renders markdown with headings, lists, links, tables, code and diag
34
34
 
35
35
 
36
36
 
37
- Architecture Overview
37
+ Architecture Overview
38
38
 
39
- This document describes the system architecture.
40
39
 
40
+ This document describes the system architecture.
41
41
  Components
42
42
 
43
- The system has three main components:
44
43
 
44
+ The system has three main components:
45
45
  - Client - handles user interaction
46
46
  - Server - processes requests
47
47
  - Database - stores data
48
-
49
-
50
48
  Links
51
49
 
50
+
52
51
  Check out the GitHub repository for the source code.
53
52
 
54
53
  See the API documentation for more details.
@@ -59,6 +58,7 @@ test('detail renders markdown with headings, lists, links, tables, code and diag
59
58
 
60
59
  Configuration Table
61
60
 
61
+
62
62
  Setting Default Description
63
63
  Host localhost Database host address
64
64
  Port 5432 Database port number
@@ -67,12 +67,14 @@ test('detail renders markdown with headings, lists, links, tables, code and diag
67
67
 
68
68
  Flow Diagram
69
69
 
70
+
70
71
  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
71
72
  │ Client │────▶│ Server │────▶│ Database │
72
73
  └─────────────┘ └─────────────┘ └─────────────┘
73
74
 
74
75
  Vertical Flow
75
76
 
77
+
76
78
  ┌─────────┐
77
79
  │ Start │
78
80
  └────┬────┘
@@ -89,12 +91,10 @@ test('detail renders markdown with headings, lists, links, tables, code and diag
89
91
 
90
92
  Code Example
91
93
 
94
+
92
95
  interface Config {
93
96
  host: string
94
97
  port: number
95
- ssl: boolean
96
- }
97
-
98
98
 
99
99
 
100
100
  esc go back ^k actions powered by termcast.app