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:
@@ -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++) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
border-bottom: 1px solid #2d3148;
|
||||
}
|
||||
header h1 { font-size: 1.5rem; font-weight: 700; color: #a78bfa; }
|
||||
header p { font-size: 0.85rem; color: #64748b; margin-top: 0.25rem; }
|
||||
header p { font-size: 0.85rem; color: #94a3b8; margin-top: 0.25rem; }
|
||||
|
||||
#help-body { line-height: 1.7; }
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
summary::before {
|
||||
content: '▶';
|
||||
font-size: 0.65rem;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
details[open] summary::before { transform: rotate(90deg); }
|
||||
@@ -89,13 +89,13 @@
|
||||
#help-body h3 {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin: 0.75rem 0 0.25rem;
|
||||
}
|
||||
|
||||
#loading { color: #64748b; font-size: 0.9rem; padding: 1rem 0; }
|
||||
#loading { color: #94a3b8; font-size: 0.9rem; padding: 1rem 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
|
||||
.logo p {
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
margin-bottom: 1.75rem;
|
||||
}
|
||||
.logo h1 { font-size: 1.4rem; font-weight: 700; color: #a78bfa; }
|
||||
.logo p { font-size: 0.8rem; color: #64748b; margin-top: 0.2rem; }
|
||||
.logo p { font-size: 0.8rem; color: #94a3b8; margin-top: 0.2rem; }
|
||||
|
||||
h2 {
|
||||
font-size: 0.9rem;
|
||||
@@ -73,7 +73,7 @@
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
input:focus { border-color: #7c3aed; }
|
||||
input[readonly] { color: #64748b; cursor: default; }
|
||||
input[readonly] { color: #94a3b8; cursor: default; }
|
||||
|
||||
.field { margin-bottom: 1rem; }
|
||||
|
||||
@@ -109,11 +109,17 @@
|
||||
.persona-list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.persona-list li a {
|
||||
.persona-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.persona-link {
|
||||
display: inline-block;
|
||||
padding: 0.3rem 0.75rem;
|
||||
background: #0f1117;
|
||||
@@ -124,14 +130,55 @@
|
||||
text-decoration: none;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
.persona-list li a:hover { border-color: #7c3aed; }
|
||||
.persona-list li em { color: #475569; font-size: 0.85rem; }
|
||||
.persona-link:hover { border-color: #7c3aed; }
|
||||
.persona-list li em { color: #94a3b8; font-size: 0.85rem; }
|
||||
|
||||
.persona-rename-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
}
|
||||
.persona-rename-toggle:hover { opacity: 1; color: #a78bfa; }
|
||||
|
||||
.persona-rename-form { display: flex; align-items: center; gap: 0.4rem; }
|
||||
.persona-rename-form input[type="text"] {
|
||||
width: 12rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: #0f1117;
|
||||
border: 1px solid #7c3aed;
|
||||
border-radius: 6px;
|
||||
color: #e2e8f0;
|
||||
font-size: 0.9rem;
|
||||
outline: none;
|
||||
}
|
||||
.persona-rename-form button[type="submit"] {
|
||||
width: auto;
|
||||
padding: 0.3rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
margin-top: 0;
|
||||
}
|
||||
.persona-rename-cancel {
|
||||
background: none;
|
||||
border: 1px solid #2d3148;
|
||||
border-radius: 6px;
|
||||
color: #94a3b8;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.3rem 0.6rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.persona-rename-cancel:hover { border-color: #94a3b8; color: #e2e8f0; }
|
||||
|
||||
.add-persona {
|
||||
display: inline-block;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: #64748b;
|
||||
color: #94a3b8;
|
||||
text-decoration: none;
|
||||
}
|
||||
.add-persona:hover { color: #a78bfa; }
|
||||
@@ -156,12 +203,33 @@
|
||||
<label>Username</label>
|
||||
<input type="text" value="{{ username }}" readonly>
|
||||
</div>
|
||||
<button type="button" id="show-rename-user" class="persona-rename-toggle"
|
||||
style="opacity:0.7; font-size:0.8rem; padding:0.3rem 0.6rem; border:1px solid #2d3148; border-radius:6px; margin-top:0.25rem;">
|
||||
✏ Change username
|
||||
</button>
|
||||
<form id="rename-user-form" method="POST" action="/settings/username"
|
||||
style="display:none; margin-top:0.75rem;">
|
||||
<div class="field">
|
||||
<label for="new_username">New username</label>
|
||||
<input type="text" id="new_username" name="new_username"
|
||||
value="{{ username }}"
|
||||
pattern="[a-z_][a-z0-9_\-]{0,31}" required autofocus>
|
||||
<p style="font-size:0.75rem; color:#94a3b8; margin-top:0.3rem;">
|
||||
Lowercase letters, digits, _ or - only. You will be logged out after renaming.
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:flex; gap:0.5rem;">
|
||||
<button type="submit" style="flex:1; padding:0.5rem; background:#7c3aed; border:none; border-radius:6px; color:#fff; font-size:0.9rem; font-weight:600; cursor:pointer;">Save</button>
|
||||
<button type="button" id="cancel-rename-user"
|
||||
style="padding:0.5rem 0.9rem; background:none; border:1px solid #2d3148; border-radius:6px; color:#94a3b8; font-size:0.9rem; cursor:pointer;">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Change password -->
|
||||
<div class="section">
|
||||
<h2>Change Password</h2>
|
||||
<form method="POST" action="/settings/password">
|
||||
<form method="POST" action="/settings/password" id="password-form">
|
||||
<div class="field">
|
||||
<label for="current_password">Current password</label>
|
||||
<input type="password" id="current_password" name="current_password"
|
||||
@@ -192,7 +260,8 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelector('form').addEventListener('submit', e => {
|
||||
// Password confirmation check
|
||||
document.getElementById('password-form').addEventListener('submit', e => {
|
||||
const np = document.getElementById('new_password').value;
|
||||
const cfm = document.getElementById('confirm_password').value;
|
||||
if (np !== cfm) {
|
||||
@@ -200,6 +269,37 @@
|
||||
alert('New passwords do not match.');
|
||||
}
|
||||
});
|
||||
|
||||
// Username rename toggle
|
||||
document.getElementById('show-rename-user').addEventListener('click', () => {
|
||||
document.getElementById('show-rename-user').style.display = 'none';
|
||||
document.getElementById('rename-user-form').style.display = 'block';
|
||||
document.getElementById('new_username').focus();
|
||||
});
|
||||
document.getElementById('cancel-rename-user').addEventListener('click', () => {
|
||||
document.getElementById('rename-user-form').style.display = 'none';
|
||||
document.getElementById('show-rename-user').style.display = '';
|
||||
});
|
||||
|
||||
// Persona rename toggle
|
||||
document.querySelectorAll('.persona-rename-toggle').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const p = btn.dataset.persona;
|
||||
const form = document.querySelector(`.persona-rename-form[data-persona="${p}"]`);
|
||||
btn.style.display = 'none';
|
||||
form.style.display = 'flex';
|
||||
form.querySelector('input[type="text"]').focus();
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.persona-rename-cancel').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const form = btn.closest('.persona-rename-form');
|
||||
const p = form.dataset.persona;
|
||||
const toggle = document.querySelector(`.persona-rename-toggle[data-persona="${p}"]`);
|
||||
form.style.display = 'none';
|
||||
toggle.style.display = '';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.logo h1 { font-size: 1.6rem; font-weight: 700; letter-spacing: 0.05em; color: #a78bfa; }
|
||||
.logo p { font-size: 0.8rem; color: #64748b; margin-top: 0.25rem; }
|
||||
.logo p { font-size: 0.8rem; color: #94a3b8; margin-top: 0.25rem; }
|
||||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
@@ -52,7 +52,7 @@
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
||||
|
||||
label small { font-weight: 400; color: #475569; }
|
||||
label small { font-weight: 400; color: #94a3b8; }
|
||||
|
||||
input, select {
|
||||
width: 100%;
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
.field { margin-bottom: 1rem; }
|
||||
|
||||
.hint { font-size: 0.75rem; color: #475569; margin-top: 0.3rem; }
|
||||
.hint { font-size: 0.75rem; color: #94a3b8; margin-top: 0.3rem; }
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
.step-label {
|
||||
font-size: 0.7rem;
|
||||
color: #475569;
|
||||
color: #94a3b8;
|
||||
text-align: right;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
--inara-border: #3d2a55;
|
||||
--accent: #c4935a;
|
||||
--text: #e8e0f0;
|
||||
--muted: #9080a8;
|
||||
--muted: #b0a2c8;
|
||||
--error-bg: #3b0f0f;
|
||||
--error-border: #7f1d1d;
|
||||
--error-text: #fca5a5;
|
||||
@@ -59,7 +59,7 @@
|
||||
--inara-border: #3d2a55;
|
||||
--accent: #c4935a;
|
||||
--text: #e8e0f0;
|
||||
--muted: #9080a8;
|
||||
--muted: #b0a2c8;
|
||||
--error-bg: #3b0f0f;
|
||||
--error-border: #7f1d1d;
|
||||
--error-text: #fca5a5;
|
||||
@@ -242,6 +242,37 @@
|
||||
}
|
||||
.session-delete-btn:hover { color: #e06c75; }
|
||||
|
||||
.session-rename-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
padding: 2px 6px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.4;
|
||||
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-rename-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
padding: 1px 5px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.session-id {
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
@@ -254,7 +285,7 @@
|
||||
}
|
||||
|
||||
.session-meta {
|
||||
font-size: 0.72rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
|
||||
Reference in New Issue
Block a user