termcast 1.5.0 → 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 (68) hide show
  1. package/dist/build.d.ts.map +1 -1
  2. package/dist/build.js +22 -5
  3. package/dist/build.js.map +1 -1
  4. package/dist/compile.d.ts.map +1 -1
  5. package/dist/compile.js +7 -1
  6. package/dist/compile.js.map +1 -1
  7. package/dist/components/bar-graph.d.ts +4 -4
  8. package/dist/components/bar-graph.js +2 -2
  9. package/dist/components/list.d.ts +7 -0
  10. package/dist/components/list.d.ts.map +1 -1
  11. package/dist/components/list.js +74 -11
  12. package/dist/components/list.js.map +1 -1
  13. package/dist/examples/list-detail-height-ratchet.d.ts +2 -0
  14. package/dist/examples/list-detail-height-ratchet.d.ts.map +1 -0
  15. package/dist/examples/list-detail-height-ratchet.js +26 -0
  16. package/dist/examples/list-detail-height-ratchet.js.map +1 -0
  17. package/dist/extensions/dev.d.ts.map +1 -1
  18. package/dist/extensions/dev.js +1 -0
  19. package/dist/extensions/dev.js.map +1 -1
  20. package/dist/globals.js +8 -0
  21. package/dist/globals.js.map +1 -1
  22. package/dist/package-json.d.ts +2 -0
  23. package/dist/package-json.d.ts.map +1 -1
  24. package/dist/package-json.js +20 -17
  25. package/dist/package-json.js.map +1 -1
  26. package/dist/profiler.d.ts +2 -0
  27. package/dist/profiler.d.ts.map +1 -0
  28. package/dist/profiler.js +390 -0
  29. package/dist/profiler.js.map +1 -0
  30. package/package.json +14 -15
  31. package/src/build.tsx +27 -5
  32. package/src/cli.tsx +0 -0
  33. package/src/compile.tsx +9 -1
  34. package/src/compile.vitest.tsx +8 -8
  35. package/src/components/bar-graph.tsx +9 -9
  36. package/src/components/list.tsx +92 -11
  37. package/src/examples/action-shortcut.vitest.tsx +4 -4
  38. package/src/examples/actions-context.vitest.tsx +2 -2
  39. package/src/examples/bar-graph-weekly.vitest.tsx +97 -97
  40. package/src/examples/github.vitest.tsx +17 -26
  41. package/src/examples/graph-bar-chart.vitest.tsx +36 -36
  42. package/src/examples/graph-polymarket.vitest.tsx +24 -24
  43. package/src/examples/graph-row.vitest.tsx +4 -4
  44. package/src/examples/graph-styles.vitest.tsx +65 -65
  45. package/src/examples/horizontal-bar-graph-weekly.vitest.tsx +52 -52
  46. package/src/examples/list-detail-height-ratchet.tsx +48 -0
  47. package/src/examples/list-detail-height-ratchet.vitest.tsx +161 -0
  48. package/src/examples/list-detail-metadata.vitest.tsx +49 -49
  49. package/src/examples/list-dropdown-default.vitest.tsx +27 -27
  50. package/src/examples/list-fetch-data.vitest.tsx +3 -3
  51. package/src/examples/list-loading-empty-view.vitest.tsx +1 -1
  52. package/src/examples/list-no-actions.vitest.tsx +3 -3
  53. package/src/examples/list-scrollbox.vitest.tsx +6 -6
  54. package/src/examples/list-spacing-mode.vitest.tsx +1 -1
  55. package/src/examples/list-with-detail.vitest.tsx +9 -9
  56. package/src/examples/list-with-dropdown.vitest.tsx +6 -6
  57. package/src/examples/list-with-sections.vitest.tsx +20 -20
  58. package/src/examples/list-with-toast.vitest.tsx +4 -4
  59. package/src/examples/simple-candle-chart.vitest.tsx +61 -59
  60. package/src/examples/simple-navigation.vitest.tsx +25 -25
  61. package/src/examples/simple-progress-bar.vitest.tsx +7 -7
  62. package/src/examples/swift-extension.vitest.tsx +3 -3
  63. package/src/examples/toast-action.vitest.tsx +4 -4
  64. package/src/extensions/dev.tsx +2 -1
  65. package/src/extensions/dev.vitest.tsx +17 -17
  66. package/src/globals.ts +9 -0
  67. package/src/package-json.tsx +24 -23
  68. package/src/profiler.tsx +487 -0
