refactor: model rule options as a per-kind sum type
Replace the wide BlockingRule/RuleDraft "god struct" — where every option existed for every kind — with a RuleConfiguration sum type that carries only the options each kind actually has: .schedule(ScheduleConfig: window + selectionMode + blockAdultContent) .timeLimit(TimeLimitConfig: dailyLimitMinutes) .openLimit(OpenLimitConfig: maxOpens) Name, days, Hard Mode, app list, and pause stay common to all kinds. This makes illegal states unrepresentable: Block / Allow Only and Block Adult Content are now structurally Schedule-only. User-visible behavior change: the Time Limit and Open Limit editors no longer offer a Block Adult Content toggle, and their detail sheets drop the "Adult websites" row — those never made sense for a usage budget. Limit rules are always Block and never engage the web-content filter. BlockingRule keeps flat columns as raw persistence behind a computed `configuration` bridge (lowest SwiftData risk; the cross-process RuleSnapshot wire format and the Screen Time extensions are untouched). Logic, the editors, and the detail sheet all switch on the sum type. Spec (§1, §3.5/§3.6, §5.2) updated first; tests reworked to the new API with new structural-guarantee unit tests and a UI test asserting the Time Limit editor/detail omit adult content. Full suite green (180 tests). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -54,7 +54,7 @@ struct RuleEnforcerTests {
|
||||
func skipsTimeLimitRules() {
|
||||
let shields = MockShieldController()
|
||||
let enforcer = RuleEnforcer(shields: shields)
|
||||
let rule = BlockingRule(name: "Time Keeper", kind: .timeLimit)
|
||||
let rule = BlockingRule(name: "Time Keeper", configuration: .timeLimit(TimeLimitConfig()))
|
||||
|
||||
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
||||
|
||||
@@ -104,7 +104,8 @@ struct RuleEnforcerTests {
|
||||
func forwardsSelectionMode() {
|
||||
let shields = MockShieldController()
|
||||
let enforcer = RuleEnforcer(shields: shields)
|
||||
let rule = BlockingRule(name: "Focus", selectionMode: .allowOnly)
|
||||
let rule = BlockingRule(
|
||||
name: "Focus", configuration: .schedule(ScheduleConfig(selectionMode: .allowOnly)))
|
||||
|
||||
enforcer.refresh(rules: [rule], at: mondayDuringWork, calendar: utc)
|
||||
|
||||
@@ -115,7 +116,8 @@ struct RuleEnforcerTests {
|
||||
func forwardsAdultContentFlag() {
|
||||
let shields = MockShieldController()
|
||||
let enforcer = RuleEnforcer(shields: shields)
|
||||
let filtered = BlockingRule(name: "Clean Mode", blockAdultContent: true)
|
||||
let filtered = BlockingRule(
|
||||
name: "Clean Mode", configuration: .schedule(ScheduleConfig(blockAdultContent: true)))
|
||||
let unfiltered = BlockingRule(name: "Plain")
|
||||
|
||||
enforcer.refresh(rules: [filtered, unfiltered], at: mondayDuringWork, calendar: utc)
|
||||
|
||||
Reference in New Issue
Block a user