feat: session naming, username/persona rename, help page, contrast fixes

- Session name field: PATCH /sessions/{id} endpoint, inline rename button in UI
- Persona rename: inline ✏ toggle form in settings, POST /settings/persona/rename
- Username rename: inline form in settings, POST /settings/username (renames home dir, forces re-login)
- Help page: dedicated /help route replacing modal, collapsible sections
- Per-persona isolation: files.py and session_store.py now scope to correct user/persona
- Contrast/visibility: muted text bumped to slate-400+, session rename btn at 0.4 opacity

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-23 23:10:12 -04:00
parent 1b425a539f
commit 0cf0d65e9e
10 changed files with 351 additions and 33 deletions

View File

@@ -235,8 +235,12 @@
}
});
// session_id → friendly name (populated on each panel render)
const sessionNames = new Map();
function renderPanel(sessions) {
sessionsPanel.innerHTML = '';
sessionNames.clear();
const newItem = makeItem('new', '+ New session', '');
newItem.addEventListener('click', () => {
@@ -259,13 +263,57 @@
}
for (const s of sessions) {
const displayName = s.name || s.session_id;
sessionNames.set(s.session_id, displayName);
const item = makeItem(
s.session_id === sessionId ? 'active' : '',
s.session_id,
displayName,
`${s.message_count} msgs · ${timeAgo(s.updated)}`
);
item.addEventListener('click', () => resumeSession(s.session_id));
// Rename button (✎)
const renameBtn = document.createElement('button');
renameBtn.className = 'session-rename-btn';
renameBtn.textContent = '✎';
renameBtn.title = 'Rename session';
renameBtn.addEventListener('click', async (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.placeholder = s.session_id;
labelEl.replaceWith(input);
input.focus();
input.select();
async function commitRename() {
const newName = input.value.trim();
await fetch(`/sessions/${s.session_id}?${_fileParams}`, {
method: 'PATCH',
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) {
sessionEl.textContent = `session: ${newName || s.session_id}`;
}
}
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); commitRename(); }
if (e.key === 'Escape') { renderPanel(sessions); }
});
input.addEventListener('blur', commitRename);
});
item.appendChild(renameBtn);
const delBtn = document.createElement('button');
delBtn.className = 'session-delete-btn';
delBtn.textContent = '×';
@@ -316,7 +364,7 @@
messagesEl.innerHTML = '';
sessionId = id;
sessionEl.textContent = `session: ${id}`;
sessionEl.textContent = `session: ${sessionNames.get(id) || id}`;
currentHistory = [];
for (let i = 0; i < data.messages.length; i++) {