termcast 1.3.47 → 1.3.49

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 (278) hide show
  1. package/dist/apis/cache.d.ts.map +1 -1
  2. package/dist/apis/cache.js +1 -2
  3. package/dist/apis/cache.js.map +1 -1
  4. package/dist/apis/localstorage.d.ts.map +1 -1
  5. package/dist/apis/localstorage.js +1 -2
  6. package/dist/apis/localstorage.js.map +1 -1
  7. package/dist/apis/sqlite.d.ts +7 -0
  8. package/dist/apis/sqlite.d.ts.map +1 -0
  9. package/dist/apis/sqlite.js +13 -0
  10. package/dist/apis/sqlite.js.map +1 -0
  11. package/dist/build.d.ts.map +1 -1
  12. package/dist/build.js +14 -5
  13. package/dist/build.js.map +1 -1
  14. package/dist/cli.js +5 -40
  15. package/dist/cli.js.map +1 -1
  16. package/dist/colors.d.ts +7 -7
  17. package/dist/colors.js +7 -7
  18. package/dist/compile.d.ts +6 -1
  19. package/dist/compile.d.ts.map +1 -1
  20. package/dist/compile.js +46 -27
  21. package/dist/compile.js.map +1 -1
  22. package/dist/components/actions.js +1 -1
  23. package/dist/components/actions.js.map +1 -1
  24. package/dist/components/bar-chart.d.ts +38 -0
  25. package/dist/components/bar-chart.d.ts.map +1 -0
  26. package/dist/components/bar-chart.js +158 -0
  27. package/dist/components/bar-chart.js.map +1 -0
  28. package/dist/components/bar-graph.d.ts +41 -0
  29. package/dist/components/bar-graph.d.ts.map +1 -0
  30. package/dist/components/bar-graph.js +95 -0
  31. package/dist/components/bar-graph.js.map +1 -0
  32. package/dist/components/detail.d.ts.map +1 -1
  33. package/dist/components/detail.js +5 -7
  34. package/dist/components/detail.js.map +1 -1
  35. package/dist/components/footer.d.ts.map +1 -1
  36. package/dist/components/footer.js +8 -9
  37. package/dist/components/footer.js.map +1 -1
  38. package/dist/components/form/date-picker.d.ts.map +1 -1
  39. package/dist/components/form/date-picker.js +7 -1
  40. package/dist/components/form/date-picker.js.map +1 -1
  41. package/dist/components/form/dropdown.d.ts.map +1 -1
  42. package/dist/components/form/dropdown.js +10 -2
  43. package/dist/components/form/dropdown.js.map +1 -1
  44. package/dist/components/form/index.d.ts.map +1 -1
  45. package/dist/components/form/index.js +4 -5
  46. package/dist/components/form/index.js.map +1 -1
  47. package/dist/components/form/use-form-navigation.d.ts.map +1 -1
  48. package/dist/components/form/use-form-navigation.js +6 -0
  49. package/dist/components/form/use-form-navigation.js.map +1 -1
  50. package/dist/components/graph.d.ts +111 -0
  51. package/dist/components/graph.d.ts.map +1 -0
  52. package/dist/components/graph.js +392 -0
  53. package/dist/components/graph.js.map +1 -0
  54. package/dist/components/icon.js +5 -5
  55. package/dist/components/icon.js.map +1 -1
  56. package/dist/components/list.d.ts +53 -5
  57. package/dist/components/list.d.ts.map +1 -1
  58. package/dist/components/list.js +125 -71
  59. package/dist/components/list.js.map +1 -1
  60. package/dist/components/loading-bar.js +3 -3
  61. package/dist/components/loading-bar.js.map +1 -1
  62. package/dist/components/loading-text.d.ts +1 -1
  63. package/dist/components/loading-text.d.ts.map +1 -1
  64. package/dist/components/loading-text.js +3 -1
  65. package/dist/components/loading-text.js.map +1 -1
  66. package/dist/components/metadata.js +2 -2
  67. package/dist/components/metadata.js.map +1 -1
  68. package/dist/components/row.d.ts +10 -0
  69. package/dist/components/row.d.ts.map +1 -0
  70. package/dist/components/row.js +12 -0
  71. package/dist/components/row.js.map +1 -0
  72. package/dist/components/table.d.ts +57 -0
  73. package/dist/components/table.d.ts.map +1 -0
  74. package/dist/components/table.js +365 -0
  75. package/dist/components/table.js.map +1 -0
  76. package/dist/descendants.js +13 -13
  77. package/dist/descendants.js.map +1 -1
  78. package/dist/examples/bar-graph-weekly.d.ts +2 -0
  79. package/dist/examples/bar-graph-weekly.d.ts.map +1 -0
  80. package/dist/examples/bar-graph-weekly.js +95 -0
  81. package/dist/examples/bar-graph-weekly.js.map +1 -0
  82. package/dist/examples/components-weird-places.d.ts +2 -0
  83. package/dist/examples/components-weird-places.d.ts.map +1 -0
  84. package/dist/examples/components-weird-places.js +46 -0
  85. package/dist/examples/components-weird-places.js.map +1 -0
  86. package/dist/examples/graph-bar-chart.d.ts +2 -0
  87. package/dist/examples/graph-bar-chart.d.ts.map +1 -0
  88. package/dist/examples/graph-bar-chart.js +270 -0
  89. package/dist/examples/graph-bar-chart.js.map +1 -0
  90. package/dist/examples/graph-multi-series.d.ts +2 -0
  91. package/dist/examples/graph-multi-series.d.ts.map +1 -0
  92. package/dist/examples/graph-multi-series.js +23 -0
  93. package/dist/examples/graph-multi-series.js.map +1 -0
  94. package/dist/examples/graph-polymarket.d.ts +2 -0
  95. package/dist/examples/graph-polymarket.d.ts.map +1 -0
  96. package/dist/examples/graph-polymarket.js +109 -0
  97. package/dist/examples/graph-polymarket.js.map +1 -0
  98. package/dist/examples/graph-row.d.ts +2 -0
  99. package/dist/examples/graph-row.d.ts.map +1 -0
  100. package/dist/examples/graph-row.js +226 -0
  101. package/dist/examples/graph-row.js.map +1 -0
  102. package/dist/examples/graph-styles.d.ts +2 -0
  103. package/dist/examples/graph-styles.d.ts.map +1 -0
  104. package/dist/examples/graph-styles.js +316 -0
  105. package/dist/examples/graph-styles.js.map +1 -0
  106. package/dist/examples/list-accessory-table.d.ts +2 -0
  107. package/dist/examples/list-accessory-table.d.ts.map +1 -0
  108. package/dist/examples/list-accessory-table.js +46 -0
  109. package/dist/examples/list-accessory-table.js.map +1 -0
  110. package/dist/examples/list-item-accessories.d.ts +2 -0
  111. package/dist/examples/list-item-accessories.d.ts.map +1 -0
  112. package/dist/examples/list-item-accessories.js +27 -0
  113. package/dist/examples/list-item-accessories.js.map +1 -0
  114. package/dist/examples/list-no-actions.d.ts +2 -0
  115. package/dist/examples/list-no-actions.d.ts.map +1 -0
  116. package/dist/examples/list-no-actions.js +7 -0
  117. package/dist/examples/list-no-actions.js.map +1 -0
  118. package/dist/examples/simple-detail-table.d.ts +2 -0
  119. package/dist/examples/simple-detail-table.d.ts.map +1 -0
  120. package/dist/examples/simple-detail-table.js +45 -0
  121. package/dist/examples/simple-detail-table.js.map +1 -0
  122. package/dist/examples/simple-graph.d.ts +2 -0
  123. package/dist/examples/simple-graph.d.ts.map +1 -0
  124. package/dist/examples/simple-graph.js +32 -0
  125. package/dist/examples/simple-graph.js.map +1 -0
  126. package/dist/examples/simple-table-wrap.d.ts +2 -0
  127. package/dist/examples/simple-table-wrap.d.ts.map +1 -0
  128. package/dist/examples/simple-table-wrap.js +37 -0
  129. package/dist/examples/simple-table-wrap.js.map +1 -0
  130. package/dist/examples/table-edge-cases.d.ts +2 -0
  131. package/dist/examples/table-edge-cases.d.ts.map +1 -0
  132. package/dist/examples/table-edge-cases.js +70 -0
  133. package/dist/examples/table-edge-cases.js.map +1 -0
  134. package/dist/examples/table-flex-grow.d.ts +2 -0
  135. package/dist/examples/table-flex-grow.d.ts.map +1 -0
  136. package/dist/examples/table-flex-grow.js +18 -0
  137. package/dist/examples/table-flex-grow.js.map +1 -0
  138. package/dist/extensions/dev.d.ts.map +1 -1
  139. package/dist/extensions/dev.js +5 -1
  140. package/dist/extensions/dev.js.map +1 -1
  141. package/dist/globals.d.ts +1 -0
  142. package/dist/globals.d.ts.map +1 -1
  143. package/dist/globals.js +2 -0
  144. package/dist/globals.js.map +1 -1
  145. package/dist/index.d.ts +10 -0
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +10 -0
  148. package/dist/index.js.map +1 -1
  149. package/dist/internal/date-picker-widget.d.ts.map +1 -1
  150. package/dist/internal/date-picker-widget.js +4 -0
  151. package/dist/internal/date-picker-widget.js.map +1 -1
  152. package/dist/internal/providers.d.ts.map +1 -1
  153. package/dist/internal/providers.js +1 -3
  154. package/dist/internal/providers.js.map +1 -1
  155. package/dist/logger.d.ts.map +1 -1
  156. package/dist/logger.js +2 -1
  157. package/dist/logger.js.map +1 -1
  158. package/dist/markdown-utils.d.ts +22 -1
  159. package/dist/markdown-utils.d.ts.map +1 -1
  160. package/dist/markdown-utils.js +66 -1
  161. package/dist/markdown-utils.js.map +1 -1
  162. package/dist/opentui.d.ts +4 -0
  163. package/dist/opentui.d.ts.map +1 -0
  164. package/dist/opentui.js +3 -0
  165. package/dist/opentui.js.map +1 -0
  166. package/dist/release.d.ts +2 -1
  167. package/dist/release.d.ts.map +1 -1
  168. package/dist/release.js +2 -1
  169. package/dist/release.js.map +1 -1
  170. package/dist/state.d.ts +1 -0
  171. package/dist/state.d.ts.map +1 -1
  172. package/dist/state.js +1 -1
  173. package/dist/state.js.map +1 -1
  174. package/dist/swift-runtime.d.ts.map +1 -1
  175. package/dist/swift-runtime.js +20 -5
  176. package/dist/swift-runtime.js.map +1 -1
  177. package/dist/theme.d.ts +1 -0
  178. package/dist/theme.d.ts.map +1 -1
  179. package/dist/theme.js +13 -0
  180. package/dist/theme.js.map +1 -1
  181. package/dist/themes/nerv.json +227 -0
  182. package/dist/themes/termcast.json +72 -71
  183. package/dist/themes.d.ts +2 -1
  184. package/dist/themes.d.ts.map +1 -1
  185. package/dist/themes.js +7 -5
  186. package/dist/themes.js.map +1 -1
  187. package/dist/utils.d.ts.map +1 -1
  188. package/dist/utils.js +4 -1
  189. package/dist/utils.js.map +1 -1
  190. package/package.json +12 -4
  191. package/src/apis/cache.test.ts +1 -1
  192. package/src/apis/cache.tsx +1 -2
  193. package/src/apis/localstorage.tsx +1 -2
  194. package/src/apis/sqlite.ts +14 -0
  195. package/src/build.tsx +15 -5
  196. package/src/cli.tsx +5 -49
  197. package/src/colors.tsx +7 -7
  198. package/src/compile.tsx +53 -30
  199. package/src/components/actions.tsx +1 -1
  200. package/src/components/bar-chart.tsx +271 -0
  201. package/src/components/bar-graph.tsx +214 -0
  202. package/src/components/detail.tsx +7 -8
  203. package/src/components/footer.tsx +14 -15
  204. package/src/components/form/date-picker.tsx +9 -0
  205. package/src/components/form/dropdown.tsx +13 -3
  206. package/src/components/form/index.tsx +4 -6
  207. package/src/components/form/use-form-navigation.tsx +6 -0
  208. package/src/components/graph.tsx +506 -0
  209. package/src/components/icon.tsx +5 -5
  210. package/src/components/list.tsx +210 -102
  211. package/src/components/loading-bar.tsx +3 -3
  212. package/src/components/loading-text.tsx +4 -2
  213. package/src/components/metadata.tsx +2 -2
  214. package/src/components/row.tsx +31 -0
  215. package/src/components/table.tsx +511 -0
  216. package/src/descendants.tsx +13 -13
  217. package/src/examples/action-shortcut.vitest.tsx +1 -1
  218. package/src/examples/actions-context.vitest.tsx +1 -1
  219. package/src/examples/bar-graph-weekly.tsx +264 -0
  220. package/src/examples/bar-graph-weekly.vitest.tsx +275 -0
  221. package/src/examples/detail-metadata-showcase.vitest.tsx +22 -22
  222. package/src/examples/form-basic.vitest.tsx +239 -0
  223. package/src/examples/form-dropdown.vitest.tsx +29 -29
  224. package/src/examples/form-tagpicker.vitest.tsx +27 -27
  225. package/src/examples/github.vitest.tsx +4 -4
  226. package/src/examples/graph-bar-chart.tsx +408 -0
  227. package/src/examples/graph-bar-chart.vitest.tsx +283 -0
  228. package/src/examples/graph-multi-series.tsx +36 -0
  229. package/src/examples/graph-multi-series.vitest.tsx +89 -0
  230. package/src/examples/graph-polymarket.tsx +182 -0
  231. package/src/examples/graph-polymarket.vitest.tsx +130 -0
  232. package/src/examples/graph-row.tsx +347 -0
  233. package/src/examples/graph-row.vitest.tsx +295 -0
  234. package/src/examples/graph-styles.tsx +457 -0
  235. package/src/examples/graph-styles.vitest.tsx +322 -0
  236. package/src/examples/list-accessory-table.tsx +77 -0
  237. package/src/examples/list-detail-metadata.vitest.tsx +21 -21
  238. package/src/examples/list-dropdown-default.vitest.tsx +12 -12
  239. package/src/examples/list-item-accessories.tsx +106 -0
  240. package/src/examples/list-item-accessories.vitest.tsx +115 -0
  241. package/src/examples/list-no-actions.tsx +18 -0
  242. package/src/examples/list-no-actions.vitest.tsx +97 -0
  243. package/src/examples/list-spacing-mode.vitest.tsx +6 -6
  244. package/src/examples/list-with-detail.vitest.tsx +73 -73
  245. package/src/examples/list-with-dropdown.vitest.tsx +49 -6
  246. package/src/examples/list-with-sections.vitest.tsx +61 -56
  247. package/src/examples/simple-detail-markdown.vitest.tsx +21 -18
  248. package/src/examples/simple-detail-table.tsx +65 -0
  249. package/src/examples/simple-detail-table.vitest.tsx +200 -0
  250. package/src/examples/simple-graph.tsx +51 -0
  251. package/src/examples/simple-graph.vitest.tsx +124 -0
  252. package/src/examples/simple-grid.vitest.tsx +3 -3
  253. package/src/examples/simple-list-search.vitest.tsx +65 -0
  254. package/src/examples/simple-navigation.vitest.tsx +3 -3
  255. package/src/examples/simple-table-wrap.tsx +55 -0
  256. package/src/examples/simple-table-wrap.vitest.tsx +91 -0
  257. package/src/examples/store.vitest.tsx +1 -1
  258. package/src/examples/table-edge-cases.tsx +72 -0
  259. package/src/examples/table-edge-cases.vitest.tsx +307 -0
  260. package/src/examples/table-flex-grow.tsx +53 -0
  261. package/src/examples/table-flex-grow.vitest.tsx +124 -0
  262. package/src/extensions/dev.tsx +7 -1
  263. package/src/globals.ts +3 -0
  264. package/src/index.tsx +31 -0
  265. package/src/internal/date-picker-widget.tsx +4 -0
  266. package/src/internal/providers.tsx +1 -4
  267. package/src/logger.tsx +2 -1
  268. package/src/markdown-utils.tsx +82 -1
  269. package/src/opentui.tsx +5 -0
  270. package/src/release.tsx +3 -0
  271. package/src/state.tsx +2 -1
  272. package/src/swift-runtime.tsx +19 -5
  273. package/src/theme.tsx +14 -0
  274. package/src/themes/nerv.json +231 -0
  275. package/src/themes/termcast.json +75 -71
  276. package/src/themes.ts +8 -5
  277. package/src/utils.test.tsx +1 -1
  278. package/src/utils.tsx +5 -1
