termcast 1.3.50 → 1.3.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/environment.d.ts +1 -0
- package/dist/apis/environment.d.ts.map +1 -1
- package/dist/apis/environment.js +5 -0
- package/dist/apis/environment.js.map +1 -1
- package/dist/app.d.ts +33 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +1125 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.js +80 -0
- package/dist/cli.js.map +1 -1
- package/dist/components/detail.d.ts.map +1 -1
- package/dist/components/detail.js +20 -17
- package/dist/components/detail.js.map +1 -1
- package/dist/components/dropdown.d.ts.map +1 -1
- package/dist/components/dropdown.js +3 -2
- package/dist/components/dropdown.js.map +1 -1
- package/dist/components/footer.d.ts +6 -0
- package/dist/components/footer.d.ts.map +1 -1
- package/dist/components/footer.js +15 -6
- package/dist/components/footer.js.map +1 -1
- package/dist/components/form/checkbox.d.ts.map +1 -1
- package/dist/components/form/checkbox.js +1 -13
- package/dist/components/form/checkbox.js.map +1 -1
- package/dist/components/form/date-picker.js +2 -2
- package/dist/components/form/date-picker.js.map +1 -1
- package/dist/components/form/description.js +1 -1
- package/dist/components/form/description.js.map +1 -1
- package/dist/components/form/dropdown.d.ts.map +1 -1
- package/dist/components/form/dropdown.js +19 -3
- package/dist/components/form/dropdown.js.map +1 -1
- package/dist/components/form/file-picker.d.ts.map +1 -1
- package/dist/components/form/file-picker.js +22 -4
- package/dist/components/form/file-picker.js.map +1 -1
- package/dist/components/form/index.d.ts +3 -1
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +6 -4
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/form/password-field.js +3 -3
- package/dist/components/form/password-field.js.map +1 -1
- package/dist/components/form/text-area.d.ts.map +1 -1
- package/dist/components/form/text-area.js +29 -6
- package/dist/components/form/text-area.js.map +1 -1
- package/dist/components/form/text-field.js +3 -3
- package/dist/components/form/text-field.js.map +1 -1
- package/dist/components/heatmap.d.ts +80 -0
- package/dist/components/heatmap.d.ts.map +1 -0
- package/dist/components/heatmap.js +405 -0
- package/dist/components/heatmap.js.map +1 -0
- package/dist/components/list.d.ts +2 -0
- package/dist/components/list.d.ts.map +1 -1
- package/dist/components/list.js +80 -52
- package/dist/components/list.js.map +1 -1
- package/dist/components/markdown.d.ts +7 -0
- package/dist/components/markdown.d.ts.map +1 -0
- package/dist/components/markdown.js +19 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/metadata.d.ts.map +1 -1
- package/dist/components/metadata.js +4 -1
- package/dist/components/metadata.js.map +1 -1
- package/dist/components/progress-bar.d.ts +37 -0
- package/dist/components/progress-bar.d.ts.map +1 -0
- package/dist/components/progress-bar.js +34 -0
- package/dist/components/progress-bar.js.map +1 -0
- package/dist/components/table.d.ts +3 -2
- package/dist/components/table.d.ts.map +1 -1
- package/dist/components/table.js +78 -63
- package/dist/components/table.js.map +1 -1
- package/dist/diagram-parser.d.ts +17 -3
- package/dist/diagram-parser.d.ts.map +1 -1
- package/dist/diagram-parser.js +17 -3
- package/dist/diagram-parser.js.map +1 -1
- package/dist/examples/list-slot.d.ts +2 -0
- package/dist/examples/list-slot.d.ts.map +1 -0
- package/dist/examples/list-slot.js +14 -0
- package/dist/examples/list-slot.js.map +1 -0
- package/dist/examples/list-with-dropdown.js +2 -4
- package/dist/examples/list-with-dropdown.js.map +1 -1
- package/dist/examples/simple-heatmap.d.ts +2 -0
- package/dist/examples/simple-heatmap.d.ts.map +1 -0
- package/dist/examples/simple-heatmap.js +37 -0
- package/dist/examples/simple-heatmap.js.map +1 -0
- package/dist/examples/simple-progress-bar.d.ts +2 -0
- package/dist/examples/simple-progress-bar.d.ts.map +1 -0
- package/dist/examples/simple-progress-bar.js +36 -0
- package/dist/examples/simple-progress-bar.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/date-picker-widget.d.ts.map +1 -1
- package/dist/internal/date-picker-widget.js +5 -4
- package/dist/internal/date-picker-widget.js.map +1 -1
- package/dist/internal/navigation.d.ts.map +1 -1
- package/dist/internal/navigation.js +7 -2
- package/dist/internal/navigation.js.map +1 -1
- package/dist/internal/providers.d.ts.map +1 -1
- package/dist/internal/providers.js +42 -4
- package/dist/internal/providers.js.map +1 -1
- package/dist/logger.js +6 -1
- package/dist/logger.js.map +1 -1
- package/dist/state.d.ts +2 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +31 -2
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +23 -1
- package/dist/theme.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +6 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -3
- package/src/apis/environment.tsx +6 -0
- package/src/app.tsx +1487 -0
- package/src/assets/default-app-icon.png +0 -0
- package/src/cli.tsx +105 -0
- package/src/components/detail.tsx +32 -22
- package/src/components/dropdown.tsx +3 -2
- package/src/components/footer.tsx +37 -7
- package/src/components/form/checkbox.tsx +2 -17
- package/src/components/form/date-picker.tsx +2 -2
- package/src/components/form/description.tsx +1 -1
- package/src/components/form/dropdown.tsx +22 -3
- package/src/components/form/file-picker.tsx +33 -10
- package/src/components/form/index.tsx +10 -6
- package/src/components/form/password-field.tsx +3 -3
- package/src/components/form/text-area.tsx +31 -6
- package/src/components/form/text-field.tsx +3 -3
- package/src/components/heatmap.tsx +584 -0
- package/src/components/list.tsx +135 -72
- package/src/components/markdown.tsx +30 -0
- package/src/components/metadata.tsx +9 -2
- package/src/components/progress-bar.tsx +112 -0
- package/src/components/table.tsx +88 -71
- package/src/diagram-parser.tsx +17 -3
- package/src/examples/bar-graph-weekly.vitest.tsx +4 -4
- package/src/examples/detail-metadata-showcase.vitest.tsx +12 -12
- package/src/examples/form-basic.vitest.tsx +117 -16
- package/src/examples/graph-bar-chart.vitest.tsx +2 -2
- package/src/examples/graph-row.vitest.tsx +10 -10
- package/src/examples/internal/descendants-rerender.vitest.tsx +94 -46
- package/src/examples/internal/simple-scrollbox.vitest.tsx +38 -14
- package/src/examples/list-dropdown-default.vitest.tsx +78 -58
- package/src/examples/list-slot.tsx +38 -0
- package/src/examples/list-with-detail.vitest.tsx +8 -8
- package/src/examples/list-with-dropdown.tsx +2 -2
- package/src/examples/list-with-dropdown.vitest.tsx +16 -16
- package/src/examples/list-with-sections.vitest.tsx +45 -32
- package/src/examples/simple-detail-table.vitest.tsx +2 -2
- package/src/examples/simple-file-picker.vitest.tsx +1 -1
- package/src/examples/simple-grid.vitest.tsx +27 -53
- package/src/examples/simple-heatmap.tsx +63 -0
- package/src/examples/simple-heatmap.vitest.tsx +88 -0
- package/src/examples/simple-progress-bar.tsx +82 -0
- package/src/examples/simple-progress-bar.vitest.tsx +72 -0
- package/src/examples/table-edge-cases.vitest.tsx +1 -1
- package/src/index.tsx +19 -0
- package/src/internal/date-picker-widget.tsx +23 -12
- package/src/internal/navigation.tsx +7 -2
- package/src/internal/providers.tsx +48 -3
- package/src/logger.tsx +6 -1
- package/src/state.tsx +38 -2
- package/src/theme.tsx +26 -2
- package/src/utils.tsx +6 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// E2E tests for ProgressBar example.
|
|
2
|
+
// Verifies ProgressBar in List.Item.Detail.Metadata updates per selected item.
|
|
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/simple-progress-bar.tsx'],
|
|
13
|
+
cols: 80,
|
|
14
|
+
rows: 24,
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
session?.close()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test('progress bars render in detail metadata and update on selection', async () => {
|
|
23
|
+
const initial = await session.text({
|
|
24
|
+
waitFor: (text) => {
|
|
25
|
+
return text.includes('OpenAI account') && text.includes('37% used')
|
|
26
|
+
},
|
|
27
|
+
timeout: 10000,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(initial).toMatchInlineSnapshot(`
|
|
31
|
+
"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
ProgressBar Metadata ─────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
> Search...
|
|
37
|
+
|
|
38
|
+
›OpenAI account default workspace │ Current session
|
|
39
|
+
Anthropic account research workspace │ █████████░░░░░░░░░░░░░░░░ 37% used
|
|
40
|
+
Google account sandbox workspace │ Resets 9pm (Asia/Bangkok)
|
|
41
|
+
│
|
|
42
|
+
│ Current week (all models)
|
|
43
|
+
│ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ 7% used
|
|
44
|
+
│ Resets Feb 27, 1pm (Asia/Bangkok)
|
|
45
|
+
│
|
|
46
|
+
↑↓ navigate ^k actions │
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
"
|
|
56
|
+
`)
|
|
57
|
+
|
|
58
|
+
expect(initial).toContain('37% used')
|
|
59
|
+
expect(initial).toContain('7% used')
|
|
60
|
+
|
|
61
|
+
await session.press('down')
|
|
62
|
+
|
|
63
|
+
const second = await session.text({
|
|
64
|
+
waitFor: (text) => {
|
|
65
|
+
return text.includes('›Anthropic account') && text.includes('82% used')
|
|
66
|
+
},
|
|
67
|
+
timeout: 10000,
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
expect(second).toContain('46% used')
|
|
71
|
+
expect(second).toContain('Europe/Rome')
|
|
72
|
+
}, 30000)
|
|
@@ -86,6 +86,7 @@ test('inline formatting table renders all rows', async () => {
|
|
|
86
86
|
Done.
|
|
87
87
|
|
|
88
88
|
|
|
89
|
+
esc go back ^k actions powered by termcast.app
|
|
89
90
|
|
|
90
91
|
|
|
91
92
|
|
|
@@ -106,7 +107,6 @@ test('inline formatting table renders all rows', async () => {
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
|
|
109
|
-
esc go back ^k actions powered by termcast.app
|
|
110
110
|
|
|
111
111
|
"
|
|
112
112
|
`)
|
package/src/index.tsx
CHANGED
|
@@ -45,6 +45,10 @@ export type {
|
|
|
45
45
|
ActionPanelSectionProps,
|
|
46
46
|
} from 'termcast/src/components/actions'
|
|
47
47
|
|
|
48
|
+
// Core UI Components - Markdown
|
|
49
|
+
export { Markdown } from 'termcast/src/components/markdown'
|
|
50
|
+
export type { MarkdownProps } from 'termcast/src/components/markdown'
|
|
51
|
+
|
|
48
52
|
// Core UI Components - Detail
|
|
49
53
|
export { Detail } from 'termcast/src/components/detail'
|
|
50
54
|
export type {
|
|
@@ -82,6 +86,21 @@ export type {
|
|
|
82
86
|
BarGraphSeriesProps,
|
|
83
87
|
} from 'termcast/src/components/bar-graph'
|
|
84
88
|
|
|
89
|
+
// Core UI Components - CalendarHeatmap
|
|
90
|
+
export { CalendarHeatmap, Heatmap } from 'termcast/src/components/heatmap'
|
|
91
|
+
export type {
|
|
92
|
+
CalendarHeatmapProps,
|
|
93
|
+
CalendarHeatmapData,
|
|
94
|
+
CalendarHeatmapCellChar,
|
|
95
|
+
HeatmapProps,
|
|
96
|
+
HeatmapData,
|
|
97
|
+
HeatmapCellChar,
|
|
98
|
+
} from 'termcast/src/components/heatmap'
|
|
99
|
+
|
|
100
|
+
// Core UI Components - ProgressBar
|
|
101
|
+
export { ProgressBar } from 'termcast/src/components/progress-bar'
|
|
102
|
+
export type { ProgressBarProps } from 'termcast/src/components/progress-bar'
|
|
103
|
+
|
|
85
104
|
// Form Components
|
|
86
105
|
import {
|
|
87
106
|
Form as FormComponent,
|
|
@@ -377,7 +377,6 @@ export function DatePickerWidget({
|
|
|
377
377
|
enableColors && focus === 'year' ? Theme.primary : undefined,
|
|
378
378
|
marginBottom: 0,
|
|
379
379
|
}}
|
|
380
|
-
onMouseDown={() => setFocus('year')}
|
|
381
380
|
>
|
|
382
381
|
<box
|
|
383
382
|
style={{
|
|
@@ -387,11 +386,17 @@ export function DatePickerWidget({
|
|
|
387
386
|
width: headerWidth,
|
|
388
387
|
}}
|
|
389
388
|
>
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
389
|
+
<box onMouseDown={() => { changeYear(-1); setFocus('year') }}>
|
|
390
|
+
<text fg={focus === 'year' ? Theme.text : Theme.textMuted}>←</text>
|
|
391
|
+
</box>
|
|
392
|
+
<box onMouseDown={() => { setFocus('year') }}>
|
|
393
|
+
<text fg={focus === 'year' ? Theme.text : Theme.textMuted}>
|
|
394
|
+
{String(y)}
|
|
395
|
+
</text>
|
|
396
|
+
</box>
|
|
397
|
+
<box onMouseDown={() => { changeYear(+1); setFocus('year') }}>
|
|
398
|
+
<text fg={focus === 'year' ? Theme.text : Theme.textMuted}>→</text>
|
|
399
|
+
</box>
|
|
395
400
|
</box>
|
|
396
401
|
</box>
|
|
397
402
|
|
|
@@ -404,7 +409,6 @@ export function DatePickerWidget({
|
|
|
404
409
|
enableColors && focus === 'month' ? Theme.primary : undefined,
|
|
405
410
|
marginBottom: 1,
|
|
406
411
|
}}
|
|
407
|
-
onMouseDown={() => setFocus('month')}
|
|
408
412
|
>
|
|
409
413
|
<box
|
|
410
414
|
style={{
|
|
@@ -414,11 +418,17 @@ export function DatePickerWidget({
|
|
|
414
418
|
width: headerWidth,
|
|
415
419
|
}}
|
|
416
420
|
>
|
|
417
|
-
<
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
421
|
+
<box onMouseDown={() => { changeMonth(-1); setFocus('month') }}>
|
|
422
|
+
<text fg={focus === 'month' ? Theme.text : Theme.textMuted}>←</text>
|
|
423
|
+
</box>
|
|
424
|
+
<box onMouseDown={() => { setFocus('month') }}>
|
|
425
|
+
<text fg={focus === 'month' ? Theme.text : Theme.textMuted}>
|
|
426
|
+
{MONTHS[m]}
|
|
427
|
+
</text>
|
|
428
|
+
</box>
|
|
429
|
+
<box onMouseDown={() => { changeMonth(+1); setFocus('month') }}>
|
|
430
|
+
<text fg={focus === 'month' ? Theme.text : Theme.textMuted}>→</text>
|
|
431
|
+
</box>
|
|
422
432
|
</box>
|
|
423
433
|
</box>
|
|
424
434
|
|
|
@@ -485,6 +495,7 @@ export function DatePickerWidget({
|
|
|
485
495
|
setSelected(d)
|
|
486
496
|
setFocus('grid')
|
|
487
497
|
ensureVisibleFor(d)
|
|
498
|
+
onChange?.(d)
|
|
488
499
|
}}
|
|
489
500
|
>
|
|
490
501
|
<text
|
|
@@ -13,6 +13,7 @@ import { CommonProps } from 'termcast/src/utils'
|
|
|
13
13
|
import { useStore, type NavigationStackItem } from 'termcast/src/state'
|
|
14
14
|
import { useIsInFocus } from 'termcast/src/internal/focus-context'
|
|
15
15
|
import { logger } from '../logger'
|
|
16
|
+
import { isAppMode } from '../apis/environment'
|
|
16
17
|
|
|
17
18
|
interface Navigation {
|
|
18
19
|
push: (element: ReactNode, onPop?: () => void) => void
|
|
@@ -172,8 +173,12 @@ export function NavigationProvider(props: NavigationProviderProps): any {
|
|
|
172
173
|
activeSearchInputRef.setText('')
|
|
173
174
|
return
|
|
174
175
|
}
|
|
175
|
-
// At root with no dialogs and no search text - exit the CLI
|
|
176
|
-
|
|
176
|
+
// At root with no dialogs and no search text - exit the CLI.
|
|
177
|
+
// In app mode (standalone desktop app), ESC at root is a no-op
|
|
178
|
+
// since destroying the renderer would kill the entire application.
|
|
179
|
+
if (!isAppMode()) {
|
|
180
|
+
renderer.destroy()
|
|
181
|
+
}
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
})
|
|
@@ -86,6 +86,7 @@ class ErrorBoundaryClass extends Component<
|
|
|
86
86
|
constructor(props: { children: ReactNode }) {
|
|
87
87
|
super(props)
|
|
88
88
|
this.state = { hasError: false, error: null }
|
|
89
|
+
this.reset = this.reset.bind(this)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
|
@@ -100,22 +101,45 @@ class ErrorBoundaryClass extends Component<
|
|
|
100
101
|
})
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
reset(): void {
|
|
105
|
+
// Clear navigation and dialog stacks so the app returns to the root view
|
|
106
|
+
// instead of re-rendering the same crashed component
|
|
107
|
+
useStore.setState({
|
|
108
|
+
navigationStack: [],
|
|
109
|
+
dialogStack: [],
|
|
110
|
+
toast: null,
|
|
111
|
+
toastWithPrimaryAction: false,
|
|
112
|
+
showActionsDialog: false,
|
|
113
|
+
})
|
|
114
|
+
this.setState({ hasError: false, error: null })
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
render(): any {
|
|
104
118
|
if (this.state.hasError) {
|
|
105
|
-
return <ErrorDisplay error={this.state.error} />
|
|
119
|
+
return <ErrorDisplay error={this.state.error} onRetry={this.reset} />
|
|
106
120
|
}
|
|
107
121
|
|
|
108
122
|
return this.props.children
|
|
109
123
|
}
|
|
110
124
|
}
|
|
111
125
|
|
|
112
|
-
function ErrorDisplay({ error }: { error: Error | null }): any {
|
|
126
|
+
function ErrorDisplay({ error, onRetry }: { error: Error | null; onRetry: () => void }): any {
|
|
113
127
|
const theme = useTheme()
|
|
128
|
+
|
|
129
|
+
useKeyboard((evt) => {
|
|
130
|
+
if (evt.name === 'return') {
|
|
131
|
+
onRetry()
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
|
|
114
135
|
return (
|
|
115
|
-
<box padding={2}>
|
|
136
|
+
<box padding={2} flexDirection="column" gap={1}>
|
|
116
137
|
<text fg={theme.error} wrapMode='none'>
|
|
117
138
|
{error?.stack}
|
|
118
139
|
</text>
|
|
140
|
+
<text fg={theme.textMuted}>
|
|
141
|
+
Press Enter to retry
|
|
142
|
+
</text>
|
|
119
143
|
</box>
|
|
120
144
|
)
|
|
121
145
|
}
|
|
@@ -125,6 +149,7 @@ const ErrorBoundary = ErrorBoundaryClass as any
|
|
|
125
149
|
export function TermcastProvider(props: ProvidersProps): any {
|
|
126
150
|
const theme = useTheme()
|
|
127
151
|
const renderer = useRenderer()
|
|
152
|
+
|
|
128
153
|
useKeyboard((key) => {
|
|
129
154
|
if (!renderer) return
|
|
130
155
|
if (key.ctrl && key.name === 'd') {
|
|
@@ -136,6 +161,26 @@ export function TermcastProvider(props: ProvidersProps): any {
|
|
|
136
161
|
}
|
|
137
162
|
})
|
|
138
163
|
|
|
164
|
+
// Cmd+C (super+c): if there's an active selection, copy it to clipboard and clear.
|
|
165
|
+
// Otherwise let the key propagate to the TUI for other handlers.
|
|
166
|
+
// In standalone apps, WezTerm forwards Cmd+C via SendKey so it arrives as super modifier.
|
|
167
|
+
useKeyboard((key) => {
|
|
168
|
+
if (!renderer) return
|
|
169
|
+
if (key.super && key.name === 'c') {
|
|
170
|
+
if (renderer.hasSelection) {
|
|
171
|
+
const selection = renderer.getSelection()
|
|
172
|
+
if (selection) {
|
|
173
|
+
const text = selection.getSelectedText()
|
|
174
|
+
if (text) {
|
|
175
|
+
Clipboard.copy(text)
|
|
176
|
+
renderer.clearSelection()
|
|
177
|
+
key.stopPropagation()
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
139
184
|
return (
|
|
140
185
|
<ErrorBoundary>
|
|
141
186
|
<Suspense fallback={<LoadingFallback />}>
|
package/src/logger.tsx
CHANGED
|
@@ -66,7 +66,12 @@ process.on('uncaughtException', (error: Error) => {
|
|
|
66
66
|
} else {
|
|
67
67
|
logger.error('Uncaught Exception:', serialize(error))
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
// In app mode, don't exit on uncaught exceptions — the error boundary
|
|
70
|
+
// will catch React errors, and crashing the whole app is worse than
|
|
71
|
+
// a broken screen the user can recover from.
|
|
72
|
+
if (process.env.TERMCAST_APP_MODE !== '1') {
|
|
73
|
+
process.exit(1)
|
|
74
|
+
}
|
|
70
75
|
})
|
|
71
76
|
|
|
72
77
|
process.on('unhandledRejection', async (reason: any, promise: Promise<any>) => {
|
package/src/state.tsx
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
1
2
|
import { create } from 'zustand'
|
|
2
3
|
import { type ReactNode } from 'react'
|
|
3
4
|
import type { TextareaRenderable } from '@opentui/core'
|
|
4
5
|
import type { RaycastPackageJson } from './package-json'
|
|
5
6
|
import type { KeyboardKeyEquivalent, KeyboardKeyModifier } from 'termcast/src/keyboard'
|
|
7
|
+
import { getResolvedTheme } from './themes'
|
|
8
|
+
import { logger } from './logger'
|
|
6
9
|
|
|
7
10
|
// Registered action shortcuts for global keyboard handling
|
|
8
11
|
// Stored by ActionPanel, consumed by List/Detail/Form keyboard handlers
|
|
@@ -73,6 +76,10 @@ interface AppState {
|
|
|
73
76
|
shouldAutoExecuteFirstAction: boolean
|
|
74
77
|
// First action title for footer display (set by offscreen ActionPanel)
|
|
75
78
|
firstActionTitle: string
|
|
79
|
+
// Selected List.Dropdown item title shown in List footer (^p label)
|
|
80
|
+
dropdownFooterLabel: string
|
|
81
|
+
// List.Dropdown tooltip shown in footer as ^p label (preferred over dropdownFooterLabel)
|
|
82
|
+
dropdownTooltip: string
|
|
76
83
|
// Flag to show actions dialog via portal
|
|
77
84
|
showActionsDialog: boolean
|
|
78
85
|
// Portal target node for rendering ActionPanel dialog in the overlay area.
|
|
@@ -107,12 +114,41 @@ export const useStore = create<AppState>(() => ({
|
|
|
107
114
|
// Actions state
|
|
108
115
|
shouldAutoExecuteFirstAction: false,
|
|
109
116
|
firstActionTitle: '',
|
|
117
|
+
dropdownFooterLabel: '',
|
|
118
|
+
dropdownTooltip: '',
|
|
110
119
|
showActionsDialog: false,
|
|
111
120
|
actionsPortalTarget: null,
|
|
112
|
-
// Theme state
|
|
113
|
-
currentThemeName: 'nerv',
|
|
121
|
+
// Theme state — TERMCAST_DEFAULT_THEME env var is set by the app launcher
|
|
122
|
+
currentThemeName: process.env.TERMCAST_DEFAULT_THEME || 'nerv',
|
|
114
123
|
// Active search input ref
|
|
115
124
|
activeSearchInputRef: null,
|
|
116
125
|
// Registered action shortcuts
|
|
117
126
|
registeredActionShortcuts: [],
|
|
118
127
|
}))
|
|
128
|
+
|
|
129
|
+
// Sync WezTerm's window background with the active termcast theme.
|
|
130
|
+
// When the theme changes, rewrite the background color in wezterm.lua.
|
|
131
|
+
// WezTerm auto-reloads the config on file change, updating the window edges/padding.
|
|
132
|
+
// The config path is passed from the launcher via TERMCAST_WEZTERM_CONFIG env var.
|
|
133
|
+
const weztermConfigPath = process.env.TERMCAST_WEZTERM_CONFIG
|
|
134
|
+
if (weztermConfigPath) {
|
|
135
|
+
useStore.subscribe((state, prevState) => {
|
|
136
|
+
if (state.currentThemeName === prevState.currentThemeName) {
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const theme = getResolvedTheme(state.currentThemeName)
|
|
141
|
+
const content = fs.readFileSync(weztermConfigPath, 'utf-8')
|
|
142
|
+
// Replace the background hex in: config.colors = { background = '#xxxxxx' }
|
|
143
|
+
const updated = content.replace(
|
|
144
|
+
/background\s*=\s*'#[0-9a-fA-F]{6}'/,
|
|
145
|
+
`background = '${theme.background}'`,
|
|
146
|
+
)
|
|
147
|
+
if (updated !== content) {
|
|
148
|
+
fs.writeFileSync(weztermConfigPath, updated)
|
|
149
|
+
}
|
|
150
|
+
} catch (e) {
|
|
151
|
+
logger.log('Failed to update wezterm config background:', e)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
}
|
package/src/theme.tsx
CHANGED
|
@@ -28,7 +28,8 @@ export function loadPersistedTheme(): string {
|
|
|
28
28
|
} catch {
|
|
29
29
|
// Ignore errors on load
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
// TERMCAST_DEFAULT_THEME is set by the app launcher to override the default theme
|
|
32
|
+
return process.env.TERMCAST_DEFAULT_THEME || defaultThemeName
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export function persistTheme(name: string): void {
|
|
@@ -58,6 +59,30 @@ export function getMarkdownSyntaxStyle(): SyntaxStyle {
|
|
|
58
59
|
return SyntaxStyle.fromStyles(getSyntaxTheme(themeName))
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
// Resolve a visible but subtle hover background for interactive rows.
|
|
63
|
+
// Some themes map background and backgroundPanel to the same value.
|
|
64
|
+
export function getInteractiveHoverBackground(theme: ResolvedTheme): string {
|
|
65
|
+
const normalize = (color: string): string => {
|
|
66
|
+
return color.toLowerCase()
|
|
67
|
+
}
|
|
68
|
+
const background = normalize(theme.background)
|
|
69
|
+
|
|
70
|
+
if (normalize(theme.backgroundElement) !== background) {
|
|
71
|
+
return theme.backgroundElement
|
|
72
|
+
}
|
|
73
|
+
if (normalize(theme.backgroundPanel) !== background) {
|
|
74
|
+
return theme.backgroundPanel
|
|
75
|
+
}
|
|
76
|
+
if (normalize(theme.borderSubtle) !== background) {
|
|
77
|
+
return theme.borderSubtle
|
|
78
|
+
}
|
|
79
|
+
if (normalize(theme.border) !== background) {
|
|
80
|
+
return theme.border
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return theme.primary
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
// Shared color palette for all chart components (Graph, BarChart, BarGraph).
|
|
62
87
|
// Order: accent, info, success, warning, error, secondary, primary (cycles with %).
|
|
63
88
|
export function getThemePalette(theme: ResolvedTheme): string[] {
|
|
@@ -80,4 +105,3 @@ export const markdownSyntaxStyle = new Proxy({} as SyntaxStyle, {
|
|
|
80
105
|
},
|
|
81
106
|
})
|
|
82
107
|
|
|
83
|
-
|
package/src/utils.tsx
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { logger } from './logger'
|
|
15
15
|
import { useStore } from './state'
|
|
16
16
|
import { initializeTheme } from './theme'
|
|
17
|
+
import { isAppMode } from './apis/environment'
|
|
17
18
|
|
|
18
19
|
export interface RenderWithProvidersOptions {
|
|
19
20
|
extensionName?: string
|
|
@@ -59,7 +60,11 @@ export async function renderWithProviders(
|
|
|
59
60
|
|
|
60
61
|
const renderer = await createCliRenderer({
|
|
61
62
|
onDestroy: () => {
|
|
62
|
-
process.
|
|
63
|
+
// In app mode, destroying the renderer should not kill the process.
|
|
64
|
+
// The app launcher manages the process lifecycle.
|
|
65
|
+
if (!isAppMode()) {
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
63
68
|
},
|
|
64
69
|
})
|
|
65
70
|
|