@meistrari/tela-cli 0.6.0 → 0.7.4

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 (59) hide show
  1. package/README.md +56 -106
  2. package/dist/commands/auth.d.ts +2 -0
  3. package/dist/commands/context.d.ts +2 -0
  4. package/dist/commands/doctor-auth.d.ts +2 -0
  5. package/dist/commands/env.d.ts +2 -0
  6. package/dist/credentials.d.ts +2 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +7027 -0
  9. package/dist/output.d.ts +13 -0
  10. package/dist/program.d.ts +2 -0
  11. package/package.json +17 -24
  12. package/scripts/install-skills.cjs +127 -0
  13. package/skills/tela-cli/SKILL.md +20 -0
  14. package/skills/tela-cli/references/agent.md +111 -0
  15. package/cli.ts +0 -25
  16. package/postinstall.sh +0 -19
  17. package/src/agent-api.ts +0 -300
  18. package/src/assets.d.ts +0 -4
  19. package/src/auth.ts +0 -432
  20. package/src/claude.ts +0 -101
  21. package/src/commands/doctor.ts +0 -35
  22. package/src/commands/feedback.ts +0 -701
  23. package/src/commands/metadata.ts +0 -242
  24. package/src/commands/push.ts +0 -208
  25. package/src/commands/run.ts +0 -1699
  26. package/src/commands/status.ts +0 -109
  27. package/src/commands/sync.ts +0 -245
  28. package/src/commit-message.ts +0 -69
  29. package/src/config.ts +0 -799
  30. package/src/diagnostics.ts +0 -813
  31. package/src/errors.ts +0 -122
  32. package/src/forgejo.ts +0 -233
  33. package/src/git.ts +0 -457
  34. package/src/ink/assets/160092__jorickhoofd__dog-bark-1.mp3 +0 -0
  35. package/src/ink/bark.tsx +0 -240
  36. package/src/ink/components.tsx +0 -177
  37. package/src/ink/runtime.tsx +0 -89
  38. package/src/ink/screens/feedback-screen.tsx +0 -394
  39. package/src/ink/screens/push-screen.tsx +0 -231
  40. package/src/ink/screens/run-screen.tsx +0 -291
  41. package/src/ink/screens/setup/breadcrumb.tsx +0 -23
  42. package/src/ink/screens/setup/setup-screen.tsx +0 -611
  43. package/src/ink/screens/setup/step-frame.tsx +0 -30
  44. package/src/ink/screens/setup/steps/done-step.tsx +0 -28
  45. package/src/ink/screens/setup/steps/environment-step.tsx +0 -22
  46. package/src/ink/screens/setup/steps/organization-step.tsx +0 -34
  47. package/src/ink/screens/setup/steps/review-step.tsx +0 -77
  48. package/src/ink/screens/setup/steps/running-step.tsx +0 -36
  49. package/src/ink/screens/setup/steps/ssh-key-step.tsx +0 -47
  50. package/src/ink/screens/setup/steps/text-field-step.tsx +0 -17
  51. package/src/ink/screens/setup/steps.ts +0 -156
  52. package/src/ink/screens/setup/types.ts +0 -65
  53. package/src/ink/screens/sync-screen.tsx +0 -282
  54. package/src/process.ts +0 -251
  55. package/src/program.ts +0 -2105
  56. package/src/runtime.ts +0 -147
  57. package/src/types.ts +0 -165
  58. package/src/ui.ts +0 -295
  59. package/src/version.ts +0 -3
