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.
15 KiB
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: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 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).
- Large title "Apps".
- 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).
- 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.
- 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:00On these days WeekendsBlock [app icons] 1 App/3 AppsUnblocks allowed Yes(hidden/Nowhen 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.
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):
- 📅 During this time
- Rows
From/Towith 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).
- Rows
- 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. - 🛡 Apps are blocked
- Row:
Selected Apps→N Apps ›— pushes the App Picker.
- Row:
- Hard Mode
⚡PRObadge — subtitle "No unblocks allowed"; trailing toggle. - 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:
- ⏳ When I use — row
This App→Select ›(app selection). - For this long — subtitle "Daily"; right-aligned value with stepper
45m ⌃⌄. - On these days: — identical day picker as Schedule.
- 🛡 Then block app — row
Untilwith stepper valueTomorrow ⌃⌄(reset point — e.g. tomorrow/next morning). - Hard Mode toggle — same as Schedule.
- 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 embedFamilyActivityPickerinstead of cloning Opal's custom picker, and keep theBlock/Allow Onlysegmented control as our own wrapper state.
4. Behavioral spec
- Activation — a Schedule rule becomes active at
Fromon an enabled day and deactivates atTo(windows crossing midnight, e.g. 22:00–06:00, must be supported — Deep Sleep preset does this). - 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.
- Unblocking — with Hard Mode off, the user may unblock mid-window ("Unblocks allowed: Yes"). With Hard Mode on, no unblocks until the window ends.
- Time-limit rules — accumulate usage daily across the selected apps;
on crossing the threshold, shield until the
Untilreset point (e.g. tomorrow), then reset the budget. - 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.
- 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".
- Live countdowns — "Starts in 22h" / "6h left" update over time (minute granularity is fine).
5. Implementation plan for Severed
5.1 Frameworks & capabilities
- FamilyControls —
AuthorizationCenter.shared.requestAuthorization(for: .individual);FamilyActivityPicker+FamilyActivitySelection(app/category/web tokens). - ManagedSettings —
ManagedSettingsStoreper rule (ManagedSettingsStore.Name("rule-<uuid>")); setstore.shield.applications/applicationCategories/webDomains. - DeviceActivity —
DeviceActivityCenter.startMonitoringwith aDeviceActivitySchedule(intervalStart:intervalEnd:repeats:)per rule; a DeviceActivityMonitor app extension applies/removes shields inintervalDidStart/intervalDidEnd. Time-limit rules useDeviceActivityEvent(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
- Data model + Apps tab with Rules section (cards from seeded sample rules, status derivation, detail sheet) — pure UI, no entitlements needed.
- New Rule sheet + Schedule editor + day picker + Hold to Commit (CRUD into SwiftData; Disable/Done editing path).
- FamilyControls authorization +
FamilyActivityPickerintegration ("Selected Apps" row, "N Apps" counts, icon clusters viaLabel(token:)). - DeviceActivity monitor extension + ManagedSettings shields (real blocking, incl. midnight-crossing windows).
- Time Limit editor + threshold events; Open Limit last (needs shield action extension + open counting).
- 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").