fix: keep conversation composer in the viewport under layout push

Long unbreakable tokens (OAuth URLs) grew the AppFrame center column past the viewport because the layout-push rule used the grid default min-height:auto. Cap --dsh-sidebar-height the same way setBottomHeight does (viewport minus PANEL_MIN) and let the column shrink and wrap.
This commit is contained in:
Zzy
2026-08-21 23:32:54 +08:00
parent f0965e1d61
commit 8ceaefcfc7
5 changed files with 156 additions and 12 deletions
+19 -12
View File
@@ -43,6 +43,7 @@ import {
import { IconPanelBottomOutline16, IconPanelRightOutline16 } from './icons.tsx'
import { Workbench, type WorkbenchActions } from './split-pane.tsx'
import { useNarrowViewport } from './breakpoints.ts'
import { layoutPushSize } from './layout-push.ts'
import { parseDesktopEnv } from './desktop-env.ts'
import { getWcoSnapshot, subscribeWco } from './wco.ts'
import { getShellPreset } from './shell-presets.ts'
@@ -804,12 +805,15 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
dragCommitted.current = true
stopDragScheduling()
const last = lastDragSize.current
const adoptedWidth = !narrow && state?.panelOpen === true
? Math.min(last?.width ?? state?.width ?? 0, window.innerWidth)
: 0
const adoptedHeight = !narrow && state?.bottomOpen === true
? Math.min(last?.height ?? state?.bottomHeight ?? 0, window.innerHeight)
: 0
const { width: adoptedWidth, height: adoptedHeight } = layoutPushSize({
narrow,
panelOpen: state?.panelOpen === true,
bottomOpen: state?.bottomOpen === true,
width: last?.width ?? state?.width ?? 0,
bottomHeight: last?.height ?? state?.bottomHeight ?? 0,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
})
applyDrag(adoptedWidth, adoptedHeight)
draggingRef.current = false
measureCenter()
@@ -826,12 +830,15 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
// On NARROW viewports the drawer FLOATS over the app shell — no push, the
// conversation keeps the full width behind the drawer.
useEffect(() => {
const width = !narrow && snapshot.state?.panelOpen === true
? Math.min(snapshot.state.width, window.innerWidth)
: 0
const height = !narrow && snapshot.state?.bottomOpen === true
? Math.min(snapshot.state.bottomHeight, window.innerHeight)
: 0
const { width, height } = layoutPushSize({
narrow,
panelOpen: snapshot.state?.panelOpen === true,
bottomOpen: snapshot.state?.bottomOpen === true,
width: snapshot.state?.width ?? 0,
bottomHeight: snapshot.state?.bottomHeight ?? 0,
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
})
writeGeometry(width, height)
}, [narrow, snapshot.state?.panelOpen, snapshot.state?.width, snapshot.state?.bottomOpen, snapshot.state?.bottomHeight])
// Unmount must release the push (issue #31): when the boundary swaps the
+32
View File
@@ -0,0 +1,32 @@
/**
* Size written to `--dsh-sidebar-width` / `--dsh-sidebar-height`.
* The conversation column (output + composer) must keep at least
* {@link PANEL_MIN} of the viewport after the bottom panel claims height.
*/
import { BOTTOM_MIN, PANEL_MIN } from './state.ts'
export interface LayoutPushInput {
narrow: boolean
panelOpen: boolean
bottomOpen: boolean
width: number
bottomHeight: number
viewportWidth: number
viewportHeight: number
}
export interface LayoutPushSize {
width: number
height: number
}
/** Compute the live layout-push size. Narrow drawers float and push 0. */
export function layoutPushSize(input: LayoutPushInput): LayoutPushSize {
if (input.narrow) return { width: 0, height: 0 }
const maxWidth = Math.max(PANEL_MIN, input.viewportWidth)
const maxHeight = Math.max(BOTTOM_MIN, input.viewportHeight - PANEL_MIN)
return {
width: input.panelOpen ? Math.min(Math.max(0, input.width), maxWidth) : 0,
height: input.bottomOpen ? Math.min(Math.max(0, input.bottomHeight), maxHeight) : 0,
}
}
+8
View File
@@ -44,6 +44,14 @@
#root [data-dsh-frame] > [data-pane="conversation"],
#root :has(> [data-slot="conversation"]) {
margin-bottom: var(--dsh-sidebar-height, 0px);
/* Grid/flex items default to min-height:auto. A long unbreakable token
(OAuth URL) then grows this column past the viewport and clips the
composer + left-rail Settings row. min-height:0 lets the cell shrink;
overflow:hidden keeps paint inside the cell; overflow-wrap wraps the
token so it cannot force the cell's intrinsic size. */
min-height: 0;
overflow: hidden;
overflow-wrap: anywhere;
transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out);
}