Post

Replies

Boosts

Views

Activity

Reply to TrustInsights: authorizationStatus returns .unavailable, but requestEvaluation returns .denied — feature missing from Settings (China mainland)
I can answer part of this. I can't test in China or on an iPhone 15 sold there, so whether this is regional gating or device limitation stays open, and only Apple can answer that. Is this region gating? I don't know. The docs for AuthorizationStatus.unavailable give three causes: an unsupported device, an app without the entitlement, or "the framework isn't available in a specific region". So, it implies that region matters. Apple has not published a list of regions or supported models, and the iOS 27 feature availability page doesn't list this feature. The support page gives no regional limit in English or Simplified Chinese. None of that tells you whether it runs on your phone. One could assume that because the Chinese support pages show how to enable this feature to the users, the feature might be available in the region. Perhaps the limitation is with the device. The fact that the Settings entry is missing indicates either/or, that something is causing the device to not have this enabled. In my tests, the entry was present even before I had any apps (including mine) that exercised the feature. Once the app was installed and ran, it appeared under the entry. Don't rule out the entitlement yet. security cms -D shows what the profile allows. What counts is what the app was signed with, so check the built app: codesign -d --entitlements - /path/to/YourApp.appwill show if the app was signed correctly and includes the entitlement in the bundle. What to test If you confirm that the binary indeed includes the entitlement, you can test, by changing one thing at a time. After each change, look for the Settings entry and log authorizationStatus(for:). I don't know whether any of these matter, which is why they're worth testing. Test with the latest 27.3 beta, in case this is indeed region gating that will be enabled later. If you can, run the same build on an iPhone bought outside China. If it gets .notDetermined or .authorized, your build and entitlement are fine. Alternatively, prepare a small project that only demonstrates your implementation of TrustInsights and attach it to your reply here. Perhaps someone outside China can test it for you. On your phone, change Settings > General > Language & Region > Region, then run it again. Change it back afterwards. Check that you're signed in to the App Store [sic]. The support page says turning the feature on might need that. I don't know if that's a typo and they mean logged in to your Apple ID on the device. If you have a test Apple Account with a different country or region, try that too. Don't move your main account for this. For comparison, on my iPhone, outside China, I added the capability in Xcode and requested nothing else. With the setting on, the permission alert appears and evaluations come back. When I turn the setting off, for the app or for the feature, authorizationStatus(for:) returns .denied. So a switched-off setting doesn't explain your .unavailable. I have a TrustInsightsSample that does work on a US phone. You can also test that to see if the lights are on. Which one is the actual gate? Act on authorizationStatus(for:). Apple's example in the framework overview checks it first and stops on .unavailable, so don't call requestEvaluation after that. It is possible that if the authorization is unavailable, the further statuses are undefined. I can't explain why you get denied there. The docs tie denied to the person's options and notAvailable to the device, and your case sounds like the second. File that in Feedback Assistant with a sysdiagnose, whatever the tests show, and post the number here in case someone from Apple wants to follow up. The Settings row The support page describes the top-level entry and its switch first, with per-app access underneath. So I wouldn't expect the top-level entry to be waiting for your app's first request. Like I said, for me on a phone where no app has ever asked, the Settings entry was present. Working without the Settings entry The WWDC session says values and errors can be overridden with launch arguments, and points to the documentation for the list. I can't find that list. If you found that key in Apple's documentation, post the link for everyone. Reading it back from UserDefaults only shows the argument reached your process. Any -key value launch argument does that, whether a framework reads it or not. You don't need Trust Insights for the UI work. Put the decision behind your own enum (no evaluation, unknown, medium, high) and drive the screens from a debug switch. That won't exercise the evaluation or reportConsumption, which still need a device where the framework is available. Ship the no-evaluation path as a normal path: your existing checks decide and the action goes ahead. Developer-side enablement The only requirement I can find documented is the com.apple.developer.trustinsights.base entitlement. I can't find anything about enabling it per region. Whether a region needs anything more is for Apple to answer, so ask that in the same Feedback report. ------------------------- | S.Beck - 7strikes.dev | -------------------------
1d
Reply to CLLocation acquisition time: supported monotonic timestamp or bounded age conversion on iOS 16+?
There is no public iOS API that exposes a raw, hardware-level monotonic timestamp for a CLLocation. Core Location strictly wraps the determination time in a wall-clock Date object. However, you can achieve a much more stable delivery delay and establish a reliable upper bound by unhooking the delivery mechanism from the main UI thread: iOS 17+: Use CLLocationManager.liveUpdates(). This API streams locations via an AsyncSequence over high-priority background Swift Concurrency threads, drastically cutting down on delivery jitter. iOS 16: Explicitly assign your CLLocationManager to execute its delegate callbacks on a dedicated background serial queue (cl_dispatch_queue_t). Once delivery is shifted to a background thread, you can map the Date to a continuous monotonic clock via ContinuousClock().instant(for: location.timestamp) right at dispatch. One crucial constraint warning Be careful with an absolute arithmetic boundary of exactly 10,000,000,000 ns (where +1 ns triggers a rejection). Because CLLocation.timestamp is a Date (backed by a 64-bit floating-point Double), it physically lacks the precision to resolve discrete nanoseconds at current epoch sizes. Furthermore, because consumer mobile devices rely heavily on Assisted GNSS (A-GPS) and cross an internal cellular-to-application processor bus, the hardware's actual time resolution is magnitudes coarser than 1 ns. If you enforce a strict nanosecond cutoff without a small cushion (or dropping down to a millisecond boundary), floating-point rounding artifacts will cause your code to randomly and arbitrarily reject fresh, perfectly valid locations. S.Beck - 7strikes.dev
1w
Reply to Supported approach for real-time iPhone fall detection while an occupational lone-worker safety app is backgrounded?
Background location will give you the execution time, and even if we assume that location sharing for a lone-worker is a justifiable use (and perhaps more so with some creative UX design), there are still some remaining bumps. App Review Guidelines say: Use Location Services in your app only when it is directly relevant to the features and services provided by the app. Location-based APIs shouldn’t be used to provide emergency services It's that second part that needs interpreting, preferably by someone who understands contracts/liabilities/legalspeak. But that said, there are a number of such lone-worker apps (including one I worked on) on the App Store, so that does not seem to be a blocker by App Review. Although they handle each app on a case-by-case basis, so YMMV. The other problem is the power drain introduced by keeping the app running in the background, and a phone without power does not get to execute any code. So, brute forcing full time background execution without some smarter handling will not end well. And a gotcha you should be aware of: if not careful, you will end up running both the location and motion updates on the same thread. Then the motion updates and the processing of them to determine a fall will saturate the thread, and starve the processing of the location updates arriving on the same thread. When the update queue fills up, the system will assume the app is not processing the updates anymore and will stop sending them. Once the location updates stop, your app gets suspended and that means no more execution time. The new CLLocationUpdate/CLBackgroundActivitySession are easier to use and handle in a separate thread than the old CoreLocation APIs. S.Beck - 7strikes.dev
Topic: App & System Services SubTopic: General Tags:
2w
Reply to How to pass off liveUpdates(_:) to main app
It isn't significant location change or a location push, and it isn't something the extension calls. The documentation is too brief there. All it means is that if you need continuous or frequent locations, your app should be producing them with its own live updates, instead of your server sending the extension one location push after another. For "passing-off" liveUpdates(_:) to the main app from the LPSE, nothing gets handed over for you. You have to build it. Or is your question about passing the location data obtained by the extension to the main app? If that is what you believe you will want to do after reading my answer here, I explain your options for implementing that in an article at 7strikes.dev First, what is documented: The extension gets a push, finds a location, reports it, and its time is up. A device gets about 360 location pushes a day (Creating a location push service extension). An app receiving live updates in a CLBackgroundActivitySession keeps receiving them in the background, and if it is terminated, Core Location launches it in the background again when there are updates. On that launch you call liveUpdates(_:) again and recreate your session objects (Discover streamlined location updates, WWDC23). A CLBackgroundActivitySession can only be started while the app is in the foreground. From the background you can only rejoin one you started earlier. CLServiceSession works the same way (Handling location updates in the background). Put those together and the extension has no way to start continuous updates in your app. So: When the person turns on continuous sharing, with your app on screen, start live updates with a background activity session and a service session, and keep them running until they turn it off. Save the fact that sharing is on, and start all of it again at every launch while it is. Use the extension for the occasional request while continuous sharing is off. If the extension thinks that continuous sharing should start, all it can do is leave a note in your App Group container. The app acts on it the next time the person opens it, or it starts running due to a background function. // When sharing is turned on, with the app on screen, // and again at every launch while it's on. service = CLServiceSession(authorization: .always) background = CLBackgroundActivitySession() updates = Task { for try await update in CLLocationUpdate.liveUpdates() { if let location = update.location { report(location) } } } Significant location change can also bring a terminated app back, but your app has to start that too, so it doesn't change the answer. If what you want to do is to get the locations in the extension and pass it on to the main app, the article at 7strikes.dev about app extensions and their containing apps goes through this, along with the other extension types. Along with some hints to help you determine if you actually need to do this at all. S.Beck: 7strikes.dev
2w
Reply to Live Activity Timeline
@picnicbob if I may put my user hat on for a change: I know when I am 4 levels deep in a station and things don't work. So, if the Live Activity shows "No connection. Updates Paused" I am sure the users will understand. If you think the solution I proposed works in general except for the conditions where pushes are lost in dead zones, you can change the LA to show a N/A message using staleDate. staleDate will work for this because the check happens on the device. When there is a periodic push to update the snapshot of the system, then no new update arrives, the staleDate passes on its own, with or without a connection. How to set it up Give every update a deadline. Each push sets a stale-date a little past when the next update is due. For example, if your server pushes every 90 seconds, set it to now + 2 minutes. Every push that arrives on time moves the deadline forward. If pushes stop arriving, the deadline passes. In the push payload: "aps": { "timestamp": 1760630000, "event": "update", "content-state": {_your content_, "updatedAt": 1760630000 }, "stale-date": 1760630120 } For updates sent from the app, pass staleDate: to ActivityContent the same way. Carry the update time in the content state, e.g. var updatedAt: Date, so the view always knows how old its data is. Have the view show both states: if context.isStale { Label("Updates paused", systemImage: "wifi.slash") Text("Last update \(Text(context.state.updatedAt, style: .relative)) ago") } else { // normal live content } Text(..., style: .relative) is one of the system-drawn views, so "3 min ago" keeps counting even with no connection. You could also show the "last update" line all the time, which gives people a hint before the stale state kicks in. Caveats Stale doesn't mean offline. It only means "no update arrived by the deadline." That could be no connection, but also your server failing, APNs throttling your pushes, or Low Power Mode delaying delivery. Wording like "Updates paused" or "Updates delayed" is accurate. "No connection" might not be. The deadline has to account for expected push delivery delays. If you set it too tight, normal delivery delays will make the activity flicker into the stale state. A margin reasonably greater than your normal push interval is a good starting point. I can't confirm how soon the screen changes. Apple's docs say that when the stale date arrives, the activity's state changes to .stale, and the view gets context.isStale. They don't say how soon after that the Lock Screen redraws. Test it on a device in airplane mode. Recovery is automatic. When the connection comes back, APNs delivers the latest push. That push carries a new stale-date, so the activity returns to normal. The app can't reliably catch this itself. You could watch for connection loss in the app with NWPathMonitor and call activity.update, but that only works while the app is running, and if it were, you wouldn't have any of these problems. The stale date is the approach that also works while the app is suspended. Test this in your demo: replace staleDate: target with a rolling deadline and you'd get both behaviors. S.Beck -- 7strikes.dev Sources: ActivityContent.staleDate (Apple) ActivityViewContext.isStale (Apple) Live Activities essentials, WWDC26 session 223 (Apple)
2w
Reply to Live Activity Timeline
Apologies for interjecting here, but I'm afraid you two are talking past each other and I can't bear to watch the train going down the wrong track 🫨 The situation is that TimelineView doesn't work in Live Activities. Either it's a bug (@DTS Engineer can help with that), or it was never documented to work in a Live Activity. I couldn't find anything from Apple saying either way. Live Activities show a stored snapshot of the view and only re-render on an ActivityKit content update. The sample here never sends one, so the .periodic schedule is never followed. Others have hit the same thing, noting that it happened to work in the debugger just like you observed yourself: https://developer.apple.com/forums/thread/766932 (FB15590204, no fix in the thread). What to use instead Use time views the system draws itself. These keep ticking without any updates: Text(timerInterval:) and ProgressView(timerInterval:) Text(date, style: .timer) or .relative Text(.currentDate, format: ...) (iOS 18+) One limit: the system picks the tick rate, so you can't get a custom interval like every 10 s. What I did to make it work (5-minute countdown) // Attributes: the target time lives in the activity data, not the view var start: Date var target: Date // Live Activity view (clamped: start > end in a ClosedRange crashes) Text(timerInterval: min(.now, target)...target) .monospacedDigit() ProgressView(timerInterval: min(start, target)...target, countsDown: true) // App: start the activity let start = Date.now let target = start.addingTimeInterval(5 * 60) Activity<WidgetExtensionAttributes>.request( attributes: .init(name: "Live Activity", start: start, target: target), content: .init(state: .init(emoji: "😀"), staleDate: target)) Setting staleDate: target lets the view check context.isStale and show "Time's up" after the countdown ends. S.Beck -- 7strikes.dev
2w
Reply to How to distinguish a foreground cold launch from a background task cold launch?
Before I answer the questions, I should mention that launchOptions is practically dead. Details below: 1. Can iOS relaunch a previously terminated app to execute a scheduled BGProcessingTask? Yes. BGProcessingTaskRequest is documented as "a request to launch your app in the background", so a cold launch straight into the background is the expected path. That is also why every launch handler has to be registered before the app finishes launching. 2. Is there a reliable way to distinguish a foreground cold launch from a background task cold launch inside application(_:didFinishLaunchingWithOptions:)? Not in application(_:didFinishLaunchingWithOptions:). Not anymore! The launch options would be the obvious place, but they are gone for good. In an app that supports scenes, launchOptions is nil; the parameter's documentation says so. Also, apps built with the iOS 27 SDK must adopt the scene-based life cycle or they fail to launch. And, every launch options key is now deprecated. What you saw with applicationState is documented too. In a scene-based app it takes the value of the most active scene, and "a scene-based app launches in the background state". No scene exists yet during didFinishLaunchingWithOptions, so it reads .background for every launch. 3. If this distinction cannot be made reliably during didFinishLaunchingWithOptions, what is the earliest lifecycle callback where I can determine that the app is entering the foreground? Split the work by where it belongs. Keep didFinishLaunchingWithOptions to what every launch needs, including the task registration, which has to happen there. Move the foreground-only startup, network requests included, to the scene delegate. sceneWillEnterForeground(_:) is the documented signal: UIKit calls it before a scene moves to the foreground, and it is always followed by sceneDidBecomeActive(_:). It runs on every return to the foreground, not only the first, so guard one-time work with a flag: func sceneWillEnterForeground(_ scene: UIScene) { // Called only when a scene is about to become visible. startForegroundWorkIfNeeded() } What used to arrive in launchOptions now arrives in the scene's connectionOptions, or through other APIs for the background cases. Apple documents the replacement for each key on its own page under UIApplication.LaunchOptionsKey. I have a note at 7strikes.dev that puts all 14 launch options and all 14 connection options side by side, which is easier to read than going through the pages one at a time. S.Beck -- 7strikes.dev
3w
Reply to Live Activity Timeline
This is doable, but not through the API alone. Live Activities have no timeline, keeping the app alive to implement a "timeline" is not a good solution as you also realize, and one push per arrival won't scale. The way through is to stop treating the push as the display and treat it as a data feed. The numbers. From what I can recall based on the MTA Developer site, the system has about 470 stations and roughly 500 trains in service at peak. But you will surely have more accurate numbers. A train reaches a new station every couple of minutes, so system-wide that's on the order of 15,000+ arrivals an hour. One push per arrival is a non-starter. But the shortest run between two stations is about 90 seconds (you probably have more accurate info, correct?). So if you send a snapshot every 90 seconds, no train can pass more than one station between updates, and the device never loses track of where a train is. Each train's state also packs into about 3 bytes, so the whole system fits in about 1.5 KB of raw data, or about 2 KB after encoding. That's well inside the payload limit. The view does the work, not the push. Each update is rendered by your own SwiftUI code in the widget extension, so the view can decode a compact payload and compute arrivals for the user's station. Timer-based text and progress views count down on their own between updates, and the view can drop trains whose arrival time had already passed when it rendered. A train that arrives between pushes simply sits at "arriving" until the next update. One risk to consider would be when the system decides to re-render the Live Activity. whether rendering happens as each push arrives, or later when the Lock Screen or Dynamic Island is shown will be up to the device state. Push a snapshot of the whole system on a fixed cadence. Use one broadcast channel and send a full system snapshot every 90 seconds or so, regardless of how many trains arrived. Because each push is a complete snapshot rather than a change list, a push lost in a dead zone only costs staleness, and the next one corrects everything. A train that has just arrived sits at "arriving" until the next push, which is accurate enough to read as "at the platform." Encode trains, not stations. Encoding arrivals per station repeats every train at every downstream stop and won't fit the payload limit. Instead, encode each train's position, and let the device work out arrivals at its own station. Each train becomes a fixed 3-byte (24-bit) record: which stopping pattern it is running (9 bits, which also implies route and direction), the index of the next stop on that pattern (7 bits), and how long until it reaches that stop, in 5-second units (8 bits, up to about 21 minutes). Size these field widths from your actual data of course, not from my guesses. Concatenate all the records and base64-encode the result into a single string field in the content state, alongside a snapshot timestamp and a version number for the schedule data: "content-state": { "v": 42, "t": 1760375575, "d": "AIEXAgc5…" } Three bytes map to exactly four base64 characters, so 500 trains become a 2,000-character string. Base64 characters need no escaping in JSON. A JSON array of numbers holding the same data would be roughly three times larger. On the device, the view decodes the string back into records. For each train on a pattern that serves the user's platform, it computes the arrival as: snapshot time + time to the train's next stop + scheduled run time from that stop to the user's station. Those run times would come from a table bundled with the app. If the version number in the payload doesn't match the bundled table, the view should say so rather than show wrong times. The hard part is the bundled schedule data. That data has to stay in sync with the server, including weekend and overnight service changes. Version it, and decide what the view shows when it meets a train pattern it doesn't recognize. Those kinds of updates can probably be left for when the app is launched by the user to start the activity anyways. Most of the real effort is in those last details: the record layout, the schedule data pipeline, and handling service changes. As they say "Some engineering might be required". Happy to compare notes on any of it if useful. S.Beck -- 7strikes.dev
3w
Reply to Apple Watch Ultra 2: headingAccuracy near 89° even with correct heading; intermittent 180° reversal
I don't have much experience with this behavior on the watch. I just thought maybe the .maritime activity would be helpful. One additional thought about logging, if you use os_log() in your app to log as well, then the sysdiagnose will contain your app's logs along with the system logs, to give you and Apple (in case you file a bug report eventually) -- S.Beck -- 7strikes.dev --
Oct ’26
Reply to Apple Watch Ultra 2: headingAccuracy near 89° even with correct heading; intermittent 180° reversal
Can't say whether it is related, or whether it affects heading at all, but if you have not tried it, see if CLActivityType.maritime makes a difference. It is new in iOS 27 and watchOS 27, for "activities in vessels on water". Set it as the activityType of the same CLLocationManager you call startUpdatingHeading() on. A surfboard is a stretch for "vessel", but it is one line to find out. I have a note at 7strikes.dev that covers the usage pattern. On the "89": according to the headingAccuracy documentation, only a negative value marks a heading as invalid. A positive value is the possible error in degrees, so 89 means the system does not trust that heading much, even at the moments it happens to be right. As for things going back to normal on land, I would not look at GPS. You are reading magneticHeading, and the documentation only brings the device's location into it for trueHeading. What it does name as causes of a bad heading are calibration and strong local magnetic fields, so I would look for something near the watch in the water that is not there on land. Take it with a pinch of salt from someone who has never touched a surfboard and all his opinions come from watching the 100 ft Wave, but I wonder if there is a lag compared to the back and forth movements of the board or not. To see what the system actually did, install the Location logging profile from Profiles and Logs before you reproduce the problem, then take a sysdiagnose and read its log archive in Console. You might be able to understand the cause. -- S.Beck -- 7strikes.dev
Oct ’26
Reply to CLLocationManager: request for a maritime/sailing CLActivityType (low-acceleration, continuous-motion use case)
Apparently, Apple has granted your wish :-) iOS 27 added an activity type for this: CLActivityType.maritime, with a matching CLLocationUpdate.LiveConfiguration.maritime for live updates. Apple's documentation describes it as positioning for "activities in vessels on water, including while anchored", and the comment in CLLocationUpdater.h says it is "tuned for vehicular maritime use cases". It came without a WWDC session or a release note, so it is easy to miss. Set .maritime on iOS 27 and later, and keep .otherNavigation for earlier systems: if #available(iOS 27, *) { manager.activityType = .maritime } else { manager.activityType = .otherNavigation } Since you are on Flutter: if your location plugin maps activity types to its own enum, it will not offer .maritime until it is updated, and you may need to set it from native code. I would retest your light-wind tracks with it before going further. If you still see the same behaviour, filing a Feedback with a sysdiagnose taken after installing the Location Services logging profile would be the next step. I have a note at 7strikes.dev that covers the live updates version as well. --- S.Beck -- 7strikes.dev
Oct ’26
Reply to Bluetooth Low Energy (BLE) 5 Extended Advertising
Putting aside that a 2 second interval is ridiculously long for a reliable discovery, and while 100ms is much better, you should try to use the closest recommended interval in Section 58.5 instead for optimum discoverability-to-power performance, I am not sure if your issue is with advertising. You say the device can be detected/scanned, but the connection cannot be established which I would take to mean advertising and discovery has worked, but you are getting stuck at the connect phase. Sounds like you are issuing a scan and then a connect to the discovered CBPeripheral. If connection is failing, there would be an error. Maybe this is an iOS 18.6.2 issue, but you might be looking at the wrong place. What errors do you see (if any) after the connectPeripheral() is called? What do you see happening in the packet trace? -- S.Beck -- 7strikes.dev
Topic: App & System Services SubTopic: Core OS Tags:
Oct ’26
Reply to AlarmKit: supported approach for a server-triggered wake-up alarm after an air-raid all-clear
I have not tested whether this can be done in an NSE. Various extensions have differing sandbox limitations on what functionality would be enabled. That's why I suggested to try it out yourselves as an experiment. While you won't be able to send actual push notifications using a free account, you can use a JSON file containing your payload in the simulator. This will still let you test your AlarmKit interaction inside the NSE, and at the very least let you know if this is a viable architecture to consider. You would create a JSON file as such: { "Simulator Target Bundle": "com.airraidapp.test", "aps": { "alert": { "title": "Airraid Update", "body": "Airraids are over" }, "mutable-content": 1 } } and call it airraid.apns (or whatever you want, just match your BundleID in the payload to your app) And then either drag and drop this onto the simulator to simulate a push, or use xcrun from a Terminal. xcrun simctl push --help shows the usage. Good luck! -- S.Beck -- 7strikes.dev
Sep ’26
Reply to AlarmKit: supported approach for a server-triggered wake-up alarm after an air-raid all-clear
This would be possible to architect through a push architecture rather than trying to poll your server, but all that will depend on whether you can set/cancel/modify alarm kit alarms from within a Notification Service Extension. I would suggest to write a quick sample receiving push notifications in an NSE, and see if you can manipulate the main app's alarms from there. If that works, you can architect this by controlling the alarms by sending push notifications based on the air-raid status. -- S.Beck -- 7strikes.dev
Sep ’26
Reply to CoreBluetooth reconnection to legacy BLE directed advertisements differs across iPad hardware
Bluetooth Low Energy (BLE) compatibility concerns with A16-powered iPads are frequently reported. A review of these forums and broader online resources yields numerous references to such issues. But in your case the issue is stemming from your side. Apple explicitly says ADV_DIRECT_IND should not be used in Accessory Design Guidelines. Your implementation may have worked in many cases, but deviating from specifications and recommendations can cause such unforeseen issues. The technical reasoning for this limitation should be obvious. Apple devices roll their Bluetooth addresses periodically, and a directed advertising to an address that is no longer in use would have nowhere to go. While you can use the permanent identity address in the advertising for a paired device, that requires your accessory to decode the random address using the IRK, which I am guessing it no longer has in DFU mode. What Apple will say about this remains to be seen, but I would say "ADV_DIRECT_IND should not be used" is something they already have said. -- S.Beck -- 7strikes.dev
Topic: App & System Services SubTopic: Core OS Tags:
Sep ’26
Reply to TrustInsights: authorizationStatus returns .unavailable, but requestEvaluation returns .denied — feature missing from Settings (China mainland)
I can answer part of this. I can't test in China or on an iPhone 15 sold there, so whether this is regional gating or device limitation stays open, and only Apple can answer that. Is this region gating? I don't know. The docs for AuthorizationStatus.unavailable give three causes: an unsupported device, an app without the entitlement, or "the framework isn't available in a specific region". So, it implies that region matters. Apple has not published a list of regions or supported models, and the iOS 27 feature availability page doesn't list this feature. The support page gives no regional limit in English or Simplified Chinese. None of that tells you whether it runs on your phone. One could assume that because the Chinese support pages show how to enable this feature to the users, the feature might be available in the region. Perhaps the limitation is with the device. The fact that the Settings entry is missing indicates either/or, that something is causing the device to not have this enabled. In my tests, the entry was present even before I had any apps (including mine) that exercised the feature. Once the app was installed and ran, it appeared under the entry. Don't rule out the entitlement yet. security cms -D shows what the profile allows. What counts is what the app was signed with, so check the built app: codesign -d --entitlements - /path/to/YourApp.appwill show if the app was signed correctly and includes the entitlement in the bundle. What to test If you confirm that the binary indeed includes the entitlement, you can test, by changing one thing at a time. After each change, look for the Settings entry and log authorizationStatus(for:). I don't know whether any of these matter, which is why they're worth testing. Test with the latest 27.3 beta, in case this is indeed region gating that will be enabled later. If you can, run the same build on an iPhone bought outside China. If it gets .notDetermined or .authorized, your build and entitlement are fine. Alternatively, prepare a small project that only demonstrates your implementation of TrustInsights and attach it to your reply here. Perhaps someone outside China can test it for you. On your phone, change Settings > General > Language & Region > Region, then run it again. Change it back afterwards. Check that you're signed in to the App Store [sic]. The support page says turning the feature on might need that. I don't know if that's a typo and they mean logged in to your Apple ID on the device. If you have a test Apple Account with a different country or region, try that too. Don't move your main account for this. For comparison, on my iPhone, outside China, I added the capability in Xcode and requested nothing else. With the setting on, the permission alert appears and evaluations come back. When I turn the setting off, for the app or for the feature, authorizationStatus(for:) returns .denied. So a switched-off setting doesn't explain your .unavailable. I have a TrustInsightsSample that does work on a US phone. You can also test that to see if the lights are on. Which one is the actual gate? Act on authorizationStatus(for:). Apple's example in the framework overview checks it first and stops on .unavailable, so don't call requestEvaluation after that. It is possible that if the authorization is unavailable, the further statuses are undefined. I can't explain why you get denied there. The docs tie denied to the person's options and notAvailable to the device, and your case sounds like the second. File that in Feedback Assistant with a sysdiagnose, whatever the tests show, and post the number here in case someone from Apple wants to follow up. The Settings row The support page describes the top-level entry and its switch first, with per-app access underneath. So I wouldn't expect the top-level entry to be waiting for your app's first request. Like I said, for me on a phone where no app has ever asked, the Settings entry was present. Working without the Settings entry The WWDC session says values and errors can be overridden with launch arguments, and points to the documentation for the list. I can't find that list. If you found that key in Apple's documentation, post the link for everyone. Reading it back from UserDefaults only shows the argument reached your process. Any -key value launch argument does that, whether a framework reads it or not. You don't need Trust Insights for the UI work. Put the decision behind your own enum (no evaluation, unknown, medium, high) and drive the screens from a debug switch. That won't exercise the evaluation or reportConsumption, which still need a device where the framework is available. Ship the no-evaluation path as a normal path: your existing checks decide and the action goes ahead. Developer-side enablement The only requirement I can find documented is the com.apple.developer.trustinsights.base entitlement. I can't find anything about enabling it per region. Whether a region needs anything more is for Apple to answer, so ask that in the same Feedback report. ------------------------- | S.Beck - 7strikes.dev | -------------------------
Replies
Boosts
Views
Activity
1d
Reply to CLLocation acquisition time: supported monotonic timestamp or bounded age conversion on iOS 16+?
There is no public iOS API that exposes a raw, hardware-level monotonic timestamp for a CLLocation. Core Location strictly wraps the determination time in a wall-clock Date object. However, you can achieve a much more stable delivery delay and establish a reliable upper bound by unhooking the delivery mechanism from the main UI thread: iOS 17+: Use CLLocationManager.liveUpdates(). This API streams locations via an AsyncSequence over high-priority background Swift Concurrency threads, drastically cutting down on delivery jitter. iOS 16: Explicitly assign your CLLocationManager to execute its delegate callbacks on a dedicated background serial queue (cl_dispatch_queue_t). Once delivery is shifted to a background thread, you can map the Date to a continuous monotonic clock via ContinuousClock().instant(for: location.timestamp) right at dispatch. One crucial constraint warning Be careful with an absolute arithmetic boundary of exactly 10,000,000,000 ns (where +1 ns triggers a rejection). Because CLLocation.timestamp is a Date (backed by a 64-bit floating-point Double), it physically lacks the precision to resolve discrete nanoseconds at current epoch sizes. Furthermore, because consumer mobile devices rely heavily on Assisted GNSS (A-GPS) and cross an internal cellular-to-application processor bus, the hardware's actual time resolution is magnitudes coarser than 1 ns. If you enforce a strict nanosecond cutoff without a small cushion (or dropping down to a millisecond boundary), floating-point rounding artifacts will cause your code to randomly and arbitrarily reject fresh, perfectly valid locations. S.Beck - 7strikes.dev
Replies
Boosts
Views
Activity
1w
Reply to Supported approach for real-time iPhone fall detection while an occupational lone-worker safety app is backgrounded?
Background location will give you the execution time, and even if we assume that location sharing for a lone-worker is a justifiable use (and perhaps more so with some creative UX design), there are still some remaining bumps. App Review Guidelines say: Use Location Services in your app only when it is directly relevant to the features and services provided by the app. Location-based APIs shouldn’t be used to provide emergency services It's that second part that needs interpreting, preferably by someone who understands contracts/liabilities/legalspeak. But that said, there are a number of such lone-worker apps (including one I worked on) on the App Store, so that does not seem to be a blocker by App Review. Although they handle each app on a case-by-case basis, so YMMV. The other problem is the power drain introduced by keeping the app running in the background, and a phone without power does not get to execute any code. So, brute forcing full time background execution without some smarter handling will not end well. And a gotcha you should be aware of: if not careful, you will end up running both the location and motion updates on the same thread. Then the motion updates and the processing of them to determine a fall will saturate the thread, and starve the processing of the location updates arriving on the same thread. When the update queue fills up, the system will assume the app is not processing the updates anymore and will stop sending them. Once the location updates stop, your app gets suspended and that means no more execution time. The new CLLocationUpdate/CLBackgroundActivitySession are easier to use and handle in a separate thread than the old CoreLocation APIs. S.Beck - 7strikes.dev
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
2w
Reply to How to pass off liveUpdates(_:) to main app
It isn't significant location change or a location push, and it isn't something the extension calls. The documentation is too brief there. All it means is that if you need continuous or frequent locations, your app should be producing them with its own live updates, instead of your server sending the extension one location push after another. For "passing-off" liveUpdates(_:) to the main app from the LPSE, nothing gets handed over for you. You have to build it. Or is your question about passing the location data obtained by the extension to the main app? If that is what you believe you will want to do after reading my answer here, I explain your options for implementing that in an article at 7strikes.dev First, what is documented: The extension gets a push, finds a location, reports it, and its time is up. A device gets about 360 location pushes a day (Creating a location push service extension). An app receiving live updates in a CLBackgroundActivitySession keeps receiving them in the background, and if it is terminated, Core Location launches it in the background again when there are updates. On that launch you call liveUpdates(_:) again and recreate your session objects (Discover streamlined location updates, WWDC23). A CLBackgroundActivitySession can only be started while the app is in the foreground. From the background you can only rejoin one you started earlier. CLServiceSession works the same way (Handling location updates in the background). Put those together and the extension has no way to start continuous updates in your app. So: When the person turns on continuous sharing, with your app on screen, start live updates with a background activity session and a service session, and keep them running until they turn it off. Save the fact that sharing is on, and start all of it again at every launch while it is. Use the extension for the occasional request while continuous sharing is off. If the extension thinks that continuous sharing should start, all it can do is leave a note in your App Group container. The app acts on it the next time the person opens it, or it starts running due to a background function. // When sharing is turned on, with the app on screen, // and again at every launch while it's on. service = CLServiceSession(authorization: .always) background = CLBackgroundActivitySession() updates = Task { for try await update in CLLocationUpdate.liveUpdates() { if let location = update.location { report(location) } } } Significant location change can also bring a terminated app back, but your app has to start that too, so it doesn't change the answer. If what you want to do is to get the locations in the extension and pass it on to the main app, the article at 7strikes.dev about app extensions and their containing apps goes through this, along with the other extension types. Along with some hints to help you determine if you actually need to do this at all. S.Beck: 7strikes.dev
Replies
Boosts
Views
Activity
2w
Reply to Live Activity Timeline
@picnicbob if I may put my user hat on for a change: I know when I am 4 levels deep in a station and things don't work. So, if the Live Activity shows "No connection. Updates Paused" I am sure the users will understand. If you think the solution I proposed works in general except for the conditions where pushes are lost in dead zones, you can change the LA to show a N/A message using staleDate. staleDate will work for this because the check happens on the device. When there is a periodic push to update the snapshot of the system, then no new update arrives, the staleDate passes on its own, with or without a connection. How to set it up Give every update a deadline. Each push sets a stale-date a little past when the next update is due. For example, if your server pushes every 90 seconds, set it to now + 2 minutes. Every push that arrives on time moves the deadline forward. If pushes stop arriving, the deadline passes. In the push payload: "aps": { "timestamp": 1760630000, "event": "update", "content-state": {_your content_, "updatedAt": 1760630000 }, "stale-date": 1760630120 } For updates sent from the app, pass staleDate: to ActivityContent the same way. Carry the update time in the content state, e.g. var updatedAt: Date, so the view always knows how old its data is. Have the view show both states: if context.isStale { Label("Updates paused", systemImage: "wifi.slash") Text("Last update \(Text(context.state.updatedAt, style: .relative)) ago") } else { // normal live content } Text(..., style: .relative) is one of the system-drawn views, so "3 min ago" keeps counting even with no connection. You could also show the "last update" line all the time, which gives people a hint before the stale state kicks in. Caveats Stale doesn't mean offline. It only means "no update arrived by the deadline." That could be no connection, but also your server failing, APNs throttling your pushes, or Low Power Mode delaying delivery. Wording like "Updates paused" or "Updates delayed" is accurate. "No connection" might not be. The deadline has to account for expected push delivery delays. If you set it too tight, normal delivery delays will make the activity flicker into the stale state. A margin reasonably greater than your normal push interval is a good starting point. I can't confirm how soon the screen changes. Apple's docs say that when the stale date arrives, the activity's state changes to .stale, and the view gets context.isStale. They don't say how soon after that the Lock Screen redraws. Test it on a device in airplane mode. Recovery is automatic. When the connection comes back, APNs delivers the latest push. That push carries a new stale-date, so the activity returns to normal. The app can't reliably catch this itself. You could watch for connection loss in the app with NWPathMonitor and call activity.update, but that only works while the app is running, and if it were, you wouldn't have any of these problems. The stale date is the approach that also works while the app is suspended. Test this in your demo: replace staleDate: target with a rolling deadline and you'd get both behaviors. S.Beck -- 7strikes.dev Sources: ActivityContent.staleDate (Apple) ActivityViewContext.isStale (Apple) Live Activities essentials, WWDC26 session 223 (Apple)
Replies
Boosts
Views
Activity
2w
Reply to Live Activity Timeline
Apologies for interjecting here, but I'm afraid you two are talking past each other and I can't bear to watch the train going down the wrong track 🫨 The situation is that TimelineView doesn't work in Live Activities. Either it's a bug (@DTS Engineer can help with that), or it was never documented to work in a Live Activity. I couldn't find anything from Apple saying either way. Live Activities show a stored snapshot of the view and only re-render on an ActivityKit content update. The sample here never sends one, so the .periodic schedule is never followed. Others have hit the same thing, noting that it happened to work in the debugger just like you observed yourself: https://developer.apple.com/forums/thread/766932 (FB15590204, no fix in the thread). What to use instead Use time views the system draws itself. These keep ticking without any updates: Text(timerInterval:) and ProgressView(timerInterval:) Text(date, style: .timer) or .relative Text(.currentDate, format: ...) (iOS 18+) One limit: the system picks the tick rate, so you can't get a custom interval like every 10 s. What I did to make it work (5-minute countdown) // Attributes: the target time lives in the activity data, not the view var start: Date var target: Date // Live Activity view (clamped: start > end in a ClosedRange crashes) Text(timerInterval: min(.now, target)...target) .monospacedDigit() ProgressView(timerInterval: min(start, target)...target, countsDown: true) // App: start the activity let start = Date.now let target = start.addingTimeInterval(5 * 60) Activity<WidgetExtensionAttributes>.request( attributes: .init(name: "Live Activity", start: start, target: target), content: .init(state: .init(emoji: "😀"), staleDate: target)) Setting staleDate: target lets the view check context.isStale and show "Time's up" after the countdown ends. S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
2w
Reply to How to distinguish a foreground cold launch from a background task cold launch?
Before I answer the questions, I should mention that launchOptions is practically dead. Details below: 1. Can iOS relaunch a previously terminated app to execute a scheduled BGProcessingTask? Yes. BGProcessingTaskRequest is documented as "a request to launch your app in the background", so a cold launch straight into the background is the expected path. That is also why every launch handler has to be registered before the app finishes launching. 2. Is there a reliable way to distinguish a foreground cold launch from a background task cold launch inside application(_:didFinishLaunchingWithOptions:)? Not in application(_:didFinishLaunchingWithOptions:). Not anymore! The launch options would be the obvious place, but they are gone for good. In an app that supports scenes, launchOptions is nil; the parameter's documentation says so. Also, apps built with the iOS 27 SDK must adopt the scene-based life cycle or they fail to launch. And, every launch options key is now deprecated. What you saw with applicationState is documented too. In a scene-based app it takes the value of the most active scene, and "a scene-based app launches in the background state". No scene exists yet during didFinishLaunchingWithOptions, so it reads .background for every launch. 3. If this distinction cannot be made reliably during didFinishLaunchingWithOptions, what is the earliest lifecycle callback where I can determine that the app is entering the foreground? Split the work by where it belongs. Keep didFinishLaunchingWithOptions to what every launch needs, including the task registration, which has to happen there. Move the foreground-only startup, network requests included, to the scene delegate. sceneWillEnterForeground(_:) is the documented signal: UIKit calls it before a scene moves to the foreground, and it is always followed by sceneDidBecomeActive(_:). It runs on every return to the foreground, not only the first, so guard one-time work with a flag: func sceneWillEnterForeground(_ scene: UIScene) { // Called only when a scene is about to become visible. startForegroundWorkIfNeeded() } What used to arrive in launchOptions now arrives in the scene's connectionOptions, or through other APIs for the background cases. Apple documents the replacement for each key on its own page under UIApplication.LaunchOptionsKey. I have a note at 7strikes.dev that puts all 14 launch options and all 14 connection options side by side, which is easier to read than going through the pages one at a time. S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
3w
Reply to Live Activity Timeline
This is doable, but not through the API alone. Live Activities have no timeline, keeping the app alive to implement a "timeline" is not a good solution as you also realize, and one push per arrival won't scale. The way through is to stop treating the push as the display and treat it as a data feed. The numbers. From what I can recall based on the MTA Developer site, the system has about 470 stations and roughly 500 trains in service at peak. But you will surely have more accurate numbers. A train reaches a new station every couple of minutes, so system-wide that's on the order of 15,000+ arrivals an hour. One push per arrival is a non-starter. But the shortest run between two stations is about 90 seconds (you probably have more accurate info, correct?). So if you send a snapshot every 90 seconds, no train can pass more than one station between updates, and the device never loses track of where a train is. Each train's state also packs into about 3 bytes, so the whole system fits in about 1.5 KB of raw data, or about 2 KB after encoding. That's well inside the payload limit. The view does the work, not the push. Each update is rendered by your own SwiftUI code in the widget extension, so the view can decode a compact payload and compute arrivals for the user's station. Timer-based text and progress views count down on their own between updates, and the view can drop trains whose arrival time had already passed when it rendered. A train that arrives between pushes simply sits at "arriving" until the next update. One risk to consider would be when the system decides to re-render the Live Activity. whether rendering happens as each push arrives, or later when the Lock Screen or Dynamic Island is shown will be up to the device state. Push a snapshot of the whole system on a fixed cadence. Use one broadcast channel and send a full system snapshot every 90 seconds or so, regardless of how many trains arrived. Because each push is a complete snapshot rather than a change list, a push lost in a dead zone only costs staleness, and the next one corrects everything. A train that has just arrived sits at "arriving" until the next push, which is accurate enough to read as "at the platform." Encode trains, not stations. Encoding arrivals per station repeats every train at every downstream stop and won't fit the payload limit. Instead, encode each train's position, and let the device work out arrivals at its own station. Each train becomes a fixed 3-byte (24-bit) record: which stopping pattern it is running (9 bits, which also implies route and direction), the index of the next stop on that pattern (7 bits), and how long until it reaches that stop, in 5-second units (8 bits, up to about 21 minutes). Size these field widths from your actual data of course, not from my guesses. Concatenate all the records and base64-encode the result into a single string field in the content state, alongside a snapshot timestamp and a version number for the schedule data: "content-state": { "v": 42, "t": 1760375575, "d": "AIEXAgc5…" } Three bytes map to exactly four base64 characters, so 500 trains become a 2,000-character string. Base64 characters need no escaping in JSON. A JSON array of numbers holding the same data would be roughly three times larger. On the device, the view decodes the string back into records. For each train on a pattern that serves the user's platform, it computes the arrival as: snapshot time + time to the train's next stop + scheduled run time from that stop to the user's station. Those run times would come from a table bundled with the app. If the version number in the payload doesn't match the bundled table, the view should say so rather than show wrong times. The hard part is the bundled schedule data. That data has to stay in sync with the server, including weekend and overnight service changes. Version it, and decide what the view shows when it meets a train pattern it doesn't recognize. Those kinds of updates can probably be left for when the app is launched by the user to start the activity anyways. Most of the real effort is in those last details: the record layout, the schedule data pipeline, and handling service changes. As they say "Some engineering might be required". Happy to compare notes on any of it if useful. S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
3w
Reply to Apple Watch Ultra 2: headingAccuracy near 89° even with correct heading; intermittent 180° reversal
I don't have much experience with this behavior on the watch. I just thought maybe the .maritime activity would be helpful. One additional thought about logging, if you use os_log() in your app to log as well, then the sysdiagnose will contain your app's logs along with the system logs, to give you and Apple (in case you file a bug report eventually) -- S.Beck -- 7strikes.dev --
Replies
Boosts
Views
Activity
Oct ’26
Reply to Apple Watch Ultra 2: headingAccuracy near 89° even with correct heading; intermittent 180° reversal
Can't say whether it is related, or whether it affects heading at all, but if you have not tried it, see if CLActivityType.maritime makes a difference. It is new in iOS 27 and watchOS 27, for "activities in vessels on water". Set it as the activityType of the same CLLocationManager you call startUpdatingHeading() on. A surfboard is a stretch for "vessel", but it is one line to find out. I have a note at 7strikes.dev that covers the usage pattern. On the "89": according to the headingAccuracy documentation, only a negative value marks a heading as invalid. A positive value is the possible error in degrees, so 89 means the system does not trust that heading much, even at the moments it happens to be right. As for things going back to normal on land, I would not look at GPS. You are reading magneticHeading, and the documentation only brings the device's location into it for trueHeading. What it does name as causes of a bad heading are calibration and strong local magnetic fields, so I would look for something near the watch in the water that is not there on land. Take it with a pinch of salt from someone who has never touched a surfboard and all his opinions come from watching the 100 ft Wave, but I wonder if there is a lag compared to the back and forth movements of the board or not. To see what the system actually did, install the Location logging profile from Profiles and Logs before you reproduce the problem, then take a sysdiagnose and read its log archive in Console. You might be able to understand the cause. -- S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
Oct ’26
Reply to CLLocationManager: request for a maritime/sailing CLActivityType (low-acceleration, continuous-motion use case)
Apparently, Apple has granted your wish :-) iOS 27 added an activity type for this: CLActivityType.maritime, with a matching CLLocationUpdate.LiveConfiguration.maritime for live updates. Apple's documentation describes it as positioning for "activities in vessels on water, including while anchored", and the comment in CLLocationUpdater.h says it is "tuned for vehicular maritime use cases". It came without a WWDC session or a release note, so it is easy to miss. Set .maritime on iOS 27 and later, and keep .otherNavigation for earlier systems: if #available(iOS 27, *) { manager.activityType = .maritime } else { manager.activityType = .otherNavigation } Since you are on Flutter: if your location plugin maps activity types to its own enum, it will not offer .maritime until it is updated, and you may need to set it from native code. I would retest your light-wind tracks with it before going further. If you still see the same behaviour, filing a Feedback with a sysdiagnose taken after installing the Location Services logging profile would be the next step. I have a note at 7strikes.dev that covers the live updates version as well. --- S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
Oct ’26
Reply to Bluetooth Low Energy (BLE) 5 Extended Advertising
Putting aside that a 2 second interval is ridiculously long for a reliable discovery, and while 100ms is much better, you should try to use the closest recommended interval in Section 58.5 instead for optimum discoverability-to-power performance, I am not sure if your issue is with advertising. You say the device can be detected/scanned, but the connection cannot be established which I would take to mean advertising and discovery has worked, but you are getting stuck at the connect phase. Sounds like you are issuing a scan and then a connect to the discovered CBPeripheral. If connection is failing, there would be an error. Maybe this is an iOS 18.6.2 issue, but you might be looking at the wrong place. What errors do you see (if any) after the connectPeripheral() is called? What do you see happening in the packet trace? -- S.Beck -- 7strikes.dev
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Oct ’26
Reply to AlarmKit: supported approach for a server-triggered wake-up alarm after an air-raid all-clear
I have not tested whether this can be done in an NSE. Various extensions have differing sandbox limitations on what functionality would be enabled. That's why I suggested to try it out yourselves as an experiment. While you won't be able to send actual push notifications using a free account, you can use a JSON file containing your payload in the simulator. This will still let you test your AlarmKit interaction inside the NSE, and at the very least let you know if this is a viable architecture to consider. You would create a JSON file as such: { "Simulator Target Bundle": "com.airraidapp.test", "aps": { "alert": { "title": "Airraid Update", "body": "Airraids are over" }, "mutable-content": 1 } } and call it airraid.apns (or whatever you want, just match your BundleID in the payload to your app) And then either drag and drop this onto the simulator to simulate a push, or use xcrun from a Terminal. xcrun simctl push --help shows the usage. Good luck! -- S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
Sep ’26
Reply to AlarmKit: supported approach for a server-triggered wake-up alarm after an air-raid all-clear
This would be possible to architect through a push architecture rather than trying to poll your server, but all that will depend on whether you can set/cancel/modify alarm kit alarms from within a Notification Service Extension. I would suggest to write a quick sample receiving push notifications in an NSE, and see if you can manipulate the main app's alarms from there. If that works, you can architect this by controlling the alarms by sending push notifications based on the air-raid status. -- S.Beck -- 7strikes.dev
Replies
Boosts
Views
Activity
Sep ’26
Reply to CoreBluetooth reconnection to legacy BLE directed advertisements differs across iPad hardware
Bluetooth Low Energy (BLE) compatibility concerns with A16-powered iPads are frequently reported. A review of these forums and broader online resources yields numerous references to such issues. But in your case the issue is stemming from your side. Apple explicitly says ADV_DIRECT_IND should not be used in Accessory Design Guidelines. Your implementation may have worked in many cases, but deviating from specifications and recommendations can cause such unforeseen issues. The technical reasoning for this limitation should be obvious. Apple devices roll their Bluetooth addresses periodically, and a directed advertising to an address that is no longer in use would have nowhere to go. While you can use the permanent identity address in the advertising for a paired device, that requires your accessory to decode the random address using the IRK, which I am guessing it no longer has in DFU mode. What Apple will say about this remains to be seen, but I would say "ADV_DIRECT_IND should not be used" is something they already have said. -- S.Beck -- 7strikes.dev
Topic: App & System Services SubTopic: Core OS Tags:
Replies
Boosts
Views
Activity
Sep ’26