fix(gui): keep sidebar controls when collapsed
A closed sidebar previously resolved to a zero-width grid track, clipping the only toggle and the settings entry with no visible recovery; the closed preference persisted across reloads, locking the sidebar shut. - columns.ts maps the closed preference (width 0) to a fixed 60px SIDEBAR_COLLAPSED rail through every step of the concession solve; closed details still resolve to zero width. - AppFrame derives data-sidebar-collapsed and the sidebar slot's collapsed owner prop from the persisted preference instead of the resolved track width, and drops the resize handle while collapsed. - SidebarRoot reads the owner collapsed prop; the expanded-only body is a separate component that unmounts while collapsed (dropping its sessions subscription), leaving the expand toggle and Settings in the rail. - The keyless web smoke gains the ui-sidebar bundle (six real bundles) and pins the 60px rail collapse/expand round through the assembled client.
This commit is contained in:
@@ -128,14 +128,15 @@ export function AppFrame({ useStore, actions, renderSlot, SessionProvider }: App
|
||||
ref={frameRef}
|
||||
className={css.frame}
|
||||
style={{ gridTemplateColumns: `${cols.sidebar}px minmax(0, 1fr) ${cols.details}px` }}
|
||||
data-sidebar-collapsed={cols.sidebar === 0 || undefined}
|
||||
data-sidebar-collapsed={panels.sidebar === 0 || undefined}
|
||||
data-details-collapsed={cols.details === 0 || undefined}
|
||||
>
|
||||
<div className={css.sidebarCol}>
|
||||
{/* Render-site slot call with live concession output: the sidebar
|
||||
stays mounted at zero width (CSS hides it), and sees its rendered
|
||||
state as owner params decided here, not precomputed upstream. */}
|
||||
{renderSlot('sidebar', { collapsed: cols.sidebar === 0, width: cols.sidebar })}
|
||||
{/* Render-site slot call with live concession output: a closed
|
||||
sidebar keeps the mounted slot at the compact-rail width, and the
|
||||
component sees its rendered state as owner params decided here
|
||||
(collapsed follows the preference, not the resolved width). */}
|
||||
{renderSlot('sidebar', { collapsed: panels.sidebar === 0, width: cols.sidebar })}
|
||||
</div>
|
||||
<SessionProvider
|
||||
empty={() => (
|
||||
@@ -153,7 +154,8 @@ export function AppFrame({ useStore, actions, renderSlot, SessionProvider }: App
|
||||
</>
|
||||
)}
|
||||
</SessionProvider>
|
||||
{cols.sidebar > 0 && <DragHandle left={cols.sidebar} onStart={onSidebarStart} onDrag={onSidebarDrag} />}
|
||||
{/* The collapsed rail is fixed-width: no resize handle while closed. */}
|
||||
{panels.sidebar > 0 && <DragHandle left={cols.sidebar} onStart={onSidebarStart} onDrag={onSidebarDrag} />}
|
||||
{cols.details > 0 && <DragHandle left={viewport - cols.details} onStart={onDetailsStart} onDrag={onDetailsDrag} />}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* details first, then sidebar, then auto-closing details (derived zero width —
|
||||
* persisted width preferences are never rewritten, so widening the window
|
||||
* restores them). Center absorbs any remaining deficit as the last resort.
|
||||
* Inputs are the layout store's plain width preferences (0 = closed).
|
||||
* Inputs are the layout store's plain width preferences (0 = closed); a
|
||||
* closed sidebar resolves to the fixed SIDEBAR_COLLAPSED control rail while
|
||||
* closed details resolve to zero width.
|
||||
*/
|
||||
|
||||
/** Resolved widths for one frame; center may drop below CENTER_MIN only at the final fallback. */
|
||||
@@ -19,6 +21,8 @@ export const SIDEBAR_MIN = 240
|
||||
export const SIDEBAR_MAX = 420
|
||||
/** Sidebar width before any user drag. */
|
||||
export const SIDEBAR_DEFAULT = 300
|
||||
/** Closed-sidebar rail: one 28px control between 16px horizontal paddings. */
|
||||
export const SIDEBAR_COLLAPSED = 60
|
||||
/** Details drag clamp floor. */
|
||||
export const DETAILS_MIN = 300
|
||||
/** Details drag clamp ceiling. */
|
||||
@@ -47,10 +51,10 @@ export function clampWidth(px: number, min: number, max: number): number {
|
||||
* @param viewport - available frame width in px.
|
||||
* @param sidebar - sidebar width preference in px (0 = closed).
|
||||
* @param details - details width preference in px (0 = closed).
|
||||
* @returns resolved widths; details 0 means visually closed (never unmounted).
|
||||
* @returns resolved widths; details 0 means visually closed (never unmounted), while a closed sidebar keeps its compact rail.
|
||||
*/
|
||||
export function computeColumns(viewport: number, sidebar: number, details: number): Columns {
|
||||
const s0 = sidebar === 0 ? 0 : clampWidth(sidebar, SIDEBAR_MIN, SIDEBAR_MAX)
|
||||
const s0 = sidebar === 0 ? SIDEBAR_COLLAPSED : clampWidth(sidebar, SIDEBAR_MIN, SIDEBAR_MAX)
|
||||
const d0 = details === 0 ? 0 : clampWidth(details, DETAILS_MIN, DETAILS_MAX)
|
||||
|
||||
// Step 1: everything fits at preferred widths.
|
||||
@@ -60,15 +64,15 @@ export function computeColumns(viewport: number, sidebar: number, details: numbe
|
||||
const d1 = d0 === 0 ? 0 : Math.max(DETAILS_MIN, viewport - s0 - CENTER_MIN)
|
||||
if (s0 + d1 + CENTER_MIN <= viewport) return { sidebar: s0, center: CENTER_MIN, details: d1 }
|
||||
|
||||
// Step 3: shrink sidebar toward its minimum.
|
||||
const s1 = s0 === 0 ? 0 : Math.max(SIDEBAR_MIN, viewport - d1 - CENTER_MIN)
|
||||
// Step 3: shrink sidebar toward its minimum (the collapsed rail never shrinks).
|
||||
const s1 = sidebar === 0 ? SIDEBAR_COLLAPSED : Math.max(SIDEBAR_MIN, viewport - d1 - CENTER_MIN)
|
||||
if (s1 + d1 + CENTER_MIN <= viewport) return { sidebar: s1, center: CENTER_MIN, details: d1 }
|
||||
|
||||
// Step 4: auto-close details (derived — preferences untouched). With the
|
||||
// details pressure gone the sidebar concession is re-solved from preference.
|
||||
if (d1 > 0) {
|
||||
if (s0 + CENTER_MIN <= viewport) return { sidebar: s0, center: viewport - s0, details: 0 }
|
||||
const s2 = s0 === 0 ? 0 : Math.max(SIDEBAR_MIN, viewport - CENTER_MIN)
|
||||
const s2 = sidebar === 0 ? SIDEBAR_COLLAPSED : Math.max(SIDEBAR_MIN, viewport - CENTER_MIN)
|
||||
return { sidebar: s2, center: Math.max(0, viewport - s2), details: 0 }
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,9 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
|
||||
/** Sidebar owner share: live column state from the frame's concession solve. */
|
||||
export interface SidebarOwnerProps {
|
||||
/** True when the concession chain rendered the column at zero width. */
|
||||
/** True when the sidebar is closed (the column renders the compact control rail). */
|
||||
collapsed: boolean
|
||||
/** Rendered column width in px (0 when collapsed). */
|
||||
/** Rendered column width in px (SIDEBAR_COLLAPSED when collapsed). */
|
||||
width: number
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user