@@ -0,0 +1,72 @@
1
+ // Example: Comprehensive table edge cases for testing.
2
+ // Tests markdown tables with inline formatting, single column/row,
3
+ // empty cells, wide content, and various column counts.
4
+
5
+ import { Detail } from 'termcast'
6
+ import { renderWithProviders } from '../utils'
7
+
8
+ const markdown = `# Table Edge Cases
9
+
10
+ ## Inline Formatting
11
+
12
+ | Feature | Syntax | Result |
13
+ |---------|--------|--------|
14
+ | Bold | **text** | bold text |
15
+ | Italic | *text* | italic text |
16
+ | Code | \`code\` | inline code |
17
+ | Link | [docs](https://example.com) | clickable link |
18
+ | Mixed | **bold** and *italic* | combined |
19
+
20
+ ## Single Column
21
+
22
+ | Name |
23
+ |------|
24
+ | Alice |
25
+ | Bob |
26
+ | Charlie |
27
+
28
+ ## Single Row
29
+
30
+ | A | B | C | D | E |
31
+ |---|---|---|---|---|
32
+ | 1 | 2 | 3 | 4 | 5 |
33
+
34
+ ## Empty Cells
35
+
36
+ | Key | Value | Notes |
37
+ |-----|-------|-------|
38
+ | host | localhost | |
39
+ | | 8080 | default port |
40
+ | ssl | | not configured |
41
+
42
+ ## Wide Table
43
+
44
+ | ID | Name | Email | Role | Department | Location |
45
+ |----|------|-------|------|------------|----------|
46
+ | 1 | Alice Johnson | alice@example.com | Engineer | Engineering | SF |
47
+ | 2 | Bob Smith | bob@example.com | Designer | Design | NYC |
48
+
49
+ ## Two Columns
50
+
51
+ | Key | Value |
52
+ |-----|-------|
53
+ | version | 2.1.0 |
54
+ | license | MIT |
55
+ | author | termcast |
56
+
57
+ ## Numeric Data
58
+
59
+ | Metric | Q1 | Q2 | Q3 | Q4 |
60
+ |--------|----|----|----|----|
61
+ | Revenue | 100 | 150 | 200 | 250 |
62
+ | Users | 1000 | 1500 | 2000 | 3000 |
63
+ | Churn | 5% | 4% | 3% | 2% |
64
+
65
+ Done.
66
+ `
67
+
68
+ function TableEdgeCases() {
69
+ return <Detail markdown={markdown} />
70
+ }
71
+
72
+ renderWithProviders(<TableEdgeCases />)
@@ -0,0 +1,307 @@
1
+ // E2E tests for table edge cases: inline formatting, single column/row,
2
+ // empty cells, wide tables, various column counts, and numeric data.
3
+
4
+ import { test, expect, afterEach, beforeEach } from 'vitest'
5
+ import { launchTerminal, Session } from 'tuistory/src'
6
+
7
+ let session: Session
8
+
9
+ beforeEach(async () => {
10
+ session = await launchTerminal({
11
+ command: 'bun',
12
+ args: ['src/examples/table-edge-cases.tsx'],
13
+ cols: 80,
14
+ rows: 80,
15
+ })
16
+ })
17
+
18
+ afterEach(() => {
19
+ session?.close()
20
+ })
21
+
22
+ test('inline formatting table renders all rows', async () => {
23
+ const text = await session.text({
24
+ waitFor: (text) => {
25
+ return text.includes('Inline Formatting') && text.includes('bold text') && text.includes('combined')
26
+ },
27
+ timeout: 10000,
28
+ })
29
+
30
+ expect(text).toMatchInlineSnapshot(`
31
+ "
32
+
33
+
34
+
35
+
36
+ Table Edge Cases
37
+
38
+ Inline Formatting
39
+
40
+ Feature Syntax Result
41
+ Bold text bold text
42
+ Italic text italic text
43
+ Code code inline code
44
+ Link docs clickable link
45
+ Mixed bold and italic combined
46
+
47
+ Single Column
48
+
49
+ Name
50
+ Alice
51
+ Bob
52
+ Charlie
53
+
54
+ Single Row
55
+
56
+ A B C D E
57
+ 1 2 3 4 5
58
+
59
+ Empty Cells
60
+
61
+ Key Value Notes
62
+ host localhost
63
+ 8080 default port
64
+ ssl not configured
65
+
66
+ Wide Table
67
+
68
+ ID Name Email Role Department Location
69
+ 1 Alice Johnson alice@example.com Engineer Engineering SF
70
+ 2 Bob Smith bob@example.com Designer Design NYC
71
+
72
+ Two Columns
73
+
74
+ Key Value
75
+ version 2.1.0
76
+ license MIT
77
+ author termcast
78
+
79
+ Numeric Data
80
+
81
+ Metric Q1 Q2 Q3 Q4
82
+ Revenue 100 150 200 250
83
+ Users 1000 1500 2000 3000
84
+ Churn 5% 4% 3% 2%
85
+
86
+ Done.
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+ esc go back ^k actions powered by termcast.app
110
+
111
+ "
112
+ `)
113
+
114
+ // All formatting rows present
115
+ expect(text).toContain('bold text')
116
+ expect(text).toContain('italic text')
117
+ expect(text).toContain('inline code')
118
+ expect(text).toContain('clickable link')
119
+ expect(text).toContain('combined')
120
+ // Headers
121
+ expect(text).toContain('Feature')
122
+ expect(text).toContain('Syntax')
123
+ expect(text).toContain('Result')
124
+ }, 30000)
125
+
126
+ test('single column table', async () => {
127
+ const text = await session.text({
128
+ waitFor: (text) => text.includes('Single Column') && text.includes('Charlie'),
129
+ timeout: 10000,
130
+ })
131
+
132
+ expect(text).toContain('Alice')
133
+ expect(text).toContain('Bob')
134
+ expect(text).toContain('Charlie')
135
+ }, 30000)
136
+
137
+ test('single row table', async () => {
138
+ const text = await session.text({
139
+ waitFor: (text) => text.includes('Single Row'),
140
+ timeout: 10000,
141
+ })
142
+
143
+ // 5 columns with single data row
144
+ expect(text).toContain('A')
145
+ expect(text).toContain('B')
146
+ expect(text).toContain('C')
147
+ expect(text).toContain('D')
148
+ expect(text).toContain('E')
149
+ }, 30000)
150
+
151
+ test('empty cells do not break layout', async () => {
152
+ const text = await session.text({
153
+ waitFor: (text) => text.includes('Empty Cells') && text.includes('not configured'),
154
+ timeout: 10000,
155
+ })
156
+
157
+ expect(text).toContain('localhost')
158
+ expect(text).toContain('default port')
159
+ expect(text).toContain('not configured')
160
+ }, 30000)
161
+
162
+ test('wide table with 6 columns fits within terminal', async () => {
163
+ const text = await session.text({
164
+ waitFor: (text) => text.includes('Wide Table') && text.includes('Engineer'),
165
+ timeout: 10000,
166
+ })
167
+
168
+ // Headers
169
+ expect(text).toContain('ID')
170
+ expect(text).toContain('Name')
171
+ expect(text).toContain('Email')
172
+ expect(text).toContain('Role')
173
+ // Data - some fields may be truncated at 80 cols with 6 equal-width columns
174
+ expect(text).toContain('Alice Johnso')
175
+ expect(text).toContain('Bob Smith')
176
+ expect(text).toContain('Engineer')
177
+ expect(text).toContain('Designer')
178
+ }, 30000)
179
+
180
+ test('two column key-value table', async () => {
181
+ const text = await session.text({
182
+ waitFor: (text) => text.includes('Two Columns') && text.includes('2.1.0'),
183
+ timeout: 10000,
184
+ })
185
+
186
+ expect(text).toContain('version')
187
+ expect(text).toContain('2.1.0')
188
+ expect(text).toContain('license')
189
+ expect(text).toContain('MIT')
190
+ expect(text).toContain('author')
191
+ expect(text).toContain('termcast')
192
+ }, 30000)
193
+
194
+ test('numeric data table', async () => {
195
+ const text = await session.text({
196
+ waitFor: (text) => text.includes('Numeric Data') && text.includes('Revenue'),
197
+ timeout: 10000,
198
+ })
199
+
200
+ expect(text).toContain('Revenue')
201
+ expect(text).toContain('Users')
202
+ expect(text).toContain('Churn')
203
+ expect(text).toContain('250')
204
+ expect(text).toContain('3000')
205
+ expect(text).toContain('2%')
206
+ }, 30000)
207
+
208
+ test('all tables render without crash - full page snapshot', async () => {
209
+ const text = await session.text({
210
+ waitFor: (text) => text.includes('Done.'),
211
+ timeout: 10000,
212
+ })
213
+
214
+ expect(text).toMatchInlineSnapshot(`
215
+ "
216
+
217
+
218
+
219
+
220
+ Table Edge Cases
221
+
222
+ Inline Formatting
223
+
224
+ Feature Syntax Result
225
+ Bold text bold text
226
+ Italic text italic text
227
+ Code code inline code
228
+ Link docs clickable link
229
+ Mixed bold and italic combined
230
+
231
+ Single Column
232
+
233
+ Name
234
+ Alice
235
+ Bob
236
+ Charlie
237
+
238
+ Single Row
239
+
240
+ A B C D E
241
+ 1 2 3 4 5
242
+
243
+ Empty Cells
244
+
245
+ Key Value Notes
246
+ host localhost
247
+ 8080 default port
248
+ ssl not configured
249
+
250
+ Wide Table
251
+
252
+ ID Name Email Role Department Location
253
+ 1 Alice Johnson alice@example.com Engineer Engineering SF
254
+ 2 Bob Smith bob@example.com Designer Design NYC
255
+
256
+ Two Columns
257
+
258
+ Key Value
259
+ version 2.1.0
260
+ license MIT
261
+ author termcast
262
+
263
+ Numeric Data
264
+
265
+ Metric Q1 Q2 Q3 Q4
266
+ Revenue 100 150 200 250
267
+ Users 1000 1500 2000 3000
268
+ Churn 5% 4% 3% 2%
269
+
270
+ Done.
271
+
272
+
273
+ esc go back ^k actions powered by termcast.app
274
+
275
+
276
+
277
+
278
+
279
+
280
+
281
+
282
+
283
+
284
+
285
+
286
+
287
+
288
+
289
+
290
+
291
+
292
+
293
+
294
+
295
+ "
296
+ `)
297
+
298
+ // Verify all section headings present
299
+ expect(text).toContain('Inline Formatting')
300
+ expect(text).toContain('Single Column')
301
+ expect(text).toContain('Single Row')
302
+ expect(text).toContain('Empty Cells')
303
+ expect(text).toContain('Wide Table')
304
+ expect(text).toContain('Two Columns')
305
+ expect(text).toContain('Numeric Data')
306
+ expect(text).toContain('Done.')
307
+ }, 30000)
@@ -0,0 +1,53 @@
1
+ // Example: Table with flexGrow to fill available width.
2
+ // Uses wrapText mode so columns distribute evenly across the full table width,
3
+ // making it visually obvious whether flexGrow stretches the table or not.
4
+ // The header background also reveals the actual table width.
5
+
6
+ import { renderWithProviders } from '../utils'
7
+ import { Table } from 'termcast/src/components/table'
8
+
9
+ function TableFlexGrow() {
10
+ const headers = ['Key', 'Value']
11
+ const rows = [
12
+ ['version', '2.1.0'],
13
+ ['license', 'MIT'],
14
+ ['author', 'termcast'],
15
+ ]
16
+
17
+ return (
18
+ <box flexDirection="column" paddingTop={1} paddingLeft={2} paddingRight={2}>
19
+ <text>With flexGrow=1 + wrapText (fills remaining space)</text>
20
+ <box height={1} />
21
+ <box flexDirection="row" width="100%">
22
+ <box width={12} flexShrink={0}>
23
+ <text>Config:</text>
24
+ </box>
25
+ <Table headers={headers} rows={rows} flexGrow={1} wrapText />
26
+ </box>
27
+
28
+ <box height={2} />
29
+
30
+ <text>Width=auto + wrapText (content-sized, no stretch)</text>
31
+ <box height={1} />
32
+ <box flexDirection="row" width="100%">
33
+ <box width={12} flexShrink={0}>
34
+ <text>Config:</text>
35
+ </box>
36
+ <Table headers={headers} rows={rows} width="auto" wrapText />
37
+ </box>
38
+
39
+ <box height={2} />
40
+
41
+ <text>With flexGrow=1 no wrapText (column-based)</text>
42
+ <box height={1} />
43
+ <box flexDirection="row" width="100%">
44
+ <box width={12} flexShrink={0}>
45
+ <text>Config:</text>
46
+ </box>
47
+ <Table headers={headers} rows={rows} flexGrow={1} />
48
+ </box>
49
+ </box>
50
+ )
51
+ }
52
+
53
+ renderWithProviders(<TableFlexGrow />)
@@ -0,0 +1,124 @@
1
+ // E2E tests for Table with flexGrow prop.
2
+ // Uses wrapText mode to make the width difference visually obvious:
3
+ // in row-based layout, columns distribute evenly so a wider table
4
+ // means wider columns. Also checks header background extent.
5
+
6
+ import { test, expect, afterEach, beforeEach } from 'vitest'
7
+ import { launchTerminal, Session } from 'tuistory/src'
8
+
9
+ let session: Session
10
+
11
+ beforeEach(async () => {
12
+ const dbSuffix = `table-flex-grow-${process.pid}-${Date.now()}`
13
+ session = await launchTerminal({
14
+ command: 'bun',
15
+ args: ['src/examples/table-flex-grow.tsx'],
16
+ cols: 80,
17
+ rows: 40,
18
+ env: {
19
+ TERMCAST_DB_SUFFIX: dbSuffix,
20
+ },
21
+ })
22
+ })
23
+
24
+ afterEach(() => {
25
+ session?.close()
26
+ })
27
+
28
+ test('flexGrow table fills remaining space next to fixed-width label', async () => {
29
+ const text = await session.text({
30
+ waitFor: (text) => {
31
+ return text.includes('flexGrow=1') && text.includes('version') && text.includes('Width=auto')
32
+ },
33
+ timeout: 10000,
34
+ })
35
+
36
+ expect(text).toMatchInlineSnapshot(`
37
+ "
38
+
39
+
40
+
41
+ With flexGrow=1 + wrapText (fills remaining space)
42
+
43
+ Config: Key Value
44
+ version 2.1.0
45
+ license MIT
46
+ author termcast
47
+
48
+
49
+ Width=auto + wrapText (content-sized, no stretch)
50
+
51
+ Config: KeVa
52
+ ve2.
53
+ liMI
54
+ aute
55
+
56
+
57
+ With flexGrow=1 no wrapText (column-based)
58
+
59
+ Config: Key Value
60
+ version 2.1.0
61
+ license MIT
62
+ author termcast
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+ "
78
+ `)
79
+
80
+ // All three table sections render
81
+ expect(text).toContain('flexGrow=1')
82
+ expect(text).toContain('Width=auto')
83
+ // Table data is visible
84
+ expect(text).toContain('version')
85
+ expect(text).toContain('2.1.0')
86
+ expect(text).toContain('termcast')
87
+ // Fixed label is present
88
+ expect(text).toContain('Config:')
89
+ }, 30000)
90
+
91
+ test('flexGrow header background is wider than width=auto header', async () => {
92
+ await session.text({
93
+ waitFor: (text) => {
94
+ return text.includes('flexGrow=1') && text.includes('Width=auto')
95
+ },
96
+ timeout: 10000,
97
+ })
98
+
99
+ // Header bg is orange (#e89500) from the default nerv theme.
100
+ // With flexGrow=1 the header row should span the full remaining width.
101
+ // With width=auto it should be content-sized (narrower).
102
+ const headerBgText = await session.text({
103
+ only: { background: '#e89500' },
104
+ timeout: 5000,
105
+ })
106
+
107
+ // The flexGrow header should have more trailing spaces (wider bg).
108
+ // Both tables have "Key" and "Value" headers, but the flexGrow one
109
+ // should have significantly more padding/whitespace in the bg.
110
+ expect(headerBgText).toContain('Key')
111
+ expect(headerBgText).toContain('Value')
112
+
113
+ // Extract the two header lines - flexGrow header should be longer
114
+ const lines = headerBgText.split('\n').filter((l) => {
115
+ return l.includes('Key')
116
+ })
117
+ // Should have at least 2 header lines (flexGrow + width=auto + column-based)
118
+ expect(lines.length).toBeGreaterThanOrEqual(2)
119
+
120
+ // The flexGrow line (first) should be wider than width=auto line (second)
121
+ const flexGrowLine = lines[0]!
122
+ const autoLine = lines[1]!
123
+ expect(flexGrowLine.length).toBeGreaterThan(autoLine.length)
124
+ }, 30000)
@@ -14,7 +14,7 @@ import { useNavigation } from 'termcast/src/internal/navigation'
14
14
  import { TermcastProvider } from 'termcast/src/internal/providers'
