mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 00:02:29 +00:00
fix: remount terminal panes after shutdown to restore PTY connections (#73)
When all PTYs are dead after shutdown, bump a generation counter on terminal tabs so React remounts TerminalPane components with fresh PTY connections instead of showing stale terminals. Also converts interfaces to type aliases in types.ts to satisfy linter. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9853bba8ba
commit
82425df6ea
@@ -386,7 +386,7 @@ export default function Terminal(): React.JSX.Element | null {
|
||||
>
|
||||
{worktreeTabs.map((tab) => (
|
||||
<TerminalPane
|
||||
key={tab.id}
|
||||
key={`${tab.id}-${tab.generation ?? 0}`}
|
||||
tabId={tab.id}
|
||||
worktreeId={worktree.id}
|
||||
cwd={worktree.path}
|
||||
|
||||
@@ -235,6 +235,24 @@ export const createWorktreeSlice: StateCreator<AppState, [], [], WorktreeSlice>
|
||||
}
|
||||
})
|
||||
|
||||
// If the worktree has tabs but all PTYs are dead (e.g. after shutdown),
|
||||
// bump generation so TerminalPanes remount with fresh PTY connections.
|
||||
if (worktreeId) {
|
||||
const tabs = get().tabsByWorktree[worktreeId] ?? []
|
||||
const allDead = tabs.length > 0 && tabs.every((tab) => !tab.ptyId)
|
||||
if (allDead) {
|
||||
set((s) => ({
|
||||
tabsByWorktree: {
|
||||
...s.tabsByWorktree,
|
||||
[worktreeId]: (s.tabsByWorktree[worktreeId] ?? []).map((tab) => ({
|
||||
...tab,
|
||||
generation: (tab.generation ?? 0) + 1
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh GitHub data (PR + issue status) when switching to a different worktree
|
||||
if (worktreeId && worktreeId !== prevActiveId) {
|
||||
get().refreshGitHubForWorktree(worktreeId)
|
||||
|
||||
+20
-18
@@ -1,5 +1,5 @@
|
||||
// ─── Repo ────────────────────────────────────────────────────────────
|
||||
export interface Repo {
|
||||
export type Repo = {
|
||||
id: string
|
||||
path: string
|
||||
displayName: string
|
||||
@@ -11,7 +11,7 @@ export interface Repo {
|
||||
}
|
||||
|
||||
// ─── Worktree (git-level) ────────────────────────────────────────────
|
||||
export interface GitWorktreeInfo {
|
||||
export type GitWorktreeInfo = {
|
||||
path: string
|
||||
head: string
|
||||
branch: string
|
||||
@@ -19,7 +19,7 @@ export interface GitWorktreeInfo {
|
||||
}
|
||||
|
||||
// ─── Worktree (app-level, enriched) ──────────────────────────────────
|
||||
export interface Worktree extends GitWorktreeInfo {
|
||||
export type Worktree = {
|
||||
id: string // `${repoId}::${path}`
|
||||
repoId: string
|
||||
displayName: string
|
||||
@@ -29,10 +29,10 @@ export interface Worktree extends GitWorktreeInfo {
|
||||
isArchived: boolean
|
||||
isUnread: boolean
|
||||
sortOrder: number
|
||||
}
|
||||
} & GitWorktreeInfo
|
||||
|
||||
// ─── Worktree metadata (persisted user-authored fields only) ─────────
|
||||
export interface WorktreeMeta {
|
||||
export type WorktreeMeta = {
|
||||
displayName: string
|
||||
comment: string
|
||||
linkedIssue: number | null
|
||||
@@ -43,7 +43,7 @@ export interface WorktreeMeta {
|
||||
}
|
||||
|
||||
// ─── Terminal Tab ────────────────────────────────────────────────────
|
||||
export interface TerminalTab {
|
||||
export type TerminalTab = {
|
||||
id: string
|
||||
ptyId: string | null
|
||||
worktreeId: string
|
||||
@@ -52,6 +52,8 @@ export interface TerminalTab {
|
||||
color: string | null
|
||||
sortOrder: number
|
||||
createdAt: number
|
||||
/** Bumped on shutdown so TerminalPane remounts with a fresh PTY. */
|
||||
generation?: number
|
||||
}
|
||||
|
||||
export type TerminalPaneSplitDirection = 'vertical' | 'horizontal'
|
||||
@@ -70,13 +72,13 @@ export type TerminalPaneLayoutNode =
|
||||
ratio?: number
|
||||
}
|
||||
|
||||
export interface TerminalLayoutSnapshot {
|
||||
export type TerminalLayoutSnapshot = {
|
||||
root: TerminalPaneLayoutNode | null
|
||||
activeLeafId: string | null
|
||||
expandedLeafId: string | null
|
||||
}
|
||||
|
||||
export interface WorkspaceSessionState {
|
||||
export type WorkspaceSessionState = {
|
||||
activeRepoId: string | null
|
||||
activeWorktreeId: string | null
|
||||
activeTabId: string | null
|
||||
@@ -89,7 +91,7 @@ export type PRState = 'open' | 'closed' | 'merged' | 'draft'
|
||||
export type IssueState = 'open' | 'closed'
|
||||
export type CheckStatus = 'pending' | 'success' | 'failure' | 'neutral'
|
||||
|
||||
export interface PRInfo {
|
||||
export type PRInfo = {
|
||||
number: number
|
||||
title: string
|
||||
state: PRState
|
||||
@@ -98,7 +100,7 @@ export interface PRInfo {
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface IssueInfo {
|
||||
export type IssueInfo = {
|
||||
number: number
|
||||
title: string
|
||||
state: IssueState
|
||||
@@ -107,14 +109,14 @@ export interface IssueInfo {
|
||||
}
|
||||
|
||||
// ─── Hooks (orca.yaml) ──────────────────────────────────────────────
|
||||
export interface OrcaHooks {
|
||||
export type OrcaHooks = {
|
||||
scripts: {
|
||||
setup?: string // Runs after worktree is created
|
||||
archive?: string // Runs before worktree is archived
|
||||
}
|
||||
}
|
||||
|
||||
export interface RepoHookSettings {
|
||||
export type RepoHookSettings = {
|
||||
mode: 'auto' | 'override'
|
||||
scripts: {
|
||||
setup: string
|
||||
@@ -133,7 +135,7 @@ export type UpdateStatus =
|
||||
| { state: 'error'; message: string; userInitiated?: boolean }
|
||||
|
||||
// ─── Settings ────────────────────────────────────────────────────────
|
||||
export interface GlobalSettings {
|
||||
export type GlobalSettings = {
|
||||
workspaceDir: string
|
||||
nestWorkspaces: boolean
|
||||
branchPrefix: 'git-username' | 'custom' | 'none'
|
||||
@@ -155,7 +157,7 @@ export interface GlobalSettings {
|
||||
terminalScrollbackBytes: number
|
||||
}
|
||||
|
||||
export interface PersistedUIState {
|
||||
export type PersistedUIState = {
|
||||
lastActiveRepoId: string | null
|
||||
lastActiveWorktreeId: string | null
|
||||
sidebarWidth: number
|
||||
@@ -166,7 +168,7 @@ export interface PersistedUIState {
|
||||
}
|
||||
|
||||
// ─── Persistence shape ──────────────────────────────────────────────
|
||||
export interface PersistedState {
|
||||
export type PersistedState = {
|
||||
schemaVersion: number
|
||||
repos: Repo[]
|
||||
worktreeMeta: Record<string, WorktreeMeta>
|
||||
@@ -180,7 +182,7 @@ export interface PersistedState {
|
||||
}
|
||||
|
||||
// ─── Filesystem ─────────────────────────────────────────────
|
||||
export interface DirEntry {
|
||||
export type DirEntry = {
|
||||
name: string
|
||||
isDirectory: boolean
|
||||
isSymlink: boolean
|
||||
@@ -190,14 +192,14 @@ export interface DirEntry {
|
||||
export type GitFileStatus = 'modified' | 'added' | 'deleted' | 'renamed' | 'untracked' | 'copied'
|
||||
export type GitStagingArea = 'staged' | 'unstaged' | 'untracked'
|
||||
|
||||
export interface GitStatusEntry {
|
||||
export type GitStatusEntry = {
|
||||
path: string
|
||||
status: GitFileStatus
|
||||
area: GitStagingArea
|
||||
oldPath?: string
|
||||
}
|
||||
|
||||
export interface GitDiffResult {
|
||||
export type GitDiffResult = {
|
||||
originalContent: string
|
||||
modifiedContent: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user