Sidebar and Auto-Zoom Documentation¶
Overview¶
The document viewer features a resizable right sidebar (chat/feed panel) with automatic zoom adjustment to maintain consistent document proportions when the viewport changes. The sidebar pushes document content to the side on desktop while maintaining an overlay mode on mobile.
Layout Architecture¶
Desktop Layout (≥769px)¶
ContentArea (full width)
├─ FloatingInputWrapper (absolute, offset by panelWidth)
└─ MainContentArea (position: relative)
├─ document-layer (width: 100 - panelWidth%)
│ └─ PDFContainer (inherits constrained width)
└─ SlidingPanel (absolute, right: 0, width: panelWidth%)
Key Behavior: - Document area shrinks via width: ${100 - panelWidth}% - Sidebar sits absolutely positioned at right edge - Smooth 0.3s cubic-bezier transition on width changes - Floating controls offset by panelWidthPx to avoid overlap
Mobile Layout (≤768px)¶
ContentArea (full width)
└─ MainContentArea
├─ document-layer (width: 100%)
└─ SlidingPanel (fixed, inset: 0, full-screen overlay)
Key Behavior: - Document stays full width - Sidebar uses position: fixed with full-screen overlay - No auto-zoom adjustments on mobile
Sidebar Width Management¶
Width Modes¶
Managed via useChatPanelWidth() hook:
type PanelMode = "quarter" | "half" | "full" | "custom";
const getPanelWidthPercentage = (): number => {
switch (mode) {
case "quarter": return 25;
case "half": return 50;
case "full": return 90;
case "custom": return customWidth || 50;
}
};
Resize System¶
Lines 1387-1441 in DocumentKnowledgeBase.tsx:
// Drag handlers with snap-to-preset logic
handleResizeStart(e) {
setIsDragging(true);
setDragStartX(e.clientX);
setDragStartWidth(getPanelWidthPercentage());
}
handleResizeMove(e) {
const deltaX = dragStartX - e.clientX;
const deltaPercent = (deltaX / window.innerWidth) * 100;
let newWidth = dragStartWidth + deltaPercent;
// Snap to presets within 3% threshold
const snapThreshold = 3;
if (Math.abs(newWidth - 25) < snapThreshold) setMode("quarter");
else if (Math.abs(newWidth - 50) < snapThreshold) setMode("half");
else if (Math.abs(newWidth - 90) < snapThreshold) setMode("full");
else {
setMode("custom");
setCustomWidth(Math.max(20, Math.min(95, newWidth)));
}
}
Auto-Zoom System¶
Core Concept¶
When the sidebar opens/resizes, the document viewport shrinks. Auto-zoom proportionally reduces zoom level to maintain the same visual document width relative to the available space.
State Management¶
const [autoZoomEnabled, setAutoZoomEnabled] = useState<boolean>(true);
const baseZoomRef = useRef<number>(zoomLevel);
const isAdjustingZoomRef = useRef<boolean>(false);
const justToggledAutoZoomRef = useRef<boolean>(false);
const prevAutoZoomEnabledRef = useRef<boolean>(autoZoomEnabled);
baseZoomRef: The "target" zoom when sidebar is closed isAdjustingZoomRef: Prevents infinite loops during programmatic zoom changes justToggledAutoZoomRef: Prevents immediate adjustment when toggle is enabled prevAutoZoomEnabledRef: Tracks toggle state changes
Zoom Calculation (Lines 731-776)¶
useEffect(() => {
if (!autoZoomEnabled || isMobile || activeLayer !== "document") return;
if (justToggledAutoZoomRef.current || isAdjustingZoomRef.current) return;
const panelWidth = getPanelWidthPercentage();
if (showRightPanel) {
// Store base zoom on first open
if (baseZoomRef.current === zoomLevel || !baseZoomRef.current) {
baseZoomRef.current = zoomLevel;
}
// Calculate adjusted zoom
const viewportReduction = (100 - panelWidth) / 100;
const adjustedZoom = baseZoomRef.current * viewportReduction;
const clampedZoom = Math.max(0.5, Math.min(4, adjustedZoom));
if (Math.abs(zoomLevel - clampedZoom) > 0.01) {
isAdjustingZoomRef.current = true;
setZoomLevel(clampedZoom);
}
} else {
// Restore base zoom when sidebar closes
if (baseZoomRef.current && Math.abs(zoomLevel - baseZoomRef.current) > 0.01) {
isAdjustingZoomRef.current = true;
setZoomLevel(baseZoomRef.current);
}
}
}, [autoZoomEnabled, showRightPanel, mode, customWidth, ...]);
Manual Zoom Tracking (Lines 778-798)¶
When user manually zooms while sidebar is open, back-calculate the base zoom:
useEffect(() => {
if (!autoZoomEnabled || isMobile || isAdjustingZoomRef.current) return;
if (showRightPanel && activeLayer === "document") {
// User manually changed zoom - back-calculate base
const panelWidth = getPanelWidthPercentage();
const viewportReduction = (100 - panelWidth) / 100;
const backCalculatedBase = zoomLevel / viewportReduction;
baseZoomRef.current = Math.max(0.5, Math.min(4, backCalculatedBase));
} else if (!showRightPanel) {
// Sidebar closed - keep baseZoom in sync
baseZoomRef.current = zoomLevel;
}
}, [autoZoomEnabled, zoomLevel, showRightPanel, ...]);
Toggle Behavior (Lines 715-729)¶
When auto-zoom is toggled ON, capture current zoom as new base:
useEffect(() => {
const wasDisabled = !prevAutoZoomEnabledRef.current;
const isNowEnabled = autoZoomEnabled;
if (wasDisabled && isNowEnabled) {
// User toggled ON - use current zoom as base
baseZoomRef.current = zoomLevel;
justToggledAutoZoomRef.current = true; // Skip next adjustment cycle
}
prevAutoZoomEnabledRef.current = autoZoomEnabled;
}, [autoZoomEnabled, zoomLevel]);
Container Width Synchronization¶
ResizeObserver (Lines 688-705)¶
Watches PDF container for width changes and updates containerWidth state:
useEffect(() => {
const node = pdfContainerRef.current;
if (!node) return;
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const newWidth = entry.contentRect.width;
setContainerWidth(newWidth);
}
});
resizeObserver.observe(node);
return () => resizeObserver.disconnect();
}, [setContainerWidth]);
Why it's needed: The PDF component (<PDF containerWidth={containerWidth} />) needs the pixel width to calculate page layouts. When sidebar opens/closes, the container width changes, triggering a re-render with the new constrained width.
Floating Controls Offset¶
Calculation (Lines 385-402)¶
const calculateFloatingControlsState = () => {
if (isMobile || !showRightPanel || activeLayer !== "document") {
return { offset: 0, visible: true };
}
const panelWidthPercent = getPanelWidthPercentage();
const windowWidth = window.innerWidth;
const panelWidthPx = (panelWidthPercent / 100) * windowWidth;
const remainingSpacePercent = 100 - panelWidthPercent;
const remainingSpacePx = windowWidth - panelWidthPx;
// Hide controls if < 10% viewport or < 100px remaining
const shouldHide = remainingSpacePercent < 10 || remainingSpacePx < 100;
return {
offset: shouldHide ? 0 : panelWidthPx,
visible: !shouldHide,
};
};
Applied to: - FloatingInputWrapper - search/chat input - EnhancedLabelSelector - label selection UI - FloatingDocumentControls - settings/analyses/extracts buttons - FloatingAnalysesPanel - analyses overlay - FloatingExtractsPanel - extracts overlay
All floating elements shift left by offset pixels to remain visible when sidebar opens.
User Controls¶
Auto-Zoom Toggle¶
Located in FloatingDocumentControls settings panel (gear icon):
<ControlItem>
<ControlLabel>
<Maximize2 />
Auto-Zoom Sidebar
</ControlLabel>
<StyledCheckbox
toggle
checked={autoZoomEnabled}
onChange={() => onAutoZoomChange?.(!autoZoomEnabled)}
/>
</ControlItem>
Enabled (default): Document auto-zooms when sidebar opens/closes Disabled: Document width adjusts but zoom stays fixed at user's manual setting
Panel Width Control¶
Located in floating controls (columns icon when sidebar is open): - Compact (25%) - Minimal sidebar width - Standard (50%) - Default balanced width - Wide (90%) - Maximum sidebar width - Custom - User-dragged width via resize handle
Example Scenarios¶
Scenario 1: Opening Sidebar¶
- User has zoom at 138%, sidebar closed
- Click chat tab → sidebar opens at 50% width
- Auto-zoom:
baseZoomRef = 1.38,adjustedZoom = 1.38 × 0.5 = 0.69(69%) - Document shrinks to 50% width but maintains same proportional size
Scenario 2: Resizing Sidebar¶
- Sidebar open at 50%, zoom at 69%
- User drags resize handle to 25% width
- Auto-zoom:
adjustedZoom = 1.38 × 0.75 = 1.035(103.5%) - Document expands to 75% width, zoom increases proportionally
Scenario 3: Manual Zoom with Sidebar Open¶
- Sidebar open at 50%, auto-zoom at 69%
- User manually zooms to 80%
- Back-calculate:
baseZoom = 0.8 / 0.5 = 1.6 - Close sidebar → zoom restores to 160% (user's intended base zoom)
Scenario 4: Toggling Auto-Zoom¶
- User disables auto-zoom, manually sets zoom to 120%, sidebar at 50%
- User enables auto-zoom
baseZoomRefcaptures current 120% (not old base)- Next sidebar resize uses 120% as baseline
Technical Notes¶
- Sidebar uses Framer Motion with spring physics for smooth animations
- Width transitions use
cubic-bezier(0.4, 0, 0.2, 1)easing for consistency - Zoom adjustments only occur on desktop (no mobile auto-zoom)
- All floating UI elements respect sidebar offset to prevent overlap
- ResizeObserver provides reactive width updates to PDF renderer
- Base zoom tracking ensures user intent is preserved across sidebar toggles