Skip to content

Commit 7fe8ac5

Browse files
MarkShawn2020claude
andcommitted
fix: remove debug logs and improve terminal exit behavior
- Remove debug logs from pty_manager.rs and TerminalPane.tsx - Keep sessions with commands open after PTY exit for scrollback visibility - Refactor workspace and panel components 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent f885561 commit 7fe8ac5

6 files changed

Lines changed: 541 additions & 560 deletions

File tree

src-tauri/src/pty_manager.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,12 @@ fn get_scrollback_path(id: &str) -> PathBuf {
4141
/// Load scrollback from disk
4242
fn load_scrollback_from_disk(id: &str) -> Option<VecDeque<u8>> {
4343
let path = get_scrollback_path(id);
44-
println!("[DEBUG][pty_manager] load_scrollback_from_disk: id={}, path={:?}, exists={}", id, path, path.exists());
4544
if path.exists() {
4645
match fs::read(&path) {
47-
Ok(data) => {
48-
println!("[DEBUG][pty_manager] load_scrollback_from_disk: loaded {} bytes", data.len());
49-
Some(VecDeque::from(data))
50-
}
51-
Err(e) => {
52-
eprintln!("[DEBUG][pty_manager] load_scrollback_from_disk: failed to read: {}", e);
53-
None
54-
}
46+
Ok(data) => Some(VecDeque::from(data)),
47+
Err(_) => None,
5548
}
5649
} else {
57-
println!("[DEBUG][pty_manager] load_scrollback_from_disk: file not found");
5850
None
5951
}
6052
}
@@ -66,15 +58,13 @@ fn save_scrollback_to_disk(id: &str, data: &VecDeque<u8>) -> Result<(), String>
6658

6759
let path = get_scrollback_path(id);
6860
let bytes: Vec<u8> = data.iter().copied().collect();
69-
println!("[DEBUG][pty_manager] save_scrollback_to_disk: id={}, path={:?}, bytes={}", id, path, bytes.len());
7061
fs::write(&path, &bytes).map_err(|e| format!("Failed to write scrollback: {}", e))?;
7162
Ok(())
7263
}
7364

7465
/// Delete scrollback file
7566
fn delete_scrollback_from_disk(id: &str) {
7667
let path = get_scrollback_path(id);
77-
println!("[DEBUG][pty_manager] delete_scrollback_from_disk: id={}, path={:?}", id, path);
7868
let _ = fs::remove_file(path);
7969
}
8070

