feat: enforce time and open limits in the background via Screen Time extensions
- Shared/ layer compiled into the app and three new extension targets: rule snapshots in the app group, the usage ledger, monitoring-plan naming, and LimitEnforcement (shared, unit-tested event reactions) - RuleScheduler mirrors rules to the app group and reconciles DeviceActivity monitoring: one daily 00:00-23:59 activity per limit rule, with a cumulative usage-threshold event per budget minute for time limits; activities restart only when their configuration changes (a restart resets threshold accounting) - OpenAppLockMonitor (DeviceActivityMonitor): midnight budget resets, records usage minutes, shields at the budget, re-shields when a granted open session ends - OpenAppLockShieldConfig: open-limit shields show 'Opened X of N times today' with an 'Open (Y left)' secondary button - OpenAppLockShieldAction: an Open press spends one open, lifts the rule's shield, and starts the ~15-minute one-shot session - extensions are classic NSExtension app extensions (the ExtensionKit product type expects an @main entry and made the app fail to install); shield-store tracking moved to app-group defaults so the app and extensions see one consistent set - maps iOS 26's new .approvedWithDataAccess authorization status to approved (it previously fell through @unknown default to notDetermined) - shares the OpenAppLock scheme (Xcode dropped the autocreated one when targets were added)
This commit is contained in:
52
AGENTS.md
52
AGENTS.md
@@ -10,18 +10,32 @@ feature set is a clone of Opal's "Rules"; the presentation is bare native iOS
|
||||
|
||||
```
|
||||
OpenAppLock/ App target (iOS 26, SwiftUI + SwiftData)
|
||||
Models/ BlockingRule (@Model), RuleDraft, RuleKind,
|
||||
Weekday, RulePreset
|
||||
Models/ BlockingRule + AppList (@Model), RuleDraft,
|
||||
RulePreset
|
||||
Logic/ Pure, heavily unit-tested:
|
||||
RuleSchedule (window math, incl. overnight),
|
||||
RuleStatus (derived status + labels),
|
||||
RulePolicy (Hard Mode gating, unblock/pause)
|
||||
RuleStatus (derived status + labels, usage-aware),
|
||||
RulePolicy (Hard Mode gating, unblock/pause,
|
||||
app-list lock), UsageDisplay (Usage-section text)
|
||||
Services/ ScreenTimeAuthorization (FamilyControls behind a
|
||||
protocol + mock), ShieldController (ManagedSettings
|
||||
shields + adult-content web filter + mock),
|
||||
RuleEnforcer (active rules → shields),
|
||||
LaunchConfiguration + SampleRules (UI-test harness)
|
||||
protocol + mock), RuleEnforcer (rules → shields),
|
||||
RuleScheduler (rules → DeviceActivity monitoring),
|
||||
AppListMigration, LaunchConfiguration +
|
||||
SampleRules (UI-test harness)
|
||||
Views/ Native SwiftUI screens (see docs spec §6)
|
||||
Shared/ Compiled into the app AND all three extensions:
|
||||
RuleKind, Weekday, RuleSchedule, AppGroup,
|
||||
UsageLedger (per-day minutes/opens),
|
||||
RuleSnapshot(+Store) (rule mirror in the app
|
||||
group), MonitoringPlan (activity/event naming),
|
||||
LimitEnforcement (shared event reactions),
|
||||
ShieldController, ShieldLookup
|
||||
OpenAppLockMonitor/ DeviceActivityMonitor extension: midnight resets,
|
||||
usage-minute checkpoints → shield at the limit,
|
||||
open-session expiry
|
||||
OpenAppLockShieldConfig/ ShieldConfiguration extension: "Opened X of N" +
|
||||
Open button on open-limit shields
|
||||
OpenAppLockShieldAction/ ShieldAction extension: Open press spends an open,
|
||||
lifts the shield, starts the ~15-min session
|
||||
OpenAppLockTests/ Swift Testing unit suites (@MainActor — the app
|
||||
target defaults to MainActor isolation)
|
||||
OpenAppLockUITests/ XCUITest flows (see harness below)
|
||||
@@ -119,12 +133,16 @@ Gotchas learned the hard way:
|
||||
|
||||
## Known gaps / next steps
|
||||
|
||||
- **No DeviceActivityMonitor extension yet**: shields apply/clear only while
|
||||
the app runs (launch, foreground 30s loop). Background window transitions,
|
||||
and any real enforcement for Time Limit / Open Limit rules (usage
|
||||
thresholds), require adding the extension target + app group.
|
||||
- Time Limit / Open Limit rules are fully modeled, editable, and displayed,
|
||||
but not enforced (see above).
|
||||
- **On-device verification of limit enforcement is pending.** The
|
||||
DeviceActivity monitor + shield extensions and the app group are in place,
|
||||
but real blocking/usage tracking is only observable on a device (the
|
||||
simulator neither tracks usage nor renders custom shields). Verify: time
|
||||
limits accrue in the Usage section and block at the budget; open-limit
|
||||
apps shield immediately with an "Open (N left)" button; an open lasts
|
||||
~15 minutes (DeviceActivity's minimum interval) before re-shielding.
|
||||
- **Schedule-rule background transitions** still rely on the app running
|
||||
(launch / foreground 30s loop); schedule rules have no DeviceActivity
|
||||
monitoring yet — only limit rules do.
|
||||
- `FamilyActivityPicker` shows few apps on the simulator; fine on device.
|
||||
- `FamilyActivityPicker` **silently ignores selections** (binding never
|
||||
updates, rows still show checkmarks) unless real FamilyControls
|
||||
@@ -134,4 +152,6 @@ Gotchas learned the hard way:
|
||||
`-ui-testing`, complete onboarding, and approve the system Screen Time
|
||||
prompts ("Allow with Passcode" works on the simulator).
|
||||
- Distribution (App Store) requires Apple's approval for the Family Controls
|
||||
entitlement; development builds work with the dev entitlement.
|
||||
entitlement **for the app and each extension bundle ID**
|
||||
(`dev.bchen.OpenAppLock`, `.Monitor`, `.ShieldConfig`, `.ShieldAction`);
|
||||
development builds work with the dev entitlement.
|
||||
|
||||
Reference in New Issue
Block a user