qwen-code/docs/developers/daemon-ui/sidebar-customization.md
yuanyuanAli ff14e65792
feat(web-shell): Add sidebar customization API for branding, navigation, session actions, and footer (#7379)
* feat(web-shell): Add sidebar customization API for branding, navigation, session actions, and footer

Add new sidebar configuration options to WebShellSidebarOptions:

- primaryNav.items: Control which built-in nav buttons are shown (newTask, plugins, scheduledTasks, goals)
- primaryNav.render(): Append custom content after built-in nav buttons
- hideProjectHeader: Hide the 'Projects' header row (search + add workspace)
- sessionActions.items: Control which session action items appear (both inline and dropdown)
- sessionActions.inlineItems: Control which items render as inline hover buttons (supports all action types with icon/text fallback)
- footer.render(): Inject custom UI elements on the left side of the footer
- Move scheduledTasks and goals from footer.items to primaryNav.items

Visibility follows a strict 'hide-first' policy: inline buttons require all three conditions (items includes + inlineItems includes + built-in capability check) to render.

* fix(web-shell): Prevent inline/dropdown duplication and add pin/archive dropdown fallback

- Critical fix 1: Dropdown items now exclude items already shown as inline buttons (added !inlineActionItems.has guard to each dropdown entry and trigger visibility).
- Critical fix 2: pin and archive now have dropdown menu entries as fallback when not configured as inline items, preventing them from becoming inaccessible.
- Narrowed inlineItems type to WebShellSidebarSessionInlineActionItem (excludes details/group which have no working inline handlers), preventing dead buttons.
- Added destructive color styling (var(--destructive)) to inline delete button when not disabled.

* fix(web-shell): Re-export new sidebar types and add visibility matrix tests

- Re-export WebShellSidebarPrimaryNavOptions, WebShellSidebarPrimaryNavItem, WebShellSidebarSessionActionsOptions, WebShellSidebarSessionActionItem, and WebShellSidebarSessionInlineActionItem from client/index.tsx so consumers can import them by name.
- Add session-action-visibility.test.ts: table-driven tests covering the items × inlineItems × capability matrix (default config, empty items/inlineItems, dedup guarantee, pin fallback to dropdown). 7 test cases, all pass.

* fix(web-shell): gate readOnly archive on sessionActionItems, fix footer null safety and docs accuracy (#7379)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-21 11:20:51 +00:00

309 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WebShell Sidebar — Customization Guide
The `WebShellSidebar` is the session list and navigation panel rendered inside
the web-shell `App` component. This document maps each visual area to its
current customization capability and identifies areas with no external injection
point.
## Enabling the sidebar
The sidebar is **disabled by default**. Pass the `sidebar` prop to enable:
```tsx
import { WebShellWithProviders } from '@qwen-code/web-shell';
<WebShellWithProviders
baseUrl="http://localhost:4170"
sidebar={true} // simple enable
// or with fine-grained options:
// sidebar={{ enabled: true, defaultCollapsed: false, ... }}
/>;
```
## Layout overview
```
┌─────────────────────────────────────┐
│ ① Branding (topRow) │ ✅ customizable
├─────────────────────────────────────┤
│ ② Primary navigation │ ✅ customizable
│ [ New task] [🧩 Plugins] │
│ [📅 Scheduled] [🎯 Goals] │
│ [custom render...] │
├─────────────────────────────────────┤
│ ③ Project header │ ✅ show/hide
│ 📁 Projects ▼ [🔍] [] │
│ Session list entries... │
│ 📦 Archived sessions │
├─────────────────────────────────────┤
│ ④ Footer action bar │ ✅ customizable
│ [⚙ Settings] v0.19 [☀] [▦] [◧] │
├─────────────────────────────────────┤
│ ⑤ Resize handle │ ❌ not customizable
└─────────────────────────────────────┘
```
## Customizable areas
### ① Branding — `branding`
```ts
interface WebShellSidebarBranding {
render?: () => ReactNode; // replace the entire branding row
hideWhenCompact?: boolean; // hide when sidebar is collapsed (default: true)
}
```
| Value | Effect |
| -------------------------------- | ------------------------------------------------- |
| `undefined` (default) | Qwen logo + "Qwen Code" text |
| `false` | Branding row hidden entirely |
| `{ render: () => <MyHeader /> }` | Full replacement with custom content |
| `{ hideWhenCompact: false }` | Keep branding visible in collapsed icon-rail mode |
```tsx
sidebar={{
branding: {
render: () => (
<div style={{ display: 'flex', gap: 8 }}>
<img src="/my-logo.svg" alt="" width={24} />
<span>My App</span>
</div>
),
},
}}
```
### ② Primary Navigation — `primaryNav`
```ts
type WebShellSidebarPrimaryNavItem =
| 'newTask' // ✏️ New Task button
| 'plugins' // 🧩 Plugins button
| 'scheduledTasks' // 📅 Scheduled Tasks button
| 'goals'; // 🎯 Goals button
interface WebShellSidebarPrimaryNavOptions {
items?: readonly WebShellSidebarPrimaryNavItem[]; // which built-in buttons to show (default: all)
render?: () => ReactNode; // additional custom content after built-in buttons
}
```
The primary navigation area contains built-in buttons controlled by `items`:
- All buttons are shown by default when `items` is not specified
- Only the listed buttons are shown when `items` is provided
- Custom content can be added via `render()` after the built-in buttons
| Value | Effect |
| ------------------------------------------ | -------------------------------------- |
| `undefined` (default) | All built-in buttons shown |
| `{ items: ['plugins'] }` | Only Plugins button |
| `{ items: ['plugins', 'scheduledTasks'] }` | Plugins + Scheduled Tasks |
| `{ items: [], render: () => ... }` | Hide all built-in, only custom content |
```tsx
sidebar={{
primaryNav: {
items: ['plugins', 'scheduledTasks'], // hide newTask and goals
render: () => (
<button onClick={() => console.log('custom action')}>
🔗 Data Sync
</button>
),
},
}}
```
### ④ Footer — `footer`
```ts
type WebShellSidebarFooterItem =
| 'settings' // ⚙ Settings panel
| 'version' // version label (e.g. "v0.19.10")
| 'theme' // ☀/🌙 light/dark toggle
| 'sessionsOverview' // ▦ session overview panel (large screens only)
| 'splitView' // ◧ split view (large screens only)
| 'daemonStatus' // 📊 daemon status panel
| 'collapse'; // ◁/▷ collapse/expand toggle
interface WebShellSidebarFooterOptions {
items?: readonly WebShellSidebarFooterItem[]; // which built-in items to show (default: all)
render?: () => ReactNode; // custom content rendered on the left side, before built-in items
}
```
| Value | Effect |
| ---------------------------------------------- | ----------------------- |
| `undefined` (default) | All items shown |
| `false` | Footer hidden entirely |
| `{ items: ['settings', 'theme', 'collapse'] }` | Only listed items shown |
The footer auto-adapts to narrow widths: labels are hidden and version is
dropped below certain thresholds.
```tsx
sidebar={{
footer: { items: ['theme', 'collapse'] }, // minimal footer
}}
```
Custom content via `render()` appears on the left side of the footer, before
the built-in items:
```tsx
sidebar={{
footer: {
items: ['collapse'],
render: () => (
<button onClick={() => openHelpCenter()}>
Help
</button>
),
},
}}
```
**Note:** `'scheduledTasks'` and `'goals'` have been moved to the primary
navigation area (②) and are shown by default. They are controlled by `primaryNav.items` instead of
`footer.items`.
### Other top-level options
```ts
interface WebShellSidebarOptions {
enabled?: boolean; // show/hide sidebar (default: true when passed)
defaultCollapsed?: boolean; // initial collapsed state (persisted in localStorage)
showCompactToggle?: boolean; // show the collapse button in the chat area (default: true)
branding?: false | WebShellSidebarBranding;
primaryNav?: WebShellSidebarPrimaryNavOptions;
hideProjectHeader?: boolean; // hide "Projects" header row (default: false = shown)
sessionActions?: WebShellSidebarSessionActionsOptions;
footer?: false | WebShellSidebarFooterOptions;
}
```
### ③ Project Header — `hideProjectHeader`
Controls visibility of the "Projects" header row (the row with the collapse
toggle, search icon, and add workspace button). Defaults to `false` (shown).
```tsx
sidebar={{
hideProjectHeader: true, // hide the "项目 ▼ [🔍] []" row
}}
```
When hidden, the session list entries and archived sessions are still shown —
the header row with its action buttons and the session search bar are removed.
### Session Row Actions — `sessionActions`
```ts
type WebShellSidebarSessionActionItem =
| 'details' // 📝 Details (dropdown sub-menu)
| 'rename' // ✏️ Rename (dropdown menu)
| 'group' // 📁 Group/Move to folder (dropdown menu)
| 'export' // 📤 Export chat history (dropdown menu)
| 'delete' // 🗑 Delete session (dropdown menu)
| 'pin' // 📌 Pin/Unpin (inline button)
| 'archive'; // 📦 Archive (inline button)
/** Subset with working inline (hover-button) handlers. */
type WebShellSidebarSessionInlineActionItem =
| 'pin'
| 'archive'
| 'rename'
| 'export'
| 'delete';
interface WebShellSidebarSessionActionsOptions {
items?: readonly WebShellSidebarSessionActionItem[]; // which actions to show (default: all)
inlineItems?: readonly WebShellSidebarSessionInlineActionItem[]; // which items appear as inline buttons (default: ['pin', 'archive'])
}
```
Controls which action buttons appear on session rows:
- **`items`**: Master control for all actions (both inline and dropdown). If an item is not in `items`, it's hidden everywhere.
- **`inlineItems`**: Controls which items appear as **inline buttons** (on hover). Defaults to `['pin', 'archive']`. Only items with working inline handlers can be used: `'pin'`, `'archive'`, `'rename'`, `'export'`, `'delete'`. `'details'` and `'group'` are dropdown-only.
**Visibility priority**: Both `items` AND the item's built-in condition AND `inlineItems` must all pass for the inline button to show. For example, `delete` as inline requires `items` to include `'delete'` AND `inlineItems` to include `'delete'`.
| Value | Effect |
| ---------------------------------------- | ------------------------------------------ |
| `undefined` (default) | All actions shown, pin + archive as inline |
| `{ inlineItems: ['pin', 'delete'] }` | Pin + delete as inline buttons |
| `{ inlineItems: [] }` | No inline buttons at all |
| `{ inlineItems: ['archive', 'export'] }` | Archive + export as inline buttons |
The dropdown trigger (⋮) is automatically hidden when no dropdown items
are enabled. Inline buttons (`pin`, `archive`) are only shown when both
their capability condition and `items` include them.
```tsx
sidebar={{
sessionActions: {
items: ['details', 'rename', 'export', 'delete', 'pin'], // which actions to show (master control)
inlineItems: ['pin', 'delete'], // pin + delete as inline buttons
},
}}
```
## Non-customizable areas
### Projects / Workspaces (inside session list)
When the session list is visible, the following sub-areas are rendered but
**not individually customizable**:
| Aspect | Detail |
| --------------------- | ----------------------------------------------------------------- |
| Data source | `useSessions()` hook → daemon API (`/sessions` endpoint) |
| Session list sorting | By creation time, descending |
| Session row rendering | Internal `renderSessionRow` `useCallback` — not injectable |
| Search / filter | Built-in search bar with client-side text matching |
| Session groups | `SessionGroupSection` component with 6 preset colors + custom hex |
| Workspace sections | `WorkspaceSection` per daemon workspace, not replaceable |
| Add workspace dialog | Built-in `AddWorkspaceDialog` |
### ⑤ Resize handle
- Drag handle on the right edge for resizing sidebar width
- Width is persisted in localStorage
- Not configurable
## Runtime behavior props
These `WebShellProps` affect sidebar behavior indirectly:
| Prop | Effect |
| ------------------------------- | -------------------------------------- |
| `onNewSession` | Override the new-session handler |
| `onLoadSession` | Override session loading logic |
| `onSessionIdChange` | React to session switches |
| `splitSessionIds` | Control split-view sessions externally |
| `theme` / `onThemeChange` | Control / observe theme |
| `language` / `onLanguageChange` | Control / observe UI language |
## Collapsed and mobile states
| State | Behavior |
| --------- | -------------------------------------------------- |
| Expanded | Full sidebar with text labels |
| Collapsed | Icon-rail mode (logo, pen icon, action icons only) |
| Mobile | Drawer slides from left with backdrop overlay |
Collapse state is persisted in `localStorage` under the key
`qwen-code-web-shell-sidebar-collapsed`.
## Source locations
| Component | File |
| ------------------- | ------------------------------------------------------------------------- |
| WebShellSidebar | `packages/web-shell/client/components/sidebar/WebShellSidebar.tsx` |
| SessionGroupSection | `packages/web-shell/client/components/sidebar/SessionGroupSection.tsx` |
| WorkspaceSection | `packages/web-shell/client/components/sidebar/WorkspaceSection.tsx` |
| Sidebar styles | `packages/web-shell/client/components/sidebar/WebShellSidebar.module.css` |
| App integration | `packages/web-shell/client/App.tsx` (search `WebShellSidebar`) |
| Entry point (dev) | `packages/web-shell/client/main.tsx` (`sidebar: true`) |