"The user's page is their context. The moment ClipDeck reaches in to fill a form, highlight a region, or mutate the DOM, the user deserves to see what's about to happen and back out cleanly. Lesson 6 is the preview-and-confirm overlay pattern that turns extension actions into a small contract instead of a surprise."
Why a Preview at All
For passive actions (read selection, save clip, take screenshot), no preview needed — the user initiated, the result lands in the side panel, nothing on the page changed. For active actions (auto-fill an input, click a button, dismiss a modal, scroll into view), the equation flips:
- The user can't always tell what the extension is about to do — the click target, the value to paste, the element to scroll to. A preview names it.
- The user might have set up the page in a specific way (form half-filled, mid-scroll). A preview gives them a moment to confirm or back out before that work is touched.
- Real-world pages have edge cases extension code can't anticipate. A preview that surfaces the planned action lets the user catch obvious wrongness before commit.
The friction is small (one click or one tap of Enter to confirm). The trust gain is enormous.
The Overlay Pattern
Inject a small overlay into the host page from the content script. The overlay describes the action, highlights the target if any, and offers Confirm / Cancel:
- Position: floating at the top-right of the viewport, or anchored near the action target. Never modal — the user should still be able to scroll the page if they need more context.
- Content: one-line summary of the action ("Paste 'service worker eviction' into this input"), a bordered preview of the target element, two buttons.
- Lifecycle: appears when the SW or popup initiates an action; dismissed on Confirm (action runs), Cancel (action aborts), or 10-second timeout (action aborts by default).
Keyboard Affordances
Enter confirms; Escape cancels. Focus the Confirm button on mount so the keyboard path is obvious. The default behavior should match what 90% of users want — usually Confirm — but the user should never feel ambushed because they hit Enter on a focused button they didn't see.
Target Highlighting
When the action affects a specific element, outline it with a temporary border so the user can see what's about to be touched:
function highlightElement(el) {
const original = el.style.outline;
el.style.outline = '2px solid #1a6bd6';
el.style.outlineOffset = '2px';
return () => { el.style.outline = original; };
}
Return a cleanup function so confirm/cancel can both unhighlight. The outline is visually loud but doesn't shift layout, which preserves the user's mental model of where things are.
The Z-Index Wars
Real pages have z-index stacks that fight you. Use the absolute max:
overlay.style.zIndex = '2147483647'; // 2^31 - 1, the maximum int
And use position: fixed so the overlay stays on top of the viewport regardless of page scroll. Pages with fixed headers sometimes still occlude; if that happens, render the overlay inside a :popover element (Chrome 114+) which is in its own top-layer and bypasses z-index entirely.
The Shadow Root Wrap
For extra safety against page CSS bleeding into your overlay, attach a shadow root to your overlay container and put your markup + styles inside it:
const host = document.createElement('div');
host.style.cssText = 'position:fixed;top:0;right:0;z-index:2147483647;';
document.body.appendChild(host);
const shadow = host.attachShadow({ mode: 'closed' });
shadow.innerHTML = `<style>...</style><div>...</div>`;
Closed shadow root means page scripts can't querySelector into it. Your overlay is invisible to the page's own analytics, accessibility tools, and event listeners — useful for extension UI that shouldn't be observable.
The Confirmation Promise
Wrap the whole flow in a Promise the caller awaits:
const confirmed = await previewAndConfirm({
summary: 'Paste "' + clip.text.slice(0, 60) + '" into this input',
target: focusedElement,
timeoutMs: 10000,
});
if (confirmed) await fillInput(focusedElement, clip.text);
Three exits: confirmed (true), cancelled (false), timeout (false). The caller branches on the boolean — no event listener dance, no callback hell. The overlay teardown happens before the Promise resolves.