@@ -0,0 +1,48 @@
1
+ // Example: List with varying detail heights to test the grow-only minHeight
2
+ // ratchet on the detail panel. Navigating from a tall detail to a short one
3
+ // should NOT cause the footer to jump up (no layout shift).
4
+ import React from 'react'
5
+ import { List } from 'termcast'
6
+ import { renderWithProviders } from '../utils'
7
+
8
+ // Generate 25 items with alternating short/tall detail content.
9
+ // Odd-indexed items have tall detail (many lines), even-indexed have short detail.
10
+ const items = Array.from({ length: 25 }, (_, i) => {
11
+ const isTall = i % 2 === 1
12
+ const markdown = isTall
13
+ ? [
14
+ `# Item ${i} (tall)`,
15
+ '',
16
+ ...Array.from({ length: 12 }, (_, j) => `Line ${j + 1} of detail content for item ${i}.`),
17
+ ].join('\n')
18
+ : `# Item ${i} (short)\n\nBrief.`
19
+
20
+ return {
21
+ id: `item-${i}`,
22
+ title: `Item ${i}`,
23
+ subtitle: isTall ? 'tall' : 'short',
24
+ markdown,
25
+ }
26
+ })
27
+
28
+ function ListDetailHeightRatchet() {
29
+ return (
30
+ <List
31
+ navigationTitle="Height Ratchet Test"
32
+ searchBarPlaceholder="Search..."
33
+ isShowingDetail={true}
34
+ >
35
+ {items.map((item) => (
36
+ <List.Item
37
+ key={item.id}
38
+ id={item.id}
39
+ title={item.title}
40
+ subtitle={item.subtitle}
41
+ detail={<List.Item.Detail markdown={item.markdown} />}
42
+ />
43
+ ))}
44
+ </List>
45
+ )
46
+ }
47
+
48
+ await renderWithProviders(<ListDetailHeightRatchet />)
@@ -0,0 +1,161 @@
1
+ // E2E tests for the detail panel grow-only height ratchet.
2
+ // Verifies that the footer does not shift up when navigating from a tall
3
+ // detail item to a short one. The detail panel's minHeight should ratchet
4
+ // upward and never shrink back.
5
+ import { test, expect, afterEach, beforeEach } from 'vitest'
6
+ import { launchTerminal, Session } from 'tuistory/src'
7
+
8
+ let session: Session
9
+
10
+ beforeEach(async () => {
11
+ session = await launchTerminal({
12
+ command: 'bun',
13
+ args: ['src/examples/list-detail-height-ratchet.tsx'],
14
+ cols: 80,
15
+ rows: 30,
16
+ })
17
+ })
18
+
19
+ afterEach(() => {
20
+ session?.close()
21
+ })
22
+
23
+ function getFooterLineNumber(snapshot: string): number {
24
+ const lines = snapshot.split('\n')
25
+ return lines.findIndex((line) => line.includes('navigate'))
26
+ }
27
+
28
+ test('detail panel height ratchet prevents footer layout shift', async () => {
29
+ // Item 0 is short detail
30
+ const shortSnapshot = await session.text({
31
+ waitFor: (text) => {
32
+ return text.includes('›Item 0') && text.includes('Brief') && text.includes('navigate')
33
+ },
34
+ timeout: 10000,
35
+ })
36
+ expect(shortSnapshot).toMatchInlineSnapshot(`
37
+ "
38
+
39
+
40
+ Height Ratchet Test ──────────────────────────────────────────────────────
41
+
42
+ > Search...
43
+
44
+ ›Item 0 short │ Item 0 (short)
45
+ Item 1 tall │
46
+ Item 2 short │
47
+ Item 3 tall │ Brief.
48
+ Item 4 short │
49
+ Item 5 tall │
50
+ Item 6 short │
51
+ Item 7 tall │
52
+ Item 8 short │
53
+ Item 9 tall │
54
+ Item 10 short │
55
+ Item 11 tall │
56
+ Item 12 short │
57
+ Item 13 tall │
58
+ Item 14 short │
59
+ Item 15 tall │
60
+ Item 16 short │
61
+ Item 17 tall │
62
+ Item 18 short │
63
+
64
+
65
+ ↑↓ navigate ^k actions :vim │
66
+
67
+ "
68
+ `)
69
+
70
+ // Navigate to Item 1 (tall detail) to ratchet the height up
71
+ await session.press('down')
72
+
73
+ const tallSnapshot = await session.text({
74
+ waitFor: (text) => {
75
+ return text.includes('›Item 1') && text.includes('Line 1') && text.includes('navigate')
76
+ },
77
+ })
78
+ expect(tallSnapshot).toMatchInlineSnapshot(`
79
+ "
80
+
81
+
82
+ Height Ratchet Test ──────────────────────────────────────────────────────
83
+
84
+ > Search...
85
+
86
+ Item 0 short │ Item 1 (tall) ▲
87
+ ›Item 1 tall │ Line 1 of detail content for item █
88
+ Item 2 short │ 1. █
89
+ Item 3 tall │ Line 2 of detail content for item █
90
+ Item 4 short │ 1. █
91
+ Item 5 tall │ Line 3 of detail content for item █
92
+ Item 6 short │ 1.
93
+ Item 7 tall │ Line 4 of detail content for item
94
+ Item 8 short │ 1.
95
+ Item 9 tall │ Line 5 of detail content for item
96
+ Item 10 short │ 1.
97
+ Item 11 tall │ Line 6 of detail content for item
98
+ Item 12 short │ 1.
99
+ Item 13 tall │ Line 7 of detail content for item
100
+ Item 14 short │ 1.
101
+ Item 15 tall │ Line 8 of detail content for item
102
+ Item 16 short │ 1.
103
+ Item 17 tall │ Line 9 of detail content for item
104
+ Item 18 short │ 1.
105
+ │ Line 10 of detail content for
106
+ │ item 1.
107
+ ↑↓ navigate ^k actions :vim │ Line 11 of detail content for ▼
108
+
109
+ "
110
+ `)
111
+
112
+ // Navigate back to Item 0 (short detail).
113
+ // The footer should stay at the same line as the tall snapshot.
114
+ await session.press('up')
115
+
116
+ const backToShortSnapshot = await session.text({
117
+ waitFor: (text) => {
118
+ return text.includes('›Item 0') && text.includes('Brief') && text.includes('navigate')
119
+ },
120
+ })
121
+ expect(backToShortSnapshot).toMatchInlineSnapshot(`
122
+ "
123
+
124
+
125
+ Height Ratchet Test ──────────────────────────────────────────────────────
126
+
127
+ > Search...
128
+
129
+ ›Item 0 short │ Item 0 (short)
130
+ Item 1 tall │ Brief.
131
+ Item 2 short │
132
+ Item 3 tall │
133
+ Item 4 short │
134
+ Item 5 tall │
135
+ Item 6 short │
136
+ Item 7 tall │
137
+ Item 8 short │
138
+ Item 9 tall │
139
+ Item 10 short │
140
+ Item 11 tall │
141
+ Item 12 short │
142
+ Item 13 tall │
143
+ Item 14 short │
144
+ Item 15 tall │
145
+ Item 16 short │
146
+ Item 17 tall │
147
+ Item 18 short │
148
+
149
+
150
+ ↑↓ navigate ^k actions :vim │
151
+
152
+ "
153
+ `)
154
+
155
+ // The key assertion: footer line should not move up when going from tall to short
156
+ const footerLineTall = getFooterLineNumber(tallSnapshot)
157
+ const footerLineBackToShort = getFooterLineNumber(backToShortSnapshot)
158
+
159
+ expect(footerLineBackToShort).toBe(footerLineTall)
160
+ expect(backToShortSnapshot).toContain('navigate')
161
+ }, 20000)
@@ -48,28 +48,28 @@ test('list detail metadata label renders short values in row layout (key: value)
48
48
 
49
49
  │ Status: Active
50
50
 
51
- ↑↓ navigate ^k actions :vim │ Website: example.com
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
51
+ │ Website: example.com
52
+
53
+
54
+
55
+
56
+
57
+
58
+
59
+
60
+
61
+
62
+
63
+
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+ ↑↓ navigate ^k actions :vim │
73
73
 
74
74
  "
75
75
  `)
@@ -113,21 +113,21 @@ test('list detail metadata renders long values in column layout (key on one line
113
113
  │ Short: OK
114
114
 
115
115
  │ URL: example.com/very/
116
- ↑↓ navigate ^k actions :vim │ long/path
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
116
+ │ long/path
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+
130
+ ↑↓ navigate ^k actions :vim │
131
131
 
132
132
  "
133
133
  `)
