iOS App
The iOS companion app is a thin SwiftUI wrapper that hosts the PWA in a WKWebView and adds native push notifications with interactive actions, home screen widgets, and an Apple Watch app. A separate native Mac app hosts the same PWA on macOS. Source code is included; the apps are not on the App Store, so you build them yourself.
What It Adds
- Real push notifications: APNs notifications that wake your phone, not web push
- Interactive snooze grid: long-press a task notification for a 3x4 grid of snooze options
- Lock screen actions: Done, +1hr, All +1hr, All → Next period, and All → <period> (one per reminder period) directly on the notification
- Reminder period checklist: a reminder period's notification expands into a checklist of that period's reminders and quota reminders. Check the ones you did, then tap Complete N checked, or Complete all
- Home screen widgets: Reminders, Today's Tasks, and Quotas, with check-off, snooze, and +1 buttons
- Apple Watch app: Reminders, Tasks, and Quotas pages, a Smart Stack widget, and notification actions on your wrist
- Home Screen quick actions: touch and hold the app icon for Snooze All +1hr, Snooze All to Next Period, Snooze All +2hr, and Snooze All to Tomorrow (your morning time). They snooze over-target tasks by the same rules as the snooze-all button, then the app opens with a "Snoozed N tasks" message and Undo
- Cross-device sync: snoozed or completed tasks automatically dismiss notifications on other devices
See Widgets, Watch & Mac for what the widgets, the Watch app, and the Mac app do.
Prerequisites
- Apple Developer Program membership ($99/year)
- APNs configured on the server (see Notifications)
- Xcode 26 or later (the widgets use the iOS 26 / watchOS 26 / macOS 26 SDKs for widget push), with the watchOS SDK installed
- xcodegen
- iPhone on iOS 17 or later; Apple Watch on watchOS 10 or later
- iOS 26 / watchOS 26 / macOS 26 for instant widget sync (see Widget sync). Older versions still work but refresh on a schedule.
Building
The iOS project uses xcodegen to generate the Xcode project from project.yml:
cd ios
xcodegen generate
open OpenTask.xcodeprojUse your own team and identifiers
The project files contain the original author's Apple Developer team ID, bundle ID prefix, App Group, and associated domain. Replace them with your own before you build:
- In
ios/project.yml(andmacos/project.ymlif you build the Mac app), setDEVELOPMENT_TEAMto your team ID, and changebundleIdPrefixand everyPRODUCT_BUNDLE_IDENTIFIERto your own reverse-DNS prefix (for examplecom.example.opentask). - Change the App Group (
group.<your prefix>) and thewebcredentials:associated domain in the entitlements sections to match your prefix and your server's domain. - Some Swift files also contain the App Group, Keychain access group, and bundle IDs as string constants. Search
ios/andmacos/for the old prefix and team ID and replace every match. - Set the server's
APNS_BUNDLE_IDto your main app's bundle ID (see Notifications).
Each embedded extension's bundle ID must start with its parent app's bundle ID (for example com.example.opentask.widgets under com.example.opentask).
Simulator builds without the watchOS SDK
Generating and building project.yml requires the watchOS SDK. To build only the iPhone targets for the simulator, generate the watch-free variant with xcodegen generate --spec project-sim.yml. It produces OpenTaskSim.xcodeproj.
Targets
| Target | Platform | Description |
|---|---|---|
OpenTask | iOS | Main app: WKWebView host, first-launch setup, APNs registration, Home Screen quick actions |
OpenTaskNotification | iOS | Notification content extension: the snooze grid and the reminder period checklist on long-press |
OpenTaskWidgets | iOS | Home screen and Lock Screen widgets (Reminders, Today's Tasks, Quotas) |
OpenTaskWatch | watchOS | Apple Watch app: Reminders, Tasks, and Quotas pages, plus notification actions |
OpenTaskWatchWidgets | watchOS | Smart Stack / watch face widget, embedded in the Watch app |
Building OpenTask embeds the notification extension, the widget extension, and the Watch app. Installing the iPhone app installs the Watch app on a paired watch.
Mac app
The Mac app is a separate, native macOS project in macos/. It is not a Mac Catalyst build of the iPhone app. It requires macOS 14 or later.
cd macos
xcodegen generate
open OpenTaskMac.xcodeprojIt uses the same bundle IDs as the iPhone app and its widgets, so macOS treats them as one app. Update macos/project.yml with your own team ID and identifiers as described above. On macOS, the App Group and Keychain access group are prefixed with your team ID.
Push on the Mac
The default Mac build is signed without the push entitlement. To receive notifications, build with the push variant of the entitlements file (OpenTaskMac/OpenTaskMac-Push.entitlements), for example by passing CODE_SIGN_ENTITLEMENTS=OpenTaskMac/OpenTaskMac-Push.entitlements -allowProvisioningUpdates to xcodebuild. See macos/README.md in the repository for details.
First-Run Setup
When the app launches for the first time:
- Enter your server URL (for example
https://tasks.example.com). Without a scheme,https://is assumed; typehttp://for a plain-HTTP server. The app checks that the server's login page is reachable. - Sign in to OpenTask in the app with your username and password.
- Allow notifications when prompted.
Once the app has a push token and you are signed in, the web app automatically creates a Bearer token for the device and stores it in the Keychain. You don't create or paste a token by hand. Notification actions, widgets, and the Watch app all use this token.
Notifications are required for widgets and the Watch
The Bearer token is only provisioned after the app receives an APNs device token, and the app only registers for one after you allow notifications. If you decline notifications, or the app is signed without the push entitlement, notification actions, widgets, and the Watch app stay unconnected.
The main app, the notification extension, and the widget extension share credentials through the App Group Keychain. The iPhone app sends them to the Watch app over WatchConnectivity. If the Watch shows Not Connected, open OpenTask on your iPhone.
The Mac app follows the same flow: enter the server URL, sign in, and allow notifications.
Server Endpoints Used
The apps and their extensions call these endpoints with the device's Bearer token:
| Endpoint | Purpose |
|---|---|
POST /api/push/apns/register | Device token registration |
DELETE /api/push/apns/register | Device token unregistration |
POST /api/push/apns/widget-token | Widget push token registration (iOS/macOS/watchOS 26) |
POST /api/notifications/actions | Done/snooze/delete from notification actions |
PATCH /api/tasks/{id} | Snooze to a specific time (notification snooze grid) |
POST /api/tasks/bulk/snooze-overdue | Bulk snooze from notifications, widgets, the Watch, and the Mac menu bar item (including slot, to snooze to a reminder period) |
POST /api/tasks/bulk/snooze | Per-task and selected-task snooze from the Tasks widget, the Watch, and the Mac menu bar item |
POST /api/tasks/bulk/complete | Complete items (reminder checklist, widget bulk select, the Mac menu bar item), including quota reminders through prompts |
POST /api/quota-prompts/consider, POST /api/quota-prompts/did | Quota reminders' dashed circle and square on the Reminders widget, the Watch, and the Smart Stack card |
POST /api/quota-prompts/restore | Put back a handled quota reminder from the Reminders widget's completed list |
POST /api/tasks/{id}/progress | Quota +1 / −1 from the Quotas widget and the Watch |
POST /api/tasks/{id}/undone | Restore a completed item from a widget's completed list |
GET /api/tasks | Open tasks and quotas for the widgets, the Watch, and the Mac menu bar item |
POST /api/tasks | Quick add from the Mac menu bar item |
GET /api/reminders | Reminders and quota reminders grouped by period, for the widgets, the Watch, and the checklist |
POST /api/task-segments/consider, /defer, /target-today | A task segment row's circle, Defer all, and Target today on the Reminders widget, the Watch, and the Smart Stack card |
GET /api/tasks/counts | The app icon and Dock badge |
GET /api/time-slots | Reminder periods, cached for the period snooze actions |
GET /api/completions | Today's completed items, for a widget's completed list |
GET /api/projects | Project names and colors for the Tasks widget |
GET /api/user/preferences | Label colors for the Quotas widget and the Watch |
POST /api/undo, POST /api/redo, GET /api/undo/status | Widget and Watch undo/redo |
POST /api/auth/session-from-token | Restore an expired web session in the app using the Bearer token |
