feat: clone Opal's rules feature with hard block enforcement

Rebuild the app around recurring screen-time blocking rules modeled on
Opal's My Apps tab:

- Onboarding with FamilyControls Screen Time authorization
- Apps home with Blocked Apps tiles and live rule cards
- New Rule sheet: Schedule/Time Limit/Open Limit types + preset gallery
- Rule editors: time windows (incl. overnight), day picker, app selection
  via FamilyActivityPicker (Block/Allow Only), rename, Hold to Commit
- Hard Mode: active hard rules cannot be edited, disabled, deleted, or
  unblocked until their window ends; soft rules pause until next window
- Shield enforcement through per-rule ManagedSettingsStore + RuleEnforcer
- 73 unit tests (Swift Testing) + 16 UI tests (XCUITest) with launch-arg
  harness for in-memory storage, mocked authorization, seeded scenarios
- docs/RULES_FEATURE_SPEC.md: spec derived from the reference recording

Known gap: background window transitions and time/open-limit thresholds
need a DeviceActivityMonitor extension; shields currently sync while the
app is running.
This commit is contained in:
2026-06-12 12:35:52 -04:00
parent c760aa696f
commit 3aac2004d2
45 changed files with 3749 additions and 206 deletions

View File

@@ -0,0 +1,31 @@
//
// OnboardingUITests.swift
// SeveredUITests
//
import XCTest
final class OnboardingUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
func testOnboardingWalksThroughPermissionToHome() throws {
let app = XCUIApplication.launchSevered(onboardingCompleted: false)
// Welcome step.
app.staticTexts["Severed"].waitToAppear()
app.buttons["onboardingContinueButton"].waitToAppear().tap()
// Permission step: granting (mocked) lands on the Apps home screen.
app.buttons["allowScreenTimeButton"].waitToAppear().tap()
app.buttons["newRuleButton"].waitToAppear()
XCTAssertTrue(app.staticTexts["Blocked Apps"].exists)
}
func testCompletedOnboardingIsSkipped() throws {
let app = XCUIApplication.launchSevered(onboardingCompleted: true)
app.buttons["newRuleButton"].waitToAppear()
XCTAssertFalse(app.buttons["onboardingContinueButton"].exists)
}
}