@@ -1,177 +0,0 @@
1
- import React, { useEffect, useMemo, useState, type ReactNode } from 'react'
2
- import { Box, Text } from 'ink'
3
- import { getTelaAsciiHeader } from '../ui.ts'
4
-
5
- export interface AppFrameProps {
6
- title: string
7
- subtitle?: string
8
- footer?: string
9
- status?: string
10
- mascotRows?: readonly string[]
11
- children: ReactNode
12
- }
13
-
14
- export function AppFrame(props: AppFrameProps): React.ReactElement {
15
- const mascot = props.mascotRows
16
- const header = useMemo(() => getTelaAsciiHeader(), [])
17
-
18
- return (
19
- <Box flexDirection="column" paddingX={2} paddingY={1}>
20
- <Box flexDirection="column">
21
- {header.map((line, index) => (
22
- <Text key={`header-${index}`} color="white">
23
- {line}
24
- </Text>
25
- ))}
26
- </Box>
27
- <Box flexDirection="column" marginTop={1}>
28
- <Text color="white" bold>Tela CLI · {props.title}</Text>
29
- {props.subtitle ? <Text dimColor>{props.subtitle}</Text> : null}
30
- {props.status ? <Text color="yellow">{props.status}</Text> : null}
31
- </Box>
32
- {mascot ? (
33
- <Box flexDirection="column" marginTop={1}>
34
- {mascot.map((row, index) => (
35
- <Box key={`mascot-${index}`} flexDirection="row">
36
- {row.split('').map((cell, cellIndex) => {
37
- const level = Number.parseInt(cell, 16)
38
- const channel = Math.round((level / 15) * 255)
39
- const colorValue = `#${channel.toString(16).padStart(2, '0').repeat(3)}`
40
- return (
41
- <Text key={`pixel-${index}-${cellIndex}`} backgroundColor={colorValue}>
42
- {' '}
43
- </Text>
44
- )
45
- })}
46
- </Box>
47
- ))}
48
- </Box>
49
- ) : null}
50
- <Box flexDirection="column" marginTop={1}>
51
- {props.children}
52
- </Box>
53
- {props.footer ? (
54
- <Box marginTop={1}>
55
- <Text dimColor>{props.footer}</Text>
56
- </Box>
57
- ) : null}
58
- </Box>
59
- )
60
- }
61
-
62
- export function Section(props: {
63
- title: string
64
- children: ReactNode
65
- }): React.ReactElement {
66
- return (
67
- <Box flexDirection="column" marginTop={1}>
68
- <Text color="cyan">{props.title}</Text>
69
- <Box flexDirection="column" marginLeft={2}>
70
- {props.children}
71
- </Box>
72
- </Box>
73
- )
74
- }
75
-
76
- export function KeyHints(props: {
77
- items: string[]
78
- }): React.ReactElement {
79
- return (
80
- <Text dimColor>{props.items.join(' ')}</Text>
81
- )
82
- }
83
-
84
- export function SelectList<T>(props: {
85
- items: Array<{
86
- key: T
87
- label: string
88
- hint?: string
89
- selected?: boolean
90
- }>
91
- currentIndex: number
92
- multiselect?: boolean
93
- }): React.ReactElement {
94
- return (
95
- <Box flexDirection="column">
96
- {props.items.map((item, index) => {
97
- const focused = index === props.currentIndex
98
- const prefix = focused ? '›' : ' '
99
- const selected = props.multiselect
100
- ? (item.selected ? '[x]' : '[ ]')
101
- : ''
102
-
103
- return (
104
- <Text key={String(index)} color={focused ? 'greenBright' : undefined}>
105
- {`${prefix} ${selected} ${item.label}`.trimEnd()}
106
- {item.hint ? <Text dimColor>{` — ${item.hint}`}</Text> : null}
107
- </Text>
108
- )
109
- })}
110
- </Box>
111
- )
112
- }
113
-
114
- export function TextEntry(props: {
115
- label: string
116
- value: string
117
- placeholder?: string
118
- error?: string
119
- masked?: boolean
120
- }): React.ReactElement {
121
- const displayValue = props.value.length > 0
122
- ? (props.masked ? '*'.repeat(props.value.length) : props.value)
123
- : (props.placeholder ? `<${props.placeholder}>` : '')
124
-
125
- return (
126
- <Box flexDirection="column">
127
- <Text>{props.label}</Text>
128
- <Text color="greenBright">{displayValue || ' '}</Text>
129
- {props.error ? <Text color="red">{props.error}</Text> : null}
130
- </Box>
131
- )
132
- }
133
-
134
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
135
-
136
- export function SpinnerText(props: {
137
- label: string
138
- active?: boolean
139
- }): React.ReactElement {
140
- const [frameIndex, setFrameIndex] = useState(0)
141
-
142
- useEffect(() => {
143
- if (props.active === false) {
144
- return
145
- }
146
-
147
- const interval = setInterval(() => {
148
- setFrameIndex(current => (current + 1) % SPINNER_FRAMES.length)
149
- }, 80)
150
-
151
- return () => {
152
- clearInterval(interval)
153
- }
154
- }, [props.active])
155
-
156
- return (
157
- <Text color="yellow">
158
- {`${SPINNER_FRAMES[frameIndex]} ${props.label}`}
159
- </Text>
160
- )
161
- }
162
-
163
- export function Callout(props: {
164
- tone: 'info' | 'warn' | 'error' | 'success'
165
- children: ReactNode
166
- }): React.ReactElement {
167
- const color = {
168
- info: 'blueBright',
169
- warn: 'yellowBright',
170
- error: 'redBright',
171
- success: 'greenBright',
172
- } as const
173
-
174
- return (
175
- <Text color={color[props.tone]}>{props.children}</Text>
176
- )
177
- }
@@ -1,89 +0,0 @@
1
- import React from 'react'
2
- import { render, type Instance, type RenderOptions } from 'ink'
3
-
4
- export interface CommandScreenController<T> {
5
- resolve(value: T, delayMs?: number): void
6
- reject(error: unknown, delayMs?: number): void
7
- }
8
-
9
- export interface RenderCommandScreenOptions extends RenderOptions {
10
- /**
11
- * When true, the Ink instance erases its rendered output before unmounting
12
- * so the final frame is not persisted to the terminal scrollback. Useful
13
- * for transient screens like the bark intro that hand off to the next
14
- * screen without leaving a visual residue.
15
- */
16
- clearOnExit?: boolean
17
- }
18
-
19
- export function renderCommandScreen<T>(
20
- build: (controller: CommandScreenController<T>) => React.ReactElement,
21
- options: RenderCommandScreenOptions = {},
22
- ): Promise<T> {
23
- const { clearOnExit, ...renderOptions } = options
24
-
25
- return new Promise<T>((resolve, reject) => {
26
- let settled = false
27
- let instance: Instance | undefined
28
-
29
- const teardown = () => {
30
- if (clearOnExit) {
31
- instance?.clear()
32
- }
33
- instance?.unmount()
34
- }
35
-
36
- const settle = (
37
- handler: (value: T) => void | never,
38
- payload: T,
39
- delayMs = 0,
40
- ) => {
41
- if (settled) {
42
- return
43
- }
44
-
45
- settled = true
46
- const finish = () => {
47
- teardown()
48
- handler(payload)
49
- }
50
-
51
- if (delayMs > 0) {
52
- setTimeout(finish, delayMs)
53
- return
54
- }
55
-
56
- queueMicrotask(finish)
57
- }
58
-
59
- const controller: CommandScreenController<T> = {
60
- resolve(value, delayMs) {
61
- settle(resolve, value, delayMs)
62
- },
63
- reject(error, delayMs) {
64
- if (settled) {
65
- return
66
- }
67
-
68
- settled = true
69
- const finish = () => {
70
- teardown()
71
- reject(error)
72
- }
73
-
74
- if (delayMs && delayMs > 0) {
75
- setTimeout(finish, delayMs)
76
- return
77
- }
78
-
79
- queueMicrotask(finish)
80
- },
81
- }
82
-
83
- instance = render(build(controller), {
84
- exitOnCtrlC: true,
85
- patchConsole: false,
86
- ...renderOptions,
87
- })
88
- })
89
- }
@@ -1,394 +0,0 @@
1
- import React, { useEffect, useMemo, useState } from 'react'
2
- import { Box, Text, useInput } from 'ink'
3
- import type {
4
- FeedbackData,
5
- FeedbackEmptyState,
6
- FeedbackEntry,
7
- FeedbackSelectionMode,
8
- } from '../../commands/feedback.ts'
9
- import {
10
- buildClaudeFeedbackPrompt,
11
- buildFeedbackNextActions,
12
- buildFeedbackReviewModel,
13
- describeFeedbackEmptyState,
14
- describeFeedbackSelectionMode,
15
- formatFeedbackSelectionSummary,
16
- getFeedbackEntryTitle,
17
- } from '../../commands/feedback.ts'
18
- import { AppFrame, Callout, KeyHints, Section } from '../components.tsx'
19
- import type { CommandScreenController } from '../runtime.tsx'
20
-
21
- export interface FeedbackSelectionResult {
22
- selectedFeedbacks: FeedbackEntry[]
23
- prompt?: string
24
- launchedClaude: boolean
25
- selectionMode: FeedbackSelectionMode
26
- }
27
-
28
- export interface FeedbackSelectionScreenProps {
29
- controller: CommandScreenController<FeedbackSelectionResult>
30
- data: FeedbackData
31
- mode: FeedbackSelectionMode
32
- }
33
-
34
- type FeedbackPhase = 'browse' | 'confirm'
35
-
36
- function renderCatalog(
37
- entries: FeedbackEntry[],
38
- input: {
39
- currentEntryId?: string
40
- selectedIds: Set<string>
41
- selectionEditable: boolean
42
- selectionVisible: boolean
43
- },
44
- ): React.ReactElement {
45
- const actionable = entries.filter(entry => entry.actionable)
46
- const informational = entries.filter(entry => !entry.actionable)
47
-
48
- const renderEntry = (entry: FeedbackEntry): React.ReactElement => {
49
- const focused = entry.id === input.currentEntryId
50
- const marker = focused ? '›' : ' '
51
- const checkbox = input.selectionVisible
52
- ? entry.actionable
53
- ? input.selectedIds.has(entry.id) ? '[x]' : '[ ]'
54
- : '[-]'
55
- : ''
56
-
57
- return (
58
- <Text key={entry.id} color={focused ? 'greenBright' : undefined}>
59
- {`${marker} ${checkbox} #${entry.number} [${entry.feedback.status}] ${getFeedbackEntryTitle(entry)}`.trimEnd()}
60
- <Text dimColor>{` — ${entry.feedback.reason || '<empty>'}`}</Text>
61
- </Text>
62
- )
63
- }
64
-
65
- return (
66
- <Box flexDirection="column">
67
- {actionable.length > 0 ? (
68
- <>
69
- <Text color="cyan">Actionable</Text>
70
- {actionable.map(renderEntry)}
71
- </>
72
- ) : null}
73
- {informational.length > 0 ? (
74
- <>
75
- {actionable.length > 0 ? <Text> </Text> : null}
76
- <Text color="cyan">Informational</Text>
77
- {informational.map(renderEntry)}
78
- </>
79
- ) : null}
80
- {entries.length === 0 ? <Text dimColor>No feedback entries found for this commit.</Text> : null}
81
- {!input.selectionEditable && input.selectionVisible ? (
82
- <Text dimColor>Selection is locked for this review mode.</Text>
83
- ) : null}
84
- </Box>
85
- )
86
- }
87
-
88
- function buildBrowseFooter(input: {
89
- mode: FeedbackSelectionMode
90
- selectionEditable: boolean
91
- }): string {
92
- if (input.mode === 'list') {
93
- return '↑/↓ move · Enter finish review · Esc exit'
94
- }
95
-
96
- if (input.selectionEditable) {
97
- return '↑/↓ move · Space/x toggle · a select-all · n clear · Enter continue/finish · Esc exit'
98
- }
99
-
100
- return '↑/↓ move · Enter continue · Esc exit'
101
- }
102
-
103
- function formatSelectedTitles(entries: FeedbackEntry[]): string[] {
104
- return entries.map(entry => `#${entry.number} ${getFeedbackEntryTitle(entry)}`)
105
- }
106
-
107
- function emptyStateTone(emptyState: FeedbackEmptyState): 'info' | 'warn' {
108
- return emptyState === 'none' ? 'warn' : 'info'
109
- }
110
-
111
- export function FeedbackSelectionScreen(props: FeedbackSelectionScreenProps): React.ReactElement {
112
- const review = useMemo(() => buildFeedbackReviewModel(props.data), [props.data])
113
- const [phase, setPhase] = useState<FeedbackPhase>('browse')
114
- const [cursor, setCursor] = useState(0)
115
- const [selectedIds, setSelectedIds] = useState<Set<string>>(() => {
116
- if (props.mode === 'all' || props.mode === 'default') {
117
- return new Set(props.data.actionableEntries.map(entry => entry.id))
118
- }
119
-
120
- return new Set<string>()
121
- })
122
-
123
- const currentEntry = props.data.entries[cursor] ?? props.data.entries[0]
124
- const selectionEditable = props.mode === 'select' || props.mode === 'default'
125
- const selectionVisible = props.mode !== 'list' && review.emptyState === 'none'
126
- const selectedFeedbacks = useMemo(() => {
127
- return props.data.actionableEntries.filter(entry => selectedIds.has(entry.id))
128
- }, [props.data.actionableEntries, selectedIds])
129
- const prompt = selectedFeedbacks.length > 0
130
- ? buildClaudeFeedbackPrompt({
131
- repoRoot: props.data.repoRoot,
132
- organizationName: props.data.organizationName,
133
- repository: props.data.repository,
134
- commitHash: props.data.commitHash,
135
- selectedFeedbacks,
136
- })
137
- : undefined
138
-
139
- useInput((input, key) => {
140
- if (phase === 'browse') {
141
- if ((key.downArrow || input === 'j') && props.data.entries.length > 0) {
142
- setCursor(current => Math.min(current + 1, props.data.entries.length - 1))
143
- return
144
- }
145
-
146
- if ((key.upArrow || input === 'k') && props.data.entries.length > 0) {
147
- setCursor(current => Math.max(current - 1, 0))
148
- return
149
- }
150
-
151
- if (key.escape) {
152
- props.controller.resolve({
153
- selectedFeedbacks: [],
154
- launchedClaude: false,
155
- selectionMode: props.mode,
156
- })
157
- return
158
- }
159
-
160
- if (selectionEditable && selectionVisible && (input === ' ' || input === 'x')) {
161
- if (!currentEntry?.actionable) {
162
- return
163
- }
164
-
165
- setSelectedIds(current => {
166
- const next = new Set(current)
167
- if (next.has(currentEntry.id)) {
168
- next.delete(currentEntry.id)
169
- }
170
- else {
171
- next.add(currentEntry.id)
172
- }
173
- return next
174
- })
175
- return
176
- }
177
-
178
- if (selectionEditable && selectionVisible && input === 'a') {
179
- setSelectedIds(new Set(props.data.actionableEntries.map(entry => entry.id)))
180
- return
181
- }
182
-
183
- if (selectionEditable && selectionVisible && input === 'n') {
184
- setSelectedIds(new Set())
185
- return
186
- }
187
-
188
- if (key.return) {
189
- if (props.mode === 'list' || review.emptyState !== 'none' || selectedFeedbacks.length === 0) {
190
- props.controller.resolve({
191
- selectedFeedbacks,
192
- launchedClaude: false,
193
- selectionMode: props.mode,
194
- })
195
- return
196
- }
197
-
198
- setPhase('confirm')
199
- }
200
- return
201
- }
202
-
203
- if (phase === 'confirm') {
204
- if (key.escape) {
205
- setPhase('browse')
206
- return
207
- }
208
-
209
- if (key.return) {
210
- props.controller.resolve({
211
- selectedFeedbacks,
212
- prompt,
213
- launchedClaude: false,
214
- selectionMode: props.mode,
215
- })
216
- }
217
- }
218
- })
219
-
220
- return (
221
- <AppFrame
222
- title="Feedback"
223
- subtitle="Review commit feedback, refine the selection, and preview the exact Claude task."
224
- footer={phase === 'browse' ? buildBrowseFooter({ mode: props.mode, selectionEditable }) : 'Enter launch Claude · Esc back'}
225
- >
226
- {phase === 'browse' ? (
227
- <Box flexDirection="row" gap={2}>
228
- <Box flexDirection="column" width={56}>
229
- <Section title="Catalog">
230
- {renderCatalog(props.data.entries, {
231
- currentEntryId: currentEntry?.id,
232
- selectedIds,
233
- selectionEditable,
234
- selectionVisible,
235
- })}
236
- </Section>
237
- </Box>
238
- <Box flexDirection="column" flexGrow={1}>
239
- <Section title="Review summary">
240
- <Text>Repository: {props.data.organizationName}/{props.data.repository}</Text>
241
- <Text>Commit: {props.data.commitHash}</Text>
242
- <Text>Mode: {describeFeedbackSelectionMode(props.mode)}</Text>
243
- <Text>Feedbacks: {review.summary.totalFeedbacks}</Text>
244
- <Text>Actionable: {review.summary.actionableCount}</Text>
245
- <Text>Informational: {review.summary.informationalCount}</Text>
246
- </Section>
247
-
248
- <Section title="Detail">
249
- {currentEntry ? (
250
- <>
251
- <Text>Focus: #{currentEntry.number}</Text>
252
- <Text>Status: {currentEntry.feedback.status}</Text>
253
- <Text>Reason: {currentEntry.feedback.reason || '<empty>'}</Text>
254
- <Text>Suggestion: {currentEntry.feedback.suggestion.trim() || '<none>'}</Text>
255
- <Text>Actionable: {currentEntry.actionable ? 'yes' : 'no'}</Text>
256
- </>
257
- ) : (
258
- <Text>No feedback entries found.</Text>
259
- )}
260
- </Section>
261
-
262
- <Section title="Selection">
263
- <Text>{`Selected actionable suggestions: ${selectedFeedbacks.length}`}</Text>
264
- <Text>{`Selection mode: ${describeFeedbackSelectionMode(props.mode)}`}</Text>
265
- </Section>
266
-
267
- {review.emptyState !== 'none' ? (
268
- <Callout tone={emptyStateTone(review.emptyState)}>
269
- {`${describeFeedbackEmptyState(review.emptyState)} Press Enter to finish the review.`}
270
- </Callout>
271
- ) : null}
272
- {review.emptyState === 'none' && selectionVisible && selectedFeedbacks.length === 0 ? (
273
- <Callout tone="warn">
274
- No suggestions are selected. Press Enter to finish without launching Claude, or choose suggestions first.
275
- </Callout>
276
- ) : null}
277
- </Box>
278
- </Box>
279
- ) : null}
280
-
281
- {phase === 'confirm' ? (
282
- <>
283
- <Section title="Selected suggestions">
284
- {formatSelectedTitles(selectedFeedbacks).map(line => (
285
- <Text key={line}>{line}</Text>
286
- ))}
287
- </Section>
288
- <Section title="Selection summary">
289
- {formatFeedbackSelectionSummary({
290
- selectionMode: props.mode,
291
- selectedFeedbacks,
292
- }).split('\n').map((line, index) => (
293
- <Text key={`selection-${index}`}>{line}</Text>
294
- ))}
295
- </Section>
296
- <Section title="Claude task">
297
- {prompt?.split('\n').map((line, index) => (
298
- <Text key={`prompt-${index}`}>{line}</Text>
299
- ))}
300
- </Section>
301
- <Callout tone="info">
302
- {`Claude will receive ${selectedFeedbacks.length} selected suggestion${selectedFeedbacks.length === 1 ? '' : 's'}.`}
303
- </Callout>
304
- </>
305
- ) : null}
306
-
307
- {phase === 'browse' ? (
308
- <KeyHints items={[
309
- `Commit ${props.data.commitHash}`,
310
- `${review.summary.totalFeedbacks} feedback item${review.summary.totalFeedbacks === 1 ? '' : 's'}`,
311
- props.mode === 'list' ? 'Review only' : `${selectedFeedbacks.length} selected`,
312
- ]} />
313
- ) : null}
314
- </AppFrame>
315
- )
316
- }
317
-
318
- export function FeedbackClaudeHandoffScreen(props: {
319
- controller: CommandScreenController<void>
320
- repoRoot: string
321
- commitHash: string
322
- selectedFeedbacks: FeedbackEntry[]
323
- launchDelayMs?: number
324
- }): React.ReactElement {
325
- useEffect(() => {
326
- props.controller.resolve(undefined, props.launchDelayMs ?? 650)
327
- }, [props.controller, props.launchDelayMs])
328
-
329
- return (
330
- <AppFrame
331
- title="Feedback"
332
- subtitle="Handing off to native Claude. Tela will resume after Claude exits."
333
- status="Launching Claude review…"
334
- >
335
- <Section title="Overview">
336
- <Text>Repo root: {props.repoRoot}</Text>
337
- <Text>Commit: {props.commitHash}</Text>
338
- <Text>Selected suggestions: {props.selectedFeedbacks.length}</Text>
339
- </Section>
340
- <Section title="Selected suggestions">
341
- {formatSelectedTitles(props.selectedFeedbacks).map(line => (
342
- <Text key={line}>{line}</Text>
343
- ))}
344
- </Section>
345
- <Callout tone="info">
346
- Claude will take over this terminal to apply the selected feedback. Exit Claude to return to Tela for the outcome review.
347
- </Callout>
348
- </AppFrame>
349
- )
350
- }
351
-
352
- export function FeedbackOutcomeScreen(props: {
353
- controller: CommandScreenController<void>
354
- commitHash: string
355
- selectionMode: FeedbackSelectionMode
356
- diffStat: string
357
- selectedFeedbacks: FeedbackEntry[]
358
- }): React.ReactElement {
359
- useEffect(() => {
360
- props.controller.resolve(undefined, 40)
361
- }, [props.controller])
362
-
363
- return (
364
- <AppFrame
365
- title="Feedback"
366
- subtitle="Claude finished applying the selected feedback suggestions."
367
- >
368
- <Section title="Summary">
369
- <Text>Commit: {props.commitHash}</Text>
370
- <Text>Mode: {describeFeedbackSelectionMode(props.selectionMode)}</Text>
371
- <Text>Selected suggestions: {props.selectedFeedbacks.length}</Text>
372
- </Section>
373
- <Section title="Selected suggestions">
374
- {formatSelectedTitles(props.selectedFeedbacks).map(line => (
375
- <Text key={line}>{line}</Text>
376
- ))}
377
- </Section>
378
- <Section title="Local diff summary">
379
- {props.diffStat.split('\n').map((line, index) => (
380
- <Text key={`diff-${index}`}>{line}</Text>
381
- ))}
382
- </Section>
383
- <Section title="Next actions">
384
- {buildFeedbackNextActions({
385
- launchedClaude: true,
386
- actionableCount: props.selectedFeedbacks.length,
387
- emptyState: 'none',
388
- }).map(action => (
389
- <Text key={action}>{action}</Text>
390
- ))}
391
- </Section>
392
- </AppFrame>
393
- )
394
- }