the basic idea
Dull is an iOS app that wraps the mobile web versions of social platforms inside a WKWebView, then injects CSS and JavaScript to hide elements like Reels, Shorts, and Explore from the rendered page. No traffic interception, no man-in-the-middle, no system-wide changes. As far as Instagram's servers can tell, you're just on mobile Safari.
what's actually doing the filtering
- 1
WKContentRuleList
A native iOS API that lets us block requests and hide DOM elements declaratively. Compiled to a JSON ruleset at app launch. Fastest layer, runs in WebKit's native code, not JavaScript.
- 2
CSS injection at documentStart
Selectors that hide elements before paint, so you never see them flash on screen. One stylesheet per platform, loaded before the page begins rendering.
- 3
JS injection at documentEnd
A MutationObserver that catches dynamically-rendered elements. Instagram and YouTube hydrate progressively, so static CSS isn't enough. We need to catch the elements that show up after first paint.
what data leaves the device
nothing.
We don't have a server for the filters. We don't have a server at all, beyond App Store metadata. Your sessions live in per-platform WKWebsiteDataStores, isolated per platform, on your device. No analytics on what you browse. The only analytics we collect is funnel-level (Mixpanel, EU endpoint, opt-out in settings): onboarding events, paywall views. Nothing about what you scroll, what you tap, what you read.
why we don't use the iOS Screen Time API
Apple's Screen Time API (DeviceActivity / FamilyControls) is designed for parental controls. It requires the user to be in a Family Sharing group with a parent approver, or the developer needs a Family Controls entitlement Apple approves app-by-app.
Dull's audience is self-directing teens and adults. Making them set up Family Sharing to filter their own phone is a non-starter. The WKWebView approach side-steps that entirely. No special entitlements, no parental-control framing, no Apple gatekeeping per-install.
what happens when Instagram redesigns
Filter selectors are tied to DOM structure. When a platform changes their HTML, some filters can break. Dull's filter system flags broken filters at app launch via health checks. If a selector matches zero elements when it used to match many, we know.
When that happens, we ship a filter update. The next app update has the new selectors. Average lag from a platform redesign to a Dull update: about 5 to 10 days, based on history.
the bypass button
There's a "···" button inside the browser that disables filters for the current session only. It exists because forcing a fix through willpower is a worse design than admitting some days you'll bypass. Each bypass is counted and visible in stats. It's not punishment, it's transparency.
open questions and known limits
- Mobile-web Instagram doesn't have full feature parity with the native app (no Notes, more limited Stories interactions).
- Some flows (DM voice notes, certain Stories stickers) work in the native app but not on mobile web.
- YouTube long-form playback is fine; livestreams occasionally buffer on the mobile web client.
- X requires login per session for some accounts; its cookie behavior is hostile to embedded webviews.
source code
Dull is a small bootstrapped app; the source isn't public. If you want a security audit, email [email protected] and we'll work something out.
questions
is this a VPN?
No.
does this require jailbreak?
No.
does this work on Android?
Not yet. Waitlist at /android.
how is this different from Safari Content Blockers?
Safari Content Blockers only work inside Safari, so they don't fix Instagram inside the Instagram app. And most people don't browse Instagram from Safari anyway. Dull's whole point is to skip the native apps and do the filtering inside its own browser.