feat: introduce reusable app lists for rules

- AppList @Model with launch migration from legacy inline selections
  (rules with identical selections share one list; idempotent)
- rules point at one app list; Block/Allow Only belongs to the rule and
  is offered only in the Schedule editor (limit kinds sanitize to Block)
- app-list picker sheet (select / create / edit / delete with in-use
  protection) replaces the per-rule selection sheet
- Hard Mode locks app-list editing while any hard rule is blocking
- SwiftData stability: relationships are only wired between managed
  models, and unit tests share one in-memory container (fresh context +
  data wipe per test) — per-test container creation trapped
  intermittently inside SwiftData
This commit is contained in:
2026-06-12 20:02:35 -04:00
parent 05d48a9f34
commit 93f2eceb3a
22 changed files with 880 additions and 138 deletions

View File

@@ -98,6 +98,15 @@ them): `newRuleButton`, `ruleCard-<name>`, `ruleStatus-<name>`,
`allowScreenTimeButton`, `permissionDeniedLabel`, `openSettingsButton`.
Gotchas learned the hard way:
- **SwiftData relationships**: never assign a relationship property (e.g.
`rule.appList`) inside a model's `init` or on un-inserted instances —
insert both models into a context first, then wire them.
- **SwiftData container churn**: repeatedly creating `ModelContainer`s for
this schema traps intermittently (EXC_BREAKPOINT inside SwiftData's
configuration setup), which Xcode shows as a test "hang" paused at a
breakpoint. Unit tests must go through `makeInMemoryContext()`
(TestSupport.swift): one shared container per process, fresh context +
data wipe per test.
- Identifiers on SwiftUI containers need `.accessibilityElement(children:
.combine)` (or a Button/control) to be queryable.
- List/Form section headers render uppercased unless `.textCase(nil)` —