"User 의 page 가 user context. ClipDeck 이 손 뻗어 form 채우거나, 영역 highlight 하거나, DOM mutate 하는 순간 user 가 일어날 거 보고 깔끔히 back out 할 자격. Lesson 6 가 extension action 을 놀람 대신 작은 contract 로 만드는 preview-and-confirm overlay 패턴."
왜 preview 가 필요한가
Passive action (selection read, clip 저장, screenshot) 엔 preview 필요 없음 — user 가 시작, 결과 가 side panel 에 도착, page 에 변경 없음. Active action (input auto-fill, button click, modal dismiss, scroll into view) 엔 방정식 flip:
- User 가 항상 extension 이 뭘 하려는지 — click target / paste value / scroll 대상 element — 알 수 없음. Preview 가 이름 붙임.
- User 가 page 를 특정 방식으로 set up 했을 수도 (form 반쯤 채움, mid-scroll). Preview 가 그 work 만져지기 전 confirm 이나 back out 순간 줘.
- Real-world page 가 extension 코드가 anticipate 못 하는 edge case 가짐. 계획된 action 을 surface 하는 preview 가 user 가 commit 전 obvious 잘못 잡게.
마찰이 작음 (confirm 위해 click 하나나 Enter tap 하나). 신뢰 이득 거대.
Overlay 패턴
Content script 에서 host page 에 작은 overlay inject. Overlay 가 action 기술, 있으면 target highlight, Confirm / Cancel 제공:
- 위치: viewport 우상단 floating, 또는 action target 근처 anchor. Never modal — user 가 더 context 필요하면 page scroll 가능해야.
- Content: action 의 한 줄 요약 ("이 input 에 'service worker eviction' paste"), target element 의 bordered preview, 버튼 둘.
- Lifecycle: SW 나 popup 이 action 시작할 때 나타남; Confirm (action 돔), Cancel (action abort), 10 초 timeout (action default abort) 에 dismiss.
키보드 affordance
Enter 가 confirm; Escape 가 cancel. Mount 시 Confirm 버튼 focus, 키보드 경로가 obvious. Default behavior 가 90% user 가 원하는 거 매칭 — 보통 Confirm — 하지만 user 가 안 본 focus 된 버튼에 Enter 쳐서 ambush 느낌 절대 없어야.
Target highlighting
Action 이 특정 element 영향 줄 때, 임시 border 로 outline 해서 user 가 만져질 것 보게:
function highlightElement(el) {
const original = el.style.outline;
el.style.outline = '2px solid #1a6bd6';
el.style.outlineOffset = '2px';
return () => { el.style.outline = original; };
}
Confirm / cancel 둘 다 unhighlight 가능하게 cleanup function 반환. Outline 이 시각적으로 시끄럽지만 layout shift 안 함, user 의 위치 mental model 보존.
Z-index 전쟁
실제 page 가 싸우는 z-index stack 가짐. Absolute max 사용:
overlay.style.zIndex = '2147483647'; // 2^31 - 1, 최대 int
그리고 page scroll 상관없이 overlay 가 viewport 위에 머무르도록 position: fixed 사용. Fixed header 가진 page 가 가끔 여전히 occlude; 그러면 자체 top-layer 에 있고 z-index 완전 우회하는 :popover element (Chrome 114+) 안에 overlay 렌더.
Shadow root wrap
Page CSS 가 overlay 에 bleed 하는 거에 추가 안전 위해, overlay container 에 shadow root attach 하고 markup + style 그 안에 두기:
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 이 page script 가 그 안에 querySelector 못 한다는 뜻. Overlay 가 page 자체 analytics / accessibility 도구 / event listener 에 invisible — observable 안 해야 할 extension UI 에 유용.
Confirmation Promise
전체 flow 를 caller 가 await 하는 Promise 로 wrap:
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);
세 exit: confirmed (true), cancelled (false), timeout (false). Caller 가 boolean 으로 branch — event listener 춤 없음, callback hell 없음. Overlay teardown 이 Promise resolve 전 일어남.