diff --git a/src/web/public/app.js b/src/web/public/app.js index fa47404f..b4ebccd7 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1844,6 +1844,8 @@ class CodemanApp { // ═══════════════════════════════════════════════════════════════ renderSessionTabs() { + // Don't re-render while user is typing in the inline rename input + if (this._inlineRenameActive) return; this._debouncedCall('sessionTabs', this._renderSessionTabsImmediate); } @@ -1988,6 +1990,7 @@ class CodemanApp { } _fullRenderSessionTabs() { + if (this._inlineRenameActive) return; const container = this.$('sessionTabs'); // Clean up any orphaned dropdowns before re-rendering diff --git a/src/web/public/session-ui.js b/src/web/public/session-ui.js index 2ca67158..e66b4417 100644 --- a/src/web/public/session-ui.js +++ b/src/web/public/session-ui.js @@ -912,11 +912,15 @@ Object.assign(CodemanApp.prototype, { const tabName = document.querySelector(`.tab-name[data-session-id="${sessionId}"]`); if (!tabName) return; + // Prevent tab re-renders from destroying the input while renaming + this._inlineRenameActive = true; + const currentName = this.getSessionName(session); const parsed = parseSessionPrefix(session.name); const originalContent = tabName.textContent; + // Clear existing content to make room for the input element tabName.textContent = ''; - tabName.innerHTML = ''; + while (tabName.firstChild) tabName.removeChild(tabName.firstChild); // If prefix detected, show it as non-editable label if (parsed) { @@ -938,6 +942,8 @@ Object.assign(CodemanApp.prototype, { input.select(); const finishRename = async () => { + if (!this._inlineRenameActive) return; // prevent double-fire + this._inlineRenameActive = false; const suffix = input.value.trim(); let fullName; if (parsed) { @@ -959,6 +965,8 @@ Object.assign(CodemanApp.prototype, { this.showToast('Failed to rename', 'error'); } } + // Re-render tabs to restore full tab structure + this.renderSessionTabs(); }; input.addEventListener('blur', finishRename);