@@ -176,18 +176,18 @@ test('list detail metadata renders colored values and tag lists', async () => {
176
176
 
177
177
  │ ──────────────────────────────────
178
178
 
179
- ↑↓ navigate ^k actions :vim │ Repo: github.com/example
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
179
+ │ Repo: github.com/example
180
+
181
+
182
+
183
+
184
+
185
+
186
+
187
+
188
+
189
+
190
+ ↑↓ navigate ^k actions :vim │
191
191
 
192
192
  "
193
193
  `)
@@ -43,11 +43,11 @@ test('dropdown defaults to first item when no value is provided', async () => {
43
43
 
44
44
 
45
45
 
46
- ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
47
46
 
48
47
 
49
48
 
50
49
 
50
+ ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
51
51
 
52
52
 
53
53
 
@@ -77,20 +77,22 @@ test('dropdown opens and shows items', async () => {
77
77
 
78
78
 
79
79
  Dropdown Default Value Example ───────────────────────────────────────────────────────────────
80
+
81
+ > Search... Apple ▾
80
82
  ╭──────────────────────────────────────────────────────────────────────────╮
81
- > Search.│ │ Apple ▾
82
- │ Filter by category esc │
83
83
  ›First Ite│ │
84
- Second It│ > Select category...
84
+ Second It│ Filter by category esc
85
85
  Vegetable│ │
86
- Carrot Wi│ ›Apple
86
+ Carrot Wi│ > Select category...
87
+ │ │
88
+ │ ›Apple │
87
89
  │ Banana │
88
90
  │ Orange │
89
91
  │ Grape │
90
92
  │ │
91
- ↵ show se│ │mcast.app
92
- │ ↵ select ↑↓ navigate powered by termcast.app │
93
93
  │ │
94
+ │ ↵ select ↑↓ navigate powered by termcast.app │
95
+ ↵ show se│ │mcast.app
94
96
  ╰──────────────────────────────────────────────────────────────────────────╯
95
97
 
96
98
 
@@ -101,8 +103,6 @@ test('dropdown opens and shows items', async () => {
101
103
 
102
104
 
103
105
 
104
-
105
-
106
106
  "
107
107
  `)
108
108
 
@@ -114,20 +114,22 @@ test('dropdown opens and shows items', async () => {
114
114
 
115
115
 
116
116
  Dropdown Default Value Example ───────────────────────────────────────────────────────────────
117
+
118
+ > Search... Apple ▾
117
119
  ╭──────────────────────────────────────────────────────────────────────────╮
118
- > Search.│ │ Apple ▾
119
- │ Filter by category esc │
120
120
  ›First Ite│ │
121
- Second It│ > Select category...
121
+ Second It│ Filter by category esc
122
122
  Vegetable│ │
123
- Carrot Wi│ Apple
123
+ Carrot Wi│ > Select category...
124
+ │ │
125
+ │ Apple │
124
126
  │ ›Banana │
125
127
  │ Orange │
126
128
  │ Grape │
127
129
  │ │
128
- ↵ show se│ │mcast.app
129
- │ ↵ select ↑↓ navigate powered by termcast.app │
130
130
  │ │
131
+ │ ↵ select ↑↓ navigate powered by termcast.app │
132
+ ↵ show se│ │mcast.app
131
133
  ╰──────────────────────────────────────────────────────────────────────────╯
132
134
 
133
135
 
@@ -138,8 +140,6 @@ test('dropdown opens and shows items', async () => {
138
140
 
139
141
 
140
142
 
141
-
142
-
143
143
  "
144
144
  `)
145
145
 
@@ -162,11 +162,11 @@ test('dropdown opens and shows items', async () => {
162
162
 
163
163
 
164
164
 
165
- ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
166
165
 
167
166
 
168
167
 
169
168
 
169
+ ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
170
170
 
171
171
 
172
172
 
@@ -196,20 +196,22 @@ test('clicking dropdown opens it', async () => {
196
196
 
197
197
 
198
198
  Dropdown Default Value Example ───────────────────────────────────────────────────────────────
199
+
200
+ > Search... Apple ▾
199
201
  ╭──────────────────────────────────────────────────────────────────────────╮
200
- > Search.│ │ Apple ▾
201
- │ Filter by category esc │
202
202
  ›First Ite│ │
203
- Second It│ > Select category...
203
+ Second It│ Filter by category esc
204
204
  Vegetable│ │
205
- Carrot Wi│ ›Apple
205
+ Carrot Wi│ > Select category...
206
+ │ │
207
+ │ ›Apple │
206
208
  │ Banana │
207
209
  │ Orange │
208
210
  │ Grape │
209
211
  │ │
210
- ↵ show se│ │mcast.app
211
- │ ↵ select ↑↓ navigate powered by termcast.app │
212
212
  │ │
213
+ │ ↵ select ↑↓ navigate powered by termcast.app │
214
+ ↵ show se│ │mcast.app
213
215
  ╰──────────────────────────────────────────────────────────────────────────╯
214
216
 
215
217
 
@@ -220,8 +222,6 @@ test('clicking dropdown opens it', async () => {
220
222
 
221
223
 
222
224
 
223
-
224
-
225
225
  "
226
226
  `)
227
227
 
@@ -244,11 +244,11 @@ test('clicking dropdown opens it', async () => {
244
244
 
245
245
 
246
246
 
247
- ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
248
247
 
249
248
 
250
249
 
251
250
 
251
+ ↵ show selected fruit ↑↓ navigate ^k actions ^p filter by categorpowered by termcast.app
252
252
 
253
253
 
254
254
 
@@ -48,8 +48,8 @@ test('list shows initial items after fetch', async () => {
48
48
 
49
49
 
50
50
 
51
- ↑↓ navigate ^k actions :vim
52
51
 
52
+ ↑↓ navigate ^k actions :vim
53
53
 
54
54
 
55
55
 
@@ -81,8 +81,8 @@ test('list shows initial items after fetch', async () => {
81
81
 
82
82
 
83
83
 
84
- ↑↓ navigate ^k actions :vim
85
84
 
85
+ ↑↓ navigate ^k actions :vim
86
86
 
87
87
 
88
88
 
@@ -113,8 +113,8 @@ test('list shows initial items after fetch', async () => {
113
113
 
114
114
 
115
115
 
116
- ↑↓ navigate ^k actions :vim
117
116
 
117
+ ↑↓ navigate ^k actions :vim
118
118
 
119
119
 
120
120
 
@@ -43,7 +43,7 @@ test('empty view shows spinner when list is loading', async () => {
43
43
 
44
44
 
45
45
 
46
- ↑↓ navigate ^k actions :vim"
46
+ "
47
47
  `)
48
48
  expect(text).toMatch(/[·• ]\s*loading\.\.\./)
49
49
  }, 10000)
@@ -46,11 +46,11 @@ test('footer does not show stale action title from built-in actions', async () =
46
46
 
47
47
 
48
48
 
49
- ↑↓ navigate ^k actions :vim
50
49
 
51
50
 
52
51
 
53
52
 
53
+ ↑↓ navigate ^k actions :vim
54
54
  "
55
55
  `)
56
56
  }, 30000)
@@ -74,6 +74,7 @@ test('ctrl+k opens built-in actions when item has no actions', async () => {
74
74
  expect(actionsPanel).toMatchInlineSnapshot(`
75
75
  "
76
76
 
77
+
77
78
  ╭────────────────────────────────────────────────────────────────╮
78
79
  │ │
79
80
  │ Actions esc │
@@ -91,7 +92,6 @@ test('ctrl+k opens built-in actions when item has no actions', async () => {
91
92
  │ │
92
93
  │ │
93
94
  │ │
94
- │ ↵ select ↑↓ navigate │
95
- │ │"
95
+ │ ↵ select ↑↓ navigate │"
96
96
  `)
97
97
  }, 30000)
@@ -90,12 +90,12 @@ test('list scrollbox auto-scrolls when navigating down', async () => {
90
90
 
91
91
  > Search items...
92
92
 
93
- Item 7 Description for item 7
94
- Item 8 Description for item 8
95
- ›▤ Item 9 Description for item 9
96
- Item 10 Description for item 10
97
- Item 11 Description for item 11
98
- Item 12 Description for item 12"
93
+ Item 1 Description for item 1
94
+ Item 2 Description for item 2
95
+ Item 3 Description for item 3
96
+ Item 4 Description for item 4
97
+ Item 5 Description for item 5
98
+ Item 6 Description for item 6"
99
99
  `)
100
100
 
101
101
  await session.press('up')
@@ -49,8 +49,8 @@ describe('spacingMode default', () => {
49
49
  ★ Favorites
50
50
 
51
51
 
52
- ↑↓ navigate ^k actions :vim
53
52
 
53
+ ↑↓ navigate ^k actions :vim
54
54
  "
55
55
  `)
56
56
 
@@ -214,11 +214,11 @@ test('list with detail view display and navigation', async () => {
214
214
  wartortle #008 Water
215
215
 
216
216
 
217
- ↵ toggle detail ↑↓ navigate ^k actions :vim powered by termcast.app
218
217
 
219
218
 
220
219
 
221
220
 
221
+ ↵ toggle detail ↑↓ navigate ^k actions :vim powered by termcast.app
222
222
 
223
223
 
224
224
 
@@ -573,16 +573,16 @@ test('list with detail layout consistency - short vs long detail content', async
573
573
 
574
574
 
575
575
 
576
+
577
+
578
+
579
+
580
+
581
+
582
+
583
+
576
584
  ↑↓ navigate ^k actions :vim │
577
585
 
578
-
579
-
580
-
581
-
582
-
583
-
584
-
585
-
586
586
  "
587
587
  `)
588
588
  expect(longDetailSnapshot).toMatchInlineSnapshot(`
@@ -215,11 +215,11 @@ test('list with dropdown navigation', async () => {
215
215
 
216
216
 
217
217
 
218
- ↑↓ navigate ^k actions ^p select drink type :vim
219
218
 
220
219
 
221
220
 
222
221
 
222
+ ↑↓ navigate ^k actions ^p select drink type :vim
223
223
 
224
224
 
225
225
 
@@ -263,9 +263,9 @@ test('small screen: dropdown accessory wastes vertical space', async () => {
263
263
  Sierra Nevada IPA Beer
264
264
  Chateau Margaux Wine
265
265
  Pinot Noir Wine
266
-
267
-
268
- ↑↓ navigate ^k actions ^p select drink type :vim"
266
+ Coca Cola Soda
267
+ Sprite Soda
268
+ Orange Juice Juice"
269
269
  `)
270
270
  } finally {
271
271
  smallSession.close()
@@ -382,11 +382,11 @@ test('list with dropdown search and filter', async () => {
382
382
 
383
383
 
384
384
 
385
- ↑↓ navigate ^k actions ^p select drink type :vim
386
385
 
387
386
 
388
387
 
389
388
 
389
+ ↑↓ navigate ^k actions ^p select drink type :vim
390
390
 
391
391
 
392
392
 
@@ -418,11 +418,11 @@ test('list with dropdown search and filter', async () => {
418
418
 
419
419
 
420
420
 
421
- ↑↓ navigate ^k actions ^p select drink type :vim
422
421
 
423
422
 
424
423
 
425
424
 
425
+ ↑↓ navigate ^k actions ^p select drink type :vim
426
426
 
427
427
 
428
428