C.W.K.
Stream
Lesson 03 of 06 · published

Panel vs Popup vs Full Tab — 맞는 surface 고르기

~10 min · side-panel, popup, tabs.create, ux, architecture

Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"세 surface, 세 lifetime, 세 정보 밀도. Lesson 3 는 decision matrix — 어느 게 어느 ClipDeck feature 얻는지, 그리고 잘못된 feature 를 잘못된 surface 에 밀어 넣는 게 가장 흔한 extension UX 버그인 이유."

세 surface

  • Popup. 작고 transient. Max ~800×600 픽셀, 합리적 크기는 320×480 쯤. 바깥 click 에 죽음. Toolbar-icon click 에 mount, focus 떠나는 순간 unmount. One-off 상호작용 — 설정 toggle / quick status / single-button action — 에 탁월.
  • Side panel. 키 크고 persistent. Full window height, user 선호에 따라 ~300–400 px wide. Page click 살아남고; tab 전환 살아남고; user 가 명시적으로 닫을 때만 죽음. User 가 반복 참조하는 ambient context 에 탁월.
  • Full tab. 가장 큼. chrome.tabs.create({ url: 'options.html' })chrome_url_overrides 로 열기. 일반 web page 와 같은 context type; 전체 window 차지 가능. 전용 app / settings page / onboarding flow / 복잡한 multi-step UI 에 탁월.

Lifetime, 비교

Surface열려 있는 동안Focus 잃을 때Re-mount 비용
Popup바깥 click 전까지모든 것매 open 마다 load + render
Side paneluser 가 toggle off 할 때까지없음첫 open 시 load, 이후 persist
Full tabuser 가 tab 닫을 때까지없음첫 open 시 load, 이후 persist

Popup mount 가 저렴하지만 자주 일어남; panel 과 full-tab mount 가 더 비싸지만 드물게 일어남. 주된 throughput-vs-latency 거래.

ClipDeck allocation

  • Popup — quick action 과 stats. "Save current selection" 버튼 (SW 로 메시지 보내 실제 capture), "Open clip list" 버튼 (side panel 열기), 오늘 카운트, "Reset counters". One-click 으로 완료되고 visible 유지 필요 없는 모든 것.
  • Side panel — clip library. Timestamp / source URL / search box / filter / inline edit-delete (Track 7) 가진 full list. User 가 article 읽으면서 열어 둠.
  • Full tab — 결국의 options page (Track 6 가 소개) 와 side panel 보다 공간 필요한 가능한 export/import view 용 예약. ClipDeck v1 은 full-tab UI 없이 ship; v2 가 추가할 수도.

결정 규칙, 질문으로: user 가 이 UI 를 얼마나 오래 봐야 해? 초 → popup. 분-부터-세션 → side panel. Long-running 작업이나 settings → full tab.

Anti-pattern

  • Clip library 를 popup 에 cramming. 10 entry 넘어가면 popup 답답. 증상: 강제 스크롤, 매 open 마다 selection 손실, filter 공간 없음. Side panel 로 밀기.
  • One-click action 을 side panel 에. User 가 dismiss 만 위해 panel 열게 강제. 증상: feature engagement 낮음. Popup 으로 밀기.
  • Settings 를 popup 안에 만들기. Multi-section form 은 공간 필요. 증상: 작은 input, form 상호작용에 popup auto-close. options_page 통해 full tab 으로 밀기.
  • Quick status check 위해 full tab 열기. Disruptive — user 가 있던 page 에서 끌어냄. 증상: user 가 uninstall. Popup 이나 panel 로 밀기.
Surface 를 user intent 의 lifetime 에 맞추기. 초 = popup, 분 = panel, session = full tab. 잘못된 surface 가 extension 이 ship 할 수 있는 가장 큰 UX 버그.
Surface 들이 코드 공유 가능? 응 — popup / side panel / full-tab page 모두 같은 JS module import 가능하고 스타일 공유 가능. ClipDeck 의 clip-row renderer 가 panel.js 와 미래 export-page.js 가 import 하는 한 clip-row.js 에 살 수 있음. Popup 은 보통 다른 니즈 (status-only) 지만, popup 과 panel 이 parallel render logic 자라면 중복보다 shared module 로 factor.

