Contents
1. Overview
workinOn maintains a short, ordered list of things you're working on — always visible on your home screen via the Large widget. Beyond personal use, it acts as a personal status board: any iOS app on the same device can post a named status slot, update it, and remove it.
There are three integration paths, each suited to different callers:
| Path | Best for | Delivers when |
|---|---|---|
| URL scheme | iOS apps, Shortcuts | App is foregrounded to handle the URL |
| Local notification | iOS apps, time-based triggers | Notification fires (silent) or user taps it |
| Remote push (APNs) | Servers, background delivery | Silently, in the background, any time |
2. URL Scheme Commands
All commands use the workinon:// scheme. All text values must be URL-encoded.
| Command | URL | Effect |
|---|---|---|
| Open | workinon://open |
Bring workinOn to the foreground |
| Add item | workinon://add?text=Buy%20milk |
Append a manual list item |
| Post status | workinon://post?key=myapp&text=Running |
Upsert a keyed status item (creates or replaces) |
| Post with photo | workinon://post?key=myapp&text=Done&photoID=<id> |
Upsert with a photo from the library |
| Remove status | workinon://remove?key=myapp |
Remove a keyed status item |
| Clear all | workinon://clear |
Remove all items (use carefully) |
Key semantics
A key identifies your app's slot on the board. Use your app name or bundle ID (e.g. nagz, com.myapp.status). Keys are case-sensitive. Posting to an existing key replaces the item in place — no duplicates. Status items appear pinned at the top of the list.
Opening from Swift
let url = URL(string: "workinon://post?key=myapp&text=Server%20running")!
UIApplication.shared.open(url)
3. Swift Helper — WorkinOnPoster
Copy Shared/WorkinOnPoster.swift into your iOS app. No dependencies,
no configuration. Suitable for: nagz-ios, qross, famster, obo-ios,
alities-mobile, pickledballs, workinOn itself.
UIApplication.shared.open(). It cannot be used in servers or background extensions that lack a UIApplication. Servers should use the Remote Push path instead.
// Post a text status
WorkinOnPoster.post(key: "nagz", text: "✓ API up · port 9800")
// Post with a photo from the library
WorkinOnPoster.post(key: "nagz", text: "Dashboard", photoID: asset.localIdentifier)
// Remove your slot
WorkinOnPoster.remove(key: "nagz")
// Add a plain manual item (no key)
WorkinOnPoster.add(text: "Fix the auth bug")
Posting images
workinOn stores photos by PHAsset.localIdentifier — it reads from the
photo library, no file copying needed. To post an image your app generated:
PHPhotoLibrary.shared().performChanges {
let req = PHAssetChangeRequest.creationRequestForAsset(from: myUIImage)
let id = req.placeholderForCreatedAsset?.localIdentifier
DispatchQueue.main.async {
WorkinOnPoster.post(key: "myapp", text: "Chart updated", photoID: id)
}
}
4. Notifications → List Entries
Any local or remote notification can carry a workinon_url key in its
userInfo payload. workinOn processes this URL when the notification
is delivered (silent) or when the user taps it (alert).
Payload format
{
"aps": {
"alert": { "title": "workinOn", "body": "Tap to add to your list" },
"sound": "default"
},
"workinon_url": "workinon://add?text=Standup%20notes"
}
The workinon_url value is any valid workinon:// URL from the table above. All commands work: add, post, remove, clear.
Behavior by delivery context
| Context | Alert shown? | Command runs |
|---|---|---|
App in foreground, workinon_url present |
No — silently executed | Immediately on delivery |
| App in background, alert notification | Yes | When user taps notification |
Silent push (content-available: 1) |
No | Immediately in background |
| App not running, alert notification | Yes | When user taps notification, launches app |
5. Swift Helper — WorkinOnNotificationHelper
Copy Shared/WorkinOnNotificationHelper.swift into your iOS app. Requires
UNUserNotificationCenter permission to be granted.
// Add an item when the user taps a notification (5 minutes from now)
WorkinOnNotificationHelper.scheduleAdd(
text: "Standup notes",
title: "workinOn",
body: "Tap to add standup to your list",
at: Date().addingTimeInterval(300)
)
// Post a silent heartbeat every 5 minutes
WorkinOnNotificationHelper.schedulePost(
key: "myserver",
text: "myServer: running",
silent: true,
repeating: .everyFiveMinutes
)
// Post daily status at 9am
WorkinOnNotificationHelper.schedulePost(
key: "myapp.daily",
text: "Daily check-in",
at: Calendar.current.date(bySettingHour: 9, minute: 0, second: 0, of: Date())!,
repeating: .daily
)
// Remove status silently at 6pm
WorkinOnNotificationHelper.scheduleRemove(
key: "myapp.office",
silent: true,
at: Calendar.current.date(bySettingHour: 18, minute: 0, second: 0, of: Date())!
)
// Cancel a scheduled notification
WorkinOnNotificationHelper.cancel(identifier: "my-notification-id")
6. Remote Push (APNs)
Servers can deliver workinOn commands silently via APNs. workinOn processes
the workinon_url key from the push payload immediately in the background
— no user interaction required.
APNs payload
{
"aps": {
"content-available": 1
},
"workinon_url": "workinon://post?key=nagzerver&text=%E2%9C%93%20API%20up%20%C2%B7%209800"
}
Python example (nagzerver)
import urllib.parse
def post_workinon_status(key: str, text: str) -> dict:
"""Build APNs payload to post a workinOn status item."""
encoded_text = urllib.parse.quote(text)
url = f"workinon://post?key={key}&text={encoded_text}"
return {
"aps": {"content-available": 1},
"workinon_url": url
}
# Usage:
payload = post_workinon_status("nagzerver", "✓ API up · port 9800")
await send_apns_push(device_token, payload, bundle_id="com.workinon.app")
APNs push headers
| Header | Value |
|---|---|
apns-push-type | background |
apns-topic | com.workinon.app |
apns-priority | 5 (low priority for silent) |
7. Shortcuts & Automation
workinOn's URL scheme works natively in the Shortcuts app — no configuration required. Use the Open URL action.
Example: "Add to workinOn"
workinon://add?text=[Encoded]Add this shortcut to Siri: "Hey Siri, add to workinOn"
Example: "Post my status"
workinon://post?key=mystatus&text=In%20a%20meetingPersonal Automations
In the Shortcuts app, go to Automation → New Automation:
| Trigger | Action URL | Effect |
|---|---|---|
| Time of day · 9am · Daily | workinon://post?key=morning&text=Morning%20check-in |
Post daily reminder to board |
| App opened · Xcode | workinon://post?key=coding&text=In%20Xcode |
Post when you start coding |
| App closed · Xcode | workinon://remove?key=coding |
Remove when you stop coding |
| Arrive at location | workinon://post?key=location&text=At%20the%20office |
Post your location status |
| Leave location | workinon://remove?key=location |
Clear when you leave |
8. Common Patterns
Server heartbeat (via APNs)
# Send from nagzerver every 5 minutes via cron or APScheduler
payload = post_workinon_status("nagzerver", "✓ up · " + current_time())
# If the item disappears from the board, the server stopped sending
Build status (iOS app)
// At build start
WorkinOnPoster.post(key: "build", text: "⏳ Build 44 in progress")
// On success
WorkinOnPoster.post(key: "build", text: "✓ Build 44 on TestFlight")
// On failure
WorkinOnPoster.post(key: "build", text: "✗ Build 44 failed")
Time-boxed focus (Notification + remove)
// Post now
WorkinOnPoster.post(key: "focus", text: "Deep work — no interruptions")
// Auto-remove in 90 minutes via silent notification
WorkinOnNotificationHelper.scheduleRemove(
key: "focus",
silent: true,
at: Date().addingTimeInterval(90 * 60)
)
Office hours (Shortcuts automation)
Create two automations: one at 9am posts workinon://post?key=office&text=Available,
one at 5pm fires workinon://remove?key=office. The widget always reflects your
current availability.
Shared/WorkinOnPoster.swift— URL scheme posting (iOS apps)Shared/WorkinOnNotificationHelper.swift— scheduled notifications (iOS apps)