feat: track limit budgets in a usage ledger and surface them in a Usage section

- RuleUsage + UsageLedger: per-rule, per-day minutes/opens in app-group
  defaults (monotonic minutes, incrementing opens, midnight rollover by
  day-keying); MockUsageLedger for tests and seeded UI scenarios
- usage-aware status: a limit rule whose daily budget is spent on an
  enabled day is active (blocking) until next midnight; Hard Mode gating
  and app-list locking honor it; soft unblock pauses until midnight
- RuleEnforcer shields spent limit rules (always Block mode) while the
  app runs
- new Usage section under Blocked Apps: '18m of 45m used today · 27m
  left', '2 of 5 opens today · 3 opens left', 'Blocked until tomorrow'
- new 'limits' seed scenario + UI tests
This commit is contained in:
2026-06-12 20:22:48 -04:00
parent cb9b2e8950
commit 8e3b8b70f5
14 changed files with 640 additions and 62 deletions

View File

@@ -14,6 +14,7 @@ struct RuleDetailSheet: View {
@Environment(\.dismiss) private var dismiss
@Environment(\.modelContext) private var modelContext
@Environment(RuleEnforcer.self) private var enforcer
@State private var isEditing = false
@State private var pendingDeletion = false
@@ -50,13 +51,14 @@ struct RuleDetailSheet: View {
}
private func detailList(now: Date) -> some View {
let status = rule.status(at: now)
let usage = enforcer.usage(for: rule, at: now)
let status = rule.status(at: now, usage: usage)
return List {
Section {
detailRows
}
Section {
if RulePolicy.canEdit(rule, at: now) {
if RulePolicy.canEdit(rule, usage: usage, at: now) {
Button {
isEditing = true
} label: {