<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: glensum</title>
    <description>The latest articles on DEV Community by glensum (@glenkit).</description>
    <link>https://dev.to/glenkit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4146503%2F1e546bec-ae0f-4475-9d59-5496586039ef.png</url>
      <title>DEV Community: glensum</title>
      <link>https://dev.to/glenkit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/glenkit"/>
    <language>en</language>
    <item>
      <title>Your screenshot tool is the leak — doing redaction entirely client-side</title>
      <dc:creator>glensum</dc:creator>
      <pubDate>Fri, 16 Oct 2026 08:23:32 +0000</pubDate>
      <link>https://dev.to/glenkit/your-screenshot-tool-is-the-leak-doing-redaction-entirely-client-side-38gn</link>
      <guid>https://dev.to/glenkit/your-screenshot-tool-is-the-leak-doing-redaction-entirely-client-side-38gn</guid>
      <description>&lt;p&gt;The most common way people redact a screenshot is still this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take the screenshot.&lt;/li&gt;
&lt;li&gt;Search "blur image online".&lt;/li&gt;
&lt;li&gt;Upload it to the first result.&lt;/li&gt;
&lt;li&gt;Blur the part that matters, download, paste into Slack.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 is the problem. You just handed an unredacted screenshot — session tokens, customer emails, API keys, production data — to a website you found ninety seconds ago. The blur happens &lt;em&gt;after&lt;/em&gt; the upload, and the "delete" button on that page deletes your copy, not theirs.&lt;/p&gt;

&lt;p&gt;It's a small, boring leak, and it happens thousands of times a day, because the tool that solves the aesthetic problem (make this text unreadable) creates a security problem (it made a copy first).&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I'd propose
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Redaction must happen on the machine the screenshot was taken on, before the image goes anywhere.&lt;/strong&gt; If the pixels reaching any network request are already covered, the rest of the pipeline stops mattering — you can paste it into a chat, a ticket, a bug report, and the sensitive part isn't in the file.&lt;/p&gt;

&lt;p&gt;That's the whole design constraint. Everything below falls out of taking it seriously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a leak, in the browser
&lt;/h2&gt;

&lt;p&gt;A screenshot in a browser is a compositor output. The extension API is &lt;code&gt;chrome.tabs.captureVisibleTab&lt;/code&gt;, which returns a data URL of the visible viewport:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chrome&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tabs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;captureVisibleTab&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point you hold a faithful copy of everything on screen, including the parts nobody wants in a public issue: the token in the address bar, the &lt;code&gt;Authorization&lt;/code&gt; header value in some debug panel, the customer list scrolled halfway down.&lt;/p&gt;

&lt;p&gt;The naive version of a redaction feature is a UI on top of that image. The important version redacts &lt;strong&gt;before you decide to keep the file&lt;/strong&gt; — i.e. the extension should never write the raw capture anywhere durable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "covered" should actually mean
&lt;/h2&gt;

&lt;p&gt;Two choices worth being deliberate about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blur vs solid fill.&lt;/strong&gt; Blur looks more polite, but for text it's a lossy encoding, not a one-way function. Low-radius blurs of high-contrast text (a monospace token on a dark terminal, for instance) have been reconstructed with deconvolution, and text heights are highly constrained, which shrinks the search space. For secrets, use an opaque fill. Blur is for faces and plates, where the content is continuous, not discrete.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redraw vs overlay.&lt;/strong&gt; Overlaying a rectangle in an HTML layer is easy and completely useless — the underlying pixels are still in the export. You need to composite (draw the image, then draw the fill on top) and export the re-encoded result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OffscreenCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgBitmap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// the capture&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#111&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                     &lt;span class="c1"&gt;// irreversible at this point&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;off&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convertToBlob&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything downstream operates on that blob. If a cover is wrong, the user redraws; there is no "uncover" and no original in the payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually reduces user error: pre-detection
&lt;/h2&gt;

