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
|
||||
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
|
||||
@@ -9,7 +9,7 @@ feature set is a clone of Opal's "Rules"; the presentation is bare native iOS
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
Severed/ App target (iOS 26, SwiftUI + SwiftData)
|
||||
OpenAppLock/ App target (iOS 26, SwiftUI + SwiftData)
|
||||
Models/ BlockingRule (@Model), RuleDraft, RuleKind,
|
||||
Weekday, RulePreset
|
||||
Logic/ Pure, heavily unit-tested:
|
||||
@@ -22,9 +22,9 @@ Severed/ App target (iOS 26, SwiftUI + SwiftData)
|
||||
RuleEnforcer (active rules → shields),
|
||||
LaunchConfiguration + SampleRules (UI-test harness)
|
||||
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)
|
||||
SeveredUITests/ XCUITest flows (see harness below)
|
||||
OpenAppLockUITests/ XCUITest flows (see harness below)
|
||||
docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
||||
recording; §6 maps it to the native presentation.
|
||||
Review/update this BEFORE behavior changes.
|
||||
@@ -50,14 +50,14 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
||||
|
||||
## 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
|
||||
`XcodeListWindows`). Make sure the scheme destination is an iOS
|
||||
**simulator**; a physical-device destination makes test runs hang or get
|
||||
cancelled.
|
||||
- The project uses Xcode file-system-synchronized groups: adding/removing
|
||||
`.swift` files on disk is enough, no pbxproj editing.
|
||||
- Family Controls entitlement is configured (`Severed/Severed.entitlements`).
|
||||
- Family Controls entitlement is configured (`OpenAppLock/OpenAppLock.entitlements`).
|
||||
FamilyControls/ManagedSettings compile and run on the simulator, but real
|
||||
blocking behavior is only observable on a device.
|
||||
|
||||
@@ -72,7 +72,7 @@ docs/RULES_FEATURE_SPEC.md Feature spec derived from the Opal reference
|
||||
|
||||
## UI-test harness
|
||||
|
||||
`SeveredApp` reads launch arguments (parsed by `LaunchConfiguration`):
|
||||
`OpenAppLockApp` reads launch arguments (parsed by `LaunchConfiguration`):
|
||||
|
||||
| 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=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
|
||||
`waitToAppear()`.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Severed
|
||||
# OpenAppLock
|
||||
|
||||
All project context for agents lives in [AGENTS.md](AGENTS.md) — read that
|
||||
first. Feature behavior is specified in
|
||||
|
||||
@@ -12,37 +12,37 @@
|
||||
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
||||
remoteInfo = Severed;
|
||||
remoteInfo = OpenAppLock;
|
||||
};
|
||||
20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 20A7EDC62E47B7CD0097608D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 20A7EDCD2E47B7CD0097608D;
|
||||
remoteInfo = Severed;
|
||||
remoteInfo = OpenAppLock;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
20A7EDCE2E47B7CD0097608D /* Severed.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Severed.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeveredTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SeveredUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenAppLock.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenAppLockTests.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 */
|
||||
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
20A7EDD02E47B7CD0097608D /* Severed */ = {
|
||||
20A7EDD02E47B7CD0097608D /* OpenAppLock */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = Severed;
|
||||
path = OpenAppLock;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
20A7EDE02E47B7CF0097608D /* SeveredTests */ = {
|
||||
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = SeveredTests;
|
||||
path = OpenAppLockTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */ = {
|
||||
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = SeveredUITests;
|
||||
path = OpenAppLockUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
@@ -75,9 +75,9 @@
|
||||
20A7EDC52E47B7CD0097608D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
20A7EDD02E47B7CD0097608D /* Severed */,
|
||||
20A7EDE02E47B7CF0097608D /* SeveredTests */,
|
||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */,
|
||||
20A7EDD02E47B7CD0097608D /* OpenAppLock */,
|
||||
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */,
|
||||
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */,
|
||||
20A7EDCF2E47B7CD0097608D /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -85,9 +85,9 @@
|
||||
20A7EDCF2E47B7CD0097608D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
20A7EDCE2E47B7CD0097608D /* Severed.app */,
|
||||
20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */,
|
||||
20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */,
|
||||
20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */,
|
||||
20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */,
|
||||
20A7EDE72E47B7CF0097608D /* OpenAppLockUITests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -95,9 +95,9 @@
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
20A7EDCD2E47B7CD0097608D /* Severed */ = {
|
||||
20A7EDCD2E47B7CD0097608D /* OpenAppLock */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "Severed" */;
|
||||
buildConfigurationList = 20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLock" */;
|
||||
buildPhases = (
|
||||
20A7EDCA2E47B7CD0097608D /* Sources */,
|
||||
20A7EDCB2E47B7CD0097608D /* Frameworks */,
|
||||
@@ -108,18 +108,18 @@
|
||||
dependencies = (
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
20A7EDD02E47B7CD0097608D /* Severed */,
|
||||
20A7EDD02E47B7CD0097608D /* OpenAppLock */,
|
||||
);
|
||||
name = Severed;
|
||||
name = OpenAppLock;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = Severed;
|
||||
productReference = 20A7EDCE2E47B7CD0097608D /* Severed.app */;
|
||||
productName = OpenAppLock;
|
||||
productReference = 20A7EDCE2E47B7CD0097608D /* OpenAppLock.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
20A7EDDC2E47B7CF0097608D /* SeveredTests */ = {
|
||||
20A7EDDC2E47B7CF0097608D /* OpenAppLockTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredTests" */;
|
||||
buildConfigurationList = 20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockTests" */;
|
||||
buildPhases = (
|
||||
20A7EDD92E47B7CF0097608D /* Sources */,
|
||||
20A7EDDA2E47B7CF0097608D /* Frameworks */,
|
||||
@@ -131,18 +131,18 @@
|
||||
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
20A7EDE02E47B7CF0097608D /* SeveredTests */,
|
||||
20A7EDE02E47B7CF0097608D /* OpenAppLockTests */,
|
||||
);
|
||||
name = SeveredTests;
|
||||
name = OpenAppLockTests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = SeveredTests;
|
||||
productReference = 20A7EDDD2E47B7CF0097608D /* SeveredTests.xctest */;
|
||||
productName = OpenAppLockTests;
|
||||
productReference = 20A7EDDD2E47B7CF0097608D /* OpenAppLockTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
20A7EDE62E47B7CF0097608D /* SeveredUITests */ = {
|
||||
20A7EDE62E47B7CF0097608D /* OpenAppLockUITests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredUITests" */;
|
||||
buildConfigurationList = 20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockUITests" */;
|
||||
buildPhases = (
|
||||
20A7EDE32E47B7CF0097608D /* Sources */,
|
||||
20A7EDE42E47B7CF0097608D /* Frameworks */,
|
||||
@@ -154,13 +154,13 @@
|
||||
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */,
|
||||
);
|
||||
fileSystemSynchronizedGroups = (
|
||||
20A7EDEA2E47B7CF0097608D /* SeveredUITests */,
|
||||
20A7EDEA2E47B7CF0097608D /* OpenAppLockUITests */,
|
||||
);
|
||||
name = SeveredUITests;
|
||||
name = OpenAppLockUITests;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = SeveredUITests;
|
||||
productReference = 20A7EDE72E47B7CF0097608D /* SeveredUITests.xctest */;
|
||||
productName = OpenAppLockUITests;
|
||||
productReference = 20A7EDE72E47B7CF0097608D /* OpenAppLockUITests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.ui-testing";
|
||||
};
|
||||
/* 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;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
@@ -200,9 +200,9 @@
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
20A7EDCD2E47B7CD0097608D /* Severed */,
|
||||
20A7EDDC2E47B7CF0097608D /* SeveredTests */,
|
||||
20A7EDE62E47B7CF0097608D /* SeveredUITests */,
|
||||
20A7EDCD2E47B7CD0097608D /* OpenAppLock */,
|
||||
20A7EDDC2E47B7CF0097608D /* OpenAppLockTests */,
|
||||
20A7EDE62E47B7CF0097608D /* OpenAppLockUITests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -258,12 +258,12 @@
|
||||
/* Begin PBXTargetDependency section */
|
||||
20A7EDDF2E47B7CF0097608D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 20A7EDCD2E47B7CD0097608D /* Severed */;
|
||||
target = 20A7EDCD2E47B7CD0097608D /* OpenAppLock */;
|
||||
targetProxy = 20A7EDDE2E47B7CF0097608D /* PBXContainerItemProxy */;
|
||||
};
|
||||
20A7EDE92E47B7CF0097608D /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 20A7EDCD2E47B7CD0097608D /* Severed */;
|
||||
target = 20A7EDCD2E47B7CD0097608D /* OpenAppLock */;
|
||||
targetProxy = 20A7EDE82E47B7CF0097608D /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
@@ -390,7 +390,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Severed/Severed.entitlements;
|
||||
CODE_SIGN_ENTITLEMENTS = OpenAppLock/OpenAppLock.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
||||
@@ -414,7 +414,7 @@
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.Severed;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLock;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = auto;
|
||||
@@ -433,7 +433,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Severed/Severed.entitlements;
|
||||
CODE_SIGN_ENTITLEMENTS = OpenAppLock/OpenAppLock.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 4A9XHUS87Q;
|
||||
@@ -457,7 +457,7 @@
|
||||
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.Severed;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLock;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
REGISTER_APP_GROUPS = YES;
|
||||
SDKROOT = auto;
|
||||
@@ -482,7 +482,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
@@ -491,7 +491,7 @@
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
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;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -507,7 +507,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredTests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
@@ -516,7 +516,7 @@
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
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;
|
||||
};
|
||||
name = Release;
|
||||
@@ -531,7 +531,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
@@ -540,7 +540,7 @@
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = Severed;
|
||||
TEST_TARGET_NAME = OpenAppLock;
|
||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||
};
|
||||
name = Debug;
|
||||
@@ -555,7 +555,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 26.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 15.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.SeveredUITests;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.bchen.OpenAppLockUITests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = NO;
|
||||
@@ -564,7 +564,7 @@
|
||||
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
TEST_TARGET_NAME = Severed;
|
||||
TEST_TARGET_NAME = OpenAppLock;
|
||||
XROS_DEPLOYMENT_TARGET = 26.0;
|
||||
};
|
||||
name = Release;
|
||||
@@ -572,7 +572,7 @@
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "Severed" */ = {
|
||||
20A7EDC92E47B7CD0097608D /* Build configuration list for PBXProject "OpenAppLock" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
20A7EDEF2E47B7CF0097608D /* Debug */,
|
||||
@@ -581,7 +581,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "Severed" */ = {
|
||||
20A7EDF12E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLock" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
20A7EDF22E47B7CF0097608D /* Debug */,
|
||||
@@ -590,7 +590,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredTests" */ = {
|
||||
20A7EDF42E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
20A7EDF52E47B7CF0097608D /* Debug */,
|
||||
@@ -599,7 +599,7 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "SeveredUITests" */ = {
|
||||
20A7EDF72E47B7CF0097608D /* Build configuration list for PBXNativeTarget "OpenAppLockUITests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
20A7EDF82E47B7CF0097608D /* Debug */,
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RulePolicy.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleSchedule.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleStatus.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// BlockingRule.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleDraft.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleKind.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RulePreset.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// Weekday.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// SeveredApp.swift
|
||||
// Severed
|
||||
// OpenAppLockApp.swift
|
||||
// OpenAppLock
|
||||
//
|
||||
// Created by Brendan Chen on 2025.08.09.
|
||||
//
|
||||
@@ -9,7 +9,7 @@ import SwiftData
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
struct SeveredApp: App {
|
||||
struct OpenAppLockApp: App {
|
||||
private let container: ModelContainer
|
||||
@State private var authorization: ScreenTimeAuthorization
|
||||
@State private var enforcer: RuleEnforcer
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// LaunchConfiguration.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleEnforcer.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// SampleRules.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// ScreenTimeAuthorization.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import FamilyControls
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// ShieldController.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import FamilyControls
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// AppsHomeView.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// DayOfWeekPicker.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// OnboardingView.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@@ -39,7 +39,7 @@ struct OnboardingView: View {
|
||||
Image(systemName: "scissors")
|
||||
.font(.system(size: 56))
|
||||
.foregroundStyle(.tint)
|
||||
Text("Severed")
|
||||
Text("OpenAppLock")
|
||||
.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.")
|
||||
.foregroundStyle(.secondary)
|
||||
@@ -56,13 +56,13 @@ struct OnboardingView: View {
|
||||
.font(.title.bold())
|
||||
.multilineTextAlignment(.center)
|
||||
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("gearshape.fill", "You can change this anytime in Settings.")
|
||||
}
|
||||
if authorization.status == .denied || authorization.lastRequestFailed {
|
||||
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)
|
||||
.foregroundStyle(.red)
|
||||
.multilineTextAlignment(.center)
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RootView.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// AppSelectionSheet.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import FamilyControls
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// NewRuleSheet.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleDetailSheet.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftData
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleEditorView.swift
|
||||
// Severed
|
||||
// OpenAppLock
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@@ -1,19 +1,19 @@
|
||||
//
|
||||
// LaunchSupportTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("Launch configuration parsing")
|
||||
struct LaunchConfigurationTests {
|
||||
@Test("Defaults are production settings")
|
||||
func defaults() {
|
||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp"])
|
||||
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp"])
|
||||
#expect(!config.isUITesting)
|
||||
#expect(config.onboardingCompleted == nil)
|
||||
#expect(config.seedScenario == nil)
|
||||
@@ -22,7 +22,7 @@ struct LaunchConfigurationTests {
|
||||
@Test("UI testing flags are recognized")
|
||||
func uiTestingFlags() {
|
||||
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.onboardingCompleted == true)
|
||||
@@ -31,13 +31,13 @@ struct LaunchConfigurationTests {
|
||||
|
||||
@Test("Onboarding can be forced on")
|
||||
func onboardingRequired() {
|
||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp", "-onboarding-required"])
|
||||
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp", "-onboarding-required"])
|
||||
#expect(config.onboardingCompleted == false)
|
||||
}
|
||||
|
||||
@Test("Unknown seed scenarios are ignored")
|
||||
func unknownScenario() {
|
||||
let config = LaunchConfiguration.parse(arguments: ["SeveredApp", "-seed-scenario=nope"])
|
||||
let config = LaunchConfiguration.parse(arguments: ["OpenAppLockApp", "-seed-scenario=nope"])
|
||||
#expect(config.seedScenario == nil)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// RuleEnforcerTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("Rule enforcement → shields")
|
||||
@@ -1,13 +1,13 @@
|
||||
//
|
||||
// RuleModelTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("BlockingRule model & persistence")
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// RulePolicyTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("Hard Mode policy")
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// RuleScheduleTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("RuleSchedule window math")
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// RuleStatusTests.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Testing
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
@MainActor
|
||||
@Suite("Rule status derivation and labels")
|
||||
@@ -1,11 +1,11 @@
|
||||
//
|
||||
// TestSupport.swift
|
||||
// SeveredTests
|
||||
// OpenAppLockTests
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import Severed
|
||||
@testable import OpenAppLock
|
||||
|
||||
/// Fixed UTC gregorian calendar so schedule math is deterministic regardless
|
||||
/// of the machine running the tests.
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// OnboardingUITests.swift
|
||||
// SeveredUITests
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@@ -11,10 +11,10 @@ final class OnboardingUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testOnboardingWalksThroughPermissionToHome() throws {
|
||||
let app = XCUIApplication.launchSevered(onboardingCompleted: false)
|
||||
let app = XCUIApplication.launchOpenAppLock(onboardingCompleted: false)
|
||||
|
||||
// Welcome step.
|
||||
app.staticTexts["Severed"].waitToAppear()
|
||||
app.staticTexts["OpenAppLock"].waitToAppear()
|
||||
app.buttons["onboardingContinueButton"].waitToAppear().tap()
|
||||
|
||||
// Permission step: granting (mocked) lands on the Apps home screen.
|
||||
@@ -24,7 +24,7 @@ final class OnboardingUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCompletedOnboardingIsSkipped() throws {
|
||||
let app = XCUIApplication.launchSevered(onboardingCompleted: true)
|
||||
let app = XCUIApplication.launchOpenAppLock(onboardingCompleted: true)
|
||||
app.buttons["newRuleButton"].waitToAppear()
|
||||
XCTAssertFalse(app.buttons["onboardingContinueButton"].exists)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleCreationUITests.swift
|
||||
// SeveredUITests
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@@ -11,7 +11,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreateScheduleRuleFromTypeCard() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.element("emptyRulesCard").waitToAppear()
|
||||
|
||||
app.buttons["newRuleButton"].tap()
|
||||
@@ -32,7 +32,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreateRuleFromPreset() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
|
||||
app.buttons["preset-work-time"].waitToAppear().tap()
|
||||
@@ -43,7 +43,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRenameRuleInEditor() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||
|
||||
@@ -62,7 +62,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDayTogglesFillRowAndHaveLargeTapTargets() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||
|
||||
@@ -80,7 +80,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDayTogglesUpdateSummary() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||
|
||||
@@ -92,7 +92,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreateTimeLimitRule() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-timeLimit"].waitToAppear().tap()
|
||||
|
||||
@@ -108,7 +108,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testAdultContentToggleFlowsToDetail() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||
|
||||
@@ -125,14 +125,14 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testAdultContentDefaultsToAllowed() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
||||
let row = app.element("detailRow-Adult websites").waitToAppear()
|
||||
XCTAssertTrue(row.label.contains("Allowed"), "Got: \(row.label)")
|
||||
}
|
||||
|
||||
func testEditorSupportsNativeSwipeBack() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
app.buttons["ruleKind-schedule"].waitToAppear().tap()
|
||||
app.staticTexts["ruleEditorTitle"].waitToAppear()
|
||||
@@ -147,7 +147,7 @@ final class RuleCreationUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testNewRuleSheetShowsTypesAndPresets() throws {
|
||||
let app = XCUIApplication.launchSevered()
|
||||
let app = XCUIApplication.launchOpenAppLock()
|
||||
app.buttons["newRuleButton"].waitToAppear().tap()
|
||||
|
||||
app.buttons["ruleKind-schedule"].waitToAppear()
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// RuleManagementUITests.swift
|
||||
// SeveredUITests
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@@ -13,7 +13,7 @@ final class RuleManagementUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDetailShowsLiveStatusAndFacts() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
|
||||
app.buttons["ruleCard-Work Time"].waitToAppear().tap()
|
||||
|
||||
@@ -29,7 +29,7 @@ final class RuleManagementUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testEditRuleTogglesHardModeOn() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
|
||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||
@@ -43,7 +43,7 @@ final class RuleManagementUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDisableRule() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
|
||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||
@@ -64,7 +64,7 @@ final class RuleManagementUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testDeleteRuleRemovesCard() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
|
||||
app.buttons["ruleCard-Sleep"].waitToAppear().tap()
|
||||
app.buttons["editRuleButton"].waitToAppear().tap()
|
||||
@@ -82,7 +82,7 @@ final class RuleManagementUITests: XCTestCase {
|
||||
}
|
||||
|
||||
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.
|
||||
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
||||
@@ -100,7 +100,7 @@ final class HardModeUITests: XCTestCase {
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -111,7 +111,7 @@ final class HardModeUITests: XCTestCase {
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -126,7 +126,7 @@ final class HardModeUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSoftRuleUnblockOfferedButHardRuleRefused() throws {
|
||||
let app = XCUIApplication.launchSevered(seedScenario: "standard")
|
||||
let app = XCUIApplication.launchOpenAppLock(seedScenario: "standard")
|
||||
|
||||
app.buttons["blockedTile-Work Time"].waitToAppear().tap()
|
||||
// Soft rule: the confirmation dialog appears instead of the refusal alert.
|
||||
@@ -1,6 +1,6 @@
|
||||
//
|
||||
// UITestSupport.swift
|
||||
// SeveredUITests
|
||||
// OpenAppLockUITests
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@@ -8,7 +8,7 @@ import XCTest
|
||||
extension XCUIApplication {
|
||||
/// Launches the app in UI-testing mode: in-memory storage, mocked Screen
|
||||
/// Time authorization, and no shield side effects.
|
||||
static func launchSevered(
|
||||
static func launchOpenAppLock(
|
||||
onboardingCompleted: Bool = true,
|
||||
seedScenario: String? = nil
|
||||
) -> 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
|
||||
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.
|
||||
for OpenAppLock.
|
||||
|
||||
---
|
||||
|
||||
@@ -126,7 +126,7 @@ Presented from "+ New". Full-height sheet, scrollable.
|
||||
and a circular "+" button bottom-right. Tapping anywhere opens the
|
||||
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` +
|
||||
> `navigationDestination(item:)`), so the system push animation and
|
||||
> 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.
|
||||
4. **Hard Mode** `⚡PRO` badge — subtitle "No unblocks allowed"; trailing
|
||||
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
|
||||
toggle. Maps to Screen Time's web-content filter
|
||||
(`ManagedSettingsStore.webContent.blockedByFilter = .auto(...)`), applied
|
||||
@@ -208,7 +208,7 @@ Full-height sheet:
|
||||
> 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
|
||||
> category/app/website UI and returns opaque tokens. **v1 of OpenAppLock should
|
||||
> embed `FamilyActivityPicker`** instead of cloning Opal's custom picker, and
|
||||
> 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
|
||||
|
||||
@@ -334,7 +334,7 @@ enum SelectionMode: String, Codable { case block, allowOnly }
|
||||
|
||||
## 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
|
||||
flows, and the accessibility identifiers intact. Sections 1–5 remain as the
|
||||
reference for *what* the feature does; presentation now maps as follows:
|
||||
|
||||
Reference in New Issue
Block a user