~15 min · clipdeck, crud, selection, messaging, storage, hotkey
Level 0Extension Curious
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Track 3 ends with the first letter of CRUD ticking off. Content script captures the user's selection, ships it to the service worker, the SW writes it to storage, the popup re-renders. One loop, one slice of real work. The rest of ClipDeck is shaped variations on this template."
The Clip Schema
Before any code, agree on the shape. A clip is:
id — string. Generated at save time, the simplest correct generator is crypto.randomUUID() (Chrome 92+). The id is the primary key for the U and D operations in Track 7.
text — string. The selected text, trimmed.
url — string. location.href at save time.
title — string. document.title at save time. Lets the popup show "From Wikipedia — Service Worker" instead of a raw URL.
savedAt — number. Date.now() milliseconds. Used for sorting newest-first and for the relative-time display.
Stored under clips in chrome.storage.local as a plain array. Five fields, no nested shapes, all JSON-clonable. Track 7's update flow will mutate text; delete removes by id. Track 4's side panel will sort/filter the same array.
The Three Triggers
Three reasonable ways to start a save, each picking up steam in a later track:
Floating button — Lesson 4 already wired one. Visible on every page; cheap to discover.
Keyboard shortcut — this lesson's primary trigger. Declared in the manifest under commands; the SW receives the command, sends a message to the active tab's content script, content script reads the selection and ships it.
Context menu — Track 5 covers this. Right-click on a selection → "Save to ClipDeck."
Real ClipDeck users will probably keep the keyboard shortcut as primary (Ctrl+Shift+K by default; user-rebindable) and the context menu as the discoverability fallback. The floating button graduates to a per-site opt-in toggle later.
The End-to-End Flow
User selects text on a page and hits Ctrl+Shift+K.
Chrome fires chrome.commands.onCommand in the SW with command name save-clip.
SW asks the active tab's content script: chrome.tabs.sendMessage(activeTab.id, { type: 'captureSelection' }).
Content script reads window.getSelection().toString(), gathers URL and title, responds with the payload.
SW receives the response, builds a clip object, reads existing clips from storage, appends, writes back.
chrome.storage.onChanged fires; the popup (if open) and the side panel (Track 4) re-render.
Each hop is small and testable. If the popup never shows the clip, walk the chain backward: check storage in DevTools, then SW logs, then content script logs.
What Goes Wrong
Selection lost on toolbar interaction. Clicking the toolbar collapses the popup and sometimes clears the selection. Hotkey + content-script capture avoids this; popup-driven "Save current selection" buttons need the content script to capture before the popup opens.
Restricted pages.chrome://, the Chrome Web Store, and view-source: URLs reject content-script injection. Wrap the SW's send-to-content-script call in a try/catch and surface a friendly "This page doesn't allow ClipDeck" message.
Empty selection. Content script should respond with a clear empty payload ({ ok: false, reason: 'no-selection' }) and let the SW decide whether to toast or silently drop.
One clip, one loop. Selection in the content script, message to the SW, write to storage, re-render via onChanged. Every later ClipDeck feature is this loop with different verbs.
Test on Wikipedia first. Wikipedia is the friendliest content-script target — no SPA reshuffles, no aggressive CSP, no selection-stealing handlers. Get the full Ctrl+Shift+K → popup-shows-clip loop working on a Wikipedia article before touching SPAs (Gmail, Twitter/X) or sites with sensitive content (banking, password managers — keep those excluded).
Code
manifest.json — version 0.5.0 with commands.save-clip declared·json
Bump clipdeck/manifest.json to the first code block (version 0.5.0, with commands.save-clip). Update clipdeck/background.js with the second code block. Update clipdeck/content.js to add the captureSelection responder from the third code block. Reload the extension. Open a Wikipedia article on Service Workers. Select a paragraph. Press Ctrl+Shift+K (Cmd+Shift+K on Mac). Open the SW DevTools console — you should see [ClipDeck SW] saved clip <uuid> now 1 total. Open the SW DevTools Application tab → Extension Storage → 'local' → 'clips' to see the array with one entry. In clipdeck/popup.html / popup.js, add a list that renders clips (one row per clip showing the first 80 characters and the title). Save another two clips. Confirm the popup updates without manual refresh — that is chrome.storage.onChanged doing its job from Lesson 4's pattern.
Hint
If Ctrl+Shift+K does nothing, check chrome://extensions → Keyboard shortcuts (link at the top) — Chrome reserves some combos and silently drops yours. Pick a different key and reload. If the SW logs no content script on this page, you're on a Chrome-internal URL or one of your exclude_matches patterns — try a different real-web page. If sendResponse returns immediately but the storage write never appears, you forgot return true in the message listener (the silent-timeout trap from Track 2 Lesson 5). The crypto.randomUUID() call requires Chrome 92+; on older Chrome use a small fallback like Date.now() + '-' + Math.random().toString(36).slice(2).
Progress
Progress is local-only — sign in to sync across devices.