&lt;p&gt;Manual redaction fails the same way manual everything fails — people miss one spot, usually the boring one. So the useful feature isn't a nicer brush; it's &lt;strong&gt;candidate detection before the user looks&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DOM-matched regions.&lt;/strong&gt; Elements with &lt;code&gt;type="password"&lt;/code&gt;, &lt;code&gt;autocomplete="cc-number"&lt;/code&gt;, &lt;code&gt;inputmode="numeric"&lt;/code&gt;, or a &lt;code&gt;name&lt;/code&gt;/&lt;code&gt;id&lt;/code&gt; matching &lt;code&gt;/token|secret|api[-_]?key|otp|ssn/i&lt;/code&gt;. The element's bounding box maps directly to image coordinates via &lt;code&gt;getBoundingClientRect()&lt;/code&gt; plus scroll offsets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Text-pattern regions.&lt;/strong&gt; Run a small regex set over the visible text nodes and highlight matches: JWT shape (&lt;code&gt;eyJ[A-Za-z0-9_-]{10,}\.&lt;/code&gt;), &lt;code&gt;sk-&lt;/code&gt;/&lt;code&gt;ghp_&lt;/code&gt;/&lt;code&gt;AKIA&lt;/code&gt; prefixes, IBAN-ish and card-number-ish digit runs, private-key headers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural guesses.&lt;/strong&gt; Terminal/console panes, &lt;code&gt;.env&lt;/code&gt;-looking blocks, admin tables — worth flagging, not auto-covering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The output should be a &lt;em&gt;proposed&lt;/em&gt; set of covers the user can delete in one click. Auto-covering without review is its own failure mode: over-covering quietly destroys the screenshot's purpose, and the user then posts a useless image or, worse, turns the feature off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where detection honestly fails
&lt;/h2&gt;

&lt;p&gt;I'd rather list the limits than imply a guarantee:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Canvas / WebGL / video.&lt;/strong&gt; If the secret is rendered into a canvas or a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; frame, there is no DOM node and no text node. Regex detection sees nothing. Only a manual cover catches these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images of text.&lt;/strong&gt; A screenshot of a screenshot, a scanned invoice, a chart with labelled series. No OCR, no detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anything virtualised.&lt;/strong&gt; Table cells recycled by a virtual list can be measured before or after they exist in the DOM, depending on scroll timing. Off-by-one-row covers are a real bug class.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shadow DOM.&lt;/strong&gt; Closed shadow roots are invisible to the traversal; you get a bounding box with no children to inspect.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the honest product claim is "detection reduces misses; the manual cover is still the guarantee." Any tool claiming otherwise is describing a best case.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for the architecture
&lt;/h2&gt;

&lt;p&gt;Client-side redaction has one more consequence, and it's the reason I'd pick this shape again: &lt;strong&gt;there's nothing to operate.&lt;/strong&gt; No upload endpoint, no object storage, no retention policy to write, no "we delete after 24h" claim to make and be audited on. The security property is structural rather than promised.&lt;/p&gt;

&lt;p&gt;For a small tool that's the difference between a feature you can ship and a small compliance project. The trade is that you can't do the heavy stuff on a server — no models, no OCR on the secret-detection step, no history sync. In exchange, the failure mode of your infrastructure is "nothing happens," which is the failure mode I want.&lt;/p&gt;

