feat: session rename UX overhaul
- Edit button (✎) moved to left of row, separated from delete (×) - Clicking ✎ hides name/meta/delete and expands input to full row width - Button changes to ✓ (accent color) while editing - Enter or ✓ click = save; Escape = cancel without saving - Removed accidental-save-on-blur behavior - Edit button: 30% opacity at rest, 75% on row hover, 100% on direct hover - Touch devices: edit button always at 60% opacity (no hover to reveal it) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -507,27 +507,58 @@
|
|||||||
const displayName = s.name || s.session_id;
|
const displayName = s.name || s.session_id;
|
||||||
sessionNames.set(s.session_id, displayName);
|
sessionNames.set(s.session_id, displayName);
|
||||||
|
|
||||||
const item = makeItem(
|
const item = document.createElement('div');
|
||||||
s.session_id === sessionId ? 'active' : '',
|
item.className = 'session-item' + (s.session_id === sessionId ? ' active' : '');
|
||||||
displayName,
|
|
||||||
`${s.message_count} msgs · ${timeAgo(s.updated)}`
|
|
||||||
);
|
|
||||||
item.addEventListener('click', () => resumeSession(s.session_id));
|
|
||||||
|
|
||||||
// Rename button (✎)
|
// ── Edit button (left) ──────────────────────────────────
|
||||||
const renameBtn = document.createElement('button');
|
const editBtn = document.createElement('button');
|
||||||
renameBtn.className = 'session-rename-btn';
|
editBtn.className = 'session-edit-btn';
|
||||||
renameBtn.textContent = '✎';
|
editBtn.textContent = '✎';
|
||||||
renameBtn.title = 'Rename session';
|
editBtn.title = 'Rename session';
|
||||||
renameBtn.addEventListener('click', async (e) => {
|
|
||||||
|
// ── Name label ──────────────────────────────────────────
|
||||||
|
const labelEl = document.createElement('span');
|
||||||
|
labelEl.className = 'session-id';
|
||||||
|
labelEl.textContent = displayName;
|
||||||
|
|
||||||
|
// ── Meta (right of name, before delete) ─────────────────
|
||||||
|
const metaEl = document.createElement('span');
|
||||||
|
metaEl.className = 'session-meta';
|
||||||
|
metaEl.textContent = `${s.message_count} msgs · ${timeAgo(s.updated)}`;
|
||||||
|
|
||||||
|
// ── Delete button (far right) ────────────────────────────
|
||||||
|
const delBtn = document.createElement('button');
|
||||||
|
delBtn.className = 'session-delete-btn';
|
||||||
|
delBtn.textContent = '×';
|
||||||
|
delBtn.title = 'Delete session';
|
||||||
|
|
||||||
|
item.append(editBtn, labelEl, metaEl, delBtn);
|
||||||
|
|
||||||
|
// Click anywhere on the row (not a button) → resume
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
if (!e.target.closest('button')) resumeSession(s.session_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Edit mode ────────────────────────────────────────────
|
||||||
|
function enterEditMode(e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const labelEl = item.querySelector('.session-id');
|
|
||||||
const current = s.name || '';
|
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.className = 'session-rename-input';
|
input.className = 'session-rename-input';
|
||||||
input.value = current;
|
input.value = s.name || '';
|
||||||
input.placeholder = s.session_id;
|
input.placeholder = s.session_id;
|
||||||
labelEl.replaceWith(input);
|
|
||||||
|
// Hide name, meta, delete — input takes their space
|
||||||
|
labelEl.hidden = true;
|
||||||
|
metaEl.hidden = true;
|
||||||
|
delBtn.hidden = true;
|
||||||
|
|
||||||
|
editBtn.textContent = '✓';
|
||||||
|
editBtn.title = 'Save name';
|
||||||
|
editBtn.className = 'session-save-btn';
|
||||||
|
editBtn.onclick = async (e) => { e.stopPropagation(); await commitRename(); };
|
||||||
|
|
||||||
|
editBtn.after(input);
|
||||||
input.focus();
|
input.focus();
|
||||||
input.select();
|
input.select();
|
||||||
|
|
||||||
@@ -538,28 +569,33 @@
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ name: newName }),
|
body: JSON.stringify({ name: newName }),
|
||||||
});
|
});
|
||||||
const res = await fetch(`/sessions?${_fileParams}`);
|
if (sessionId === s.session_id)
|
||||||
const data = await res.json();
|
|
||||||
renderPanel(data.sessions);
|
|
||||||
// Update status bar if this is the active session
|
|
||||||
if (sessionId === s.session_id) {
|
|
||||||
sessionEl.textContent = `session: ${newName || s.session_id}`;
|
sessionEl.textContent = `session: ${newName || s.session_id}`;
|
||||||
}
|
|
||||||
if (newName) showToast('Session renamed', 'success');
|
if (newName) showToast('Session renamed', 'success');
|
||||||
|
const res = await fetch(`/sessions?${_fileParams}`);
|
||||||
|
renderPanel((await res.json()).sessions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEdit() {
|
||||||
|
input.remove();
|
||||||
|
labelEl.hidden = false;
|
||||||
|
metaEl.hidden = false;
|
||||||
|
delBtn.hidden = false;
|
||||||
|
editBtn.textContent = '✎';
|
||||||
|
editBtn.title = 'Rename session';
|
||||||
|
editBtn.className = 'session-edit-btn';
|
||||||
|
editBtn.onclick = enterEditMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.addEventListener('keydown', (e) => {
|
input.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
||||||
if (e.key === 'Escape') { renderPanel(sessions); }
|
if (e.key === 'Escape') { e.preventDefault(); cancelEdit(); }
|
||||||
});
|
});
|
||||||
input.addEventListener('blur', commitRename);
|
}
|
||||||
});
|
|
||||||
item.appendChild(renameBtn);
|
|
||||||
|
|
||||||
const delBtn = document.createElement('button');
|
editBtn.onclick = enterEditMode;
|
||||||
delBtn.className = 'session-delete-btn';
|
|
||||||
delBtn.textContent = '×';
|
// ── Delete ───────────────────────────────────────────────
|
||||||
delBtn.title = 'Delete session';
|
|
||||||
delBtn.addEventListener('click', async (e) => {
|
delBtn.addEventListener('click', async (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
await fetch(`/sessions/${s.session_id}?${_fileParams}`, { method: 'DELETE' });
|
await fetch(`/sessions/${s.session_id}?${_fileParams}`, { method: 'DELETE' });
|
||||||
@@ -572,10 +608,8 @@
|
|||||||
showToast('Session deleted');
|
showToast('Session deleted');
|
||||||
}
|
}
|
||||||
const res = await fetch(`/sessions?${_fileParams}`);
|
const res = await fetch(`/sessions?${_fileParams}`);
|
||||||
const data = await res.json();
|
renderPanel((await res.json()).sessions);
|
||||||
renderPanel(data.sessions);
|
|
||||||
});
|
});
|
||||||
item.appendChild(delBtn);
|
|
||||||
|
|
||||||
sessionsPanel.appendChild(item);
|
sessionsPanel.appendChild(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@
|
|||||||
}
|
}
|
||||||
.session-delete-btn:hover { color: #e06c75; }
|
.session-delete-btn:hover { color: #e06c75; }
|
||||||
|
|
||||||
.session-rename-btn {
|
.session-edit-btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -330,13 +330,30 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
opacity: 0.4;
|
opacity: 0.3;
|
||||||
transition: opacity 0.15s, color 0.15s;
|
transition: opacity 0.15s, color 0.15s;
|
||||||
min-width: 24px;
|
min-width: 24px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.session-item:hover .session-rename-btn { opacity: 1; }
|
.session-item:hover .session-edit-btn { opacity: 0.75; }
|
||||||
.session-rename-btn:hover { color: var(--accent); }
|
.session-edit-btn:hover { color: var(--accent); opacity: 1; }
|
||||||
|
|
||||||
|
.session-save-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 2px 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
transition: opacity 0.15s;
|
||||||
|
}
|
||||||
|
.session-save-btn:hover { opacity: 0.75; }
|
||||||
|
|
||||||
.session-rename-input {
|
.session-rename-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1609,6 +1626,9 @@
|
|||||||
min-width: 36px;
|
min-width: 36px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* On touch: edit button always fully visible (no hover to reveal it) */
|
||||||
|
.session-edit-btn { opacity: 0.6; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user