Files
deepseek-harness/packages/client/ui-sidebar/README.md
T
imccyu 0d521bdd0d refactor(client): drop owner-era leftovers and gate sidebar New Session on session/create
- The Conversation binding tracking returns to master: the retainInfo subscription and the
  plugin-owned binding effects are a separate fix, not a served-page seam.
- The deliverables opener drops the ownerHostRoutes parameter no caller passes any more.
- Assertions on existing connection and HMR behavior, the menu-open-state spec, and the
  settings-general roster splice return to master.
- Workspace shortcuts no longer re-read Session access through source(); the Host refuses a
  denied rename, fork, or archive.
- The sidebar New Session button, the window-chrome New Session control, and the expanded
  brand shortcut render only while useRemoteAccess('session/create') allows it.
2026-10-22 21:44:25 +08:00

12 KiB
Raw Blame History

description, kind
description kind
Sidebar shell plugin for the dsh web client: brand row, New Session action, collapse control, scroll-aware region seat, and bottom-pinned Settings seat. package-reference

@deepseek-ai/dsh-client-ui-sidebar

English | 中文

Desktop product events use the optional product analytics service; ordinary Web interactions are excluded.

Summary

The dsh web client sidebar lets users recognize the active build, start a new session, collapse navigation to a 56px rail, browse Workspaces and Sessions, and open Settings. It preserves a bottom-pinned Settings entry and hides idle scrollbars without moving browser rows. New Session uses an explicitly selected Workspace, then the current Session's Workspace, then the most recently active Workspace; if none exists, it opens a blank New Session page. Deployments can replace the brand mark or name while retaining the navigation controls and rail geometry.

Table of Contents


Use this package

The sidebar is the navigation shell: users see the brand, start new sessions, collapse the rail, and reach Settings. Feature plugins fill its seats — ui-workspace fills sidebar.workspaces, ui-settings registers the trigger row and settings panel at sidebar.settings.

Brand and New Session

The expanded brand row renders sidebar.brand.mark and sidebar.brand.name as independent single slots; the collapsed rail renders the same mark slot. On Web and Windows desktop, clicking the expanded brand starts a new session; hovering or focusing it shows no tooltip. Without occupants, the shell uses the fish mark and a localized local-build label. A complete build stacks a code badge below the label as version[-commit][-dirty], using DSH_CLIENT_VERSION, the optional 7-character DSH_CLIENT_COMMIT_HASH, and DSH_CLIENT_GIT_DIRTY=true; missing version metadata omits the badge. Clicking the fallback mark or label of such a build copies that badge text instead of starting a session, also on macOS desktop, and a successful write shows a Version copied toast. New Session targets the explicit Workspace used by a scoped action, otherwise the current Session's Workspace, otherwise the most recently active Workspace; when none exists it clears into the blank New Session page. The expanded New Session button displays the effective binding as trailing grey text on keyboard focus regardless of hover capability, and on hover when the device supports it. While the shortcut is visible, the centered icon and label fade before it when space is narrow; CSS reserves the shortcut’s width without measuring the button. Icon-only controls retain tooltips with platform-formatted keycaps, including the resident macOS header control when the sidebar is hidden. All controls expose aria-keyshortcuts. While the global access snapshot excludes session/create, the brand row stays inert and neither the New Session button nor the window-chrome New Session control renders; the Host still decides every create request.

Global panel entries

Plugins add an icon component to the root-scoped sidebar.panellist list with an id, optional order, and a string or locale-aware label. The same id addresses the component registered in the layout's root-scoped main keyed slot; selecting a missing main entry throws without changing the current selection. The label supplies plain visible text, the accessible name, and the collapsed tooltip. Expanded navigation rows have a 2px gap. Each row reads its own selected state through usePanelInfo; moving DOM focus to search or a directory picker does not change the displayed panel or its selected row. With no panel registrations, neither the list nor spacing for it is rendered. The shipped composition registers no example panel.

While the root-scoped sidebar.panellist.actions list has registrations, a More button styled as a panel row follows the rows inside the navigation landmark; the collapsed rail shows its label as a tooltip. It opens a menu whose rows are that list's registrations. Each receives entries (customizable panels in registration order with id, label, and pinned; Plugins is never listed), onOpen(id), which selects the panel, onPinnedChange(id, pinned), and onDismiss(), which closes the menu. The sidebar keeps one root list of unpinned panel ids in browser localStorage under dsh.slot.manual-order.sidebar.panellist.v1, stored as { "unpinned": string[] } and shared by every Session; the rows omit unpinned panels except the selected one, which keeps its row and aria-current until another panel is selected, and Plugins always shows. Unpinning hides only the row; the main panel, its registration, and the current selection stay unchanged. Ids of panels that are not registered stay stored, so a panel that registers again stays unpinned; stored content other than a string array shows every row.

Collapse behavior

The sidebar and conversation-header toggles show the effective shortcut from the command catalog in their hover and keyboard-focus tooltips and aria-keyshortcuts. An unbound command shows only the action label.

The top expand button hosts the optional, non-interactive sidebar.toggle.badge slot while collapsed. Its occupant supplies status and tooltip content without adding another action or changing the button's navigation behavior.

