termcast 1.4.1 → 1.6.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 (180) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +30 -12
  3. package/dist/build.js.map +1 -1
  4. package/dist/cli.js +0 -40
  5. package/dist/cli.js.map +1 -1
  6. package/dist/compile.d.ts.map +1 -1
  7. package/dist/compile.js +7 -1
  8. package/dist/compile.js.map +1 -1
  9. package/dist/components/bar-graph.d.ts +23 -8
  10. package/dist/components/bar-graph.d.ts.map +1 -1
  11. package/dist/components/bar-graph.js +84 -40
  12. package/dist/components/bar-graph.js.map +1 -1
  13. package/dist/components/dotted-line-graph.d.ts +86 -0
  14. package/dist/components/dotted-line-graph.d.ts.map +1 -0
  15. package/dist/components/dotted-line-graph.js +260 -0
  16. package/dist/components/dotted-line-graph.js.map +1 -0
  17. package/dist/components/extension-preferences.d.ts.map +1 -1
  18. package/dist/components/extension-preferences.js +1 -10
  19. package/dist/components/extension-preferences.js.map +1 -1
  20. package/dist/components/graph.d.ts.map +1 -1
  21. package/dist/components/graph.js +7 -1
  22. package/dist/components/graph.js.map +1 -1
  23. package/dist/components/histogram.d.ts +42 -0
  24. package/dist/components/histogram.d.ts.map +1 -0
  25. package/dist/components/histogram.js +115 -0
  26. package/dist/components/histogram.js.map +1 -0
  27. package/dist/components/horizontal-bar-graph.d.ts +47 -0
  28. package/dist/components/horizontal-bar-graph.d.ts.map +1 -0
  29. package/dist/components/horizontal-bar-graph.js +137 -0
  30. package/dist/components/horizontal-bar-graph.js.map +1 -0
  31. package/dist/components/list.d.ts +9 -0
  32. package/dist/components/list.d.ts.map +1 -1
  33. package/dist/components/list.js +84 -21
  34. package/dist/components/list.js.map +1 -1
  35. package/dist/examples/bar-graph-weekly.js +2 -2
  36. package/dist/examples/bar-graph-weekly.js.map +1 -1
  37. package/dist/examples/charts-showcase-barchart.d.ts +2 -0
  38. package/dist/examples/charts-showcase-barchart.d.ts.map +1 -0
  39. package/dist/examples/charts-showcase-barchart.js +10 -0
  40. package/dist/examples/charts-showcase-barchart.js.map +1 -0
  41. package/dist/examples/charts-showcase-bargraph.d.ts +2 -0
  42. package/dist/examples/charts-showcase-bargraph.d.ts.map +1 -0
  43. package/dist/examples/charts-showcase-bargraph.js +60 -0
  44. package/dist/examples/charts-showcase-bargraph.js.map +1 -0
  45. package/dist/examples/charts-showcase-candle.d.ts +2 -0
  46. package/dist/examples/charts-showcase-candle.d.ts.map +1 -0
  47. package/dist/examples/charts-showcase-candle.js +30 -0
  48. package/dist/examples/charts-showcase-candle.js.map +1 -0
  49. package/dist/examples/charts-showcase-graph.d.ts +2 -0
  50. package/dist/examples/charts-showcase-graph.d.ts.map +1 -0
  51. package/dist/examples/charts-showcase-graph.js +33 -0
  52. package/dist/examples/charts-showcase-graph.js.map +1 -0
  53. package/dist/examples/charts-showcase-heatmap.d.ts +2 -0
  54. package/dist/examples/charts-showcase-heatmap.d.ts.map +1 -0
  55. package/dist/examples/charts-showcase-heatmap.js +36 -0
  56. package/dist/examples/charts-showcase-heatmap.js.map +1 -0
  57. package/dist/examples/charts-showcase-mixed.d.ts +2 -0
  58. package/dist/examples/charts-showcase-mixed.d.ts.map +1 -0
  59. package/dist/examples/charts-showcase-mixed.js +30 -0
  60. package/dist/examples/charts-showcase-mixed.js.map +1 -0
  61. package/dist/examples/charts-showcase-progress.d.ts +2 -0
  62. package/dist/examples/charts-showcase-progress.d.ts.map +1 -0
  63. package/dist/examples/charts-showcase-progress.js +10 -0
  64. package/dist/examples/charts-showcase-progress.js.map +1 -0
  65. package/dist/examples/graph-multi-series.js +1 -1
  66. package/dist/examples/graph-multi-series.js.map +1 -1
  67. package/dist/examples/horizontal-bar-graph-weekly.d.ts +2 -0
  68. package/dist/examples/horizontal-bar-graph-weekly.d.ts.map +1 -0
  69. package/dist/examples/horizontal-bar-graph-weekly.js +67 -0
  70. package/dist/examples/horizontal-bar-graph-weekly.js.map +1 -0
  71. package/dist/examples/list-detail-height-ratchet.d.ts +2 -0
  72. package/dist/examples/list-detail-height-ratchet.d.ts.map +1 -0
  73. package/dist/examples/list-detail-height-ratchet.js +26 -0
  74. package/dist/examples/list-detail-height-ratchet.js.map +1 -0
  75. package/dist/examples/simple-dotted-line-graph.d.ts +2 -0
  76. package/dist/examples/simple-dotted-line-graph.d.ts.map +1 -0
  77. package/dist/examples/simple-dotted-line-graph.js +39 -0
  78. package/dist/examples/simple-dotted-line-graph.js.map +1 -0
  79. package/dist/examples/simple-histogram.d.ts +2 -0
  80. package/dist/examples/simple-histogram.d.ts.map +1 -0
  81. package/dist/examples/simple-histogram.js +47 -0
  82. package/dist/examples/simple-histogram.js.map +1 -0
  83. package/dist/extensions/dev.d.ts.map +1 -1
  84. package/dist/extensions/dev.js +1 -0
  85. package/dist/extensions/dev.js.map +1 -1
  86. package/dist/globals.js +8 -0
  87. package/dist/globals.js.map +1 -1
  88. package/dist/index.d.ts +6 -0
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +6 -0
  91. package/dist/index.js.map +1 -1
  92. package/dist/package-json.d.ts +2 -0
  93. package/dist/package-json.d.ts.map +1 -1
  94. package/dist/package-json.js +20 -17
  95. package/dist/package-json.js.map +1 -1
  96. package/dist/platform/node/sqlite.d.ts +6 -5
  97. package/dist/platform/node/sqlite.d.ts.map +1 -1
  98. package/dist/platform/node/sqlite.js +30 -14
  99. package/dist/platform/node/sqlite.js.map +1 -1
  100. package/dist/profiler.d.ts +2 -0
  101. package/dist/profiler.d.ts.map +1 -0
  102. package/dist/profiler.js +390 -0
  103. package/dist/profiler.js.map +1 -0
  104. package/dist/theme.d.ts.map +1 -1
  105. package/dist/theme.js +11 -9
  106. package/dist/theme.js.map +1 -1
  107. package/dist/utils/run-command.d.ts.map +1 -1
  108. package/dist/utils/run-command.js +8 -19
  109. package/dist/utils/run-command.js.map +1 -1
  110. package/dist/utils.d.ts +1 -19
  111. package/dist/utils.d.ts.map +1 -1
  112. package/dist/utils.js +1 -100
  113. package/dist/utils.js.map +1 -1
  114. package/package.json +18 -21
  115. package/src/build.tsx +38 -15
  116. package/src/cli.tsx +3 -40
  117. package/src/compile.tsx +9 -1
  118. package/src/compile.vitest.tsx +8 -8
  119. package/src/components/bar-graph.tsx +217 -111
  120. package/src/components/dotted-line-graph.tsx +407 -0
  121. package/src/components/extension-preferences.tsx +2 -12
  122. package/src/components/graph.tsx +5 -1
  123. package/src/components/histogram.tsx +228 -0
  124. package/src/components/horizontal-bar-graph.tsx +279 -0
  125. package/src/components/list.tsx +112 -26
  126. package/src/examples/action-shortcut.vitest.tsx +20 -20
  127. package/src/examples/actions-context.vitest.tsx +2 -2
  128. package/src/examples/bar-graph-weekly.tsx +2 -2
  129. package/src/examples/bar-graph-weekly.vitest.tsx +103 -102
  130. package/src/examples/charts-showcase-bargraph.tsx +103 -0
  131. package/src/examples/detail-metadata-showcase.vitest.tsx +12 -12
  132. package/src/examples/form-basic.vitest.tsx +11 -11
  133. package/src/examples/form-dropdown.vitest.tsx +11 -11
  134. package/src/examples/form-scroll.vitest.tsx +1 -1
  135. package/src/examples/form-tagpicker.vitest.tsx +11 -11
  136. package/src/examples/github.vitest.tsx +22 -31
  137. package/src/examples/graph-bar-chart.vitest.tsx +36 -36
  138. package/src/examples/graph-multi-series.tsx +1 -1
  139. package/src/examples/graph-polymarket.vitest.tsx +24 -24
  140. package/src/examples/graph-row.vitest.tsx +14 -14
  141. package/src/examples/graph-styles.vitest.tsx +77 -77
  142. package/src/examples/horizontal-bar-graph-weekly.tsx +138 -0
  143. package/src/examples/horizontal-bar-graph-weekly.vitest.tsx +164 -0
  144. package/src/examples/list-detail-height-ratchet.tsx +48 -0
  145. package/src/examples/list-detail-height-ratchet.vitest.tsx +161 -0
  146. package/src/examples/list-detail-metadata.vitest.tsx +51 -51
  147. package/src/examples/list-dropdown-default.vitest.tsx +27 -27
  148. package/src/examples/list-fetch-data.vitest.tsx +3 -3
  149. package/src/examples/list-loading-empty-view.vitest.tsx +1 -1
  150. package/src/examples/list-no-actions.vitest.tsx +3 -3
  151. package/src/examples/list-scrollbox.vitest.tsx +6 -6
  152. package/src/examples/list-spacing-mode.vitest.tsx +1 -1
  153. package/src/examples/list-with-detail.vitest.tsx +55 -55
  154. package/src/examples/list-with-dropdown.vitest.tsx +6 -6
  155. package/src/examples/list-with-sections.vitest.tsx +20 -20
  156. package/src/examples/list-with-toast.vitest.tsx +4 -4
  157. package/src/examples/simple-candle-chart.vitest.tsx +61 -59
  158. package/src/examples/simple-dotted-line-graph.tsx +53 -0
  159. package/src/examples/simple-dotted-line-graph.vitest.tsx +62 -0
  160. package/src/examples/simple-grid.vitest.tsx +4 -4
  161. package/src/examples/simple-heatmap.vitest.tsx +9 -9
  162. package/src/examples/simple-histogram.tsx +90 -0
  163. package/src/examples/simple-navigation.vitest.tsx +25 -25
  164. package/src/examples/simple-progress-bar.vitest.tsx +7 -7
  165. package/src/examples/swift-extension.vitest.tsx +5 -5
  166. package/src/examples/toast-action.vitest.tsx +4 -4
  167. package/src/extensions/dev.tsx +2 -1
  168. package/src/extensions/dev.vitest.tsx +17 -17
  169. package/src/globals.ts +9 -0
  170. package/src/index.tsx +21 -0
  171. package/src/package-json.tsx +24 -23
  172. package/src/platform/node/sqlite.ts +29 -13
  173. package/src/profiler.tsx +487 -0
  174. package/src/theme.tsx +11 -10
  175. package/src/utils/run-command.tsx +10 -19
  176. package/src/utils.tsx +0 -163
  177. package/src/examples/store.tsx +0 -4
  178. package/src/examples/store.vitest.tsx +0 -78
  179. package/src/extensions/home.tsx +0 -227
  180. package/src/extensions/store.tsx +0 -375
