From dfb0108814b4fad88f34cdc72dc5fb6c8649caa7 Mon Sep 17 00:00:00 2001 From: Aamer Akhter Date: Mon, 11 May 2026 20:48:58 -0400 Subject: [PATCH] fix(client): preserve inline rename input across tab re-renders When the inline session-rename input is open, any incoming SSE event that triggers renderSessionTabs() (a sibling session updating, a hook firing, a status change) destroys the input element mid-keystroke and the user loses what they were typing. Add a _inlineRenameActive flag that: - guards the two render paths (renderSessionTabs and _fullRenderSessionTabs) so they bail out early while a rename is in progress; - is set true when the inline input mounts (session-ui.js); - is cleared in finishRename, which then explicitly calls renderSessionTabs to restore the normal tab structure. Also add a re-entrance guard at the top of finishRename so the blur event and the Enter keydown do not both fire it (was a latent double-call). Drive-by: replace tabName.innerHTML = "" with explicit child removal. The preceding textContent = "" already clears the element; this avoids an innerHTML write on a node that takes user-supplied content on the next line. Follow-up to the inline-rename feature cherry-picked from #60. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/web/public/app.js | 3 +++ src/web/public/session-ui.js | 10 +++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) 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);