&lt;p&gt;I built this into a small extension — &lt;a href="https://glenkit.com/veilshot?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=devto-5" rel="noopener noreferrer"&gt;Veilshot&lt;/a&gt;. Pricing, stated plainly: the free tier does the manual covers (no account, no network requests at all — that's checked in CI on every build, not just promised in a policy); Pro is a one-off $19 for pattern detection, keyword rules and per-site rules, no subscription. It's a paid product and I'm not going to pretend otherwise. But the pattern is the more useful takeaway, and it generalises well past screenshots: &lt;strong&gt;if the data is sensitive, transform it before it moves, not after.&lt;/strong&gt; Blur tools on the web got the order wrong, and that's the entire bug.&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Turning what you read into Anki cards without the typing</title>
      <dc:creator>glensum</dc:creator>
      <pubDate>Tue, 13 Oct 2026 04:12:21 +0000</pubDate>
      <link>https://dev.to/glenkit/turning-what-you-read-into-anki-cards-without-the-typing-4gbh</link>
      <guid>https://dev.to/glenkit/turning-what-you-read-into-anki-cards-without-the-typing-4gbh</guid>
      <description>&lt;h1&gt;
  
  
  Turning what you read into Anki cards without the typing
&lt;/h1&gt;

&lt;p&gt;Here's a workflow that fails quietly, every day, for a lot of people:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You read something good — a paper, a lecture PDF, a long article, a YouTube lecture.&lt;/li&gt;
&lt;li&gt;You think "this belongs in Anki."&lt;/li&gt;
&lt;li&gt;You open Anki, create a deck, and start typing cards by hand.&lt;/li&gt;
&lt;li&gt;You make six cards, it's been twenty minutes, and you close the window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The material never makes it in. Not because spaced repetition doesn't work — it obviously does — but because the &lt;em&gt;input&lt;/em&gt; step is manual while the &lt;em&gt;review&lt;/em&gt; step is automated. Anki's scheduler is a piece of software. Your card creation is you, typing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottleneck is card creation, not review
&lt;/h2&gt;

&lt;p&gt;Most "I fell off Anki" stories I've heard are really "I stopped making cards" stories. Reviewing thirty cards takes a few minutes and has an algorithm pushing you along. Writing thirty cards takes an hour of undifferentiated typing, and nothing pushes you along at all.&lt;/p&gt;

&lt;p&gt;That asymmetry is worth naming, because it tells you where a tool can actually help. Nobody needs help being nagged to review. What's missing is a low-friction path from "the thing I just read" to "a card in my deck."&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to close the gap — and what each one costs
&lt;/h2&gt;

&lt;p&gt;If you look at how people have tried to solve this, there are basically three shapes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CSV / export pipelines.&lt;/strong&gt; Generate cards somewhere, export to a file, import into Anki. This works, and it's the most common DIY approach. The costs show up later: field mapping breaks when your template changes, the import dialog steals focus, and there's &lt;em&gt;no deduplication&lt;/em&gt; — run it twice on the same article and you get two copies of every card. In a deck you'll keep for years, duplicate cards are worse than missing cards, because they double your review load silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anki add-ons.&lt;/strong&gt; Running inside Anki is convenient if that's where you already are. But by definition an add-on starts &lt;em&gt;after&lt;/em&gt; the text exists somewhere — it can't see the PDF you have open in a browser tab, and it can't see the article you're reading. It also only runs on desktop Anki, and it breaks on Anki version bumps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A browser extension.&lt;/strong&gt; This is the shape I ended up building. The argument for it: the content arrives in the browser first — pages, PDFs, YouTube transcripts. If you intercept it there, you're working at the actual point of friction, and you don't need the user to leave what they're doing to go make cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing through AnkiConnect instead of exporting files
&lt;/h2&gt;

&lt;p&gt;The technical decision that mattered most was to write cards into the collection through &lt;a href="https://foosoft.net/projects/anki-connect/" rel="noopener noreferrer"&gt;AnkiConnect&lt;/a&gt; rather than generating an import file.&lt;/p&gt;

&lt;p&gt;AnkiConnect is a local HTTP server exposed by an Anki add-on (code &lt;code&gt;2055492159&lt;/code&gt;, free). Because it can query the collection, you can do two things a file export fundamentally cannot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ask the collection what already exists before writing&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;findNotes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deck:"Reading" "{{text}}"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// then write only the notes that aren't there yet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addNotes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first call is the interesting one. A dedupe check against the live collection turns "generate 40 cards" into "generate 40 cards, 12 of which you already have" — and the second group never gets written. It also means cards land with the right note type and tags, in the deck you chose, with no import dialog and no field remapping.&lt;/p&gt;

&lt;p&gt;The honest cost of this approach: &lt;strong&gt;it requires desktop Anki running locally.&lt;/strong&gt; That's a real constraint — no AnkiWeb-only setup, no mobile. I took the trade because a card that lands in the wrong deck with the wrong template isn't really a card.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-device inference, or your own key
&lt;/h2&gt;

&lt;p&gt;The second decision was where inference runs. The default is Chrome's built-in on-device model, which means the selected text never leaves the machine — no upload, no account, no telemetry. If you want a stronger model, you paste your own API key (OpenAI / Anthropic / Google) and the request goes browser → provider directly.&lt;/p&gt;

&lt;p&gt;Two things fall out of that architecture, and I'd rather state them plainly than let them be discovered later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because inference runs on the user's machine or the user's key, there's no per-generation cost sitting on a server of mine. That's &lt;em&gt;why&lt;/em&gt; a one-time price is possible instead of a subscription — not a marketing stance.&lt;/li&gt;
&lt;li&gt;The only outbound request the extension makes is license activation. Nothing in the pipeline requires a server I operate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What generation gets wrong (and why "prune" is the real feature)
&lt;/h2&gt;

&lt;p&gt;LLMs will happily produce forty mediocre cards from a page of prose. Forty mediocre cards are worse than twelve good ones: they're more review load, lower retention per minute, and they train you to skim.&lt;/p&gt;

&lt;p&gt;So the workflow the tool is built around is &lt;strong&gt;generate → prune → write&lt;/strong&gt;, not generate-and-accept. Cards come back in a side panel where you edit or delete them before any of them touch the collection. The list-splitting template exists for the same reason: an enumeration in the source ("five causes of X") is a place where one card per item beats one card asking for all five.&lt;/p&gt;

&lt;p&gt;If there's one habit I'd recommend regardless of tooling: cap what you write per reading session. Twenty cards from an article you understood beats eighty from an article you skimmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full disclosure, and a question
&lt;/h2&gt;

&lt;p&gt;I'm the developer of &lt;a href="https://glenkit.com/ankimate?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=devto-4" rel="noopener noreferrer"&gt;AnkiMate&lt;/a&gt; — a Chrome/Edge extension that does the above. Pricing, stated plainly: free tier is 3 generations a day and writing cards into Anki is always free and unlimited; Pro is $19.9 one-time (Cloze, batch generation, future templates), no subscription. It's a paid product and I'm not going to pretend otherwise.&lt;/p&gt;

&lt;p&gt;I'm not claiming it's better than what you use — I don't have the user base to make that claim honestly. What I'd genuinely like to know is where your own pipeline breaks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the friction in capturing the source, in generating the cards, or in the pruning/editing step afterwards?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My assumption while building was that pruning is the part people underrate. If you're a heavy Cloze user and that assumption is wrong, I'd rather find out now.&lt;/p&gt;

</description>
      <category>anki</category>
      <category>productivity</category>
      <category>ai</category>
      <category>learning</category>
    </item>
    <item>
      <title>Why most cookie-banner blockers don't actually work</title>
      <dc:creator>glensum</dc:creator>
      <pubDate>Thu, 08 Oct 2026 14:58:51 +0000</pubDate>
      <link>https://dev.to/glenkit/why-most-cookie-banner-blockers-dont-actually-work-o41</link>
      <guid>https://dev.to/glenkit/why-most-cookie-banner-blockers-dont-actually-work-o41</guid>
      <description>&lt;h1&gt;
  
  
  Why most cookie-banner blockers don't actually work
&lt;/h1&gt;

&lt;p&gt;Install a typical cookie-banner blocker, then watch the network tab while you visit a site. Here is what actually happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The page loads. The consent management platform (CMP) script executes and builds the banner.&lt;/li&gt;
&lt;li&gt;The blocker's CSS sets &lt;code&gt;display: none&lt;/code&gt; on the banner element.&lt;/li&gt;
&lt;li&gt;The banner disappears from your screen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The CMP never receives a "reject" signal.&lt;/strong&gt; Non-essential scripts keep loading exactly as before.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That fourth step is the part almost nobody talks about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding is a cosmetic operation
&lt;/h2&gt;

&lt;p&gt;A cookie banner is not a decorative overlay you're allowed to delete. It's the UI of a consent platform — a script that is supposed to &lt;em&gt;record a decision&lt;/em&gt; and hand that decision to the tag manager, which then decides whether to fire analytics, ads, session replay, and so on.&lt;/p&gt;

&lt;p&gt;If you remove the banner with CSS, you have removed the UI and left the machinery untouched. The page has no idea what you wanted, so it defaults to its own default, which is usually "everything on until told otherwise."&lt;/p&gt;

&lt;p&gt;The visible result is identical. The legal and technical result is the opposite of what the user intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a real opt-out actually requires
&lt;/h2&gt;

&lt;p&gt;Consent platforms are designed to accept a decision from a few specific entry points. To genuinely reject, a tool has to use one of them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trigger the platform's own reject control&lt;/strong&gt; — the same element a human would click, selected per platform (&lt;code&gt;#onetrust-reject-all-handler&lt;/code&gt;, &lt;code&gt;[data-testid="reject-all"]&lt;/code&gt;, &lt;code&gt;button[aria-label*="reject"]&lt;/code&gt;, …). It has to be the &lt;em&gt;actual&lt;/em&gt; control, not a look-alike button the tool injects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call the platform's public API&lt;/strong&gt;, e.g. OneTrust's &lt;code&gt;OneTrust.RejectAll()&lt;/code&gt;, or Cookiebot's consent submit methods. These write the consent record that the tag manager reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post a proper TCF v2 string&lt;/strong&gt; for IAB-compliant platforms, so downstream vendors see a "no consent for purposes 1–10" state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a reason most blockers skip this: it's platform-specific, the DOM changes constantly, and each CMP has its own naming. A CSS rule works on every site forever. An API call breaks next Tuesday. So the ecosystem settled on the version that's easy to maintain instead of the one that works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The signal almost nobody sends: GPC
&lt;/h2&gt;

&lt;p&gt;Global Privacy Control is a legally recognised opt-out preference signal under the CCPA/CPRA and several US state privacy laws. It has two parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A request header, &lt;code&gt;Sec-GPC: 1&lt;/code&gt;&lt;/strong&gt; — trivial to add from an MV3 extension via a declarative rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"modifyHeaders"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requestHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"header"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sec-GPC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"urlFilter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"resourceTypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"main_frame"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sub_frame"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. The JS property &lt;code&gt;navigator.globalPrivacyControl&lt;/code&gt;&lt;/strong&gt;, which sites can read directly. Chrome doesn't ship it (yet), so an extension has to define it in a content script running before page scripts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globalPrivacyControl&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;configurable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* already defined */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because it's a &lt;em&gt;legal&lt;/em&gt; signal in some jurisdictions, a site that receives it is supposed to stop selling or sharing your data even without a banner interaction. Yet a quick audit of the popular CSS-hiding blockers shows most of them never send it. Hiding a banner is easy to demo; sending a header that no screenshot can show isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this category went stale
&lt;/h2&gt;

&lt;p&gt;Here's the part that made me actually build something.&lt;/p&gt;

&lt;p&gt;The extension with the largest install base in this category was acquired a few years ago and — as of this writing — has not shipped an update since mid-2024. The store reviews have shifted accordingly: a large share of the nearly 1,900 ratings are now one star, and the top-voted comments read like &lt;em&gt;"stopped working on many sites"&lt;/em&gt; and &lt;em&gt;"breaks scrolling"&lt;/em&gt;. Meanwhile a community fork that just keeps the rules updated has picked up a couple hundred thousand users.&lt;/p&gt;

&lt;p&gt;That tells you two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users in this category &lt;strong&gt;do&lt;/strong&gt; switch tools — the switching cost people assume exists isn't really there.&lt;/li&gt;
&lt;li&gt;The category's problem is &lt;strong&gt;maintenance&lt;/strong&gt;, not novelty. Consent platforms change their DOM; a blocker that isn't updated decays into "hides some banners, silently fails on others."&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The three constraints I built around
&lt;/h2&gt;

&lt;p&gt;Full disclosure: I'm the developer of a blocker in this space, &lt;a href="https://glenkit.com/cookieguard" rel="noopener noreferrer"&gt;CookieGuard&lt;/a&gt;. I'm not going to claim it's "better" — I have too few users to make that claim honestly. What I can describe is the shape of the problem I constrained myself to, because each constraint comes straight from what the reviews complain about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Press the real button.&lt;/strong&gt; Identifies the CMP by name (27 of them), triggers its genuine reject action or calls its own API; unknown banners fall back to a phrase library (224 don't-sell phrases across 35 languages); verifies the banner is actually gone rather than assuming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't break the page.&lt;/strong&gt; The rejection is scoped to the banner it dismissed. No page rewriting, and it never clicks "Accept" on your behalf — a consent tool that accepts on your behalf is worse than no tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nothing leaves the machine.&lt;/strong&gt; No account, no analytics, no telemetry. The optional per-site memory stores a hostname and the action you took, and clears in one click.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's a manifest V3 extension, rules ship as a versioned JSON file (because that's the part that has to stay current), and the only outbound requests it makes are an optional daily rules fetch and licence activation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I'd actually like answered
&lt;/h2&gt;

&lt;p&gt;Treat the tool as irrelevant to this post. The design question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Should a consent tool be judged by whether the opt-out legally registers, or is hiding the banner good enough?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the answer is "hiding is fine," then the entire GPC layer and the CMP-specific API calls are wasted complexity, and the whole category can stay six lines of CSS. If the answer is "it should actually register," then most of the popular tools are shipping the cosmetic version of a legal feature — and that's worth saying out loud.&lt;/p&gt;

&lt;p&gt;Either way: if you've hit banners that neither approach handles, the shape of the failure is more useful to me than a rating. Tell me what the site was and what happened.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>browsers</category>
    </item>
    <item>
      <title>From web page to Anki deck in one click: a flashcard workflow that stays in your browser</title>
      <dc:creator>glensum</dc:creator>
      <pubDate>Wed, 30 Sep 2026 02:09:21 +0000</pubDate>
      <link>https://dev.to/glenkit/from-web-page-to-anki-deck-in-one-click-a-flashcard-workflow-that-stays-in-your-browser-2dln</link>
      <guid>https://dev.to/glenkit/from-web-page-to-anki-deck-in-one-click-a-flashcard-workflow-that-stays-in-your-browser-2dln</guid>
      <description>&lt;p&gt;If you use &lt;a href="https://apps.ankiweb.net/" rel="noopener noreferrer"&gt;Anki&lt;/a&gt; seriously, you already know the workflow: read an article, spot a fact worth remembering, switch windows, paste it into a note template, reformat it, repeat. The reading and the card-making happen in two different worlds, and the friction quietly kills half the cards you intended to make.&lt;/p&gt;

&lt;p&gt;This post walks through a workflow that keeps everything in the browser tab: select text, generate flashcards, review them, and write them straight into your Anki decks. It uses &lt;a href="https://glenkit.com/ankimate?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;AnkiMate&lt;/a&gt;, a Chrome and Edge extension my team built — but the setup (AnkiConnect + a capture habit) works with other tools too, so most of this post applies even if you use something else.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 60-second setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install the &lt;a href="https://chromewebstore.google.com/detail/bloicodjmkmfdobmefmkijmlfkjpjjjf?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;AnkiMate extension&lt;/a&gt; (also &lt;a href="https://microsoftedge.microsoft.com/addons/detail/cbinjkcachcgacclkpemaogiefhjgjag?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;on Edge&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;In desktop Anki, install the AnkiConnect add-on (code &lt;code&gt;2055492159&lt;/code&gt;) and restart Anki.&lt;/li&gt;
&lt;li&gt;That's it. The extension talks to your local Anki over AnkiConnect — there is no account, no cloud sync step, no middleman.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The workflow: select → generate → review → write
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Select any text&lt;/strong&gt; on an article, lecture note, or documentation page (or use full page / PDF / YouTube transcript as input).&lt;/li&gt;
&lt;li&gt;Right-click → &lt;strong&gt;Generate flashcards&lt;/strong&gt;. A side panel opens with draft cards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit or delete&lt;/strong&gt; anything you don't like. Nothing is final here — every card is editable, removable, and draft-saved.&lt;/li&gt;
&lt;li&gt;Pick a deck and hit &lt;strong&gt;Write to Anki&lt;/strong&gt;. Cards land in your collection tagged and deduplicated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last step is the one that saves the most time. Cards are written directly into the deck you choose, deduplicated by content hash — and if the source page changed, the card gets updated instead of duplicated. No more discovering three near-identical cards about the same fact a month later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates tuned by subject, not one generic prompt
&lt;/h2&gt;

&lt;p&gt;A generic "make flashcards from this text" prompt produces generic cards. AnkiMate ships nine templates, each tuned for a subject:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Medicine (USMLE)&lt;/strong&gt; — expects clinical detail, not trivia&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GRE vocabulary&lt;/strong&gt; — word, definition, usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Language learning&lt;/strong&gt; — front/back with context, plus a reversed-vocabulary variant&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Law&lt;/strong&gt; — element-based structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Programming&lt;/strong&gt; — concept and code-focused cards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List breakdown&lt;/strong&gt; — turns enumerations into atomic cards (the classic "one fact per card" rule, enforced for you)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;General&lt;/strong&gt; — everything else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eight templates are free. Pro adds &lt;strong&gt;Cloze deletions&lt;/strong&gt; (fill-in-the-blank), which is the right format for definitions, dates, and anatomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy: on-device AI by default, BYOK if you want more
&lt;/h2&gt;

&lt;p&gt;This matters to us, so it's the design center: &lt;strong&gt;on-device AI handles generation by default, and your selected text never leaves the browser&lt;/strong&gt;. If you want a stronger cloud model, you plug in &lt;strong&gt;your own API key&lt;/strong&gt; — and then your text goes directly from the extension to that provider, not through our servers. We never see your selections, your cards, or your key. (We wrote up &lt;a href="https://glenkit.com/blog/summarize-articles-byok-privacy?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;why this matters&lt;/a&gt; in more detail — most "AI readers" proxy every page you visit through their own backend.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing, honestly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free, every day, no card required&lt;/strong&gt;: 3 generations per day (roughly 45–90 cards), all 8 non-Pro templates, all input types, unlimited writes to Anki. For most students this is genuinely enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro · Lifetime $19.9&lt;/strong&gt; (launch price, $29.9 after): unlimited generations, batch generation of 20+ cards from long pages, and the Cloze template. One payment, no subscription — the price of two coffees for the tool you'll use every study session.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Desktop Anki (with AnkiConnect) needs to be running when you write cards — AnkiMobile/AnkiDroid alone won't receive them.&lt;/li&gt;
&lt;li&gt;Generation quality tracks source quality: a well-structured page produces great cards; a rambling forum thread needs more editing.&lt;/li&gt;
&lt;li&gt;It makes cards, not understanding. Spaced repetition still requires you to actually do the reviews.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Install from the &lt;a href="https://chromewebstore.google.com/detail/bloicodjmkmfdobmefmkijmlfkjpjjjf?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt; or &lt;a href="https://microsoftedge.microsoft.com/addons/detail/cbinjkcachcgacclkpemaogiefhjgjag?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;Edge Add-ons&lt;/a&gt;, or read more at &lt;a href="https://glenkit.com/ankimate?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ankimate" rel="noopener noreferrer"&gt;glenkit.com/ankimate&lt;/a&gt;. If you have questions about the AnkiConnect setup or template design, leave a comment — I reply to all of them.&lt;/p&gt;

</description>
      <category>anki</category>
      <category>productivity</category>
      <category>learning</category>
      <category>browsers</category>
    </item>
    <item>
      <title>How to summarize 2-hour videos and 50-page articles without leaving the browser tab</title>
      <dc:creator>glensum</dc:creator>
      <pubDate>Tue, 29 Sep 2026 07:39:22 +0000</pubDate>
      <link>https://dev.to/glenkit/how-to-summarize-2-hour-videos-and-50-page-articles-without-leaving-the-browser-tab-ak9</link>
      <guid>https://dev.to/glenkit/how-to-summarize-2-hour-videos-and-50-page-articles-without-leaving-the-browser-tab-ak9</guid>
      <description>&lt;p&gt;A 40-minute conference talk. A 90-minute podcast. A 12-minute tutorial that could have been a blog post. Most of what's valuable in a video is its &lt;em&gt;content&lt;/em&gt; — and content can be read.&lt;/p&gt;

&lt;p&gt;I've been building &lt;a href="https://glenkit.com/glensum.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=summarize-long-content" rel="noopener noreferrer"&gt;GlenSum&lt;/a&gt;, a browser extension that summarizes long videos and articles directly in the tab. This post covers the three working approaches to media summarization in a browser, the privacy trade-off most people don't notice they're making, and what "long content" actually means for an LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways to summarize a YouTube video (slowest to fastest)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. The transcript, by hand
&lt;/h3&gt;

&lt;p&gt;Every video with captions has a transcript: expand the description → &lt;em&gt;Show transcript&lt;/em&gt; → select all → paste into any AI chat.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time:&lt;/strong&gt; 3–5 minutes of fiddly copying, and raw transcripts are messy — no punctuation, no speaker labels, timestamps everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; one-off use when you can't install anything&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Paste the URL into an AI chat
&lt;/h3&gt;

&lt;p&gt;Some assistants accept a YouTube URL directly and fetch the transcript themselves. Paste the link, ask for key points.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost:&lt;/strong&gt; often limited by your plan's usage quota&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caveat:&lt;/strong&gt; availability varies by model and region — and now the video passes through yet another service&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best for:&lt;/strong&gt; occasional videos when you already pay for an assistant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. A browser extension, one click
&lt;/h3&gt;

&lt;p&gt;Extensions built for this read the transcript from the page you already have open and generate a structured summary in a side panel. The workflow becomes: open the video → click the extension → read the summary. No copying, no tab-switching.&lt;/p&gt;

&lt;p&gt;The extras that matter: adjustable summary length, follow-up questions about the content ("what did they say about X?"), bilingual output when the talk isn't in your first language, and Markdown/PDF export so the summary lands in your notes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The privacy trade-off most people miss
&lt;/h2&gt;

&lt;p&gt;Here's the part that motivated building this in the first place. When you paste a URL or transcript into a hosted summarizer, your &lt;strong&gt;reading and watching history becomes someone's dataset&lt;/strong&gt;. Article by article, video by video, a profile of what you consume accumulates on a server you don't control. That's a real cost that never shows up on the pricing page.&lt;/p&gt;

&lt;p&gt;The alternative is BYOK — bring your own key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You paste an API key from your own AI provider (OpenAI, Google, a local gateway, anything OpenAI-compatible) into the extension once.&lt;/li&gt;
&lt;li&gt;The extension reads the page content in your browser and sends it &lt;strong&gt;directly to the provider you chose&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;There is no middleman server. No account. No logs on my side, because there is no "my side" — the extension ships as static code, and the network calls go from your browser to your provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The trade-off is honest: you manage your own key, and free-tier users can still use built-in free models without one. But nobody in the middle ever sees what you read.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "long" actually means for an LLM
&lt;/h2&gt;

&lt;p&gt;Summarizing a 2-hour talk isn't just "paste transcript into prompt". A two-hour transcript is roughly 25,000–40,000 words — technically it may fit a modern context window, but quality degrades: the model skims, drops the middle, and blends sections together.&lt;/p&gt;

&lt;p&gt;The approach that works is &lt;strong&gt;map-reduce over chunks&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map:&lt;/strong&gt; split the transcript (or article) into overlapping chunks at natural boundaries — chapters, speaker turns, section headings — and summarize each chunk separately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce:&lt;/strong&gt; merge the chunk summaries into a final structured summary, keeping section-level detail instead of a mushy average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also why page-native tools beat copy-paste: the extension already knows the page structure, so it can split at real boundaries instead of arbitrary token counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a summary is the wrong tool
&lt;/h2&gt;

&lt;p&gt;Summaries are ideal for &lt;strong&gt;inverted-pyramid content&lt;/strong&gt;: talks, podcasts, news analysis, tutorials — where information density is low and predictable. They're poor substitutes for &lt;strong&gt;demonstration content&lt;/strong&gt;: if someone is showing you how to do a three-point turn, no summary captures the steering.&lt;/p&gt;

&lt;p&gt;A good rule: summarize first to decide &lt;em&gt;whether&lt;/em&gt; to watch — not to avoid watching things worth watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;GlenSum is free to install and works out of the box (built-in free models, or your own key for unlimited use):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Site &amp;amp; docs: &lt;a href="https://glenkit.com/glensum.html?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=summarize-long-content" rel="noopener noreferrer"&gt;glenkit.com/glensum&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🧩 &lt;a href="https://microsoftedge.microsoft.com/addons/detail/glensum-web-summary-ass/bpiieipdednngkkgdhmgmfbbbncepdpd" rel="noopener noreferrer"&gt;Edge Add-ons&lt;/a&gt; · &lt;a href="https://chromewebstore.google.com/detail/glensum-web-summary-assis/ppljcihchddoaoebcgkjobinhhcloaek" rel="noopener noreferrer"&gt;Chrome Web Store&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions about the architecture — especially around BYOK flows or chunking strategies for long transcripts — happy to answer in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>productivity</category>
      <category>chrome</category>
    </item>
  </channel>
</rss>
