~14 min · clipdeck, mode-toggle, badge, per-tab, storage
Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Track 5 가 작고 정직한 권한으로 끝남: ClipDeck 이 언제 주목하는지 user 가 결정. Tax return 타이핑 중인 page 에서 pause, article 로 돌아가는 순간 un-pause. State 가 storage 에 살아 모든 surface 가 sync 유지."
이유
User 가 항상 ClipDeck active 원하는 거 아님. Privacy 이유, 놀라운-clip 회피, 또는 그냥 민감한 form 채울 동안 extension off 인 거 아는 편안함. Pause toggle 이 trust 의 막대:
Per-tab scope 라 다시 enable 기억할 필요 없음.
SW eviction 너머 persistent 라 pause 가 silent reset 안 됨.
Badge 에 visible 해서 한눈에 알 수 있음.
Popup / keyboard shortcut / (선택적) side panel 에서 닿음.
State shape
chrome.storage.local 의 단순 배열:
{ pausedTabs: number[] }
Tab ID 가 browser restart 너머 durable 안 함 (Chrome 이 새 거 할당), 하지만 pause 는 tab 이 존재하는 동안만 의미 있음, 그래서 fine — tab 닫히면 id 사라지고 같은 id 의 fresh tab 이 un-paused 로 시작.
일일 정도 cleanup pass (또는 chrome.runtime.onStartup 의 하나) 가 chrome.tabs.query({}) 와 intersect 해서 stale id 제거. 배열이 영원히 자라는 거 막음.
세 touch point
Pause state 가 중요한 곳마다 같은 storage 값 read:
Popup 버튼. 현재 tab 의 pause state read, toggle, write back, storage.onChanged 가 badge 와 content script update.
Keyboard shortcut. 같은 logic, command toggle-pause 의 chrome.commands.onCommand 에서 trigger (Lesson 4 에서 wire).
Content script gate. Capture flow 전 content script 가 storage 확인; pause 면 no-op 응답, SW 가 아무것도 기록 안 함.
SW 가 active tab 의 pause state 변경마다 badge 도 write — paused 면 setBadgeText({ tabId, text: 'II' }), un-paused 면 empty.
Race-free update
배열 read 후 write 가 두 event 가까이 fire (user 가 popup 버튼 클릭 동시 hotkey 누름) 시 race 부름. Defensive 패턴은 항상 같은 transaction 안에서 re-read 하는 작은 mutation helper:
async function togglePauseForTab(tabId) {
const { pausedTabs = [] } = await chrome.storage.local.get('pausedTabs');
const next = pausedTabs.includes(tabId)
? pausedTabs.filter((t) => t !== tabId)
: [...pausedTabs, tabId];
await chrome.storage.local.set({ pausedTabs: next });
return next.includes(tabId);
}
엄격히 atomic 아님 — get 과 set 사이 다른 event 가 insert 가능 — 하지만 human-rate toggle 엔 사실상 괜찮음. ClipDeck 이 multi-window sync 자라면 sequence id 가진 적절한 locking 패턴으로 swap.
시각 피드백
Badge 가 정직한 signal. 세 state:
Active + 오늘 clip 있음 → brand color 의 숫자 ("3").
Active + 오늘 clip 없음 → empty badge.
Paused → grey background 의 "II" 나 "PAUSE".
Storage 변경마다 count-mode 와 pause-mode 사이 전환. Popup 도 버튼 label flip: "Pause on this site" ↔ "Resume on this site," side panel 이 user 가 paused tab browsing 중이면 banner 표시 가능 ("Capture paused. Resume?").
Track 5 마무리
이 lesson 으로 ClipDeck 이 full action surface 가짐:
오늘-count + pause indicator 둘 다 하는 badge 가진 icon.
Stats, primary action 셋, discoverable settings link 가진 popup.
Label 에 실제 선택 텍스트 가진 right-click context menu.
User-bindable keyboard shortcut 넷.
Omnibox 검색 keyword clip.
모든 곳에 반영된 per-tab pause/resume.
Track 6 가 다음 합리적 step — permission model. ClipDeck 이 왜 그게 요청하는 걸 요청하는지, 필요한 순간에만 요청하는 법, 거절하는 user 위해 design 하는 법.
Pause 는 user 의 trust dial. Per-tab scope, storage 에 persist, badge 와 popup 에 mirror. Surface 셋, state 하나, 놀람 없음.
대안으로서 chrome.storage.session. Chrome 102+ 가 chrome.storage.session ship — browser 닫히면 죽는 in-memory storage area. Browser restart 너머 reset 돼야 하는 (일부 케이스에서 user 기대) pause state 엔 session 이 깔끔한 fit. ClipDeck v1 은 pause 가 restart 살아남게 local 사용, 선택은 자기 거 — 둘 다 틀린 거 없음, 다른 default 인코딩.
Code
background.js — toggle + badge refresh + tab close cleanup·javascript
첫 번째 code block 을 clipdeck/background.js 에 추가 — togglePauseForTab helper / per-tab badge refresh / command wiring / tab-close cleanup. 두 번째 code block 을 clipdeck/popup.js 에 추가 — Pause 버튼 handler 와 live label. togglePause 메시지 router (두 번째 code block 맨 아래 주석) 를 background.js 에 추가. 기존 save-clip handler 를 세 번째 code block 의 gated 버전으로 교체. Reload. 아무 page 에서 popup 열기 — Pause on this tab. Click — badge 가 grey 의 II 로 변경, label 이 Resume on this tab 으로 flip. Ctrl+Shift+K 누르기 — 아무것도 저장 안 됨. Resume on this tab click — badge 복원, save-clip 다시 동작. Tab 전환 — badge 가 각 tab 의 per-tab state 독립 반영. Paused tab 닫고 같은 URL 재오픈 — state 가 fresh (un-paused), 의도대로.
Hint
Pause 시 badge 가 II 로 안 변경되면 chrome.action.setBadgeText await 잊은 것 — Promise. Popup 에서 togglePause 가 Receiving end does not exist throw 하면, togglePause message 의 SW listener 등록 안 됨 (두 번째 code block 맨 아래 commented router — 추가). Paused 동안 save-clip 여전히 저장하면, SW-side gate 가 handler 에 없음 — save-clip 의 기존 chrome.commands.onCommand listener 찾아 그 맨 위에 pause 확인 추가.
Progress
Progress is local-only — sign in to sync across devices.