gitbutlerapp/gitbutler · error · Error
Expected to find content container
Error message
Expected to find content container
What it means
setupViewport assumes the Scrollbar component's viewport element has a child element (the content container) at index 0, using it to track content size for thumb positioning. When the viewport has no children at setup time the component throws this error instead of rendering a scrollbar that can never be sized correctly.
Solutions
- Ensure the viewport always contains its content wrapper before Scrollbar binds (render content inside the viewport slot).
- Defer Scrollbar setup until the content element exists (await tick() / mount after data loads).
- Patch setupViewport to defer or re-check with a MutationObserver instead of throwing when children is empty.
- In tests, always render a child element inside the viewport.
- Verify the element passed as viewport is the scrolling container, not the content itself.
Example fix
// before
const content = viewport.children.item(0);
if (!content) throw new Error("Expected to find content container");
// after
const content = viewport.children.item(0);
if (!content) {
new MutationObserver((_m, obs) => {
if (viewport.children.item(0)) { obs.disconnect(); setupViewport(viewport); }
}).observe(viewport, { childList: true });
return;
} Defensive patterns
Strategy: validation
Validate before calling
// before wiring Scrollbar
if (!viewport || viewport.children.length === 0) {
await tick(); // or wait for content render
if (viewport.children.length === 0) return; // skip scrollbar entirely
} Type guard
function hasContentContainer(el: Element): boolean {
return el.children.length > 0 && el.children.item(0) !== null;
} Try / catch
try {
setupViewport(viewport);
} catch (err) {
if (err instanceof Error && err.message === "Expected to find content container") {
observeUntilChild(viewport, () => setupViewport(viewport)); // retry on DOM mutation
} else throw err;
} Prevention
- Always render the content wrapper inside the viewport's default slot.
- Never mount Scrollbar before its content exists (await data/tick first).
- Keep the viewport → content DOM structure the component expects when refactoring.
- Render a placeholder child during loading states so the container is never empty.
- Cover Scrollbar mount with a test that includes slotted children.
When it happens
Trigger: Scrollbar is attached to a viewport element that has no child elements yet — e.g. the slot/content is conditionally rendered after mount, the content is removed before setup runs, or the component is wired to the wrong (empty) element.
Common situations: Async-loaded list content not yet in the DOM when Scrollbar initializes; {#if} blocks hiding the content initially; tests mounting Scrollbar without slot children; refactoring that changed the DOM nesting so viewport.children[0] no longer exists.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Error triggering button click via hotkey:
- No branch selected!
- No branch selected!
- No provider found for
- No stack selected!
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/abc8fb3479d5460f.
Report an issue: GitHub.
Appendix: source
Thrown at packages/ui/src/lib/components/scroll/Scrollbar.svelte:158
if (!shouldShowOnHover) return;
visible = false;
}
function setupViewport(viewport: Element) {
if (!viewport) return;
if (typeof window.ResizeObserver === "undefined") {
throw new Error("window.ResizeObserver is missing.");
}
const observerSize = new ResizeObserver(() => {
updateTrack();
});
observerSize.observe(viewport);
const content = viewport.children.item(0);
if (!content) {
throw new Error("Expected to find content container");
}
// Sometimes the content size changes before the browser fires a
// scroll event, so we compensate here to avoid thumb jumpiness.
// However, if viewport.scrollTop has already been adjusted by
// another component (e.g. VirtualList's scroll compensation),
// we must use the actual value to avoid double-compensating.
const observerContentSize = new ResizeObserver(() => {
if (lastHeight) {
const diff = content.scrollHeight - lastHeight;
// If viewport.scrollTop diverged from our last-known value,
// another component (e.g. VirtualList) already compensated.
if (viewport.scrollTop !== scrollTop) {
scrollTop = viewport.scrollTop;
} else if (diff !== 0) {
scrollTop = viewport.scrollTop + diff;
}
}View on GitHub (pinned to 58e5313667)