"ChromeEmbed 의 background.js 가 한 job 하는 120 줄: content script 에서 context 받고, tab 별 최신 거 hold, request 시 side panel 한테 건넴. Lesson 2 가 세 context 묶는 bus 패턴."
세 message type
전체 bus 가 세 가지 traffic:
pippa:host-context— user 가 scroll, select, focus 할 때 content script 가 push. Payload 가 full viewport snapshot (Lesson 6).pippa:request-context— 현재 context 원할 때 (mount, iframe load, 명시적 refresh) side panel 이 pull.pippa:open-panel— user 가 action icon click 할 때 popup 이 push. SW 가 user-gesture-derive 된 호출로 chrome.sidePanel.open 호출해서 응답.
그게 전부. Clip storage 없음, chat 상태 없음, soul/brain wiring 없음 — 그것들이 cwkPippa (panel iframe 통해 load) 에 살아. SW 가 얇은 coordinator.
Per-tab Map
const latestContextByTab = new Map() 가 SW 의 유일한 상태. tabId 로 key, 가장 최근 host-context payload 로 value. 두 write:
- Content script 가
pippa:host-contextpush 할 때 handler 가 이전 entry 와 merge (이전 push 에서 selection persistence carry forward; sub-frame push 가 top-frame 상태 blow away 안 함). - SW 가 panel 위해 fresh context pull (
requestContextFromActiveTab) 할 때 결과를 Map 에 저장.
한 read: panel 이 context 요청할 때 SW 가 가장 최근 cached entry 반환하면서 live content script 도 re-query — best of both worlds. SW 가 push 와 ask 사이 evict 됐으면 Map 이 빈 채로 다시 시작; re-query 경로가 fill.
Merge logic
주의 깊게 read 가치:
const next = incomingIsSubframe && previous
? {
...previous,
snapshot_at: incoming.snapshot_at || previous.snapshot_at,
selection: incoming.selection || previous.selection,
}
: {
...(previous || {}),
...incoming,
};
Sub-frame 이 push 하면 (user page 안 iframe), top-frame 의 viewport 텍스트와 URL 를 blow away 안 시킴. 그것의 timestamp 와 selection 만 take. Top-frame 이 push 하면 모든 것 새 상태로 받아들임. 이게 iframe 가진 실제 page (Stack Overflow embed, YouTube video) 가 경쟁 context payload push 하는 거 본 후에만 등장하는 nuance.
Selection fallback
readSelectionFromPage 가 programmatic chrome.scripting.executeScript 호출. Content script 가 selection 보고 안 했어도 (busy 거나 crash 했을 수도), SW 가 Chrome 한테 어떤 frame 의 live selection 이든 read 요청 가능. Fallback 이 message-based content-script query 옆에서 돌고, 다음 두 결과 merge.
Track 6 의 activeTab + scripting combo 가 이걸 standing host permission 없이 동작하게. User-gesture-trigger 된 scripting 호출이 정확히 activeTab cover.
Side panel open 경로
Popup 이 SW 한테 panel 열기 요청할 때:
chrome.windows.getCurrent().then((windowInfo) => {
if (windowInfo.id !== undefined) {
chrome.sidePanel.open({ windowId: windowInfo.id }).catch(() => {});
}
sendResponse({ ok: true });
});
windowId form 이 critical — tabId 대신 전달하면 그 한 tab 으로 panel scope; windowId 가 전체 window 위해 열기. .catch(() => {}) 가 open 완료 전 user 가 panel dismiss 하는 race 를 silently 흡수. Track 4 Lesson 2 의 user-gesture-rule 적용: popup click 이 gesture, SW 가 message 통해 carry.
이 SW 에 없는 것
ChromeEmbed v0.1 이 일부러 안 하는 것:
- SW lifetime 너머 context persist — Chrome evict 하면 Map 비움.
- Context history 유지 — tab 당 최신만.
- cwkPippa 의 backend 와 대화 — iframe 이 모든 real work, SW 는 message 만 forward.
- Clip / soul / brain logic 구현 — 그게 panel iframe content, SW 의 job 아님.
안 추가하는 규율이 SW 작고 obviously 올바르게 유지. 모든 줄이 세 message type 중 하나로 정당화; 'just in case' 로 있는 거 없음.