# sux A firefox plugin to filter shit out of your bsky feed Mostly, it was just an excuse to fuck with jev. What follows is pure Kimi-lish, because I don't feel like doing better at the moment. It'll be rewritten at some point in the next week or two, probably after the userscript-based rewrite. The neuralese is pretty complete tho. A feed filter for [bsky.app](https://bsky.app) (Firefox, Zen Twilight, and other Firefox-based browsers). Every post Bluesky's web app retrieves is judged by [Jev](https://docs.typesafe.ai/) (TypeSafe's System One model); posts that don't pass your rules are hidden. ## Rules (set in the sidebar) - **Two words** — Jev decides whether a post is more word 1 or word 2. A post is shown when Jev puts at least 50% probability on word 1. - **One word + direction** — Jev answers "is this post `${word}`?" with a probability. Choose *are mostly* (shown when > 50%) or *are NOT* (shown when < 50%). Exactly 50% is hidden either way. - **Both rules on** — by default a post must pass *all* enabled rules (AND); switch to *any* (OR) in the sidebar. - The **Filtering on** checkbox fully disables the extension with one press. When off, the page is left completely untouched. Posts stay **hidden until judged** (usually well under a second, slower under rate limits). Verdicts are cached by rule set + post text, so reposts and re-scrolls cost nothing. Image-only posts with no alt text are shown — there is nothing for Jev to judge. ## Setup 1. Get a TypeSafe API key (https://typesafe.ai). 2. Build: `npx web-ext build` → `web-ext-artifacts/sux_jev_feed_filter-0.1.0.zip`. 3. Install (persists across restarts): quit the browser, copy the zip to `/extensions/@sux.xpi`, and put `user_pref("xpinstall.signatures.required", false);` in a `user.js` file in the profile dir (unsigned sideloads need it). Restart — Firefox registers the drop-in on startup. 4. Open bsky.app, then the *sux* sidebar. If Firefox shows the **Grant site access** banner, click it (Firefox treats host permissions as opt-in) and reload bsky.app tabs. 5. Paste your API key, press **Test**, enable a rule, flip **Filtering on**. To update after code changes: rebuild the zip, copy it over `@sux.xpi`, restart. Posts are read from the rendered feed DOM (permalink anchors + item text), not from network interception — bsky.app's API client uses a captured `fetch` reference that page-world wrappers cannot see, so DOM extraction is the reliable channel. A page-world interceptor still runs as a supplement. ## Costs One Jev API call per unique post text per rule configuration. The sidebar shows judged / hidden / waiting counts and the last API error. ## Notes & limitations - The sidebar is styled with the Everforest dark palette by default and adopts your Firefox/Zen browser-theme colors automatically when the theme exposes them via the theme API (`theme.js`); it re-applies live when the theme changes. - The mapping from rendered feed items to posts uses Bluesky's permalink anchors (`/profile//post/`), not CSS class names, but markup changes on bsky.app can still break it — if the feed stops filtering after an app update, this is the first place to look (`content.js`). - Quote-posts and reply-parents inside an item are not judged separately; the main post's verdict governs the whole item. - Only feed surfaces are intercepted: timeline, author feeds, custom feeds, search, and threads — not notifications. ## Development ``` node --test test/ # unit tests (no key needed) source typesafe_api_key.secret.sh && node scripts/live-check.mjs # live API check npx web-ext lint # manifest/API lint ``` Layout: `shared.js` (pure decision logic, unit-tested), `interceptor.js` (page-world fetch hook), `background.js` (queue + Jev client + cache), `content.js` (DOM), `sidebar.*` (settings UI).