diff --git a/packages/components/src/components/loro-sidebar.tsx b/packages/components/src/components/loro-sidebar.tsx
index fc3707548..9f8a97828 100644
--- a/packages/components/src/components/loro-sidebar.tsx
+++ b/packages/components/src/components/loro-sidebar.tsx
@@ -64,6 +64,19 @@ import { useStableNow } from '@/hooks/use-stable-now';
export type LoroSidebarNavKey = 'home' | 'archive' | 'tasks';
+/** One utility in the footer rail. `filter` is the mobile-only organize/scope
+ * popover; the other three are the footer's icon buttons. */
+export type LoroSidebarFooterItem = 'settings' | 'help' | 'archive' | 'filter';
+
+/** Every footer utility, which is what the footer renders when a host says
+ * nothing. Declared once so the default cannot drift from the union. */
+export const LORO_SIDEBAR_FOOTER_ITEMS: readonly LoroSidebarFooterItem[] = [
+ 'settings',
+ 'help',
+ 'archive',
+ 'filter',
+];
+
export type LoroSidebarChatScope = 'my' | 'team';
export type LoroSidebarOrganizeMode = SidebarOrganizeMode;
@@ -167,6 +180,32 @@ export interface LoroSidebarProps {
afterSessionListContent?: ReactNode;
bottomFloatingContent?: ReactNode;
+ /**
+ * Suppresses the workspace-identity header row. A host that already renders
+ * its own workspace header — an embedder mounting the sidebar body inside an
+ * existing shell — would otherwise stack two of them.
+ */
+ hideHeader?: boolean;
+ /**
+ * Suppresses the footer utility rail (settings, help, archive, and on mobile
+ * the filter popover). Same reason as {@link hideHeader}: a host that serves
+ * those entries from its own chrome would otherwise render them twice. On
+ * mobile this also removes the only filter trigger, so a host that hides the
+ * footer owns the organize/scope controls too.
+ */
+ hideFooter?: boolean;
+ /**
+ * Which footer utilities render, when the footer renders at all.
+ *
+ * {@link hideFooter} is all-or-nothing, and a host that serves its own
+ * settings and its own help — but NOT its own archive — has no way to keep the
+ * one entry it wants. Listing the items is that way: the default is every
+ * item, so a host that says nothing renders exactly what it rendered before.
+ * Ignored while {@link hideFooter} is set, which stays the shorter spelling
+ * for "none of them".
+ */
+ footerItems?: readonly LoroSidebarFooterItem[];
+
repoSections?: LoroSidebarRepoSection[];
chats?: LoroSidebarChatItem[];
sessionListProps?: SessionListProps;
@@ -631,6 +670,9 @@ export const LoroSidebar = memo(function LoroSidebar({
desktopFilterPlaceholder,
afterSessionListContent,
bottomFloatingContent,
+ hideHeader = false,
+ hideFooter = false,
+ footerItems = LORO_SIDEBAR_FOOTER_ITEMS,
repoSections = defaultRepoSections,
chats = defaultChats,
sessionListProps,
@@ -873,118 +915,120 @@ export const LoroSidebar = memo(function LoroSidebar({
!isMobile && 'p-[2px]'
)}
>
-
- )}
- {/* Collapse toggle anchored to the header's top-right corner.
- `top-2` centers the h-7 button inside the standard h-11 header.
- On macOS Electron the header is taller (`h-[72px] pt-7`) and its
- top sits 11px below the window top (card `mt-2` + 1px border +
- inner `p-[2px]`); `-top-0.5` then puts the button center at
- 11 - 2 + 14 = 23px, exactly on the traffic-light centerline
- (`trafficLightPosition.y` 16 + 7px radius in
- apps/electron/src/main/window.ts) — and level with the
- collapsed-state expand button (`top-[9px]` in
- web-chat-landing-screen.tsx), so the control stays put across
- collapse/expand. */}
- {!isMobile && onRequestCollapse ? (
-
- ) : null}
-
+ )}
+ {/* Collapse toggle anchored to the header's top-right corner.
+ `top-2` centers the h-7 button inside the standard h-11 header.
+ On macOS Electron the header is taller (`h-[72px] pt-7`) and its
+ top sits 11px below the window top (card `mt-2` + 1px border +
+ inner `p-[2px]`); `-top-0.5` then puts the button center at
+ 11 - 2 + 14 = 23px, exactly on the traffic-light centerline
+ (`trafficLightPosition.y` 16 + 7px radius in
+ apps/electron/src/main/window.ts) — and level with the
+ collapsed-state expand button (`top-[9px]` in
+ web-chat-landing-screen.tsx), so the control stays put across
+ collapse/expand. */}
+ {!isMobile && onRequestCollapse ? (
+
+ ) : null}
+