refactor: rename app from Severed to OpenAppLock
Full rename ahead of App Store submission: project, targets, bundle identifiers (dev.bchen.Severed -> dev.bchen.OpenAppLock), entitlements file, app entry point, user-facing onboarding strings, test helpers, and docs. Verified: all 62 unit tests pass. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
18
AGENTS.md
18
AGENTS.md
@@ -1,6 +1,6 @@
|
|||||||
# Severed — Agent Guide
|
# OpenAppLock — Agent Guide
|
||||||
|
|
||||||
Severed is an iOS Screen Time app: recurring **rules** that block selected
|
OpenAppLock is an iOS Screen Time app: recurring **rules** that block selected
|
||||||
apps (Schedule windows, Time Limits, Open Limits), with a **Hard Mode** that
|
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
|
makes an active block impossible to lift, edit, or delete until it ends. The
|
||||||
feature set is a clone of Opal's "Rules"; the presentation is bare native iOS
|
feature set is a clone of Opal's "Rules"; the presentation is bare native iOS
|
||||||
@@ -9,7 +9,7 @@ feature set is a clone of Opal's "Rules"; the presentation is bare native iOS
|
|||||||
## Repo layout
|
## Repo layout
|
||||||
|
|
||||||
```
|
```
|
||||||
Severed/ App target (iOS 26, SwiftUI + SwiftData)
|
OpenAppLock/ App target (iOS 26, SwiftUI + SwiftData)
|
||||||
Models/ BlockingRule (@Model), RuleDraft, RuleKind,
|
Models/ BlockingRule (@Model), RuleDraft, RuleKind,
|
||||||
Weekday, RulePreset
|
Weekday, RulePreset
|
||||||
Logic/ Pure, heavily unit-tested:
|
Logic/ Pure, heavily unit-tested:
|
||||||
@@ -22,9 +22,9 @@ Severed/ App target (iOS 26, SwiftUI + SwiftData)
|
|||||||
RuleEnforcer (active rules → shields),
|
RuleEnforcer (active rules → shields),
|
||||||
LaunchConfiguration + SampleRules (UI-test harness)
|
LaunchConfiguration + SampleRules (UI-test harness)
|
||||||
Views/ Native SwiftUI screens (see docs spec §6)
|
Views/ Native SwiftUI screens (see docs spec §6)
|
||||||
SeveredTests/ Swift Testing unit suites (@MainActor — the app
|
OpenAppLockTests/ Swift Testing unit suites (@MainActor — the app
|
||||||
target defaults to MainActor isolation)
|
target defaults to MainActor isolation)
|
||||||
SeveredUITests/ XCUITest flows (see harness below)
|
OpenAppLockUITests/ XCUITest flows (see harness below)
|
||||||
docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
||||||
recording; §6 maps it to the native presentation.
|
recording; §6 maps it to the native presentation.
|
||||||
Review/update this BEFORE behavior changes.
|
Review/update this BEFORE behavior changes.
|
||||||
@@ -50,14 +50,14 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
|||||||
|
|
||||||
## Build & test
|
## Build & test
|
||||||
|
|
||||||
- Open `Severed.xcodeproj` in Xcode; build/test through the **Xcode MCP**
|
- Open `OpenAppLock.xcodeproj` in Xcode; build/test through the **Xcode MCP**
|
||||||
tools (`BuildProject`, `RunAllTests`, `RunSomeTests` — get the tab id from
|
tools (`BuildProject`, `RunAllTests`, `RunSomeTests` — get the tab id from
|
||||||
`XcodeListWindows`). Make sure the scheme destination is an iOS
|
`XcodeListWindows`). Make sure the scheme destination is an iOS
|
||||||
**simulator**; a physical-device destination makes test runs hang or get
|
**simulator**; a physical-device destination makes test runs hang or get
|
||||||
cancelled.
|
cancelled.
|
||||||
- The project uses Xcode file-system-synchronized groups: adding/removing
|
- The project uses Xcode file-system-synchronized groups: adding/removing
|
||||||
`.swift` files on disk is enough, no pbxproj editing.
|
`.swift` files on disk is enough, no pbxproj editing.
|
||||||
- Family Controls entitlement is configured (`Severed/Severed.entitlements`).
|
- Family Controls entitlement is configured (`OpenAppLock/OpenAppLock.entitlements`).
|
||||||
FamilyControls/ManagedSettings compile and run on the simulator, but real
|
FamilyControls/ManagedSettings compile and run on the simulator, but real
|
||||||
blocking behavior is only observable on a device.
|
blocking behavior is only observable on a device.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
|||||||
|
|
||||||
## UI-test harness
|
## UI-test harness
|
||||||
|
|
||||||
`SeveredApp` reads launch arguments (parsed by `LaunchConfiguration`):
|
`OpenAppLockApp` reads launch arguments (parsed by `LaunchConfiguration`):
|
||||||
|
|
||||||
| Argument | Effect |
|
| Argument | Effect |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -81,7 +81,7 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
|||||||
| `-seed-scenario=standard` | Active soft rule "Work Time" + upcoming "Sleep" |
|
| `-seed-scenario=standard` | Active soft rule "Work Time" + upcoming "Sleep" |
|
||||||
| `-seed-scenario=hard-mode-active` | Active Hard Mode rule "Locked In" + upcoming "Sleep" |
|
| `-seed-scenario=hard-mode-active` | Active Hard Mode rule "Locked In" + upcoming "Sleep" |
|
||||||
|
|
||||||
Use `XCUIApplication.launchSevered(...)` (UITestSupport.swift), which also
|
Use `XCUIApplication.launchOpenAppLock(...)` (UITestSupport.swift), which also
|
||||||
provides `app.element(_:)` for identifier lookup across element types and
|
provides `app.element(_:)` for identifier lookup across element types and
|
||||||
`waitToAppear()`.
|
`waitToAppear()`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Severed
|
# OpenAppLock
|
||||||
|
|
||||||
All project context for agents lives in [AGENTS.md](AGENTS.md) — read that
|
All project context for agents lives in [AGENTS.md](AGENTS.md) — read that
|
||||||
first. Feature behavior is specified in
|
first. Feature behavior is specified in
|
||||||
|
|||||||
@@ -12,37 +12,37 @@
|
|||||||
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
||||||
remoteInfo = Severed;
|
remoteInfo = OpenAppLock;
|
||||||
};
|
};
|
||||||
20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */ = {
|
20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
||||||
remoteInfo = Severed;
|
remoteInfo = OpenAppLock;
|
||||||
};
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
20A7EDCE2E47B7CD0097608D /* Severed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Severed.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenAppLock.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeveredTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenAppLockTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeveredUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
20A7EDE72E47B7CF0097608D /* OpenAppLockUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenAppLockUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
20A7EDD02E47B7CD0097608D /* Severed */ = {
|
20A7EDD02E47B7CD0097608D /* OpenAppLock */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
path = Severed;
|
path = OpenAppLock;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
20A7EDE02E47B7CF0097608D /* SeveredTests */ = {
|
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
path = SeveredTests;
|
path = OpenAppLockTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */ = {
|
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
path = SeveredUITests;
|
path = OpenAppLockUITests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
20A7EDC52E47B7CD0097608D = {
|
20A7EDC52E47B7CD0097608D = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
20A7EDD02E47B7CD0097608D /* Severed */,
|
20A7EDD02E47B7CD0097608D /* OpenAppLock */,
|
||||||
20A7EDE02E47B7CF0097608D /* SeveredTests */,
|
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */,
|
||||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */,
|
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */,
|
||||||
20A7EDCF2E47B7CD0097608D /* Products */,
|
20A7EDCF2E47B7CD0097608D /* Products */,
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -85,9 +85,9 @@
|
|||||||
20A7EDCF2E47B7CD0097608D /* Products */ = {
|
20A7EDCF2E47B7CD0097608D /* Products */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
20A7EDCE2E47B7CD0097608D /* Severed.app */,
|
20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */,
|
||||||
20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */,
|
20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */,
|
||||||
20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */,
|
20A7EDE72E47B7CF0097608D /* OpenAppLockUITests.xctest */,
|
||||||
);
|
);
|
||||||
name = Products;
|
name = Products;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -95,9 +95,9 @@
|
|||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
20A7EDCD2E47B7CD0097608D /* Severed */ = {
|
20A7EDCD2E47B7CD0097608D /* OpenAppLock */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "Severed" */;
|
buildConfigurationList = 20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLock" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
20A7EDCA2E47B7CD0097608D /* Sources */,
|
20A7EDCA2E47B7CD0097608D /* Sources */,
|
||||||
20A7EDCB2E47B7CD0097608D /* Frameworks */,
|
20A7EDCB2E47B7CD0097608D /* Frameworks */,
|
||||||
@@ -108,18 +108,18 @@
|
|||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
20A7EDD02E47B7CD0097608D /* Severed */,
|
20A7EDD02E47B7CD0097608D /* OpenAppLock */,
|
||||||
);
|
);
|
||||||
name = Severed;
|
name = OpenAppLock;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = Severed;
|
productName = OpenAppLock;
|
||||||
productReference = 20A7EDCE2E47B7CD0097608D /* Severed.app */;
|
productReference = 20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */;
|
||||||
productType = "com.apple.product-type.application";
|
productType = "com.apple.product-type.application";
|
||||||
};
|
};
|
||||||
20A7EDDC2E47B7CF0097608D /* SeveredTests */ = {
|
20A7EDDC2E47B7CF0097608D /* OpenAppLockTests */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredTests" */;
|
buildConfigurationList = 20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
20A7EDD92E47B7CF0097608D /* Sources */,
|
20A7EDD92E47B7CF0097608D /* Sources */,
|
||||||
20A7EDDA2E47B7CF0097608D /* Frameworks */,
|
20A7EDDA2E47B7CF0097608D /* Frameworks */,
|
||||||
@@ -131,18 +131,18 @@
|
|||||||
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */,
|
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
20A7EDE02E47B7CF0097608D /* SeveredTests */,
|
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */,
|
||||||
);
|
);
|
||||||
name = SeveredTests;
|
name = OpenAppLockTests;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = SeveredTests;
|
productName = OpenAppLockTests;
|
||||||
productReference = 20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */;
|
productReference = 20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.unit-test";
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
};
|
};
|
||||||
20A7EDE62E47B7CF0097608D /* SeveredUITests */ = {
|
20A7EDE62E47B7CF0097608D /* OpenAppLockUITests */ = {
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredUITests" */;
|
buildConfigurationList = 20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockUITests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
20A7EDE32E47B7CF0097608D /* Sources */,
|
20A7EDE32E47B7CF0097608D /* Sources */,
|
||||||
20A7EDE42E47B7CF0097608D /* Frameworks */,
|
20A7EDE42E47B7CF0097608D /* Frameworks */,
|
||||||
@@ -154,13 +154,13 @@
|
|||||||
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */,
|
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */,
|
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */,
|
||||||
);
|
);
|
||||||
name = SeveredUITests;
|
name = OpenAppLockUITests;
|
||||||
packageProductDependencies = (
|
packageProductDependencies = (
|
||||||
);
|
);
|
||||||
productName = SeveredUITests;
|
productName = OpenAppLockUITests;
|
||||||
productReference = 20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */;
|
productReference = 20A7EDE72E47B7CF0097608D /* OpenAppLockUITests.xctest */;
|
||||||
productType = "com.apple.product-type.bundle.ui-testing";
|
productType = "com.apple.product-type.bundle.ui-testing";
|
||||||
};
|
};
|
||||||
/* End PBXNativeTarget section */
|
/* End PBXNativeTarget section */
|
||||||
@@ -186,7 +186,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
buildConfigurationList = 20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "Severed" */;
|
buildConfigurationList = 20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "OpenAppLock" */;
|
||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
@@ -200,9 +200,9 @@
|
|||||||
projectDirPath = "";
|
projectDirPath = "";
|
||||||
projectRoot = "";
|
projectRoot = "";
|
||||||
targets = (
|
targets = (
|
||||||
20A7EDCD2E47B7CD0097608D /* Severed */,
|
20A7EDCD2E47B7CD0097608D /* OpenAppLock */,
|
||||||
20A7EDDC2E47B7CF0097608D /* SeveredTests */,
|
20A7EDDC2E47B7CF0097608D /* OpenAppLockTests */,
|
||||||
20A7EDE62E47B7CF0097608D /* SeveredUITests */,
|
20A7EDE62E47B7CF0097608D /* OpenAppLockUITests */,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/* End PBXProject section */
|
/* End PBXProject section */
|
||||||
@@ -258,12 +258,12 @@
|
|||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */ = {
|
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 20A7EDCD2E47B7CD0097608D /* Severed */;
|
target = 20A7EDCD2E47B7CD0097608D /* OpenAppLock */;
|
||||||
targetProxy = 20A7EDDE2E47B7CF0097608D /* PBXContainerItemProxy */;
|
targetProxy = 20A7EDDE2E47B7CF0097608D /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */ = {
|
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 20A7EDCD2E47B7CD0097608D /* Severed */;
|
target = 20A7EDCD2E47B7CD0097608D /* OpenAppLock */;
|
||||||
targetProxy = 20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */;
|
targetProxy = 20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */;
|
||||||
};
|
};
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
@@ -390,7 +390,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = Severed/Severed.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenAppLock/OpenAppLock.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
||||||
@@ -414,7 +414,7 @@
|
|||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.Severed;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLock;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
@@ -433,7 +433,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_ENTITLEMENTS = Severed/Severed.entitlements;
|
CODE_SIGN_ENTITLEMENTS = OpenAppLock/OpenAppLock.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
||||||
@@ -457,7 +457,7 @@
|
|||||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.Severed;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLock;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
REGISTER_APP_GROUPS = YES;
|
REGISTER_APP_GROUPS = YES;
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
@@ -482,7 +482,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredTests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
@@ -491,7 +491,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Severed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Severed";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenAppLock.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenAppLock";
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -507,7 +507,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredTests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockTests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
@@ -516,7 +516,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Severed.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Severed";
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/OpenAppLock.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/OpenAppLock";
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -531,7 +531,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockUITests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
@@ -540,7 +540,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_TARGET_NAME = Severed;
|
TEST_TARGET_NAME = OpenAppLock;
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@@ -555,7 +555,7 @@
|
|||||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredUITests;
|
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockUITests;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SDKROOT = auto;
|
SDKROOT = auto;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
TEST_TARGET_NAME = Severed;
|
TEST_TARGET_NAME = OpenAppLock;
|
||||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -572,7 +572,7 @@
|
|||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "Severed" */ = {
|
20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "OpenAppLock" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
20A7EDEF2E47B7CF0097608D /* Debug */,
|
20A7EDEF2E47B7CF0097608D /* Debug */,
|
||||||
@@ -581,7 +581,7 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "Severed" */ = {
|
20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLock" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
20A7EDF22E47B7CF0097608D /* Debug */,
|
20A7EDF22E47B7CF0097608D /* Debug */,
|
||||||
@@ -590,7 +590,7 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredTests" */ = {
|
20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockTests" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
20A7EDF52E47B7CF0097608D /* Debug */,
|
20A7EDF52E47B7CF0097608D /* Debug */,
|
||||||
@@ -599,7 +599,7 @@
|
|||||||
defaultConfigurationIsVisible = 0;
|
defaultConfigurationIsVisible = 0;
|
||||||
defaultConfigurationName = Release;
|
defaultConfigurationName = Release;
|
||||||
};
|
};
|
||||||
20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredUITests" */ = {
|
20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockUITests" */ = {
|
||||||
isa = XCConfigurationList;
|
isa = XCConfigurationList;
|
||||||
buildConfigurations = (
|
buildConfigurations = (
|
||||||
20A7EDF82E47B7CF0097608D /* Debug */,
|
20A7EDF82E47B7CF0097608D /* Debug */,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RulePolicy.swift
|
// RulePolicy.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleSchedule.swift
|
// RuleSchedule.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleStatus.swift
|
// RuleStatus.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// BlockingRule.swift
|
// BlockingRule.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleDraft.swift
|
// RuleDraft.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleKind.swift
|
// RuleKind.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RulePreset.swift
|
// RulePreset.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// Weekday.swift
|
// Weekday.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// SeveredApp.swift
|
// OpenAppLockApp.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
// Created by Brendan Chen on 2025.08.09.
|
// Created by Brendan Chen on 2025.08.09.
|
||||||
//
|
//
|
||||||
@@ -9,7 +9,7 @@ import SwiftData
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct SeveredApp: App {
|
struct OpenAppLockApp: App {
|
||||||
private let container: ModelContainer
|
private let container: ModelContainer
|
||||||
@State private var authorization: ScreenTimeAuthorization
|
@State private var authorization: ScreenTimeAuthorization
|
||||||
@State private var enforcer: RuleEnforcer
|
@State private var enforcer: RuleEnforcer
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// LaunchConfiguration.swift
|
// LaunchConfiguration.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleEnforcer.swift
|
// RuleEnforcer.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// SampleRules.swift
|
// SampleRules.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// ScreenTimeAuthorization.swift
|
// ScreenTimeAuthorization.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import FamilyControls
|
import FamilyControls
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// ShieldController.swift
|
// ShieldController.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import FamilyControls
|
import FamilyControls
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// AppsHomeView.swift
|
// AppsHomeView.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftData
|
import SwiftData
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// DayOfWeekPicker.swift
|
// DayOfWeekPicker.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// OnboardingView.swift
|
// OnboardingView.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@@ -39,7 +39,7 @@ struct OnboardingView: View {
|
|||||||
Image(systemName: "scissors")
|
Image(systemName: "scissors")
|
||||||
.font(.system(size: 56))
|
.font(.system(size: 56))
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
Text("Severed")
|
Text("OpenAppLock")
|
||||||
.font(.largeTitle.bold())
|
.font(.largeTitle.bold())
|
||||||
Text("Block your most distracting apps with rules that keep you honest — on a schedule, with no way out when you choose Hard Mode.")
|
Text("Block your most distracting apps with rules that keep you honest — on a schedule, with no way out when you choose Hard Mode.")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
@@ -56,13 +56,13 @@ struct OnboardingView: View {
|
|||||||
.font(.title.bold())
|
.font(.title.bold())
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
VStack(alignment: .leading, spacing: 14) {
|
VStack(alignment: .leading, spacing: 14) {
|
||||||
bullet("shield.fill", "Severed uses Apple's Screen Time framework to block the apps you choose.")
|
bullet("shield.fill", "OpenAppLock uses Apple's Screen Time framework to block the apps you choose.")
|
||||||
bullet("hand.raised.fill", "Your app activity stays on this device and is never collected.")
|
bullet("hand.raised.fill", "Your app activity stays on this device and is never collected.")
|
||||||
bullet("gearshape.fill", "You can change this anytime in Settings.")
|
bullet("gearshape.fill", "You can change this anytime in Settings.")
|
||||||
}
|
}
|
||||||
if authorization.status == .denied || authorization.lastRequestFailed {
|
if authorization.status == .denied || authorization.lastRequestFailed {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
Text("Screen Time access was declined. Severed can't block apps without it.")
|
Text("Screen Time access was declined. OpenAppLock can't block apps without it.")
|
||||||
.font(.footnote)
|
.font(.footnote)
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RootView.swift
|
// RootView.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// AppSelectionSheet.swift
|
// AppSelectionSheet.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import FamilyControls
|
import FamilyControls
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// NewRuleSheet.swift
|
// NewRuleSheet.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftData
|
import SwiftData
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleDetailSheet.swift
|
// RuleDetailSheet.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftData
|
import SwiftData
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleEditorView.swift
|
// RuleEditorView.swift
|
||||||
// Severed
|
// OpenAppLock
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
//
|
//
|
||||||
// LaunchSupportTests.swift
|
// LaunchSupportTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("Launch configuration parsing")
|
@Suite("Launch configuration parsing")
|
||||||
struct LaunchConfigurationTests {
|
struct LaunchConfigurationTests {
|
||||||
@Test("Defaults are production settings")
|
@Test("Defaults are production settings")
|
||||||
func defaults() {
|
func defaults() {
|
||||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp"])
|
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp"])
|
||||||
#expect(!config.isUITesting)
|
#expect(!config.isUITesting)
|
||||||
#expect(config.onboardingCompleted == nil)
|
#expect(config.onboardingCompleted == nil)
|
||||||
#expect(config.seedScenario == nil)
|
#expect(config.seedScenario == nil)
|
||||||
@@ -22,7 +22,7 @@ struct LaunchConfigurationTests {
|
|||||||
@Test("UI testing flags are recognized")
|
@Test("UI testing flags are recognized")
|
||||||
func uiTestingFlags() {
|
func uiTestingFlags() {
|
||||||
let config = LaunchConfiguration.parse(arguments: [
|
let config = LaunchConfiguration.parse(arguments: [
|
||||||
"SeveredApp", "-ui-testing", "-onboarding-completed", "-seed-scenario=standard",
|
"OpenAppLockApp", "-ui-testing", "-onboarding-completed", "-seed-scenario=standard",
|
||||||
])
|
])
|
||||||
#expect(config.isUITesting)
|
#expect(config.isUITesting)
|
||||||
#expect(config.onboardingCompleted == true)
|
#expect(config.onboardingCompleted == true)
|
||||||
@@ -31,13 +31,13 @@ struct LaunchConfigurationTests {
|
|||||||
|
|
||||||
@Test("Onboarding can be forced on")
|
@Test("Onboarding can be forced on")
|
||||||
func onboardingRequired() {
|
func onboardingRequired() {
|
||||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp", "-onboarding-required"])
|
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp", "-onboarding-required"])
|
||||||
#expect(config.onboardingCompleted == false)
|
#expect(config.onboardingCompleted == false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Unknown seed scenarios are ignored")
|
@Test("Unknown seed scenarios are ignored")
|
||||||
func unknownScenario() {
|
func unknownScenario() {
|
||||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp", "-seed-scenario=nope"])
|
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp", "-seed-scenario=nope"])
|
||||||
#expect(config.seedScenario == nil)
|
#expect(config.seedScenario == nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
//
|
//
|
||||||
// RuleEnforcerTests.swift
|
// RuleEnforcerTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("Rule enforcement → shields")
|
@Suite("Rule enforcement → shields")
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
//
|
//
|
||||||
// RuleModelTests.swift
|
// RuleModelTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftData
|
import SwiftData
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("BlockingRule model & persistence")
|
@Suite("BlockingRule model & persistence")
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
//
|
//
|
||||||
// RulePolicyTests.swift
|
// RulePolicyTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("Hard Mode policy")
|
@Suite("Hard Mode policy")
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
//
|
//
|
||||||
// RuleScheduleTests.swift
|
// RuleScheduleTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("RuleSchedule window math")
|
@Suite("RuleSchedule window math")
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
//
|
//
|
||||||
// RuleStatusTests.swift
|
// RuleStatusTests.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Testing
|
import Testing
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite("Rule status derivation and labels")
|
@Suite("Rule status derivation and labels")
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
//
|
//
|
||||||
// TestSupport.swift
|
// TestSupport.swift
|
||||||
// SeveredTests
|
// OpenAppLockTests
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@testable import Severed
|
@testable import OpenAppLock
|
||||||
|
|
||||||
/// Fixed UTC gregorian calendar so schedule math is deterministic regardless
|
/// Fixed UTC gregorian calendar so schedule math is deterministic regardless
|
||||||
/// of the machine running the tests.
|
/// of the machine running the tests.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// OnboardingUITests.swift
|
// OnboardingUITests.swift
|
||||||
// SeveredUITests
|
// OpenAppLockUITests
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -11,10 +11,10 @@ final class OnboardingUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testOnboardingWalksThroughPermissionToHome() throws {
|
func testOnboardingWalksThroughPermissionToHome() throws {
|
||||||
let app = XCUIApplication.launchSevered(onboardingCompleted: false)
|
let app = XCUIApplication.launchOpenAppLock(onboardingCompleted: false)
|
||||||
|
|
||||||
// Welcome step.
|
// Welcome step.
|
||||||
app.staticTexts["Severed"].waitToAppear()
|
app.staticTexts["OpenAppLock"].waitToAppear()
|
||||||
app.buttons["onboardingContinueButton"].waitToAppear().tap()
|
app.buttons["onboardingContinueButton"].waitToAppear().tap()
|
||||||
|
|
||||||
// Permission step: granting (mocked) lands on the Apps home screen.
|
// Permission step: granting (mocked) lands on the Apps home screen.
|
||||||
@@ -24,7 +24,7 @@ final class OnboardingUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCompletedOnboardingIsSkipped() throws {
|
func testCompletedOnboardingIsSkipped() throws {
|
||||||
let app = XCUIApplication.launchSevered(onboardingCompleted: true)
|
let app = XCUIApplication.launchOpenAppLock(onboardingCompleted: true)
|
||||||
app.buttons["newRuleButton"].waitToAppear()
|
app.buttons["newRuleButton"].waitToAppear()
|
||||||
XCTAssertFalse(app.buttons["onboardingContinueButton"].exists)
|
XCTAssertFalse(app.buttons["onboardingContinueButton"].exists)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleCreationUITests.swift
|
// RuleCreationUITests.swift
|
||||||
// SeveredUITests
|
// OpenAppLockUITests
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -11,7 +11,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreateScheduleRuleFromTypeCard() throws {
|
func testCreateScheduleRuleFromTypeCard() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.element("emptyRulesCard").waitToAppear()
|
app.element("emptyRulesCard").waitToAppear()
|
||||||
|
|
||||||
app.buttons["newRuleButton"].tap()
|
app.buttons["newRuleButton"].tap()
|
||||||
@@ -32,7 +32,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreateRuleFromPreset() throws {
|
func testCreateRuleFromPreset() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
|
|
||||||
app.buttons["preset-work-time"].waitToAppear().tap()
|
app.buttons["preset-work-time"].waitToAppear().tap()
|
||||||
@@ -43,7 +43,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testRenameRuleInEditor() throws {
|
func testRenameRuleInEditor() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDayTogglesFillRowAndHaveLargeTapTargets() throws {
|
func testDayTogglesFillRowAndHaveLargeTapTargets() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDayTogglesUpdateSummary() throws {
|
func testDayTogglesUpdateSummary() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testCreateTimeLimitRule() throws {
|
func testCreateTimeLimitRule() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
|
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testAdultContentToggleFlowsToDetail() throws {
|
func testAdultContentToggleFlowsToDetail() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -125,14 +125,14 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testAdultContentDefaultsToAllowed() throws {
|
func testAdultContentDefaultsToAllowed() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
||||||
let row = app.element("detailRow-Adult websites").waitToAppear()
|
let row = app.element("detailRow-Adult websites").waitToAppear()
|
||||||
XCTAssertTrue(row.label.contains("Allowed"), "Got: \(row.label)")
|
XCTAssertTrue(row.label.contains("Allowed"), "Got: \(row.label)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEditorSupportsNativeSwipeBack() throws {
|
func testEditorSupportsNativeSwipeBack() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||||
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
||||||
@@ -147,7 +147,7 @@ final class RuleCreationUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNewRuleSheetShowsTypesAndPresets() throws {
|
func testNewRuleSheetShowsTypesAndPresets() throws {
|
||||||
let app = XCUIApplication.launchSevered()
|
let app = XCUIApplication.launchOpenAppLock()
|
||||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||||
|
|
||||||
app.buttons["ruleKind-schedule"].waitToAppear()
|
app.buttons["ruleKind-schedule"].waitToAppear()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// RuleManagementUITests.swift
|
// RuleManagementUITests.swift
|
||||||
// SeveredUITests
|
// OpenAppLockUITests
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -13,7 +13,7 @@ final class RuleManagementUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDetailShowsLiveStatusAndFacts() throws {
|
func testDetailShowsLiveStatusAndFacts() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ final class RuleManagementUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testEditRuleTogglesHardModeOn() throws {
|
func testEditRuleTogglesHardModeOn() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||||
@@ -43,7 +43,7 @@ final class RuleManagementUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDisableRule() throws {
|
func testDisableRule() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||||
@@ -64,7 +64,7 @@ final class RuleManagementUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testDeleteRuleRemovesCard() throws {
|
func testDeleteRuleRemovesCard() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||||
@@ -82,7 +82,7 @@ final class RuleManagementUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testUnblockActiveSoftRule() throws {
|
func testUnblockActiveSoftRule() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
// The active rule surfaces in Blocked Apps; unblocking pauses it.
|
// The active rule surfaces in Blocked Apps; unblocking pauses it.
|
||||||
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
||||||
@@ -100,7 +100,7 @@ final class HardModeUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testHardLockedRuleCannotBeEdited() throws {
|
func testHardLockedRuleCannotBeEdited() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "hard-mode-active")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
|
||||||
|
|
||||||
app.buttons["ruleCard-Locked In"].waitToAppear().tap()
|
app.buttons["ruleCard-Locked In"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ final class HardModeUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testHardLockedRuleCannotBeUnblocked() throws {
|
func testHardLockedRuleCannotBeUnblocked() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "hard-mode-active")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "hard-mode-active")
|
||||||
|
|
||||||
app.buttons["blockedTile-Locked In"].waitToAppear().tap()
|
app.buttons["blockedTile-Locked In"].waitToAppear().tap()
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ final class HardModeUITests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testSoftRuleUnblockOfferedButHardRuleRefused() throws {
|
func testSoftRuleUnblockOfferedButHardRuleRefused() throws {
|
||||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||||
|
|
||||||
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
||||||
// Soft rule: the confirmation dialog appears instead of the refusal alert.
|
// Soft rule: the confirmation dialog appears instead of the refusal alert.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//
|
//
|
||||||
// UITestSupport.swift
|
// UITestSupport.swift
|
||||||
// SeveredUITests
|
// OpenAppLockUITests
|
||||||
//
|
//
|
||||||
|
|
||||||
import XCTest
|
import XCTest
|
||||||
@@ -8,7 +8,7 @@ import XCTest
|
|||||||
extension XCUIApplication {
|
extension XCUIApplication {
|
||||||
/// Launches the app in UI-testing mode: in-memory storage, mocked Screen
|
/// Launches the app in UI-testing mode: in-memory storage, mocked Screen
|
||||||
/// Time authorization, and no shield side effects.
|
/// Time authorization, and no shield side effects.
|
||||||
static func launchSevered(
|
static func launchOpenAppLock(
|
||||||
onboardingCompleted: Bool = true,
|
onboardingCompleted: Bool = true,
|
||||||
seedScenario: String? = nil
|
seedScenario: String? = nil
|
||||||
) -> XCUIApplication {
|
) -> XCUIApplication {
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
# Severed — "Rules" Feature Spec (derived from Opal screen recording)
|
# 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
|
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.
|
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
|
This spec describes what was observed, then maps it onto an implementation plan
|
||||||
for Severed.
|
for OpenAppLock.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ Presented from "+ New". Full-height sheet, scrollable.
|
|||||||
and a circular "+" button bottom-right. Tapping anywhere opens the
|
and a circular "+" button bottom-right. Tapping anywhere opens the
|
||||||
Schedule editor pre-filled with the preset's name/times/days.
|
Schedule editor pre-filled with the preset's name/times/days.
|
||||||
|
|
||||||
> **Navigation (Severed):** picking a rule type or preset **pushes** the
|
> **Navigation (OpenAppLock):** picking a rule type or preset **pushes** the
|
||||||
> editor inside the sheet via native SwiftUI navigation (`NavigationStack` +
|
> editor inside the sheet via native SwiftUI navigation (`NavigationStack` +
|
||||||
> `navigationDestination(item:)`), so the system push animation and
|
> `navigationDestination(item:)`), so the system push animation and
|
||||||
> edge-swipe-back work; the editor keeps its custom header chrome.
|
> edge-swipe-back work; the editor keeps its custom header chrome.
|
||||||
@@ -150,7 +150,7 @@ Sections (each an inset rounded group with a small icon + caption header):
|
|||||||
- Row: `Selected Apps` → `N Apps ›` — pushes the App Picker.
|
- Row: `Selected Apps` → `N Apps ›` — pushes the App Picker.
|
||||||
4. **Hard Mode** `⚡PRO` badge — subtitle "No unblocks allowed"; trailing
|
4. **Hard Mode** `⚡PRO` badge — subtitle "No unblocks allowed"; trailing
|
||||||
toggle.
|
toggle.
|
||||||
5. **Block Adult Content** *(Severed addition — not in the Opal video)* —
|
5. **Block Adult Content** *(OpenAppLock addition — not in the Opal video)* —
|
||||||
subtitle "Filter adult websites while this rule is active"; trailing
|
subtitle "Filter adult websites while this rule is active"; trailing
|
||||||
toggle. Maps to Screen Time's web-content filter
|
toggle. Maps to Screen Time's web-content filter
|
||||||
(`ManagedSettingsStore.webContent.blockedByFilter = .auto(...)`), applied
|
(`ManagedSettingsStore.webContent.blockedByFilter = .auto(...)`), applied
|
||||||
@@ -208,7 +208,7 @@ Full-height sheet:
|
|||||||
> Implementation note: Opal ships its own app categorization. On iOS, third
|
> Implementation note: Opal ships its own app categorization. On iOS, third
|
||||||
> parties cannot enumerate installed apps; the system-sanctioned route is
|
> parties cannot enumerate installed apps; the system-sanctioned route is
|
||||||
> `FamilyActivityPicker` (FamilyControls), which provides its own
|
> `FamilyActivityPicker` (FamilyControls), which provides its own
|
||||||
> category/app/website UI and returns opaque tokens. **v1 of Severed should
|
> category/app/website UI and returns opaque tokens. **v1 of OpenAppLock should
|
||||||
> embed `FamilyActivityPicker`** instead of cloning Opal's custom picker, and
|
> embed `FamilyActivityPicker`** instead of cloning Opal's custom picker, and
|
||||||
> keep the `Block`/`Allow Only` segmented control as our own wrapper state.
|
> keep the `Block`/`Allow Only` segmented control as our own wrapper state.
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ Full-height sheet:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Implementation plan for Severed
|
## 5. Implementation plan for OpenAppLock
|
||||||
|
|
||||||
### 5.1 Frameworks & capabilities
|
### 5.1 Frameworks & capabilities
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ enum SelectionMode: String, Codable { case block, allowOnly }
|
|||||||
|
|
||||||
## 6. Native UI re-skin (current presentation)
|
## 6. Native UI re-skin (current presentation)
|
||||||
|
|
||||||
Severed has since replaced the Opal-style custom presentation with the bare
|
OpenAppLock has since replaced the Opal-style custom presentation with the bare
|
||||||
iOS design language, keeping the backend (models, logic, services), the
|
iOS design language, keeping the backend (models, logic, services), the
|
||||||
flows, and the accessibility identifiers intact. Sections 1–5 remain as the
|
flows, and the accessibility identifiers intact. Sections 1–5 remain as the
|
||||||
reference for *what* the feature does; presentation now maps as follows:
|
reference for *what* the feature does; presentation now maps as follows:
|
||||||
|
|||||||
Reference in New Issue
Block a user