mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix: drop phantom ended-tab stubs, trust server as source of truth (#93)
Previously the client cached open session tabs in localStorage and resurrected any that the server no longer knew about as grayed-out "ended" stubs. On multi-device use (close tab on mobile, open desktop) this left stale phantom tabs the user had to manually dismiss. Remove _restoreEndedTabs / _saveTabMetadata, the session._ended branch in selectSession, the data-ended render attribute, and the matching CSS rule. Clear the legacy localStorage key on init to purge stale entries. Co-authored-by: Teigen <teigen@TeigendeMac-mini.local>
This commit is contained in:
+6
-42
@@ -1794,8 +1794,10 @@ class CodemanApp {
|
||||
}
|
||||
});
|
||||
|
||||
// Restore tabs that were open before refresh but are no longer on the server
|
||||
this._restoreEndedTabs();
|
||||
// Server is source of truth for open sessions — don't resurrect stale tabs
|
||||
// from localStorage (would show phantom "ended" tabs when a session was closed
|
||||
// on another device).
|
||||
try { localStorage.removeItem('codeman-tab-meta'); } catch {}
|
||||
|
||||
// Sync sessionOrder with current sessions (preserve order, add new, remove stale)
|
||||
this.syncSessionOrder();
|
||||
@@ -2117,8 +2119,7 @@ class CodemanApp {
|
||||
const tallTabsEnabled = this._tallTabsEnabled ?? false;
|
||||
const showFolder = tallTabsEnabled && session.name && folderName && folderName !== name;
|
||||
|
||||
const endedAttr = session._ended ? ' data-ended="1"' : '';
|
||||
parts.push(`<div class="session-tab ${isActive ? 'active' : ''}${alertClass}" data-id="${id}" data-color="${color}"${endedAttr} onclick="app.selectSession('${escapeHtml(id)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(id)}')" tabindex="0" role="tab" aria-selected="${isActive ? 'true' : 'false'}" aria-label="${escapeHtml(name)} session" ${session.workingDir ? `title="${escapeHtml(session.workingDir)}"` : ''}>
|
||||
parts.push(`<div class="session-tab ${isActive ? 'active' : ''}${alertClass}" data-id="${id}" data-color="${color}" onclick="app.selectSession('${escapeHtml(id)}')" oncontextmenu="event.preventDefault(); app.startInlineRename('${escapeHtml(id)}')" tabindex="0" role="tab" aria-selected="${isActive ? 'true' : 'false'}" aria-label="${escapeHtml(name)} session" ${session.workingDir ? `title="${escapeHtml(session.workingDir)}"` : ''}>
|
||||
${_tabIdx < 9 ? '<span class="tab-number">' + (_tabIdx + 1) + '</span>' : ''}
|
||||
<span class="tab-status ${status}" aria-hidden="true"></span>
|
||||
<span class="tab-info">
|
||||
@@ -2138,9 +2139,6 @@ class CodemanApp {
|
||||
|
||||
container.innerHTML = parts.join('');
|
||||
|
||||
// Persist tab metadata for refresh recovery
|
||||
this._saveTabMetadata();
|
||||
|
||||
// Set up drag-and-drop handlers for tab reordering
|
||||
this.setupTabDragHandlers();
|
||||
|
||||
@@ -2240,33 +2238,6 @@ class CodemanApp {
|
||||
}
|
||||
}
|
||||
|
||||
// Save tab metadata to localStorage so ended sessions can be restored after refresh
|
||||
_saveTabMetadata() {
|
||||
try {
|
||||
const meta = {};
|
||||
for (const [id, s] of this.sessions) {
|
||||
if (s._ended) continue; // Don't persist ended stubs back
|
||||
meta[id] = { id, name: s.name || '', workingDir: s.workingDir || '', mode: s.mode || 'claude', color: s.color || 'default' };
|
||||
}
|
||||
localStorage.setItem('codeman-tab-meta', JSON.stringify(meta));
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Restore tabs that were open before refresh but are no longer on the server
|
||||
_restoreEndedTabs() {
|
||||
try {
|
||||
const saved = localStorage.getItem('codeman-tab-meta');
|
||||
if (!saved) return;
|
||||
const meta = JSON.parse(saved);
|
||||
for (const [id, info] of Object.entries(meta)) {
|
||||
if (!this.sessions.has(id)) {
|
||||
// Add a stub session so the tab renders
|
||||
this.sessions.set(id, { id, name: info.name, workingDir: info.workingDir, mode: info.mode, color: info.color, status: 'ended', _ended: true });
|
||||
}
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
// Set up drag-and-drop handlers on tab elements
|
||||
setupTabDragHandlers() {
|
||||
const container = this.$('sessionTabs');
|
||||
@@ -2546,16 +2517,9 @@ class CodemanApp {
|
||||
// Check if this is a restored session that needs to be attached
|
||||
const session = this.sessions.get(sessionId);
|
||||
|
||||
// Ended tabs (restored from localStorage, no longer on server) — show message, skip buffer load
|
||||
if (session?._ended) {
|
||||
this.terminal.clear();
|
||||
this.terminal.write('\r\n \x1b[2mSession ended. Close tab or click to reopen.\x1b[0m\r\n');
|
||||
return;
|
||||
}
|
||||
|
||||
// Track working directory for path normalization in Project Insights
|
||||
this.currentSessionWorkingDir = session?.workingDir || null;
|
||||
if (session && session.pid === null && !session._ended) {
|
||||
if (session && session.pid === null) {
|
||||
// Session has no PTY attached — either restored after server restart
|
||||
// or detached for some other reason. Re-attach regardless of status.
|
||||
try {
|
||||
|
||||
@@ -293,7 +293,6 @@ body {
|
||||
}
|
||||
.session-tab .tab-status.error { background: var(--red); }
|
||||
.session-tab .tab-status.ended { background: var(--text-muted); opacity: 0.5; }
|
||||
.session-tab[data-ended] { opacity: 0.55; }
|
||||
|
||||
/* Session color coding - left border indicator */
|
||||
.session-tab[data-color="red"] { border-left: 3px solid var(--session-red); }
|
||||
|
||||
@@ -444,7 +444,6 @@ Object.assign(CodemanApp.prototype, {
|
||||
if (
|
||||
activeResizeSession &&
|
||||
activeResizeSession.mode !== 'shell' &&
|
||||
!activeResizeSession._ended &&
|
||||
this.terminal &&
|
||||
this.isTerminalAtBottom()
|
||||
) {
|
||||
|
||||
Reference in New Issue
Block a user