# OpenAppLock — "Rules" Feature Spec (derived from Opal screen recording) Source: `ScreenRecording_06-12-2026 11-26-19_1.MP4` (iPhone, 4:29). The rules feature appears roughly between 1:00–3:00 of the recording, on Opal's "My Apps" tab. This spec describes what was observed, then maps it onto an implementation plan for OpenAppLock. --- ## 1. Concept A **Rule** is a recurring, automated app-blocking policy. Unlike a one-off block/timer session, a rule re-arms itself on a schedule. Three rule types are offered (Opal's "New Rule" sheet): | Type | Icon | Example shown | Semantics | |------|------|---------------|-----------| | **Schedule** | calendar grid | "e.g. 9-5, Daily" | Block selected apps during a daily time window on chosen days | | **Time Limit** | hourglass | "e.g. 45m/day" | After N minutes of cumulative use of selected apps per day, block them until a reset point | | **Open Limit** | padlock | "e.g. 5 opens/day" | After N opens of selected apps per day, block them (not demoed in video; inferred from card) | **Common attributes** — present for *every* rule kind: - **Name** — user-editable, free text (presets: "Work Time", "Weekend Zen", "Locked In", "Sleep", "Wind Down", "Deep Sleep", "Laser Focus", "Reading Time", "Gym Time"; defaults for new rules: "In the Zone" (schedule), "Time Keeper" (time limit)) - **Days of week** — 7-day toggle set, summarized as "Weekdays" / "Weekends" / "Every day" / custom - **App List** *(OpenAppLock refinement)* — each rule points to exactly one **App List**: a named, reusable selection of apps/categories/websites stored independently of any rule. When editing a rule the user picks an existing list or creates a new one; editing a list affects every rule that uses it. Deleting a list detaches it from its rules (they fall back to "no apps"). - **Hard Mode** — boolean, PRO-gated in Opal; subtitle "No unblocks allowed". When off, the rule detail shows "Unblocks allowed: Yes" - **Enabled/disabled** — a rule can be disabled without deleting ("Disable Rule") **Per-kind options** — each kind carries only the options that make sense for it. The data model expresses this as a sum type (`RuleConfiguration`, see §5.2) so a kind structurally cannot hold another kind's options: - **Schedule** — a recurring **time window** (`From`/`To`, may cross midnight), a **Selection mode** (**Block** the list, or **Allow Only** = block everything except it; the mode belongs to the *rule*, not the list), and **Block Adult Content** (engage Screen Time's adult-website filter while the window is active). - **Time Limit** — a **daily minutes budget**. - **Open Limit** — a **daily opens budget**. Selection mode and Block Adult Content are **Schedule-only**: a usage budget over "everything except X" is not meaningful, and engaging a web-content filter when a *usage* budget is spent does not fit the feature. Time Limit and Open Limit rules are always Block and never touch the adult-content filter. Derived status (drives card/detail UI): - **Active** → countdown to window end: green pill "6h left" - **Inactive** → countdown to next activation: "Starts in 22h" / "Starts in 11h" --- ## 2. Screen inventory & navigation map ``` Tab bar: [Home] [My Apps] [Timer] │ ▼ Apps screen (large title "Apps") ├── "Blocked Apps" section ├── "Rules >" section ──ta p "+ New"──▶ New Rule sheet │ │ ├── tap rule-type card ─▶ Rule Editor (blank/default) │ │ └── tap preset card ───▶ Rule Editor (pre-filled) │ └── tap rule card ─▶ Rule Detail sheet │ └── "Edit Rule" ─▶ Rule Editor (edit mode) │ └── "Selected Apps >" ─▶ App Picker └── "Apps" section (folders: Distracting / Always Allowed / Never Allowed) ``` All rules UI is presented as **sheets stacked over the Apps screen** (dimmed, blurred background; grabber at top; circular ✕ or ‹ button top-left). Nothing navigates by push except within the sheet stack. --- ## 3. Screens in detail ### 3.1 Apps screen ("My Apps" tab) Dark theme throughout (near-black background, very dark green tint). 1. **Large title** "Apps". 2. **Blocked Apps** — section header; horizontal row of currently-blocked app icons. Each icon has a lock badge overlay and a teal/green rounded-rect outline; caption "Unblock" under the icon. Tapping unblocks (with friction if hard mode — not demoed). *(OpenAppLock)* Time/Open Limit rules whose budget is spent for the day also appear here, blocked until midnight. 3. **Usage** *(OpenAppLock addition — not in the Opal video)* — a section directly below Blocked Apps showing live tracking for every enabled Time/Open Limit rule scheduled today: - Time Limit row: subtitle "18m of 45m used today", trailing "27m left"; when spent: "Blocked until tomorrow" (red). - Open Limit row: subtitle "2 of 5 opens today", trailing "3 opens left"; when spent: "Blocked until tomorrow" (red). Usage numbers come from the shared app-group ledger written by the DeviceActivity monitor and shield-action extensions. 3. **Rules** — header row: "Rules ›" (leading, tappable to a full list, presumably) and "**+ New**" (trailing, green tint) which opens the New Rule sheet. - Content: horizontally scrolling row of **rule cards** (~2 visible). - **Rule card** anatomy (rounded ~24pt corners): - Top: icon pair — rule-type icon (calendar) → small arrow → shield icon. Active rule: icons in color, card tinted dark green. Inactive: greyscale. - Middle: status — active: green capsule pill "6h left"; inactive: plain text "Starts in 22h". - Bottom: rule **name** (semibold), then a sub-row "Block" + tiny cluster of the blocked app icons. 4. **Apps** — section of folder-style groups: "Distracting (4 items)" showing a 2×2 mini icon grid, "Always Allowed", "Never Allowed (Hidden)" with an eye-slash glyph. (Out of scope for the rules clone but shares the app selection model.) ### 3.2 Rule Detail sheet Presented on tapping a rule card. Partial-height card sheet. - Top-left: circular ✕ close button. - Centered: icon pair (rule type → shield), then caption "`Schedule, Starts in 22h`" (type + live status), then large title ("Weekend Zen"). - **Detail rows** (single inset rounded card, label left / value right): | Label | Example value | |---|---| | During this time | `09:00 – 12:00` | | On these days | `Weekends` | | Block | `[app icons] 1 App` / `3 Apps` | | Unblocks allowed | `Yes` (hidden/`No` when Hard Mode) | - Bottom: full-width white pill button "**✎ Edit Rule**" → morphs the sheet into the Rule Editor in edit mode. ### 3.3 New Rule sheet Presented from "+ New". Full-height sheet, scrollable. - Header: ✕ left, centered title "**New Rule**". - **Rule type row** — 3 horizontally arranged cards (Schedule / Time Limit / Open Limit), each: glyph, bold name, example caption ("e.g. 9-5, Daily", "e.g. 45m/day", "e.g. 5 opens/day"). Tapping opens the matching editor with defaults. - **Preset gallery** — vertically scrolling sections, each with a bold header + grey subtitle, containing a 2-up grid of photo-backed preset cards: - **Get More Done** — "Maximize your productivity while staying sane." - Work Time (Schedule 09:00–17:00, Block, weekdays) - Laser Focus (Schedule 14:00–18:00, Block) - **Sleep, Relax and Reset** — "Sleep better, rise refreshed." - Wind Down (Schedule 20:00–22:00, Block) - Deep Sleep (Schedule 22:00–06:00, Block) - **Build Healthy Habits** — "Spend time on important things." - Reading Time (Schedule ~19:45–20:45, Block) - Gym Time (Schedule 17:30–18:30, Block) - **Preset card** anatomy: full-bleed background photo, top row icon pair (type → shield), time range caption, name, "Block" + suggested app icons, and a circular "+" button bottom-right. Tapping anywhere opens the Schedule editor pre-filled with the preset's name/times/days. > **Navigation (OpenAppLock):** picking a rule type or preset **pushes** the > editor inside the sheet via native SwiftUI navigation (`NavigationStack` + > `navigationDestination(item:)`), so the system push animation and > edge-swipe-back work; the editor keeps its custom header chrome. ### 3.4 Rule Editor — Schedule type Sheet with: ‹ back (top-left), centered **rule name** as title, ✎ pencil button (top-right) to rename. Sections (each an inset rounded group with a small icon + caption header): 1. **📅 During this time** - Rows `From` / `To` with right-aligned time + stepper chevrons (`09:00 ⌃⌄`). - A dotted vertical line with ●/○ endpoints visually links From → To. - Tapping a row expands an inline wheel time picker (24h, demoed changing From 22:00 → 23:00). 2. **On these days:** — trailing summary label ("Weekdays"/"Weekends"/custom); row of 7 circular toggles `S M T W T F S`; selected = filled white circle with black letter, unselected = dark circle. 3. **🛡 Apps are blocked** - Row: `App List` → ` · N Apps ›` (or `Choose ›` when none) — presents the App List picker. - A segmented `Block | Allow Only` row (Schedule editor only) chooses how the rule interprets its list; the section header reads "Apps are blocked" / "Only these apps are allowed" accordingly. 4. **Hard Mode** `⚡PRO` badge — subtitle "No unblocks allowed"; trailing toggle. 5. **Block Adult Content** *(OpenAppLock addition — not in the Opal video; **Schedule rules only**)* — subtitle "Filter adult websites while this rule is active"; trailing toggle. Maps to Screen Time's web-content filter (`ManagedSettingsStore.webContent.blockedByFilter = .auto(...)`), applied and cleared together with the rule's shield. Surfaces in the rule detail as an "Adult websites | Blocked/Allowed" row. Time Limit and Open Limit editors do **not** offer this toggle (see §1, Per-kind options). 6. **CTA** - Creating: full-width gradient pill "**Hold to Commit**" — a press-and-hold interaction (deliberate friction) that fills, then saves and dismisses to the Apps screen where the new card appears. - Editing existing: "**✓ Done**" pill, plus a red text button "**⏸ Disable Rule**" beneath it. ### 3.5 Rule Editor — Time Limit type ("Time Keeper") Same chrome (back / title / rename). Sections: 1. **⏳ When I use** — row `This App` → `Select ›` (app selection). 2. **For this long** — subtitle "Daily"; right-aligned value with stepper `45m ⌃⌄`. 3. **On these days:** — identical day picker as Schedule. 4. **🛡 Then block app** — row `Until` with stepper value `Tomorrow ⌃⌄` (reset point — e.g. tomorrow/next morning). 5. **Hard Mode** toggle — same as Schedule. 6. **Hold to Commit**. *(No Block Adult Content toggle — Schedule-only.)* ### 3.6 Rule Editor — Open Limit type Not demoed beyond its card. Spec by analogy: "When I open [apps]" / "More than `N opens ⌃⌄` (Daily)" / day picker / "Then block until …" / Hard Mode / Hold to Commit. *(No Block Adult Content toggle — Schedule-only.)* ### 3.7 App Picker (shared component — also used in onboarding & timer) Full-height sheet: - Header: ‹ back, centered title "**Selected**", and a circular green **✓** confirm button top-right. - **Segmented control**: `Block` | `Allow Only`. - Top rows: "**+ Add App or Website**", a "Suggested" horizontal row of app icons (one-tap add), and a "Never Allowed — 0 Apps" row with footnote "Never allowed Apps will also be blocked". - Hint text: *Select apps/websites, tap ">" to expand*. - **Category list** — each row: circular checkbox (tri-state: empty / partially-selected count / checked), emoji glyph, category name, trailing selected-count + chevron to expand into individual apps: `All Apps & Categories, Social, Games, Entertainment, Creativity, Education, Health & Fitness, Information & Reading, Productivity & Finance, Shopping & Food`. - **Search bar** pinned near bottom (with mic). Typing surfaces app matches and website suggestions (e.g. typing "insta" offers `instagram.com`), letting users add arbitrary domains. - Footer: "**N Apps Selected**" caption + white pill "**Save**" (+ "Cancel"). > Implementation note: Opal ships its own app categorization. On iOS, third > parties cannot enumerate installed apps; the system-sanctioned route is > `FamilyActivityPicker` (FamilyControls), which provides its own > category/app/website UI and returns opaque tokens. **v1 of OpenAppLock should > embed `FamilyActivityPicker`** instead of cloning Opal's custom picker. > > **App Lists (OpenAppLock):** the selection itself lives on a reusable > **App List** (`@Model AppList`: name + encoded `FamilyActivitySelection`). > The editor's App List row presents a picker sheet listing saved lists > (checkmark on the rule's current list; tap to select), an Edit affordance > per list, and a "New List" flow — a name field plus an embedded > `FamilyActivityPicker`. The `Block`/`Allow Only` segmented control lives in > the Schedule rule editor (it is rule state, not list state). Legacy rules > that stored an inline selection are migrated at launch: one list per > distinct selection (rules sharing identical selection data share a list), > named " Apps". Lists in use by a rule cannot be deleted from the > picker. While any **Hard Mode** rule is actively blocking, all lists are > read-only — the picker hides Edit/Delete and shows a lock notice — because > editing a list would be a back door out of the hard block. Creating new > lists and selecting lists for other rules remain available. --- ## 4. Behavioral spec 1. **Activation** — a Schedule rule becomes active at `From` on an enabled day and deactivates at `To` (windows crossing midnight, e.g. 22:00–06:00, must be supported — Deep Sleep preset does this). 2. **While active** — the rule's app selection is shielded (and, when the rule's Block Adult Content toggle is on, Screen Time's adult-website filter is engaged for the same span); blocked apps also surface in the "Blocked Apps" row on the Apps screen; the card turns green with a "Xh left" pill. 3. **Unblocking** — with Hard Mode off, the user may unblock mid-window ("Unblocks allowed: Yes"). With Hard Mode on, no unblocks until the window ends. 4. **Time-limit rules** — accumulate usage daily across the selected apps; on crossing the threshold, shield until the `Until` reset point (e.g. tomorrow), then reset the budget. *(OpenAppLock specifics)*: usage lives in a per-rule, per-day **usage ledger** in the app group. A limit rule's derived status becomes `active(until: next midnight)` once the ledger reports the budget spent on an enabled day — it then surfaces in Blocked Apps, Hard Mode gating applies, and a soft unblock pauses it until midnight. Open-limit rules work the same with an opens budget; while opens remain, their apps stay shielded with an "Open" button on the shield (each press spends one open and lifts the shield for up to 15 minutes — the DeviceActivity minimum interval). Because the shield is what *counts* opens, an enabled open-limit rule scheduled today is shielded **proactively from the start of the day, even before any opens are spent** — by *both* the background (`LimitEnforcement.handleDayStart`) and the foreground (`RuleEnforcer.refresh`) paths, so a freshly created open-limit rule gates its apps immediately and the gate survives the app being foregrounded. The one exception is a **granted "Open" session**: pressing Open lifts the shield for ~15 minutes, recorded as an expiry in the shared `OpenSessionStore`; while that session is live, neither path re-shields the rule (so the sanctioned session is never cut short), and the monitor re-shields when the session's one-shot activity ends. Unlike a *spent* budget, this proactive gate does **not** put the rule in "Blocked Apps" (which lists only rules whose budget is exhausted); it shows under "Usage" with its remaining opens. 5. **Disable vs delete** — "Disable Rule" pauses scheduling but keeps the rule (card presumably shows disabled state). No delete flow was shown; add delete via swipe/long-press or a button in the editor. 6. **Commit friction** — creating/committing a rule uses press-and-hold ("Hold to Commit"), echoing Opal's philosophy that *starting* a commitment should be deliberate. Editing uses a plain "Done". 7. **Live countdowns** — "Starts in 22h" / "6h left" update over time (minute granularity is fine). 8. **Overlapping rules — strictest enforcement wins.** When several rules target the same app, the app is blocked if **any** of them is currently blocking it; rules never cancel each other out. This is structural rather than a resolved decision: each rule owns its own `ManagedSettingsStore` (`rule-`), Screen Time **unions** shields across all stores, and a rule only ever writes/clears *its own* store. Consequences: - An open-limit and a time-limit rule on the same app each block via their own store, so whichever's budget is spent **first** blocks the app, regardless of the other's remaining budget. - An **Allow-Only** schedule cannot punch a hole for an app that another rule blocks: `.all(except:)` is itself a *shield* directive ("block everything except these"), not a whitelist that lifts other stores' shields. So if a schedule "allows" an app but a time limit blocks it, the time-limit block stands. - A soft **unblock** pauses only the one rule it was invoked on; other rules blocking the same app keep it blocked. There is deliberately **no** central merge of selections into a single shield set — such a merge would be the only place a block could be accidentally dropped. --- ## 5. Implementation plan for OpenAppLock ### 5.1 Frameworks & capabilities - **FamilyControls** — `AuthorizationCenter.shared.requestAuthorization(for: .individual)`; `FamilyActivityPicker` + `FamilyActivitySelection` (app/category/web tokens). - **ManagedSettings** — `ManagedSettingsStore` per rule (`ManagedSettingsStore.Name("rule-")`); set `store.shield.applications` / `applicationCategories` / `webDomains`. - **DeviceActivity** — `DeviceActivityCenter.startMonitoring` with a `DeviceActivitySchedule(intervalStart:intervalEnd:repeats:)` per rule; a **DeviceActivityMonitor app extension** applies/removes shields in `intervalDidStart`/`intervalDidEnd`. Time-limit rules use `DeviceActivityEvent(applications:threshold:)` + `eventDidReachThreshold`. - Requires the **Family Controls entitlement** (works in dev; distribution needs Apple approval) and an App Group to share rule data with the monitor extension. ### 5.2 Data model (SwiftData) The domain currency is a **sum type** so a rule can only hold the options that belong to its kind — illegal states (e.g. an Open Limit rule with a time window, or a Time Limit rule with Block Adult Content) are unrepresentable: ```swift enum RuleKind: String, Codable { case schedule, timeLimit, openLimit } enum SelectionMode: String, Codable { case block, allowOnly } enum RuleConfiguration: Hashable, Sendable { case schedule(ScheduleConfig) case timeLimit(TimeLimitConfig) case openLimit(OpenLimitConfig) var kind: RuleKind { … } } struct ScheduleConfig: Hashable, Sendable { // Schedule-only options var startMinutes: Int // minutes from midnight, e.g. 540 = 09:00 var endMinutes: Int // may be ≤ start (crosses midnight) var selectionMode: SelectionMode var blockAdultContent: Bool // webContent.blockedByFilter = .auto(...) } struct TimeLimitConfig: Hashable, Sendable { var dailyLimitMinutes: Int } struct OpenLimitConfig: Hashable, Sendable { var maxOpens: Int } ``` The kind-common attributes (`name`, `days`, `hardMode`, `isEnabled`, `appList`, `pausedUntil`) live alongside the configuration: ```swift @Model final class BlockingRule { var id: UUID var name: String var isEnabled: Bool var hardMode: Bool var appList: AppList? var days: [Int] // 1...7, Calendar weekday numbers var pausedUntil: Date? var createdAt: Date // The kind-specific options, exposed as a computed bridge over the // model's raw stored columns: var configuration: RuleConfiguration { get set } var kind: RuleKind { configuration.kind } } ``` `RuleDraft` (the editors' value-type working copy) carries the same `configuration` + common fields, so each editor only renders its kind's options. `BlockingRule` persists the configuration as flat columns and the cross-process `RuleSnapshot` mirror keeps its flat wire shape; both are read/written exclusively through the sum type. `FamilyActivitySelection` is `Codable` → stored on the `AppList` as `Data`. Status ("active", "starts in Xh", "Xh left") is **derived**, not stored. ### 5.3 View inventory | View | Notes | |---|---| | `AppsView` (tab) | Sections: Blocked Apps, Rules carousel, (later) app folders | | `RuleCardView` | Card per §3.1, active/inactive styling | | `RuleDetailSheet` | §3.2, rows + Edit Rule | | `NewRuleSheet` | §3.3, type cards + preset gallery (`RulePreset` static data) | | `ScheduleRuleEditor` | §3.4 | | `TimeLimitRuleEditor` | §3.5 | | `DayOfWeekPicker` | 7 circle toggles + summary ("Weekdays"/"Weekends"/…) | | `AppSelectionView` | wraps `FamilyActivityPicker`, Block/Allow Only segmented control | | `HoldToCommitButton` | long-press progress fill, haptics, fires on completion | | `RuleScheduler` (service) | translates `BlockingRule` ⇄ DeviceActivity monitoring | | `ShieldController` (service) | applies/clears `ManagedSettingsStore` shields | ### 5.4 Suggested build order 1. Data model + Apps tab with Rules section (cards from seeded sample rules, status derivation, detail sheet) — pure UI, no entitlements needed. 2. New Rule sheet + Schedule editor + day picker + Hold to Commit (CRUD into SwiftData; Disable/Done editing path). 3. FamilyControls authorization + `FamilyActivityPicker` integration ("Selected Apps" row, "N Apps" counts, icon clusters via `Label(token:)`). 4. DeviceActivity monitor extension + ManagedSettings shields (real blocking, incl. midnight-crossing windows). 5. Time Limit editor + threshold events; Open Limit last (needs shield action extension + open counting). 6. Preset gallery content + polish (gradients, photos, haptics, live countdown timers). ### 5.5 Background enforcement architecture (implemented) - **App group** `group.dev.bchen.OpenAppLock` shares four stores between the app and its extensions: `RuleSnapshotStore` (Codable rule mirror, written by `RuleScheduler` on every enforcement refresh), `UsageLedger` (per-rule, per-day minutes/opens), `OpenSessionStore` (per-rule expiry of a granted "Open" session), and the shield-store tracking list. - **`RuleScheduler` (app)** reconciles DeviceActivity monitoring with the enabled rules: - **Limit rules** — one repeating 00:00–23:59 activity per rule (`rule-`); time-limit rules carry one cumulative usage-threshold event per budget minute (`minutes-`) over the rule's app list. - **Schedule rules** — one (or, for windows that cross midnight, two) repeating window activit(ies) per rule matching the rule's `From…To` window (`sched-` and, for the post-midnight half, `sched2-`). These carry no events; they exist purely to wake the monitor at the window edges so shields engage **in the background even when the app is closed**. A window that ends exactly at midnight, or is shorter than DeviceActivity's 15-minute minimum interval, may fail to register (`intervalTooShort`) and falls back to the foreground loop. Activities restart only when their configuration changes, because a restart resets threshold accounting. - **`OpenAppLockMonitor`** (DeviceActivityMonitor extension): interval start = midnight reset for limit rules (open-limit rules re-shield so opens can be counted; time-limit shields clear for the fresh budget); each `minutes-` event records usage and shields at the budget; a finished `open-session-` one-shot re-shields after a granted open. For schedule-window activities (`sched-`/`sched2-`), **both** interval start and interval end **recompute** the rule's live schedule state from its snapshot (`RuleSchedule.isActive`, honouring enabled days, pause and the midnight-crossing rule) and apply or clear the shield accordingly — the same logic `RuleEnforcer.refresh` runs in the foreground, so the two paths agree. - **Reliability posture** — DeviceActivity interval callbacks are "first device use after the boundary", are known to fire late or be skipped (device asleep, OS regressions on iOS 17/18/26), and a shield written over an app the user already has open may not visibly engage until that app is relaunched (a long-standing Screen Time platform limitation). Background monitoring is therefore **best-effort**; `RuleEnforcer.refresh` (launch + 30 s foreground loop) is retained as the reconciliation safety net and is the source of truth whenever the app runs. To keep that net consistent with the background, `refresh` applies the **same** open-limit proactive gate as `handleDayStart`: an enabled, scheduled-today, un-paused open-limit rule is shielded even before its budget is spent, *unless* the `OpenSessionStore` reports a still-running granted open for it — so the foreground loop establishes the turnstile for newly created rules and never re-locks an app mid-session. - **`OpenAppLockShieldConfig`** (ShieldConfiguration extension): every shield carries the same generic **"App Blocked"** title — rule names are never shown, since the rule a shield is attributed to cannot be determined reliably when several rules cover the same app. Open-limit shields keep their functional detail under that title ("Opened X of N times today" with an "Open (Y left)" secondary button while opens remain); all other shields just read "This app is blocked by OpenAppLock." The text-only decision lives in the pure, unit-tested `ShieldPresentation` (in `Shared/`). - **`OpenAppLockShieldAction`** (ShieldAction extension): the Open press spends one open in the ledger, lifts the rule's shield, records the session expiry in `OpenSessionStore`, and starts the ~15-minute one-shot session (DeviceActivity's minimum interval); the monitor clears that record when the session ends. - All shared logic lives in `Shared/` (notably `LimitEnforcement`), unit tested from the app test target. ### 5.6 Out of scope (seen in video, not part of "rules") - Onboarding flow, paywall ("You know Opal works. Make it permanent."), Home tab gem/score UI, Timer tab (one-off focus sessions, "Leave Early?" friction screen), notification nudges ("Complete Your Setup"). --- ## 6. Native UI re-skin (current presentation) OpenAppLock has since replaced the Opal-style custom presentation with the bare iOS design language, keeping the backend (models, logic, services), the flows, and the accessibility identifiers intact. Sections 1–5 remain as the reference for *what* the feature does; presentation now maps as follows: | Spec element | Native presentation | |---|---| | Apps home | `NavigationStack` + `List`; "Blocked Apps" and "Rules" sections; **rules are list rows** (kind icon, name, block summary, trailing live status — green when active); "+" toolbar button | | Rule detail | Sheet with inline nav title (name + "Schedule, 6h left" caption), `LabeledContent` rows, "Edit Rule" row pushes the editor; hard-locked rules show a lock row instead | | New Rule | `List` with a "Rule Type" section and preset sections as plain rows; editor pushed via `navigationDestination(item:)` | | Rule editor | Native `Form`: an inline **Name text field** at the top (no separate rename button; empty names fall back to the kind default), `DatePicker` rows, full-width day-circle row (≥44pt tap targets) with the summary in the section header, toggle rows with footers, stepper rows. Both modes commit via a **checkmark** in the navigation bar (labels: "Add Rule" / "Done"; replaces Hold to Commit). In edit mode an **ellipsis menu** ("Rule Actions") next to the checkmark holds Disable Rule and the destructive Delete Rule | | Onboarding / app picker | System styling, `.borderedProminent` buttons, default color scheme (no forced dark, default accent) | Dropped custom components: `Theme`, `HoldToCommitButton`, `RuleCardView`, icon-pair/circle-button chrome.