@@ -211,11 +201,9 @@ pub fn create_session(
211201

212202
// Initialize scrollback buffer - load from disk if exists (for app restart recovery)
213203
{
214-
println!("[DEBUG][pty_manager] create_session: loading scrollback for id={}", id);
215204
let mut scrollback = PTY_SCROLLBACK.lock().map_err(|e| e.to_string())?;
216205
let buffer = load_scrollback_from_disk(&id)
217206
.unwrap_or_else(|| VecDeque::with_capacity(SCROLLBACK_MAX_BYTES));
218-
println!("[DEBUG][pty_manager] create_session: scrollback buffer size={}", buffer.len());
219207
scrollback.insert(id.clone(), buffer);
220208
}
221209
// Initialize last save timestamp

src/components/GlobalHeader/VerticalFeatureTabs.tsx

Lines changed: 99 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -699,106 +699,114 @@ function ProjectSessionsGroup({
699699
};
700700

701701
const handleResumeSession = async (session: Session) => {
702-
if (!workspace) return;
702+
// Use functional update to avoid race conditions with stale closure values
703+
let savedWorkspace: WorkspaceData | null = null;
703704

704-
// Select this project first
705-
navigate({ type: "workspace", projectId: project.id, mode: "features" });
705+
setWorkspace((currentWorkspace) => {
706+
if (!currentWorkspace) return currentWorkspace;
706707

707-
// Find or create a feature to add the session terminal
708-
const targetFeature = project.features.find((f) => !f.archived);
708+
// Find current project from latest state
709+
const currentProject = currentWorkspace.projects.find((p) => p.id === project.id);
710+
if (!currentProject) return currentWorkspace;
709711

710-
// If no active feature, just navigate to the project
711-
if (!targetFeature) {
712-
const newWorkspace: WorkspaceData = {
713-
...workspace,
714-
active_project_id: project.id,
715-
};
716-
setWorkspace(newWorkspace);
717-
await invoke("workspace_save", { data: newWorkspace });
718-
return;
719-
}
712+
// Find or create a feature to add the session terminal
713+
const targetFeature = currentProject.features.find((f) => !f.archived);
720714

721-
// Create a new terminal session with cc --resume command
722-
const panelId = targetFeature.panels[0]?.id;
723-
const title = session.summary || "CC Session";
724-
const command = `cc --resume "${session.id}"`;
725-
726-
if (!panelId) {
727-
// No panel exists, create one with the resume command
728-
const newPanelId = crypto.randomUUID();
729-
const ptySessionId = crypto.randomUUID();
730-
const ptyId = crypto.randomUUID();
731-
732-
const newPanel = {
733-
id: newPanelId,
734-
sessions: [{ id: ptySessionId, pty_id: ptyId, title, command }],
735-
active_session_id: ptySessionId,
736-
is_shared: false,
737-
cwd: project.path,
738-
};
715+
// If no active feature, just set active project
716+
if (!targetFeature) {
717+
savedWorkspace = {
718+
...currentWorkspace,
719+
active_project_id: project.id,
720+
};
721+
return savedWorkspace;
722+
}
739723

740-
const newProjects = workspace.projects.map((p) => {
741-
if (p.id !== project.id) return p;
742-
return {
743-
...p,
744-
features: p.features.map((f) => {
745-
if (f.id !== targetFeature!.id) return f;
746-
return {
747-
...f,
748-
panels: [...f.panels, newPanel],
749-
layout: { type: "panel" as const, panelId: newPanelId },
750-
};
751-
}),
752-
active_feature_id: targetFeature!.id,
753-
view_mode: "features" as const,
724+
// Create a new terminal session with claude --resume command
725+
const panelId = targetFeature.panels[0]?.id;
726+
const title = session.summary || "Untitled";
727+
const command = `claude --resume "${session.id}"`;
728+
729+
if (!panelId) {
730+
// No panel exists, create one with the resume command
731+
const newPanelId = crypto.randomUUID();
732+
const ptySessionId = crypto.randomUUID();
733+
const ptyId = crypto.randomUUID();
734+
735+
const newPanel = {
736+
id: newPanelId,
737+
sessions: [{ id: ptySessionId, pty_id: ptyId, title, command }],
738+
active_session_id: ptySessionId,
739+
is_shared: false,
740+
cwd: project.path,
754741
};
755-
});
756742

757-
const newWorkspace: WorkspaceData = {
758-
...workspace,
759-
projects: newProjects,
760-
active_project_id: project.id,
761-
};
762-
setWorkspace(newWorkspace);
763-
await invoke("workspace_save", { data: newWorkspace });
764-
} else {
765-
// Add a new session tab to the first panel
766-
const ptySessionId = crypto.randomUUID();
767-
const ptyId = crypto.randomUUID();
768-
769-
const newProjects = workspace.projects.map((p) => {
770-
if (p.id !== project.id) return p;
771-
return {
772-
...p,
773-
features: p.features.map((f) => {
774-
if (f.id !== targetFeature!.id) return f;
775-
return {
776-
...f,
777-
panels: f.panels.map((panel) => {
778-
if (panel.id !== panelId) return panel;
779-
return {
780-
...panel,
781-
sessions: [
782-
...(panel.sessions || []),
783-
{ id: ptySessionId, pty_id: ptyId, title, command },
784-
],
785-
active_session_id: ptySessionId,
786-
};
787-
}),
788-
};
789-
}),
790-
active_feature_id: targetFeature!.id,
791-
view_mode: "features" as const,
743+
const newProjects = currentWorkspace.projects.map((p) => {
744+
if (p.id !== project.id) return p;
745+
return {
746+
...p,
747+
features: p.features.map((f) => {
748+
if (f.id !== targetFeature.id) return f;
749+
return {
750+
...f,
751+
panels: [...f.panels, newPanel],
752+
layout: { type: "panel" as const, panelId: newPanelId },
753+
};
754+
}),
755+
active_feature_id: targetFeature.id,
756+
view_mode: "features" as const,
757+
};
758+
});
759+
760+
savedWorkspace = {
761+
...currentWorkspace,
762+
projects: newProjects,
763+
active_project_id: project.id,
792764
};
793-
});
765+
return savedWorkspace;
766+
} else {
767+
// Add a new session tab to the first panel
768+
const ptySessionId = crypto.randomUUID();
769+
const ptyId = crypto.randomUUID();
770+
771+
const newProjects = currentWorkspace.projects.map((p) => {
772+
if (p.id !== project.id) return p;
773+
return {
774+
...p,
775+
features: p.features.map((f) => {
776+
if (f.id !== targetFeature.id) return f;
777+
return {
778+
...f,
779+
panels: f.panels.map((panel) => {
780+
if (panel.id !== panelId) return panel;
781+
return {
782+
...panel,
783+
sessions: [
784+
...(panel.sessions || []),
785+
{ id: ptySessionId, pty_id: ptyId, title, command },
786+
],
787+
active_session_id: ptySessionId,
788+
};
789+
}),
790+
};
791+
}),
792+
active_feature_id: targetFeature.id,
793+
view_mode: "features" as const,
794+
};
795+
});
794796

795-
const newWorkspace: WorkspaceData = {
796-
...workspace,
797-
projects: newProjects,
798-
active_project_id: project.id,
799-
};
800-
setWorkspace(newWorkspace);
801-
await invoke("workspace_save", { data: newWorkspace });
797+
savedWorkspace = {
798+
...currentWorkspace,
799+
projects: newProjects,
800+
active_project_id: project.id,
801+
};
802+
return savedWorkspace;
803+
}
804+
});
805+
806+
// Save after state update - use the captured workspace from functional update
807+
if (savedWorkspace) {
808+
await invoke("workspace_save", { data: savedWorkspace });
809+
navigate({ type: "workspace", projectId: project.id, mode: "features" });
802810
}
803811
};
804812

src/components/PanelGrid/PanelGrid.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useCallback, useEffect, useState } from "react";
22
import { Allotment } from "allotment";
33
import "allotment/dist/style.css";
4-
import { ChevronLeftIcon, ChevronRightIcon, DrawingPinFilledIcon } from "@radix-ui/react-icons";
4+
import { ChevronLeftIcon, ChevronRightIcon, DrawingPinFilledIcon, PlusIcon } from "@radix-ui/react-icons";
55
import { SessionPanel } from "./SessionPanel";
66
import type { LayoutNode } from "../../views/Workspace/types";
77

@@ -164,12 +164,6 @@ export function PanelGrid({
164164
}
165165
}, [controlledOnPanelFocus, controlledActivePanelId]);
166166

167-
// Auto-create terminal when empty
168-
useEffect(() => {
169-
if (panels.length === 0 && onInitialPanelCreate) {
170-
onInitialPanelCreate();
171-
}
172-
}, [panels.length, onInitialPanelCreate]);
173167

174168
// Auto-select first panel if current active is gone
175169
useEffect(() => {
@@ -179,7 +173,22 @@ export function PanelGrid({
179173
}, [panels, activePanelId]);
180174

181175
if (panels.length === 0) {
182-
return null;
176+
return (
177+
<div className="h-full w-full flex items-center justify-center bg-canvas">
178+
<div className="text-center">
179+
<p className="text-muted-foreground mb-4">No terminals open</p>
180+
{onInitialPanelCreate && (
181+
<button
182+
onClick={onInitialPanelCreate}
183+
className="inline-flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
184+
>
185+
<PlusIcon className="w-4 h-4" />
186+
New Terminal
187+
</button>
188+
)}
189+
</div>
190+
</div>
191+
);
183192
}
184193

185194
// Use tree layout if available

src/components/PanelGrid/SessionPanel.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,17 @@ export const SessionPanel = memo(function SessionPanel({
112112
}: SessionPanelProps) {
113113
const handleTitleChange = useCallback(
114114
(sessionId: string) => (title: string) => {
115+
// Only update title if current title is "Untitled" (preserve user-set titles like session summary)
116+
// Also skip if session not found yet (React hasn't finished updating props)
117+
const session = panel.sessions.find((s) => s.id === sessionId);
118+
if (!session || session.title !== "Untitled") return;
119+
115120
// Strip ANSI escape sequences and control characters from terminal title
116121
// eslint-disable-next-line no-control-regex
117122
const cleanTitle = title.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|[\x00-\x1f]/g, '').trim();
118123
if (cleanTitle) onSessionTitleChange(sessionId, cleanTitle);
119124
},
120-
[onSessionTitleChange]
125+
[onSessionTitleChange, panel.sessions]
121126
);
122127

123128
return (

0 commit comments

Comments
 (0)