diff --git a/src/client/Sidebar.tsx b/src/client/Sidebar.tsx index b8bf8a6..3a6f9e2 100644 --- a/src/client/Sidebar.tsx +++ b/src/client/Sidebar.tsx @@ -311,8 +311,9 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) { // ("squeezes the agent output area") — it starts at the app sidebar's // right edge and ends at the details column's left edge (the details // column sits between the center and the right panel). Measured directly - // from the AppFrame's center column DOM (children[1], the layout.css - // nth-child(2) column) so the bottom panel tracks the column's real + // from the AppFrame's center column DOM (the parent of the + // [data-slot="conversation"] wrapper — layout.css's center column) so the + // bottom panel tracks the column's real // horizontal edges — including the animated margin-right push while the // right panel opens/closes; a frame that never appears keeps the initial // zero-size fallback (the panel renders at 0 width until measured). @@ -340,17 +341,20 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) { useEffect(() => { let disposed = false let observer: ResizeObserver | undefined - // Locate the AppFrame's center column (children[1] of the frame div — - // layout.css's nth-child(2)). The shell swaps the boot page for the - // AppFrame only AFTER boot settles, so the first query may miss it. - // Never give up: watch #root's children (the swap mutates them) and - // re-run this locator — querying once and bailing would strand the - // panel at the zero-size fallback forever (observed: a 1px sliver at - // the viewport's left edge). + // Locate the AppFrame's center column. DSH 0.1.x wraps slot hosts in + // [data-slot] containers: the conversation slot wrapper + // ([data-slot="conversation"]) sits directly inside the center column, + // so its parent IS that column — no hashed-class or positional + // dependency (layout.css uses the same anchor). The shell swaps the + // boot page for the AppFrame only AFTER boot settles, so the first + // query may miss it. Never give up: watch #root's children (the swap + // mutates them) and re-run this locator — querying once and bailing + // would strand the panel at the zero-size fallback forever (observed: + // a 1px sliver at the viewport's left edge). const locate = (): void => { if (disposed) return - const frame = document.querySelector('#root > div') - const col = frame?.children[1] as HTMLElement | undefined + const col = document.querySelector('#root [data-slot="conversation"]') + ?.parentElement as HTMLElement | undefined if (col === undefined) { if (centerColRef.current !== null) { centerColRef.current = null diff --git a/src/client/layout.css b/src/client/layout.css index 04351e6..4b3a677 100644 --- a/src/client/layout.css +++ b/src/client/layout.css @@ -6,11 +6,13 @@ * conversation output and the input bar, like a VSCode sidebar. * * The bottom panel squeezes ONLY the center column — it must not cover the - * app's own left sidebar or the right panel. The AppFrame's grid items are - * (#root > div > div:nth-child(2)): sidebarCol, centerCol, detailsCol, so - * the vertical push lands on the center column alone. The fixed panels - * cover the vacated strips, which looks seamless because #root's background - * is the theme base. + * app's own left sidebar or the right panel. DSH 0.1.x wraps slot hosts in + * [data-slot] containers, so the AppFrame grid lives one level deeper: + * #root > div[data-slot="root"] > div (the frame). Its grid items are + * (frame > div:nth-child(1..3)): sidebarCol, centerCol, detailsCol, so the + * vertical push lands on the center column alone. The fixed panels cover + * the vacated strips, which looks seamless because #root's background is + * the theme base. * * The sizes ride CSS variables updated by the Sidebar shell (0 while * collapsed); expand/collapse animates both the margins and the panel @@ -23,10 +25,10 @@ } /* The AppFrame's grid items are sidebarCol, centerCol, detailsCol (children - 1-3 of #root > div) — nth-child(2) is the center column. A stretched grid - item shrinks by its margins, so the conversation content (output + input - bar) lifts without touching the sidebars. */ -#root > div > div:nth-child(2) { + 1-3 of #root > div[data-slot="root"] > div) — nth-child(2) is the center + column. A stretched grid item shrinks by its margins, so the conversation + content (output + input bar) lifts without touching the sidebars. */ +#root > div[data-slot="root"] > div > div:nth-child(2) { margin-bottom: var(--dsh-sidebar-height, 0px); transition: margin-bottom var(--ds-transition-duration-slow) var(--ds-ease-in-out); } @@ -35,20 +37,23 @@ corner. Push the DSH session header's right padding out so its right-aligned utilities (the "Session log" download capsule) yield the corner instead of hiding under the cluster. The header default right-pads 28px; the 2-button - cluster spans right 10→70px, so 78px clears it with an 8px gap. The - Sidebar shell toggles the body attribute with the panel open state. */ -body[data-dsh-sidebar-collapsed] #root > div > div:nth-child(2) header { + cluster spans right 10→70px, so 78px clears it with an 8px gap. Anchor on + the header's slot host wrapper ([data-slot="conversation.session.header"]) + rather than a positional path: DSH 0.1.x nests the header several levels + under the center column. The Sidebar shell toggles the body attribute with + the panel open state. */ +body[data-dsh-sidebar-collapsed] [data-slot="conversation.session.header"] > header { padding-right: 78px; } body[data-dsh-sidebar-dragging] #root, -body[data-dsh-sidebar-dragging] #root > div > div:nth-child(2) { +body[data-dsh-sidebar-dragging] #root > div[data-slot="root"] > div > div:nth-child(2) { transition: none; } @media (prefers-reduced-motion: reduce) { #root, - #root > div > div:nth-child(2) { + #root > div[data-slot="root"] > div > div:nth-child(2) { transition: none; } }