Code

background.js — full-tab options page 열기·javascript
// background.js — 전용 full-tab page 열기 (나중 settings/export 용)
async function openOptionsTab() {
  // chrome.runtime.openOptionsPage 가 설정되면 manifest 의 options_page 사용,
  // 아니면 manifest.json 의 options_ui 선언 여는 걸로 fallback.
  if (chrome.runtime.openOptionsPage) {
    await chrome.runtime.openOptionsPage();
  } else {
    await chrome.tabs.create({ url: chrome.runtime.getURL("options.html") });
  }
}

// Manifest 가 options page 선언해야 함:
//   "options_page": "options.html"
// — 또는 더 풍부한:
//   "options_ui": { "page": "options.html", "open_in_tab": true }
popup.js — popup 을 library 가 아닌 quick status + entry point 로·javascript
// popup.js — library view 를 side panel 에 위임하는 minimal popup
async function quickStats() {
  const { clips = [], totalVisits = 0 } = await chrome.storage.local.get([
    "clips",
    "totalVisits",
  ]);
  document.getElementById("clipCount").textContent = String(clips.length);
  document.getElementById("visitCount").textContent = String(totalVisits);
}

document.getElementById("openPanelBtn").addEventListener("click", async () => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  if (tab?.id) await chrome.sidePanel.open({ tabId: tab.id });
});

quickStats();
panel.js — library view 가 persistent surface 에 머묾·javascript
// panel.js — full library 가 여기 살아
// (source title / timestamp / source URL 와 함께 모든 clip 렌더.)
async function render() {
  const { clips = [] } = await chrome.storage.local.get("clips");
  const root = document.getElementById("list");
  root.innerHTML = clips.length === 0 ? "<p class='empty'>No clips yet.</p>" : "";
  for (const clip of clips) {
    const row = document.createElement("div");
    row.className = "row";
    row.innerHTML = `
      <div class="meta">
        <a href="${clip.url}" target="_blank">${escapeHtml(clip.title || clip.url)}</a>
        <time>${new Date(clip.savedAt).toLocaleString()}</time>
      </div>
      <p>${escapeHtml(clip.text)}</p>`;
    root.appendChild(row);
  }
}

function escapeHtml(s) {
  return String(s).replace(/[&<>"']/g, (ch) => ({
    "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;",
  })[ch]);
}

chrome.storage.onChanged.addListener((c, a) => a === "local" && "clips" in c && render());
render();

External links

Exercise

clipdeck/popup.html 을 Clips: <span id="clipCount"></span>, Visits: <span id="visitCount"></span>, 기존 Reset / Ping 버튼, 새 Open Clip List 버튼만 보이도록 refactor. Clip list 렌더링을 세 번째 code block 사용해 clipdeck/panel.js 로 옮기기. Reload. Toolbar icon click — popup 이 이제 작고, stats + action 만. Open Clip List click — panel 이 browse 위해 인계. Clip 몇 개 저장 후 tab 전환; panel content 가 onChanged 통해 update. Bonus: manifest 에 "options_page": "options.html"<h1>ClipDeck Settings (coming Track 6)</h1> placeholder options.html 추가, popup 에서 chrome.runtime.openOptionsPage 버튼 추가 — Chrome 이 실제 tab 여는지 확인.
Hint
Refactor 후 popup 답답해 보이면 body 에 명시적 width: 280px; padding: 12px; 설정 — popup 이 content 에 auto-size 지만 max width 가짐. Clip 저장할 때 panel update 안 되면 panel.js 가 chrome.storage.onChanged 구독 AND area === 'local' 으로 filter 하는지 더블 체크. openOptionsPage 버튼은 현대 Chrome 에서 manifest 변경 없이도 종종 동작 (generic placeholder page 로 fallback), 하지만 적절한 UX 는 명시적 options_page 선언 필요 — user 가 chrome://extensions → ClipDeck → Details → Extension options 로도 찾을 수 있게.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.