Unofficial Capacitor-based iOS wrapper for X/Twitter with optional self-hosted push notifications.
This started as a personal iPhone wrapper for x.com/home, then grew into a
more capable experimental client shell with native notifications, a large tweak
surface, and an optional self-hosted watcher backend.
It is not affiliated with, endorsed by, or maintained by X Corp. Treat it as an open-source experiment for sideloading and personal builds, not as an official or App Store-ready client.
- Wraps a remote X/Twitter URL in a native iPhone
WKWebView - Adds a large tweak surface through the injected control panel
- Persists app-owned settings on-device
- Can register for APNs and open X links from native notifications
- Includes an optional self-hosted watcher server for account notifications
- Can watch public profiles without the official X API
- Best suited for sideloading, local builds, and experimentation
- Not an App Store-ready client in its current form
- Native notifications require your own APNs setup and push server
- The optional watcher can work without the official X API, but that approach is inherently brittle because it depends on X's current frontend behavior
- If you fork it, use your own bundle ID, APNs credentials, tokens, and server configuration
The in-app Tweaks panel is one of the main reasons this project exists. It is
not just a thin web wrapper.
Examples of what can be changed:
- Home timeline behavior
- default to
Following - hide
For you - sort the
Followingfeed - add topic filters above
For you - hide countries or regions from
For you
- default to
- Appearance
- choose the dark mode theme, including
DimandLights Out - replace X branding with Twitter-style branding
- adjust layout density and full-width behavior
- tweak follow/following button styling
- choose the dark mode theme, including
- Feed cleanup
- hide suggested content, inline prompts, Grok, Jobs, Subscriptions, and upsells
- hide blue-check reply noise and premium-heavy surfaces
- hide view counts and other metrics
- hide compose UI or disable the home timeline entirely
- Notifications
- connect the app to a self-hosted watcher backend
- add account watches for
Posts onlyorPosts + replies - receive native APNs that open the target X link in the app
- Profile and conversation cleanup
- hide retweets on profiles
- hide likes/retweets in Notifications
- show country flags and profile location cues
- reduce clutter in quote-tweet and reply flows
Some tweaks depend on X's current markup and behavior. When X changes its frontend, parts of the tweak layer may need maintenance.
capacitor.config.ts: Capacitor app config and remote URL handlingios/App/App: native iOS shell, push handling, and injected tweaks bridgeserver: optional notification backend and X account watcherwww: local fallback/offline pages
- macOS with Xcode
- Node.js 20+
- An Apple Developer account if you want real device installs and APNs
- An always-on machine if you want self-hosted notifications
- VPS, Mac mini, Raspberry Pi, or similar
This repo is set up so secrets and signing stay out of git.
- App runtime secrets live in
.env - Notification server secrets live in
server/.env - Local iOS signing values live in
ios/signing.local.xcconfig
None of those files should be committed.
- Copy
.env.exampleto.env - Copy
ios/signing.local.example.xcconfigtoios/signing.local.xcconfig - Set your local app values:
CAP_REMOTE_URL=https://x.com/home
CAP_APP_ID=com.example.decenttwitter
CAP_APP_NAME=Decent Twitter- Set your local signing values:
APP_DISPLAY_NAME = Decent Twitter
APP_BUNDLE_ID = com.example.decenttwitter
APP_DEVELOPMENT_TEAM = YOUR_TEAM_ID
- Install dependencies and sync iOS:
npm install
npm run ios:sync
npm run ios:open- In Xcode, select your Apple Development team if needed and run the app on a device or simulator.
After the one-time Xcode signing setup is done, you can use the helper script:
npm run ios:devices
npm run ios:device -- YOUR_DEVICE_IDIf you prefer raw xcodebuild + devicectl, the helper script in
scripts/run-ios-device.sh shows the exact flow.
This app can receive native APNs notifications, but it does not inherit X's own web notifications automatically.
There are two layers:
- Native iOS notification plumbing in the app
- An optional self-hosted watcher that decides when to send pushes
- Native test pushes
- Per-account watches
Posts onlyorPosts + replies- Self-hosted profile polling without the official X API
- Official X push delivery inside
WKWebView - DMs
- Full home-timeline notifications
- Reliable background watching without a backend
- The app asks for notification permission on launch.
- The app registers an APNs device token.
- The app registers that installation with your push server.
- The push server watches selected X accounts.
- When a matching post is detected, the server sends APNs.
- Tapping the push opens the target X URL in the wrapper.
- Copy
server/.env.exampletoserver/.env - Fill these required values:
PUSH_ADMIN_TOKEN=replace-with-a-long-random-admin-token
PUSH_DEVICE_REGISTRATION_TOKEN=replace-with-a-long-random-device-token
APPLE_TEAM_ID=YOUR_TEAM_ID
APPLE_KEY_ID=YOUR_KEY_ID
APPLE_BUNDLE_ID=com.example.decenttwitter
APPLE_PRIVATE_KEY_PATH=/absolute/path/to/AuthKey_ABC123XYZ.p8
APNS_DRY_RUN=false- Install and start the server:
cd server
npm install
npm run install:browser
npm run dev- Point the app at the server in your root
.env:
CAP_PUSH_SERVER_URL=https://your-server.example.com/
CAP_PUSH_SERVER_REGISTRATION_TOKEN=the-same-device-registration-token-from-server-env
CAP_PUSH_DEVICE_NAME=My iPhone- Sync and rebuild the iOS app:
cd ..
npm run ios:syncSome accounts do not expose posts clearly to logged-out visitors. In that case, the server can use a logged-in X session:
X_AUTH_TOKEN=
X_CT0=Those values belong only in server/.env.
Once the app is connected to a working push server:
- Launch the app and allow notifications
- Open a profile inside the app
- Open the profile overflow menu
- Pick the custom watch option
- Choose
Posts only,Posts + replies, orOff
The repository also includes a small admin dashboard for debugging devices, watches, and test pushes.
- This project is best treated as an experimental client, not an App Store-ready product
- A public distribution strategy would require a more compliant architecture
- If you fork it, use your own bundle ID, APNs keys, and server tokens
If this project is useful, consider sponsoring development.