diff --git a/static/channels.js b/static/channels.js index fd577d1..943cc9a 100644 --- a/static/channels.js +++ b/static/channels.js @@ -308,9 +308,14 @@ function switchChannel(name) { const savedId = _channelScrollMsg[name]; if (savedId) { const el = document.querySelector(`.message[data-id="${savedId}"]`); - if (el) { el.scrollIntoView({ block: 'start' }); return; } + if (el) { + el.scrollIntoView({ block: 'start' }); + if (typeof dayFloatRefresh === 'function') dayFloatRefresh(); + return; + } } window.scrollToBottom(); + if (typeof dayFloatRefresh === 'function') dayFloatRefresh(); } function filterMessagesByChannel() { diff --git a/static/chat.js b/static/chat.js index df48a03..f12c286 100644 --- a/static/chat.js +++ b/static/chat.js @@ -577,7 +577,8 @@ function connectWebSocket() { const container = document.getElementById('messages'); const toRemove = []; for (const el of container.children) { - if (el.dataset.id && (el.dataset.channel || 'general') === clearChannel) { + const isDivider = el.classList.contains('date-divider'); + if ((el.dataset.id || isDivider) && (el.dataset.channel || 'general') === clearChannel) { toRemove.push(el); } } @@ -591,6 +592,7 @@ function connectWebSocket() { lastMessageDate = null; lastMessageDates = {}; } + dayFloatRefresh(); requestAnimationFrame(() => { const _clearDbgAfter = _clearDbgList ? _clearDbgList.children.length : -1; console.log('CLEAR_DEBUG after clear (next frame), jobs-panel-children=' + _clearDbgAfter); @@ -666,6 +668,69 @@ function maybeInsertDateDivider(container, msg) { } } +// --- Floating day indicator --- + +let dayFloatDividers = null; // live HTMLCollection — follows divider add/remove +let dayFloatFadeTimer = null; +let dayFloatRafPending = false; + +function dayFloatLabel() { + if (!dayFloatDividers) { + const container = document.getElementById('messages'); + if (!container) return null; + dayFloatDividers = container.getElementsByClassName('date-divider'); + } + const scroll = document.getElementById('timeline'); + if (!scroll) return null; + const top = scroll.getBoundingClientRect().top; + let label = null; + for (const d of dayFloatDividers) { + if (d.style.display === 'none') continue; + if (d.getBoundingClientRect().top <= top) { + label = d.textContent; + } else { + break; + } + } + return label; +} + +function dayFloatHide() { + const float = document.getElementById('day-float'); + if (!float) return; + if (dayFloatFadeTimer) { clearTimeout(dayFloatFadeTimer); dayFloatFadeTimer = null; } + float.classList.remove('visible'); +} + +function dayFloatOnScroll() { + if (dayFloatRafPending) return; + dayFloatRafPending = true; + requestAnimationFrame(() => { + dayFloatRafPending = false; + const float = document.getElementById('day-float'); + if (!float) return; + const label = dayFloatLabel(); + if (!label) { dayFloatHide(); return; } + float.querySelector('span').textContent = label; + float.classList.add('visible'); + if (dayFloatFadeTimer) clearTimeout(dayFloatFadeTimer); + dayFloatFadeTimer = setTimeout(() => { + dayFloatFadeTimer = null; + float.classList.remove('visible'); + }, 1200); + }); +} + +function dayFloatRefresh() { + // Recompute without starting the show/fade cycle: swap the label if the + // pill is relevant, hide (cancelling any fade) if no day is above the top. + const float = document.getElementById('day-float'); + if (!float) return; + const label = dayFloatLabel(); + if (!label) { dayFloatHide(); return; } + float.querySelector('span').textContent = label; +} + // --- Messages --- function appendMessage(msg) { @@ -2499,6 +2564,7 @@ function setupScroll() { unreadCount = 0; } updateScrollAnchor(); + dayFloatOnScroll(); }); // Keep pinned to bottom when content changes (e.g. images load) diff --git a/static/index.html b/static/index.html index 955feb8..0cf03c2 100644 --- a/static/index.html +++ b/static/index.html @@ -4,7 +4,7 @@ agentchattr - + @@ -172,6 +172,7 @@

agentchattr

+
Loading messages... @@ -342,8 +343,8 @@

agentchattr

- + - + diff --git a/static/style.css b/static/style.css index 0871d58..3064f09 100644 --- a/static/style.css +++ b/static/style.css @@ -2553,6 +2553,38 @@ body.channels-in-sidebar #channel-bar { background: var(--border); } +/* --- Floating day indicator --- */ + +#day-float { + position: sticky; + top: 8px; + height: 0; + display: flex; + align-items: flex-start; + justify-content: center; + z-index: 6; + pointer-events: none; +} + +#day-float span { + background: var(--bg-msg); + border: 1px solid var(--border); + border-radius: var(--radius-full); + padding: 3px 10px; + color: var(--text-dim); + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + text-indent: 0.06em; + opacity: 0; + transition: opacity 0.25s ease; +} + +#day-float.visible span { + opacity: 1; +} + /* --- Code copy button --- */ .code-copy-btn {