pbakaus/impeccable · error · Error
No painted ancestor for Svelte shader proxy
Error message
No painted ancestor for Svelte shader proxy
What it means
captureElementFromRenderedAncestor throws this when findShaderProxyCaptureRoot(el) returns null — i.e. it cannot find a suitable painted ancestor element for the Svelte shader proxy to rasterize with html-to-image (ms.domToCanvas). The shader effect composites a background captured from an ancestor; without a painted ancestor, capture, upload, and the shader cannot agree on what sits behind the element.
Source
Thrown at skill/scripts/live-browser.js:8139
nr.left <= er.left + 0.5 &&
nr.top <= er.top + 0.5 &&
nr.right >= er.right - 0.5 &&
nr.bottom >= er.bottom - 0.5;
if (containsElement && paintsShaderProxySurface(node)) return node;
node = node.parentElement;
}
return null;
}
// Capture the element (with current annotations baked in) and return
// { blob, paper }: the PNG Blob, plus the representative backdrop tone for the
// shader's halftone ground (so capture, upload, and shader all agree on what
// sits behind the element). Shared between the Go flow (uploads the blob) and
// the shader-resume path.
async function captureElementFromRenderedAncestor(ms, el, opts) {
const doc = el.ownerDocument || document;
const captureRoot = findShaderProxyCaptureRoot(el);
if (!captureRoot) throw new Error('No painted ancestor for Svelte shader proxy');
const rootCanvas = await ms.domToCanvas(captureRoot, opts);
const S = opts.scale;
const er = el.getBoundingClientRect();
const rr = captureRoot.getBoundingClientRect();
const sx = (er.left - rr.left) * S;
const sy = (er.top - rr.top) * S;
const sw = er.width * S;
const sh = er.height * S;
if (sw <= 0 || sh <= 0) throw new Error('Selected element has no visible capture rect');
const crop = doc.createElement('canvas');
crop.width = Math.max(1, Math.round(sw));
crop.height = Math.max(1, Math.round(sh));
const cctx = crop.getContext('2d', { willReadFrequently: true });
cctx.drawImage(rootCanvas, sx, sy, sw, sh, 0, 0, crop.width, crop.height);
const paper = dominantRgb01(cctx, crop.width, crop.height) || averageRgb01(cctx, crop.width, crop.height);
const blob = await new Promise((res) => crop.toBlob(res, 'image/png'));
if (!blob) throw new Error('Ancestor crop failed to produce a PNG blob');
return { blob, paper };View on GitHub (pinned to 2bc2879276)
Solutions
- Ensure the target element is attached, visible, and has a painted ancestor with layout (no display:none/hidden subtree)
- Apply the effect to an inner, actually-painted element rather than a bare proxy/host wrapper
- Check findShaderProxyCaptureRoot's expectations against the Svelte component's rendered DOM and adjust the selector heuristic if custom wrappers intervene
- Wrap the target in an element with a background/position so a valid capture root exists
Defensive patterns
Strategy: validation
Validate before calling
function hasPaintedAncestor(el) {
if (!el.isConnected) return false;
let n = el.parentElement;
while (n) {
const cs = getComputedStyle(n);
if (cs.display !== 'none' && cs.visibility !== 'hidden') return true;
n = n.parentElement;
}
return false;
}
// call before triggering the shader capture Type guard
function isCaptureReady(el) {
return el instanceof Element && el.isConnected
&& el.getBoundingClientRect().width > 0;
} Try / catch
try {
await captureElementFromRenderedAncestor(ms, el, opts);
} catch (err) {
if (err.message.includes('No painted ancestor')) {
showToast('Effect needs a visible parent element; select the inner content instead.');
}
} Prevention
- Only apply shader effects to attached, visible elements with laid-out ancestors
- Avoid targeting bare Svelte host/wrapper components; select the painted inner element
- Wait for mount/first paint before triggering capture on freshly rendered components
- Check display:none/visibility ancestors before enabling the effect in the UI
When it happens
Trigger: Requesting a shader/halftone effect on a Svelte component whose element has no ancestor that produces a valid capture root — e.g. the element is position:fixed with no positioned/painted ancestor, is inside a detached or display:none subtree, or the proxy DOM structure differs from what findShaderProxyCaptureRoot expects.
Common situations: Applying the effect to an element inside a collapsed/hidden panel; Svelte wrapper components adding extra nesting that breaks the ancestor heuristic; running the effect on an element not yet painted (just mounted, off-DOM).
Related errors
- Selected element has no visible capture rect
- [impeccable] Svelte component abort cleanup failed:
- [impeccable] Svelte component reset cleanup failed:
- [impeccable] Could not find original element in live DOM.
- variant_compile_failed
AI-assisted analysis of pbakaus/impeccable@2bc2879276 (2026-09-08).
Data as JSON: /api/errors/ba4599c3295c147f.
Report an issue: GitHub.