@@ -27,18 +27,18 @@ afterEach(() => {
27
27
  test('ctrl+r shortcut should trigger Refresh action directly', async () => {
28
28
  // Wait for list to render
29
29
  await session.text({
30
- waitFor: (text) => /Refresh count: 0/.test(text),
30
+ waitFor: (text) => /nt: 0/.test(text),
31
31
  })
32
32
 
33
33
  const initial = await session.text()
34
- expect(initial).toContain('Refresh count: 0')
34
+ expect(initial).toContain('nt: 0')
35
35
 
36
36
  // Press ctrl+r directly to trigger Refresh action
37
37
  await session.press(['ctrl', 'r'])
38
38
 
39
39
  // Wait for the refresh to take effect
40
40
  const afterCtrlR = await session.text({
41
- waitFor: (text) => /Refresh count: 1/.test(text),
41
+ waitFor: (text) => /nt: 1/.test(text),
42
42
  timeout: 5000,
43
43
  })
44
44
  expect(afterCtrlR).toMatchInlineSnapshot(`
@@ -49,7 +49,7 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
49
49
 
50
50
  > Search...
51
51
 
52
- Refresh count: 1 Press ctrl+r to refresh (shortcut) or Enter the
52
+ Refres...unt: 1Press ctrl+r to refresh...ter then select Refresh
53
53
 
54
54
 
55
55
 
@@ -57,11 +57,11 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
57
57
 
58
58
 
59
59
 
60
- ↵ refresh ↑↓ navigate ^k actions :vim
61
60
 
62
61
 
63
62
 
64
63
 
64
+ ↵ refresh ↑↓ navigate ^k actions :vim
65
65
  "
66
66
  `)
67
67
  }, 30000)
@@ -69,7 +69,7 @@ test('ctrl+r shortcut should trigger Refresh action directly', async () => {
69
69
  test('action shortcut is displayed in action panel', async () => {
70
70
  // Wait for list to render
71
71
  await session.text({
72
- waitFor: (text) => /Refresh count: 0/.test(text),
72
+ waitFor: (text) => /nt: 0/.test(text),
73
73
  })
74
74
 
75
75
  // Open action panel with ctrl+k
@@ -85,6 +85,7 @@ test('action shortcut is displayed in action panel', async () => {
85
85
  expect(actionsPanel).toMatchInlineSnapshot(`
86
86
  "
87
87
 
88
+
88
89
  ╭────────────────────────────────────────────────────────────────╮
89
90
  │ │
90
91
  │ Actions esc │
@@ -102,38 +103,37 @@ test('action shortcut is displayed in action panel', async () => {
102
103
  │ │
103
104
  │ │
104
105
  │ │
105
- │ ↵ select ↑↓ navigate │
106
- │ │"
106
+ │ ↵ select ↑↓ navigate │"
107
107
  `)
108
108
  }, 30000)
109
109
 
110
110
  test('action works via Enter (auto-execute first action)', async () => {
111
111
  // Wait for list to render
112
112
  await session.text({
113
- waitFor: (text) => /Refresh count: 0/.test(text),
113
+ waitFor: (text) => /nt: 0/.test(text),
114
114
  })
115
115
 
116
116
  // Press Enter to auto-execute first action (Refresh)
117
117
  await session.press('return')
118
118
 
119
119
  const afterEnter = await session.text({
120
- waitFor: (text) => /Refresh count: 1/.test(text),
120
+ waitFor: (text) => /nt: 1/.test(text),
121
121
  timeout: 5000,
122
122
  })
123
123
 
124
- expect(afterEnter).toContain('Refresh count: 1')
124
+ expect(afterEnter).toContain('nt: 1')
125
125
  }, 30000)
126
126
 
127
127
  test('ctrl+x shortcut should trigger Reset action directly', async () => {
128
128
  // Wait for list to render and increment once via Enter
129
129
  await session.text({
130
- waitFor: (text) => /Refresh count: 0/.test(text),
130
+ waitFor: (text) => /nt: 0/.test(text),
131
131
  })
132
132
 
133
133
  // Increment via Enter first
134
134
  await session.press('return')
135
135
  await session.text({
136
- waitFor: (text) => /Refresh count: 1/.test(text),
136
+ waitFor: (text) => /nt: 1/.test(text),
137
137
  timeout: 5000,
138
138
  })
139
139
 
@@ -142,27 +142,27 @@ test('ctrl+x shortcut should trigger Reset action directly', async () => {
142
142
 
143
143
  // Wait for the reset to take effect
144
144
  const afterCtrlX = await session.text({
145
- waitFor: (text) => /Refresh count: 0/.test(text),
145
+ waitFor: (text) => /nt: 0/.test(text),
146
146
  timeout: 5000,
147
147
  })
148
- expect(afterCtrlX).toContain('Refresh count: 0')
148
+ expect(afterCtrlX).toContain('nt: 0')
149
149
  }, 30000)
150
150
 
151
151
  test('alt+d shortcut should trigger Double action directly', async () => {
152
152
  // Wait for list to render
153
153
  await session.text({
154
- waitFor: (text) => /Refresh count: 0/.test(text),
154
+ waitFor: (text) => /nt: 0/.test(text),
155
155
  })
156
156
 
157
157
  // Increment twice via Enter to get count=2
158
158
  await session.press('return')
159
159
  await session.text({
160
- waitFor: (text) => /Refresh count: 1/.test(text),
160
+ waitFor: (text) => /nt: 1/.test(text),
161
161
  timeout: 5000,
162
162
  })
163
163
  await session.press('return')
164
164
  await session.text({
165
- waitFor: (text) => /Refresh count: 2/.test(text),
165
+ waitFor: (text) => /nt: 2/.test(text),
166
166
  timeout: 5000,
167
167
  })
168
168
 
@@ -171,8 +171,8 @@ test('alt+d shortcut should trigger Double action directly', async () => {
171
171
 
172
172
  // Wait for the double to take effect
173
173
  const afterAltD = await session.text({
174
- waitFor: (text) => /Refresh count: 4/.test(text),
174
+ waitFor: (text) => /nt: 4/.test(text),
175
175
  timeout: 5000,
176
176
  })
177
- expect(afterAltD).toContain('Refresh count: 4')
177
+ expect(afterAltD).toContain('nt: 4')
178
178
  }, 30000)
@@ -38,6 +38,7 @@ test('actions preserve React context through portal', async () => {
38
38
  expect(actionsPanel).toMatchInlineSnapshot(`
39
39
  "
40
40
 
41
+
41
42
  ╭────────────────────────────────────────────────────────────────╮
42
43
  │ │
43
44
  │ Actions esc │
@@ -55,8 +56,7 @@ test('actions preserve React context through portal', async () => {
55
56
  │ │
56
57
  │ │
57
58
  │ │
58
- │ ↵ select ↑↓ navigate │
59
- │ │"
59
+ │ ↵ select ↑↓ navigate │"
60
60
  `)
61
61
 
62
62
  // Select "Show Counter" (first action, already selected)
@@ -151,7 +151,7 @@ function BarGraphWeeklyExample() {
151
151
  <List.Item.Detail
152
152
  metadata={
153
153
  <List.Item.Detail.Metadata>
154
- <BarGraph height={10} labels={manyColsLabels}>
154
+ <BarGraph height={10} labels={manyColsLabels} barWidth={1}>
155
155
  {manyColsSeries.map((s, i) => {
156
156
  return <BarGraph.Series key={i} data={s.data} title={s.title} />
157
157
  })}
@@ -261,4 +261,4 @@ function BarGraphWeeklyExample() {
261
261
  )
262
262
  }
263
263
 
264
- renderWithProviders(<BarGraphWeeklyExample />)
264
+ void renderWithProviders(<BarGraphWeeklyExample />)
@@ -1,5 +1,5 @@
1
1
  // E2E tests for BarGraph vertical stacked bar chart.
2
- // Bar segments are filled with chars so they show in text snapshots.
2
+ // Bar segments use (full block) chars for solid, gap-free columns.
3
3
 
4
4
  import { test, expect, afterEach, beforeEach } from 'vitest'
5
5
  import { launchTerminal, Session } from 'tuistory/src'
@@ -22,7 +22,7 @@ afterEach(() => {
22
22
  test('bar graph renders bars, labels, and legend', async () => {
23
23
  const text = await session.text({
24
24
  waitFor: (text) => {
25
- return text.includes('Mon') && text.includes('Sat') && text.includes('Direct')
25
+ return text.includes('Mon') && text.includes('Direct') && text.includes('0.0│')
26
26
  },
27
27
  timeout: 10000,
28
28
  })
@@ -35,47 +35,47 @@ test('bar graph renders bars, labels, and legend', async () => {
35
35
 
36
36
  > Search...
37
37
 
38
- ›Weekly Traffic 3 channels across 6 d ███
39
- Revenue by Region EMEA / APAC / Amer ███ ███ ███ ███
40
- Server Load CPU / Memory / IO │ ███ ███ ███ ███
41
- Many Columns (20) Overflow test with ███ ███ ███ ███ ███
42
- Many Series (8) Legend overflow test │ ███ ███ ███ ███ ███ ███
43
- Long Labels Labels wider than bar co ███ ███ ███ ███ ███ ███
44
- Week 1 vs Week 2 Two graphs in a Row │ ███ ███ ███ ███ ███ ███
45
- ███ ███ ███ ███ ███ ███
46
- ███ ███ ███ ███ ███ ███
47
- Mon Tue Wed Thu Fri Sat
48
- ↵ open detail ↑↓ navigate ^k act │ ■ Direct ■ Organic ■ Referral
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
38
+ ›Weekly Traffi 3 channel...oss 6 days 110.0│ ██
39
+ Revenue by Regio EMEA / A...Americas │██ ██ ██ ██
40
+ Server Load CPU / Memory / IO │ 82.5│██ ██ ██ ██
41
+ Many C...ns (20)Overflow...h 20 bars │██ ██ ██ ██ ██
42
+ Many Series (8) Legend overflow test │ 55.0│██ ██ ██ ██ ██ ██
43
+ Long Label Labels wide...bar columns │██ ██ ██ ██ ██ ██
44
+ Week 1 vs Week 2 Two graphs in a Row │ 27.5│██ ██ ██ ██ ██ ██
45
+ │██ ██ ██ ██ ██ ██
46
+ 0.0│██ ██ ██ ██ ██ ██
47
+ Mon Tue Wed Thu FriSat
48
+ │ ■ Direct ■ Organic ■ Referral
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+ ↵ open detail ↑↓ navigate ^k act │
60
60
 
61
61
  "
62
62
  `)
63
63
 
64
64
  expect(text).toContain('Mon')
65
- expect(text).toContain('Sat')
66
65
  expect(text).toContain('Direct')
66
+ expect(text).toContain('0.0│')
67
67
  expect(text).toContain('█')
68
68
  }, 30000)
69
69
 
70
70
  test('many columns (20) clips with overflow hidden', async () => {
71
- await session.text({ waitFor: (t) => t.includes('Many Columns'), timeout: 10000 })
71
+ await session.text({ waitFor: (t) => t.includes('(20)'), timeout: 10000 })
72
72
  // Navigate: Weekly Traffic, Revenue, Server Load, Many Columns = 3 downs
73
73
  session.sendKey('down')
74
74
  session.sendKey('down')
75
75
  session.sendKey('down')
76
76
 
77
77
  await session.text({
78
- waitFor: (t) => t.includes('›Many Columns'),
78
+ waitFor: (t) => t.includes('›Many') && t.includes('(20)'),
79
79
  timeout: 10000,
80
80
  })
81
81
  await session.waitIdle()
@@ -83,16 +83,16 @@ test('many columns (20) clips with overflow hidden', async () => {
83
83
 
84
84
  // Bar graph rendering has non-deterministic ANSI highlights, so use toContain checks
85
85
  // instead of inline snapshot for the bars area
86
- expect(text).toContain('›Many Columns')
86
+ expect(text).toContain('›Many')
87
87
  expect(text).toContain('BarGraph Showcase')
88
- expect(text).toContain('███')
88
+ expect(text).toContain('')
89
89
 
90
90
  // Some labels visible, overflow clips the rest
91
- expect(text).toContain('D1')
91
+ expect(text).toContain('D')
92
92
  expect(text).toContain('█')
93
93
  }, 30000)
94
94
 
95
- test('many series (8) legend clips on one line', async () => {
95
+ test('many series (8) bottom legend clips on one row', async () => {
96
96
  await session.text({ waitFor: (t) => t.includes('Many Series'), timeout: 10000 })
97
97
  // Navigate: Weekly, Revenue, Server, Many Columns, Many Series = 4 downs
98
98
  session.sendKey('down')
@@ -113,39 +113,40 @@ test('many series (8) legend clips on one line', async () => {
113
113
 
114
114
  > Search...
115
115
 
116
- Weekly Traffic 3 channels across 6 d███ ███ ███ ███ ███
117
- Revenue by Region EMEA / APAC / Amer ███ ███ ███ ███ ███ ███
118
- Server Load CPU / Memory / IO │ ███ ███ ███ ███ ███ ███
119
- Many Columns (20) Overflow test with ███ ███ ███ ███ ███ ███
120
- ›Many Series (8) Legend overflow test │ ███ ███ ███ ███ ███ ███
121
- Long Labels Labels wider than bar co ███ ███ ███ ███ ███ ███
122
- Week 1 vs Week 2 Two graphs in a Row │ ███ ███ ███ ███ ███ ███
123
- ███ ███ ███ ███ ███ ███
124
- ███ ███ ███ ███ ███ ███
125
- Mon Tue Wed Thu Fri Sat
126
- ↑↓ navigate ^k actions :vim │ ■ Series 1 ■ Series 2 ■ Series 3
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
116
+ Weekly Traffi 3 channel...oss 6 days328.0│██ ██ ██ ██ ██
117
+ Revenue by Regio EMEA / A...Americas │██ ██ ██ ██ ██ ██
118
+ Server Load CPU / Memory / IO │ 246.0│██ ██ ██ ██ ██ ██
119
+ Many C...ns (20)Overflow...h 20 bars │██ ██ ██ ██ ██ ██
120
+ ›Many Series (8) Legend overflow test │ 164.0│██ ██ ██ ██ ██ ██
121
+ Long Label Labels wide...bar columns │██ ██ ██ ██ ██ ██
122
+ Week 1 vs Week 2 Two graphs in a Row │ 82.0│██ ██ ██ ██ ██ ██
123
+ │██ ██ ██ ██ ██ ██
124
+ 0.0│██ ██ ██ ██ ██ ██
125
+ Mon Tue Wed Thu FriSat
126
+ │ ■ Series 1 ■ Series 2 ■ Series 3
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+
136
+
137
+ ↑↓ navigate ^k actions :vim │
138
138
 
139
139
  "
140
140
  `)
141
141
 
142
- // First series visible in legend
142
+ // Bottom legend is a single clipped row by default.
143
143
  expect(text).toContain('Series 1')
144
+ expect(text).toContain('Series 3')
144
145
  expect(text).toContain('█')
145
146
  }, 30000)
146
147
 
147
148
  test('long labels truncated by overflow hidden', async () => {
148
- await session.text({ waitFor: (t) => t.includes('Long Labels'), timeout: 10000 })
149
+ await session.text({ waitFor: (t) => t.includes('Labels wide'), timeout: 10000 })
149
150
  // Navigate: Weekly, Revenue, Server, Many Columns, Many Series, Long Labels = 5 downs
150
151
  session.sendKey('down')
151
152
  session.sendKey('down')
@@ -154,7 +155,7 @@ test('long labels truncated by overflow hidden', async () => {
154
155
  session.sendKey('down')
155
156
 
156
157
  const text = await session.text({
157
- waitFor: (t) => t.includes('›Long Labels'),
158
+ waitFor: (t) => t.includes('›Long Label') || t.includes('›Lon...bels'),
158
159
  timeout: 10000,
159
160
  })
160
161
 
@@ -166,33 +167,33 @@ test('long labels truncated by overflow hidden', async () => {
166
167
 
167
168
  > Search...
168
169
 
169
- Weekly Traffic 3 channels across 6 d ███
170
- Revenue by Region EMEA / APAC / Amer ███ ███ ███
171
- Server Load CPU / Memory / IO │ ███ ███ ███ ███
172
- Many Columns (20) Overflow test with ███ ███ ███ ███
173
- Many Series (8) Legend overflow test │ ███ ███ ███ ███ ███
174
- ›Long Labels Labels wider than bar co ███ ███ ███ ███ ███ ███
175
- Week 1 vs Week 2 Two graphs in a Row │ ███ ███ ███ ███ ███ ███
176
- ███ ███ ███ ███ ███ ███
177
- ███ ███ ███ ███ ███ ███
178
- Mon Tue Wed Thu Fri Sat
179
- ↑↓ navigate ^k actions :vim │ ■ Views ■ Clicks
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
170
+ Weekly Traffi 3 channel...oss 6 days 75.0│ ██
171
+ Revenue by Regio EMEA / A...Americas │██ ██ ██
172
+ Server Load CPU / Memory / IO │ 56.3│██ ██ ██ ██
173
+ Many C...ns (20)Overflow...h 20 bars │██ ██ ██ ██
174
+ Many Series (8) Legend overflow test │ 37.5│██ ██ ██ ██ ██
175
+ ›Long Label Labels wide...bar columns │██ ██ ██ ██ ██ ██
176
+ Week 1 vs Week 2 Two graphs in a Row │ 18.8│██ ██ ██ ██ ██ ██
177
+ │██ ██ ██ ██ ██ ██
178
+ 0.0│██ ██ ██ ██ ██ ██
179
+ Monday Thursday
180
+ │ ■ Views ■ Clicks
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+
191
+ ↑↓ navigate ^k actions :vim │
191
192
 
192
193
  "
193
194
  `)
194
195
 
195
- expect(text).toContain('Long Labels')
196
+ expect(text).toContain('Labels wide')
196
197
  expect(text).toContain('█')
197
198
  }, 30000)
198
199
 
@@ -219,31 +220,31 @@ test('side-by-side bar graphs in a Row', async () => {
219
220
 
220
221
  > Search...
221
222
 
222
- Weekly Traffic 3 channels across 6 d
223
- Revenue by Region EMEA / APAC / Amer ███ ███ █ ███
224
- Server Load CPU / Memory / IO │ ███ ███ █ ███ ███
225
- Many Columns (20) Overflow test with ███ ███ ███ █ ███ ███ ███ ███
226
- Many Series (8) Legend overflow test │ ███ ███ ███ ███ █ ███ ███ ███ ███
227
- Long Labels Labels wider than bar co ███ ███ ███ ███ █ ███ ███ ███ ███
228
- ›Week 1 vs Week 2 Two graphs in a Row │ ███ ███ ███ ███ █ ███ ███ ███ ███
229
- ███ ███ ███ ███ █ ███ ███ ███ ███
230
- ███ ███ ███ ███ █ ███ ███ ███ ███
231
- Mon Tue Wed Thu F Mon Tue Wed Thu
232
- ↵ open detail ↑↓ navigate ^k act │ ■ Direct Organi ■ Direct Organ
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
223
+ Weekly Traffi 3 channel...oss 6 days 110.0│ 130.0│
224
+ Revenue by Regio EMEA / A...Americas │██ ██ │██
225
+ Server Load CPU / Memory / IO │ 82.5│██ ██ 97.5│██ ██
226
+ Many C...ns (20)Overflow...h 20 bars │██ ██ ██ │██ ██ ██
227
+ Many Series (8) Legend overflow test │ 55.0│██ ██ ██ 65.0│██ ██ ██
228
+ Long Label Labels wide...bar columns │██ ██ ██ │██ ██ ██
229
+ ›Week 1 vs Week 2 Two graphs in a Row │ 27.5│██ ██ ██ 32.5│██ ██ ██
230
+ │██ ██ ██ │██ ██ ██
231
+ 0.0│██ ██ ██ 0.0│██ ██ ██
232
+ Mon Tue Wed Mon Tue We
233
+ │ ■ Direct Organ ■ Direct Orga
234
+
235
+
236
+
237
+
238
+
239
+
240
+
241
+
242
+
243
+
244
+ ↵ open detail ↑↓ navigate ^k act │
244
245
 
245
246
  "
246
247
  `)
247
248
 
248
- expect(text).toContain('Mon')
249
+ expect(text).toContain('Direct')
249
250
  }, 30000)
@@ -0,0 +1,103 @@
1
+ // Showcase: BarGraph vertical stacked bar chart inside a Detail view.
2
+ // Recreates a monthly model spend chart with stacked per-day usage.
3
+
4
+ import { BarGraph, Detail } from 'termcast'
5
+ import { renderWithProviders } from '../utils'
6
+
7
+ const labels = Array.from({ length: 30 }, (_, i) => {
8
+ const day = i + 1
9
+ if (day % 2 === 0) {
10
+ return ''
11
+ }
12
+ return String(day).padStart(2, '0')
13
+ })
14
+
15
+ const emptyMonth = Array.from({ length: 30 }, () => 0)
16
+
17
+ function withSpend(points: Record<number, number>): number[] {
18
+ return emptyMonth.map((_, i) => points[i + 1] || 0)
19
+ }
20
+
21
+ const modelSpend = [
22
+ {
23
+ title: 'deepseek-v4-flash (go)',
24
+ color: '#b5bb69',
25
+ data: withSpend({ 21: 0.6, 25: 1.05, 26: 1.3, 27: 1.9, 28: 0.55, 29: 0.8, 30: 1.1 }),
26
+ },
27
+ {
28
+ title: 'deepseek-v4-pro (go)',
29
+ color: '#8f59b5',
30
+ data: withSpend({ 25: 0.12 }),
31
+ },
32
+ {
33
+ title: 'kimi-k2.6 (go)',
34
+ color: '#64b86a',
35
+ data: withSpend({ 22: 3.25, 23: 1.65, 24: 2.2, 25: 2.05 }),
36
+ },
37
+ {
38
+ title: 'mimo-v2-omni (go)',
39
+ color: '#aaa25f',
40
+ data: withSpend({ 25: 0.12, 26: 0.15 }),
41
+ },
42
+ {
43
+ title: 'mimo-v2-pro (go)',
44
+ color: '#5ba895',
45
+ data: withSpend({ 16: 1.5, 17: 0.75, 21: 2.5 }),
46
+ },
47
+ {
48
+ title: 'minimax-m2.7 (go)',
49
+ color: '#6c6cb8',
50
+ data: withSpend({ 20: 5 }),
51
+ },
52
+ {
53
+ title: 'qwen3.6-plus (go)',
54
+ color: '#b07a5c',
55
+ data: withSpend({ 17: 2.65, 18: 6.55, 19: 3.15 }),
56
+ },
57
+ ]
58
+
59
+ function ChartsShowcaseBarGraph() {
60
+ return (
61
+ <Detail
62
+ navigationTitle="Model Spend"
63
+ markdown="# Model Spend\n\nApril 2026 usage by model and API key."
64
+ metadata={
65
+ <Detail.Metadata>
66
+ <box flexDirection="row" gap={2} paddingBottom={1}>
67
+ <box border borderColor="#2a2a2a" paddingLeft={2} paddingRight={2} height={3} justifyContent="center">
68
+ <text>‹ April 2026 ›</text>
69
+ </box>
70
+ <box border borderColor="#2d5fa8" paddingLeft={2} paddingRight={2} height={3} justifyContent="center">
71
+ <text>All Models⌄</text>
72
+ </box>
73
+ <box border borderColor="#2a2a2a" paddingLeft={2} paddingRight={2} height={3} justifyContent="center">
74
+ <text>All Keys⌄</text>
75
+ </box>
76
+ </box>
77
+ <box border borderColor="#2a2a2a" paddingTop={2} paddingLeft={2} paddingRight={2} paddingBottom={1}>
78
+ <BarGraph
79
+ height={24}
80
+ labels={labels}
81
+ barGap={1}
82
+ yFormat={(value) => `$${value.toFixed(0)}`}
83
+ showLegend
84
+ >
85
+ {modelSpend.map((series) => {
86
+ return (
87
+ <BarGraph.Series
88
+ key={series.title}
89
+ color={series.color}
90
+ data={series.data}
91
+ title={series.title}
92
+ />
93
+ )
94
+ })}
95
+ </BarGraph>
96
+ </box>
97
+ </Detail.Metadata>
98
+ }
99
+ />
100
+ )
101
+ }
102
+
103
+ void renderWithProviders(<ChartsShowcaseBarGraph />)