import * as React from 'react'; import { PanelLeftOpen, Sparkles } from 'lucide-react'; import { cn } from '@/lib/utils'; import KrowAssistant from './KrowAssistant'; import { useAssistantPanel } from './AssistantPanelContext'; import { ResizeDivider } from './ResizeDivider'; const EXPANDED_WIDTH = 620; /** * Gutter separating the panel from the dashboard, split between the drag handle * and the panel's own lead padding. Both halves are named because the track width * is their sum plus the panel: get that arithmetic wrong and the column either * overflows or leaves a strip of unexplained empty space at the shell's edge. */ const HANDLE_WIDTH = 12; const LEAD_PADDING = 8; const GUTTER = HANDLE_WIDTH + LEAD_PADDING; /** Expanded must never dominate: the dashboard stays the primary experience. */ const MAX_VIEWPORT_SHARE = 0.42; /** * Below this width there is no room for a column *beside* the dashboard — a * 380px track on a 375px phone collapses `main` to nothing — so Owliver stacks * underneath the page instead. * * Deliberately Tailwind's `md` (768px), not `lg`: tablets already lay the inline * column out acceptably, so they keep the two-column workspace and only phones * stack. */ const STACK_BREAKPOINT = 768; /** Tracks viewport width so the panel can be clamped and the layout switched. */ function useViewportWidth() { const [width, setWidth] = React.useState(() => typeof window === 'undefined' ? 1440 : window.innerWidth ); React.useEffect(() => { const onResize = () => setWidth(window.innerWidth); window.addEventListener('resize', onResize); return () => window.removeEventListener('resize', onResize); }, []); return width; } /** * The collapsed state — a compact docked trigger. * * The earlier version was a full-height rail inside the layout column. It solved * the wrong problem: it kept Owliver *findable*, but it also kept the column, so * collapsing bought the dashboard 44px instead of the 400px the user was asking * for, and it did it with rotated text down the side of the page. * * So collapsing now removes the column entirely — the layout goes to one column * and `main` takes the whole shell — and the way back is this small docked pill. * It is deliberately the only floating control in the product, and it exists only * in the collapsed state, where there is nowhere else for it to live. */ function CollapsedTrigger({ page, onRestore }) { return ( ); } /** * AssistantPanel — the Owliver workspace column the Admin layout renders. * * Owliver is part of the page on every supported route, so this is a column in * the layout rather than an overlay: the dashboard reflows beside it instead of * being covered, and collapsing restores the original layout exactly. * * Five structural details matter, and every one of them was a bug at some point: * * 1. Nothing above the sticky element clips. An ancestor with `overflow: hidden` * becomes the nearest scroll container and silently disables * `position: sticky` beneath it. * 2. The aside stretches to the shell's full height (`self-stretch`), so its * sticky context runs the whole page rather than ending partway down and * leaving empty space beside a long dashboard. * 3. `min-w-0` on the aside. A flex item's default `min-width: auto` refuses to * go narrower than its content, so the track ignored its own width and stalled * at the panel's old size on the way back from expanded. * 4. Only the track has a width. The panel fills what the track leaves after the * handle and the lead padding, so the two can never disagree — which is * exactly what (3) was: two widths transitioning, one winning. * 5. Collapsing removes the column outright and leaves a compact docked trigger. * An earlier version kept a full-height rail in place, which bought the * dashboard 44px when the user had asked for 400 — and spelled "Owliver" down * the side of the page to do it. * * Expanding widens the same panel in place — an analysis workspace inside the * application layout, never a fullscreen takeover. */ export function AssistantPanel({ stickyClassName, panelHeightClassName }) { const { context, isOpen, isExpanded, width, minWidth, maxWidth, setWidth, resetWidth, open, close, expand, restore, } = useAssistantPanel(); const viewportWidth = useViewportWidth(); // No assistant on this route: no column, no rail, no trace in the layout. if (!context) return null; /* Expanded overrides the dragged width; otherwise the user's own width wins. Both are clamped against the viewport so the dashboard is never squeezed. */ const viewportCap = Math.round(viewportWidth * MAX_VIEWPORT_SHARE); const panelWidth = isExpanded ? Math.max(width, Math.min(EXPANDED_WIDTH, viewportCap)) : Math.min(width, Math.max(minWidth, viewportCap)); /* Collapsed: no column at all. The layout falls back to a single column, `main` takes the whole shell, and the only thing left is the docked trigger — so there is never a reserved empty gutter where the panel used to be. */ if (!isOpen) { return ; } const trackWidth = panelWidth + GUTTER; /* Phones: stack Owliver under the dashboard. A 380px column beside the page is not a layout at this width, and an overlay that opens on load would put a sheet between the user and the page they asked for. */ if (viewportWidth < STACK_BREAKPOINT) { return ( ); } return ( ); } export default AssistantPanel;