ignore fleetcode directory
This commit is contained in:
parent
61d2defd93
commit
f05d2502c5
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -139,5 +139,3 @@ dist
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
.vite/
|
.vite/
|
||||||
|
|
||||||
.fleetcode/
|
|
||||||
41
main.ts
41
main.ts
|
|
@ -46,6 +46,44 @@ function getNextSessionNumber(): number {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Git worktree helper functions
|
// Git worktree helper functions
|
||||||
|
async function ensureFleetcodeExcluded(projectDir: string) {
|
||||||
|
// Check if we've already initialized this project (persisted across app restarts)
|
||||||
|
const initializedProjects: string[] = (store as any).get("excludeInitializedProjects", []);
|
||||||
|
if (initializedProjects.includes(projectDir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const excludeFilePath = path.join(projectDir, ".git", "info", "exclude");
|
||||||
|
const excludeEntry = ".fleetcode/";
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Ensure .git/info directory exists
|
||||||
|
const infoDir = path.dirname(excludeFilePath);
|
||||||
|
if (!fs.existsSync(infoDir)) {
|
||||||
|
fs.mkdirSync(infoDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read existing exclude file or create empty string
|
||||||
|
let excludeContent = "";
|
||||||
|
if (fs.existsSync(excludeFilePath)) {
|
||||||
|
excludeContent = fs.readFileSync(excludeFilePath, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if .fleetcode/ is already excluded
|
||||||
|
if (!excludeContent.includes(excludeEntry)) {
|
||||||
|
// Add .fleetcode/ to exclude file
|
||||||
|
const newContent = excludeContent.trim() + (excludeContent.trim() ? "\n" : "") + excludeEntry + "\n";
|
||||||
|
fs.writeFileSync(excludeFilePath, newContent, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark this project as initialized and persist
|
||||||
|
initializedProjects.push(projectDir);
|
||||||
|
(store as any).set("excludeInitializedProjects", initializedProjects);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error ensuring .fleetcode excluded:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number): Promise<string> {
|
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number): Promise<string> {
|
||||||
const git = simpleGit(projectDir);
|
const git = simpleGit(projectDir);
|
||||||
const fleetcodeDir = path.join(projectDir, ".fleetcode");
|
const fleetcodeDir = path.join(projectDir, ".fleetcode");
|
||||||
|
|
@ -137,6 +175,9 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
|
||||||
const sessionId = `session-${Date.now()}`;
|
const sessionId = `session-${Date.now()}`;
|
||||||
const sessionName = `Session ${sessionNumber}`;
|
const sessionName = `Session ${sessionNumber}`;
|
||||||
|
|
||||||
|
// Ensure .fleetcode is excluded (async, don't wait)
|
||||||
|
ensureFleetcodeExcluded(config.projectDir);
|
||||||
|
|
||||||
// Create git worktree
|
// Create git worktree
|
||||||
const worktreePath = await createWorktree(config.projectDir, config.parentBranch, sessionNumber);
|
const worktreePath = await createWorktree(config.projectDir, config.parentBranch, sessionNumber);
|
||||||
|
|
||||||
|
|
|
||||||
71
renderer.ts
71
renderer.ts
|
|
@ -76,18 +76,58 @@ function createTerminalUI(sessionId: string) {
|
||||||
|
|
||||||
// Listen for bell character to mark unread activity
|
// Listen for bell character to mark unread activity
|
||||||
term.onBell(() => {
|
term.onBell(() => {
|
||||||
console.log(`Bell received for session ${sessionId}, activeSessionId: ${activeSessionId}`);
|
|
||||||
if (activeSessionId !== sessionId) {
|
if (activeSessionId !== sessionId) {
|
||||||
console.log(`Marking session ${sessionId} as unread`);
|
|
||||||
markSessionAsUnread(sessionId);
|
markSessionAsUnread(sessionId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle resize
|
// Handle resize - only refit if dimensions actually changed
|
||||||
|
let lastCols = term.cols;
|
||||||
|
let lastRows = term.rows;
|
||||||
|
let resizeTimeout: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
const resizeHandler = () => {
|
const resizeHandler = () => {
|
||||||
if (activeSessionId === sessionId) {
|
if (activeSessionId === sessionId) {
|
||||||
fitAddon.fit();
|
// Clear any pending resize
|
||||||
ipcRenderer.send("session-resize", sessionId, term.cols, term.rows);
|
if (resizeTimeout) {
|
||||||
|
clearTimeout(resizeTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce the fit call
|
||||||
|
resizeTimeout = setTimeout(() => {
|
||||||
|
// Calculate what the new dimensions would be
|
||||||
|
const container = sessionElement;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const rect = container.getBoundingClientRect();
|
||||||
|
const core = (term as any)._core;
|
||||||
|
if (!core) return;
|
||||||
|
|
||||||
|
// Estimate new dimensions based on container size
|
||||||
|
const newCols = Math.floor(rect.width / core._renderService.dimensions.actualCellWidth);
|
||||||
|
const newRows = Math.floor(rect.height / core._renderService.dimensions.actualCellHeight);
|
||||||
|
|
||||||
|
// Only fit if dimensions actually changed significantly (more than 1 char difference)
|
||||||
|
if (Math.abs(newCols - lastCols) > 1 || Math.abs(newRows - lastRows) > 1) {
|
||||||
|
// Save scroll position before fitting
|
||||||
|
const wasAtBottom = term.buffer.active.viewportY === term.buffer.active.baseY;
|
||||||
|
const savedScrollPosition = term.buffer.active.viewportY;
|
||||||
|
|
||||||
|
fitAddon.fit();
|
||||||
|
|
||||||
|
lastCols = term.cols;
|
||||||
|
lastRows = term.rows;
|
||||||
|
|
||||||
|
// Restore scroll position unless we were at the bottom (in which case stay at bottom)
|
||||||
|
if (!wasAtBottom && savedScrollPosition !== term.buffer.active.viewportY) {
|
||||||
|
term.scrollToLine(savedScrollPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.send("session-resize", sessionId, term.cols, term.rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeTimeout = null;
|
||||||
|
}, 100); // 100ms debounce
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener("resize", resizeHandler);
|
window.addEventListener("resize", resizeHandler);
|
||||||
|
|
@ -375,7 +415,17 @@ function switchToSession(sessionId: string) {
|
||||||
session.terminal.focus();
|
session.terminal.focus();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (session.fitAddon && session.terminal) {
|
if (session.fitAddon && session.terminal) {
|
||||||
|
// Save scroll position before fitting
|
||||||
|
const wasAtBottom = session.terminal.buffer.active.viewportY === session.terminal.buffer.active.baseY;
|
||||||
|
const savedScrollPosition = session.terminal.buffer.active.viewportY;
|
||||||
|
|
||||||
session.fitAddon.fit();
|
session.fitAddon.fit();
|
||||||
|
|
||||||
|
// Restore scroll position unless we were at the bottom
|
||||||
|
if (!wasAtBottom && savedScrollPosition !== session.terminal.buffer.active.viewportY) {
|
||||||
|
session.terminal.scrollToLine(savedScrollPosition);
|
||||||
|
}
|
||||||
|
|
||||||
ipcRenderer.send("session-resize", sessionId, session.terminal.cols, session.terminal.rows);
|
ipcRenderer.send("session-resize", sessionId, session.terminal.cols, session.terminal.rows);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
@ -464,28 +514,27 @@ const IDLE_DELAY_MS = 500; // 0.5 seconds of no output = Claude is done
|
||||||
ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => {
|
ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => {
|
||||||
const session = sessions.get(sessionId);
|
const session = sessions.get(sessionId);
|
||||||
if (session && session.terminal) {
|
if (session && session.terminal) {
|
||||||
session.terminal.write(data);
|
// Filter out [3J (clear scrollback) to prevent viewport resets during interactive menus
|
||||||
|
// Keep [2J (clear screen) which is needed for the menu redraw
|
||||||
|
const filteredData = data.replace(/\x1b\[3J/g, '');
|
||||||
|
|
||||||
|
session.terminal.write(filteredData);
|
||||||
|
|
||||||
// Only mark as unread if this is not the active session
|
// Only mark as unread if this is not the active session
|
||||||
if (activeSessionId !== sessionId && session.hasActivePty) {
|
if (activeSessionId !== sessionId && session.hasActivePty) {
|
||||||
console.log(`[Unread] Session ${sessionId} received output while inactive`);
|
|
||||||
|
|
||||||
// Clear any existing idle timer
|
// Clear any existing idle timer
|
||||||
const existingTimer = sessionIdleTimers.get(sessionId);
|
const existingTimer = sessionIdleTimers.get(sessionId);
|
||||||
if (existingTimer) {
|
if (existingTimer) {
|
||||||
console.log(`[Unread] Clearing existing idle timer for session ${sessionId}`);
|
|
||||||
clearTimeout(existingTimer);
|
clearTimeout(existingTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a new timer - if no output for IDLE_DELAY_MS, mark as unread
|
// Set a new timer - if no output for IDLE_DELAY_MS, mark as unread
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
console.log(`[Unread] ✓ No output for ${IDLE_DELAY_MS}ms - Claude is done! Marking session ${sessionId} as unread`);
|
|
||||||
markSessionAsUnread(sessionId);
|
markSessionAsUnread(sessionId);
|
||||||
sessionIdleTimers.delete(sessionId);
|
sessionIdleTimers.delete(sessionId);
|
||||||
}, IDLE_DELAY_MS);
|
}, IDLE_DELAY_MS);
|
||||||
|
|
||||||
sessionIdleTimers.set(sessionId, timer);
|
sessionIdleTimers.set(sessionId, timer);
|
||||||
console.log(`[Unread] Set idle timer for session ${sessionId} - will trigger in ${IDLE_DELAY_MS}ms if no more output`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue