diff --git a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md
index 42bf3fb8..f3e66647 100644
--- a/packages/ui/src/components/session/sidebar/DOCUMENTATION.md
+++ b/packages/ui/src/components/session/sidebar/DOCUMENTATION.md
@@ -29,7 +29,7 @@
- `SidebarHeader.tsx`: Top header UI for add-project, session search, selection mode, project sort, and the display menu (recent toggle, collapse/expand all).
- A successful add/create/clone from the project-directory dialog transitions to a new-session draft targeted at that project, matching the project's `+` action; changing project metadata alone must not leave the visible session or draft on a different directory.
- `SidebarNav.tsx`: Text navigation rows above the tree (New session, Scheduled, Multi-run, Archive); hidden in VS Code.
-- `SidebarActivitySections.tsx`: Global top section renderer for project-only `recent` sessions followed by OpenChamber-managed `chats`, styled as zone headers.
+- `SidebarActivitySections.tsx`: Global top section renderer for OpenChamber-managed `chats` followed by optional project-only `recent` sessions, styled as zone headers. The desktop sticky identity overlay follows the activity header whose sentinel has crossed the scroller edge, so a small scroll cannot relabel Chats as Recent.
- `SidebarFooter.tsx`: Static footer with icon-only settings, shortcuts, and about actions.
- `SidebarProjectsList.tsx`: Main scrollable renderer for project zones and their flat/archived groups plus empty/search states; owns project drag-to-reorder.
- `SessionGroupSection.tsx`: Renders one flat (or archived) group: sessions first, then flat folder entries with path labels, show-more batching, and explicit loading/error/retry state for empty groups. Archived buckets (VS Code) virtualize past 50 rows.
diff --git a/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx b/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
index d3f95a35..d61b66b3 100644
--- a/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
+++ b/packages/ui/src/components/session/sidebar/SidebarActivitySections.tsx
@@ -183,6 +183,11 @@ export function SidebarActivitySections(props: Props): React.ReactNode {
return (
+
{
+ const key = element.getAttribute('data-sidebar-activity-sentinel');
+ if (key === 'chats' || key === 'active-now') return key;
+ return null;
+};
const getProjectLabel = (project: ProjectSection['project'], homeDirectory: string | null): string => (
formatProjectLabel(
@@ -135,6 +142,7 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
// can resolve the scrolling ancestor synchronously (no getComputedStyle
// walk) and skip the cost of a style recalc on every render.
const scrollContainerRef = React.useRef
(null);
+ const [leadingActivitySection, setLeadingActivitySection] = React.useState('chats');
// Keep per-scroll measurements out of React state so the interaction guard
// can read the current fade boundary without rerendering the sidebar.
const topFadeSizeRef = React.useRef(0);
@@ -167,6 +175,38 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
syncTopFade(scrollContainerRef.current);
}
}, [enableStickyFade, hasProjectScroller, syncTopFade]);
+ React.useEffect(() => {
+ const root = scrollContainerRef.current;
+ if (!enableStickyFade || !root || !props.hasSharedSessions) return;
+
+ const sentinels = Array.from(root.querySelectorAll('[data-sidebar-activity-sentinel]'));
+ if (sentinels.length === 0) return;
+ const stuckSections = new Set();
+ const syncLeadingSection = (): void => {
+ let nextSection = sentinels[0] ? readActivitySectionKey(sentinels[0]) : null;
+ for (const sentinel of sentinels) {
+ const key = readActivitySectionKey(sentinel);
+ if (key && stuckSections.has(key)) nextSection = key;
+ }
+ if (nextSection) setLeadingActivitySection((current) => current === nextSection ? current : nextSection);
+ };
+ const observer = new IntersectionObserver((entries) => {
+ const rootTop = root.getBoundingClientRect().top;
+ for (const entry of entries) {
+ const key = readActivitySectionKey(entry.target);
+ if (!key) continue;
+ if (!entry.isIntersecting && entry.boundingClientRect.top < (entry.rootBounds?.top ?? rootTop)) {
+ stuckSections.add(key);
+ } else {
+ stuckSections.delete(key);
+ }
+ }
+ syncLeadingSection();
+ }, { root, threshold: 0 });
+ sentinels.forEach((sentinel) => observer.observe(sentinel));
+ syncLeadingSection();
+ return () => observer.disconnect();
+ }, [enableStickyFade, props.hasSharedSessions, props.topContent]);
let stuckProject: ProjectSection['project'] | null = null;
for (const section of props.projectSections) {
if (props.stuckProjectHeaders.has(section.project.id)) {
@@ -393,9 +433,11 @@ function SidebarProjectsListComponent(props: Props): React.ReactNode {
/>
) : (
<>
-
+
- {t('sessions.sidebar.activity.recentTitle')}
+ {t(leadingActivitySection === 'chats'
+ ? 'sessions.sidebar.activity.chatsTitle'
+ : 'sessions.sidebar.activity.recentTitle')}
>
)}