Chip makers race to re-tool fabs before the subsidy window closes
Three of the largest foundries have moved capital spending forward by a quarter, betting that the credit will not be renewed.
A filter list blocks what someone has already seen and written a rule for. This one reads the page. Watch Jev walk every element of a news site, score how ad-like each one is, and hand your code a number it can threshold — then run the same page through a filter list and through an LLM and see what each one costs you.
Note on the animation: Jev’s answers are revealed one element at a time so you can read them. In a real integration all thirty questions come back in the same round trip — the walk you see is your own loop applying the answers, not the model thinking.
These are the elements that decide whether a blocker is any good. The easy ones — a masthead, three news stories, a footer — every engine gets right. Columns fill in as you run each scan above.
✓ did what the policy asked · ~ defensible but not what the policy asked · ✗ wrong
Your code walks the DOM and builds one small state object per candidate element: what it says, what it links to, how big it is, where it sits. No pixels — Jev reads text and structured data, and the browser already has both.
Then two questions about each element. Not “what should I do with this?” — that is your decision, and it depends on settings Jev knows nothing about. Just how ad-like is it, and what kind of thing is it.
// one element of fifteen
state = {
url: "dailyledger.example/technology",
element: {
tag: "article", classes: "story",
text: "Paid partnership · Nordvault — Seven signs your
home network is already exposed — Most routers ship
with defaults that anyone can look up…",
links: ["nordvault.example/?utm_source=ledger&utm_campaign=q3"],
size: "412x132", slot: "3rd item in the main story river"
},
page: "technology news index, 6 stories, 1 sidebar"
}
questions = {
isAd: noul("This element is a paid placement whose purpose is to
promote a product or service, rather than editorial
content the reader came for."),
kind: choice("What kind of element is this?", {
display_ad: "A bought banner or unit from an ad network.",
sponsored_content: "Editorial-looking content paid for by a brand.",
affiliate_block: "Editorial picks that earn a commission on clicks.",
newsletter_modal: "The site asking for an email address.",
cookie_banner: "A consent or privacy prompt.",
house_promo: "The site promoting its own subscription.",
content: "What the reader came for, or site furniture."
})
}
// 96 ms later, for all fifteen elements at once
answers["07"] = { isAd: { noul: 0.93 },
kind: { choice: "sponsored_content", confidence: 0.88 } }
Nothing in that answer decides anything. The deciding is eleven lines of your own code, and it is the part your users actually argue about — so it belongs where you can change it without touching a model:
function policy(a, settings) {
// interruptions go, whatever the ad score says
if (a.kind.choice === "cookie_banner") return "hide";
if (a.kind.choice === "newsletter_modal") return "hide";
// confident ad: hide it, unless the reader asked to see
// sponsored posts marked rather than removed
if (a.isAd.noul >= 0.85)
return (a.kind.choice === "sponsored_content" && settings.labelSponsored)
? "label" : "hide";
// genuinely in between: dim and label, never disappear
if (a.isAd.noul >= 0.45) return "dim";
return "keep";
}
A filter list is a list of things someone has already seen. An LLM understands the page but takes seconds and costs cents. Jev is the narrow middle: it only answers the question you defined, and it answers it fast enough to run on every element of every page.
| Filter list | LLM | Jev | |
|---|---|---|---|
| Matches on | Selectors and URLs a volunteer wrote down after seeing the ad. | Meaning, read from the page text. | Meaning, read from the page text. |
| Time per page | Under a millisecond. | 2–5 seconds, after the page has already rendered. | About 96 ms for the whole batch. |
| Cost per page | Free. | ~$0.012 at frontier prices. A thousand pages a day is $12. | ~$0.0001. A thousand pages a day is a cent. |
| An ad nobody has seen before | Invisible until someone files a rule. | Caught. | Caught. |
| Renamed or rotating classes | Defeated by a build step. | Irrelevant — it reads the words. | Irrelevant — it reads the words. |
| Native “paid partnership” posts | Same markup as real articles. No rule can separate them. | Caught. | Caught, and labelled as such. |
| Borderline things | No concept of borderline. | Gives you its opinion, not a dial. | Gives you a calibrated number to threshold. |
| Output your code gets | A boolean per rule. | Free text or JSON you parse, often CSS selectors that break. | A probability and a category per element. |
| Same page twice | Identical. | May differ on the awkward ones. | Same distribution. |
| Runs on device | Yes. | No — the page goes to a server. | No — the page goes to a server. |
| Good at | Blocking known ad servers at the network layer, for free, before a byte is fetched. | The odd hard page, offline analysis, writing the rules and thresholds in the first place. | Judging every element of every page, continuously, at a price that lets you. |
A filter list knows what an ad looked like yesterday. A judgment model can tell you what this element is doing on the page today — and hand your code a number instead of an opinion.
This is not a replacement. Rules block known ad servers at the network layer, for free, before a single byte is fetched. Classifying an element happens after the ad has already loaded — you saved the reader’s attention, not their bandwidth or their beacon. The sensible design is rules first, judgment for everything that has no rule yet.
Any model-based blocker sends page text to a server, which is an uncomfortable trade in exactly this product. On-device or self-hosted is the honest answer, and it changes the latency and cost numbers above. Worth saying out loud rather than burying.
0.98 is not proof. Jev cannot return an option you did not define, so your parser never breaks — but it can still be wrong about an element, and over-blocking has a real cost. The filter list in this demo deleted a breaking news banner because of one bad rule; a bad threshold does the same thing at scale.
Every probability, latency and price in the panels is invented for the demo. The figures behind them are TypeSafe’s published launch numbers — 70–500 ms per request, $42 per billion input tokens, output free, calibrated confidence on every answer. Jev has been in early access since 15 September 2026.
No mainstream blocker classifies elements with a hosted model today, and this page is a teaching demo rather than a proposal. What is real is the shape of the decision: a loop over elements, one narrow question each, a number back, and a policy in code.