mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 07:59:42 +02:00
Merge remote-tracking branch 'origin/master' into worktree-grok-mode
# Conflicts: # src/web/public/app.js
This commit is contained in:
@@ -1851,15 +1851,22 @@ Object.assign(CodemanApp.prototype, {
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
this._activeRename?.cancel();
|
||||
|
||||
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;
|
||||
tabName.classList.add('tab-name-renaming');
|
||||
|
||||
const currentName = this.getSessionName(session);
|
||||
const parsed = parseSessionPrefix(session.name);
|
||||
const originalContent = tabName.textContent;
|
||||
const originalChildren = [...tabName.childNodes].map((node) => node.cloneNode(true));
|
||||
const restoreOriginalChildren = () => {
|
||||
tabName.replaceChildren(...originalChildren.map((node) => node.cloneNode(true)));
|
||||
};
|
||||
// Clear existing content to make room for the input element
|
||||
tabName.textContent = '';
|
||||
while (tabName.firstChild) tabName.removeChild(tabName.firstChild);
|
||||
@@ -1867,6 +1874,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
// If prefix detected, show it as non-editable label
|
||||
if (parsed) {
|
||||
const prefixLabel = document.createElement('span');
|
||||
prefixLabel.className = 'tab-rename-prefix';
|
||||
prefixLabel.textContent = parsed.prefix + ': ';
|
||||
prefixLabel.style.cssText = 'color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;';
|
||||
tabName.appendChild(prefixLabel);
|
||||
@@ -1879,36 +1887,67 @@ Object.assign(CodemanApp.prototype, {
|
||||
input.className = 'tab-rename-input';
|
||||
// 80px is tuned for the narrow header tab; a full-width sidebar row can and
|
||||
// should give the whole line to the input.
|
||||
const renameWidth = this.isSessionSidebarActive?.() ? '100%' : '80px';
|
||||
const renameWidth = tabName.closest('.tab-rail') ? 'auto' : this.isSessionSidebarActive?.() ? '100%' : '80px';
|
||||
input.style.cssText = `width: ${renameWidth}; min-width: 0; font-size: 0.75rem; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;`;
|
||||
|
||||
tabName.appendChild(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
|
||||
const finishRename = async ({ commit }) => {
|
||||
if (!this._inlineRenameActive) return; // prevent double-fire
|
||||
let editSettled = false;
|
||||
let invalidated = false;
|
||||
let completed = false;
|
||||
|
||||
const releaseRenderGuard = () => {
|
||||
if (this._activeRename !== renameHandle) return;
|
||||
this._inlineRenameActive = false;
|
||||
};
|
||||
|
||||
const completeCurrentRename = () => {
|
||||
if (this._activeRename !== renameHandle) return;
|
||||
completed = true;
|
||||
releaseRenderGuard();
|
||||
this._activeRename = null;
|
||||
this.renderSessionTabs();
|
||||
};
|
||||
|
||||
const cancelRename = () => {
|
||||
if (invalidated || completed) return;
|
||||
invalidated = true;
|
||||
editSettled = true;
|
||||
tabName.classList.remove('tab-name-renaming');
|
||||
restoreOriginalChildren();
|
||||
completeCurrentRename();
|
||||
};
|
||||
|
||||
const finishRename = async ({ commit }) => {
|
||||
if (editSettled || invalidated) return;
|
||||
editSettled = true;
|
||||
tabName.classList.remove('tab-name-renaming');
|
||||
|
||||
// Aborted (e.g. the session was deleted mid-rename, or Escape): re-render
|
||||
// so any ghost DOM is replaced with the canonical tab list, and skip the
|
||||
// API call — a cancel must not fire a stale rename PUT.
|
||||
if (!commit) {
|
||||
this.renderSessionTabs();
|
||||
cancelRename();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._activeRename !== renameHandle) return;
|
||||
releaseRenderGuard();
|
||||
|
||||
const suffix = input.value.trim();
|
||||
const fullName = parsed ? parsed.prefix + (suffix ? ': ' + suffix : '') : suffix;
|
||||
tabName.textContent = fullName || originalContent;
|
||||
if (fullName === session.name) restoreOriginalChildren();
|
||||
else tabName.textContent = fullName || originalContent;
|
||||
|
||||
// Skip the API call if the session vanished between focus and blur.
|
||||
const stillExists = this.sessions.has(sessionId);
|
||||
if (stillExists && fullName !== session.name) {
|
||||
const confirmed = await this._putSessionName(sessionId, fullName);
|
||||
if (invalidated || this._activeRename !== renameHandle || !this.sessions.has(sessionId)) return;
|
||||
if (confirmed === null) {
|
||||
tabName.textContent = originalContent;
|
||||
restoreOriginalChildren();
|
||||
this.showToast('Failed to rename', 'error');
|
||||
} else {
|
||||
// The re-render below repaints from this.sessions, so the new name has
|
||||
@@ -1917,14 +1956,15 @@ Object.assign(CodemanApp.prototype, {
|
||||
}
|
||||
}
|
||||
// Re-render tabs to restore full tab structure
|
||||
this.renderSessionTabs();
|
||||
completeCurrentRename();
|
||||
};
|
||||
|
||||
// Register only after the input is wired so a throw above can't strand state.
|
||||
this._activeRename = {
|
||||
const renameHandle = {
|
||||
sessionId,
|
||||
cancel: () => finishRename({ commit: false }),
|
||||
cancel: cancelRename,
|
||||
};
|
||||
this._activeRename = renameHandle;
|
||||
|
||||
input.addEventListener('blur', () => finishRename({ commit: true }));
|
||||
input.addEventListener('keydown', (e) => {
|
||||
|
||||
Reference in New Issue
Block a user