15
15
  import { showToast, Toast } from 'termcast/src/apis/toast'
16
16
  import { Icon } from 'termcast'
17
- import { useTheme } from 'termcast/src/theme'
17
+ import { useTheme, initializeTheme } from 'termcast/src/theme'
18
18
  import { logger } from '../logger'
19
19
  import { getCommandsWithFiles, CommandWithFile, RaycastPackageJson } from '../package-json'
20
20
  import { buildExtensionCommands } from '../build'
@@ -226,6 +226,9 @@ export async function startDevMode({
226
226
  devRebuildCount: 1,
227
227
  })
228
228
 
229
+ // Load theme after state reset — extensionPath is now set so it reads from the correct DB
230
+ initializeTheme()
231
+
229
232
  function App(): any {
230
233
  const devElement = useStore((state) => state.devElement)
231
234
  // REMOVED: key={devRebuildCount} - we want to preserve the React tree!
@@ -296,6 +299,9 @@ export async function startCompiledExtension({
296
299
  devRebuildCount: 1,
297
300
  })
298
301
 
302
+ // Load theme after state reset — extensionPath is now set so it reads from the correct DB
303
+ initializeTheme()
304
+
299
305
  function App(): any {
300
306
  const devElement = useStore((state) => state.devElement)
301
307
  return <TermcastProvider>{devElement}</TermcastProvider>
package/src/globals.ts CHANGED
@@ -10,6 +10,7 @@ import * as opentuiReact from '@opentui/react'
10
10
  import * as react from 'react'
11
11
  import * as reactJsxRuntime from 'react/jsx-runtime'
12
12
  import * as termcastApi from './index'
13
+ import * as termcastOpentui from './opentui'
13
14
 
14
15
  declare global {
15
16
  var opentuiCore: typeof opentuiCore
@@ -17,6 +18,7 @@ declare global {
17
18
  var react: typeof react
18
19
  var reactJsxRuntime: typeof reactJsxRuntime
19
20
  var termcastApi: typeof termcastApi
21
+ var termcastOpentui: typeof termcastOpentui
20
22
  }
21
23
 
22
24
  // Initialize globals
@@ -30,5 +32,6 @@ globalThis._Fragment = reactJsxRuntime.Fragment
30
32
 
31
33
  globalThis.reactJsxRuntime = reactJsxRuntime
32
34
  globalThis.termcastApi = termcastApi
35
+ globalThis.termcastOpentui = termcastOpentui
33
36
 
34
37
  globalThis.logger = logger
package/src/index.tsx CHANGED
@@ -52,6 +52,36 @@ export type {
52
52
  DetailPropsWithLoading,
53
53
  } from 'termcast/src/components/detail'
54
54
 
55
+ // Core UI Components - Graph
56
+ export { Graph } from 'termcast/src/components/graph'
57
+ export type {
58
+ GraphProps,
59
+ GraphLineProps,
60
+ GraphVariant,
61
+ } from 'termcast/src/components/graph'
62
+
63
+ // Core UI Components - Row
64
+ export { Row } from 'termcast/src/components/row'
65
+ export type { RowProps } from 'termcast/src/components/row'
66
+
67
+ // Core UI Components - Table
68
+ export { Table } from 'termcast/src/components/table'
69
+ export type { TableProps } from 'termcast/src/components/table'
70
+
71
+ // Core UI Components - BarChart
72
+ export { BarChart } from 'termcast/src/components/bar-chart'
73
+ export type {
74
+ BarChartProps,
75
+ BarChartSegmentProps,
76
+ } from 'termcast/src/components/bar-chart'
77
+
78
+ // Core UI Components - BarGraph
79
+ export { BarGraph } from 'termcast/src/components/bar-graph'
80
+ export type {
81
+ BarGraphProps,
82
+ BarGraphSeriesProps,
83
+ } from 'termcast/src/components/bar-graph'
84
+
55
85
  // Form Components
56
86
  import {
57
87
  Form as FormComponent,
@@ -158,6 +188,7 @@ import type {
158
188
  ImageFallback,
159
189
  } from 'termcast/src/components/image'
160
190
 
191
+
161
192
  export function Image(props: ImageProps): any {
162
193
  return ImageComponent(props)
163
194
  }
@@ -256,7 +256,9 @@ export function DatePickerWidget({
256
256
  } else if (focus === 'year') {
257
257
  // At top of widget, trigger callback or cycle to bottom
258
258
  if (onFirstRowUpKey) {
259
+ key.stopPropagation()
259
260
  onFirstRowUpKey()
261
+ return
260
262
  } else {
261
263
  // Cycle to grid if no callback
262
264
  setFocus('grid')
@@ -274,7 +276,9 @@ export function DatePickerWidget({
274
276
  if (remainingDays < 7) {
275
277
  // At bottom of grid, trigger callback or cycle to top
276
278
  if (onLastRowDownKey) {
279
+ key.stopPropagation()
277
280
  onLastRowDownKey()
281
+ return
278
282
  } else {
279
283
  // Cycle to year if no callback
280
284
  setFocus('year')
@@ -11,7 +11,7 @@ import { NavigationProvider } from 'termcast/src/internal/navigation'
11
11
  import { CommonProps, termcastMaxContentWidth } from 'termcast/src/utils'
12
12
  import { Cache } from 'termcast/src/apis/cache'
13
13
  import { logger } from 'termcast/src/logger'
14
- import { useTheme, initializeTheme } from 'termcast/src/theme'
14
+ import { useTheme } from 'termcast/src/theme'
15
15
  import { useStore } from 'termcast/src/state'
16
16
  import { useKeyboard, useRenderer } from '@opentui/react'
17
17
  import { initializeErrorHandlers } from 'termcast/src/internal/error-handler'
@@ -22,9 +22,6 @@ import { Clipboard } from '../apis/clipboard'
22
22
  // Initialize error handlers at module load time
23
23
  initializeErrorHandlers()
24
24
 
25
- // Initialize theme from persisted storage
26
- initializeTheme()
27
-
28
25
  const queryClient = new QueryClient({
29
26
  defaultOptions: {
30
27
  queries: {