diff --git a/src/web/public/app.js b/src/web/public/app.js
index d5b9bf8b..413e346a 100644
--- a/src/web/public/app.js
+++ b/src/web/public/app.js
@@ -8956,7 +8956,7 @@ class CodemanApp {
${mode === 'shell' ? 'sh' : mode === 'opencode' ? 'oc' : ''}
- ${escapeHtml(name)}
+ ${escapeHtml(name)}
${showFolder ? `\u{1F4C1} ${escapeHtml(folderName)}` : ''}
@@ -11380,6 +11380,24 @@ class CodemanApp {
menu.appendChild(sep);
}
+ // Rename item
+ const renameItem = document.createElement('div');
+ renameItem.className = 'session-ctx-item';
+ renameItem.setAttribute('role', 'menuitem');
+ renameItem.tabIndex = 0;
+ const renameIcon = document.createElement('span');
+ renameIcon.className = 'session-ctx-icon';
+ renameIcon.textContent = '\u270E';
+ const renameLabel = document.createElement('span');
+ renameLabel.textContent = 'Rename';
+ renameItem.appendChild(renameIcon);
+ renameItem.appendChild(renameLabel);
+ renameItem.addEventListener('click', () => {
+ this.closeSessionContextMenu();
+ this.startInlineRename(sessionId);
+ });
+ menu.appendChild(renameItem);
+
// Remove item
const removeItem = document.createElement('div');
removeItem.className = 'session-ctx-item session-ctx-item-danger';
@@ -13205,7 +13223,7 @@ class CodemanApp {
const session = this.sessions.get(sessionId);
if (!session) return;
- const tabName = document.querySelector(`.tab-name[data-session-id="${sessionId}"]`);
+ const tabName = document.querySelector(`.tab-name[data-session-id="${sessionId}"], .drawer-session-name[data-session-id="${sessionId}"]`);
if (!tabName) return;
const currentName = this.getSessionName(session);
@@ -20349,11 +20367,13 @@ class CodemanApp {
modelHtml = `${modelShort}`;
}
+ const displayName = escapeHtml(muxSession.name || muxSession.muxName);
+ const sidAttr = escapeHtml(muxSession.sessionId);
html += `
${statusLabel}
-
${modelHtml} ${escapeHtml(muxSession.name || muxSession.muxName)}
+
${modelHtml} ${displayName}
${tokenHtml}
${costHtml}
@@ -20363,7 +20383,8 @@ class CodemanApp {
-
+
+
`;
@@ -20372,6 +20393,60 @@ class CodemanApp {
body.innerHTML = html;
}
+ startMuxSessionRename(sessionId) {
+ const muxSession = this.muxSessions.find(s => s.sessionId === sessionId);
+ if (!muxSession) return;
+ const nameSpan = document.querySelector(`.mux-session-name[data-session-id="${sessionId}"]`);
+ if (!nameSpan) return;
+ const current = muxSession.name || muxSession.muxName || '';
+ const originalText = nameSpan.textContent;
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.value = muxSession.name || '';
+ input.placeholder = current;
+ input.className = 'mux-rename-input';
+ input.style.cssText = 'font: inherit; padding: 2px 4px; background: var(--bg-input); border: 1px solid var(--accent); border-radius: 3px; color: var(--text); outline: none;';
+ nameSpan.textContent = '';
+ nameSpan.appendChild(input);
+ input.focus();
+ input.select();
+
+ let done = false;
+ const finish = async (commit) => {
+ if (done) return;
+ done = true;
+ const next = input.value.trim();
+ if (!commit || !next || next === current) {
+ nameSpan.textContent = originalText;
+ return;
+ }
+ try {
+ const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/name`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name: next })
+ });
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data = await res.json();
+ const finalName = (data && data.name) || next;
+ muxSession.name = finalName;
+ const sess = this.sessions.get(sessionId);
+ if (sess) sess.name = finalName;
+ this.renderMuxSessions();
+ if (typeof this.renderSessionTabs === 'function') this.renderSessionTabs();
+ } catch (err) {
+ nameSpan.textContent = originalText;
+ this.showToast(`Rename failed: ${err.message}`, 'error');
+ }
+ };
+
+ input.addEventListener('blur', () => finish(true));
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') { e.preventDefault(); input.blur(); }
+ else if (e.key === 'Escape') { finish(false); }
+ });
+ }
+
renderMonitorSubagents() {
const body = document.getElementById('monitorSubagentsBody');
const stats = document.getElementById('monitorSubagentStats');
@@ -21803,6 +21878,64 @@ const SessionDrawer = {
return String(str ?? '');
},
+ _startInlineRename(row, nameEl, session) {
+ if (row.querySelector('.drawer-rename-input')) return;
+ const currentDisplay = app.getSessionName(session);
+ const input = document.createElement('input');
+ input.type = 'text';
+ input.value = session.name || '';
+ input.placeholder = currentDisplay;
+ input.className = 'drawer-rename-input';
+ input.style.cssText = 'flex:1;min-width:0;font:inherit;padding:2px 4px;background:var(--bg-input);border:1px solid var(--accent);border-radius:3px;color:var(--text);outline:none';
+
+ nameEl.replaceWith(input);
+ input.focus();
+ input.select();
+
+ let done = false;
+ const finish = async (commit) => {
+ if (done) return;
+ done = true;
+ const next = input.value.trim();
+ const restore = () => {
+ if (input.parentNode) input.replaceWith(nameEl);
+ };
+ if (!commit || !next || next === (session.name || '')) {
+ restore();
+ return;
+ }
+ try {
+ const res = await fetch(`/api/sessions/${encodeURIComponent(session.id)}/name`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name: next }),
+ });
+ const data = await res.json().catch(() => ({}));
+ if (!res.ok || data.success === false) {
+ app.showToast?.(`Rename failed: ${data.error || res.statusText}`, 'error');
+ restore();
+ return;
+ }
+ session.name = next;
+ nameEl.textContent = app.getSessionName(session);
+ restore();
+ if (typeof app.renderSessionTabs === 'function') app.renderSessionTabs();
+ if (typeof app.renderMuxSessions === 'function') app.renderMuxSessions();
+ } catch (err) {
+ app.showToast?.(`Rename failed: ${err.message}`, 'error');
+ restore();
+ }
+ };
+
+ input.addEventListener('click', (e) => e.stopPropagation());
+ input.addEventListener('dblclick', (e) => e.stopPropagation());
+ input.addEventListener('blur', () => finish(true));
+ input.addEventListener('keydown', (e) => {
+ if (e.key === 'Enter') { e.preventDefault(); finish(true); }
+ else if (e.key === 'Escape') { e.preventDefault(); finish(false); }
+ });
+ },
+
_renderSessionRow(s) {
const isActive = s.id === app.activeSessionId;
const isRunning = s.status === 'running' || s.status === 'active' || s.status === 'busy';
@@ -21820,6 +21953,8 @@ const SessionDrawer = {
const name = document.createElement('span');
name.className = 'drawer-session-name';
+ name.dataset.sessionId = s.id;
+ name.title = 'Double-click to rename';
name.textContent = app.getSessionName(s);
const badge = document.createElement('span');
@@ -21862,6 +21997,15 @@ const SessionDrawer = {
}
});
+ row.addEventListener('dblclick', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (typeof window.getSelection === 'function') {
+ try { window.getSelection().removeAllRanges(); } catch(_) {}
+ }
+ SessionDrawer._startInlineRename(row, name, s);
+ });
+
return row;
},