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;
|
||||
sessionNames.set(s.session_id, displayName);
|
||||
|
||||
const item = makeItem(
|
||||
s.session_id === sessionId ? 'active' : '',
|
||||
displayName,
|
||||
`${s.message_count} msgs · ${timeAgo(s.updated)}`
|
||||
);
|
||||
item.addEventListener('click', () => resumeSession(s.session_id));
|
||||
const item = document.createElement('div');
|
||||
item.className = 'session-item' + (s.session_id === sessionId ? ' active' : '');
|
||||
|
||||
// Rename button (✎)
|
||||
const renameBtn = document.createElement('button');
|
||||
renameBtn.className = 'session-rename-btn';
|
||||
renameBtn.textContent = '✎';
|
||||
renameBtn.title = 'Rename session';
|
||||
renameBtn.addEventListener('click', async (e) => {
|
||||
// ── Edit button (left) ──────────────────────────────────
|
||||
const editBtn = document.createElement('button');
|
||||
editBtn.className = 'session-edit-btn';
|
||||
editBtn.textContent = '✎';
|
||||
editBtn.title = 'Rename session';
|
||||
|
||||
// ── 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();
|
||||
const labelEl = item.querySelector('.session-id');
|
||||
const current = s.name || '';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.className = 'session-rename-input';
|
||||
input.value = current;
|
||||
input.value = s.name || '';
|
||||
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.select();
|
||||
|
||||
@@ -538,28 +569,33 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: newName }),
|
||||
});
|
||||
const res = await fetch(`/sessions?${_fileParams}`);
|
||||
const data = await res.json();
|
||||
renderPanel(data.sessions);
|
||||
// Update status bar if this is the active session
|
||||
if (sessionId === s.session_id) {
|
||||
if (sessionId === s.session_id)
|
||||
sessionEl.textContent = `session: ${newName || s.session_id}`;
|
||||
}
|
||||
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) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
||||
if (e.key === 'Escape') { renderPanel(sessions); }
|
||||
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
|
||||
if (e.key === 'Escape') { e.preventDefault(); cancelEdit(); }
|
||||
});
|
||||
input.addEventListener('blur', commitRename);
|
||||
});
|
||||
item.appendChild(renameBtn);
|
||||
}
|
||||
|
||||
const delBtn = document.createElement('button');
|
||||
delBtn.className = 'session-delete-btn';
|
||||
delBtn.textContent = '×';
|
||||
delBtn.title = 'Delete session';
|
||||
editBtn.onclick = enterEditMode;
|
||||
|
||||
// ── Delete ───────────────────────────────────────────────
|
||||
delBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
await fetch(`/sessions/${s.session_id}?${_fileParams}`, { method: 'DELETE' });
|
||||
@@ -572,10 +608,8 @@
|
||||
showToast('Session deleted');
|
||||
}
|
||||
const res = await fetch(`/sessions?${_fileParams}`);
|
||||
const data = await res.json();
|
||||
renderPanel(data.sessions);
|
||||
renderPanel((await res.json()).sessions);
|
||||
});
|
||||
item.appendChild(delBtn);
|
||||
|
||||
sessionsPanel.appendChild(item);
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@
|
||||
}
|
||||
.session-delete-btn:hover { color: #e06c75; }
|
||||
|
||||
.session-rename-btn {
|
||||
.session-edit-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
@@ -330,13 +330,30 @@
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.4;
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
min-width: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.session-item:hover .session-rename-btn { opacity: 1; }
|
||||
.session-rename-btn:hover { color: var(--accent); }
|
||||
.session-item:hover .session-edit-btn { opacity: 0.75; }
|
||||
.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 {
|
||||
flex: 1;
|
||||
@@ -1609,6 +1626,9 @@
|
||||
min-width: 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) {
|
||||
|
||||
Reference in New Issue
Block a user