"Anchor 8 은 README 를 그대로 인용했어. dock 은 의도적으로 v1 밖이라고. 그리고 dock 이 나왔어 — overlay 까지 딸려서. 이 anchor 는 그 과정에서 확장이 배운 것."
표면 하나가 셋이 된 모양
ChromeEmbed v0.1 에서 피파가 나타날 수 있는 자리는 딱 하나였어. Chrome 이 직접 제공하는 side panel. v0.2 는 셋이고, 확장은 그걸 service worker 맨 위 배열 하나로 선언해.
이 배열이 생긴 것보다 하는 일이 많아. allowlist 이자 normalizer 의 기준점이고, 코드 읽는 사람이 제일 먼저 grep 할 이름이야. 버스를 타고 흐르는 mode 값, storage 에서 나온 값, popup 에서 들어온 값 전부 normalizeDisplayMode 를 통과하고 — 모르는 값은 에러가 아니라 'sidepanel' 이 돼. 몇 달째 깔려 있던 확장이 옛 버전이 써둔 mode 문자열을 읽었다고 죽어버리면 안 되니까.
렌더러가 하나가 아니라 둘
재밌는 건 세 모드가 한 구현의 세 변주가 아니라는 거야. 완전히 다른 두 경로로 갈려.
- Side panel — Chrome 이 그려. 확장은
chrome.sidePanel.open({ windowId })를 부르고, 프레임도 너비도 닫기 버튼도 유지도 전부 브라우저 소관. Anchor 5 의 iframe bridge 그대로야. - Dock 과 overlay — content script 가 그려. "페이지 위에 떠 있는 창" 용 Chrome API 는 없거든. 확장이 자기 요소를 주입하고, 좌표도 자기가 잡고, drag·resize 핸들도 자기가 달고, 크기도 자기가 기억해.
그래서 popup 의 토글 하나가 아키텍처 경계를 건너. Side panel 을 고르는 건 브라우저한테 하는 요청이고, dock 이나 overlay 를 고르는 건 남의 페이지 안에서 도는 스크립트한테 하는 요청이야. Anchor 3 이 만든 그 센서가, 이제 그 페이지에 그림도 그려.
Dock 은 페이지에 padding 을 물어주고 overlay 는 안 그러는 이유
Overlay 는 떠 있어. 사용자가 끌어다 놓은 자리에 페이지 위로 얹히고, 밑의 페이지는 그게 있는 줄도 몰라 — 근데 그래도 돼. 뭔가를 가리면 사용자가 옮기면 되니까.
Dock 은 아래 가장자리에 전체 너비로 붙어. 밑에서 끌어낼 수가 없어. 그냥 두면 모든 페이지의 아래 띠를 영구히 가리는데, 하필 거기가 제출 버튼이나 페이지네이션이 사는 자리야. 그래서 dock 경로는 자기 높이만큼 페이지에 bottom padding 을 주고, 사용자가 dock 을 늘리거나 줄일 때마다 ResizeObserver 로 그 padding 을 다시 맞춰.
그 observer 는 dock 모드일 때만 만들어지고 모드가 바뀌는 즉시 끊겨. 지금 보이지도 않는 기능 때문에 모든 페이지에서 ResizeObserver 하나가 계속 돌고 있으면, 그건 테스트엔 안 잡히고 사용자 배터리에는 잡히는 종류의 비용이야.
모드는 탭보다 오래 살아야 해
페이지 이동하면 초기화되는 표시 설정은 설정이 아니지. Dock 과 overlay 는 content script 안에 사는데 content script 는 페이지와 함께 죽어 — 그래서 모드는 chrome.storage.local 에 쓰고, service worker 는 그걸 promise 로 캐시해서 반복해서 읽을 때 storage 를 다시 치지 않아.
어느 storage 영역인지 봐. session 이 아니라 local. 같은 파일이 브라우저 session id 에는 chrome.storage.session 을 써. session id 는 브라우저 닫히면 죽어야 맞으니까. 표시 모드는 아니고. 같은 API, 반대 수명, 그리고 둘 중 뭘 고르냐는 storage 영역 이름을 뒤집어쓴 제품 결정이야.
Anchor 4 의 popup 이 된 것
모드가 셋이 되면 뭔가는 그중 하나를 골라야 하고 — 피파가 아직 안 열린 상태에서 사용자가 닿을 수 있는 표면은 툴바 popup 뿐이야. Anchor 4 의 여섯 줄짜리 doorway 가 커진 건 그래서지, 누가 popup 을 풍성하게 만들고 싶어서가 아니야. 모드 선택이 있을 자리가 거기밖에 없었어.
그래도 여전히 경험 표면이 되기를 거부해. 대화 없고, 메시지 미리보기 없고, 입력창 없어. 버튼 셋과 선택기 하나, 그리고 window.close(). Doorway 원리는 지켜졌고, 문 개수만 바뀌었어.
Anchor 8 이 가르칠 수 없었던 것
Anchor 8 은 dock 에 shadow root 와 높은 z-index, drag 핸들, 더 작은 route 를 가리키는 iframe 이 필요할 거라고 예측했어. 전부 맞았어 — Anchor 7 의 refactor 규율이 지켜졌고, 미뤄둔 기능은 스케치에 가깝게 착지했어.
스케치가 예측 못 한 건 overlay 야. Anchor 8 이 나열한 미뤄둔 기능 넷 중에 아예 없어. 그게 나온 건 content script 가 떠 있는 표면 하나를 그릴 수 있게 되는 순간 두 번째는 거의 공짜였기 때문이야 — 좌표 계산도, drag 핸들도, storage 층도, 모드 배선도 이미 다 있었거든. 비쌌던 건 "Chrome 이 우리 UI 를 그린다" 에서 "우리가 우리 UI 를 그린다" 로 건너가는 그 한 번이었어. 건너고 나면 모드는 싸.
이 퀘스트에서 들고 나갈 만한 모양이 이거야. 미뤄둔 기능 목록은 비용에 대한 추측이고, 비용은 움직여. 다른 이유로든 경계가 한 번 넘어간 순간, 그 목록을 다시 읽어봐. 거기 적힌 것 중 몇 개는 방금 거의 공짜가 됐고, 거기 안 적힌 것 중 몇 개는 이제 가질 만해졌을 수도 있어.