diff --git a/public/app.js b/public/app.js index fdefa1b6..54aa6d8e 100644 --- a/public/app.js +++ b/public/app.js @@ -36,6 +36,8 @@ const statusIndicator = document.getElementById('status-indicator'); const statusText = document.getElementById('status-text'); const sidebarEl = document.getElementById('sidebar'); const sidebarToggle = document.getElementById('sidebar-toggle'); +const sessionTitleHeaderEl = document.getElementById('session-title-header'); +const sessionTitleHeaderTextEl = document.getElementById('session-title-header-text'); const sidebarOverlay = document.getElementById('sidebar-overlay'); const refreshSessionsBtn = document.getElementById('refresh-sessions-btn'); @@ -255,10 +257,11 @@ function handleRPCEvent(event) { messageRenderer.renderError(`Extension error: ${event.error}`); break; case 'session_name': - // Auto-title: update sidebar with new session name + // Auto-title: update sidebar and header with new session name if (event.name) { const activeItem = document.querySelector('.session-item.active .session-title'); if (activeItem) activeItem.textContent = event.name; + updateHeaderSessionTitle(event.name); } break; } @@ -866,11 +869,56 @@ async function fetchModelInfo() { currentThinkingLevel = stateData.data.thinkingLevel; updateThinkingBtn(); } + if (stateData.success && stateData.data?.sessionName) { + updateHeaderSessionTitle(stateData.data.sessionName); + } } catch (e) { // ignore } } +function updateHeaderSessionTitle(name) { + sessionTitleHeaderTextEl.textContent = name || 'New Session'; +} + +function startHeaderRename() { + if (isMirrorMode && !viewingActiveSession) return; + if (sessionTitleHeaderEl.querySelector('.session-title-header-input')) return; + const currentName = sessionTitleHeaderTextEl.textContent; + + const input = document.createElement('input'); + input.className = 'session-title-header-input'; + input.value = currentName; + sessionTitleHeaderEl.replaceChild(input, sessionTitleHeaderTextEl); + input.focus(); + input.select(); + + const commit = async () => { + const newName = input.value.trim(); + if (newName && newName !== currentName) { + try { + await fetch('/api/rpc', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ type: 'set_session_name', name: newName }), + }); + } catch { /* silent */ } + } + sessionTitleHeaderTextEl.textContent = newName || currentName; + input.replaceWith(sessionTitleHeaderTextEl); + const activeItem = document.querySelector('.session-item.active .session-title'); + if (activeItem && newName) activeItem.textContent = newName; + }; + + input.addEventListener('blur', commit); + input.addEventListener('keydown', (ke) => { + if (ke.key === 'Enter') { ke.preventDefault(); input.blur(); } + if (ke.key === 'Escape') { input.value = currentName; input.blur(); } + }); +} + +sessionTitleHeaderEl.addEventListener('click', startHeaderRename); + function updateModelLabel() { const shortName = currentModelId.replace(/^claude-/, '').replace(/-\d{8}$/, ''); modelDropdownLabel.textContent = shortName || 'model'; @@ -1113,6 +1161,7 @@ async function newSession() { lastInputTokens = 0; updateCostDisplay(); updateTokenUsage(); + updateHeaderSessionTitle(null); await switchSession(null); sidebar.clearActive(); if (isMobile()) { @@ -1128,6 +1177,7 @@ async function handleSessionSelect(session, project) { lastInputTokens = 0; updateCostDisplay(); updateTokenUsage(); + updateHeaderSessionTitle(session.name || session.firstMessage || 'Empty session'); await switchSession(session.filePath, session, project); // Close sidebar on mobile after selecting @@ -1231,6 +1281,7 @@ function handleMirrorSync(data) { viewingActiveSession = true; updateMirrorInputState(); updateMirrorLiveIndicator(); + updateHeaderSessionTitle(data.sessionName); // Update model display if (data.model) { @@ -1291,6 +1342,7 @@ pollInstances(); // Enable/disable input based on whether we're viewing the live session function updateMirrorInputState() { + updateHeaderRenameAffordance(); if (!isMirrorMode) return; const inputArea = document.querySelector('.input-area'); @@ -1305,6 +1357,16 @@ function updateMirrorInputState() { } } +// Renaming only ever affects pi's currently live session, so only allow it +// from the header while that live session is the one being viewed. +function updateHeaderRenameAffordance() { + const renamable = !isMirrorMode || viewingActiveSession; + sessionTitleHeaderEl.classList.toggle('readonly', !renamable); + sessionTitleHeaderEl.title = renamable + ? 'Click to rename' + : 'Only the active session can be renamed here'; +} + // ═══════════════════════════════════════ // Session history rendering // ═══════════════════════════════════════ diff --git a/public/index.html b/public/index.html index 9531c217..afaefe84 100644 --- a/public/index.html +++ b/public/index.html @@ -45,6 +45,11 @@