diff --git a/cortex/static/app.js b/cortex/static/app.js index 7c7ff02..7ede668 100644 --- a/cortex/static/app.js +++ b/cortex/static/app.js @@ -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); } diff --git a/cortex/static/style.css b/cortex/static/style.css index 3948bf3..39a372b 100644 --- a/cortex/static/style.css +++ b/cortex/static/style.css @@ -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) {