# OpenAppLock — Agent Guide OpenAppLock is an iOS Screen Time app: recurring **rules** that block selected apps (Schedule windows, Time Limits, Open Limits), with a **Hard Mode** that makes an active block impossible to lift, edit, or delete until it ends. The presentation is bare native iOS (List/Form/NavigationStack, default color scheme). ## Repo layout ``` OpenAppLock/ App target (iOS 26, SwiftUI + SwiftData) Models/ BlockingRule + AppList (@Model), RuleDraft, RulePreset Logic/ Pure, heavily unit-tested: RuleStatus (derived status + labels, usage-aware), RulePolicy (Hard Mode gating, unblock/pause, app-list lock), UsageDisplay (Usage-section text) Services/ ScreenTimeAuthorization (FamilyControls behind a protocol + mock), RuleEnforcer (rules → shields), RuleScheduler (rules → DeviceActivity monitoring), AppListMigration, LaunchConfiguration + SampleRules (UI-test harness) Views/ Native SwiftUI screens (see docs spec §6) Shared/ Compiled into the app AND all three extensions: RuleKind, Weekday, RuleSchedule, AppGroup, UsageLedger (per-day minutes/opens), RuleSnapshot(+Store) (rule mirror in the app group), MonitoringPlan (activity/event naming), LimitEnforcement (shared event reactions), ShieldController, ShieldLookup OpenAppLockMonitor/ DeviceActivityMonitor extension: midnight resets, usage-minute checkpoints → shield at the limit, open-session expiry OpenAppLockShieldConfig/ ShieldConfiguration extension: "Opened X of N" + Open button on open-limit shields OpenAppLockShieldAction/ ShieldAction extension: Open press spends an open, lifts the shield, starts the ~15-min session OpenAppLockTests/ Swift Testing unit suites (@MainActor — the app target defaults to MainActor isolation) OpenAppLockUITests/ XCUITest flows (see harness below) docs/RULES_FEATURE_SPEC.md Feature spec for the rules behavior; §6 maps it to the native presentation. Review/update this BEFORE behavior changes. docs/SWIFT_GUIDELINES.md Swift coding/testing/patterns/security standards agents must follow on this project. ``` ## Domain facts worth knowing - Times are stored as **minutes from midnight**; `end <= start` means the window crosses midnight (e.g. 22:00→06:00) and belongs to the day it *starts* on. `start == end` = 24h window. - Status is always **derived** (`rule.status(at:calendar:)`), never stored: `disabled / dormant / active(until:) / paused(until:) / upcoming(startsAt:)`. Countdown labels round hours **up** (e.g. "6h left"). - **Hard Mode**: `RulePolicy` is the single gate — while a hard-mode rule is actively blocking, canEdit/canDisable/canDelete/canUnblock are all false. Soft rules can be "unblocked", which sets `pausedUntil` = window end (the rule re-arms at its next window). - Shields: one `ManagedSettingsStore` per rule (`rule-`), tracked in UserDefaults for stray cleanup. `blockAdultContent` engages `webContent.blockedByFilter = .auto()` alongside the shield. - `RuleEnforcer.refresh` is the only place shields change; the home view runs it on rule changes and a 30s loop while visible. ## Build & test - Open `OpenAppLock.xcodeproj` in Xcode; build/test through the **Xcode MCP** tools (`BuildProject`, `RunAllTests`, `RunSomeTests` — get the tab id from `XcodeListWindows`). Make sure the scheme destination is an iOS **simulator**; a physical-device destination makes test runs hang or get cancelled. - The project uses Xcode file-system-synchronized groups: adding/removing `.swift` files on disk is enough, no pbxproj editing. - Family Controls entitlement is configured (`OpenAppLock/OpenAppLock.entitlements`). FamilyControls/ManagedSettings compile and run on the simulator, but real blocking behavior is only observable on a device. ## Workflow expectations (user preference) These three are non-negotiable defaults — follow them on every task, not only when reminded: - **Always plan before execution.** Think through and lay out the approach (a written plan / plan mode for anything non-trivial) and confirm scope before editing code. Do not start changing files until the plan is clear. - **Always use red-green TDD.** Update `docs/RULES_FEATURE_SPEC.md` first for behavior changes, write the failing test, run it (compile failure counts as red), implement, re-run focused tests, then the full suite. Run tests often and fail fast. - **Always attempt to validate the UI manually before committing.** Build and run the app (simulator/device) and visually confirm the change behaves as intended. This step **may be skipped only when such tooling is unavailable** (e.g. the Xcode MCP / a simulator is not reachable in the session) — in that case, say so explicitly and hand the verification back to the user rather than silently skipping it. - Conventional commits (`feat:`, `fix:`, `refactor:` …). **Agent attribution is required**: every commit an agent authors or co-authors must end with a `Co-Authored-By:` trailer naming the specific agent/model that did the work, added manually in the commit message — e.g. `Co-Authored-By: Claude Opus 4.8 (1M context) ` (a non-Claude agent uses its own name/email). Commit only when the user asks. ## UI-test harness `OpenAppLockApp` reads launch arguments (parsed by `LaunchConfiguration`): | Argument | Effect | |---|---| | `-ui-testing` | In-memory SwiftData store, mock authorization, mock shields | | `-onboarding-completed` / `-onboarding-required` | Force the onboarding flag | | `-seed-scenario=standard` | Active soft rule "Work Time" + upcoming "Sleep" | | `-seed-scenario=hard-mode-active` | Active Hard Mode rule "Locked In" + upcoming "Sleep" | Use `XCUIApplication.launchOpenAppLock(...)` (UITestSupport.swift), which also provides `app.element(_:)` for identifier lookup across element types and `waitToAppear()`. Key accessibility identifiers (keep stable — tests and future work rely on them): `newRuleButton`, `ruleCard-`, `ruleStatus-`, `blockedTile-`, `nothingBlockedLabel`, `emptyRulesCard`, `closeNewRuleButton`, `ruleKind-`, `preset-`, `ruleEditorTitle`, `fromTimePicker`/`toTimePicker`, `dayToggle-1…7`, `selectedAppsRow`, `hardModeToggle`, `adultContentToggle`, `dailyLimitStepper(+Value)`, `maxOpensStepper(+Value)`, `commitRuleButton`, `doneButton`, `toggleEnabledButton`, `deleteRuleButton`, `closeDetailButton`, `detailRuleName`, `detailStatusLabel`, `detailRow-