During a live collapse, the expanded content fades out at its current width, the upper controls share one fade and leftward translation into the 56px rail, and the layout's column slide ends the motion. A page that starts collapsed renders the rail statically, and reduced-motion mode disables both transitions. The bottom-pinned sidebar.settings control shares the fade timing but has no horizontal translation.

On Windows Electron, html[data-windows-titlebar] fixes the sidebar toggle in the caption's top-left corner in both states, aligned with New Session's left edge only when expanded. The expanded brand sits below the caption and above New Session, with 8px of extra space above that button. Collapsing hides the brand and sidebar content and places New Session between the sidebar toggle and the Desktop-owned menus. The sidebar sets the root --dsh-windows-menu-start to 84px when collapsed; the Desktop preload uses it to position its menu after New Session and defaults to 48px when expanded. Caption icon buttons use centered 16px glyphs in 28px controls with the compact control radius and exclude themselves from the window drag region. The sidebar toggle and New Session bubbles open below the caption, where the Desktop-owned menu text cannot cover them; an occupying sidebar.toggle.badge chooses its own bubble side.

macOS desktop

Under html[data-platform='darwin'] (set only by the desktop preload) the expanded column opens with a 52px top strip that clears the hiddenInset traffic lights and carries the collapse toggle; the strip and the logo row below each mark themselves data-window-drag, so each row's own box is the window's drag region (ui-web base.css declares the one darwin drag rule), and the brand wordmark is not a New Session shortcut there — the dedicated New Session button keeps the action — and collapsing hides the column entirely instead of leaving the rail. The package registers HeaderLeadingControls into the frame's shell.leading window-chrome seat (ui-layout), which mounts it — the open-sidebar and New Session controls beside the traffic lights — only while the column is hidden, over every main panel. The Desktop reference owns window integration.

Scrollbars

Scrollbars in the column are a pointer affordance: the shell rebinds the scrollbar indirection to transparent whenever the pointer is outside the column and keeps the thumb drawn for 2s after the pointer leaves, so a list nobody is pointing at carries no bar. The reservation that keeps rows from moving belongs to the scrolling region (ui-workspace), so revealing a thumb never reflows.

Click analytics

On Desktop, the official navigation controls report sidebar_menu_click with menu_name and position before they act. Without the analytics service, the controls only navigate. Selecting a panel by other means reports nothing, and the sidebar reports no exposure events.

  • The expanded brand and the New Session button report menu_name=new_session with position=sidebar. While the Windows caption hosts the collapsed New Session button, that button reports position=titlebar; the macOS New Session control beside the traffic lights also reports position=titlebar.
  • The Plugins and Automation tasks rows report menu_name=plugin and menu_name=cron with position=sidebar. Other panel rows report nothing.
  • Each More button click reports menu_name=more with position=sidebar, whether it opens or closes the menu.
  • A More menu row that opens Plugins or Automation tasks through onOpen(id) reports menu_name=plugin or menu_name=cron with position=sidebar_more. The report precedes the registration check, so a request for a panel that has unregistered still counts although nothing is selected.

Understand the implementation

Implementation internals — click to expand

The shell is pure composition: SidebarRootComponentProps composes the layout owner share, the global useSessions and useWorkspaces hooks, the declared brand, the sidebar.workspaces and sidebar.settings child slots, and injected navigation callbacks. Panel entries and their optional titles use the same composition path. Panel metadata is derived from list registrations and locale changes; selection belongs to the layout store. The injected trackEntryClick(attributes) forwards to ctx.get('productAnalytics')?.track('sidebar_menu_click', attributes), and selectPanel only selects, so each control supplies its own entry and position.

Slot discipline

Declaration-aware slots.inject() lets a replacing package activate before or after the sidebar. The foot is the sidebar.settings seat: the sidebar renders only the bottom-pinned layout slot and shares its column state (wide). The /client exports are the plugin body (apply/inject) plus the contract types only; SidebarRoot, the row components, and the tree derivation remain package-internal behind the slot registration.


Further Exploration

These pages cover the surfaces that fill the shell's seats and the composition model.

  • ui-workspace — the Workspace and Session browser rendered into sidebar.workspaces.
  • ui-settings — the settings domain base registering the trigger row at sidebar.settings.
  • ui-layout — the layout owner whose rail and column state the collapse uses.
  • ui-theme — the scrollbar token indirection the shell rebinds.
  • Slot system standard — the composition model behind the seats.

Model Experience

None, as the package is a browser-side UI plugin layer that registers nothing model-facing.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

These limits define what the shell owns versus what its occupants own; they are current package constraints.

  • Session state-dot rendering is owned by ui-workspace — no done/error notification sources are available to this shell.
  • Workspace browser behavior is composition-owned — grouping, ordering, search, and row state belong to ui-workspace, not this shell.
  • "New task completed" unread marking is local viewing state — completion-time > last-seen never reaches the host.
  • Copying the local-build version is mouse-only — keyboard activation of the brand starts a session.

Dev Note

Working context for maintainers — click to expand

None.