If you spend time on LinkedIn or X, you already know the feeling. You open the app to catch up with people you actually follow, and instead you scroll past promoted posts, "people you may know" suggestions, and so much AI-generated content that the real posts get buried underneath all of it. I hit that wall often enough that I decided to fix it for myself. I also went back later and wrote the whole pile down — what actually clutters a LinkedIn feed in 2026 is that list, and it turned out to be a lot more than ads.
I'm not primarily a Chrome extension developer. What I had was a clear, specific problem and Claude Code sitting in my terminal. So instead of spending a few weekends learning the Manifest V3 APIs from scratch, I described what I wanted, section by section, and let it write the code while I made the decisions — what to hide, how to label it, when to trust it.
The first real prompt
I didn't write a novel. The first ask was close to this:
Build a Chrome Manifest V3 extension that runs on LinkedIn and X. Scan the feed, detect promoted/sponsored posts and "people you may know" suggestions, and collapse each one into a small placeholder bar the user can click to restore. No backend, no build step — plain JS files Chrome loads directly. Store settings in chrome.storage.local. Add a popup with on/off toggles per category.
What came back was a real, working skeleton in that first sitting: a content script that scanned the feed, a classifier, a collapse/restore mechanic, and a popup wired to storage. I loaded it unpacked, opened LinkedIn, and it worked well enough to keep going.
That's the part that looks like magic. It isn't the whole story.
What happened when LinkedIn changed how it builds the feed?
Every selector the extension relied on stopped matching overnight. A few weeks in, LinkedIn quietly rewrote how it renders its feed, moving to a server-driven UI where the page is assembled from a data schema instead of stable, readable class names. The hooks I had been detecting posts with were simply gone.
Posts I used to catch with one line of CSS were now nested inside generic, auto-generated wrapper elements with nothing useful to grab onto. That's not a bug you patch with a deprecation fix. The floor had moved, and the entire LinkedIn detection layer had to be rebuilt — new selectors, a text-walking fallback for when selectors fail, and a button-based ancestor search as a second line of defense, so the extension wouldn't go blind the next time LinkedIn shipped a redesign.
The honest version of "AI wrote my app" is that AI wrote a strong first draft. LinkedIn breaking it a few weeks later is the part a one-prompt story usually leaves out.
I found more of these along the way, all small, all real: a circuit breaker meant to detect a dead feed was latching permanently instead of resetting per page load. Custom keyword matching failed silently against curly apostrophes, so "don't" in a user's blocklist wouldn't match "don't" typed with a smart quote. Each one got a fix and a regression test, not a rewrite.
What does Zenfeed actually hide on LinkedIn, X, and YouTube?
It hides promoted and sponsored posts, suggested-content modules like "People you may know," and anything matching your own keyword list, plus a Rest from AI timer that puts AI posts away for a set number of minutes. That's the full set on LinkedIn and X. On YouTube it does less on purpose: Rest from AI, keywords, and an opt-in Shorts toggle.
Underneath, Zenfeed runs the same shared machinery — scan, classify, collapse, restore — across all three sites, but each site gets its own adapter, because LinkedIn, X, and YouTube don't expose their feeds the same way. X has stable data-testid hooks, so it needs less guesswork. YouTube uses a different collapse style entirely — blurred in place instead of folded into a bar, so the grid keeps its shape. Here's what's actually behind the toggles:
Collapses sponsored posts on LinkedIn and "Ad" / "Promoted" tweets on X.
"People you may know," "Who to follow," "Discover more" — off by default, since it's a looser match.
A timer that hides AI-flagged posts for a set number of minutes, with a live countdown badge on the toolbar icon.
Block posts by your own word list, matched case-insensitively, across LinkedIn and X right away, and on YouTube once you opt in.
Collapses the entire YouTube Shorts shelf as one unit, not tile by tile. Off by default.
Filters LinkedIn Company and Showcase Page posts by checking the byline link, not the post text. Off by default.
Why "off by default" shows up twice
Suggested-content and Shorts detection are content-type filters, not unambiguous ad labels. A false positive on day one costs more trust than a miss does, so both ship opt-in. That's a product decision, not a technical limitation — and it's the kind of call that "one prompt" can't make for you.
How do you know Zenfeed isn't hiding something you wanted to see?
The honest answer is testing, and a lot more of it than the features themselves needed. A feed extension that's wrong even occasionally is worse than useless, because the mistake is invisible — you never find out about the post it hid when it shouldn't have. So most of the later work wasn't new features, it was tests.
A Playwright suite drives real page structure per site, and jsdom unit tests cover the parts that don't need a browser. There's a dedicated test for the LinkedIn sidebar ad sweep, one for company-page detection, one for the YouTube Shorts shelf, and one just for Rest from AI's countdown logic — that one matters because Rest from AI is a timer, not a permanent block, and a timer that doesn't hand your feed back on schedule is a broken promise. Nine separate npm run test:* commands, because "it looks like it works" and "it's still right after LinkedIn's next redesign" are different bars.
Where it landed
Zenfeed is live on the Chrome Web Store, on version 1.1.4 as of this post. It's free, it's built by one person, and it started as a tool to fix my own feed before it was anything else. It only works on Chrome right now — Firefox handles this kind of extension differently, so that's a later project, not a promise.