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. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
45
Severed/Services/LaunchConfiguration.swift
Normal file
45
Severed/Services/LaunchConfiguration.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// LaunchConfiguration.swift
|
||||
// Severed
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Launch-argument configuration used by UI tests: in-memory storage, mocked
|
||||
/// Screen Time authorization, forced onboarding state, and seeded scenarios.
|
||||
struct LaunchConfiguration: Equatable {
|
||||
enum SeedScenario: String {
|
||||
/// One actively blocking rule ("Work Time") and one upcoming rule ("Sleep").
|
||||
case standard
|
||||
/// An actively blocking Hard Mode rule ("Locked In") plus an upcoming rule.
|
||||
case hardModeActive = "hard-mode-active"
|
||||
}
|
||||
|
||||
var isUITesting = false
|
||||
/// Forces the onboarding-completed flag at launch. Nil leaves stored state alone.
|
||||
var onboardingCompleted: Bool?
|
||||
var seedScenario: SeedScenario?
|
||||
|
||||
static let uiTestingFlag = "-ui-testing"
|
||||
static let onboardingCompletedFlag = "-onboarding-completed"
|
||||
static let onboardingRequiredFlag = "-onboarding-required"
|
||||
static let seedScenarioPrefix = "-seed-scenario="
|
||||
|
||||
static func parse(arguments: [String]) -> LaunchConfiguration {
|
||||
var config = LaunchConfiguration()
|
||||
config.isUITesting = arguments.contains(uiTestingFlag)
|
||||
if arguments.contains(onboardingCompletedFlag) {
|
||||
config.onboardingCompleted = true
|
||||
} else if arguments.contains(onboardingRequiredFlag) {
|
||||
config.onboardingCompleted = false
|
||||
}
|
||||
if let seedArgument = arguments.first(where: { $0.hasPrefix(seedScenarioPrefix) }) {
|
||||
config.seedScenario = SeedScenario(
|
||||
rawValue: String(seedArgument.dropFirst(seedScenarioPrefix.count))
|
||||
)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
static let current = parse(arguments: ProcessInfo.processInfo.arguments)
|
||||
}
|
||||
Reference in New Issue
Block a user