# NousResearch/hermes-agent · Desktop sidebar section header overlap fix

## Open now

- Nothing open on GitHub.

These issues and pull requests address a bug where section headers in the desktop sidebar can overlap each other. The work involves CSS and layout changes to prevent this overlap behavior.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #43147. Verify it addresses your case.
- **Recommended:** read PR #43147 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #42987: Desktop sidebar: Sessions and messaging headers can overlap

## Prior & competing PRs

- **#43147: fix(desktop): sidebar sections never overlap — two-mode CSS scroll + collapse/cap groups**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx`, `apps/desktop/src/app/chat/sidebar/index.tsx`, `apps/desktop/src/app/chat/sidebar/load-more-row.tsx`, `apps/desktop/src/store/layout.ts`, `apps/desktop/src/styles.css`
  - fix hunks:
  - `apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx`

    ```diff
    @@ -14,6 +14,8 @@ import type { CronJob } from '@/types/hermes'
     import { jobState, jobTitle, STATE_DOT } from '../../cron/job-state'
     import { SidebarPanelLabel } from '../../shell/sidebar-label'
     
    +import { SidebarLoadMoreRow } from './load-more-row'
    +
     const INACTIVE_STATES = new Set(['completed', 'disabled', 'error', 'paused'])
     
     // Recent runs shown in the inline quick-peek — enough to glance at history
    ```

    ```diff
    @@ -24,6 +26,11 @@ const PEEK_RUN_LIMIT = 5
     // open peek so a freshly-fired run shows up within a few seconds.
     const PEEK_POLL_INTERVAL_MS = 8000
     
    +// Keep the section compact: show a few jobs up front, reveal more in larger
    +// steps on demand (mirrors the messaging sections in the sidebar).
    +const INITIAL_VISIBLE_JOBS = 3
    +const LOAD_MORE_STEP = 10
    +
     const relativeFmt = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto', style: 'short' })
     
     // Localized "in 5 min" / "2 hr ago" without hand-rolled strings — picks the
    ```

    ```diff
    @@ -33,17 +40,25 @@ function relativeTime(targetMs: number, nowMs: number): string {
       const abs = Math.abs(diff)
       const sign = diff < 0 ? -1 : 1
     
    -  if (abs < 60_000) {return relativeFmt.format(sign * Math.round(abs / 1000), 'second')}
    +  if (abs < 60_000) {
    +    return relativeFmt.format(sign * Math.round(abs / 1000), 'second')
    +  }
     
    -  if (abs < 3_600_000) {return relativeFmt.format(sign * Math.round(abs / 60_000), 'minute')}
    +  if (abs < 3_600_000) {
    +    return relativeFmt.format(sign * Math.round(abs / 60_000), 'minute')
    +  }
     
    -  if (abs < 86_400_000) {return relativeFmt.format(sign * Math.round(abs / 3_600_000), 'hour')}
    +  if (abs < 86_400_000) {
    +    return relativeFmt.format(sign * Math.round(abs / 3_600_000), 'hour')
    +  }
     
       return relativeFmt.format(sign * Math.round(abs / 86_400_000), 'day')
     }
     
     function nextRunMs(job: CronJob): null | number {
    -  if (!job.next_run_at) {return null}
    +  if (!job.next_run_at) {
    +    return null
    +  }
     
       const ms = Date.parse(job.next_run_at)
     
    ```

    ```diff
    @@ -54,7 +69,9 @@ function nextRunMs(job: CronJob): null | number {
     // the timestamp is what tells them apart. Compact (no year, no seconds) for the
     // narrow sidebar.
     function formatRunTime(seconds?: null | number): string {
    -  if (!seconds) {return '—'}
    +  if (!seconds) {
    +    return '—'
    +  }
     
       const date = new Date(seconds * 1000)
     
    ```

    ```diff
    @@ -90,11 +107,15 @@ export function SidebarCronJobsSection({
       const [nowMs, setNowMs] = useState(() => Date.now())
       // Single-open inline peek so the section stays scannable.
       const [peekJobId, setPeekJobId] = useState<null | string>(null)
    +  // Rows revealed so far; starts compact, grows in steps via "load more".
    +  const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_JOBS)
     
       // One clock for the whole section (rows are pure) so the countdowns tick
       // without re-rendering the rest of the sidebar. Only runs while expanded.
       useEffect(() => {
    -    if (!open) {return}
    +    if (!open) {
    +      return
    +    }
     
         const id = window.setInterval(() => setNowMs(Date.now()), 1000)
     
    ```

    ```diff
    @@ -108,17 +129,25 @@ export function SidebarCronJobsSection({
           const an = nextRunMs(a)
           const bn = nextRunMs(b)
     
    -      if (an !== null && bn !== null && an !== bn) {return an - bn}
    +      if (an !== null && bn !== null && an !== bn) {
    +        return an - bn
    +      }
     
    -      if (an === null && bn !== null) {return 1}
    +      if (an === null && bn !== null) {
    +        return 1
    +      }
     
    -      if (an !== null && bn === null) {return -1}
    +      if (an !== null && bn === null) {
    +        return -1
    +      }
     
           return jobTitle(a).localeCompare(jobTitle(b))
         })
       }, [jobs])
     
    -  const shown = sorted.slice(0, max)
    +  const cap = Math.min(visibleCount, max)
    +  const shown = sorted.slice(0, cap)
    +  const hiddenCount = Math.min(sorted.length, max) - shown.length
       // When capped, signal "50+" rather than implying the list is complete.
       const countLabel = jobs.length > max ? `${max}+` : String(jobs.length)
     
    ```

    ```diff
    @@ -139,7 +168,7 @@ export function SidebarCronJobsSection({
             </button>
           </div>
           {open && (
    -        <SidebarGroupContent className="flex max-h-72 shrink-0 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75">
    +        <SidebarGroupContent className="flex max-h-72 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75 compact:max-h-none compact:overflow-visible">
               {shown.map(job => (
                 <CronJobSidebarRow
                   expanded={peekJobId === job.id}
    ```

    ```diff
    @@ -152,6 +181,12 @@ export function SidebarCronJobsSection({
                   onTrigger={() => onTriggerJob(job.id)}
                 />
               ))}
    +          {hiddenCount > 0 && (
    +            <SidebarLoadMoreRow
    +              onClick={() => setVisibleCount(count => count + LOAD_MORE_STEP)}
    +              step={Math.min(LOAD_MORE_STEP, hiddenCount)}
    +            />
    +          )}
             </SidebarGroupContent>
           )}
         </SidebarGroup>
    ```

    ```diff
    @@ -181,11 +216,7 @@ function CronJobSidebarRow({
       const next = nextRunMs(job)
       const label = jobTitle(job)
     
    -  const meta = INACTIVE_STATES.has(state)
    -    ? (c.states[state] ?? state)
    -    : next !== null
    -      ? relativeTime(next, nowMs)
    -      : '—'
    +  const meta = INACTIVE_STATES.has(state) ? (c.states[state] ?? state) : next !== null ? relativeTime(next, nowMs) : '—'
     
       return (
         <div>
    ```

    ```diff
    @@ -257,13 +288,7 @@ function CronJobSidebarRow({
       )
     }
     
    -function CronJobSidebarRuns({
    -  jobId,
    -  onOpenRun
    -}: {
    -  jobId: string
    -  onOpenRun: (sessionId: string) => void
    -}) {
    +function CronJobSidebarRuns({ jobId, onOpenRun }: { jobId: string; onOpenRun: (sessionId: string) => void }) {
       const { t } = useI18n()
       const c = t.cron
       const selectedSessionId = useStore($selectedStoredSessionId)
    ```

    ```diff
    @@ -275,16 +300,22 @@ function CronJobSidebarRuns({
         const load = () =>
           getCronJobRuns(jobId, PEEK_RUN_LIMIT)
             .then(result => {
    -          if (!cancelled) {setRuns(result)}
    +          if (!cancelled) {
    +            setRuns(result)
    +          }
             })
             .catch(() => {
    -          if (!cancelled) {setRuns(prev => prev ?? [])}
    +          if (!cancelled) {
    +            setRuns(prev => prev ?? [])
    +          }
             })
     
         void load()
     
         const intervalId = window.setInterval(() => {
    -      if (document.visibilityState === 'visible') {void load()}
    +      if (document.visibilityState === 'visible') {
    +        void load()
    +      }
         }, PEEK_POLL_INTERVAL_MS)
     
         return () => {
    ```
  - `apps/desktop/src/app/chat/sidebar/index.tsx`

    ```diff
    @@ -48,6 +48,7 @@ import {
       $pinnedSessionIds,
       $sidebarAgentsGrouped,
       $sidebarCronOpen,
    +  $sidebarMessagingOpenIds,
       $sidebarOpen,
       $sidebarOverlayMounted,
       $sidebarPinsOpen,
    ```

    ```diff
    @@ -64,6 +65,7 @@ import {
       setSidebarSessionOrderIds,
       setSidebarWorkspaceOrderIds,
       SIDEBAR_SESSIONS_PAGE_SIZE,
    +  toggleSidebarMessagingOpen,
       unpinSession
     } from '@/store/layout'
     import {
    ```

    ```diff
    @@ -93,12 +95,19 @@ import { SidebarPanelLabel } from '../../shell/sidebar-label'
     import type { SidebarNavItem } from '../../types'
     
     import { SidebarCronJobsSection } from './cron-jobs-section'
    +import { SidebarLoadMoreRow } from './load-more-row'
     import { ProfileRail } from './profile-switcher'
     import { SidebarSessionRow } from './session-row'
     import { VirtualSessionList } from './virtual-session-list'
     
     const VIRTUALIZE_THRESHOLD = 25
     
    +// Non-session groups (messaging platforms) stay compact: show a few rows up
    +// front, reveal more in larger steps on demand. Keeps a busy platform from
    +// dominating the sidebar before the user asks to see it.
    +const NON_SESSION_INITIAL_ROWS = 3
    +const NON_SESSION_LOAD_STEP = 10
    +
     // Render the modifier key the user actually presses on this platform. The
     // global accelerator is bound to both Cmd+N (macOS) and Ctrl+N (everywhere
     // else) in desktop-controller.tsx, but the hint should match muscle memory.
    ```

    ```diff
    @@ -128,6 +137,16 @@ const WORKSPACE_PAGE = 5
     const PROFILE_INITIAL_PAGE = 5
     const GROUP_DND_ID_PREFIX = 'group:'
     
    +// Two modes via the `compact` height variant (styles.css):
    +//   tall    → each section is shrink-0, capped, its own scroller; Sessions is flex-1.
    +//   compact → COMPACT_FLAT drops the caps so the whole stack scrolls as one.
    +// Sections stay shrink-0 so none can be squeezed below its content and bleed onto
    +// the next — the flexbox `min-height: auto` overlap trap that caused the bug.
    +const COMPACT_FLAT = 'compact:max-h-none compact:overflow-visible'
    +
    +// A non-session group's scroll body: own scroller when tall, flattened when compact.
    +const GROUP_BODY = cn('overflow-y-auto overscroll-contain', COMPACT_FLAT)
    +
     const groupDndId = (id: string) => `${GROUP_DND_ID_PREFIX}${id}`
     
     const parseGroupDndId = (id: string) =>
    ```

    ```diff
    @@ -334,7 +353,9 @@ export function ChatSidebar({
       const [newSessionKbdFlash, setNewSessionKbdFlash] = useState(false)
       const [profileLoadMorePending, setProfileLoadMorePending] = useState<Record<string, boolean>>({})
       const [messagingLoadMorePending, setMessagingLoadMorePending] = useState<Record<string, boolean>>({})
    -  const [messagingOpen, setMessagingOpen] = useState<Record<string, boolean>>({})
    +  const messagingOpenIds = useStore($sidebarMessagingOpenIds)
    +  // Per-platform count of rows currently revealed (starts at NON_SESSION_INITIAL_ROWS).
    +  const [messagingVisible, setMessagingVisible] = useState<Record<string, number>>({})
       const searchInputRef = useRef<HTMLInputElement>(null)
       const trimmedQuery = searchQuery.trim()
     
    ```

    ```diff
    @@ -538,6 +559,18 @@ export function ChatSidebar({
         [onLoadMoreMessaging]
       )
     
    +  // Reveal another batch of a platform's rows; fetch from the backend too if we
    +  // run past what's loaded and more remain on disk.
    +  const revealMoreMessaging = (platform: string, loaded: number, hasMore: boolean) => {
    +    const next = (messagingVisible[platform] ?? NON_SESSION_INITIAL_ROWS) + NON_SESSION_LOAD_STEP
    +
    +    setMessagingVisible(prev => ({ ...prev, [platform]: next }))
    +
    +    if (next > loaded && hasMore) {
    +      loadMoreForMessaging(platform)
    +    }
    +  }
    +
       // Each messaging platform is its own self-managed section: split the
       // separately-fetched messaging slice by source, newest platform first, rows
       // within a platform by recency. Per-platform totals (when a "load more" has
    ```

    ```diff
    @@ -650,6 +683,12 @@ export function ChatSidebar({
     
       const displayAgentGroups = showAllProfiles ? profileGroups : agentsGrouped ? agentGroups : undefined
     
    +  // The recents list owns its own (virtualized) scroll container only when it's a
    +  // long flat list. In that case it must keep its scroller even in short mode, so
    +  // we don't flatten it (flattening would defeat virtualization). Short flat lists
    +  // and grouped views flatten into the single outer scroll instead.
    +  const recentsVirtualizes = !displayAgentGroups?.length && displayAgentSessions.length >= VIRTUALIZE_THRESHOLD
    +
       useEffect(() => {
         if (!displayAgentGroups?.length || showAllProfiles) {
           return
    ```

    ```diff
    @@ -781,9 +820,7 @@ export function ChatSidebar({
                           <item.icon className="size-4 shrink-0 text-[color-mix(in_srgb,currentColor_72%,transparent)]" />
                           {contentVisible && (
                             <>
    -                          <span className="min-w-0 flex-1 truncate">
    -                            {s.nav[item.id] ?? item.label}
    -                          </span>
    +                          <span className="min-w-0 flex-1 truncate">{s.nav[item.id] ?? item.label}</span>
                               {isNewSession && (
                                 <KbdGroup
                                   className={cn('ml-auto', newSessionKbdFlash && 'opacity-100!')}
    ```

    ```diff
    @@ -812,175 +849,191 @@ export function ChatSidebar({
               </div>
             )}
     
    -        {contentVisible && showSessionSections && trimmedQuery && (
    -          <SidebarSessionsSection
    -            activeSessionId={activeSidebarSessionId}
    -            contentClassName="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75"
    -            emptyState={
    -              <div className="grid min-h-24 place-items-center rounded-lg px-2 text-center text-xs text-(--ui-text-tertiary)">
    -                {s.noMatch(trimmedQuery)}
    -              </div>
    -            }
    -            label={s.results}
    -            labelMeta={String(searchResults.length)}
    -            onArchiveSession={onArchiveSession}
    -            onDeleteSession={onDeleteSession}
    -            onResumeSession={onResumeSession}
    -            onToggle={() => undefined}
    -            onTogglePin={pinSession}
    -            open
    -            pinned={false}
    -            rootClassName="min-h-0 flex-1 p-0"
    -            sessions={searchResults}
    -            workingSessionIdSet={workingSessionIdSet}
    -          />
    -        )}
    +        {contentVisible && showSessionSections && (
    +          <div className="flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain pb-1.75">
    +            {trimmedQuery && (
    +              <SidebarSessionsSection
    +                activeSessionId={activeSidebarSessionId}
    +                contentClassName="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75"
    +                emptyState={
    +                  <div className="grid min-h-24 place-items-center rounded-lg px-2 text-center text-xs text-(--ui-text-tertiary)">
    +                    {s.noMatch(trimmedQuery)}
    +                  </div>
    +                }
    +                label={s.results}
    +                labelMeta={String(searchResults.length)}
    +                onArchiveSession={onArchiveSession}
    +                onDeleteSession={onDeleteSession}
    +                onResumeSession={onResumeSession}
    +                onToggle={() => undefined}
    +                onTogglePin={pinSession}
    +                open
    +                pinned={false}
    +                rootClassName="min-h-32 flex-1 overflow-hidden p-0"
    +                sessions={searchResults}
    +                workingSessionIdSet={workingSessionIdSet}
    +              />
    +            )}
     
    -        {contentVisible && showSessionSections && !trimmedQuery && (
    -          <SidebarSessionsSection
    -            activeSessionId={activeSidebarSessionId}
    -            contentClassName="flex min-h-10 shrink-0 flex-col gap-px rounded-lg pb-2 pt-1"
    -            dndSensors={dndSensors}
    -            emptyState={<SidebarPinnedEmptyState />}
    -            label={s.pinned}
    -            onArchiveSession={onArchiveSession}
    -            onDeleteSession={onDeleteSession}
    -            onReorder={handlePinnedDragEnd}
    -            onResumeSession={onResumeSession}
    -            onToggle={() => setSidebarPinsOpen(!pinsOpen)}
    -            onTogglePin={unpinSession}
    -            open={pinsOpen}
    -            pinned
    -            rootClassName="shrink-0 p-0 pb-1"
    -            sessions={pinnedSessions}
    -            sortable={pinnedSessions.length > 1}
    -            workingSessionIdSet={workingSessionIdSet}
    -          />
    -        )}
    +            {!trimmedQuery && (
    +              <SidebarSessionsSection
    +                activeSessionId={activeSidebarSessionId}
    +                contentClassName={cn('flex max-h-44 flex-col gap-px rounded-lg pb-2 pt-1', GROUP_BODY)}
    +                dndSensors={dndSensors}
    +                emptyState={<SidebarPinnedEmptyState />}
    +                label={s.pinned}
    +                onArchiveSession={onArchiveSession}
    +                onDeleteSession={onDeleteSession}
    +                onReorder={handlePinnedDragEnd}
    +                onResumeSession={onResumeSession}
    +                onToggle={() => setSidebarPinsOpen(!pinsOpen)}
    +                onTogglePin={unpinSession}
    +                open={pinsOpen}
    +                pinned
    +                rootClassName="shrink-0 p-0 pb-1"
    +                sessions={pinnedSessions}
    +                sortable={pinnedSessions.length > 1}
    +                workingSessionIdSet={workingSessionIdSet}
    +              />
    +            )}
     
    -        {contentVisible && showSessionSections && !trimmedQuery && (
    -          <SidebarSessionsSection
    -            activeSessionId={activeSidebarSessionId}
    -            contentClassName={cn(
    -              'flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain pb-1.75',
    -              // Separate profile sections clearly in the ALL view; rows inside
    -              // each group keep their own tight gap-px rhythm.
    -              showAllProfiles ? 'gap-3' : 'gap-px'
    +            {!trimmedQuery && (
    +              <SidebarSessionsSection
    +                activeSessionId={activeSidebarSessionId}
    +                contentClassName={cn(
    +                  'flex min-h-0 flex-1 flex-col overflow-y-auto overscroll-contain pb-1.75',
    +                  // Separate profile sections clearly in the ALL view; rows inside
    +                  // each group keep their own tight gap-px rhythm.
    +                  showAllProfiles ? 'gap-3' : 'gap-px',
    +                  // Flatten into the single scroll when compact — unless this is the
    +                  // virtualized long list, which must keep its own scroller.
    +                  !recentsVirtualizes && COMPACT_FLAT
    +                )}
    +                dndSensors={dndSensors}
    +                emptyState={showSessionSkeletons ? <SidebarSessionSkeletons /> : <SidebarAllPinnedState />}
    +                footer={
    +                  // Hide "load more" only when workspace-grouped (those groups page
    +                  // themselves). ALL-profiles now pages per-profile from each profile
    +                  // header; the global footer only applies to non-ALL views.
    +                  !showAllProfiles && !agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
    +                    <SidebarLoadMoreRow
    +                      loading={sessionsLoading}
    +                      onClick={onLoadMoreSessions}
    +                      step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
    +                    />
    +                  ) : null
    +                }
    +                forceEmptyState={showSessionSkeletons}
    +                groups={displayAgentGroups}
    +                headerAction={
    +                  // Always reserve the icon-xs (size-6) slot so the header keeps the
    +                  // same height whether or not the toggle renders — otherwise the
    +                  // "Sessions" label jumps when switching to the ALL-profiles view.
    +                  // Grouping operates on unpinned recents; if everything is pinned
    +                  // the toggle does nothing, and it's irrelevant in the ALL-profiles
    +                  // view (always grouped by profile), so hide the button (not the slot).
    +                  <div className="grid size-6 shrink-0 place-items-center">
    +                    {!showAllProfiles && agentSessions.length > 0 ? (
    +                      <Tip label={agentsGrouped ? s.groupTitleGrouped : s.groupTitleUngrouped}>
    +                        <Button
    +                          aria-label={agentsGrouped ? s.groupAriaGrouped : s.groupAriaUngrouped}
    +                          className={cn(
    +                            'text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
    +                            agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
    +                          )}
    +                          onClick={event => {
    +                            event.stopPropagation()
    +                            setSidebarRecentsOpen(true)
    +                            setSidebarAgentsGrouped(!agentsGrouped)
    +                          }}
    +                          size="icon-xs"
    +                          variant="ghost"
    +                        >
    +                          <Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
    +                        </Button>
    +                      </Tip>
    +                    ) : null}
    +                  </div>
    +                }
    +                label={s.sessions}
    +                labelMeta={recentsMeta}
    +                onArchiveSession={onArchiveSession}
    +                onDeleteSession={onDeleteSession}
    +                onNewSessionInWorkspace={showAllProfiles ? undefined : onNewSessionInWorkspace}
    +                onReorder={showAllProfiles ? undefined : handleAgentDragEnd}
    +                onResumeSession={onResumeSession}
    +                onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
    +                onTogglePin={pinSession}
    +                open={agentsOpen}
    +                pinned={false}
    +                rootClassName={cn(
    +                  'min-h-32 flex-1 overflow-hidden p-0',
    +                  !recentsVirtualizes && 'compact:min-h-0 compact:flex-none compact:overflow-visible'
    +                )}
    +                sessions={displayAgentSessions}
    +                sortable={!showAllProfiles && agentSessions.length > 1}
    +                workingSessionIdSet={workingSessionIdSet}
    +              />
                 )}
    -            dndSensors={dndSensors}
    -            emptyState={showSessionSkeletons ? <SidebarSessionSkeletons /> : <SidebarAllPinnedState />}
    -            footer={
    -              // Hide "load more" only when workspace-grouped (those groups page
    -              // themselves). ALL-profiles now pages per-profile from each profile
    -              // header; the global footer only applies to non-ALL views.
    -              !showAllProfiles && !agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
    -                <SidebarLoadMoreRow
    -                  loading={sessionsLoading}
    -                  onClick={onLoadMoreSessions}
    -                  step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
    -                />
    -              ) : null
    -            }
    -            forceEmptyState={showSessionSkeletons}
    -            groups={displayAgentGroups}
    -            headerAction={
    -              // Always reserve the icon-xs (size-6) slot so the header keeps the
    -              // same height whether or not the toggle renders — otherwise the
    -              // "Sessions" label jumps when switching to the ALL-profiles view.
    -              // Grouping operates on unpinned recents; if everything is pinned
    -              // the toggle does nothing, and it's irrelevant in the ALL-profiles
    -              // view (always grouped by profile), so hide the button (not the slot).
    -              <div className="grid size-6 shrink-0 place-items-center">
    -                {!showAllProfiles && agentSessions.length > 0 ? (
    -                  <Tip label={agentsGrouped ? s.groupTitleGrouped : s.groupTitleUngrouped}>
    -                    <Button
    -                      aria-label={agentsGrouped ? s.groupAriaGrouped : s.groupAriaUngrouped}
    -                      className={cn(
    -                        'text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
    -                        agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
    -                      )}
    -                      onClick={event => {
    -                        event.stopPropagation()
    -                        setSidebarRecentsOpen(true)
    -                        setSidebarAgentsGrouped(!agentsGrouped)
    -                      }}
    -                      size="icon-xs"
    -                      variant="ghost"
    -                    >
    -                      <Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
    -                    </Button>
    -                  </Tip>
    -                ) : null}
    -              </div>
    -            }
    -            label={s.sessions}
    -            labelMeta={recentsMeta}
    -            onArchiveSession={onArchiveSession}
    -            onDeleteSession={onDeleteSession}
    -            onNewSessionInWorkspace={showAllProfiles ? undefined : onNewSessionInWorkspace}
    -            onReorder={showAllProfiles ? undefined : handleAgentDragEnd}
    -            onResumeSession={onResumeSession}
    -            onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
    -            onTogglePin={pinSession}
    -            open={agentsOpen}
    -            pinned={false}
    -            rootClassName="min-h-0 flex-1 p-0"
    -            sessions={displayAgentSessions}
    -            sortable={!showAllProfiles && agentSessions.length > 1}
    -            workingSessionIdSet={workingSessionIdSet}
    -          />
    -        )}
     
    -        {contentVisible && showSessionSections && !trimmedQuery &&
    -          messagingGroups.map(group => (
    -            <SidebarSessionsSection
    -              activeSessionId={activeSidebarSessionId}
    -              contentClassName="flex max-h-56 shrink-0 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75"
    -              emptyState={null}
    -              footer={
    -                group.hasMore ? (
    -                  <SidebarLoadMoreRow
    -                    loading={Boolean(messagingLoadMorePending[group.sourceId])}
    -                    onClick={() => loadMoreForMessaging(group.sourceId)}
    -                    step={Math.max(0, group.total - group.sessions.length)}
    +            {!trimmedQuery &&
    +              messagingGroups.map(group => {
    +                const visible = messagingVisible[group.sourceId] ?? NON_SESSION_INITIAL_ROWS
    +                const shownSessions = group.sessions.slice(0, visible)
    +                // More to show if rows are hidden behind the cap, or the backend
    +                // still has older threads on disk.
    +                const canRevealMore = visible < group.sessions.length || group.hasMore
    +
    +                return (
    +                  <SidebarSessionsSection
    +                    activeSessionId={activeSidebarSessionId}
    +                    contentClassName={cn('flex max-h-56 flex-col gap-px pb-1.75', GROUP_BODY)}
    +                    emptyState={null}
    +                    footer={
    +                      canRevealMore ? (
    +                        <SidebarLoadMoreRow
    +                          loading={Boolean(messagingLoadMorePending[group.sourceId])}
    +                          onClick={() => revealMoreMessaging(group.sourceId, group.sessions.length, group.hasMore)}
    +                          step={Math.min(NON_SESSION_LOAD_STEP, Math.max(0, group.total - shownSessions.length))}
    +                        />
    +                      ) : null
    +                    }
    +                    key={group.sourceId}
    +                    label={group.label}
    +                    labelIcon={
    +                      <PlatformAvatar
    +                        className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
    +                        platformId={group.sourceId}
    +                        platformName={group.label}
    +                      />
    +                    }
    +                    labelMeta={countLabel(group.sessions.length, group.total)}
    +                    onArchiveSession={onArchiveSession}
    +                    onDeleteSession={onDeleteSession}
    +                    onResumeSession={onResumeSession}
    +                    onToggle={() => toggleSidebarMessagingOpen(group.sourceId)}
    +                    onTogglePin={pinSession}
    +                    open={messagingOpenIds.includes(group.sourceId)}
    +                    pinned={false}
    +                    rootClassName="shrink-0 p-0"
    +                    sessions={shownSessions}
    +                    workingSessionIdSet={workingSessionIdSet}
                       />
    -                ) : null
    -              }
    -              key={group.sourceId}
    -              label={group.label}
    -              labelIcon={
    -                <PlatformAvatar
    -                  className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
    -                  platformId={group.sourceId}
    -                  platformName={group.label}
    -                />
    -              }
    -              labelMeta={countLabel(group.sessions.length, group.total)}
    -              onArchiveSession={onArchiveSession}
    -              onDeleteSession={onDeleteSession}
    -              onResumeSession={onResumeSession}
    -              onToggle={() =>
    -                setMessagingOpen(prev => ({ ...prev, [group.sourceId]: prev[group.sourceId] === false }))
    -              }
    -              onTogglePin={pinSession}
    -              open={messagingOpen[group.sourceId] !== false}
    -              pinned={false}
    -              rootClassName="shrink-0 p-0"
    -              sessions={group.sessions}
    -              workingSessionIdSet={workingSessionIdSet}
    -            />
    -          ))}
    -
    -        {contentVisible && !trimmedQuery && cronJobs.length > 0 && (
    -          <SidebarCronJobsSection
    -            jobs={cronJobs}
    -            label={s.cronJobs}
    -            onManageJob={onManageCronJob}
    -            onOpenRun={onResumeSession}
    -            onToggle={() => setSidebarCronOpen(!cronOpen)}
    -            onTriggerJob={onTriggerCronJob}
    -            open={cronOpen}
    -          />
    +                )
    +              })}
    +
    +            {!trimmedQuery && cronJobs.length > 0 && (
    +              <SidebarCronJobsSection
    +                jobs={cronJobs}
    +                label={s.cronJobs}
    +                onManageJob={onManageCronJob}
    +                onOpenRun={onResumeSession}
    +                onToggle={() => setSidebarCronOpen(!cronOpen)}
    +                onTriggerJob={onTriggerCronJob}
    +                open={cronOpen}
    +              />
    +            )}
    +          </div>
             )}
     
             {contentVisible && !showSessionSections && <div className="min-h-0 flex-1" />}
    ```

    ```diff
    @@ -1222,6 +1275,7 @@ function SidebarSessionsSection({
         inner = (
           <VirtualSessionList
             activeSessionId={activeSessionId}
    +        className={contentClassName}
             onArchiveSession={onArchiveSession}
             onDeleteSession={onDeleteSession}
             onResumeSession={onResumeSession}
    ```

    ```diff
    @@ -1446,30 +1500,3 @@ interface SortableSessionRowProps {
     function SortableSidebarSessionRow(props: SortableSessionRowProps) {
       return <SidebarSessionRow {...props} {...useSortableBindings(props.session.id)} />
     }
    -
    -interface SidebarLoadMoreRowProps {
    -  loading: boolean
    -  onClick: () => void
    -  step: number
    -}
    -
    -function SidebarLoadMoreRow({ loading, onClick, step }: SidebarLoadMoreRowProps) {
    -  const { t } = useI18n()
    -  const label = loading ? t.sidebar.loading : step > 0 ? t.sidebar.loadCount(step) : t.sidebar.loadMore
    -
    -  return (
    -    <button
    -      className="flex min-h-5 items-center gap-1.5 self-start bg-transparent pl-2 text-left text-[0.6875rem] text-(--ui-text-tertiary) transition-colors duration-100 ease-out hover:text-foreground hover:transition-none disabled:cursor-default disabled:opacity-60 disabled:hover:text-(--ui-text-tertiary)"
    -      disabled={loading}
    -      onClick={onClick}
    -      type="button"
    -    >
    -      {/* Seat the icon in the same w-3.5 column session rows use for their dot
    -          so the chevron + label line up with the rows above. */}
    -      <span className="grid w-3.5 shrink-0 place-items-center">
    -        <Codicon className="opacity-70" name={loading ? 'loading' : 'chevron-down'} size="0.75rem" spinning={loading} />
    -      </span>
    -      <span>{label}</span>
    -    </button>
    -  )
    -}
    ```
  - `apps/desktop/src/app/chat/sidebar/load-more-row.tsx`

    ```diff
    @@ -0,0 +1,30 @@
    +import { Codicon } from '@/components/ui/codicon'
    +import { useI18n } from '@/i18n'
    +
    +interface SidebarLoadMoreRowProps {
    +  step: number
    +  onClick: () => void
    +  loading?: boolean
    +}
    +
    +// "Load N more" affordance shared by the recents, messaging, and cron sections.
    +// The chevron sits in the same w-3.5 column the rows use for their dot, so it
    +// lines up with the list above.
    +export function SidebarLoadMoreRow({ step, onClick, loading = false }: SidebarLoadMoreRowProps) {
    +  const { t } = useI18n()
    +  const label = loading ? t.sidebar.loading : step > 0 ? t.sidebar.loadCount(step) : t.sidebar.loadMore
    +
    +  return (
    +    <button
    +      className="flex min-h-5 items-center gap-1.5 self-start bg-transparent pl-2 text-left text-[0.6875rem] text-(--ui-text-tertiary) transition-colors duration-100 ease-out hover:text-foreground hover:transition-none disabled:cursor-default disabled:opacity-60 disabled:hover:text-(--ui-text-tertiary)"
    +      disabled={loading}
    +      onClick={onClick}
    +      type="button"
    +    >
    +      <span className="grid w-3.5 shrink-0 place-items-center">
    +        <Codicon className="opacity-70" name={loading ? 'loading' : 'chevron-down'} size="0.75rem" spinning={loading} />
    +      </span>
    +      <span>{label}</span>
    +    </button>
    +  )
    +}
    ```
  - `apps/desktop/src/store/layout.ts`

    ```diff
    @@ -23,6 +23,7 @@ export const SIDEBAR_SESSIONS_PAGE_SIZE = 50
     const SIDEBAR_PINNED_STORAGE_KEY = 'hermes.desktop.pinnedSessions'
     const SIDEBAR_AGENTS_GROUPED_STORAGE_KEY = 'hermes.desktop.agentsGroupedByWorkspace'
     const SIDEBAR_CRON_OPEN_STORAGE_KEY = 'hermes.desktop.sidebarCronOpen'
    +const SIDEBAR_MESSAGING_OPEN_STORAGE_KEY = 'hermes.desktop.sidebarMessagingOpen'
     const SIDEBAR_SESSION_ORDER_STORAGE_KEY = 'hermes.desktop.sessionOrder'
     const SIDEBAR_WORKSPACE_ORDER_STORAGE_KEY = 'hermes.desktop.workspaceOrder'
     const PANES_FLIPPED_STORAGE_KEY = 'hermes.desktop.panesFlipped'
    ```

    ```diff
    @@ -68,6 +69,10 @@ export const $sidebarRecentsOpen = atom(true)
     // default (it only renders at all when cron sessions exist) so the
     // scheduler's `[IMPORTANT: …]` first-message previews don't spam recents.
     export const $sidebarCronOpen = atom(storedBoolean(SIDEBAR_CRON_OPEN_STORAGE_KEY, false))
    +// Messaging platform sections collapse by default (they can be numerous and
    +// tall). We persist the ids the user has *explicitly expanded*, so the default
    +// stays collapsed unless they've opened a platform before.
    +export const $sidebarMessagingOpenIds = atom<string[]>(storedStringArray(SIDEBAR_MESSAGING_OPEN_STORAGE_KEY))
     export const $sidebarAgentsGrouped = atom(storedBoolean(SIDEBAR_AGENTS_GROUPED_STORAGE_KEY, false))
     // When true, the sessions sidebar moves to the right and the file browser +
     // preview rail move to the left — a mirror of the default layout.
    ```

    ```diff
    @@ -77,6 +82,7 @@ export const $sessionsLimit = atom(SIDEBAR_SESSIONS_PAGE_SIZE)
     
     $pinnedSessionIds.subscribe(ids => persistStringArray(SIDEBAR_PINNED_STORAGE_KEY, [...ids]))
     $sidebarCronOpen.subscribe(open => persistBoolean(SIDEBAR_CRON_OPEN_STORAGE_KEY, open))
    +$sidebarMessagingOpenIds.subscribe(ids => persistStringArray(SIDEBAR_MESSAGING_OPEN_STORAGE_KEY, [...ids]))
     $sidebarSessionOrderIds.subscribe(ids => persistStringArray(SIDEBAR_SESSION_ORDER_STORAGE_KEY, [...ids]))
     $sidebarWorkspaceOrderIds.subscribe(ids => persistStringArray(SIDEBAR_WORKSPACE_ORDER_STORAGE_KEY, [...ids]))
     $sidebarAgentsGrouped.subscribe(grouped => persistBoolean(SIDEBAR_AGENTS_GROUPED_STORAGE_KEY, grouped))
    ```

    ```diff
    @@ -139,6 +145,14 @@ export function setSidebarCronOpen(open: boolean) {
       $sidebarCronOpen.set(open)
     }
     
    +export function toggleSidebarMessagingOpen(sourceId: string) {
    +  const current = $sidebarMessagingOpenIds.get()
    +
    +  $sidebarMessagingOpenIds.set(
    +    current.includes(sourceId) ? current.filter(id => id !== sourceId) : [...current, sourceId]
    +  )
    +}
    +
     export function setSidebarAgentsGrouped(grouped: boolean) {
       $sidebarAgentsGrouped.set(grouped)
     }
    ```
  - `apps/desktop/src/styles.css`

    ```diff
    @@ -5,6 +5,10 @@
     @import '@vscode/codicons/dist/codicon.css';
     @custom-variant dark (&:is(.dark *));
     
    +/* Sidebar sections: tall viewports give each its own scroller; compact ones
    +   (this variant) flatten everything into one shared scroll. See ChatSidebar. */
    +@custom-variant compact (@media (max-height: 768px));
    +
     @font-face {
       font-family: 'Collapse';
       font-style: normal;
    ```

    ```diff
    @@ -266,10 +270,12 @@
         --dt-user-bubble: var(--ui-chat-bubble-background);
         --dt-user-bubble-border: var(--ui-stroke-tertiary);
     
    -    --dt-font-sans: 'Segoe WPC', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif,
    -      'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji;
    -    --dt-font-mono: 'Cascadia Code', 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace,
    +    --dt-font-sans:
    +      'Segoe WPC', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif,
           'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji;
    +    --dt-font-mono:
    +      'Cascadia Code', 'JetBrains Mono', 'SF Mono', ui-monospace, Menlo, Consolas, monospace, 'Apple Color Emoji',
    +      'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji;
         --dt-base-size: 1rem;
         --dt-line-height: 1.5;
         --dt-letter-spacing: 0;
    ```

    ```diff
    @@ -914,7 +920,11 @@ canvas {
     
       display: block;
       inline-size: var(--fit-available-space);
    -  font-size: clamp(var(--fit-min, 1em), 1em * var(--fit-ratio), var(--fit-max, infinity * 1px) - var(--fit-support-sentinel));
    +  font-size: clamp(
    +    var(--fit-min, 1em),
    +    1em * var(--fit-ratio),
    +    var(--fit-max, infinity * 1px) - var(--fit-support-sentinel)
    +  );
     }
     
     @container (inline-size > 0) {
    ```
- **#42988: fix(desktop): prevent sidebar section header overlap**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `8bb65295532c7d353f081e3aaf63ed6f06ca1bd0`, `#43147`, `#42987`

## Fix status

- Merged to main in PR #43147 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-06-10`.
- Subsystem: `apps/desktop`
