Quietgram — a minimal Instagram client for the target iPhone: a WKWebView wrapper around instagram.com that keeps DMs, people search/follow, and the activity page, and blocks the feed, explore, reels, stories, and post permalinks.
Three layers of restriction:
URLPolicy.swift— allowlist of instagram.com paths, enforced inWKNavigationDelegate.decidePolicyFor(full document loads only).- JS mirror of the same rules in
Injection.swift— Instagram is an SPA, so client-side pushState route changes bypass the delegate; the injected script re-checks on every route change and bounces to /direct/inbox/. - CSS in
Injection.swift— hides nav links to blocked surfaces, the explore grid on the search page, and the post grid/highlights on profiles. Selectors are structural (roles, hrefs,header ~ *), never Instagram's hashed class names. Keep Swift and JS policy in sync; the unit tests cover the Swift side.
The .xcodeproj is NOT committed. project.yml (XcodeGen) declares the
project; just gen regenerates the xcodeproj from it. Edit project.yml for
targets/settings/entitlements — never hand-edit the pbxproj. New source
files under App/ are picked up automatically on the next just gen.
XcodeGen is a brew dependency: brew install xcodegen.
Team ID: 467A4PRB8F (personal team — NOT the free 7-day-only tier).
Bundle IDs are always com.alexmiller.<app>. Two install
modes, same split Receptor uses:
| Mode | Recipe | Signing | Validity | Logs |
|---|---|---|---|---|
| DEBUG (dev loop) | just build |
Automatic, Apple Development | 7 days | readable |
| STABLE (daily use) | just deploy |
Manual, Apple Distribution + Ad Hoc profile | 1 year | stripped |
Rules:
- If Alex asks for device logs → the app must be a DEBUG install. Release
strips
get-task-allow;just logsreads nothing from a STABLE install. - STABLE needs a per-app Ad Hoc provisioning profile named
"<App> Ad Hoc Provisioning Profile"installed in~/Library/MobileDevice/Provisioning Profiles/. Creating it is one-time click-ops on developer.apple.com (see README). Ifjust deployfails with "Profile doesn't match", the profile is missing or expired. - Device installs use
xcrun devicectl device install app(wired into the just recipes). SetIOS_DEVICE_IDto the target device's UDID (xcrun devicectl list devices); device recipes fail fast without it. just checkis the CI-able correctness gate: simulator build withCODE_SIGNING_ALLOWED=NO— no signing, no device needed.
- Sources live flat under
App/; growViews/,Models/,Services/subfolders only when the file count demands it (Receptor's layout is the reference for a grown app). - No secrets in the app bundle. Anything sensitive is entered in a Settings
screen at runtime and stored in Keychain/App Group defaults (see
Receptor's
Configuration.swiftfor the pattern). - Assets:
App/Assets.xcassets; the single 1024×1024 AppIcon slot is the only icon you provide (iOS scales the rest).