{"record":{"id":"abc8fb3479d5460f","repo":"gitbutlerapp/gitbutler","slug":"expected-to-find-content-container","errorCode":null,"errorMessage":"Expected to find content container","messagePattern":"Expected to find content container","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/ui/src/lib/components/scroll/Scrollbar.svelte","lineNumber":158,"sourceCode":"\t\tif (!shouldShowOnHover) return;\n\t\tvisible = false;\n\t}\n\n\tfunction setupViewport(viewport: Element) {\n\t\tif (!viewport) return;\n\n\t\tif (typeof window.ResizeObserver === \"undefined\") {\n\t\t\tthrow new Error(\"window.ResizeObserver is missing.\");\n\t\t}\n\n\t\tconst observerSize = new ResizeObserver(() => {\n\t\t\tupdateTrack();\n\t\t});\n\t\tobserverSize.observe(viewport);\n\n\t\tconst content = viewport.children.item(0);\n\t\tif (!content) {\n\t\t\tthrow new Error(\"Expected to find content container\");\n\t\t}\n\n\t\t// Sometimes the content size changes before the browser fires a\n\t\t// scroll event, so we compensate here to avoid thumb jumpiness.\n\t\t// However, if viewport.scrollTop has already been adjusted by\n\t\t// another component (e.g. VirtualList's scroll compensation),\n\t\t// we must use the actual value to avoid double-compensating.\n\t\tconst observerContentSize = new ResizeObserver(() => {\n\t\t\tif (lastHeight) {\n\t\t\t\tconst diff = content.scrollHeight - lastHeight;\n\t\t\t\t// If viewport.scrollTop diverged from our last-known value,\n\t\t\t\t// another component (e.g. VirtualList) already compensated.\n\t\t\t\tif (viewport.scrollTop !== scrollTop) {\n\t\t\t\t\tscrollTop = viewport.scrollTop;\n\t\t\t\t} else if (diff !== 0) {\n\t\t\t\t\tscrollTop = viewport.scrollTop + diff;\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/packages/ui/src/lib/components/scroll/Scrollbar.svelte#L140-L176","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst content = viewport.children.item(0);\nif (!content) throw new Error(\"Expected to find content container\");\n// after\nconst content = viewport.children.item(0);\nif (!content) {\n  new MutationObserver((_m, obs) => {\n    if (viewport.children.item(0)) { obs.disconnect(); setupViewport(viewport); }\n  }).observe(viewport, { childList: true });\n  return;\n}","handlingStrategy":"validation","validationCode":"// before wiring Scrollbar\nif (!viewport || viewport.children.length === 0) {\n  await tick(); // or wait for content render\n  if (viewport.children.length === 0) return; // skip scrollbar entirely\n}","typeGuard":"function hasContentContainer(el: Element): boolean {\n  return el.children.length > 0 && el.children.item(0) !== null;\n}","tryCatchPattern":"try {\n  setupViewport(viewport);\n} catch (err) {\n  if (err instanceof Error && err.message === \"Expected to find content container\") {\n    observeUntilChild(viewport, () => setupViewport(viewport)); // retry on DOM mutation\n  } else throw err;\n}","preventionTips":["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."],"tags":["svelte","dom","scrollbar"],"backgroundTag":"resource-not-found","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}