diff --git a/static/sessions.js b/static/sessions.js index a065dd6..121a2ca 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -198,26 +198,53 @@ function renderSessionListFromCache(){ // Date grouping: Pinned / Today / Yesterday / Earlier const now=Date.now(); const ONE_DAY=86400000; - let lastGroup=''; const ordered=[...pinned,...unpinned].slice(0,50); - if(pinned.length){ - const hdr=document.createElement('div'); - hdr.style.cssText='font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#f5c542;padding:10px 10px 4px;opacity:.9;'; - hdr.textContent='\u2605 Pinned'; - list.appendChild(hdr); + // Collapse state persisted in localStorage + let _groupCollapsed={}; + try{_groupCollapsed=JSON.parse(localStorage.getItem('hermes-date-groups-collapsed')||'{}');}catch(e){} + const _saveCollapsed=()=>{try{localStorage.setItem('hermes-date-groups-collapsed',JSON.stringify(_groupCollapsed));}catch(e){}}; + // Group sessions by date + const groups=[]; + let curLabel=null,curItems=[]; + if(pinned.length) groups.push({label:'\u2605 Pinned',items:pinned,isPinned:true}); + for(const s of unpinned){ + const ts=(s.updated_at||s.created_at||0)*1000; + const label=ts>now-ONE_DAY?'Today':ts>now-2*ONE_DAY?'Yesterday':'Earlier'; + if(label!==curLabel){ + if(curItems.length) groups.push({label:curLabel,items:curItems}); + curLabel=label;curItems=[s]; + } else { curItems.push(s); } } - for(const s of ordered){ - if(!s.pinned){ - const ts=(s.updated_at||s.created_at||0)*1000; // group by last activity, not creation - const group=ts>now-ONE_DAY?'Today':ts>now-2*ONE_DAY?'Yesterday':'Earlier'; - if(group!==lastGroup){ - lastGroup=group; - const hdr=document.createElement('div'); - hdr.style.cssText='font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);padding:10px 10px 4px;opacity:.8;'; - hdr.textContent=group; - list.appendChild(hdr); - } - } + if(curItems.length) groups.push({label:curLabel,items:curItems}); + // Render groups with collapsible headers + for(const g of groups){ + const wrapper=document.createElement('div'); + wrapper.className='session-date-group'; + const hdr=document.createElement('div'); + hdr.className='session-date-header'+(g.isPinned?' pinned':''); + const caret=document.createElement('span'); + caret.className='session-date-caret'; + caret.textContent='\u25B8'; // right-pointing triangle + const label=document.createElement('span'); + label.textContent=g.label; + hdr.appendChild(caret);hdr.appendChild(label); + const body=document.createElement('div'); + body.className='session-date-body'; + if(_groupCollapsed[g.label]){body.style.display='none';caret.classList.add('collapsed');} + hdr.onclick=()=>{ + const isCollapsed=body.style.display==='none'; + body.style.display=isCollapsed?'':'none'; + caret.classList.toggle('collapsed',!isCollapsed); + _groupCollapsed[g.label]=!isCollapsed; + _saveCollapsed(); + }; + wrapper.appendChild(hdr); + for(const s of g.items){ body.appendChild(_renderOneSession(s)); } + wrapper.appendChild(body); + list.appendChild(wrapper); + } + // ── Render session items (extracted for group body use) ── + function _renderOneSession(s){ const el=document.createElement('div'); const isActive=S.session&&s.session_id===S.session.session_id; el.className='session-item'+(isActive?' active':'')+(isActive&&S.session&&S.session._flash?' new-flash':'')+(s.archived?' archived':'')+(s.is_cli_session?' cli-session':''); @@ -385,7 +412,7 @@ function renderSessionListFromCache(){ _clickTimer=null; startRename(); }; - list.appendChild(el); + return el; } } diff --git a/static/style.css b/static/style.css index db72b53..fa8c5b0 100644 --- a/static/style.css +++ b/static/style.css @@ -37,6 +37,12 @@ .session-item:has(.session-title-input) .session-actions{display:none;} @keyframes newflash{0%{background:rgba(124,185,255,0.22);color:var(--blue);}100%{background:transparent;color:var(--muted);}} .session-item.new-flash{animation:newflash 1.4s ease-out forwards;} + /* Collapsible date group headers */ + .session-date-header{display:flex;align-items:center;gap:5px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);padding:8px 10px 4px;cursor:pointer;user-select:none;opacity:.8;transition:opacity .15s;} + .session-date-header:hover{opacity:1;} + .session-date-header.pinned{color:#f5c542;} + .session-date-caret{font-size:9px;transition:transform .2s;flex-shrink:0;display:inline-block;} + .session-date-caret.collapsed{transform:rotate(-90deg);} .toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(20,30,50,.95);backdrop-filter:blur(12px);border:1px solid rgba(124,185,255,0.25);color:var(--text);font-size:13px;padding:10px 20px;border-radius:12px;pointer-events:none;opacity:0;transition:opacity .2s,transform .2s;z-index:100;box-shadow:0 4px 20px rgba(0,0,0,.3);letter-spacing:.01em;} .toast.show{opacity:1;transform:translateX(-50%) translateY(-2px);} .reconnect-banner{display:none;background:#1a2535;border:1px solid rgba(201,168,76,0.4);border-radius:10px;padding:10px 16px;margin:10px auto;max-width:780px;font-size:13px;color:var(--gold);display:none;align-items:center;justify-content:space-between;gap:12px;}