@@ -28,6 +28,7 @@ import { ReviewScreen } from './components/ReviewScreen'
2828import { CommandPalette } from './components/CommandPalette'
2929import { HotkeyCheatsheet } from './components/HotkeyCheatsheet'
3030import { NewProjectScreen } from './components/NewProjectScreen'
31+ import { ReportIssueScreen , onOpenReportIssue , type OpenReportIssueDetail } from './components/ReportIssueScreen'
3132import iconUrl from '../../resources/icon.png'
3233import { PerfMonitorHUD } from './components/PerfMonitorHUD'
3334import { focusTerminalById } from './components/XTerminal'
@@ -187,6 +188,8 @@ export default function App(): JSX.Element {
187188 const [ showPerfMonitor , setShowPerfMonitor ] = useState ( false )
188189 const [ showHotkeyCheatsheet , setShowHotkeyCheatsheet ] = useState ( false )
189190 const [ showNewProject , setShowNewProject ] = useState ( false )
191+ const [ reportIssueState , setReportIssueState ] = useState < OpenReportIssueDetail | null > ( null )
192+ const [ crashedTabIds , setCrashedTabIds ] = useState < ReadonlySet < string > > ( ( ) => new Set ( ) )
190193 // `theme` and `defaultAgent` are both seeded at init, so we track
191194 // explicit confirmation separately for the onboarding step checkmarks.
192195 const [ themeChosen , setThemeChosen ] = useState ( false )
@@ -271,6 +274,46 @@ const setQuestStep = useCallback((next: QuestStep) => {
271274 return cleanup
272275 } , [ ] )
273276
277+ // Report Issue — triggered from the Help menu, the sidebar, the
278+ // Settings Support section, and the openReportIssueFor() helper (used
279+ // by the error boundary). Closes any open overlay (Settings, hotkey
280+ // cheatsheet) so the full-screen report takes over the center area.
281+ useEffect ( ( ) => {
282+ const openReport = ( detail : OpenReportIssueDetail ) : void => {
283+ setShowSettings ( false )
284+ setShowHotkeyCheatsheet ( false )
285+ setReportIssueState ( detail )
286+ }
287+ const cleanupMenu = window . api . onOpenReportIssue ( ( ) => openReport ( { } ) )
288+ const cleanupBus = onOpenReportIssue ( ( detail ) => openReport ( detail ) )
289+ return ( ) => {
290+ cleanupMenu ( )
291+ cleanupBus ( )
292+ }
293+ } , [ ] )
294+
295+ // Debug: Crash Focused Tab (Help menu → for testing the ErrorBoundary).
296+ // Finds the active worktree's active pane and flips its active tab into
297+ // a throwing render. The boundary catches it inside the tab.
298+ useEffect ( ( ) => {
299+ return window . api . onDebugCrashFocusedTab ( ( ) => {
300+ const wtPath = activeWorktreeId
301+ if ( ! wtPath ) return
302+ const tree = panes [ wtPath ]
303+ if ( ! tree ) return
304+ const leaves = getLeaves ( tree )
305+ const paneId = activePaneId [ wtPath ] ?? leaves [ 0 ] ?. id
306+ const leaf = leaves . find ( ( l ) => l . id === paneId ) ?? leaves [ 0 ]
307+ const tabId = leaf ?. activeTabId
308+ if ( ! tabId ) return
309+ setCrashedTabIds ( ( prev ) => {
310+ const next = new Set ( prev )
311+ next . add ( tabId )
312+ return next
313+ } )
314+ } )
315+ } , [ activeWorktreeId , panes , activePaneId ] )
316+
274317 // Trigger a full PR refresh in main. Used by the sidebar refresh button
275318 // and after worktree creation/removal.
276319 const fetchAllPRStatuses = useCallback ( ( ) => {
@@ -975,7 +1018,7 @@ const setQuestStep = useCallback((next: QuestStep) => {
9751018 if ( ! paneTree ) return null
9761019 const leaves = getLeaves ( paneTree )
9771020 if ( leaves . length === 0 || ! leaves . some ( ( l ) => l . tabs . length > 0 ) ) return null
978- const isVisible = ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && wt . path === activeWorktreeId && ! pendingDeletionByPath [ wt . path ]
1021+ const isVisible = ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && wt . path === activeWorktreeId && ! pendingDeletionByPath [ wt . path ]
9791022 return (
9801023 < div
9811024 key = { wt . path }
@@ -992,6 +1035,7 @@ const setQuestStep = useCallback((next: QuestStep) => {
9921035 statuses = { statuses }
9931036 shellActivity = { shellActivity }
9941037 visible = { isVisible }
1038+ crashedTabIds = { crashedTabIds }
9951039 nameAgentSessions = { nameAgentSessions }
9961040 onSelectTab = { handleSelectTab }
9971041 onAddTab = { handleAddTerminalTab }
@@ -1019,6 +1063,15 @@ const setQuestStep = useCallback((next: QuestStep) => {
10191063 defaultRepoRoot = { activeWorktreeId ? worktreeRepoByPath [ activeWorktreeId ] : undefined }
10201064 />
10211065 ) }
1066+ { reportIssueState !== null && (
1067+ < ReportIssueScreen
1068+ onClose = { ( ) => setReportIssueState ( null ) }
1069+ initialKind = { reportIssueState . kind }
1070+ initialTitle = { reportIssueState . title }
1071+ initialBody = { reportIssueState . body }
1072+ prefilledContext = { reportIssueState . context }
1073+ />
1074+ ) }
10221075 { showActivity && (
10231076 < div className = "flex-1 min-w-0 flex" >
10241077 < Activity
@@ -1080,12 +1133,12 @@ const setQuestStep = useCallback((next: QuestStep) => {
10801133 </ div >
10811134 )
10821135 } ) ( ) }
1083- { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && ! activeWorktreeId && worktrees . length > 0 && (
1136+ { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && ! activeWorktreeId && worktrees . length > 0 && (
10841137 < div className = "flex-1 flex items-center justify-center text-dim" >
10851138 Select a worktree to begin
10861139 </ div >
10871140 ) }
1088- { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && isPendingId ( activeWorktreeId ) && ( ( ) => {
1141+ { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && isPendingId ( activeWorktreeId ) && ( ( ) => {
10891142 const pending = pendingWorktrees . find ( ( p ) => p . id === activeWorktreeId )
10901143 if ( ! pending ) return null
10911144 return (
@@ -1097,7 +1150,7 @@ const setQuestStep = useCallback((next: QuestStep) => {
10971150 />
10981151 )
10991152 } ) ( ) }
1100- { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && activeWorktreeId && pendingDeletionByPath [ activeWorktreeId ] && (
1153+ { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && activeWorktreeId && pendingDeletionByPath [ activeWorktreeId ] && (
11011154 < DeletingWorktreeScreen
11021155 deletion = { pendingDeletionByPath [ activeWorktreeId ] }
11031156 onDismiss = { handleDismissPendingDeletion }
@@ -1109,10 +1162,10 @@ const setQuestStep = useCallback((next: QuestStep) => {
11091162 onFinish = { ( ) => setQuestStep ( 'done' ) }
11101163 />
11111164 { /* Right panel — hidden on the new-worktree screen so the form gets the full width */ }
1112- { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && ! rightColumnHidden && (
1165+ { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && ! rightColumnHidden && (
11131166 < ResizeHandle onDelta = { handleRightPanelResize } />
11141167 ) }
1115- { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && ! rightColumnHidden && (
1168+ { ! showNewWorktree && ! showActivity && ! showCleanup && ! showCommandCenter && ! showReview && reportIssueState === null && ! rightColumnHidden && (
11161169 < RightColumn
11171170 width = { rightPanelWidth }
11181171 activeWorktreeId = { activeWorktreeId }
0 commit comments