From ab84eef849904c31e88d552da7a44e1efa616269 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Wed, 17 Jun 2026 23:35:28 -0400 Subject: [PATCH 1/2] feat: add iPad sidebar layout and iPad CI test matrix The post-onboarding shell now adapts to the horizontal size class instead of stretching the iPhone tab bar across the iPad canvas: a bottom TabView in compact width (iPhone, iPad multitasking) and a left NavigationSplitView sidebar in regular width (full-screen iPad), per Apple's HIG. The three sections (Home / Rules / Settings) and their views are reused verbatim in both layouts. - AppSection enum is the single source of truth for tab items and sidebar rows; MainLayout.resolve picks the chrome from horizontalSizeClass. - New MainView shell owns the enforcement lifecycle (lifted off MainTabView) so it runs regardless of layout; MainSidebarView is the regular-width view. - CI: test.yml now runs the suite on both iPhone 17 Pro and iPad Pro 13-inch (M4) via a fail-fast:false matrix with per-leg artifacts. Tests (TDD red -> green): new AppSection / MainLayout unit suites and an idiom-aware NavigationChromeUITests. Existing UI helpers made sidebar-aware (goTo*Tab / waitForMainUI); iPhone-presentation-specific flows adapted to iPad (nav BackButton instead of window-edge swipe, scroll the shorter form sheet, gate window-relative width to iPhone) or skipped on iPad with a documented reason. Suite is green on both devices (iPad 231 pass/1 skip, iPhone 232 pass). Docs: spec section 6 and AGENTS.md updated for the adaptive shell. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01SZ9eNtWxt96J66dG3amUSU --- .github/workflows/test.yml | 21 ++++-- AGENTS.md | 15 +++- Docs/AGENT_RULES_FEATURE_SPEC.md | 24 ++++-- OpenAppLock/Views/MainSidebarView.swift | 51 +++++++++++++ OpenAppLock/Views/MainTabView.swift | 60 +++------------ OpenAppLock/Views/MainView.swift | 75 +++++++++++++++++++ OpenAppLock/Views/Navigation/AppSection.swift | 35 +++++++++ OpenAppLock/Views/Navigation/MainLayout.swift | 22 ++++++ OpenAppLock/Views/RootView.swift | 2 +- OpenAppLockTests/AppSectionTests.swift | 45 +++++++++++ OpenAppLockTests/MainLayoutTests.swift | 30 ++++++++ OpenAppLockUITests/AppListUITests.swift | 10 ++- .../NavigationChromeUITests.swift | 53 +++++++++++++ OpenAppLockUITests/OnboardingUITests.swift | 11 ++- OpenAppLockUITests/RuleCreationUITests.swift | 25 +++++-- .../RuleManagementUITests.swift | 8 +- OpenAppLockUITests/UITestSupport.swift | 41 ++++++++-- 17 files changed, 439 insertions(+), 89 deletions(-) create mode 100644 OpenAppLock/Views/MainSidebarView.swift create mode 100644 OpenAppLock/Views/MainView.swift create mode 100644 OpenAppLock/Views/Navigation/AppSection.swift create mode 100644 OpenAppLock/Views/Navigation/MainLayout.swift create mode 100644 OpenAppLockTests/AppSectionTests.swift create mode 100644 OpenAppLockTests/MainLayoutTests.swift create mode 100644 OpenAppLockUITests/NavigationChromeUITests.swift diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4f451c..15e6891 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,10 +7,21 @@ on: jobs: test: - name: Run all tests on iOS + name: Test on ${{ matrix.device }} runs-on: macos-26 timeout-minutes: 30 + strategy: + # Run every device leg to completion: an iPad failure shouldn't hide the + # iPhone result (or vice versa). + fail-fast: false + matrix: + include: + - device: iPhone 17 Pro + slug: iphone + - device: iPad Pro 13-inch (M4) + slug: ipad + steps: - name: Checkout current repository uses: actions/checkout@v4 @@ -24,7 +35,7 @@ jobs: xcodebuild build-for-testing \ -project OpenAppLock.xcodeproj \ -scheme OpenAppLock \ - -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -destination 'platform=iOS Simulator,name=${{ matrix.device }}' \ -xcconfig Config/CI.xcconfig - name: Run tests @@ -34,7 +45,7 @@ jobs: xcodebuild test-without-building \ -project OpenAppLock.xcodeproj \ -scheme OpenAppLock \ - -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \ + -destination 'platform=iOS Simulator,name=${{ matrix.device }}' \ -resultBundlePath ./output/test-result.xcresult \ -xcconfig Config/CI.xcconfig @@ -45,11 +56,11 @@ jobs: - name: Upload test result uses: actions/upload-artifact@v4 with: - name: test-result + name: test-result-${{ matrix.slug }} path: output/test-result.zip - name: Check for test failure if: steps.tests.outcome == 'failure' run: | - echo "Tests failed. Check the uploaded artifacts for details." + echo "Tests failed on ${{ matrix.device }}. Check the uploaded artifacts for details." exit 1 diff --git a/AGENTS.md b/AGENTS.md index cc72984..05a9639 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,8 +82,9 @@ off-limits to agent edits. - Shields: one `ManagedSettingsStore` per rule (`rule-`), tracked in UserDefaults for stray cleanup. `blockAdultContent` engages `webContent.blockedByFilter = .auto()` alongside the shield. -- `RuleEnforcer.refresh` is the only place shields change; the home view runs - it on rule changes and a 30s loop while visible. +- `RuleEnforcer.refresh` is the only place shields change; the post-onboarding + shell (`MainView`) runs it on rule changes and a 30s loop while the app is open, + regardless of the active layout (compact `TabView` vs regular-width sidebar). ## Build & test @@ -155,7 +156,8 @@ them): `newRuleButton`, `ruleCard-`, `ruleStatus-`, `maxOpensStepper(+Value)`, `commitRuleButton`, `doneButton`, `toggleEnabledButton`, `deleteRuleButton`, `closeDetailButton`, `detailRuleName`, `detailStatusLabel`, `detailRow-