How we reviewed this: we built it in Docker with Bun, ran its tests offline, and executed its shipped page script and content script against stand-in DOM and APIs to see what they do. We made no Jev calls.
What it does
sharp, by tshmieldev, filters your X timeline against an instruction you write:
A reader filters their X timeline with this instruction, in their own words: “…”. Follow it literally: where it names what to hide, hide those posts; where it names what to keep, hide every post it does not cover.
The cheap work happens locally first — author allow and block lists, muted words, skipped surfaces, and a viewport gate so posts two screens away are never sent. Only survivors are judged, one request per post, deliberately never batched so one post’s context can’t leak into another’s.
The nicest design choice: the probability is cached and the threshold is applied at display time, so moving the slider re-filters your whole timeline for free.
118 tests pass offline, the build produces both Chrome and Firefox targets, and there is a test asserting the content script cannot obtain the API key.
The part that isn’t in the README
The extension injects a script into x.com’s own page context that wraps window.fetch and broadcasts X’s request headers — including the CSRF token and the client transaction id — via postMessage. We ran the shipped bundle against a stub and captured them.
Those values are reachable by same-origin page scripts anyway, so this isn’t a credential leak to a third party; the extension uses them to act on your behalf (its “not interested” feature). But neither the README nor the privacy policy mentions that the extension wraps X’s networking, reads timeline JSON, or reuses your session headers — and the broadcast happens even with that feature switched off, since the script doesn’t consult settings.
Separately, the README says nothing is shown before it’s judged. In practice, a post that fails judgment three times is shown unjudged: the shield is only applied while a verdict is queued or running. We reproduced that with a provider that throws.
What it sends
Per post: the author handle, the post text (up to 30,000 characters), the post it replies to (truncated), and optional image descriptions — the URLs go to a vision model, not into the text request. There’s no redaction; the privacy policy says so. Verdicts are cached under a hash rather than the text.
The key is stored in extension-local storage, stripped from anything the page can reach, and the code deliberately doesn’t echo provider error bodies because they can contain credentials or post text. There’s even a migration that deletes keys left behind in synced storage. That care makes the undocumented header broadcast stand out more, not less.
Update, 20 September: fixed and documented
Both findings landed in #7 the same day, and we checked the code rather than the issue.
The page script is now gated. src/x/wire.ts opens by saying so:
// "Teach X too" uses either, so nothing is read or passed on unless the content
// script says that setting is on.
The content script tells it whether the setting is on; with it off the script reads nothing and forwards nothing. It is still injected, because a manifest content script cannot be loaded conditionally, and the author handled the ordering problem honestly: X’s first timeline response arrives before settings can be read, so a few messages are held and then released or dropped, with ten seconds of silence counted as off.
The privacy policy now has a section called “What Sharp does on X as you” that names what we found, in plain terms — that it wraps the page’s fetch and reads “the headers X signs its own API requests with, which include your X session credentials (authorization, x-csrf-token and related headers)” — and states that they never leave the x.com page.
The README no longer overclaims. “Nothing shown before it’s judged” now continues: “If your provider keeps failing, a post is shown after three tries rather than held back forever.” Failing open is deliberate, the reasoning is sound — a broken provider should not blank your timeline — and now it is written down.
The author declined one of our three points, and we think correctly: Chrome returns tab.url without the tabs permission for hosts the extension already has access to, which are exactly the sites it filters, so requesting tabs would add a permission warning for nothing.
Verdict
The most polished consumer product in this round: shipped in two stores, real tests, sensible local pre-filtering, and a threshold model that respects what a calibrated probability is for.
Install it knowing that your timeline’s text goes to a third party post by post, and that it hooks X’s networking in a way the documentation doesn’t tell you about. Both are defensible; neither should be a surprise.
For hiding clutter rather than posts, see Unclutter.
See how it compares with other tools in Best Jev tools, tested hands-on.
Review updated Sep 20, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.