Files
OpenAppLock/docs/RULES_FEATURE_SPEC.md
Brendan Chen e6c87baeba feat: clone Opal's rules feature with hard block enforcement
Rebuild the app around recurring screen-time blocking rules modeled on
Opal's My Apps tab:

- Onboarding with FamilyControls Screen Time authorization
- Apps home with Blocked Apps tiles and live rule cards
- New Rule sheet: Schedule/Time Limit/Open Limit types + preset gallery
- Rule editors: time windows (incl. overnight), day picker, app selection
  via FamilyActivityPicker (Block/Allow Only), rename, Hold to Commit
- Hard Mode: active hard rules cannot be edited, disabled, deleted, or
  unblocked until their window ends; soft rules pause until next window
- Shield enforcement through per-rule ManagedSettingsStore + RuleEnforcer
- 73 unit tests (Swift Testing) + 16 UI tests (XCUITest) with launch-arg
  harness for in-memory storage, mocked authorization, seeded scenarios
- docs/RULES_FEATURE_SPEC.md: spec derived from the reference recording

Known gap: background window transitions and time/open-limit thresholds
need a DeviceActivityMonitor extension; shields currently sync while the
app is running.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 12:35:52 -04:00

15 KiB
Raw Blame History

Severed — "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:003:00 of the recording, on Opal's "My Apps" tab. This spec describes what was observed, then maps it onto an implementation plan for Severed.


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 across all types:

  • 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 selection — apps, categories, and websites; selection mode is either Block (block selected) or Allow Only (block everything except selected)
  • 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")

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).
  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:0017:00, Block, weekdays)
      • Laser Focus (Schedule 14:0018:00, Block)
    • Sleep, Relax and Reset — "Sleep better, rise refreshed."
      • Wind Down (Schedule 20:0022:00, Block)
      • Deep Sleep (Schedule 22:0006:00, Block)
    • Build Healthy Habits — "Spend time on important things."
      • Reading Time (Schedule ~19:4520:45, Block)
      • Gym Time (Schedule 17:3018: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.

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: Selected AppsN Apps — pushes the App Picker.
  4. Hard Mode ⚡PRO badge — subtitle "No unblocks allowed"; trailing toggle.
  5. 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 AppSelect (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.

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.

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 Severed should embed FamilyActivityPicker instead of cloning Opal's custom picker, and keep the Block/Allow Only segmented control as our own wrapper state.


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:0006:00, must be supported — Deep Sleep preset does this).
  2. While active — the rule's app selection is shielded; 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.
  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).

5. Implementation plan for Severed

5.1 Frameworks & capabilities

  • FamilyControlsAuthorizationCenter.shared.requestAuthorization(for: .individual); FamilyActivityPicker + FamilyActivitySelection (app/category/web tokens).
  • ManagedSettingsManagedSettingsStore per rule (ManagedSettingsStore.Name("rule-<uuid>")); set store.shield.applications / applicationCategories / webDomains.
  • DeviceActivityDeviceActivityCenter.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)

Replace the template AppListProfile with:

enum RuleKind: String, Codable { case schedule, timeLimit, openLimit }
enum SelectionMode: String, Codable { case block, allowOnly }

@Model final class BlockingRule {
    var id: UUID
    var name: String
    var kind: RuleKind
    var isEnabled: Bool
    var hardMode: Bool
    var selectionMode: SelectionMode
    var selectionData: Data          // encoded FamilyActivitySelection
    var days: [Int]                  // 1...7, Calendar weekday numbers
    // schedule
    var startMinutes: Int           // minutes from midnight, e.g. 540 = 09:00
    var endMinutes: Int             // may be < start (crosses midnight)
    // time limit
    var dailyLimitMinutes: Int?
    var resetPoint: String?          // "tomorrow" | "nextMorning" …
    // open limit
    var maxOpens: Int?
    var createdAt: Date
}

FamilyActivitySelection is Codable → store 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 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").