fix(client): preserve inline rename input across tab re-renders (#81)

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) <noreply@anthropic.com>
This commit is contained in:
aakhter
2026-05-12 09:10:34 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 41a82fcf02
commit eade261763
2 changed files with 12 additions and 1 deletions
+3
View File
@@ -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
+9 -1
View File
@@ -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);